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,199 @@
|
|
|
1
|
+
import { codexGoalMessageId, codexItemMessageId } from './codexMessageIdentity.js';
|
|
2
|
+
function textMutation(event) {
|
|
3
|
+
const id = codexItemMessageId(event.turnId, event.itemId);
|
|
4
|
+
if (!id)
|
|
5
|
+
return null;
|
|
6
|
+
const completed = event.type === 'completed' || event.completed === true;
|
|
7
|
+
return {
|
|
8
|
+
operation: 'upsert',
|
|
9
|
+
mode: event.type === 'delta' ? 'append' : 'replace',
|
|
10
|
+
message: {
|
|
11
|
+
id,
|
|
12
|
+
role: 'assistant',
|
|
13
|
+
type: 'text',
|
|
14
|
+
turnId: event.turnId,
|
|
15
|
+
itemId: event.itemId,
|
|
16
|
+
text: event.type === 'delta' ? event.delta ?? '' : event.text ?? '',
|
|
17
|
+
completed,
|
|
18
|
+
streaming: !completed,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function projectCodexConversationMutation(event) {
|
|
23
|
+
if (event.type === 'conversation')
|
|
24
|
+
return event.mutation;
|
|
25
|
+
if (event.type === 'started' || event.type === 'snapshot'
|
|
26
|
+
|| event.type === 'delta' || event.type === 'completed') {
|
|
27
|
+
return textMutation(event);
|
|
28
|
+
}
|
|
29
|
+
if (event.type === 'turnCompleted') {
|
|
30
|
+
return event.turnId ? { operation: 'settleTurn', turnId: event.turnId } : null;
|
|
31
|
+
}
|
|
32
|
+
if (event.type === 'goal') {
|
|
33
|
+
if (['blocked', 'usageLimited', 'budgetLimited', 'complete'].includes(event.goal.status)
|
|
34
|
+
&& !event.turnId)
|
|
35
|
+
return null;
|
|
36
|
+
const id = codexGoalMessageId(event.goal, event.event);
|
|
37
|
+
if (!id)
|
|
38
|
+
return null;
|
|
39
|
+
return {
|
|
40
|
+
operation: 'upsert',
|
|
41
|
+
mode: 'replace',
|
|
42
|
+
message: {
|
|
43
|
+
id,
|
|
44
|
+
role: 'assistant',
|
|
45
|
+
type: 'goal',
|
|
46
|
+
turnId: event.turnId,
|
|
47
|
+
event: event.event,
|
|
48
|
+
goal: event.goal,
|
|
49
|
+
completed: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Clearing the current Goal changes session state, not an already recorded conversation card.
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function recordOf(value) {
|
|
57
|
+
return value != null && typeof value === 'object' && !Array.isArray(value)
|
|
58
|
+
? value
|
|
59
|
+
: null;
|
|
60
|
+
}
|
|
61
|
+
function nonEmpty(value) {
|
|
62
|
+
return typeof value === 'string' && value ? value : null;
|
|
63
|
+
}
|
|
64
|
+
export function parseCodexConversationMutationValue(value) {
|
|
65
|
+
const record = recordOf(value);
|
|
66
|
+
if (!record)
|
|
67
|
+
return null;
|
|
68
|
+
if (record.operation === 'settleTurn') {
|
|
69
|
+
const turnId = nonEmpty(record.turnId);
|
|
70
|
+
return turnId ? { operation: 'settleTurn', turnId } : null;
|
|
71
|
+
}
|
|
72
|
+
if (record.operation !== 'upsert' || !['append', 'replace'].includes(String(record.mode)))
|
|
73
|
+
return null;
|
|
74
|
+
const message = recordOf(record.message);
|
|
75
|
+
if (!message)
|
|
76
|
+
return null;
|
|
77
|
+
const id = nonEmpty(message.id);
|
|
78
|
+
const turnId = nonEmpty(message.turnId);
|
|
79
|
+
if (!id || message.role !== 'assistant' || typeof message.completed !== 'boolean')
|
|
80
|
+
return null;
|
|
81
|
+
if (message.type === 'text') {
|
|
82
|
+
const itemId = nonEmpty(message.itemId);
|
|
83
|
+
if (!turnId || !itemId || id !== codexItemMessageId(turnId, itemId)
|
|
84
|
+
|| typeof message.text !== 'string' || typeof message.streaming !== 'boolean')
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
operation: 'upsert', mode: record.mode,
|
|
88
|
+
message: {
|
|
89
|
+
id, role: 'assistant', type: 'text', turnId, itemId, text: message.text,
|
|
90
|
+
completed: message.completed, streaming: message.streaming,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const goal = recordOf(message.goal);
|
|
95
|
+
const event = nonEmpty(message.event);
|
|
96
|
+
const itemId = nonEmpty(message.itemId);
|
|
97
|
+
const statuses = ['active', 'paused', 'blocked', 'usageLimited', 'budgetLimited', 'complete'];
|
|
98
|
+
const events = ['set', 'restarted', ...statuses];
|
|
99
|
+
const objective = nonEmpty(goal?.objective);
|
|
100
|
+
const numericKeys = ['createdAt', 'updatedAt', 'tokensUsed', 'timeUsedSeconds'];
|
|
101
|
+
const invalidNumber = numericKeys.some((key) => goal && Object.hasOwn(goal, key)
|
|
102
|
+
&& (typeof goal[key] !== 'number' || !Number.isFinite(goal[key])));
|
|
103
|
+
const invalidBudget = goal && Object.hasOwn(goal, 'tokenBudget') && goal.tokenBudget !== null
|
|
104
|
+
&& (typeof goal.tokenBudget !== 'number' || !Number.isFinite(goal.tokenBudget));
|
|
105
|
+
if (message.type !== 'goal' || message.completed !== true || !goal || !objective
|
|
106
|
+
|| !statuses.includes(String(goal.status)) || !event || !events.includes(event)
|
|
107
|
+
|| invalidNumber || invalidBudget
|
|
108
|
+
|| (message.turnId !== null && !turnId)
|
|
109
|
+
|| (id !== codexGoalMessageId(goal, event)
|
|
110
|
+
&& (!turnId || !itemId || id !== codexItemMessageId(turnId, itemId))))
|
|
111
|
+
return null;
|
|
112
|
+
return {
|
|
113
|
+
operation: 'upsert', mode: record.mode,
|
|
114
|
+
message: {
|
|
115
|
+
id, role: 'assistant', type: 'goal', turnId: turnId || null, event,
|
|
116
|
+
goal: goal, completed: true,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// The raw App Server fields remain on the event for compatibility, but consumers only receive this
|
|
121
|
+
// canonical mutation. Reconstructing the mutation from the validated event prevents a forged duplicate
|
|
122
|
+
// payload from smuggling a second identity or lifecycle into the Web projection.
|
|
123
|
+
export function parseCodexConversationMutation(value, event) {
|
|
124
|
+
const expected = projectCodexConversationMutation(event);
|
|
125
|
+
if (!expected)
|
|
126
|
+
return value === null ? null : undefined;
|
|
127
|
+
const supplied = parseCodexConversationMutationValue(value);
|
|
128
|
+
if (!supplied)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (expected.operation === 'settleTurn') {
|
|
131
|
+
if (supplied.operation !== 'settleTurn')
|
|
132
|
+
return undefined;
|
|
133
|
+
return supplied.turnId === expected.turnId ? expected : undefined;
|
|
134
|
+
}
|
|
135
|
+
if (supplied.operation !== 'upsert')
|
|
136
|
+
return undefined;
|
|
137
|
+
const message = supplied.message;
|
|
138
|
+
if (supplied.mode !== expected.mode)
|
|
139
|
+
return undefined;
|
|
140
|
+
const projected = expected.message;
|
|
141
|
+
if (message.id !== projected.id || message.role !== projected.role
|
|
142
|
+
|| message.type !== projected.type || message.turnId !== projected.turnId
|
|
143
|
+
|| message.completed !== projected.completed)
|
|
144
|
+
return undefined;
|
|
145
|
+
if (projected.type === 'text') {
|
|
146
|
+
if (message.type !== 'text')
|
|
147
|
+
return undefined;
|
|
148
|
+
if (message.itemId !== projected.itemId || message.text !== projected.text
|
|
149
|
+
|| message.streaming !== projected.streaming)
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
if (message.type !== 'goal' || message.event !== projected.event)
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return expected;
|
|
157
|
+
}
|
|
158
|
+
export function projectCodexRolloutMutation(value) {
|
|
159
|
+
const message = recordOf(value);
|
|
160
|
+
const id = nonEmpty(message?.id);
|
|
161
|
+
if (!message || !id || message.role !== 'assistant')
|
|
162
|
+
return null;
|
|
163
|
+
if (message.type === 'text') {
|
|
164
|
+
const turnId = nonEmpty(message.turnId);
|
|
165
|
+
const itemId = nonEmpty(message.itemId);
|
|
166
|
+
if (!turnId || !itemId || typeof message.text !== 'string'
|
|
167
|
+
|| id !== codexItemMessageId(turnId, itemId))
|
|
168
|
+
return null;
|
|
169
|
+
return {
|
|
170
|
+
operation: 'upsert', mode: 'replace',
|
|
171
|
+
message: {
|
|
172
|
+
id, role: 'assistant', type: 'text', turnId, itemId, text: message.text,
|
|
173
|
+
completed: true, streaming: false,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (message.type !== 'goal')
|
|
178
|
+
return null;
|
|
179
|
+
const parsed = parseCodexConversationMutationValue({
|
|
180
|
+
operation: 'upsert', mode: 'replace',
|
|
181
|
+
message: { ...message, completed: true },
|
|
182
|
+
});
|
|
183
|
+
return parsed?.operation === 'upsert' ? parsed : null;
|
|
184
|
+
}
|
|
185
|
+
export function reconcileCodexRolloutMessages(previous, liveMessageIds, messages) {
|
|
186
|
+
const fingerprints = new Map();
|
|
187
|
+
const mutations = [];
|
|
188
|
+
for (const value of messages) {
|
|
189
|
+
const mutation = projectCodexRolloutMutation(value);
|
|
190
|
+
if (!mutation || mutation.operation !== 'upsert')
|
|
191
|
+
continue;
|
|
192
|
+
const fingerprint = JSON.stringify(mutation.message);
|
|
193
|
+
fingerprints.set(mutation.message.id, fingerprint);
|
|
194
|
+
if (liveMessageIds.has(mutation.message.id)
|
|
195
|
+
&& previous.get(mutation.message.id) !== fingerprint)
|
|
196
|
+
mutations.push(mutation);
|
|
197
|
+
}
|
|
198
|
+
return { fingerprints, mutations };
|
|
199
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const HUNK_HEADER = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
|
|
2
|
+
function changedLines(lines) {
|
|
3
|
+
return lines.filter((line) => /^[+-]/.test(line)
|
|
4
|
+
&& !line.startsWith('+++') && !line.startsWith('---'));
|
|
5
|
+
}
|
|
6
|
+
export function parseUnifiedDiff(value, { created = false } = {}) {
|
|
7
|
+
const lines = String(value || '').split('\n');
|
|
8
|
+
let added = 0;
|
|
9
|
+
let removed = 0;
|
|
10
|
+
const hunks = [];
|
|
11
|
+
let current = null;
|
|
12
|
+
const flush = () => {
|
|
13
|
+
if (current?.lines.length)
|
|
14
|
+
hunks.push(current);
|
|
15
|
+
current = null;
|
|
16
|
+
};
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
const header = HUNK_HEADER.exec(line);
|
|
19
|
+
if (header) {
|
|
20
|
+
flush();
|
|
21
|
+
current = { oldStart: Number(header[1]), newStart: Number(header[2]), lines: [] };
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (line.startsWith('+') && !line.startsWith('+++'))
|
|
25
|
+
added++;
|
|
26
|
+
else if (line.startsWith('-') && !line.startsWith('---'))
|
|
27
|
+
removed++;
|
|
28
|
+
if (current && /^[ +\-]/.test(line) && !line.startsWith('+++') && !line.startsWith('---')) {
|
|
29
|
+
current.lines.push(line);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
flush();
|
|
33
|
+
return {
|
|
34
|
+
added,
|
|
35
|
+
removed,
|
|
36
|
+
hunks: hunks.length ? hunks : null,
|
|
37
|
+
...(created ? { created: true } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function applyPatchSection(value, path) {
|
|
41
|
+
const lines = String(value || '').split('\n');
|
|
42
|
+
let active = false;
|
|
43
|
+
const section = [];
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const header = /^\*\*\* (?:Add|Update|Delete) File: (.+)$/.exec(line);
|
|
46
|
+
if (header) {
|
|
47
|
+
if (active)
|
|
48
|
+
break;
|
|
49
|
+
active = (header[1] ?? '').trim() === path;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (active && line === '*** End Patch')
|
|
53
|
+
break;
|
|
54
|
+
if (active)
|
|
55
|
+
section.push(line);
|
|
56
|
+
}
|
|
57
|
+
return section;
|
|
58
|
+
}
|
|
59
|
+
function diffKey(lines) {
|
|
60
|
+
return changedLines(lines).join('\0');
|
|
61
|
+
}
|
|
62
|
+
function sameFilePath(left, right) {
|
|
63
|
+
const a = String(left || '').replaceAll('\\', '/').replace(/^\.\//, '');
|
|
64
|
+
const b = String(right || '').replaceAll('\\', '/').replace(/^\.\//, '');
|
|
65
|
+
return a === b || a.endsWith(`/${b}`) || b.endsWith(`/${a}`);
|
|
66
|
+
}
|
|
67
|
+
function candidateDiffs(thread) {
|
|
68
|
+
const candidates = [];
|
|
69
|
+
for (const turn of thread?.turns || []) {
|
|
70
|
+
for (const item of turn?.items || []) {
|
|
71
|
+
if (item?.type !== 'fileChange')
|
|
72
|
+
continue;
|
|
73
|
+
for (const change of item.changes || []) {
|
|
74
|
+
const diff = parseUnifiedDiff(change?.diff, { created: change?.kind?.type === 'add' });
|
|
75
|
+
if (!diff.hunks)
|
|
76
|
+
continue;
|
|
77
|
+
candidates.push({
|
|
78
|
+
turnId: typeof turn.id === 'string' && turn.id ? turn.id : null,
|
|
79
|
+
path: typeof change?.path === 'string' ? change.path : '',
|
|
80
|
+
key: diffKey(String(change?.diff || '').split('\n')),
|
|
81
|
+
diff,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return candidates;
|
|
87
|
+
}
|
|
88
|
+
export function enrichCodexFileDiffs(messages, thread) {
|
|
89
|
+
const candidates = candidateDiffs(thread);
|
|
90
|
+
const used = new Set();
|
|
91
|
+
return messages.map((message) => {
|
|
92
|
+
const tool = message?.tool;
|
|
93
|
+
const positioned = tool?.diff?.hunks?.length
|
|
94
|
+
&& tool.diff.hunks.every((hunk) => Number.isInteger(hunk.oldStart) && Number.isInteger(hunk.newStart));
|
|
95
|
+
if (tool?.name !== 'apply_patch' || !tool.diff || positioned)
|
|
96
|
+
return message;
|
|
97
|
+
const targetDiff = tool.diff;
|
|
98
|
+
const input = Array.isArray(tool.input) ? null : tool.input;
|
|
99
|
+
const path = typeof input?.file_path === 'string' ? input.file_path : '';
|
|
100
|
+
const key = diffKey(applyPatchSection(input?.patch, path));
|
|
101
|
+
const matches = (candidate, index) => !used.has(index)
|
|
102
|
+
&& sameFilePath(candidate.path, path)
|
|
103
|
+
&& candidate.diff.added === targetDiff.added
|
|
104
|
+
&& candidate.diff.removed === targetDiff.removed
|
|
105
|
+
&& (!message.turnId || !candidate.turnId || candidate.turnId === message.turnId);
|
|
106
|
+
let index = candidates.findIndex((candidate, candidateIndex) => matches(candidate, candidateIndex)
|
|
107
|
+
&& (!key || !candidate.key || candidate.key === key));
|
|
108
|
+
if (index < 0 && !key)
|
|
109
|
+
index = candidates.findIndex(matches);
|
|
110
|
+
if (index < 0)
|
|
111
|
+
return message;
|
|
112
|
+
const candidate = candidates[index];
|
|
113
|
+
if (!candidate)
|
|
114
|
+
return message;
|
|
115
|
+
used.add(index);
|
|
116
|
+
return { ...message, tool: { ...tool, diff: candidate.diff } };
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function nonEmpty(value) {
|
|
2
|
+
return typeof value === 'string' && value ? value : null;
|
|
3
|
+
}
|
|
4
|
+
export function codexItemMessageId(turnId, itemId, child = null) {
|
|
5
|
+
const turn = nonEmpty(turnId);
|
|
6
|
+
const item = nonEmpty(itemId);
|
|
7
|
+
if (!turn || !item)
|
|
8
|
+
return null;
|
|
9
|
+
const base = `codex:${turn}:${item}`;
|
|
10
|
+
return child == null ? base : `${base}:child-${encodeURIComponent(String(child))}`;
|
|
11
|
+
}
|
|
12
|
+
export function codexGoalMessageId(goal, event) {
|
|
13
|
+
const lifecycle = nonEmpty(event) || nonEmpty(goal?.status);
|
|
14
|
+
const marker = goal?.createdAt ?? goal?.updatedAt ?? nonEmpty(goal?.objective);
|
|
15
|
+
if (!lifecycle || marker == null)
|
|
16
|
+
return null;
|
|
17
|
+
return `codex-goal:${encodeURIComponent(String(marker))}:${encodeURIComponent(lifecycle)}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const PLAN_STATUSES = new Map([
|
|
2
|
+
['pending', 'pending'],
|
|
3
|
+
['inProgress', 'inProgress'],
|
|
4
|
+
['in_progress', 'inProgress'],
|
|
5
|
+
['completed', 'completed'],
|
|
6
|
+
]);
|
|
7
|
+
// Keep plan projection deliberately small and text-only. App Server uses camelCase statuses while the
|
|
8
|
+
// persisted Code Mode call uses snake_case; the phone receives one stable shape from either source.
|
|
9
|
+
export function normalizeCodexPlan(value) {
|
|
10
|
+
if (!Array.isArray(value))
|
|
11
|
+
return null;
|
|
12
|
+
return value.slice(0, 100).flatMap((item) => {
|
|
13
|
+
const record = item && typeof item === 'object' ? item : null;
|
|
14
|
+
const step = typeof record?.step === 'string' ? record.step.trim().slice(0, 2_000) : '';
|
|
15
|
+
const status = typeof record?.status === 'string' ? PLAN_STATUSES.get(record.status) : undefined;
|
|
16
|
+
return step && status ? [{ step, status }] : [];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function codexPlanSnapshot(turnId, value, explanation = null) {
|
|
20
|
+
const steps = normalizeCodexPlan(value);
|
|
21
|
+
if (!turnId || !steps?.length)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
turnId,
|
|
25
|
+
steps,
|
|
26
|
+
...(typeof explanation === 'string' && explanation.trim()
|
|
27
|
+
? { explanation: explanation.trim().slice(0, 4_000) }
|
|
28
|
+
: {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
function recordOf(value) {
|
|
2
|
+
return value != null && typeof value === 'object' && !Array.isArray(value)
|
|
3
|
+
? value
|
|
4
|
+
: null;
|
|
5
|
+
}
|
|
6
|
+
function stringOf(value) {
|
|
7
|
+
return typeof value === 'string' && value ? value : null;
|
|
8
|
+
}
|
|
9
|
+
function timeOf(value) {
|
|
10
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
11
|
+
}
|
|
12
|
+
export function parseCodexQueueItem(value) {
|
|
13
|
+
const record = recordOf(value);
|
|
14
|
+
if (!record)
|
|
15
|
+
return null;
|
|
16
|
+
const id = stringOf(record.id);
|
|
17
|
+
const text = stringOf(record.text);
|
|
18
|
+
const createdAt = timeOf(record.createdAt);
|
|
19
|
+
if (!id || !text || createdAt == null)
|
|
20
|
+
return null;
|
|
21
|
+
const requestId = record.requestId == null ? undefined : stringOf(record.requestId) ?? undefined;
|
|
22
|
+
if (record.requestId != null && !requestId)
|
|
23
|
+
return null;
|
|
24
|
+
if (record.editing != null && record.editing !== true)
|
|
25
|
+
return null;
|
|
26
|
+
return {
|
|
27
|
+
...record, id, text, createdAt,
|
|
28
|
+
...(requestId ? { requestId } : {}),
|
|
29
|
+
...(record.editing === true ? { editing: true } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function parseCodexSendResult(value) {
|
|
33
|
+
const record = recordOf(value);
|
|
34
|
+
if (!record)
|
|
35
|
+
return null;
|
|
36
|
+
if (record.queued === true) {
|
|
37
|
+
const item = parseCodexQueueItem(record.item);
|
|
38
|
+
return item ? { ...record, queued: true, item } : null;
|
|
39
|
+
}
|
|
40
|
+
if (record.queued != null && record.queued !== false)
|
|
41
|
+
return null;
|
|
42
|
+
const turn = record.turn == null ? record.turn : recordOf(record.turn);
|
|
43
|
+
if (record.turn != null && !turn)
|
|
44
|
+
return null;
|
|
45
|
+
if (turn?.id != null && typeof turn.id !== 'string')
|
|
46
|
+
return null;
|
|
47
|
+
return { ...record, ...(record.queued === false ? { queued: false } : {}), ...(turn ? { turn } : {}) };
|
|
48
|
+
}
|
|
49
|
+
export function parseCodexSubmissionReceipt(value) {
|
|
50
|
+
const record = recordOf(value);
|
|
51
|
+
if (!record)
|
|
52
|
+
return null;
|
|
53
|
+
const pane = stringOf(record.pane);
|
|
54
|
+
const threadId = stringOf(record.threadId);
|
|
55
|
+
const requestId = stringOf(record.requestId);
|
|
56
|
+
const text = stringOf(record.text);
|
|
57
|
+
const statuses = ['pending', 'queued', 'accepted'];
|
|
58
|
+
const status = statuses.find((candidate) => candidate === record.status);
|
|
59
|
+
const createdAt = timeOf(record.createdAt);
|
|
60
|
+
const updatedAt = timeOf(record.updatedAt);
|
|
61
|
+
if (!pane || !threadId || !requestId || !text || !status || createdAt == null || updatedAt == null)
|
|
62
|
+
return null;
|
|
63
|
+
const queueItemId = record.queueItemId == null ? undefined : stringOf(record.queueItemId) ?? undefined;
|
|
64
|
+
const turnId = record.turnId == null ? undefined : stringOf(record.turnId) ?? undefined;
|
|
65
|
+
if ((record.queueItemId != null && !queueItemId) || (record.turnId != null && !turnId))
|
|
66
|
+
return null;
|
|
67
|
+
if (status === 'queued' && !queueItemId)
|
|
68
|
+
return null;
|
|
69
|
+
return {
|
|
70
|
+
pane, threadId, requestId, text, status, createdAt, updatedAt,
|
|
71
|
+
...(queueItemId ? { queueItemId } : {}),
|
|
72
|
+
...(turnId ? { turnId } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function parseCodexSubmissionReceiptView(value) {
|
|
76
|
+
const record = recordOf(value);
|
|
77
|
+
if (!record)
|
|
78
|
+
return null;
|
|
79
|
+
const requestId = stringOf(record.requestId);
|
|
80
|
+
const statuses = ['pending', 'queued', 'accepted'];
|
|
81
|
+
const status = statuses.find((candidate) => candidate === record.status);
|
|
82
|
+
const queueItemId = record.queueItemId == null ? undefined : stringOf(record.queueItemId) ?? undefined;
|
|
83
|
+
const turnId = record.turnId == null ? undefined : stringOf(record.turnId) ?? undefined;
|
|
84
|
+
if (!requestId || !status
|
|
85
|
+
|| (record.queueItemId != null && !queueItemId)
|
|
86
|
+
|| (record.turnId != null && !turnId)
|
|
87
|
+
|| (status === 'queued' && !queueItemId))
|
|
88
|
+
return null;
|
|
89
|
+
return {
|
|
90
|
+
requestId, status,
|
|
91
|
+
...(queueItemId ? { queueItemId } : {}),
|
|
92
|
+
...(turnId ? { turnId } : {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function parseCodexOutboxSnapshot(value) {
|
|
96
|
+
const record = recordOf(value);
|
|
97
|
+
if (!record || record.version !== 1 || !Array.isArray(record.queues) || !Array.isArray(record.receipts)) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const queues = [];
|
|
101
|
+
const queueKeys = new Set();
|
|
102
|
+
for (const candidate of record.queues) {
|
|
103
|
+
const queue = recordOf(candidate);
|
|
104
|
+
if (!queue || !Array.isArray(queue.items))
|
|
105
|
+
return null;
|
|
106
|
+
const pane = stringOf(queue.pane);
|
|
107
|
+
const threadId = stringOf(queue.threadId);
|
|
108
|
+
const items = queue.items.map(parseCodexQueueItem);
|
|
109
|
+
if (!pane || !threadId || items.some((item) => !item))
|
|
110
|
+
return null;
|
|
111
|
+
const queueKey = `${pane}\0${threadId}`;
|
|
112
|
+
if (queueKeys.has(queueKey))
|
|
113
|
+
return null;
|
|
114
|
+
queueKeys.add(queueKey);
|
|
115
|
+
const itemIds = new Set();
|
|
116
|
+
const itemRequestIds = new Set();
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
if (itemIds.has(item.id) || (item.requestId && itemRequestIds.has(item.requestId)))
|
|
119
|
+
return null;
|
|
120
|
+
itemIds.add(item.id);
|
|
121
|
+
if (item.requestId)
|
|
122
|
+
itemRequestIds.add(item.requestId);
|
|
123
|
+
}
|
|
124
|
+
queues.push({ pane, threadId, items: items });
|
|
125
|
+
}
|
|
126
|
+
const receipts = record.receipts.map(parseCodexSubmissionReceipt);
|
|
127
|
+
if (receipts.some((receipt) => !receipt))
|
|
128
|
+
return null;
|
|
129
|
+
const receiptKeys = new Set();
|
|
130
|
+
for (const receipt of receipts) {
|
|
131
|
+
const receiptKey = `${receipt.pane}\0${receipt.threadId}\0${receipt.requestId}`;
|
|
132
|
+
if (receiptKeys.has(receiptKey))
|
|
133
|
+
return null;
|
|
134
|
+
receiptKeys.add(receiptKey);
|
|
135
|
+
}
|
|
136
|
+
return { version: 1, queues, receipts: receipts };
|
|
137
|
+
}
|