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,158 @@
|
|
|
1
|
+
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
2
|
+
const RESTORE_REQUEST_FIELDS = new Set(['checkpointId', 'sessions']);
|
|
3
|
+
const PROTECTION_STATUSES = ['protected', 'unprotected', 'degraded'];
|
|
4
|
+
const PLAN_ACTIONS = ['create', 'create-renamed', 'already-present', 'unsupported'];
|
|
5
|
+
const OPERATION_STATUSES = ['pending', 'running', 'succeeded', 'partial', 'failed', 'interrupted'];
|
|
6
|
+
const RESULT_STATUSES = ['restored', 'already-present', 'failed'];
|
|
7
|
+
const RESULT_STAGES = ['plan', 'topology', 'agent', 'restore', 'reconcile'];
|
|
8
|
+
const recordOf = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
9
|
+
? value : null);
|
|
10
|
+
const memberOf = (values, value) => (typeof value === 'string' && values.includes(value) ? value : undefined);
|
|
11
|
+
const finiteNumber = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
|
|
12
|
+
const optionalString = (value) => (typeof value === 'string' ? value : value === null ? null : undefined);
|
|
13
|
+
const stringArray = (value) => (Array.isArray(value) ? value.filter((item) => typeof item === 'string') : []);
|
|
14
|
+
const optionalField = (key, value) => (value === undefined ? {} : { [key]: value });
|
|
15
|
+
export function isWorkspaceSafeId(value) {
|
|
16
|
+
return typeof value === 'string' && SAFE_ID.test(value) && value !== '.' && value !== '..';
|
|
17
|
+
}
|
|
18
|
+
export function parseWorkspaceRestoreRequest(value, defaultCheckpointId = 'latest') {
|
|
19
|
+
const request = recordOf(value);
|
|
20
|
+
if (!request || Object.keys(request).some((key) => !RESTORE_REQUEST_FIELDS.has(key)))
|
|
21
|
+
return null;
|
|
22
|
+
const checkpointId = request.checkpointId === undefined
|
|
23
|
+
? defaultCheckpointId
|
|
24
|
+
: request.checkpointId;
|
|
25
|
+
if (!isWorkspaceSafeId(checkpointId))
|
|
26
|
+
return null;
|
|
27
|
+
const parsed = { checkpointId };
|
|
28
|
+
if (request.sessions !== undefined) {
|
|
29
|
+
if (!Array.isArray(request.sessions)
|
|
30
|
+
|| request.sessions.some((name) => typeof name !== 'string' || !name || name.length > 256
|
|
31
|
+
|| /[\x00-\x1f\x7f]/.test(name)))
|
|
32
|
+
return null;
|
|
33
|
+
parsed.sessions = [...request.sessions];
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
export function parseWorkspaceProtectionStatus(value) {
|
|
38
|
+
const status = recordOf(value);
|
|
39
|
+
if (!status)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
...optionalField('status', memberOf(PROTECTION_STATUSES, status.status)),
|
|
43
|
+
...optionalField('lastSuccessfulCaptureAt', optionalString(status.lastSuccessfulCaptureAt)),
|
|
44
|
+
...optionalField('errorCode', optionalString(status.errorCode)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function parseWorkspaceRestoreStart(value) {
|
|
48
|
+
const start = recordOf(value);
|
|
49
|
+
if (!start)
|
|
50
|
+
return null;
|
|
51
|
+
return {
|
|
52
|
+
...optionalField('operationId', typeof start.operationId === 'string' ? start.operationId : undefined),
|
|
53
|
+
...optionalField('status', memberOf(OPERATION_STATUSES, start.status)),
|
|
54
|
+
...optionalField('reused', typeof start.reused === 'boolean' ? start.reused : undefined),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const countsOf = (value) => {
|
|
58
|
+
const counts = recordOf(value);
|
|
59
|
+
if (!counts)
|
|
60
|
+
return undefined;
|
|
61
|
+
return {
|
|
62
|
+
...optionalField('sessions', finiteNumber(counts.sessions)),
|
|
63
|
+
...optionalField('windows', finiteNumber(counts.windows)),
|
|
64
|
+
...optionalField('panes', finiteNumber(counts.panes)),
|
|
65
|
+
...optionalField('agents', finiteNumber(counts.agents)),
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
const planSessionOf = (value) => {
|
|
69
|
+
const session = recordOf(value);
|
|
70
|
+
return session ? {
|
|
71
|
+
...optionalField('logicalId', optionalString(session.logicalId)),
|
|
72
|
+
...optionalField('sourceName', optionalString(session.sourceName)),
|
|
73
|
+
...optionalField('targetName', optionalString(session.targetName)),
|
|
74
|
+
...optionalField('action', session.action === null ? null : memberOf(PLAN_ACTIONS, session.action)),
|
|
75
|
+
} : null;
|
|
76
|
+
};
|
|
77
|
+
export function parseWorkspaceRecoveryPlan(value) {
|
|
78
|
+
const plan = recordOf(value);
|
|
79
|
+
if (!plan)
|
|
80
|
+
return null;
|
|
81
|
+
const rawPlanSummary = recordOf(plan.planSummary);
|
|
82
|
+
const summary = countsOf(plan.summary);
|
|
83
|
+
const planSummary = rawPlanSummary ? {
|
|
84
|
+
...countsOf(rawPlanSummary),
|
|
85
|
+
...optionalField('create', finiteNumber(rawPlanSummary.create)),
|
|
86
|
+
...optionalField('renamed', finiteNumber(rawPlanSummary.renamed)),
|
|
87
|
+
...optionalField('alreadyPresent', finiteNumber(rawPlanSummary.alreadyPresent)),
|
|
88
|
+
...optionalField('unsupported', finiteNumber(rawPlanSummary.unsupported)),
|
|
89
|
+
} : undefined;
|
|
90
|
+
const pendingCount = finiteNumber(plan.pendingCount)
|
|
91
|
+
?? (plan.pendingCount === null ? null : undefined);
|
|
92
|
+
return {
|
|
93
|
+
...optionalField('checkpointId', optionalString(plan.checkpointId)),
|
|
94
|
+
...optionalField('capturedAt', optionalString(plan.capturedAt)),
|
|
95
|
+
...optionalField('changeReason', optionalString(plan.changeReason)),
|
|
96
|
+
...optionalField('serverNow', optionalString(plan.serverNow)),
|
|
97
|
+
...optionalField('expiresAt', optionalString(plan.expiresAt)),
|
|
98
|
+
...optionalField('promptEligible', typeof plan.promptEligible === 'boolean' ? plan.promptEligible : undefined),
|
|
99
|
+
...optionalField('resolved', typeof plan.resolved === 'boolean' ? plan.resolved : undefined),
|
|
100
|
+
...optionalField('pendingCount', pendingCount),
|
|
101
|
+
...optionalField('summary', summary),
|
|
102
|
+
...optionalField('planSummary', planSummary),
|
|
103
|
+
...(Array.isArray(plan.sessions) ? { sessions: plan.sessions.flatMap((session) => {
|
|
104
|
+
const parsed = planSessionOf(session);
|
|
105
|
+
return parsed ? [parsed] : [];
|
|
106
|
+
}) } : {}),
|
|
107
|
+
...(Array.isArray(plan.warningCodes) ? { warningCodes: stringArray(plan.warningCodes) } : {}),
|
|
108
|
+
...(plan.mapping !== undefined ? { mapping: plan.mapping } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function parseWorkspaceRestoreOperation(value) {
|
|
112
|
+
const operation = recordOf(value);
|
|
113
|
+
if (!operation)
|
|
114
|
+
return null;
|
|
115
|
+
const progress = recordOf(operation.progress);
|
|
116
|
+
const request = recordOf(operation.request);
|
|
117
|
+
const results = Array.isArray(operation.results)
|
|
118
|
+
? operation.results.flatMap((candidate) => {
|
|
119
|
+
const result = recordOf(candidate);
|
|
120
|
+
const session = planSessionOf(candidate);
|
|
121
|
+
if (!result || !session)
|
|
122
|
+
return [];
|
|
123
|
+
return [{
|
|
124
|
+
...session,
|
|
125
|
+
...optionalField('status', result.status === null ? null : memberOf(RESULT_STATUSES, result.status)),
|
|
126
|
+
...optionalField('stage', result.stage === null ? null : memberOf(RESULT_STAGES, result.stage)),
|
|
127
|
+
...optionalField('errorCode', optionalString(result.errorCode)),
|
|
128
|
+
...optionalField('errorMessage', optionalString(result.errorMessage)),
|
|
129
|
+
warningCodes: stringArray(result.warningCodes),
|
|
130
|
+
}];
|
|
131
|
+
}) : [];
|
|
132
|
+
const parsedProgress = progress ? {
|
|
133
|
+
...optionalField('completed', finiteNumber(progress.completed)),
|
|
134
|
+
...optionalField('total', finiteNumber(progress.total)),
|
|
135
|
+
} : undefined;
|
|
136
|
+
const checkpointId = typeof request?.checkpointId === 'string' ? request.checkpointId : undefined;
|
|
137
|
+
const parsedRequest = request && checkpointId ? {
|
|
138
|
+
checkpointId,
|
|
139
|
+
...(Array.isArray(request.sessions) ? { sessions: stringArray(request.sessions) } : {}),
|
|
140
|
+
...(typeof request.historical === 'boolean' ? { historical: request.historical } : {}),
|
|
141
|
+
} : undefined;
|
|
142
|
+
return {
|
|
143
|
+
...(typeof operation.id === 'string' ? { id: operation.id } : {}),
|
|
144
|
+
...optionalField('status', operation.status === null ? null : memberOf(OPERATION_STATUSES, operation.status)),
|
|
145
|
+
...optionalField('request', parsedRequest),
|
|
146
|
+
...optionalField('createdAt', optionalString(operation.createdAt)),
|
|
147
|
+
...optionalField('updatedAt', optionalString(operation.updatedAt)),
|
|
148
|
+
...optionalField('startedAt', optionalString(operation.startedAt)),
|
|
149
|
+
...optionalField('completedAt', optionalString(operation.completedAt)),
|
|
150
|
+
...optionalField('progress', parsedProgress),
|
|
151
|
+
...optionalField('summary', countsOf(operation.summary)),
|
|
152
|
+
...(Array.isArray(operation.results) ? { results } : {}),
|
|
153
|
+
...optionalField('errorCode', optionalString(operation.errorCode)),
|
|
154
|
+
...optionalField('errorMessage', optionalString(operation.errorMessage)),
|
|
155
|
+
...(Array.isArray(operation.warningCodes) ? { warningCodes: stringArray(operation.warningCodes) } : {}),
|
|
156
|
+
...(operation.mapping !== undefined ? { mapping: operation.mapping } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handmux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "A mobile vibe-coding cockpit — built on tmux: drive your live session, Claude Code / Codex — anything a terminal can run — from your phone.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -22,27 +22,32 @@
|
|
|
22
22
|
"pwa"
|
|
23
23
|
],
|
|
24
24
|
"engines": {
|
|
25
|
-
"node": ">=
|
|
25
|
+
"node": ">=22.16.0"
|
|
26
26
|
},
|
|
27
27
|
"bin": {
|
|
28
28
|
"handmux": "bin/handmux.js"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
|
-
"bin",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"README.zh-CN.md"
|
|
31
|
+
"bin/handmux.js",
|
|
32
|
+
"bin/node-version.js",
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"README.zh-CN.md",
|
|
36
|
+
"LICENSE"
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
38
|
-
"start": "node bin/handmux.js start",
|
|
39
|
-
"dev": "node bin/handmux.js start --foreground",
|
|
40
|
-
"handmux": "node bin/handmux.js",
|
|
39
|
+
"start": "npm run build:server && node dist/bin/handmux.js start",
|
|
40
|
+
"dev": "npm run build:server && node dist/bin/handmux.js start --foreground",
|
|
41
|
+
"handmux": "npm run build:server && node dist/bin/handmux.js",
|
|
42
|
+
"build:server": "node scripts/build-server.mjs",
|
|
41
43
|
"bundle": "node scripts/bundle-web.mjs",
|
|
42
|
-
"prepack": "
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"test
|
|
44
|
+
"prepack": "npm run bundle && npm run build:server",
|
|
45
|
+
"typecheck:source": "node scripts/check-typescript-product-source.mjs",
|
|
46
|
+
"typecheck": "tsc --project tsconfig.json",
|
|
47
|
+
"test": "npm run build:server && vitest run",
|
|
48
|
+
"test:pack": "node scripts/pack-smoke.mjs",
|
|
49
|
+
"test:workspace-host": "npm run build:server && HANDMUX_WORKSPACE_HOST=1 vitest run test/workspaceHost.test.js",
|
|
50
|
+
"test:workspace-docker": "npm run build:server && HANDMUX_WORKSPACE_DOCKER=1 vitest run test/workspaceDocker.test.js"
|
|
46
51
|
},
|
|
47
52
|
"dependencies": {
|
|
48
53
|
"@clack/prompts": "0.11.0",
|
|
@@ -58,17 +63,25 @@
|
|
|
58
63
|
"qrcode-terminal": "^0.12.0"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
66
|
+
"@types/busboy": "^1.5.4",
|
|
67
|
+
"@types/compression": "^1.8.1",
|
|
68
|
+
"@types/express": "4.17.25",
|
|
69
|
+
"@types/node": "22.20.1",
|
|
70
|
+
"@types/supertest": "7.2.1",
|
|
71
|
+
"@types/web-push": "^3.6.4",
|
|
72
|
+
"@types/ws": "^8.18.1",
|
|
61
73
|
"supertest": "^7.0.0",
|
|
74
|
+
"typescript": "6.0.3",
|
|
62
75
|
"vitest": "^2.0.0"
|
|
63
76
|
},
|
|
64
77
|
"whatsNew": [
|
|
78
|
+
{"version":"0.25.0","date":"2026-09-01","zh":"Pi 原生接入 · 多 Agent 对话更可靠","en":"Native Pi support · more reliable agent chat"},
|
|
79
|
+
{"version":"0.24.0","date":"2026-08-11","zh":"Codex 任务进度与 Goal 原生上线","en":"Native Codex task progress and Goals"},
|
|
65
80
|
{"version":"0.23.0","date":"2026-08-09","zh":"Codex 托管对话正式上线","en":"Managed Codex chat is here"},
|
|
66
81
|
{"version":"0.22.1","date":"2026-08-04","zh":"网页宽度与网站版本分开控制","en":"Separate page width and site-version controls"},
|
|
67
82
|
{"version":"0.22.0","date":"2026-08-03","zh":"全屏设置 · 窗口与分屏管理更清晰","en":"Full-screen Settings · clearer window and pane management"},
|
|
68
83
|
{"version":"0.21.0","date":"2026-08-03","zh":"网页预览:手机直连、电脑代理与静态目录","en":"Web preview: direct, computer proxy, and static folders"},
|
|
69
84
|
{"version":"0.20.3","date":"2026-08-01","zh":"实时终端显示更稳定流畅","en":"More stable, smoother live terminals"},
|
|
70
|
-
{"version":"0.20.2","date":"2026-07-27","zh":"实时终端长时间运行更流畅","en":"Smoother long-running live terminals"}
|
|
71
|
-
{"version":"0.20.1","date":"2026-07-26","zh":"聊天发送修复 · 电脑键盘不中断","en":"Reliable chat sending · uninterrupted desktop input"},
|
|
72
|
-
{"version":"0.20.0","date":"2026-07-26","zh":"终端实时推送 · 弱网自动回退","en":"Live terminal pushing · automatic network fallback"}
|
|
85
|
+
{"version":"0.20.2","date":"2026-07-27","zh":"实时终端长时间运行更流畅","en":"Smoother long-running live terminals"}
|
|
73
86
|
]
|
|
74
87
|
}
|
package/hooks/handmux-notify.sh
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# handmux 上报 hook. $1 = stop | notify | prompt | end | resume | permreq | compacting | compact | stopfail.
|
|
3
|
-
# (compacting/compact/stopfail 是版本门控事件:PreCompact/PostCompact/StopFailure,只在够新的 Claude 上注册。)
|
|
4
|
-
# stdin = Claude Code 原始 payload(JSON).
|
|
5
|
-
# (resume = PostToolUse on AskUserQuestion/ExitPlanMode:答完选项/批准计划 → 状态翻回进行中、带所选项。)
|
|
6
|
-
# (permreq = PermissionRequest:真实弹框一出现就发、带 tool_name → 比 permission_prompt 早亮「需要你」。)
|
|
7
|
-
# 只做一件事:把本次事件写进一个本地 JSON 状态文件(键=tmux pane,值=该 pane 最新事件)。不联网、
|
|
8
|
-
# 不依赖服务进程是否在跑 → 永不阻塞 Claude(始终 exit 0)。服务端按需读这个文件(见 claudeEvents.js)。
|
|
9
|
-
# 真正的读-改-写交给同目录的 handmux-write.js(node:真 JSON 解析 + 文件锁,多 pane 并发 hook 不丢更新)。
|
|
10
|
-
CFG="$(dirname "$0")/handmux-notify.env"
|
|
11
|
-
[ -f "$CFG" ] && . "$CFG"
|
|
12
|
-
PANE="${CLAUDE_PANE:-$TMUX_PANE}"
|
|
13
|
-
[ -z "$PANE" ] && exit 0 # 不在 tmux 里(没有 pane 可定位)→ 暂不记录
|
|
14
|
-
FILE="${HANDMUX_STATE:-$HOME/.claude/handmux-state.json}"
|
|
15
|
-
# 毫秒时间戳:与旧实现的 Date.now() 同单位(客户端已阅水位线兼容)。优先 perl,退化到 秒×1000。
|
|
16
|
-
TS=$(perl -MTime::HiRes -e 'printf "%.0f", Time::HiRes::time()*1000' 2>/dev/null)
|
|
17
|
-
[ -z "$TS" ] && TS=$(( $(date +%s) * 1000 ))
|
|
18
|
-
HOST=$(hostname 2>/dev/null || printf '')
|
|
19
|
-
# payload 经 stdin 原样流给 node(不在 shell 里转义,避免坏数据);pane 含 '%' 直接进 JSON 字段,
|
|
20
|
-
# 不再进 URL → 彻底告别旧的 "%110 被 url-decode 丢弃" 那类坑。
|
|
21
|
-
node "$(dirname "$0")/handmux-write.cjs" "$FILE" "$PANE" "$1" "$TS" "$HOST" 2>/dev/null || true
|
|
22
|
-
exit 0
|
package/hooks/handmux-write.cjs
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// handmux hook writer. Updates ONE JSON state file keyed by tmux pane id with this pane's latest
|
|
3
|
-
// Claude event: { "%pane": { ts, src, host, payload }, ... }. Invoked by handmux-notify.sh:
|
|
4
|
-
// node handmux-write.cjs <file> <pane> <src> <ts> <host> (stdin = Claude's raw hook payload JSON)
|
|
5
|
-
//
|
|
6
|
-
// .cjs (not .js): runs standalone via `node <file>`, including from ~/.claude/hooks (no package.json) —
|
|
7
|
-
// .cjs forces CommonJS everywhere; a bare .js under the server tree ("type":"module") would be ESM and
|
|
8
|
-
// break require().
|
|
9
|
-
//
|
|
10
|
-
// Why node (not pure shell): the file is a single JSON object, so each event is a read-modify-write that
|
|
11
|
-
// must parse JSON and not clobber other panes — shell can't do that safely. The user runs many Claude
|
|
12
|
-
// panes at once, so hooks fire concurrently: we take a short O_EXCL lock (stealing a stale one) around
|
|
13
|
-
// the read-modify-write and replace atomically (tmp + rename), so concurrent writers don't lose updates
|
|
14
|
-
// or corrupt the file. Best-effort throughout and silent — the hook is fire-and-forget and must never
|
|
15
|
-
// fail Claude (the shell wrapper swallows errors and always exits 0).
|
|
16
|
-
const fs = require('node:fs');
|
|
17
|
-
|
|
18
|
-
const [, , file, pane, src, ts, host = ''] = process.argv;
|
|
19
|
-
if (!file || !pane || !src) process.exit(0);
|
|
20
|
-
|
|
21
|
-
let payload = {};
|
|
22
|
-
try { payload = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch { /* unreadable stdin → {} */ }
|
|
23
|
-
|
|
24
|
-
// idle_prompt ("been idle ~60s") is decided in update() against the pane's PRIOR state: it either trails
|
|
25
|
-
// a resting state (done/needs → drop, so it can't bump ts and re-surface an already-cleared 已完成) or it
|
|
26
|
-
// terminates an ESC-interrupted working turn (→ clear the stuck 进行中). Flag it here; the read-modify-
|
|
27
|
-
// write under the lock — the only place we can read the prior state safely — makes the call.
|
|
28
|
-
const isIdle = src === 'notify' && payload && payload.notification_type === 'idle_prompt';
|
|
29
|
-
|
|
30
|
-
// Synchronous nap without busy-spinning (the hook runs async, so a few ms is free). SharedArrayBuffer
|
|
31
|
-
// may be unavailable in odd runtimes — fall back to a tiny busy loop so the lock retry still paces.
|
|
32
|
-
const nap = (ms) => {
|
|
33
|
-
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
|
|
34
|
-
catch { const end = Date.now() + ms; while (Date.now() < end) { /* spin */ } }
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
try { fs.mkdirSync(path.dirname(file), { recursive: true }); } catch { /* ignore */ }
|
|
38
|
-
|
|
39
|
-
function update() {
|
|
40
|
-
let obj = {};
|
|
41
|
-
try { const j = JSON.parse(fs.readFileSync(file, 'utf8')); if (j && typeof j === 'object' && !Array.isArray(j)) obj = j; }
|
|
42
|
-
catch { /* fresh / corrupt / half-written → start clean */ }
|
|
43
|
-
const prevSrc = obj[pane] && obj[pane].src;
|
|
44
|
-
if (isIdle) {
|
|
45
|
-
// idle after a resting state (done/needs/nothing) is just the "still waiting" reminder → drop and
|
|
46
|
-
// leave the file as it was (recording it would bump ts and re-surface an already-cleared 已完成).
|
|
47
|
-
// idle after a WORKING turn (prompt/resume) that never got a Stop = an ESC interrupt / walk-away —
|
|
48
|
-
// no Stop hook fires there, so idle is the only signal the turn ended. Without this the pane would
|
|
49
|
-
// stay latched at 进行中 forever; treat it as a soft end and clear the pane.
|
|
50
|
-
if (prevSrc === 'prompt' || prevSrc === 'resume') { delete obj[pane]; }
|
|
51
|
-
else return; // resting → drop without writing
|
|
52
|
-
} else if (src === 'end') {
|
|
53
|
-
// SessionEnd drops the pane on a clean exit. But /clear (and /resume) END the old session AND START a
|
|
54
|
-
// new one — SessionEnd(old) + SessionStart(new) fire as two async hooks, in either order. A late end for
|
|
55
|
-
// the OLD session must NOT wipe the NEW session's binding: only drop when what we currently hold IS the
|
|
56
|
-
// ending session (or neither side carries a session_id to compare — preserve the plain clean-exit drop).
|
|
57
|
-
const curSid = obj[pane] && obj[pane].payload && obj[pane].payload.session_id;
|
|
58
|
-
const endSid = payload && payload.session_id;
|
|
59
|
-
if (!curSid || !endSid || curSid === endSid) delete obj[pane];
|
|
60
|
-
} else {
|
|
61
|
-
obj[pane] = { ts: Number(ts) || 0, src, host, payload };
|
|
62
|
-
}
|
|
63
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
64
|
-
fs.writeFileSync(tmp, JSON.stringify(obj));
|
|
65
|
-
fs.renameSync(tmp, file); // atomic: a torn write can't corrupt the file
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const lock = `${file}.lock`;
|
|
69
|
-
let held = false;
|
|
70
|
-
for (let i = 0; i < 60 && !held; i++) { // ~0.9s budget, then write lockless (best-effort)
|
|
71
|
-
try { fs.closeSync(fs.openSync(lock, 'wx')); held = true; } // O_EXCL → atomic "I hold it"
|
|
72
|
-
catch {
|
|
73
|
-
try { if (Date.now() - fs.statSync(lock).mtimeMs > 3000) fs.unlinkSync(lock); } catch { /* steal a stale lock */ }
|
|
74
|
-
nap(15);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
try { update(); } catch { /* best effort */ }
|
|
78
|
-
if (held) { try { fs.unlinkSync(lock); } catch { /* ignore */ } }
|