Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions docs/integrations/deepseek-harness-control-plane-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ Status: implemented in this repository as the optional

The package embeds LoopX into a visible DeepSeek Harness (DSH) Session without
moving execution authority out of DSH or durable control-plane authority out of
LoopX. It ships three cooperating surfaces:
LoopX. It ships four cooperating surfaces:

- `/loopx-init`, which installs or repairs the LoopX CLI and DSH workflow
skills;
- a passive same-session Driver, which may queue one LoopX continuation only
after the exact Session invokes the installed `loopx` skill;
- a loopback-only GoalBar Host and web Client, which show one exact bound
Goal/Agent lane and expose guarded Start and Pause actions.
Goal/Agent lane and expose guarded Start and Pause actions;
- a web-Client personal workspace panel: one global **LoopX** sidebar entry
whose central panel embeds the already-running `loopx dashboard` workspace
on loopback, presenting LoopX's own UI without reading or duplicating
control-plane state.

The older [`deepseek-harness` connector](deepseek-harness-connector.md) remains
a separate headless Turn adapter. The native plugin uses the
Expand All @@ -25,7 +29,7 @@ contract, not as competing implementations to collapse into one transport:

| Path | Primary use | Session and wakeup owner | Frontend |
| --- | --- | --- | --- |
| `dsh-loopx-plugin` | Person-driven work in a DSH Session | DSH Session; optional same-session Driver after an exact `loopx` skill invocation | DSH Web/Desktop GoalBar and native skills |
| `dsh-loopx-plugin` | Person-driven work in a DSH Session | DSH Session; optional same-session Driver after an exact `loopx` skill invocation | DSH Web/Desktop GoalBar, personal workspace panel and native skills |
| LoopX Turn on DSH | Headless, bounded, quota-governed work selected without a person present | LoopX Turn and the outer scheduler | LoopX App/CLI executor readback, artifacts and Todo settlement |

Do not duplicate the LoopX control plane inside DSH. Both routes must converge
Expand Down
38 changes: 36 additions & 2 deletions packages/dsh-loopx-plugin/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# LoopX for DeepSeek Harness

`dsh-loopx-plugin` is one independently versioned DSH package with three
separate Loader rows:
`dsh-loopx-plugin` is one independently versioned DSH package with four
cooperating surfaces (three Loader rows and one Client panel):

