@ljwei-stak/dsh-model-route-bot 0.66.3 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/bot-product.mjs +81 -5
- package/.dsh-plugin/client.js +17490 -1791
- package/README.i18n.yaml +4 -4
- package/README.md +409 -364
- package/README.zh.md +409 -364
- package/apps/integration-client/README.md +31 -7
- package/apps/integration-client/api.mjs +14 -3
- package/apps/integration-client/binding-editor.jsx +308 -109
- package/apps/integration-client/binding-model.mjs +262 -0
- package/apps/integration-client/binding.css +144 -0
- package/apps/integration-client/bot-model-status.mjs +18 -0
- package/apps/integration-client/bot-profile-identity.jsx +197 -0
- package/apps/integration-client/bot-profile.css +57 -0
- package/apps/integration-client/bot-profile.mjs +97 -0
- package/apps/integration-client/bot-settings-dialog.css +18 -0
- package/apps/integration-client/bot-settings-dialog.jsx +111 -0
- package/apps/integration-client/bot-thread-header-bridge.css +20 -0
- package/apps/integration-client/bot-thread-header.jsx +278 -0
- package/apps/integration-client/bot-threads-panel.jsx +378 -0
- package/apps/integration-client/bot-threads.css +169 -0
- package/apps/integration-client/chat-design.mjs +250 -0
- package/apps/integration-client/chat-ui.jsx +175 -0
- package/apps/integration-client/chat.css +477 -0
- package/apps/integration-client/composer-draft-binding.mjs +50 -0
- package/apps/integration-client/composer-draft-bound.jsx +14 -0
- package/apps/integration-client/composer-draft-host.jsx +142 -0
- package/apps/integration-client/composer-draft-session.jsx +287 -0
- package/apps/integration-client/composer-draft-witness.mjs +115 -0
- package/apps/integration-client/composer-draft.css +4 -0
- package/apps/integration-client/composer-draft.mjs +322 -0
- package/apps/integration-client/contact-attention.jsx +54 -0
- package/apps/integration-client/data-panel.jsx +439 -0
- package/apps/integration-client/data.css +69 -0
- package/apps/integration-client/desktop-bridge.mjs +1 -0
- package/apps/integration-client/desktop-http-socket.mjs +49 -0
- package/apps/integration-client/desktop-keyboard.mjs +32 -0
- package/apps/integration-client/desktop-pane.jsx +99 -30
- package/apps/integration-client/desktop-stream.mjs +13 -3
- package/apps/integration-client/desktop-tools.jsx +19 -0
- package/apps/integration-client/desktop-touch.mjs +27 -0
- package/apps/integration-client/desktop.css +41 -0
- package/apps/integration-client/independent-chat-controller.mjs +224 -0
- package/apps/integration-client/independent-chat-entry.jsx +61 -0
- package/apps/integration-client/independent-chat-navigation.mjs +21 -0
- package/apps/integration-client/independent-chat-page.jsx +148 -0
- package/apps/integration-client/independent-chat-rich.jsx +64 -0
- package/apps/integration-client/independent-chat-rich.mjs +30 -0
- package/apps/integration-client/independent-chat.css +73 -0
- package/apps/integration-client/index.jsx +24 -16
- package/apps/integration-client/library-panel.jsx +4 -1
- package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
- package/apps/integration-client/main-route-editor.jsx +94 -0
- package/apps/integration-client/main-route-model.mjs +100 -0
- package/apps/integration-client/model-picker.css +37 -0
- package/apps/integration-client/model-picker.jsx +83 -0
- package/apps/integration-client/native-activity.css +141 -0
- package/apps/integration-client/native-activity.jsx +276 -0
- package/apps/integration-client/native-activity.mjs +384 -0
- package/apps/integration-client/notifications-panel.jsx +186 -0
- package/apps/integration-client/notifications.css +126 -0
- package/apps/integration-client/pairing-qr.jsx +79 -0
- package/apps/integration-client/panels.jsx +126 -115
- package/apps/integration-client/product-slots.jsx +46 -0
- package/apps/integration-client/routine-recorder.mjs +23 -2
- package/apps/integration-client/secure-pairing.jsx +84 -0
- package/apps/integration-client/tasks-panel.jsx +32 -10
- package/apps/integration-client/ui-icons.jsx +17 -0
- package/apps/integration-client/usage-explorer.jsx +43 -0
- package/apps/integration-client/usage-report.mjs +71 -0
- package/apps/integration-client/usage.css +34 -0
- package/apps/integration-client/web-panel-model.mjs +140 -0
- package/apps/integration-client/web-panel-page.jsx +221 -0
- package/apps/integration-client/web-panel-pins-live.jsx +28 -0
- package/apps/integration-client/web-panel.css +139 -0
- package/apps/integration-client/workspace-model.mjs +67 -0
- package/apps/integration-client/workspace-navigation.mjs +56 -0
- package/apps/integration-client/workspace-ui.jsx +173 -0
- package/apps/integration-client/workspace.css +332 -0
- package/modules/bot-runtime/SOURCE.json +176 -50
- package/modules/bot-runtime/index.mjs +169 -13
- package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
- package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
- package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
- package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
- package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
- package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
- package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
- package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
- package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
- package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
- package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
- package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
- package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
- package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
- package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
- package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
- package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
- package/modules/bot-runtime/read-model.mjs +19 -4
- package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
- package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
- package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
- package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
- package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
- package/modules/independent-runtime/index.mjs +152 -62
- package/modules/independent-runtime/remote-tools.mjs +8 -5
- package/modules/openmaus-protocol/NOTICE +10 -0
- package/modules/openmaus-protocol/README.md +15 -0
- package/modules/openmaus-protocol/SOURCE.json +68 -0
- package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
- package/modules/openmaus-protocol/index.mjs +1 -0
- package/modules/openmaus-protocol/transport.mjs +77 -0
- package/modules/openmaus-protocol/vendor/LICENSE +202 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
- package/package.json +20 -6
- package/packages/bot-acp/README.md +156 -0
- package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-acp/licenses/NOTICE.md +27 -0
- package/packages/bot-acp/src/acp-adapter.mjs +795 -0
- package/packages/bot-acp/src/acp-host.mjs +148 -0
- package/packages/bot-acp/src/native-launch.mjs +121 -0
- package/packages/bot-acp/src/permission-policy.mjs +283 -0
- package/packages/bot-acp/src/rpc-codec.mjs +265 -0
- package/packages/bot-acp/src/session-core.mjs +581 -0
- package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
- package/packages/bot-engines/README.md +281 -0
- package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-engines/licenses/NOTICE.md +32 -0
- package/packages/bot-engines/src/claude-engine.mjs +772 -0
- package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
- package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
- package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
- package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
- package/packages/bot-engines/src/claude-protocol.mjs +177 -0
- package/packages/bot-engines/src/driver-catalog.mjs +334 -0
- package/packages/bot-engines/src/host-loop-request.mjs +95 -0
- package/packages/bot-engines/src/host-registrations.mjs +105 -0
- package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
- package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
- package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
- package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
- package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
- package/packages/cli-runtime/src/native-peer.ps1 +51 -3
- package/packages/cli-runtime/src/native-process.mjs +77 -14
- package/packages/model-router-plugin/README.md +3 -3
- package/packages/model-router-plugin/package.json +1 -1
- package/packages/orchestration/src/binding-product.mjs +19 -0
- package/packages/orchestration/src/bot-data-actions.mjs +45 -0
- package/packages/orchestration/src/bot-data-chart.mjs +109 -0
- package/packages/orchestration/src/bot-data-context.mjs +44 -0
- package/packages/orchestration/src/bot-data-engine.mjs +809 -0
- package/packages/orchestration/src/bot-data-io.mjs +186 -0
- package/packages/orchestration/src/bot-data-sql.mjs +195 -0
- package/packages/orchestration/src/bot-data-surface.mjs +144 -0
- package/packages/orchestration/src/bot-data-tools.mjs +68 -0
- package/packages/orchestration/src/bot-data-worker.mjs +228 -0
- package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
- package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
- package/packages/orchestration/src/bot-loop.mjs +152 -19
- package/packages/orchestration/src/bot-notifications.mjs +159 -0
- package/packages/orchestration/src/bot-policy.mjs +43 -4
- package/packages/orchestration/src/bot-profile.mjs +171 -0
- package/packages/orchestration/src/bot-routine.mjs +4 -4
- package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
- package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
- package/packages/orchestration/src/bot-serving.mjs +164 -0
- package/packages/orchestration/src/bot-session-store.mjs +105 -9
- package/packages/orchestration/src/bot-settings.mjs +50 -11
- package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
- package/packages/orchestration/src/bot-thread-host.mjs +187 -0
- package/packages/orchestration/src/bot-threads.mjs +790 -0
- package/packages/orchestration/src/data-product-access.mjs +37 -0
- package/packages/orchestration/src/data-tool-contract.mjs +291 -0
- package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
- package/packages/orchestration/src/desktop-proxy.mjs +311 -0
- package/packages/orchestration/src/desktop-sessions.mjs +43 -9
- package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
- package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
- package/packages/orchestration/src/independent-chat-page.mjs +39 -0
- package/packages/orchestration/src/independent-chat-service.mjs +308 -0
- package/packages/orchestration/src/independent-context-state.mjs +44 -0
- package/packages/orchestration/src/independent-message-wire.mjs +84 -0
- package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
- package/packages/orchestration/src/independent-tool-families.mjs +73 -0
- package/packages/orchestration/src/independent-tool-host.mjs +344 -0
- package/packages/orchestration/src/main-bot-grant.mjs +15 -0
- package/packages/orchestration/src/main-model-routes.mjs +208 -0
- package/packages/orchestration/src/model-accounting.mjs +99 -35
- package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
- package/packages/orchestration/src/model-loop-frame.mjs +179 -0
- package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
- package/packages/orchestration/src/model-request-bounds.mjs +111 -0
- package/packages/orchestration/src/native-activity-contract.mjs +491 -0
- package/packages/orchestration/src/native-activity-service.mjs +102 -0
- package/packages/orchestration/src/native-activity-store.mjs +249 -0
- package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
- package/packages/orchestration/src/native-data-tools.mjs +100 -0
- package/packages/orchestration/src/native-remote-actions.mjs +742 -0
- package/packages/orchestration/src/native-team-tools.mjs +73 -0
- package/packages/orchestration/src/pairing-invite.mjs +37 -0
- package/packages/orchestration/src/product-composition.mjs +1559 -1001
- package/packages/orchestration/src/remote-machines.mjs +82 -14
- package/packages/orchestration/src/routine-execution.mjs +5 -3
- package/packages/orchestration/src/routine-session.mjs +19 -13
- package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
- package/packages/orchestration/src/task-journal.mjs +29 -0
- package/packages/orchestration/src/task-product.mjs +17 -0
- package/packages/orchestration/src/team-assignment-store.mjs +120 -0
- package/packages/orchestration/src/team-bridge.mjs +282 -0
- package/packages/orchestration/src/team-scheduler.mjs +83 -0
- package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
- package/packages/orchestration/src/team-tool-contract.mjs +284 -0
- package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
- package/packages/orchestration/src/web-panel-actions.mjs +103 -0
- package/packages/orchestration/src/web-panel-framing.mjs +227 -0
- package/packages/orchestration/src/web-panel-store.mjs +206 -0
- package/remote-computer-agent/README.md +39 -29
- package/remote-computer-agent/build.mjs +3 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
- package/remote-computer-agent/dist/cli.mjs +1688 -168
- package/remote-computer-agent/dist/cli.mjs.map +4 -4
- package/remote-computer-agent/dist/index.mjs +1579 -122
- package/remote-computer-agent/dist/index.mjs.map +4 -4
- package/remote-computer-agent/dist/native-peer.ps1 +51 -3
- package/remote-computer-agent/package-lock.json +308 -2
- package/remote-computer-agent/package.json +2 -1
- package/remote-computer-agent/pair-device.ps1 +20 -0
- package/remote-computer-agent/setup.ps1 +38 -16
- package/remote-computer-agent/src/cli.ts +24 -3
- package/remote-computer-agent/src/desktop-session.ts +11 -3
- package/remote-computer-agent/src/e2e-server.ts +81 -0
- package/remote-computer-agent/src/pairing-page.ts +35 -0
- package/remote-computer-agent/src/protocol.ts +25 -25
- package/remote-computer-agent/src/secure-pairing.ts +105 -0
- package/remote-computer-agent/src/store.ts +20 -5
- package/remote-computer-agent/src/transport.ts +39 -15
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Pure Claude Code stream-json protocol helpers for the conversation-session adapter.
|
|
2
|
+
//
|
|
3
|
+
// Adapted from OpenMausBot server/drivers/claude.ts at pin
|
|
4
|
+
// 1bc4749aa8307d7d6998bf86de4ef6478460dd93 (Apache-2.0); see licenses/NOTICE.md.
|
|
5
|
+
// These functions are side-effect free so they can be tested without any child process.
|
|
6
|
+
|
|
7
|
+
/** Turn text bounds mirror the target's CLI turn validation. */
|
|
8
|
+
export function validTurnText(text) {
|
|
9
|
+
return typeof text === 'string' && text.trim() !== '' && !text.includes('\0') && Buffer.byteLength(text) <= 64_000
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A stdin user message. The prompt rides stdin, never argv. */
|
|
13
|
+
export function claudeUserMessage(text) {
|
|
14
|
+
return { type: 'user', message: { role: 'user', content: text } }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const firstText = content => {
|
|
18
|
+
if (typeof content === 'string') return content
|
|
19
|
+
if (!Array.isArray(content)) return ''
|
|
20
|
+
return content.filter(block => block?.type === 'text' && typeof block.text === 'string').map(block => block.text).join('')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** What an error `result` says failed: its errors list, else its result text, else its kind. */
|
|
24
|
+
export function claudeResultError(frame) {
|
|
25
|
+
const errors = Array.isArray(frame.errors) ? frame.errors.filter(value => typeof value === 'string' && value.trim() !== '') : []
|
|
26
|
+
if (errors.length) return errors.join('; ')
|
|
27
|
+
if (typeof frame.result === 'string' && frame.result.trim()) return frame.result
|
|
28
|
+
return `Claude ended the turn with an error (${String((frame.subtype !== 'success' && frame.subtype) || frame.terminal_reason || 'unknown')}).`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A turn's cost from total_cost_usd, which is the process's running total: the growth
|
|
32
|
+
* since `previous`, or the whole figure when the start is unknown or the total went down.
|
|
33
|
+
* Anything the CLI did not report as a finite number is not a cost: it stays null. */
|
|
34
|
+
export function turnCostFromRunningTotal(total, previous) {
|
|
35
|
+
if (typeof total !== 'number' || !Number.isFinite(total)) return null
|
|
36
|
+
if (previous === null || total < previous) return total
|
|
37
|
+
return Number((total - previous).toFixed(10))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** total_cost_usd and per-model modelUsage [input, cache read, cache write, output] from a
|
|
41
|
+
* `result`; both cover the whole session, including what --resume restored. */
|
|
42
|
+
export function claudeCostSnapshot(total, modelUsage) {
|
|
43
|
+
if (typeof total !== 'number' || !Number.isFinite(total)) return null
|
|
44
|
+
const count = value => (typeof value === 'number' && Number.isFinite(value) ? value : 0)
|
|
45
|
+
const models = {}
|
|
46
|
+
if (modelUsage && typeof modelUsage === 'object' && !Array.isArray(modelUsage)) {
|
|
47
|
+
for (const [model, raw] of Object.entries(modelUsage)) {
|
|
48
|
+
if (!raw || typeof raw !== 'object') continue
|
|
49
|
+
models[model] = [count(raw.inputTokens), count(raw.cacheReadInputTokens), count(raw.cacheCreationInputTokens), count(raw.outputTokens)]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { total, models }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One native `result` frame. */
|
|
56
|
+
export function nativeTurnResult(frame) {
|
|
57
|
+
const usage = frame.usage && typeof frame.usage === 'object' ? frame.usage : null
|
|
58
|
+
return {
|
|
59
|
+
ok: frame.is_error !== true,
|
|
60
|
+
stopReason: frame.is_error === true
|
|
61
|
+
? frame.terminal_reason ?? (typeof frame.subtype === 'string' && frame.subtype !== 'success' ? frame.subtype : null) ?? frame.stop_reason ?? null
|
|
62
|
+
: frame.stop_reason ?? frame.terminal_reason ?? null,
|
|
63
|
+
cost: typeof frame.total_cost_usd === 'number' && Number.isFinite(frame.total_cost_usd) ? frame.total_cost_usd : null,
|
|
64
|
+
...(usage ? { usage: resultUsage(usage) } : {}),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** result.usage is per turn "in streaming-input sessions" even on a retained process;
|
|
69
|
+
* input components (fresh + cache read + cache write) are summed, cachedInput reported
|
|
70
|
+
* separately, and anything missing stays null instead of being invented. */
|
|
71
|
+
export function resultUsage(usage) {
|
|
72
|
+
const nonNegative = value => (typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null)
|
|
73
|
+
const input = nonNegative(usage.input_tokens)
|
|
74
|
+
const cacheRead = nonNegative(usage.cache_read_input_tokens)
|
|
75
|
+
const cacheWrite = nonNegative(usage.cache_creation_input_tokens)
|
|
76
|
+
const output = nonNegative(usage.output_tokens)
|
|
77
|
+
if (input === null && cacheRead === null && cacheWrite === null && output === null) return null
|
|
78
|
+
const parts = [input, cacheRead, cacheWrite].filter(value => value !== null)
|
|
79
|
+
return {
|
|
80
|
+
input: parts.length ? parts.reduce((sum, value) => sum + value, 0) : null,
|
|
81
|
+
output,
|
|
82
|
+
...(cacheRead !== null ? { cachedInput: cacheRead } : {}),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Merges native turns into one logical turn. Cost takes the latest figure: total_cost_usd
|
|
87
|
+
* is cumulative and the latest result is read rather than summed. */
|
|
88
|
+
export function sumNativeTurnResults(earlier, latest) {
|
|
89
|
+
if (!earlier) return latest
|
|
90
|
+
const usage = earlier.usage && latest.usage
|
|
91
|
+
? {
|
|
92
|
+
input: (earlier.usage.input ?? 0) + (latest.usage.input ?? 0),
|
|
93
|
+
output: (earlier.usage.output ?? 0) + (latest.usage.output ?? 0),
|
|
94
|
+
...((earlier.usage.cachedInput !== undefined || latest.usage.cachedInput !== undefined)
|
|
95
|
+
? { cachedInput: (earlier.usage.cachedInput ?? 0) + (latest.usage.cachedInput ?? 0) } : {}),
|
|
96
|
+
}
|
|
97
|
+
: latest.usage ?? earlier.usage
|
|
98
|
+
return {
|
|
99
|
+
ok: earlier.ok && latest.ok,
|
|
100
|
+
stopReason: latest.stopReason ?? earlier.stopReason,
|
|
101
|
+
cost: latest.cost ?? earlier.cost,
|
|
102
|
+
...(usage ? { usage } : {}),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Classify one parsed stdout JSON line into a typed protocol event. Anything the engine
|
|
108
|
+
* does not act on is returned as { type: 'ignored', raw } rather than dropped silently.
|
|
109
|
+
*/
|
|
110
|
+
export function classifyClaudeFrame(frame) {
|
|
111
|
+
if (!frame || typeof frame !== 'object') return { type: 'ignored', reason: 'not-an-object' }
|
|
112
|
+
switch (frame.type) {
|
|
113
|
+
case 'system':
|
|
114
|
+
if (frame.subtype === 'init') {
|
|
115
|
+
return {
|
|
116
|
+
type: 'init',
|
|
117
|
+
sessionId: typeof frame.session_id === 'string' && frame.session_id ? frame.session_id : null,
|
|
118
|
+
model: typeof frame.model === 'string' && frame.model ? frame.model : null,
|
|
119
|
+
permissionMode: typeof frame.permissionMode === 'string' && frame.permissionMode ? frame.permissionMode : null,
|
|
120
|
+
tools: Array.isArray(frame.tools) ? frame.tools.filter(value => typeof value === 'string') : [],
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (frame.subtype === 'compact_boundary') return { type: 'compacted' }
|
|
124
|
+
return { type: 'ignored', reason: `system/${frame.subtype}` }
|
|
125
|
+
case 'stream_event': {
|
|
126
|
+
const event = frame.event ?? {}
|
|
127
|
+
const delta = event.delta ?? {}
|
|
128
|
+
if (frame.parent_tool_use_id) return { type: 'ignored', reason: 'subagent-narration' }
|
|
129
|
+
if (event.type === 'content_block_delta') {
|
|
130
|
+
if (delta.type === 'text_delta' && typeof delta.text === 'string' && delta.text) return { type: 'assistant-delta', delta: delta.text }
|
|
131
|
+
if (delta.type === 'thinking_delta' && typeof delta.thinking === 'string' && delta.thinking) return { type: 'reasoning-delta', delta: delta.thinking }
|
|
132
|
+
}
|
|
133
|
+
return { type: 'ignored', reason: `stream_event/${event.type ?? 'unknown'}` }
|
|
134
|
+
}
|
|
135
|
+
case 'assistant': {
|
|
136
|
+
const message = frame.message ?? {}
|
|
137
|
+
const text = firstText(message.content)
|
|
138
|
+
const out = { type: 'assistant', text }
|
|
139
|
+
if (Array.isArray(message.content)) {
|
|
140
|
+
const toolUses = message.content.filter(block => block?.type === 'tool_use')
|
|
141
|
+
if (toolUses.length) out.toolUses = toolUses.map(block => ({ id: typeof block.id === 'string' ? block.id : null, name: typeof block.name === 'string' ? block.name : null, input: block.input ?? null }))
|
|
142
|
+
}
|
|
143
|
+
if (message.usage && typeof message.usage === 'object') out.usage = message.usage
|
|
144
|
+
return out
|
|
145
|
+
}
|
|
146
|
+
case 'user': {
|
|
147
|
+
if (frame.isReplay === true) return { type: 'replay' }
|
|
148
|
+
const content = frame.message?.content
|
|
149
|
+
if (!Array.isArray(content)) return { type: 'ignored', reason: 'user/non-tool-result' }
|
|
150
|
+
const results = content.filter(block => block?.type === 'tool_result')
|
|
151
|
+
if (!results.length) return { type: 'ignored', reason: 'user/non-tool-result' }
|
|
152
|
+
return {
|
|
153
|
+
type: 'tool-result',
|
|
154
|
+
results: results.map(block => ({ toolUseId: typeof block.tool_use_id === 'string' ? block.tool_use_id : null, ok: block.is_error !== true })),
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
case 'result':
|
|
158
|
+
if (frame.origin?.kind === 'task-notification') return { type: 'ignored', reason: 'task-notification' }
|
|
159
|
+
return { type: 'result', isError: frame.is_error === true, native: nativeTurnResult(frame), errors: Array.isArray(frame.errors) ? frame.errors.filter(value => typeof value === 'string') : [] }
|
|
160
|
+
default:
|
|
161
|
+
return { type: 'ignored', reason: `type/${String(frame.type)}` }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Permission broker wire parsing (host side). A line must be {t:'ask', id, kind, tool, input}. */
|
|
166
|
+
export function parseBrokerAsk(message) {
|
|
167
|
+
if (!message || typeof message !== 'object' || message.t !== 'ask') return null
|
|
168
|
+
const id = typeof message.id === 'string' && message.id ? message.id : null
|
|
169
|
+
if (!id) return null
|
|
170
|
+
const kind = message.kind === 'question' ? 'question' : 'permission'
|
|
171
|
+
return { id, kind, tool: typeof message.tool === 'string' && message.tool ? message.tool : 'tool', input: message.input && typeof message.input === 'object' && !Array.isArray(message.input) ? message.input : {} }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Serialize a broker answer line. */
|
|
175
|
+
export function brokerAnswer(id, behavior, message, source, always = false) {
|
|
176
|
+
return { t: 'answer', id, behavior, message, source, ...(always ? { always: true } : {}) }
|
|
177
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// Closed catalog of the 26 upstream OpenMausBot built-in Bot engine drivers.
|
|
2
|
+
//
|
|
3
|
+
// Upstream source: OpenMausBot at pin 1bc4749aa8307d7d6998bf86de4ef6478460dd93
|
|
4
|
+
// (Apache-2.0), server/drivers/builtIn.ts BUILT_IN_DRIVERS. Every entry records the
|
|
5
|
+
// upstream file it was verified against. This catalog is closed: exactly these 26 ids
|
|
6
|
+
// exist, no unknown id resolves, and no arbitrary executable/endpoint/command can be
|
|
7
|
+
// registered as a driver. The 13 CLIs in .dsh-plugin/shared/official-tool-registry.mjs
|
|
8
|
+
// are one-shot official tools, a different and smaller set; they are NOT drivers and are
|
|
9
|
+
// deliberately not conflated with this list.
|
|
10
|
+
|
|
11
|
+
export const UPSTREAM_SOURCE = Object.freeze({
|
|
12
|
+
project: 'OpenMausBot',
|
|
13
|
+
pin: '1bc4749aa8307d7d6998bf86de4ef6478460dd93',
|
|
14
|
+
license: 'Apache-2.0',
|
|
15
|
+
driversRoot: 'server/drivers',
|
|
16
|
+
builtInList: 'server/drivers/builtIn.ts (BUILT_IN_DRIVERS)',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const unavailable = (reason, target) => ({ availability: 'unavailable', availabilityReason: reason, targetCapability: target })
|
|
20
|
+
|
|
21
|
+
const api = (id, name, evidence, extra = {}) => ({
|
|
22
|
+
id, name, kind: 'api-provider', transport: 'https-openai-chat-completions',
|
|
23
|
+
multipleInstances: true,
|
|
24
|
+
evidence,
|
|
25
|
+
...unavailable(
|
|
26
|
+
'API provider channel, not a conversation engine: stateless OpenAI chat-completions HTTP calls with no retained session process, no native tools and no permission broker. It belongs to the Host model-routing channel, not to packages/bot-engines.',
|
|
27
|
+
'api-provider-channel'),
|
|
28
|
+
...extra,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const acpPending = (id, name, evidence, extra = {}) => ({
|
|
32
|
+
id, name, kind: 'conversation-engine',
|
|
33
|
+
evidence,
|
|
34
|
+
...unavailable(
|
|
35
|
+
'CLI conversation engine whose upstream adapter runs it over ACP (Agent Client Protocol) stdio. Porting it requires the upstream acp/core.ts session/state machine first; it is deliberately not faked by spawning the CLI ad hoc.',
|
|
36
|
+
'pending-acp-core'),
|
|
37
|
+
...extra,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export const BOT_ENGINE_DRIVERS = Object.freeze([
|
|
41
|
+
// ---- API providers (8) — createOpenAIChatRuntime, one request per call, no session ----
|
|
42
|
+
Object.freeze(api('grok', 'Grok (API)', Object.freeze({
|
|
43
|
+
file: 'server/drivers/grok.ts', anchor: 'L33-45',
|
|
44
|
+
modelSelection: 'Static catalog, default grok-4.7 (grok-4.7/grok-4/grok-4-fast/grok-3-mini)',
|
|
45
|
+
credentials: 'Per-instance apiKeyEnv, default XAI_API_KEY; default URL https://api.x.ai/v1',
|
|
46
|
+
session: 'None: one chat-completions request per model call',
|
|
47
|
+
permissions: 'None: no local tools, no permission flow',
|
|
48
|
+
usage: 'Usage reported by the xAI chat-completions response',
|
|
49
|
+
}))),
|
|
50
|
+
Object.freeze(api('groq', 'Groq (API)', Object.freeze({
|
|
51
|
+
file: 'server/drivers/groq.ts', anchor: 'L41-95',
|
|
52
|
+
modelSelection: 'Static default catalog refreshed from GET /models with the API key (8s timeout)',
|
|
53
|
+
credentials: 'GROQ_API_KEY (instance env, or process env when URL is the default)',
|
|
54
|
+
session: 'None: stateless chat-completions calls',
|
|
55
|
+
permissions: 'None',
|
|
56
|
+
usage: 'Provider-reported usage; includeUsageInCompleted; billing metered',
|
|
57
|
+
}))),
|
|
58
|
+
Object.freeze(api('mistral', 'Mistral (API)', Object.freeze({
|
|
59
|
+
file: 'server/drivers/mistral.ts', anchor: 'L48-60',
|
|
60
|
+
modelSelection: 'Static catalog with configured-model override',
|
|
61
|
+
credentials: 'MISTRAL_API_KEY (instance env first, then process env)',
|
|
62
|
+
session: 'None',
|
|
63
|
+
permissions: 'None',
|
|
64
|
+
usage: 'Provider-reported usage',
|
|
65
|
+
}))),
|
|
66
|
+
Object.freeze(api('cerebras', 'Cerebras (API)', Object.freeze({
|
|
67
|
+
file: 'server/drivers/cerebras.ts', anchor: 'L39-51',
|
|
68
|
+
modelSelection: 'Static catalog with configured-model override',
|
|
69
|
+
credentials: 'CEREBRAS_API_KEY (instance env first, then process env)',
|
|
70
|
+
session: 'None',
|
|
71
|
+
permissions: 'None',
|
|
72
|
+
usage: 'Provider-reported usage',
|
|
73
|
+
}))),
|
|
74
|
+
Object.freeze(api('greenference', 'Greenference (API)', Object.freeze({
|
|
75
|
+
file: 'server/drivers/greenference.ts', anchor: 'L57-71',
|
|
76
|
+
modelSelection: 'Static catalog with configured-model override',
|
|
77
|
+
credentials: 'GREENFERENCE_API_KEY (instance env; process env only on the default URL)',
|
|
78
|
+
session: 'None',
|
|
79
|
+
permissions: 'None',
|
|
80
|
+
usage: 'Provider-reported usage',
|
|
81
|
+
}))),
|
|
82
|
+
Object.freeze(api('meta', 'Meta Muse (API)', Object.freeze({
|
|
83
|
+
file: 'server/drivers/meta.ts', anchor: 'L54-68',
|
|
84
|
+
modelSelection: 'Static catalog with configured-model override',
|
|
85
|
+
credentials: 'META_API_KEY (instance env; process env only on the default URL)',
|
|
86
|
+
session: 'None',
|
|
87
|
+
permissions: 'None',
|
|
88
|
+
usage: 'Provider-reported usage',
|
|
89
|
+
}))),
|
|
90
|
+
Object.freeze(api('minimax', 'MiniMax (API)', Object.freeze({
|
|
91
|
+
file: 'server/drivers/minimax.ts', anchor: 'L11,L71-86',
|
|
92
|
+
modelSelection: 'Static catalog; URL from config or MINIMAX_BASE_URL',
|
|
93
|
+
credentials: 'MINIMAX_API_KEY (API key env); local mmx-cli profile via "mmx auth login --api-key" also supported upstream',
|
|
94
|
+
session: 'None',
|
|
95
|
+
permissions: 'None',
|
|
96
|
+
usage: 'Provider-reported usage',
|
|
97
|
+
}), { access: 'custom' })),
|
|
98
|
+
Object.freeze(api('openai-compat', 'Other (OpenAI-compatible)', Object.freeze({
|
|
99
|
+
file: 'server/drivers/openai-compat.ts', anchor: 'L43-184',
|
|
100
|
+
modelSelection: 'Caller-configured model id on any OpenAI-compatible endpoint (default OpenRouter)',
|
|
101
|
+
credentials: 'Configurable apiKeyEnv (OMB_OPENAI_API_KEY / OMB_OPENROUTER_API_KEY are first-class), else the endpoint key is added to the instance config',
|
|
102
|
+
session: 'None',
|
|
103
|
+
permissions: 'None',
|
|
104
|
+
usage: 'Provider-reported usage',
|
|
105
|
+
}), { access: 'api' })),
|
|
106
|
+
|
|
107
|
+
// ---- Conversation engines (18) ----
|
|
108
|
+
Object.freeze({
|
|
109
|
+
id: 'claude', name: 'Claude', kind: 'conversation-engine',
|
|
110
|
+
transport: 'cli-stream-json-stdio (retained process; -p --output-format stream-json --input-format stream-json --verbose --include-partial-messages)',
|
|
111
|
+
multipleInstances: true,
|
|
112
|
+
evidence: Object.freeze({
|
|
113
|
+
file: 'server/drivers/claude.ts', anchor: 'L758-771,L1006-1013',
|
|
114
|
+
modelSelection: '--model <slug> on argv; static catalog plus endpoint/org extras upstream',
|
|
115
|
+
credentials: 'Subscription OAuth under an isolated CLAUDE_CONFIG_DIR, or ANTHROPIC_API_KEY for managed/endpoint accounts; parent account env keys are stripped',
|
|
116
|
+
session: 'One retained CLI process per thread; init frame confirms session_id; later turns are new stdin user messages; relaunch uses --resume <session_id>; a steered continuation can chain several native result frames into one logical turn',
|
|
117
|
+
permissions: '--permission-mode default|acceptEdits|auto|bypassPermissions; permission/question asks arrive through an MCP permission-prompt tool backed by a local ask/answer broker socket; duplicate ask ids and post-close asks fail closed',
|
|
118
|
+
usage: 'result.usage is per-turn in streaming-input sessions; total_cost_usd is the process running total, so a turn cost is the growth since the previous result; missing values stay unknown',
|
|
119
|
+
}),
|
|
120
|
+
availability: 'implemented',
|
|
121
|
+
availabilityReason: null,
|
|
122
|
+
targetCapability: 'claude-engine',
|
|
123
|
+
implementation: 'src/claude-engine.mjs',
|
|
124
|
+
}),
|
|
125
|
+
Object.freeze({
|
|
126
|
+
id: 'codex', name: 'Codex', kind: 'conversation-engine',
|
|
127
|
+
transport: 'cli-app-server-ndjson (codex app-server JSON-RPC over stdio)',
|
|
128
|
+
multipleInstances: true,
|
|
129
|
+
evidence: Object.freeze({
|
|
130
|
+
file: 'server/drivers/codex.ts', anchor: 'L8-9,L579',
|
|
131
|
+
modelSelection: 'Model selected on thread/start and switchable per turn upstream',
|
|
132
|
+
credentials: 'ChatGPT plan login or OpenAI API key per instance',
|
|
133
|
+
session: 'Threads resume by codex thread id (thread/resume); completion is turn/completed; approvals arrive as server-to-client requests',
|
|
134
|
+
permissions: 'Upstream maps approval modes onto codex approval policy per thread',
|
|
135
|
+
usage: 'Token usage is reported as a running total for the app-server process',
|
|
136
|
+
}),
|
|
137
|
+
...unavailable('Not yet ported as a Bot conversation engine. The target already runs codex as one-shot task execution (packages/cli-runtime/src/codex-session-core.mjs); lifting it into a retained multi-turn Bot engine is a separate port with its own tests.', 'pending-engine-port'),
|
|
138
|
+
}),
|
|
139
|
+
Object.freeze({
|
|
140
|
+
id: 'museCode', name: 'Muse Code', kind: 'conversation-engine',
|
|
141
|
+
transport: 'msdp-stdio (Muse Session Protocol over "muse serve" stdio)',
|
|
142
|
+
multipleInstances: true, access: 'subscription',
|
|
143
|
+
evidence: Object.freeze({
|
|
144
|
+
file: 'server/drivers/muse-code.ts', anchor: 'L250-262,L444-462',
|
|
145
|
+
modelSelection: 'model/list from the signed-in account; static fallback (muse-wonder-1.0, muse-spark-1.3/1.2); session/set_model',
|
|
146
|
+
credentials: '"muse login" under the instance HOME, or META_API_KEY for that instance',
|
|
147
|
+
session: 'session/start confirms sessionId; session/resume (sessionNotFound falls back to a fresh session); hosts are keyed by cwd/approval/MCP posture',
|
|
148
|
+
permissions: 'approval/requested with availableChoices (approved once / approvedForSession / denied); full-auto maps to allowAll',
|
|
149
|
+
usage: 'session/tokenUsage and turn/completed usage (inputTokens/outputTokens/cachedTokens); no dollar figure',
|
|
150
|
+
}),
|
|
151
|
+
...unavailable('Requires the Muse Session Protocol host port (upstream drives it through @muse-code/sdk Connection over a supervised stdio transport). Not faked.', 'pending-engine-port'),
|
|
152
|
+
}),
|
|
153
|
+
Object.freeze({
|
|
154
|
+
id: 'pi', name: 'pi', kind: 'conversation-engine',
|
|
155
|
+
transport: 'cli-jsonrpc-stdio (pi --mode rpc --no-session)',
|
|
156
|
+
multipleInstances: true, access: 'custom',
|
|
157
|
+
evidence: Object.freeze({
|
|
158
|
+
file: 'server/drivers/pi.ts', anchor: 'L4-5,L57,L363-364',
|
|
159
|
+
modelSelection: 'BYOK: pi reads ~/.pi/agent/auth.json itself; the driver holds no key',
|
|
160
|
+
credentials: 'None held by the host; pi manages its own auth',
|
|
161
|
+
session: 'resumeCursor is pi sessionFile (switch_session); --no-session accepts a prompt without a session',
|
|
162
|
+
permissions: 'pi asks through its own RPC requests',
|
|
163
|
+
usage: 'Reported by pi per turn',
|
|
164
|
+
}),
|
|
165
|
+
...unavailable('Not yet ported: the upstream driver drives pi over "pi --mode rpc --no-session" JSON-RPC stdio (BYOK auth under ~/.pi), which needs its own RPC adapter, session-file cursors and request broker before it can be a Bot engine. Not faked.', 'pending-engine-port'),
|
|
166
|
+
}),
|
|
167
|
+
Object.freeze({
|
|
168
|
+
id: 'antigravityAgent', name: 'Google Gemini (Antigravity)', kind: 'conversation-engine',
|
|
169
|
+
transport: 'acp-stdio via managed runtime (resolveAntigravityRuntime executable, isolated Google profile)',
|
|
170
|
+
multipleInstances: true,
|
|
171
|
+
evidence: Object.freeze({
|
|
172
|
+
file: 'server/drivers/antigravity.ts', anchor: 'L31-88',
|
|
173
|
+
modelSelection: 'Static gemini-3.8-flash with low/medium/high variants; AGY_ACP_DEFAULT_MODEL seeds new sessions',
|
|
174
|
+
credentials: 'Google account OAuth into an isolated profile (prepareAntigravityProfile)',
|
|
175
|
+
session: 'ACP resume method "resume"; session-scoped approval',
|
|
176
|
+
permissions: 'yolo / auto_edit / default session modes',
|
|
177
|
+
usage: 'ACP session reports',
|
|
178
|
+
}),
|
|
179
|
+
...unavailable('Requires the ACP core port plus the managed Antigravity runtime resolver.', 'pending-acp-core'),
|
|
180
|
+
}),
|
|
181
|
+
Object.freeze(acpPending('grokAgent', 'Grok', Object.freeze({
|
|
182
|
+
file: 'server/drivers/acp/grok.ts', anchor: 'L368-395',
|
|
183
|
+
modelSelection: 'Static grok-4.7/4.6/4.5 plus local slugs from ~/.grok/config.toml; session/set_model (never on narrowed tool scope)',
|
|
184
|
+
credentials: 'grok.com subscription login at ~/.grok/auth.json via grok login --device-auth; XAI_API_KEY is deleted from the child env because a leaked key silently flips billing to pay-as-you-go',
|
|
185
|
+
session: 'One ACP session per conversation; -m must sit between "agent" and "stdio"',
|
|
186
|
+
permissions: '--permission-mode default|auto|acceptEdits|bypassPermissions before "agent"; verified native tool selection only on grok 1.0.41 agent profiles',
|
|
187
|
+
usage: 'ACP token usage reports',
|
|
188
|
+
}), { transport: 'acp-stdio (grok --permission-mode … agent [-m slug] [--reasoning-effort lvl] stdio)' })),
|
|
189
|
+
Object.freeze(acpPending('geminiAgent', 'Gemini', Object.freeze({
|
|
190
|
+
file: 'server/drivers/acp/gemini.ts', anchor: 'L1-5,L51-88',
|
|
191
|
+
modelSelection: 'gemini-2.5-pro / gemini-2.5-flash, -m on spawn',
|
|
192
|
+
credentials: 'GEMINI_API_KEY / GOOGLE_API_KEY, Vertex, or an enterprise Code Assist licence; consumer Google logins stopped working 2026-06-18 (route those accounts through antigravityAgent)',
|
|
193
|
+
session: 'ACP session lifecycle',
|
|
194
|
+
permissions: '--yolo (full) / --approval-mode auto_edit (edits) / ask otherwise',
|
|
195
|
+
usage: 'ACP session reports',
|
|
196
|
+
}), { transport: 'acp-stdio (gemini --acp)', access: 'api' })),
|
|
197
|
+
Object.freeze(acpPending('kimiAgent', 'Kimi', Object.freeze({
|
|
198
|
+
file: 'server/drivers/acp/kimi.ts', anchor: 'L521-566',
|
|
199
|
+
modelSelection: 'CLI catalog aliases from ~/.kimi-code/config.toml (kimi provider list reports the same four); -m is global and precedes "acp"',
|
|
200
|
+
credentials: 'Kimi Code account login via device sign-in; MOONSHOT_API_KEY/KIMI_API_KEY are deleted so billing cannot flip to pay-as-you-go',
|
|
201
|
+
session: 'ACP session lifecycle',
|
|
202
|
+
permissions: 'ACP permission requests',
|
|
203
|
+
usage: 'ACP session reports',
|
|
204
|
+
}), { transport: 'acp-stdio (kimi -m <model> acp)' })),
|
|
205
|
+
Object.freeze(acpPending('droidAgent', 'Droid', Object.freeze({
|
|
206
|
+
file: 'server/drivers/acp/droid.ts', anchor: 'L173-185',
|
|
207
|
+
modelSelection: 'Driver model catalog',
|
|
208
|
+
credentials: 'droid login or FACTORY_API_KEY',
|
|
209
|
+
session: 'ACP session lifecycle',
|
|
210
|
+
permissions: 'ACP permission requests',
|
|
211
|
+
usage: 'ACP session reports',
|
|
212
|
+
}), { transport: 'acp-stdio (droid)' })),
|
|
213
|
+
Object.freeze(acpPending('cursorAgent', 'Cursor', Object.freeze({
|
|
214
|
+
file: 'server/drivers/acp/cursor.ts', anchor: 'L474-513',
|
|
215
|
+
modelSelection: 'Models fetched from the cursor-agent CLI; --model is a global flag before "acp"',
|
|
216
|
+
credentials: 'cursor_login OAuth, or CURSOR_API_KEY / CURSOR_AUTH_TOKEN (a key in env already authenticates and suppresses the login flow)',
|
|
217
|
+
session: 'ACP session lifecycle',
|
|
218
|
+
permissions: '--force (full auto) / --auto-review (auto) on argv',
|
|
219
|
+
usage: 'ACP session reports',
|
|
220
|
+
}), { transport: 'acp-stdio (cursor-agent [--force|--auto-review] [--model id] acp)' })),
|
|
221
|
+
Object.freeze(acpPending('opencodeGo', 'OpenCode', Object.freeze({
|
|
222
|
+
file: 'server/drivers/acp/opencode-go.ts', anchor: 'L851-912',
|
|
223
|
+
modelSelection: 'Catalog loaded from the opencode CLI/config (OPENCODE_API_KEY plus provider envs); typed/injected models declared via env; free no-login Zen fallback is gated',
|
|
224
|
+
credentials: 'OPENCODE_API_KEY or per-provider keys, or opencode auth login storage',
|
|
225
|
+
session: 'ACP sessions; shared process across conversations (shareProcess); global project folder isolation for non-git folders',
|
|
226
|
+
permissions: 'OPENCODE_PERMISSION env allow-map under full auto; owned-directory grants otherwise',
|
|
227
|
+
usage: 'ACP session reports',
|
|
228
|
+
}), { transport: 'acp-stdio (opencode acp)' })),
|
|
229
|
+
Object.freeze(acpPending('qwenAgent', 'Qwen', Object.freeze({
|
|
230
|
+
file: 'server/drivers/acp/qwen.ts', anchor: 'L208-240',
|
|
231
|
+
modelSelection: 'Provider routes from ~/.qwen (model_routes); ACP session/load switches and confirms the whole route; unconfigured or ambiguous models refuse',
|
|
232
|
+
credentials: 'Qwen Code account/config under QWEN_RUNTIME_DIR / QWEN_HOME / ~/.qwen',
|
|
233
|
+
session: 'ACP session lifecycle; restartOnMcpChange (live session/load ignores fresh MCP credentials)',
|
|
234
|
+
permissions: '--approval-mode default|auto-edit|auto, --yolo for full auto',
|
|
235
|
+
usage: 'ACP session reports; QWEN_DEBUG_LOG_FILE parsed for quiet-status evidence',
|
|
236
|
+
}), { transport: 'acp-stdio (qwen --acp)', access: 'custom' })),
|
|
237
|
+
Object.freeze(acpPending('hermesAgent', 'Hermes', Object.freeze({
|
|
238
|
+
file: 'server/drivers/acp/hermes.ts', anchor: 'L407-456',
|
|
239
|
+
modelSelection: 'resolveHermesModels from the hermes CLI config; session/set_model per turn; one concrete model bound per grant',
|
|
240
|
+
credentials: 'hermes setup login; OPENAI_API_KEY/OPENROUTER_API_KEY deleted (a leftover key auto-resolves to OpenRouter with no Authorization header)',
|
|
241
|
+
session: 'ACP session lifecycle; restartOnMcpChange',
|
|
242
|
+
permissions: 'ACP permission requests',
|
|
243
|
+
usage: 'ACP session reports',
|
|
244
|
+
}), { transport: 'acp-stdio (hermes acp)', access: 'custom' })),
|
|
245
|
+
Object.freeze(acpPending('clineAgent', 'Cline', Object.freeze({
|
|
246
|
+
file: 'server/drivers/acp/cline.ts', anchor: 'L55-69',
|
|
247
|
+
modelSelection: 'Driver model catalog',
|
|
248
|
+
credentials: 'Cline CLI sign-in',
|
|
249
|
+
session: 'ACP session lifecycle',
|
|
250
|
+
permissions: '--auto-approve false: every action asks',
|
|
251
|
+
usage: 'ACP session reports',
|
|
252
|
+
}), { transport: 'acp-stdio (cline --acp --auto-approve false)' })),
|
|
253
|
+
Object.freeze(acpPending('copilotAgent', 'GitHub Copilot', Object.freeze({
|
|
254
|
+
file: 'server/drivers/acp/copilot.ts', anchor: 'L186-199',
|
|
255
|
+
modelSelection: 'Driver model catalog',
|
|
256
|
+
credentials: 'GitHub Copilot subscription login',
|
|
257
|
+
session: 'ACP sessions; one-shot session-state kept out of the person\'s copilot --resume list',
|
|
258
|
+
permissions: 'ACP permission requests',
|
|
259
|
+
usage: 'ACP session reports',
|
|
260
|
+
}), { transport: 'acp-stdio (copilot --acp --stdio)' })),
|
|
261
|
+
Object.freeze(acpPending('commandCodeAgent', 'Command Code', Object.freeze({
|
|
262
|
+
file: 'server/drivers/acp/command-code.ts', anchor: 'L82-95',
|
|
263
|
+
modelSelection: 'Driver model catalog',
|
|
264
|
+
credentials: 'Command Code CLI sign-in',
|
|
265
|
+
session: 'ACP session lifecycle',
|
|
266
|
+
permissions: 'ACP permission requests',
|
|
267
|
+
usage: 'ACP session reports',
|
|
268
|
+
}), { transport: 'acp-stdio (command-code acp)' })),
|
|
269
|
+
Object.freeze({
|
|
270
|
+
id: 'customAcp', name: 'Custom (ACP)', kind: 'conversation-engine',
|
|
271
|
+
transport: 'acp-stdio (user-configured command)',
|
|
272
|
+
multipleInstances: true, access: 'custom',
|
|
273
|
+
evidence: Object.freeze({
|
|
274
|
+
file: 'server/drivers/acp/custom.ts', anchor: 'L14-26',
|
|
275
|
+
modelSelection: 'agent-default plus models advertised by the configured agent',
|
|
276
|
+
credentials: 'Whatever the configured ACP command uses',
|
|
277
|
+
session: 'ACP session lifecycle',
|
|
278
|
+
permissions: 'ACP permission requests',
|
|
279
|
+
usage: 'ACP session reports',
|
|
280
|
+
}),
|
|
281
|
+
...unavailable('Pending ACP core and trusted owner registration: custom engines remain in migration scope. Registration must commit a Host-approved executable pin, grant scope and permission broker; arbitrary executable/command fields in model or turn requests remain rejected.', 'pending-acp-core'),
|
|
282
|
+
}),
|
|
283
|
+
Object.freeze(acpPending('deepseekAgent', 'DeepSeek Harness', Object.freeze({
|
|
284
|
+
file: 'server/drivers/acp/deepseek.ts', anchor: 'L286-305',
|
|
285
|
+
modelSelection: 'Driver model catalog',
|
|
286
|
+
credentials: 'DeepSeek account login in the dsh CLI',
|
|
287
|
+
session: 'ACP session lifecycle',
|
|
288
|
+
permissions: 'ACP permission requests',
|
|
289
|
+
usage: 'ACP session reports',
|
|
290
|
+
}), { transport: 'acp-stdio (dsh --profile acp)' })),
|
|
291
|
+
].map(Object.freeze))
|
|
292
|
+
|
|
293
|
+
const byId = new Map(BOT_ENGINE_DRIVERS.map(driver => [driver.id, driver]))
|
|
294
|
+
if (byId.size !== BOT_ENGINE_DRIVERS.length) throw new Error('DUPLICATE_BOT_ENGINE_DRIVER_ID')
|
|
295
|
+
|
|
296
|
+
/** The closed id set, exactly the 26 upstream built-in drivers. */
|
|
297
|
+
export const BOT_ENGINE_DRIVER_IDS = Object.freeze([...byId.keys()])
|
|
298
|
+
|
|
299
|
+
export function getBotEngineDriver(id) {
|
|
300
|
+
if (typeof id !== 'string') return undefined
|
|
301
|
+
return byId.get(id)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function listBotEngineDrivers() {
|
|
305
|
+
return BOT_ENGINE_DRIVERS.map(driver => Object.freeze({ ...driver, evidence: driver.evidence }))
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Resolve a driver request into an engine factory. Only 'claude' is implemented; every
|
|
310
|
+
* other id returns an explicit unavailable verdict with its reason. Unknown ids throw.
|
|
311
|
+
* The input is never allowed to carry an executable, endpoint, command or credential:
|
|
312
|
+
* those are Host-owned and attempting to supply them is an error, not a configuration.
|
|
313
|
+
*/
|
|
314
|
+
export async function resolveBotEngine(request) {
|
|
315
|
+
if (!request || typeof request !== 'object' || Array.isArray(request)) throw new TypeError('INVALID_ENGINE_REQUEST')
|
|
316
|
+
assertNoAdHocDriverInput(request)
|
|
317
|
+
const { driver: id } = request
|
|
318
|
+
const entry = byId.get(id)
|
|
319
|
+
if (!entry) throw new Error(`UNKNOWN_BOT_ENGINE_DRIVER: ${JSON.stringify(String(id))} (closed catalog of ${BOT_ENGINE_DRIVERS.length} upstream drivers)`)
|
|
320
|
+
if (entry.availability !== 'implemented') {
|
|
321
|
+
return { status: 'unavailable', driver: entry.id, name: entry.name, reason: entry.availabilityReason, targetCapability: entry.targetCapability }
|
|
322
|
+
}
|
|
323
|
+
const { createClaudeEngine } = await import('./claude-engine.mjs')
|
|
324
|
+
return { status: 'ready', driver: entry.id, name: entry.name, createEngine: createClaudeEngine, implementation: entry.implementation }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Ad-hoc engine inputs are refused outright: authority comes from committed Host state only. */
|
|
328
|
+
export function assertNoAdHocDriverInput(request) {
|
|
329
|
+
const forbidden = ['executable', 'path', 'command', 'cmd', 'endpoint', 'url', 'baseUrl', 'args', 'argv', 'env', 'environment', 'credentials', 'apiKey', 'token', 'autoApprove', 'alwaysAllow', 'bypassPermissions']
|
|
330
|
+
for (const key of Object.keys(request)) {
|
|
331
|
+
if (forbidden.includes(key)) throw new TypeError(`HOST_OWNED_ENGINE_INPUT: ${key} may not appear in a driver request`)
|
|
332
|
+
}
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const fail = code => { throw Object.assign(new Error(code), { code }) }
|
|
2
|
+
const TEXT_FIELDS = new Set(['provider', 'model', 'messages', 'system', 'signal', 'sessionId'])
|
|
3
|
+
const CONTROL_FIELDS = new Set(['maxTokens', 'reasoningEffort', 'temperature', 'stop', 'purpose'])
|
|
4
|
+
const META_FIELDS = new Set(['tools', 'toolHistory'])
|
|
5
|
+
const HOST_FACT_SOURCES = new Set(['runtime-context', 'ds-bot-time-context'])
|
|
6
|
+
|
|
7
|
+
/** Project only a true, canonical Host loop request into the text-only native
|
|
8
|
+
* route. Nonempty tools and controls remain unsupported; no authority is
|
|
9
|
+
* accepted from a caller and the original request stays untouched for accounting.
|
|
10
|
+
*/
|
|
11
|
+
export function projectHostLoopTextRequest(request, { session, isLoopRequest, allowRouteContext = false } = {}) {
|
|
12
|
+
if (!session || session.id !== request?.sessionId || typeof isLoopRequest !== 'function'
|
|
13
|
+
|| !isLoopRequest(request) || !Object.isFrozen(request) || !Object.isFrozen(request.messages)) fail('CLAUDE_HOST_LOOP_REQUEST_REQUIRED')
|
|
14
|
+
if (Object.keys(request).some(key => !TEXT_FIELDS.has(key) && !CONTROL_FIELDS.has(key) && !META_FIELDS.has(key))) fail('CLAUDE_ROUTE_UNKNOWN_REQUEST_FIELD')
|
|
15
|
+
if ([...CONTROL_FIELDS].some(key => request[key] !== undefined)) fail('CLAUDE_ROUTE_CAPABILITY_REFUSED')
|
|
16
|
+
if (request.tools !== undefined && (!Array.isArray(request.tools) || request.tools.length)) fail('CLAUDE_ROUTE_CAPABILITY_REFUSED')
|
|
17
|
+
const canonical = session.deriveMessages()
|
|
18
|
+
if (!Array.isArray(request.messages) || canonical.length !== request.messages.length
|
|
19
|
+
|| canonical.some((message, index) => message !== request.messages[index])) fail('CLAUDE_HOST_HISTORY_MISMATCH')
|
|
20
|
+
const history = session.toolHistory()
|
|
21
|
+
if (!history || !Array.isArray(history.tools) || !Array.isArray(history.updates)
|
|
22
|
+
|| request.toolHistory !== undefined && request.toolHistory !== history) fail('CLAUDE_HOST_TOOL_HISTORY_UNSUPPORTED')
|
|
23
|
+
const foreign = allowRouteContext ? lastCompletedForeignReply(canonical, session, request) : null
|
|
24
|
+
if ((history.tools.length || history.updates.length) && !foreign) fail('CLAUDE_HOST_TOOL_HISTORY_UNSUPPORTED')
|
|
25
|
+
const declarationIds = new Set(history.updates.map(update => update.messageId))
|
|
26
|
+
const messages = []
|
|
27
|
+
for (const message of request.messages) {
|
|
28
|
+
if (foreign && message.role === 'developer' && message.source?.kind === 'tool-registry' && declarationIds.has(message.id)
|
|
29
|
+
&& Array.isArray(message.content) && message.content.every(block => ['tool-addition', 'tool-removal'].includes(block.type))) continue
|
|
30
|
+
if (message.role === 'user' && (HOST_FACT_SOURCES.has(message.source?.kind)
|
|
31
|
+
|| allowRouteContext && message.source?.kind === 'model-selection' && message.source.form === 'notice')) {
|
|
32
|
+
const previous = messages.at(-1)
|
|
33
|
+
if (previous?.role !== 'user' || HOST_FACT_SOURCES.has(previous.source?.kind)) fail('CLAUDE_HOST_RUNTIME_CONTEXT_UNSUPPORTED')
|
|
34
|
+
const text = plainText(message.content)
|
|
35
|
+
const input = plainText(previous.content)
|
|
36
|
+
messages[messages.length - 1] = { ...previous, content: [{ type: 'text', text: `${input}\n\n[Host runtime context]\n${text}` }] }
|
|
37
|
+
} else messages.push(message)
|
|
38
|
+
}
|
|
39
|
+
return { ...Object.fromEntries([...TEXT_FIELDS].filter(key => request[key] !== undefined).map(key => [key, request[key]])), messages }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function lastCompletedForeignReply(messages, session, route) {
|
|
43
|
+
const reply = messages.findLast(message => message.role === 'assistant' && message.source?.kind === 'model'
|
|
44
|
+
&& (message.source.provider !== route.provider || message.source.model !== route.model))
|
|
45
|
+
if (!reply) return null
|
|
46
|
+
const event = session.log?.findLast(item => item.type === 'assistant/message' && item.data.message?.id === reply.id)
|
|
47
|
+
if (!event || !session.log.some(item => item.type === 'turn/end' && item.data.turn === event.data.turn && item.data.reason?.kind === 'completed')) {
|
|
48
|
+
fail('CLAUDE_HOST_ROUTE_CONTEXT_UNCONFIRMED')
|
|
49
|
+
}
|
|
50
|
+
return reply
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A verified text prefix can initialize a new process as reference context. */
|
|
54
|
+
export function hostLoopRouteContext(projected, { session, redact = text => text } = {}) {
|
|
55
|
+
const messages = projected.messages[0]?.role === 'system' ? projected.messages.slice(1) : projected.messages
|
|
56
|
+
const boundary = lastCompletedForeignReply(messages, session, projected)
|
|
57
|
+
if (!boundary) return null
|
|
58
|
+
const prefixLength = messages.indexOf(boundary) + 1
|
|
59
|
+
const completed = new Set(session.log.filter(event => event.type === 'turn/end' && event.data.reason?.kind === 'completed').map(event => event.data.turn))
|
|
60
|
+
const confirmed = new Map()
|
|
61
|
+
let turn = null
|
|
62
|
+
for (const event of session.log) {
|
|
63
|
+
if (event.type === 'turn/start') turn = event.data.turn
|
|
64
|
+
else if (event.type === 'turn/end') turn = null
|
|
65
|
+
else if (event.type === 'user/message' && completed.has(turn)) confirmed.set(event.data.id, event.type)
|
|
66
|
+
else if (event.type === 'assistant/message' && completed.has(event.data.turn) && !event.data.interrupted) confirmed.set(event.data.message?.id, event.type)
|
|
67
|
+
}
|
|
68
|
+
const history = messages.slice(0, prefixLength).map(message => {
|
|
69
|
+
if (!message.id || !['user', 'assistant'].includes(message.role) || confirmed.get(message.id) !== `${message.role}/message`) {
|
|
70
|
+
fail('CLAUDE_HOST_ROUTE_CONTEXT_UNSUPPORTED')
|
|
71
|
+
}
|
|
72
|
+
return { role: message.role, text: redact(plainText(message.content)) }
|
|
73
|
+
})
|
|
74
|
+
const text = JSON.stringify(history).replaceAll('<', '\\u003c')
|
|
75
|
+
const context = `\n\n<host_conversation_history>\nCompleted Host conversation, provided as reference. Continue with the new user input.\n${text}\n</host_conversation_history>`
|
|
76
|
+
if (Buffer.byteLength(`${hostSessionPersona(session, '')}${context}`) > 32_000) fail('CLAUDE_HOST_ROUTE_CONTEXT_LIMIT')
|
|
77
|
+
return { prefixLength, context }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function plainText(content) {
|
|
81
|
+
if (typeof content === 'string') return content
|
|
82
|
+
if (!Array.isArray(content) || content.some(part => part.type !== 'text' || typeof part.text !== 'string')) fail('CLAUDE_HOST_RUNTIME_CONTEXT_UNSUPPORTED')
|
|
83
|
+
return content.map(part => part.text).join('')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The persona is the Host's own current Session projection, never a request's
|
|
87
|
+
* proposed system field. It is still compared by the native adapter before use.
|
|
88
|
+
*/
|
|
89
|
+
export function hostSessionPersona(session, fallback = null) {
|
|
90
|
+
const head = session?.deriveMessages?.()[0]
|
|
91
|
+
if (head?.role !== 'system') return fallback
|
|
92
|
+
if (typeof head.content === 'string') return head.content
|
|
93
|
+
if (!Array.isArray(head.content) || head.content.some(part => part.type !== 'text' || typeof part.text !== 'string')) fail('CLAUDE_PERSONA_UNSUPPORTED')
|
|
94
|
+
return head.content.map(part => part.text).join('')
|
|
95
|
+
}
|