handmux 0.23.0 → 0.25.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/README.md +31 -8
- package/README.zh-CN.md +31 -8
- package/bin/handmux.js +14 -688
- package/bin/node-version.js +20 -0
- package/dist/bin/handmux.js +1015 -0
- package/dist/connectors/bridgeClient.js +385 -0
- package/dist/connectors/claude/index.js +476 -0
- package/dist/connectors/pi/bridgeClient.js +8 -0
- package/dist/connectors/pi/index.js +851 -0
- package/dist/hooks/handmux-notify.sh +64 -0
- package/{hooks → dist/hooks}/handmux-statusline.cjs +28 -6
- package/dist/hooks/handmux-write.cjs +264 -0
- package/dist/package.json +127 -0
- package/dist/public/assets/index-CTrqTqtT.css +32 -0
- package/dist/public/assets/index-DmjeMSQA.js +346 -0
- package/dist/public/assets/pcm-worklet-CxcDy7PB.js +1 -0
- package/{public → dist/public}/index.html +5 -4
- package/dist/src/agent-runtime/adapter.js +204 -0
- package/dist/src/agent-runtime/bridge.js +1006 -0
- package/dist/src/agent-runtime/bridgeStore.js +25 -0
- package/dist/src/agent-runtime/bridgeTransport.js +619 -0
- package/dist/src/agent-runtime/bridgeTypes.js +13 -0
- package/dist/src/agent-runtime/builtinRuntime.js +193 -0
- package/dist/src/agent-runtime/conversation.js +2179 -0
- package/dist/src/agent-runtime/conversationActivation.js +75 -0
- package/dist/src/agent-runtime/conversationActivity.js +69 -0
- package/dist/src/agent-runtime/conversationControls.js +206 -0
- package/dist/src/agent-runtime/conversationLiveHub.js +276 -0
- package/dist/src/agent-runtime/conversationStore.js +352 -0
- package/dist/src/agent-runtime/conversationTypes.js +1 -0
- package/dist/src/agent-runtime/conversationValidation.js +339 -0
- package/dist/src/agent-runtime/inbox.js +973 -0
- package/dist/src/agent-runtime/inboxPushProjection.js +69 -0
- package/dist/src/agent-runtime/inboxStore.js +25 -0
- package/dist/src/agent-runtime/inboxTypes.js +1 -0
- package/dist/src/agent-runtime/interaction.js +829 -0
- package/dist/src/agent-runtime/interactionLiveHub.js +225 -0
- package/dist/src/agent-runtime/interactionStore.js +18 -0
- package/dist/src/agent-runtime/interactionTypes.js +1 -0
- package/dist/src/agent-runtime/legacyInboxProjection.js +70 -0
- package/dist/src/agent-runtime/migrateLegacyCodexOutbox.js +336 -0
- package/dist/src/agent-runtime/resourceTypes.js +1 -0
- package/dist/src/agent-runtime/resources.js +245 -0
- package/dist/src/agent-runtime/run.js +328 -0
- package/dist/src/agent-runtime/runtime.js +793 -0
- package/dist/src/agent-runtime/sessionControl.js +164 -0
- package/dist/src/agent-runtime/subscriptionUsage.js +190 -0
- package/dist/src/agent-runtime/subscriptionUsageLegacy.js +18 -0
- package/dist/src/agent-runtime/tmuxRuntime.js +156 -0
- package/dist/src/agents/claude.js +203 -0
- package/dist/src/agents/claudeConversation.js +362 -0
- package/dist/src/agents/claudeInteraction.js +134 -0
- package/dist/src/agents/claudeSubscriptionUsage.js +61 -0
- package/dist/src/agents/codex.js +262 -0
- package/dist/src/agents/codexConversation.js +1103 -0
- package/dist/src/agents/codexConversationActivation.js +121 -0
- package/dist/src/agents/codexConversationControls.js +256 -0
- package/dist/src/agents/codexInteraction.js +306 -0
- package/dist/src/agents/codexSessionControl.js +118 -0
- package/dist/src/agents/codexSubscriptionUsage.js +313 -0
- package/dist/src/agents/codexTerminalControl.js +23 -0
- package/dist/src/agents/conversationProjectionSafety.js +416 -0
- package/dist/src/agents/index.js +39 -0
- package/dist/src/agents/nativeInbox.js +264 -0
- package/dist/src/agents/nativeInboxSources.js +115 -0
- package/dist/src/agents/pi.js +28 -0
- package/dist/src/agents/piConversation.js +523 -0
- package/dist/src/agents/piConversationHistory.js +502 -0
- package/dist/src/agents/piInboxBridge.js +368 -0
- package/dist/src/agents/piSessionControl.js +32 -0
- package/dist/src/agents/processIdentity.js +94 -0
- package/dist/src/agents/scanUtils.js +262 -0
- package/dist/src/apiAccountProviders.js +194 -0
- package/dist/src/apiAccounts.js +592 -0
- package/dist/src/apiErrors.js +86 -0
- package/{src → dist/src}/appName.js +12 -13
- package/dist/src/asr/iflyConfig.js +7 -0
- package/dist/src/asr/iflySign.js +14 -0
- package/dist/src/auth.js +27 -0
- package/dist/src/browser/bootstrap.js +72 -0
- package/dist/src/browser/cookieProfiles.js +413 -0
- package/dist/src/browser/coordinator.js +51 -0
- package/dist/src/browser/hammerheadCompat.js +104 -0
- package/dist/src/browser/hammerheadRedirectCompat.js +37 -0
- package/dist/src/browser/manager.js +560 -0
- package/dist/src/browser/originLabel.js +15 -0
- package/dist/src/browser/profilePersistence.js +235 -0
- package/dist/src/browser/publicProxy.js +176 -0
- package/dist/src/browser/routes.js +159 -0
- package/dist/src/browser/sessionStore.js +72 -0
- package/dist/src/browser/siteVersion.js +121 -0
- package/dist/src/browser/targetPolicy.js +163 -0
- package/{src → dist/src}/browser/worker.js +14 -18
- package/dist/src/browser/workerClient.js +286 -0
- package/dist/src/browser/workerServer.js +91 -0
- package/dist/src/captureBackground.js +95 -0
- package/dist/src/claudeEvents.js +525 -0
- package/dist/src/cli/agentIntegration.js +131 -0
- package/dist/src/cli/agentLauncher.js +26 -0
- package/dist/src/cli/claudeHooks.js +290 -0
- package/{src → dist/src}/cli/cloudflareUrl.js +2 -3
- package/dist/src/cli/cloudflared.js +106 -0
- package/dist/src/cli/codexManaged.js +80 -0
- package/{src → dist/src}/cli/cpolarUrl.js +8 -9
- package/dist/src/cli/drivers.js +98 -0
- package/dist/src/cli/hookScaffold.js +39 -0
- package/dist/src/cli/i18n/en.js +392 -0
- package/{src → dist/src}/cli/i18n/index.js +27 -25
- package/dist/src/cli/i18n/zh.js +391 -0
- package/dist/src/cli/legacyCodexHooks.js +52 -0
- package/{src → dist/src}/cli/natappUrl.js +2 -3
- package/dist/src/cli/options.js +245 -0
- package/dist/src/cli/piExtension.js +155 -0
- package/dist/src/cli/probe.js +16 -0
- package/{src → dist/src}/cli/prompt.js +4 -6
- package/dist/src/cli/pushCmd.js +102 -0
- package/{src → dist/src}/cli/qr.js +19 -19
- package/dist/src/cli/service.js +154 -0
- package/dist/src/cli/setupModel.js +194 -0
- package/dist/src/cli/setupWizard.js +647 -0
- package/dist/src/cli/shortcutEditor.js +351 -0
- package/dist/src/cli/sshTunnel.js +18 -0
- package/dist/src/cli/state.js +117 -0
- package/dist/src/cli/statusLine.js +115 -0
- package/dist/src/cli/supervisor.js +278 -0
- package/dist/src/cli/supervisorHealth.js +21 -0
- package/dist/src/cli/supervisorLaunch.js +148 -0
- package/dist/src/cli/supervisorProcesses.js +62 -0
- package/dist/src/cli/supervisorState.js +62 -0
- package/dist/src/cli/tmuxVersion.js +53 -0
- package/{src → dist/src}/cli/tunlite.js +7 -10
- package/dist/src/cli/tunnelClients.js +72 -0
- package/{src → dist/src}/cli/updateCheck.js +98 -69
- package/{src → dist/src}/cli/urlHost.js +8 -4
- package/dist/src/cli/workspaceCmd.js +347 -0
- package/dist/src/codexAppServer.js +3436 -0
- package/dist/src/codexConversationProjection.js +199 -0
- package/dist/src/codexDiff.js +118 -0
- package/dist/src/codexMessageIdentity.js +18 -0
- package/dist/src/codexPlan.js +30 -0
- package/dist/src/codexQueueProtocol.js +137 -0
- package/dist/src/codexStreamProtocol.js +207 -0
- package/dist/src/codexToolProtocol.js +68 -0
- package/dist/src/codexTranscriptParse.js +835 -0
- package/dist/src/codexUsageSnapshot.js +228 -0
- package/dist/src/config.js +8 -0
- package/{src → dist/src}/docPath.js +22 -24
- package/dist/src/docs.js +337 -0
- package/dist/src/git.js +284 -0
- package/dist/src/healthProtocol.js +97 -0
- package/dist/src/httpApi.js +96 -0
- package/dist/src/jsonStore.js +15 -0
- package/{src → dist/src}/keyNames.js +3 -3
- package/dist/src/notifications.js +111 -0
- package/dist/src/orphans.js +167 -0
- package/dist/src/paneInput.js +42 -0
- package/dist/src/pendingPrompt.js +194 -0
- package/dist/src/previewServer.js +202 -0
- package/dist/src/previews.js +179 -0
- package/dist/src/privateStateStore.js +124 -0
- package/dist/src/projectTask/backup.js +224 -0
- package/dist/src/projectTask/lock.js +108 -0
- package/dist/src/projectTask/migrations.js +90 -0
- package/dist/src/projectTask/routes.js +155 -0
- package/dist/src/projectTask/runtime.js +182 -0
- package/dist/src/projectTask/schema.js +56 -0
- package/dist/src/projectTask/store.js +457 -0
- package/dist/src/push.js +224 -0
- package/dist/src/routes/agents.js +631 -0
- package/dist/src/routes/apiAccounts.js +82 -0
- package/dist/src/routes/files.js +230 -0
- package/dist/src/routes/git.js +89 -0
- package/dist/src/routes/health.js +16 -0
- package/dist/src/routes/notifications.js +16 -0
- package/dist/src/routes/previews.js +43 -0
- package/dist/src/routes/push.js +145 -0
- package/dist/src/routes/sessions.js +244 -0
- package/dist/src/routes/system.js +220 -0
- package/dist/src/routes/terminal.js +244 -0
- package/dist/src/routes/workspace.js +267 -0
- package/dist/src/server.js +315 -0
- package/dist/src/shortcutConfig.js +83 -0
- package/{src → dist/src}/staticCache.js +5 -3
- package/{src → dist/src}/staticCompression.js +5 -6
- package/dist/src/terminalStream.js +441 -0
- package/{src → dist/src}/tmux/commands.js +185 -172
- package/dist/src/tmux/format.js +47 -0
- package/dist/src/transcriptParse.js +261 -0
- package/dist/src/transcriptReader.js +209 -0
- package/{src → dist/src}/trimCapture.js +11 -11
- package/dist/src/uploadTypes.js +31 -0
- package/dist/src/urlPolicy.js +18 -0
- package/dist/src/usagePaths.js +15 -0
- package/dist/src/workspace/agentRunner.js +301 -0
- package/dist/src/workspace/atomicJson.js +31 -0
- package/dist/src/workspace/capture.js +139 -0
- package/dist/src/workspace/checkpointer.js +228 -0
- package/dist/src/workspace/environment.js +71 -0
- package/dist/src/workspace/lock.js +137 -0
- package/dist/src/workspace/mapping.js +128 -0
- package/dist/src/workspace/operations.js +271 -0
- package/dist/src/workspace/paths.js +15 -0
- package/dist/src/workspace/planner.js +292 -0
- package/dist/src/workspace/restore.js +363 -0
- package/dist/src/workspace/runtime.js +318 -0
- package/dist/src/workspace/schema.js +156 -0
- package/dist/src/workspace/store.js +464 -0
- package/dist/src/workspace/tmuxAdapter.js +704 -0
- package/dist/src/workspaceProtocol.js +158 -0
- package/package.json +30 -17
- package/hooks/handmux-notify.sh +0 -22
- package/hooks/handmux-write.cjs +0 -78
- package/public/assets/index-C3N7OkyP.css +0 -32
- package/public/assets/index-D1DWcC5u.js +0 -337
- package/src/.gitkeep +0 -0
- package/src/agents/claude.js +0 -159
- package/src/agents/codex.js +0 -168
- package/src/agents/index.js +0 -21
- package/src/agents/processIdentity.js +0 -72
- package/src/agents/scanUtils.js +0 -225
- package/src/asr/iflyConfig.js +0 -10
- package/src/asr/iflySign.js +0 -16
- package/src/auth.js +0 -30
- package/src/browser/bootstrap.js +0 -68
- package/src/browser/cookieProfiles.js +0 -403
- package/src/browser/coordinator.js +0 -47
- package/src/browser/hammerheadCompat.js +0 -86
- package/src/browser/hammerheadRedirectCompat.js +0 -38
- package/src/browser/manager.js +0 -556
- package/src/browser/originLabel.js +0 -17
- package/src/browser/profilePersistence.js +0 -241
- package/src/browser/publicProxy.js +0 -175
- package/src/browser/routes.js +0 -154
- package/src/browser/sessionStore.js +0 -78
- package/src/browser/siteVersion.js +0 -124
- package/src/browser/targetPolicy.js +0 -137
- package/src/browser/workerClient.js +0 -253
- package/src/browser/workerServer.js +0 -98
- package/src/captureBackground.js +0 -93
- package/src/claudeEvents.js +0 -399
- package/src/cli/claudeHooks.js +0 -206
- package/src/cli/cloudflared.js +0 -99
- package/src/cli/codexManaged.js +0 -77
- package/src/cli/drivers.js +0 -97
- package/src/cli/hookScaffold.js +0 -33
- package/src/cli/i18n/en.js +0 -385
- package/src/cli/i18n/zh.js +0 -384
- package/src/cli/legacyCodexHooks.js +0 -49
- package/src/cli/options.js +0 -211
- package/src/cli/probe.js +0 -16
- package/src/cli/pushCmd.js +0 -60
- package/src/cli/service.js +0 -126
- package/src/cli/setupModel.js +0 -154
- package/src/cli/setupWizard.js +0 -506
- package/src/cli/shortcutEditor.js +0 -288
- package/src/cli/sshTunnel.js +0 -12
- package/src/cli/state.js +0 -90
- package/src/cli/statusLine.js +0 -90
- package/src/cli/supervisor.js +0 -183
- package/src/cli/supervisorProcesses.js +0 -57
- package/src/cli/tmuxVersion.js +0 -49
- package/src/cli/tunnelClients.js +0 -62
- package/src/cli/workspaceCmd.js +0 -300
- package/src/codexAppServer.js +0 -1620
- package/src/codexDiff.js +0 -108
- package/src/codexTranscriptParse.js +0 -413
- package/src/codexUsageSnapshot.cjs +0 -129
- package/src/config.js +0 -9
- package/src/docs.js +0 -228
- package/src/git.js +0 -208
- package/src/httpApi.js +0 -72
- package/src/jsonStore.js +0 -24
- package/src/notifications.js +0 -68
- package/src/orphans.js +0 -152
- package/src/pendingPrompt.js +0 -163
- package/src/previewServer.js +0 -168
- package/src/previews.js +0 -116
- package/src/push.js +0 -183
- package/src/routes/codex.js +0 -473
- package/src/routes/files.js +0 -172
- package/src/routes/git.js +0 -57
- package/src/routes/notifications.js +0 -19
- package/src/routes/previews.js +0 -35
- package/src/routes/push.js +0 -119
- package/src/routes/sessions.js +0 -168
- package/src/routes/system.js +0 -132
- package/src/routes/terminal.js +0 -210
- package/src/routes/transcript.js +0 -171
- package/src/routes/workspace.js +0 -267
- package/src/server.js +0 -186
- package/src/shortcutConfig.js +0 -75
- package/src/terminalStream.js +0 -391
- package/src/tmux/format.js +0 -45
- package/src/transcriptParse.js +0 -180
- package/src/transcriptReader.js +0 -101
- package/src/uploadTypes.js +0 -30
- package/src/urlPolicy.js +0 -14
- package/src/usage.js +0 -260
- package/src/workspace/agentRunner.js +0 -188
- package/src/workspace/atomicJson.js +0 -32
- package/src/workspace/capture.js +0 -139
- package/src/workspace/checkpointer.js +0 -187
- package/src/workspace/environment.js +0 -75
- package/src/workspace/lock.js +0 -128
- package/src/workspace/mapping.js +0 -124
- package/src/workspace/operations.js +0 -252
- package/src/workspace/paths.js +0 -16
- package/src/workspace/planner.js +0 -247
- package/src/workspace/restore.js +0 -279
- package/src/workspace/runtime.js +0 -314
- package/src/workspace/schema.js +0 -136
- package/src/workspace/store.js +0 -382
- package/src/workspace/tmuxAdapter.js +0 -554
- /package/{public → dist/public}/fonts/JetBrainsMonoNerdFontMono-Regular.woff2 +0 -0
- /package/{public → dist/public}/fonts/TWUnifont.woff2 +0 -0
- /package/{public → dist/public}/icons/apple-touch-icon.png +0 -0
- /package/{public → dist/public}/icons/badge-96.png +0 -0
- /package/{public → dist/public}/icons/icon-192-maskable.png +0 -0
- /package/{public → dist/public}/icons/icon-192.png +0 -0
- /package/{public → dist/public}/icons/icon-512-maskable.png +0 -0
- /package/{public → dist/public}/icons/icon-512.png +0 -0
- /package/{public → dist/public}/icons/logo.svg +0 -0
- /package/{public → dist/public}/manifest.webmanifest +0 -0
- /package/{public → dist/public}/offline.html +0 -0
- /package/{public → dist/public}/sw.js +0 -0
- /package/{src → dist/src}/browser/protocol.js +0 -0
- /package/{src → dist/src}/cli/cfNamed.js +0 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { PiConversationHistory } from './piConversationHistory.js';
|
|
4
|
+
import { sanitizeConversationToolDraft, sanitizeConversationToolItem, } from './conversationProjectionSafety.js';
|
|
5
|
+
const ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,255}$/;
|
|
6
|
+
const LEAF_RE = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
7
|
+
const CURRENT_PI_IMPLEMENTATION_VERSION = 6;
|
|
8
|
+
const MIN_MUTATION_COMPATIBLE_PI_IMPLEMENTATION_VERSION = 4;
|
|
9
|
+
const MIN_STEER_COMPATIBLE_PI_IMPLEMENTATION_VERSION = 6;
|
|
10
|
+
const MAX_SNAPSHOT_ACTIVE_TOOLS = 256;
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function isRun(target) {
|
|
15
|
+
return 'runId' in target;
|
|
16
|
+
}
|
|
17
|
+
function snapshotActiveTool(value) {
|
|
18
|
+
if (!isRecord(value) || typeof value.provisionalId !== 'string'
|
|
19
|
+
|| !ID_RE.test(value.provisionalId) || !isRecord(value.draft)
|
|
20
|
+
|| value.draft.kind !== 'tool_call' || typeof value.draft.callId !== 'string'
|
|
21
|
+
|| !ID_RE.test(value.draft.callId) || typeof value.draft.name !== 'string'
|
|
22
|
+
|| value.draft.name.length === 0 || value.draft.name.length > 256)
|
|
23
|
+
return null;
|
|
24
|
+
let settlement;
|
|
25
|
+
if (value.settlement !== undefined) {
|
|
26
|
+
if (!isRecord(value.settlement) || typeof value.settlement.durableItemId !== 'string'
|
|
27
|
+
|| !ID_RE.test(value.settlement.durableItemId) || !isRecord(value.settlement.item))
|
|
28
|
+
return null;
|
|
29
|
+
settlement = {
|
|
30
|
+
durableItemId: value.settlement.durableItemId,
|
|
31
|
+
item: sanitizeConversationToolItem(value.settlement.item),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (value.committedLeafId !== undefined && (typeof value.committedLeafId !== 'string'
|
|
35
|
+
|| !LEAF_RE.test(value.committedLeafId) || settlement === undefined))
|
|
36
|
+
return null;
|
|
37
|
+
return {
|
|
38
|
+
provisionalId: value.provisionalId,
|
|
39
|
+
draft: sanitizeConversationToolDraft(value.draft),
|
|
40
|
+
...(settlement === undefined ? {} : { settlement }),
|
|
41
|
+
...(value.committedLeafId === undefined ? {} : { committedLeafId: value.committedLeafId }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function snapshot(value, sessionId) {
|
|
45
|
+
if (!isRecord(value) || value.sessionId !== sessionId
|
|
46
|
+
|| typeof value.leafId !== 'string' || !LEAF_RE.test(value.leafId)
|
|
47
|
+
|| typeof value.viewId !== 'string' || !ID_RE.test(value.viewId)
|
|
48
|
+
|| (value.implementationVersion !== undefined
|
|
49
|
+
&& (!Number.isSafeInteger(value.implementationVersion)
|
|
50
|
+
|| value.implementationVersion <= 0))
|
|
51
|
+
|| (value.sessionFile !== undefined && (typeof value.sessionFile !== 'string'
|
|
52
|
+
|| !path.isAbsolute(value.sessionFile) || value.sessionFile.length > 4096))
|
|
53
|
+
|| (value.pendingItems !== undefined && !Array.isArray(value.pendingItems))
|
|
54
|
+
|| (value.activeTools !== undefined && (!Array.isArray(value.activeTools)
|
|
55
|
+
|| value.activeTools.length > MAX_SNAPSHOT_ACTIVE_TOOLS)))
|
|
56
|
+
return null;
|
|
57
|
+
const activeTools = value.activeTools === undefined ? undefined
|
|
58
|
+
: value.activeTools.map(snapshotActiveTool);
|
|
59
|
+
if (activeTools?.some((tool) => tool === null))
|
|
60
|
+
return null;
|
|
61
|
+
const parsedActiveTools = activeTools;
|
|
62
|
+
if (parsedActiveTools
|
|
63
|
+
&& new Set(parsedActiveTools.map((tool) => tool.provisionalId)).size !== parsedActiveTools.length) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
// Missing means a pre-handshake Connector. It remains readable, but discovery marks it as stale
|
|
68
|
+
// and disables broken send controls until the user runs Pi's documented `/reload` command.
|
|
69
|
+
implementationVersion: value.implementationVersion === undefined
|
|
70
|
+
? 1 : value.implementationVersion,
|
|
71
|
+
sessionId, leafId: value.leafId, viewId: value.viewId,
|
|
72
|
+
...(value.sessionFile === undefined ? {} : { sessionFile: value.sessionFile }),
|
|
73
|
+
...(value.pendingItems === undefined ? {} : {
|
|
74
|
+
pendingItems: value.pendingItems.map(sanitizeConversationToolItem),
|
|
75
|
+
}),
|
|
76
|
+
...(parsedActiveTools === undefined ? {} : { activeTools: parsedActiveTools }),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function payload(value) {
|
|
80
|
+
if (!isRecord(value) || typeof value.type !== 'string')
|
|
81
|
+
return null;
|
|
82
|
+
if (value.type === 'stream.gap')
|
|
83
|
+
return { type: value.type };
|
|
84
|
+
if (value.type === 'history.changed') {
|
|
85
|
+
return typeof value.leafId === 'string' && LEAF_RE.test(value.leafId)
|
|
86
|
+
&& typeof value.viewId === 'string' && ID_RE.test(value.viewId)
|
|
87
|
+
? { type: value.type, leafId: value.leafId, viewId: value.viewId } : null;
|
|
88
|
+
}
|
|
89
|
+
if (!ID_RE.test(String(value.provisionalId)))
|
|
90
|
+
return null;
|
|
91
|
+
const provisionalId = value.provisionalId;
|
|
92
|
+
if (value.type === 'item.opened' && isRecord(value.draft)) {
|
|
93
|
+
return {
|
|
94
|
+
type: value.type, provisionalId,
|
|
95
|
+
draft: sanitizeConversationToolDraft(value.draft),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (value.type === 'item.delta' && isRecord(value.delta)) {
|
|
99
|
+
if (value.delta.op === 'text.append' && value.delta.target === 'tool_result.content') {
|
|
100
|
+
// Sanitizing chunks independently is unsafe: a credential or absolute path can span chunks.
|
|
101
|
+
// Keep the running provisional and wait for a complete sanitized replace/settlement.
|
|
102
|
+
return { type: 'tool_result.delta_ignored', provisionalId };
|
|
103
|
+
}
|
|
104
|
+
const delta = value.delta.op === 'item.replace' && isRecord(value.delta.draft)
|
|
105
|
+
? {
|
|
106
|
+
...value.delta,
|
|
107
|
+
draft: sanitizeConversationToolDraft(value.delta.draft),
|
|
108
|
+
}
|
|
109
|
+
: value.delta;
|
|
110
|
+
return { type: value.type, provisionalId, delta };
|
|
111
|
+
}
|
|
112
|
+
if (value.type === 'item.settled') {
|
|
113
|
+
if (value.durableItemId !== undefined && !ID_RE.test(String(value.durableItemId)))
|
|
114
|
+
return null;
|
|
115
|
+
if (value.item !== undefined && !isRecord(value.item))
|
|
116
|
+
return null;
|
|
117
|
+
return {
|
|
118
|
+
type: value.type,
|
|
119
|
+
provisionalId,
|
|
120
|
+
...(value.durableItemId === undefined ? {} : { durableItemId: value.durableItemId }),
|
|
121
|
+
...(value.item === undefined ? {} : {
|
|
122
|
+
item: sanitizeConversationToolItem(value.item),
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (value.type === 'item.cancelled') {
|
|
127
|
+
const reasons = ['interrupted', 'superseded', 'provider_error', 'stream_reset'];
|
|
128
|
+
if (value.reason !== undefined && !reasons.includes(String(value.reason)))
|
|
129
|
+
return null;
|
|
130
|
+
return {
|
|
131
|
+
type: value.type,
|
|
132
|
+
provisionalId,
|
|
133
|
+
...(value.reason === undefined ? {} : {
|
|
134
|
+
reason: value.reason,
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const PI_SEND_REASONS = Object.freeze({
|
|
141
|
+
invalid_request: 'invalid_request',
|
|
142
|
+
unsupported: 'unsupported',
|
|
143
|
+
unsupported_delivery: 'unsupported',
|
|
144
|
+
stale_run: 'stale_run',
|
|
145
|
+
conflict: 'conflict',
|
|
146
|
+
agent_busy: 'temporarily_unavailable',
|
|
147
|
+
provider_rejected: 'provider_rejected',
|
|
148
|
+
temporarily_unavailable: 'temporarily_unavailable',
|
|
149
|
+
delivery_unconfirmed: 'delivery_unconfirmed',
|
|
150
|
+
native_delivery_unconfirmed: 'delivery_unconfirmed',
|
|
151
|
+
});
|
|
152
|
+
function dispatchReceipt(value) {
|
|
153
|
+
if (!isRecord(value) || !['accepted', 'queued', 'rejected', 'unknown'].includes(String(value.status))) {
|
|
154
|
+
return { outcome: 'unknown', nativeMutation: 'unknown', reason: 'delivery_unconfirmed' };
|
|
155
|
+
}
|
|
156
|
+
const status = value.status;
|
|
157
|
+
if (status === 'accepted' || status === 'queued')
|
|
158
|
+
return {
|
|
159
|
+
outcome: 'accepted',
|
|
160
|
+
...(typeof value.nativeId === 'string' && value.nativeId ? { nativeId: value.nativeId } : {}),
|
|
161
|
+
};
|
|
162
|
+
if (status === 'rejected' && value.reason === 'agent_busy') {
|
|
163
|
+
return { outcome: 'busy', nativeMutation: false };
|
|
164
|
+
}
|
|
165
|
+
const reason = value.reason === undefined
|
|
166
|
+
? undefined : typeof value.reason === 'string' && value.reason
|
|
167
|
+
? PI_SEND_REASONS[value.reason] ?? (status === 'rejected'
|
|
168
|
+
? 'provider_rejected' : 'delivery_unconfirmed')
|
|
169
|
+
: status === 'rejected' ? 'provider_rejected' : 'delivery_unconfirmed';
|
|
170
|
+
if (status === 'rejected')
|
|
171
|
+
return {
|
|
172
|
+
outcome: 'rejected', nativeMutation: false, reason: reason ?? 'provider_rejected',
|
|
173
|
+
};
|
|
174
|
+
return { outcome: 'unknown', nativeMutation: 'unknown', reason: reason ?? 'delivery_unconfirmed' };
|
|
175
|
+
}
|
|
176
|
+
function interruptReceipt(value) {
|
|
177
|
+
if (!isRecord(value) || !['accepted', 'rejected', 'unknown'].includes(String(value.status))) {
|
|
178
|
+
return { status: 'unknown', reason: 'temporarily_unavailable' };
|
|
179
|
+
}
|
|
180
|
+
const status = value.status;
|
|
181
|
+
const reason = status === 'accepted' || value.reason === undefined
|
|
182
|
+
? undefined : typeof value.reason === 'string' && value.reason
|
|
183
|
+
&& PI_SEND_REASONS[value.reason]
|
|
184
|
+
? PI_SEND_REASONS[value.reason] : 'temporarily_unavailable';
|
|
185
|
+
return {
|
|
186
|
+
status,
|
|
187
|
+
...(reason === undefined ? {} : { reason }),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function createPiConversationActivityReader(host) {
|
|
191
|
+
return {
|
|
192
|
+
async read(run) {
|
|
193
|
+
const value = await host.request(run, 'conversation', 'activity', {});
|
|
194
|
+
if (!isRecord(value) || !['idle', 'working', 'waiting', 'compacting', 'unknown']
|
|
195
|
+
.includes(String(value.activity)))
|
|
196
|
+
return { activity: 'unknown', activeTurn: { state: 'unknown' } };
|
|
197
|
+
const active = isRecord(value.activeTurn) ? value.activeTurn : {};
|
|
198
|
+
return {
|
|
199
|
+
activity: value.activity,
|
|
200
|
+
activeTurn: active.state === 'active' && typeof active.nativeTurnId === 'string'
|
|
201
|
+
? { state: 'active', nativeTurnId: active.nativeTurnId }
|
|
202
|
+
: active.state === 'none' ? { state: 'none' } : { state: 'unknown' },
|
|
203
|
+
...(typeof value.completionToken === 'string' && value.completionToken
|
|
204
|
+
? { completionToken: value.completionToken } : {}),
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export function createPiConversationAdapter({ host, sessionsRoot, history = new PiConversationHistory({
|
|
210
|
+
sessionsRoot: sessionsRoot ?? path.join(os.homedir(), '.pi', 'agent', 'sessions'),
|
|
211
|
+
}), }) {
|
|
212
|
+
if (!host || typeof host.openChannel !== 'function' || typeof host.request !== 'function') {
|
|
213
|
+
throw new TypeError('Pi Conversation adapter requires LocalAgentBridgeHost');
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
apiVersion: 1,
|
|
217
|
+
async discoverNative(target) {
|
|
218
|
+
if (target.agentId !== 'pi' || !target.sessionId)
|
|
219
|
+
return null;
|
|
220
|
+
if (isRun(target))
|
|
221
|
+
history.beginLive(target.sessionId, target.runId);
|
|
222
|
+
const discovered = await history.discover(target.sessionId);
|
|
223
|
+
if (!discovered)
|
|
224
|
+
return null;
|
|
225
|
+
const implementationVersion = isRun(target)
|
|
226
|
+
? target.implementationVersion : discovered.implementationVersion;
|
|
227
|
+
const reloadRequired = isRun(target)
|
|
228
|
+
&& (implementationVersion ?? 1) < CURRENT_PI_IMPLEMENTATION_VERSION;
|
|
229
|
+
const mutationCompatible = isRun(target)
|
|
230
|
+
&& (implementationVersion ?? 1) >= MIN_MUTATION_COMPATIBLE_PI_IMPLEMENTATION_VERSION;
|
|
231
|
+
const steerCompatible = isRun(target)
|
|
232
|
+
&& (implementationVersion ?? 1) >= MIN_STEER_COMPATIBLE_PI_IMPLEMENTATION_VERSION;
|
|
233
|
+
return {
|
|
234
|
+
session: { agentId: 'pi', sessionId: target.sessionId },
|
|
235
|
+
...(isRun(target) ? { run: target } : {}),
|
|
236
|
+
sourceViewId: discovered.sourceViewId,
|
|
237
|
+
capabilities: mutationCompatible ? {
|
|
238
|
+
history: true, live: 'delta',
|
|
239
|
+
sendable: true, send: ['prompt'], interrupt: true, branching: true,
|
|
240
|
+
...(steerCompatible ? { steer: true } : {}),
|
|
241
|
+
} : isRun(target)
|
|
242
|
+
? { history: true, live: 'delta', branching: true }
|
|
243
|
+
: { history: true, live: 'poll', branching: true },
|
|
244
|
+
...(isRun(target) ? {
|
|
245
|
+
implementation: {
|
|
246
|
+
version: implementationVersion ?? 1,
|
|
247
|
+
...(reloadRequired ? { reloadRequired: true } : {}),
|
|
248
|
+
},
|
|
249
|
+
} : {}),
|
|
250
|
+
};
|
|
251
|
+
},
|
|
252
|
+
readNativePage: (session, request) => history.readPage(session, request),
|
|
253
|
+
async observeNative(run, sink) {
|
|
254
|
+
const sessionId = run.ref.sessionId;
|
|
255
|
+
if (!sessionId)
|
|
256
|
+
throw new Error('Pi run has no session');
|
|
257
|
+
history.beginLive(sessionId, run.ref.runId);
|
|
258
|
+
let phase = 'opening';
|
|
259
|
+
let handle;
|
|
260
|
+
let bridgeSequence = 0;
|
|
261
|
+
let sourceSequence = 0;
|
|
262
|
+
let activeLeaf = '';
|
|
263
|
+
let activeView = '';
|
|
264
|
+
let snapshotToolIds = new Set();
|
|
265
|
+
const recoverableToolIds = new Set();
|
|
266
|
+
const provisionalLifecycle = new Map();
|
|
267
|
+
const buffered = [];
|
|
268
|
+
let tail = Promise.resolve();
|
|
269
|
+
const emit = async (operation) => {
|
|
270
|
+
await sink({ ...operation, sourceSequence: ++sourceSequence });
|
|
271
|
+
};
|
|
272
|
+
const acceptOperation = async (operation) => {
|
|
273
|
+
if (operation.type === 'tool_result.delta_ignored')
|
|
274
|
+
return;
|
|
275
|
+
if (operation.type === 'item.opened') {
|
|
276
|
+
const lifecycle = provisionalLifecycle.get(operation.provisionalId);
|
|
277
|
+
if (lifecycle !== undefined && recoverableToolIds.has(operation.provisionalId))
|
|
278
|
+
return;
|
|
279
|
+
provisionalLifecycle.set(operation.provisionalId, 'open');
|
|
280
|
+
}
|
|
281
|
+
else if (operation.type === 'item.settled') {
|
|
282
|
+
const lifecycle = provisionalLifecycle.get(operation.provisionalId);
|
|
283
|
+
if ((lifecycle === 'settled' || lifecycle === 'cancelled')
|
|
284
|
+
&& recoverableToolIds.has(operation.provisionalId))
|
|
285
|
+
return;
|
|
286
|
+
provisionalLifecycle.set(operation.provisionalId, 'settled');
|
|
287
|
+
}
|
|
288
|
+
else if (operation.type === 'item.cancelled') {
|
|
289
|
+
const lifecycle = provisionalLifecycle.get(operation.provisionalId);
|
|
290
|
+
if ((lifecycle === 'settled' || lifecycle === 'cancelled')
|
|
291
|
+
&& recoverableToolIds.has(operation.provisionalId))
|
|
292
|
+
return;
|
|
293
|
+
provisionalLifecycle.set(operation.provisionalId, 'cancelled');
|
|
294
|
+
}
|
|
295
|
+
await emit(operation);
|
|
296
|
+
};
|
|
297
|
+
const restoreSnapshotTools = async (tools, historyLeaf, historicalToolCallIds = new Set()) => {
|
|
298
|
+
snapshotToolIds = new Set(tools.map((tool) => tool.provisionalId));
|
|
299
|
+
for (const tool of tools) {
|
|
300
|
+
recoverableToolIds.add(tool.provisionalId);
|
|
301
|
+
const lifecycle = provisionalLifecycle.get(tool.provisionalId);
|
|
302
|
+
if (lifecycle === undefined) {
|
|
303
|
+
// The Connector keeps a committed tombstone until the next native turn so a live
|
|
304
|
+
// observer can finish its provisional before history advances. A fresh observer already
|
|
305
|
+
// reads that leaf from history and must not recreate the completed tool beside it.
|
|
306
|
+
if (tool.settlement && (tool.committedLeafId === historyLeaf
|
|
307
|
+
|| (tool.draft.kind === 'tool_call'
|
|
308
|
+
&& historicalToolCallIds.has(tool.draft.callId)))) {
|
|
309
|
+
provisionalLifecycle.set(tool.provisionalId, 'settled');
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
provisionalLifecycle.set(tool.provisionalId, 'open');
|
|
313
|
+
await emit({
|
|
314
|
+
type: 'item.opened', provisionalId: tool.provisionalId, draft: tool.draft,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if (tool.settlement && provisionalLifecycle.get(tool.provisionalId) === 'open') {
|
|
318
|
+
provisionalLifecycle.set(tool.provisionalId, 'settled');
|
|
319
|
+
await emit({
|
|
320
|
+
type: 'item.settled', provisionalId: tool.provisionalId,
|
|
321
|
+
durableItemId: tool.settlement.durableItemId,
|
|
322
|
+
item: tool.settlement.item,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const reconcileToolsBeforeHistory = async () => {
|
|
328
|
+
for (const provisionalId of recoverableToolIds) {
|
|
329
|
+
if (snapshotToolIds.has(provisionalId)
|
|
330
|
+
|| provisionalLifecycle.get(provisionalId) !== 'open')
|
|
331
|
+
continue;
|
|
332
|
+
provisionalLifecycle.set(provisionalId, 'cancelled');
|
|
333
|
+
await emit({ type: 'item.cancelled', provisionalId, reason: 'superseded' });
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const emitGap = async () => {
|
|
337
|
+
const afterSourceSequence = sourceSequence;
|
|
338
|
+
await emit({ type: 'stream.gap', afterSourceSequence });
|
|
339
|
+
};
|
|
340
|
+
const process = async (event) => {
|
|
341
|
+
if (phase === 'closed')
|
|
342
|
+
return;
|
|
343
|
+
if (event.type === 'gap') {
|
|
344
|
+
bridgeSequence = Math.max(bridgeSequence, event.afterSequence);
|
|
345
|
+
await emitGap();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (event.type === 'snapshot') {
|
|
349
|
+
const next = snapshot(event.value, sessionId);
|
|
350
|
+
if (!next || event.sequence <= bridgeSequence)
|
|
351
|
+
throw new Error('Invalid Pi Conversation snapshot');
|
|
352
|
+
bridgeSequence = event.sequence;
|
|
353
|
+
const sameView = next.viewId === activeView;
|
|
354
|
+
const sameLeaf = next.leafId === activeLeaf;
|
|
355
|
+
const previousHistoryToken = sameView && sameLeaf
|
|
356
|
+
? (await history.readPage({ agentId: 'pi', sessionId }, { limit: 1 })).sourceHistoryToken
|
|
357
|
+
: undefined;
|
|
358
|
+
history.setLiveSnapshot(sessionId, run.ref.runId, next.leafId, {
|
|
359
|
+
...(next.sessionFile === undefined ? {} : { sessionFile: next.sessionFile }),
|
|
360
|
+
implementationVersion: next.implementationVersion,
|
|
361
|
+
...(next.pendingItems === undefined ? {} : { items: next.pendingItems }),
|
|
362
|
+
}, next.viewId !== activeView ? next.viewId : undefined);
|
|
363
|
+
if (next.viewId !== activeView) {
|
|
364
|
+
activeLeaf = next.leafId;
|
|
365
|
+
activeView = next.viewId;
|
|
366
|
+
await emitGap();
|
|
367
|
+
}
|
|
368
|
+
else if (next.leafId !== activeLeaf) {
|
|
369
|
+
if (next.activeTools)
|
|
370
|
+
await restoreSnapshotTools(next.activeTools, next.leafId);
|
|
371
|
+
activeLeaf = next.leafId;
|
|
372
|
+
const page = await history.readPage({ agentId: 'pi', sessionId }, { limit: 1 });
|
|
373
|
+
if (next.activeTools)
|
|
374
|
+
await reconcileToolsBeforeHistory();
|
|
375
|
+
await emit({
|
|
376
|
+
type: 'history.committed',
|
|
377
|
+
sourceViewId: page.sourceViewId, sourceHistoryToken: page.sourceHistoryToken,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
if (next.activeTools)
|
|
382
|
+
await restoreSnapshotTools(next.activeTools, next.leafId);
|
|
383
|
+
const page = await history.readPage({ agentId: 'pi', sessionId }, { limit: 1 });
|
|
384
|
+
if (page.sourceHistoryToken !== previousHistoryToken) {
|
|
385
|
+
if (next.activeTools)
|
|
386
|
+
await reconcileToolsBeforeHistory();
|
|
387
|
+
await emit({
|
|
388
|
+
type: 'history.committed',
|
|
389
|
+
sourceViewId: page.sourceViewId, sourceHistoryToken: page.sourceHistoryToken,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (event.event.sequence <= bridgeSequence)
|
|
396
|
+
throw new Error('Pi Conversation event is out of order');
|
|
397
|
+
bridgeSequence = event.event.sequence;
|
|
398
|
+
const operation = payload(event.event.payload);
|
|
399
|
+
if (!operation)
|
|
400
|
+
throw new Error('Invalid Pi Conversation event');
|
|
401
|
+
if (operation.type === 'stream.gap') {
|
|
402
|
+
await emitGap();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (operation.type === 'history.changed') {
|
|
406
|
+
history.setLiveSnapshot(sessionId, run.ref.runId, operation.leafId, {}, operation.viewId !== activeView ? operation.viewId : undefined);
|
|
407
|
+
activeLeaf = operation.leafId;
|
|
408
|
+
if (operation.viewId !== activeView) {
|
|
409
|
+
activeView = operation.viewId;
|
|
410
|
+
await emitGap();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const page = await history.readPage({ agentId: 'pi', sessionId }, { limit: 1 });
|
|
414
|
+
await reconcileToolsBeforeHistory();
|
|
415
|
+
await emit({
|
|
416
|
+
type: 'history.committed',
|
|
417
|
+
sourceViewId: page.sourceViewId, sourceHistoryToken: page.sourceHistoryToken,
|
|
418
|
+
});
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
await acceptOperation(operation);
|
|
422
|
+
};
|
|
423
|
+
const failClosed = async () => {
|
|
424
|
+
if (phase === 'closed')
|
|
425
|
+
return;
|
|
426
|
+
try {
|
|
427
|
+
await emitGap();
|
|
428
|
+
}
|
|
429
|
+
catch { /* Core may already have closed the stream */ }
|
|
430
|
+
handle?.close();
|
|
431
|
+
phase = 'closed';
|
|
432
|
+
};
|
|
433
|
+
handle = await host.openChannel(run, 'conversation', (event) => {
|
|
434
|
+
if (phase === 'opening') {
|
|
435
|
+
buffered.push(structuredClone(event));
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
tail = tail.then(() => process(event)).catch(failClosed);
|
|
439
|
+
return tail;
|
|
440
|
+
});
|
|
441
|
+
if (handle.snapshotAvailability !== 'ready') {
|
|
442
|
+
handle.close();
|
|
443
|
+
throw new Error('Pi Conversation snapshot is unavailable');
|
|
444
|
+
}
|
|
445
|
+
const baseline = snapshot(handle.snapshot, sessionId);
|
|
446
|
+
if (!baseline) {
|
|
447
|
+
handle.close();
|
|
448
|
+
throw new Error('Pi Conversation snapshot is invalid');
|
|
449
|
+
}
|
|
450
|
+
history.setLiveSnapshot(sessionId, run.ref.runId, baseline.leafId, {
|
|
451
|
+
...(baseline.sessionFile === undefined ? {} : { sessionFile: baseline.sessionFile }),
|
|
452
|
+
implementationVersion: baseline.implementationVersion,
|
|
453
|
+
...(baseline.pendingItems === undefined ? {} : { items: baseline.pendingItems }),
|
|
454
|
+
});
|
|
455
|
+
activeLeaf = baseline.leafId;
|
|
456
|
+
activeView = baseline.viewId;
|
|
457
|
+
bridgeSequence = handle.streamSequence;
|
|
458
|
+
const discovered = await history.discover(sessionId);
|
|
459
|
+
if (!discovered) {
|
|
460
|
+
handle.close();
|
|
461
|
+
throw new Error('Pi Conversation snapshot is not readable yet');
|
|
462
|
+
}
|
|
463
|
+
// A durability wait may time out just before Pi flushes JSONL. On a later observation the
|
|
464
|
+
// Connector snapshot can still contain an uncommitted settlement even though native history
|
|
465
|
+
// already owns that call. Read the authoritative branch once and suppress only those settled
|
|
466
|
+
// synthetic tools whose callId is now present there; running tools always remain provisional.
|
|
467
|
+
const openingHistory = await history.readPage({ agentId: 'pi', sessionId }, { limit: Number.MAX_SAFE_INTEGER });
|
|
468
|
+
const historicalToolCallIds = new Set(openingHistory.items.flatMap((item) => (item.kind === 'tool_call' ? [item.callId] : [])));
|
|
469
|
+
if (baseline.activeTools) {
|
|
470
|
+
await restoreSnapshotTools(baseline.activeTools, baseline.leafId, historicalToolCallIds);
|
|
471
|
+
}
|
|
472
|
+
phase = 'live';
|
|
473
|
+
for (const event of buffered.splice(0)) {
|
|
474
|
+
if (event.type === 'event' && event.event.sequence <= bridgeSequence)
|
|
475
|
+
continue;
|
|
476
|
+
if (event.type === 'snapshot' && event.sequence <= bridgeSequence)
|
|
477
|
+
continue;
|
|
478
|
+
tail = tail.then(() => process(event));
|
|
479
|
+
}
|
|
480
|
+
tail = tail.catch(failClosed);
|
|
481
|
+
return {
|
|
482
|
+
checkpoint: {
|
|
483
|
+
sourceViewId: discovered.sourceViewId,
|
|
484
|
+
// Snapshot-restored provisional items are opening suffix events, not part of history.
|
|
485
|
+
// Returning zero lets Conversation Core apply any number of them without inventing source
|
|
486
|
+
// sequence values that could collide with the next Bridge operation.
|
|
487
|
+
sourceSequence: 0,
|
|
488
|
+
},
|
|
489
|
+
close() {
|
|
490
|
+
if (phase === 'closed')
|
|
491
|
+
return;
|
|
492
|
+
phase = 'closed';
|
|
493
|
+
buffered.splice(0);
|
|
494
|
+
handle?.close();
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
},
|
|
498
|
+
async dispatchPrompt(run, request) {
|
|
499
|
+
const response = await host.request(run, 'conversation', 'send', {
|
|
500
|
+
clientRequestId: request.clientRequestId,
|
|
501
|
+
text: request.text,
|
|
502
|
+
delivery: 'prompt',
|
|
503
|
+
});
|
|
504
|
+
return dispatchReceipt(response);
|
|
505
|
+
},
|
|
506
|
+
async dispatchSteer(run, request) {
|
|
507
|
+
if ((run.ref.implementationVersion ?? 1) < MIN_STEER_COMPATIBLE_PI_IMPLEMENTATION_VERSION) {
|
|
508
|
+
return { outcome: 'rejected', nativeMutation: false, reason: 'unsupported' };
|
|
509
|
+
}
|
|
510
|
+
const response = await host.request(run, 'conversation', 'send', {
|
|
511
|
+
clientRequestId: request.clientRequestId,
|
|
512
|
+
text: request.text,
|
|
513
|
+
origin: 'steer',
|
|
514
|
+
delivery: request.plan.kind === 'steer-active-turn' ? 'steer' : 'prompt',
|
|
515
|
+
plan: request.plan,
|
|
516
|
+
});
|
|
517
|
+
return dispatchReceipt(response);
|
|
518
|
+
},
|
|
519
|
+
async dispatchInterrupt(run) {
|
|
520
|
+
return interruptReceipt(await host.request(run, 'conversation', 'interrupt', {}));
|
|
521
|
+
},
|
|
522
|
+
};
|
|
523
|
+
}
|