- the init row automatically installs or upgrades the LoopX CLI, installs the
packaged workflow skills into `$DSH_AGENTS_HOME/skills` (default
Expand All @@ -15,6 +15,11 @@ separate Loader rows:
`/api/loopx.goalbar` route required by DSH 0.1.5. Its web Client adds a compact
GoalBar between DSH's native GoalBar and Queue dock rows, visible only for one
exact live `(goalId, loopxAgentId)` binding.
- the web Client also adds one global sidebar entry, **LoopX**, whose central
panel embeds the already-running personal workspace from
`loopx dashboard` on loopback. The panel is presentation only: it starts no
service, reads no LoopX state itself, and renders a recovery notice plus
retry when that loopback service is absent.

Installing the plugin and starting DSH load and prepare these capabilities;
neither creates a binding nor activates the Driver. The GoalBar
Expand Down Expand Up @@ -432,6 +437,35 @@ a change token, not authorization or a compare-and-swap guard. Installing the
package grants no model tool authority, does not create or repair bindings, and
does not change LoopX core state by itself.

## Personal workspace panel

The web Client registers one global sidebar entry, **LoopX**, and the matching
central panel. The panel embeds the personal workspace served by
`loopx dashboard` at its default loopback address
(`http://127.0.0.1:8767/chat/`) in an iframe. Start the workspace first:

```bash
loopx dashboard # default loopback address; opens the browser unless --no-open
```

The panel then shows the same workspace in DSH. It is navigation, not a second
control plane: the iframe loads LoopX's own UI and LoopX remains the authority
for Goals, Todos, quotas, and bindings. The iframe shares no state with the DSH
Session, and the plugin adds no LoopX model tool, credential, or registry read
for it.

When the loopback service is absent, the panel keeps the frame mounted, states
the recovery command, and offers **Retry**; starting `loopx dashboard` and
retrying recovers without reloading DSH. Removing or disabling the plugin
removes the sidebar entry and panel without changing LoopX state.

Current limitation: the address is the dashboard's default loopback address.
A workspace started with an explicit other `--port` is not discovered by the
panel; keep the default port, or open that URL in DSH's own browser panel.
Hosts that do not declare the global sidebar or keyed main slots (DSH releases
before the 0.2.1 line) keep the GoalBar and skills but show no panel: the
injection simply waits for a host that declares those slots.

## Automatic initialization and `/loopx-init` repair

When DSH loads the plugin, the init row runs the same typed initialization
Expand Down
2 changes: 2 additions & 0 deletions packages/dsh-loopx-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@
"@deepseek-ai/dsh-client-connection": "0.2.1-rc.2",
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
"@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
"@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.2",
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
"@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.2",
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
"@deepseek-ai/dsh-commands": "0.2.1-rc.2",
"@deepseek-ai/dsh-llm": "0.2.1-rc.2",
Expand Down
6 changes: 6 additions & 0 deletions packages/dsh-loopx-plugin/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 36 additions & 4 deletions packages/dsh-loopx-plugin/smoke/dsh-client-artifact-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const packedStaticEntries = new Set([
'package/lib/observer.js',
'package/lib/types/cli.d.ts',
'package/lib/types/client/LoopXGoalBar.d.ts',
'package/lib/types/client/dashboard.d.ts',
'package/lib/types/client/index.d.ts',
'package/lib/types/client/locale.d.ts',
'package/lib/types/client/rpc.d.ts',
Expand Down Expand Up @@ -300,11 +301,32 @@ function createClientApplyHarness() {
},
slots: {
inject(name, install) {
assert.equal(name, 'conversation.input.dock')
assert(
['sidebar.panellist', 'main', 'conversation.input.dock'].includes(name),
`unexpected slot injection ${name}`,
)
const dispose = install()
if (typeof dispose === 'function') effects.push(dispose)
},
register(config, component) {
if (config.name === 'sidebar.panellist') {
assert.equal(config.id, 'loopx-dashboard')
assert.equal(config.order, 7)
assert.equal(typeof config.label, 'function')
assert.equal(config.label(), 'LoopX')
assert.equal(typeof component, 'function')
assert.equal(slots.has('sidebar:loopx-dashboard'), false)
slots.add('sidebar:loopx-dashboard')
return () => slots.delete('sidebar:loopx-dashboard')
}
if (config.name === 'main') {
assert.equal(config.key, 'loopx-dashboard')
assert.equal(config.locale, 'loopx.dashboard')
assert.equal(typeof component, 'function')
assert.equal(slots.has('main:loopx-dashboard'), false)
slots.add('main:loopx-dashboard')
return () => slots.delete('main:loopx-dashboard')
}
assert.equal(config.name, 'conversation.input.dock')
assert.equal(config.id, 'loopx-goal')
assert.equal(config.order, 15)
Expand Down Expand Up @@ -339,6 +361,7 @@ async function assertClientArtifact(root) {
assert(!/sourceMappingURL|\.(?:ts|tsx)\.map/iu.test(source), 'client artifact contains a source map')
assert(!/(?:^|["'])node:/mu.test(source), 'client artifact contains a Node builtin')
assert(!/(?:typert|remote|EventSource)/u.test(source), 'client artifact contains a forbidden transport')
assert(source.includes('http://127.0.0.1:8767/chat/'), 'client artifact lacks the loopback workspace address')
assert(!/\/(?:Users|home|private|workspace)\//u.test(source), 'client artifact contains an absolute path')
assert(!/[A-Za-z]:\\(?:Users|home|workspace)\\/u.test(source), 'client artifact contains a Windows absolute path')

Expand Down Expand Up @@ -373,9 +396,14 @@ async function assertClientArtifact(root) {
assert.deepEqual(first.labels, [
'dsh-loopx-plugin CSS ownership',
'dsh-loopx-plugin GoalBar locale',
'dsh-loopx-plugin dashboard locale',
])
assert.equal(first.locales.size, 2)
assert.deepEqual([...first.slots], [
'sidebar:loopx-dashboard',
'main:loopx-dashboard',
'loopx-goal',
])
assert.equal(first.locales.size, 1)
assert.deepEqual([...first.slots], ['loopx-goal'])
assert.equal(styles.length, 1)
assert.equal(styles[0].dataset.plugin, packageId)
assert.equal(styles[0].dataset.pluginCss, `${packageId}/goalbar.module.css`)
Expand All @@ -399,7 +427,11 @@ async function assertClientArtifact(root) {
const reapplied = createClientApplyHarness()
assert.equal(cachedExports.apply(reapplied.ctx), undefined)
assert.equal(styles.length, 1, 'cached-export reapply did not restore CSS')
assert.deepEqual([...reapplied.slots], ['loopx-goal'])
assert.deepEqual([...reapplied.slots], [
'sidebar:loopx-dashboard',
'main:loopx-dashboard',
'loopx-goal',
])
reapplied.dispose()
assert.equal(styles.length, 0, 'cached-export unload retained CSS')
removeOwnedStylesForHmr()
Expand Down
23 changes: 17 additions & 6 deletions packages/dsh-loopx-plugin/smoke/dsh-goalbar-runtime-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const packedStaticEntries = new Set([
'package/lib/observer.js',
'package/lib/types/cli.d.ts',
'package/lib/types/client/LoopXGoalBar.d.ts',
'package/lib/types/client/dashboard.d.ts',
'package/lib/types/client/index.d.ts',
'package/lib/types/client/locale.d.ts',
'package/lib/types/client/rpc.d.ts',
Expand Down Expand Up @@ -748,24 +749,34 @@ async function materializeServedClient(source) {
locale: { register: () => () => undefined },
slots: {
inject(name, install) {
assert.equal(name, 'conversation.input.dock')
assert(
['sidebar.panellist', 'main', 'conversation.input.dock'].includes(name),
`unexpected slot injection ${name}`,
)
const dispose = install()
if (typeof dispose === 'function') effects.push(dispose)
},
register(config, component) {
assert.equal(typeof component, 'function')
slots.set(config.id, config)
return () => slots.delete(config.id)
const key = config.id ?? `main:${config.key}`
assert.equal(slots.has(key), false, `duplicate slot ${key}`)
slots.set(key, config)
return () => slots.delete(key)
},
},
on: () => () => undefined,
}
client.apply(ctx)
const config = slots.get('loopx-goal')
assert.equal(config.order, 15)
assert.deepEqual([...slots].sort((a, b) => a[1].order - b[1].order).map(([id]) => id), [
'goal', 'loopx-goal', 'queue',
])
const sidebar = slots.get('loopx-dashboard')
assert.equal(sidebar.order, 7)
assert.equal(slots.get('main:loopx-dashboard').key, 'loopx-dashboard')
const dockOrder = [...slots.entries()]
.filter(([id]) => ['goal', 'loopx-goal', 'queue'].includes(id))
.sort((a, b) => a[1].order - b[1].order)
.map(([id]) => id)
assert.deepEqual(dockOrder, ['goal', 'loopx-goal', 'queue'])
assert.equal(config.inject('session-one').rpcSessionId, 'session-one')
assert.equal(config.inject('session-two').rpcSessionId, 'session-two')
return {
Expand Down
1 change: 1 addition & 0 deletions packages/dsh-loopx-plugin/smoke/dsh-profile-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const packedStaticEntries = new Set([
'package/lib/observer.js',
'package/lib/types/cli.d.ts',
'package/lib/types/client/LoopXGoalBar.d.ts',
'package/lib/types/client/dashboard.d.ts',
'package/lib/types/client/index.d.ts',
'package/lib/types/client/locale.d.ts',
'package/lib/types/client/rpc.d.ts',
Expand Down
Loading
Loading