@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// the first Main Bot of a new team.
|
|
3
3
|
import { mkdir } from 'node:fs/promises'
|
|
4
4
|
import { MAX_AVATAR_IMAGE, ROOM_MAX_MEMBERS, ROOM_MODES, ROOM_NAME_MAX, ROOM_NOTICE_MAX } from './constants.js'
|
|
5
|
-
import { currentColor, isColor, normalize, unquote } from './text.js'
|
|
5
|
+
import { currentColor, isColor, normalize, splitRef, unquote } from './text.js'
|
|
6
6
|
import { nameProblem } from '../shared/mentions.js'
|
|
7
7
|
|
|
8
8
|
const BROWSER_GRANT_KINDS = new Set(['post', 'upload', 'download', 'delete', '*'])
|
|
@@ -420,6 +420,114 @@ export function install(rt) {
|
|
|
420
420
|
return bot
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
// Trusted product-only profile edit. No notice is delivered, no model is selected,
|
|
424
|
+
// and no kickoff is released. The owner service supplies the final synchronous
|
|
425
|
+
// committed-state check; the live base is compared at the same mutation boundary.
|
|
426
|
+
async function updateBotProfile(id, patch, { base, check } = {}) {
|
|
427
|
+
const fail = (code, message) => { throw Object.assign(new Error(message), { code }) }
|
|
428
|
+
const fields = { name: 40, role: 24, instructions: 4000 }
|
|
429
|
+
const plain = value => value && typeof value === 'object' && !Array.isArray(value)
|
|
430
|
+
if (!plain(patch) || !Object.keys(patch).length || Object.keys(patch).some(key => !Object.hasOwn(fields, key))
|
|
431
|
+
|| !plain(base) || typeof check !== 'function') fail('BOT_PROFILE_INVALID', 'Invalid profile mutation')
|
|
432
|
+
const bot = rt.botOf(id)
|
|
433
|
+
if (!bot || state.bots[id] !== bot) fail('BOT_PROFILE_UNKNOWN_BOT', 'Unknown Bot')
|
|
434
|
+
if (rt.readOnly() !== undefined) fail('PRODUCT_READ_ONLY', rt.readOnly())
|
|
435
|
+
const next = {}
|
|
436
|
+
for (const field of Object.keys(patch)) {
|
|
437
|
+
if (typeof patch[field] !== 'string' || patch[field].trim().length > fields[field]) fail('BOT_PROFILE_INVALID', 'Invalid profile field')
|
|
438
|
+
if (typeof base[field] !== 'string' || (bot[field] ?? '') !== base[field]) fail('BOT_PROFILE_CONFLICT', 'Bot profile changed before commit')
|
|
439
|
+
next[field] = patch[field].trim()
|
|
440
|
+
}
|
|
441
|
+
if (next.name !== undefined) {
|
|
442
|
+
const clash = rt.findBot(next.name)
|
|
443
|
+
const pending = isHeld('bot', next.name) && normalize(next.name) !== normalize(bot.name)
|
|
444
|
+
const problem = next.name === bot.name ? undefined : nameProblem(next.name)
|
|
445
|
+
if (!next.name || (clash && clash.id !== id) || pending || problem !== undefined) fail('BOT_PROFILE_NAME_INVALID', 'Bot name is not available')
|
|
446
|
+
}
|
|
447
|
+
const checked = check(bot)
|
|
448
|
+
if (checked && typeof checked.then === 'function') fail('BOT_PROFILE_INVALID', 'Profile commit check must be synchronous')
|
|
449
|
+
// No await between the check and all three field assignments.
|
|
450
|
+
const renamed = next.name !== undefined && next.name !== bot.name
|
|
451
|
+
Object.assign(bot, next)
|
|
452
|
+
const rooms = renamed ? Object.values(state.rooms).filter(room => !room.named && room.members.includes(id)) : []
|
|
453
|
+
for (const room of rooms) room.name = autoRoomName(room.members)
|
|
454
|
+
await rt.save()
|
|
455
|
+
// Session titles are presentation. A failure cannot undo an already persisted
|
|
456
|
+
// profile or turn the successful save into a request the UI might retry.
|
|
457
|
+
if (renamed) {
|
|
458
|
+
try {
|
|
459
|
+
const agent = await rt.resolveAgent(rt.chatOf(id))
|
|
460
|
+
const current = rt.botOf(id)
|
|
461
|
+
if (current) await ctx.sessionTitle.rename(agent.session, current.name)
|
|
462
|
+
} catch (error) { rt.warn('ds-bot: profile session title not updated: %s', error?.message ?? error) }
|
|
463
|
+
for (const room of rooms) await retitleRoom(room)
|
|
464
|
+
}
|
|
465
|
+
return bot
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Trusted product-only thread records. A thread Session is made by the caller
|
|
469
|
+
// through rt.createSession — the same factory every own chat uses — and
|
|
470
|
+
// recorded here in one save, so the committed state the Host lifecycle
|
|
471
|
+
// compiles and the directory the product keeps can never disagree about who
|
|
472
|
+
// owns a Session. Nothing here delivers a notice, picks a model beyond the
|
|
473
|
+
// Bot's own, or starts a turn.
|
|
474
|
+
const THREAD_LIMIT = 64
|
|
475
|
+
const THREAD_ID = /^[A-Za-z0-9_-]{1,128}$/
|
|
476
|
+
const threadFail = (code, message) => { throw Object.assign(new Error(message), { code }) }
|
|
477
|
+
async function addThreadSession(botId, threadId, sessionId, { name } = {}) {
|
|
478
|
+
const readOnly = rt.readOnly()
|
|
479
|
+
if (readOnly !== undefined) threadFail('PRODUCT_READ_ONLY', readOnly)
|
|
480
|
+
const bot = rt.botOf(botId)
|
|
481
|
+
if (!bot || state.bots[botId] !== bot) threadFail('BOT_THREAD_UNKNOWN_BOT', 'Unknown Bot')
|
|
482
|
+
if (!THREAD_ID.test(threadId ?? '') || !THREAD_ID.test(sessionId ?? '')) threadFail('BOT_THREAD_INVALID', 'Invalid thread identity')
|
|
483
|
+
const threads = bot.threads ?? []
|
|
484
|
+
if (threads.length >= THREAD_LIMIT) threadFail('BOT_THREAD_LIMIT', 'Too many threads on one Bot')
|
|
485
|
+
if (threads.some(thread => thread.threadId === threadId)) threadFail('BOT_THREAD_KNOWN', 'That thread already has a Session')
|
|
486
|
+
for (const other of Object.values(state.bots)) {
|
|
487
|
+
for (const thread of other.threads ?? []) if (thread.sessionId === sessionId) threadFail('BOT_THREAD_SESSION_TAKEN', 'That Session already belongs to a thread')
|
|
488
|
+
}
|
|
489
|
+
const record = { threadId, sessionId, createdAt: Date.now(), ...(typeof name === 'string' && name.trim() ? { name: name.trim().slice(0, 80) } : {}) }
|
|
490
|
+
;(bot.threads ??= []).push(record)
|
|
491
|
+
await rt.save()
|
|
492
|
+
// A thread runs on its Bot's model and permission preset, as a fresh part does.
|
|
493
|
+
const ref = await rt.desiredModel(bot)
|
|
494
|
+
if (ref !== undefined) {
|
|
495
|
+
const effort = await rt.desiredEffort(bot, ref)
|
|
496
|
+
await rt.selectSessionModel(sessionId, { ...splitRef(ref), ...(effort === undefined ? {} : { reasoningEffort: effort }) })
|
|
497
|
+
.catch(error => rt.warn('ds-bot: model for a thread of %s not applied: %s', bot.name, error))
|
|
498
|
+
}
|
|
499
|
+
await rt.applyPermission(bot, sessionId)
|
|
500
|
+
.catch(error => rt.warn('ds-bot: permission for a thread of %s not applied: %s', bot.name, error))
|
|
501
|
+
return { ...record }
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function renameThread(botId, threadId, name) {
|
|
505
|
+
const readOnly = rt.readOnly()
|
|
506
|
+
if (readOnly !== undefined) threadFail('PRODUCT_READ_ONLY', readOnly)
|
|
507
|
+
const bot = rt.botOf(botId)
|
|
508
|
+
const thread = bot?.threads?.find(entry => entry.threadId === threadId)
|
|
509
|
+
if (!bot || !thread) threadFail('BOT_THREAD_UNKNOWN', 'Unknown thread')
|
|
510
|
+
if (typeof name !== 'string' || name.trim().length > 80) threadFail('BOT_THREAD_INVALID', 'Invalid thread name')
|
|
511
|
+
thread.name = name.trim()
|
|
512
|
+
await rt.save()
|
|
513
|
+
return { ...thread }
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Archive hides a thread and drops its authority in the same save: the
|
|
517
|
+
// Session and its history stay, and only an explicit restore brings it back.
|
|
518
|
+
async function setThreadArchived(botId, threadId, archivedAt) {
|
|
519
|
+
const readOnly = rt.readOnly()
|
|
520
|
+
if (readOnly !== undefined) threadFail('PRODUCT_READ_ONLY', readOnly)
|
|
521
|
+
const bot = rt.botOf(botId)
|
|
522
|
+
const thread = bot?.threads?.find(entry => entry.threadId === threadId)
|
|
523
|
+
if (!bot || !thread) threadFail('BOT_THREAD_UNKNOWN', 'Unknown thread')
|
|
524
|
+
if (archivedAt === undefined || archivedAt === null) delete thread.archivedAt
|
|
525
|
+
else if (Number.isFinite(archivedAt) && archivedAt >= 0) thread.archivedAt = archivedAt
|
|
526
|
+
else threadFail('BOT_THREAD_INVALID', 'Invalid archive stamp')
|
|
527
|
+
await rt.save()
|
|
528
|
+
return { ...thread }
|
|
529
|
+
}
|
|
530
|
+
|
|
423
531
|
async function setFlags(id, { pinned, hidden }) {
|
|
424
532
|
const entry = rt.botOf(id) ?? rt.roomOf(id) ?? rt.own(state.agents, id)
|
|
425
533
|
if (entry === undefined) throw new Error('Unknown conversation')
|
|
@@ -694,7 +802,7 @@ export function install(rt) {
|
|
|
694
802
|
const archived = rt.archiveBotMemory(id)
|
|
695
803
|
const current = rt.chatOf(id)
|
|
696
804
|
// Questions and exchange turns are kept per Session, so every part of the chat is cleared.
|
|
697
|
-
const parts = new Set([id, current, ...(bot.segments ?? []).map(segment => segment.sessionId)])
|
|
805
|
+
const parts = new Set([id, current, ...(bot.segments ?? []).map(segment => segment.sessionId), ...(bot.threads ?? []).map(thread => thread.sessionId)])
|
|
698
806
|
// `archiving` stands until the archive's path is known: the memory may already be
|
|
699
807
|
// there, and no region of its own closes it yet.
|
|
700
808
|
const closedArchive = bot.private === true ? {
|
|
@@ -769,7 +877,8 @@ export function install(rt) {
|
|
|
769
877
|
}
|
|
770
878
|
|
|
771
879
|
Object.assign(rt, {
|
|
772
|
-
ensureWorkspace, keepTeamFolder, createSession, createBot, duplicateBot, updateBot,
|
|
880
|
+
ensureWorkspace, keepTeamFolder, createSession, createBot, duplicateBot, updateBot, updateBotProfile,
|
|
881
|
+
addThreadSession, renameThread, setThreadArchived, setFlags,
|
|
773
882
|
roomBusy, autoRoomName, retitleRoom, createRoom, updateRoom, deleteRoom,
|
|
774
883
|
setMain, deleteBot, bootstrap, releaseKickoff, greetingOut, holdKickoffAgain, userWrote, userAnswered,
|
|
775
884
|
})
|
|
@@ -430,6 +430,8 @@ export function install(rt) {
|
|
|
430
430
|
const roomOwners = new Map()
|
|
431
431
|
/** @type {Map<string, {botId?:string, roomId?:string}>} */
|
|
432
432
|
const pastOwners = new Map()
|
|
433
|
+
/** @type {Map<string, string>} */
|
|
434
|
+
const threadOwners = new Map()
|
|
433
435
|
const chatOf = botId => botOf(botId)?.sessionId ?? botId
|
|
434
436
|
const sessionOfRoom = room => (room === undefined ? undefined : room.sessionId ?? room.id)
|
|
435
437
|
const indexSessions = () => {
|
|
@@ -437,9 +439,23 @@ export function install(rt) {
|
|
|
437
439
|
groupOwners.clear()
|
|
438
440
|
roomOwners.clear()
|
|
439
441
|
pastOwners.clear()
|
|
442
|
+
threadOwners.clear()
|
|
440
443
|
for (const bot of Object.values(state.bots)) {
|
|
441
444
|
for (const segment of bot.segments ?? []) pastOwners.set(segment.sessionId, { botId: bot.id })
|
|
442
445
|
chatOwners.set(chatOf(bot.id), bot.id)
|
|
446
|
+
// A live thread Session is another own chat of the same Bot: it speaks
|
|
447
|
+
// with the Bot's identity, prompt, tools and money, so it sits in
|
|
448
|
+
// chatOwners beside the Bot's default chat. chatOf() stays the default
|
|
449
|
+
// chat, which is a different thing from any one thread. An archived
|
|
450
|
+
// thread keeps its history readable and gains no authority.
|
|
451
|
+
for (const thread of bot.threads ?? []) {
|
|
452
|
+
if (thread?.sessionId === undefined) continue
|
|
453
|
+
if (thread.archivedAt === undefined || thread.archivedAt === null) {
|
|
454
|
+
if (chatOwners.has(thread.sessionId)) { warn('ds-bot: thread Session %s is owned already; it keeps its first owner', thread.sessionId); continue }
|
|
455
|
+
chatOwners.set(thread.sessionId, bot.id)
|
|
456
|
+
threadOwners.set(thread.sessionId, bot.id)
|
|
457
|
+
} else pastOwners.set(thread.sessionId, { botId: bot.id, threadId: thread.threadId })
|
|
458
|
+
}
|
|
443
459
|
}
|
|
444
460
|
for (const room of Object.values(state.rooms)) {
|
|
445
461
|
const sessionId = sessionOfRoom(room)
|
|
@@ -461,6 +477,22 @@ export function install(rt) {
|
|
|
461
477
|
const selfOf = id => chatOwners.get(id) ?? groupOwners.get(id)?.botId ?? pastOwners.get(id)?.botId
|
|
462
478
|
// A Session where the Bot speaks now, as opposed to an archived segment.
|
|
463
479
|
const isBotSession = id => chatOwners.has(id) || groupOwners.has(id)
|
|
480
|
+
// Threads: several independent own chats under one Bot. threadsOf() is the
|
|
481
|
+
// read-only record list, threadOf() the live owner of one Session, and
|
|
482
|
+
// sessionsOfBot() every Session that speaks for a Bot.
|
|
483
|
+
const threadsOf = botId => (botOf(botId)?.threads ?? []).map(thread => ({ ...thread }))
|
|
484
|
+
const threadOf = sessionId => {
|
|
485
|
+
if (typeof sessionId !== 'string') return undefined
|
|
486
|
+
for (const bot of Object.values(state.bots)) {
|
|
487
|
+
for (const thread of bot.threads ?? []) {
|
|
488
|
+
if (thread.sessionId !== sessionId) continue
|
|
489
|
+
return thread.archivedAt === undefined || thread.archivedAt === null ? { botId: bot.id, threadId: thread.threadId } : undefined
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return undefined
|
|
493
|
+
}
|
|
494
|
+
const threadSessionOf = (botId, threadId) => threadsOf(botId).find(thread => thread.threadId === threadId)?.sessionId
|
|
495
|
+
const sessionsOfBot = botId => [...new Set([botId, chatOf(botId), ...(botOf(botId)?.segments ?? []).map(segment => segment.sessionId), ...threadsOf(botId).map(thread => thread.sessionId), ...groupSessionsOf(botId)])]
|
|
464
496
|
const groupSessionsOf = botId => Object.values(state.rooms).map(room => room.sessions?.[botId]).filter(Boolean)
|
|
465
497
|
const dropGroupSession = (room, botId) => {
|
|
466
498
|
if (room.memberDirs !== undefined) delete room.memberDirs[botId]
|
|
@@ -506,7 +538,8 @@ export function install(rt) {
|
|
|
506
538
|
Object.assign(rt, {
|
|
507
539
|
state, load, save, refresh, readOnly, own,
|
|
508
540
|
botOf, roomOf, isMain, chatOf, sessionOfRoom, selfOf, isBotSession,
|
|
509
|
-
chatOwners, groupOwners, roomOwners, pastOwners,
|
|
541
|
+
chatOwners, groupOwners, roomOwners, pastOwners, threadOwners,
|
|
542
|
+
threadsOf, threadOf, threadSessionOf, sessionsOfBot,
|
|
510
543
|
groupSessionsOf, dropGroupSession, retireGroupSession, findBot, findRooms, isAdminOf,
|
|
511
544
|
})
|
|
512
545
|
}
|
|
@@ -304,7 +304,7 @@ export function install(rt) {
|
|
|
304
304
|
const owners = new Map()
|
|
305
305
|
const botOwner = bot => ({ owner: bot.id, name: bot.name })
|
|
306
306
|
for (const bot of Object.values(state.bots)) {
|
|
307
|
-
for (const sessionId of
|
|
307
|
+
for (const sessionId of rt.sessionsOfBot(bot.id)) owners.set(sessionId, botOwner(bot))
|
|
308
308
|
}
|
|
309
309
|
for (const room of Object.values(state.rooms)) {
|
|
310
310
|
for (const sessionId of [rt.sessionOfRoom(room), ...(room.parts ?? []).map(segment => segment.sessionId)].filter(Boolean)) owners.set(sessionId, { owner: `room:${room.id}`, name: room.name })
|
|
@@ -1,62 +1,152 @@
|
|
|
1
|
-
import { createRemoteTools } from './remote-tools.mjs'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
1
|
+
import { createRemoteTools } from './remote-tools.mjs'
|
|
2
|
+
import { INDEPENDENT_TOOL_NAMES, independentToolEntry } from '../../packages/orchestration/src/independent-tool-catalog.mjs'
|
|
3
|
+
/**
|
|
4
|
+
* Mount the independent Bot runtime as this plugin's own path.
|
|
5
|
+
*
|
|
6
|
+
* This plugin does not have to borrow the Harness delivery pipeline: it owns a session store, a
|
|
7
|
+
* loop and a dispatch point, so a turn is just "input in, answer out" through the Host's model
|
|
8
|
+
* service. The provider below asks the Host's own trusted execution frame for that answer — the
|
|
9
|
+
* committed Bot, the model that Bot has saved, the Bot policy and the one money stream — and the
|
|
10
|
+
* assembly module keeps the two runtimes from ever serving one Bot.
|
|
11
|
+
*
|
|
12
|
+
* Everything is opt-in: without `enabled` the runtime is never constructed, and nothing about the
|
|
13
|
+
* existing Bot integration changes.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { join } from 'node:path'
|
|
17
|
+
import { createIndependentRuntime } from '../../packages/orchestration/src/bot-runtime-assembly.mjs'
|
|
18
|
+
import { collectAnswer } from '../../packages/orchestration/src/bot-independent-stream.mjs'
|
|
19
|
+
|
|
20
|
+
const fail = code => { const error = new Error(code); error.code = code; throw error }
|
|
21
|
+
|
|
22
|
+
export { collectAnswer }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Where this Bot's runtime switch lives. The path is published even when the independent runtime
|
|
26
|
+
* is switched off, because a Bot that was moved before it was switched off still has a committed
|
|
27
|
+
* mode on disk: a Host that cannot read that back has to refuse, not assume integration.
|
|
28
|
+
*/
|
|
29
|
+
export function runtimeSwitchPath({ settings = {}, stateDir } = {}) {
|
|
30
|
+
if (typeof stateDir !== 'string' || stateDir === '') throw new TypeError('RUNTIME_STATE_DIR_REQUIRED')
|
|
31
|
+
return settings.dir ? String(settings.dir) : join(stateDir, 'independent-runtime')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {{
|
|
36
|
+
* ctx: { get: (name: string) => unknown, llm?: object, logger?: object },
|
|
37
|
+
* settings?: { enabled?: boolean, dir?: string, maxSteps?: number,
|
|
38
|
+
* remoteInvoke?: (botId: string, tool: string, args: object) => Promise<unknown>,
|
|
39
|
+
* remoteTools?: string[], toolsFor?: (botId: string) => object | null },
|
|
40
|
+
* stateDir: string,
|
|
41
|
+
* integration?: (request: object) => Promise<object>,
|
|
42
|
+
* serving?: object | null,
|
|
43
|
+
* now?: () => number,
|
|
44
|
+
* }} options
|
|
45
|
+
*/
|
|
46
|
+
export function createPluginRuntime({ ctx, settings = {}, stateDir, integration, serving = null, now = Date.now } = {}) {
|
|
47
|
+
if (settings.enabled !== true) return null
|
|
48
|
+
if (!ctx || typeof ctx.get !== 'function') throw new TypeError('PLUGIN_RUNTIME_REQUIRES_CONTEXT')
|
|
49
|
+
const llm = ctx.get('llm') ?? ctx.llm
|
|
50
|
+
if (!llm || typeof llm.stream !== 'function') throw new TypeError('PLUGIN_RUNTIME_REQUIRES_LLM')
|
|
51
|
+
// The Bot product publishes the only frame an independent turn may run in. It is resolved per
|
|
52
|
+
// call, never captured at construction: the plugin starts the product synchronously and composes
|
|
53
|
+
// it asynchronously, so `connect` may build this runtime before the frame exists. A turn that
|
|
54
|
+
// starts before it does is refused — a Host with no committed Bot, no Bot policy and no money
|
|
55
|
+
// path must not answer from a global provider/model setting.
|
|
56
|
+
const executionOf = () => {
|
|
57
|
+
const execution = (() => { try { return ctx.get('botIndependentExecution') ?? null } catch { return null } })()
|
|
58
|
+
if (!execution || typeof execution.complete !== 'function' || typeof execution.routeFor !== 'function' || typeof execution.assertTurn !== 'function') {
|
|
59
|
+
throw Object.assign(new Error('宿主尚未发布 Bot 产品的独立执行上下文,本次请求被拒绝。'), { code: 'INDEPENDENT_EXECUTION_UNAVAILABLE', outcome: 'refused' })
|
|
60
|
+
}
|
|
61
|
+
return execution
|
|
62
|
+
}
|
|
63
|
+
// Per Bot, per step: the route comes from that Bot's own committed record and the call runs in
|
|
64
|
+
// the Host's frame, so one Bot can neither pick another Bot's model nor answer without policy.
|
|
65
|
+
// The tool schemas travel with the request: they are the ones the Host just re-decided for this
|
|
66
|
+
// step, so the model is only ever told about tools this Bot holds right now.
|
|
67
|
+
const providerFor = botId => async ({ messages, signal, tools, onText, conversationId }) => {
|
|
68
|
+
const execution = executionOf()
|
|
69
|
+
return execution.complete({ botId, ...execution.routeFor(botId), messages, signal, ...(conversationId ? { conversationId } : {}), ...(tools ? { tools } : {}), ...(onText ? { onText } : {}) })
|
|
70
|
+
}
|
|
71
|
+
// Tools are per Bot and Host-bound. The order is the Host's own, decided per call:
|
|
72
|
+
//
|
|
73
|
+
// 1. The Host's published independent tool service (`createIndependentToolHost`), which owns the
|
|
74
|
+
// machine directory, the durable Task journal and the per-Bot catalog. This is the only path
|
|
75
|
+
// on which a write is a tracked Task.
|
|
76
|
+
// 2. An explicit per-Bot resolver the Host passes in (`toolsFor`), for a Host that composes its
|
|
77
|
+
// own surface.
|
|
78
|
+
// 3. The Host's own remote invoker, which carries this Bot's subject, live binding, root and a
|
|
79
|
+
// fresh task/attempt per call. Without a durable Task backend this path offers the model the
|
|
80
|
+
// read-only tools only, and names the rest as unavailable — a Host that cannot track a write
|
|
81
|
+
// must not pretend it can.
|
|
82
|
+
//
|
|
83
|
+
// A Host that offers none of the three offers nothing, and the loop then says so: no shared
|
|
84
|
+
// workspace root is handed to every Bot as a stand-in.
|
|
85
|
+
const hostFor = botId => {
|
|
86
|
+
const host = (() => { try { return ctx.get('botIndependentToolHost') ?? null } catch { return null } })()
|
|
87
|
+
if (host && typeof host.forBot === 'function') return host.forBot(botId) ?? null
|
|
88
|
+
if (typeof settings.toolsFor === 'function') return settings.toolsFor(botId) ?? null
|
|
89
|
+
if (typeof settings.remoteInvoke === 'function') return legacyToolsFor(botId)
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
// The Host's own remote invoker, in the vocabulary the loop speaks. Reads are offered by their
|
|
93
|
+
// catalog names; the machine-tool spellings still dispatch, because a Host that wired `remoteInvoke`
|
|
94
|
+
// already applies its own binding, root and task checks there.
|
|
95
|
+
const legacyToolsFor = botId => {
|
|
96
|
+
const inner = createRemoteTools({
|
|
97
|
+
invoke: (tool, args, context) => settings.remoteInvoke(botId, tool, args, context),
|
|
98
|
+
...(Array.isArray(settings.remoteTools) ? { names: settings.remoteTools } : {}),
|
|
99
|
+
})
|
|
100
|
+
const granted = new Set(inner.names)
|
|
101
|
+
const offered = INDEPENDENT_TOOL_NAMES.map(independentToolEntry)
|
|
102
|
+
.filter(entry => entry && entry.machineTool && entry.backend === null && granted.has(entry.machineTool))
|
|
103
|
+
const unavailable = INDEPENDENT_TOOL_NAMES.map(independentToolEntry)
|
|
104
|
+
.filter(entry => entry && (entry.backend !== null || !granted.has(entry.machineTool)))
|
|
105
|
+
.map(entry => ({ name: entry.name, code: entry.backend ? 'TASK_BACKEND_UNAVAILABLE' : 'MACHINE_TOOL_NOT_GRANTED' }))
|
|
106
|
+
return {
|
|
107
|
+
names: offered.map(entry => entry.name),
|
|
108
|
+
origin: inner.origin,
|
|
109
|
+
isReadonly: name => offered.some(entry => entry.name === name && entry.effect === 'read'),
|
|
110
|
+
schemas: () => offered.map(entry => ({ name: entry.name, description: entry.description, parameters: entry.parameters })),
|
|
111
|
+
unavailable: () => unavailable,
|
|
112
|
+
async call(name, args = {}, context = {}) {
|
|
113
|
+
const entry = offered.find(item => item.name === name)
|
|
114
|
+
return inner.call(entry ? entry.machineTool : name, args, context)
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const runtime = createIndependentRuntime({
|
|
119
|
+
dir: runtimeSwitchPath({ settings, stateDir }),
|
|
120
|
+
integration: typeof integration === 'function' ? integration : async () => fail('INDEPENDENT_RUNTIME_NO_INTEGRATION'),
|
|
121
|
+
providerFor,
|
|
122
|
+
toolsFor: hostFor,
|
|
123
|
+
// The Bot product's own frame re-decides the turn before every transcript write, every
|
|
124
|
+
// provider step and every tool call, so a move, a deletion, a revocation or an emptied grant
|
|
125
|
+
// stops the turn at the next checkpoint instead of finishing on a state that no longer stands.
|
|
126
|
+
assertTurn: botId => executionOf().assertTurn(botId),
|
|
127
|
+
// Published by the Host composition; a model/owner payload can never select
|
|
128
|
+
// the callback or turn a stop request into confirmed native-tree retirement.
|
|
129
|
+
retireConversation: async (botId, details) => {
|
|
130
|
+
const retirement = (() => { try { return ctx.get('botIndependentConversationRetirement') } catch { return null } })()
|
|
131
|
+
if (typeof retirement !== 'function') throw Object.assign(new Error('INDEPENDENT_CONTEXT_RETIREMENT_UNAVAILABLE'), { code: 'INDEPENDENT_CONTEXT_RETIREMENT_UNAVAILABLE', outcome: 'refused' })
|
|
132
|
+
return retirement(botId, details)
|
|
133
|
+
},
|
|
134
|
+
now,
|
|
135
|
+
// The Host's own serving admission: the same lease the integrated stream holds, so a Bot is
|
|
136
|
+
// never written by both paths and a move sees a turn this path is running.
|
|
137
|
+
...(serving ? { serving } : {}),
|
|
138
|
+
...(Number.isSafeInteger(settings.maxSteps) ? { maxSteps: settings.maxSteps } : {}),
|
|
139
|
+
})
|
|
140
|
+
// A Host that unloads must not leave this runtime's loops behind: a turn that never finishes would
|
|
141
|
+
// otherwise sit on its lease until the lease expires. The effect is the Host's own disposal seam and
|
|
142
|
+
// closing twice is a no-op. The Host's tool service is asked to close as well - this runtime is its
|
|
143
|
+
// only consumer - but a Host that owns its lifecycle may close it first, which is also a no-op.
|
|
144
|
+
if (typeof ctx.effect === 'function') {
|
|
145
|
+
ctx.effect(() => async () => {
|
|
146
|
+
const host = (() => { try { return ctx.get('botIndependentToolHost') ?? null } catch { return null } })()
|
|
147
|
+
try { host?.close?.() } catch { /* the Host owns its own lifecycle */ }
|
|
148
|
+
runtime.close()
|
|
149
|
+
}, 'model-router: independent Bot runtime')
|
|
150
|
+
}
|
|
151
|
+
return runtime
|
|
152
|
+
}
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* grant, and exposes the same tool names the model-facing path uses. A name outside the allowed set
|
|
7
7
|
* never reaches `invoke`, so a reply cannot invent a tool.
|
|
8
8
|
*
|
|
9
|
-
* `invoke(tool, args)` must already bind machine, root, Bot, task and operation identity
|
|
10
|
-
*
|
|
9
|
+
* `invoke(tool, args, context)` must already bind machine, root, Bot, task and operation identity,
|
|
10
|
+
* and it receives the turn's cancel signal so a stop reaches the machine layer. Nothing here passes
|
|
11
|
+
* an endpoint, a path outside the root, or an operation id.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
export const REMOTE_TOOL_NAMES = Object.freeze([
|
|
@@ -21,7 +22,7 @@ const fail = (code, message) => { const error = new Error(message ?? code); erro
|
|
|
21
22
|
export const READ_ONLY_TOOLS = Object.freeze(['machine_ping', 'fs_list', 'fs_read', 'operation_status', 'artifact_stat', 'artifact_read'])
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* @param {{ invoke: (tool: string, args: object) => Promise<unknown>, names?: string[], origin?: string }} options
|
|
25
|
+
* @param {{ invoke: (tool: string, args: object, context?: { signal?: AbortSignal, botId?: string }) => Promise<unknown>, names?: string[], origin?: string }} options
|
|
25
26
|
*/
|
|
26
27
|
export function createRemoteTools({ invoke, names = REMOTE_TOOL_NAMES, machines = [], origin = '远程电脑' } = {}) {
|
|
27
28
|
if (typeof invoke !== 'function') throw new TypeError('REMOTE_TOOLS_REQUIRE_INVOKE')
|
|
@@ -37,7 +38,7 @@ export function createRemoteTools({ invoke, names = REMOTE_TOOL_NAMES, machines
|
|
|
37
38
|
/** Whether a name may run beside another call in the same step. */
|
|
38
39
|
isReadonly: name => allowed.includes(name) && READ_ONLY_TOOLS.includes(name),
|
|
39
40
|
origin,
|
|
40
|
-
async call(name, args = {}) {
|
|
41
|
+
async call(name, args = {}, context = {}) {
|
|
41
42
|
if (typeof name !== 'string' || !allowed.includes(name)) fail('TOOL_NOT_GRANTED')
|
|
42
43
|
if (!args || typeof args !== 'object' || Array.isArray(args)) fail('INVALID_TOOL_ARGS')
|
|
43
44
|
if (Object.hasOwn(args, 'machineId') && !machinesAllowed.includes(args.machineId)) fail('MACHINE_NOT_ALLOWED')
|
|
@@ -45,7 +46,9 @@ export function createRemoteTools({ invoke, names = REMOTE_TOOL_NAMES, machines
|
|
|
45
46
|
for (const key of ['rootId', 'botId', 'taskId', 'attemptId', 'operationId', 'endpoint', 'secretRef']) {
|
|
46
47
|
if (Object.hasOwn(args, key)) fail(`TOOL_ARG_FORBIDDEN:${key}`)
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
+
// The turn's cancel travels with the call, so a stop reaches the machine layer instead of
|
|
50
|
+
// being discovered only after the whole round finished.
|
|
51
|
+
return invoke(name, args, { ...(context?.signal ? { signal: context.signal } : {}), ...(context?.botId ? { botId: context.botId } : {}) })
|
|
49
52
|
},
|
|
50
53
|
})
|
|
51
54
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
OpenMausBot protocol components
|
|
2
|
+
Upstream: https://github.com/milind-soni/OpenMausBot
|
|
3
|
+
Pinned commit: 1bc4749aa8307d7d6998bf86de4ef6478460dd93
|
|
4
|
+
Author: Milind Soni and OpenMausBot contributors
|
|
5
|
+
License: Apache License 2.0; see vendor/LICENSE.
|
|
6
|
+
|
|
7
|
+
The companion end-to-end encryption protocol, descriptor, credentials, yamux
|
|
8
|
+
codec and interoperability vectors are copied without modification. Product
|
|
9
|
+
adapters and their permission policy are separate from the frozen upstream
|
|
10
|
+
files. SOURCE.json records each source path and byte hash.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# OpenMausBot 端到端协议
|
|
2
|
+
|
|
3
|
+
冻结复用 `companion/src/e2e` 的 Noise、凭据派生、设备描述符、yamux、流会话与 Node 客户端。来源、固定提交、完整许可证和每文件 SHA-256 位于 `SOURCE.json`、`NOTICE` 与 `vendor/LICENSE`。
|
|
4
|
+
|
|
5
|
+
这是全功能迁移的协议底座,导入协议本身不授予任何 Bot、电脑或模型权限。产品适配器负责一次性配对、设备生命周期、Host 授权与原任务身份;不改冻结源文件。`scripts/openmaus-protocol-entry.mjs` 聚合冻结源码,经固定 esbuild 构建为 `dist/protocol.mjs`;生产 `index.mjs` 只导出 JavaScript。Node 不允许在 node_modules 中直接擦除 TypeScript,因此不能把开发目录中直接导入 `.ts` 的成功当作安装后可用。
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
node scripts/sync-openmaus-protocol.mjs --check
|
|
9
|
+
node scripts/sync-openmaus-protocol.mjs --source ../OpenMausBot
|
|
10
|
+
node scripts/build-openmaus-protocol.mjs
|
|
11
|
+
node scripts/build-openmaus-protocol.mjs --check
|
|
12
|
+
node --test tests/pairing-protocol.test.mjs
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
导入只接受固定提交的可信 checkout;允许 Windows 检出换行差异,写入 vendor 的始终是固定 Git blob 原始字节。更换提交是显式的源升级,不能自动取远端最新版本。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"upstream": "https://github.com/milind-soni/OpenMausBot",
|
|
4
|
+
"commit": "1bc4749aa8307d7d6998bf86de4ef6478460dd93",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"source": "companion/src/e2e/credentials.ts",
|
|
9
|
+
"target": "vendor/companion/src/e2e/credentials.ts",
|
|
10
|
+
"sha256": "1b576030c9e795eb64f5bee9fdc5918d6cfb92f2481b70259f7636991d6f5f6f",
|
|
11
|
+
"bytes": 6328
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"source": "companion/src/e2e/noise.ts",
|
|
15
|
+
"target": "vendor/companion/src/e2e/noise.ts",
|
|
16
|
+
"sha256": "9269403237b43246142d5dd69bee0269d3b41f81c6bfe95520693320b6469250",
|
|
17
|
+
"bytes": 15871
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"source": "companion/src/e2e/descriptor.ts",
|
|
21
|
+
"target": "vendor/companion/src/e2e/descriptor.ts",
|
|
22
|
+
"sha256": "5a7b39e4571e8b83fd85a3b6141cc09501918326afcc58c6ccb4a61344bbb2c8",
|
|
23
|
+
"bytes": 4570
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"source": "companion/src/e2e/yamux.ts",
|
|
27
|
+
"target": "vendor/companion/src/e2e/yamux.ts",
|
|
28
|
+
"sha256": "1292b278d229d3ed8b92a32aec4137e04329d6fb2497d90ffee6f6d4bef20683",
|
|
29
|
+
"bytes": 13359
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"source": "companion/src/e2e/session.ts",
|
|
33
|
+
"target": "vendor/companion/src/e2e/session.ts",
|
|
34
|
+
"sha256": "1ff7d26fe674ccab9f75026fec15f85bd791f99d75b888eaf24b7be744c4da0e",
|
|
35
|
+
"bytes": 14336
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"source": "companion/src/e2e/phone.ts",
|
|
39
|
+
"target": "vendor/companion/src/e2e/phone.ts",
|
|
40
|
+
"sha256": "9fd5a069eecdea29afe5fb00ea2bfc5d42d8aae448927cd9ce3770a8cd2de5d0",
|
|
41
|
+
"bytes": 4083
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"source": "companion/src/e2e/vectors/v1.json",
|
|
45
|
+
"target": "vendor/companion/src/e2e/vectors/v1.json",
|
|
46
|
+
"sha256": "5471a0070ddbb69dca34dee6da550de4581e8e1a40e22e0f97873d539793bf4e",
|
|
47
|
+
"bytes": 204553
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"source": "companion/src/e2e/vectors/v1.sha256",
|
|
51
|
+
"target": "vendor/companion/src/e2e/vectors/v1.sha256",
|
|
52
|
+
"sha256": "99de21e9eef973a92e651517d88e977a1ef91fb441183648115b8a06b24796ee",
|
|
53
|
+
"bytes": 74
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"source": "companion/src/e2e/README.md",
|
|
57
|
+
"target": "vendor/companion/src/e2e/README.md",
|
|
58
|
+
"sha256": "b752ee693e97c9674a207561be536105a34f90e99eaaead49b1f644f2b69589d",
|
|
59
|
+
"bytes": 29207
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"source": "LICENSE",
|
|
63
|
+
"target": "vendor/LICENSE",
|
|
64
|
+
"sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
|
|
65
|
+
"bytes": 11358
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|