@platless/axom-cli 0.3.13 → 0.6.36
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 +18 -5
- package/dist/axom-patch/cli/README.md +119 -9
- package/dist/axom-patch/cli/account-limits/README.md +27 -0
- package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
- package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
- package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
- package/dist/axom-patch/cli/account-limits/generate.py +123 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +81 -0
- package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
- package/dist/axom-patch/cli/claude-account-limits.json +37 -0
- package/dist/axom-patch/cli/codex-account-limits.json +37 -0
- package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
- package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
- package/dist/axom-patch/cli/nightly-task.md +194 -0
- package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
- package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
- package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
- package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -5
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -7
- package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
- package/dist/axom-patch/cli/qoder-steering.md +20 -25
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
- package/dist/axom-patch/cli/runtime.json +39 -15
- package/dist/axom-patch/cli/zcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/zcode-models.json +37 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +72 -0
- package/dist/cli.js +4 -9
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +65 -3
- package/dist/commands/workstation.js +73 -7
- package/dist/install/claude/commands.js +13 -13
- package/dist/keepers/acp/keeper.mjs +3083 -0
- package/dist/keepers/acp/manifest.json +9 -0
- package/dist/keepers/terminal/keeper.mjs +8469 -0
- package/dist/keepers/terminal/manifest.json +9 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
- package/dist/mcp.js +1 -1
- package/dist/native/darwin-arm64/axom-quic +0 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2192 -636
- package/dist/workstation/acp/acpClient.js +84 -495
- package/dist/workstation/acp/adoption.js +10 -396
- package/dist/workstation/acp/binaryInstall.js +100 -27
- package/dist/workstation/acp/builds/gc.js +245 -0
- package/dist/workstation/acp/builds/install/binary.js +28 -0
- package/dist/workstation/acp/builds/install/npm.js +324 -0
- package/dist/workstation/acp/builds/install/uv.js +254 -0
- package/dist/workstation/acp/builds/migrate.js +195 -0
- package/dist/workstation/acp/builds/model.js +128 -0
- package/dist/workstation/acp/builds/nodeGate.js +100 -0
- package/dist/workstation/acp/builds/store.js +352 -0
- package/dist/workstation/acp/commandTableProbe.js +68 -0
- package/dist/workstation/acp/configOptions.js +103 -1
- package/dist/workstation/acp/envSecrets.js +27 -0
- package/dist/workstation/acp/initializeResponse.js +290 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +239 -159
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +60 -3
- package/dist/workstation/acp/permission.js +42 -10
- package/dist/workstation/acp/processor/checkpoint.js +78 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +282 -0
- package/dist/workstation/acp/processor/emit.js +26 -0
- package/dist/workstation/acp/processor/jsonrpc.js +172 -0
- package/dist/workstation/acp/processor/memoryLog.js +49 -0
- package/dist/workstation/acp/processor/origins.js +48 -0
- package/dist/workstation/acp/processor/output.js +67 -0
- package/dist/workstation/acp/processor/projection.js +42 -0
- package/dist/workstation/acp/processor/record.js +128 -0
- package/dist/workstation/acp/processor/runtime.js +105 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +496 -0
- package/dist/workstation/acp/processor/state.js +67 -0
- package/dist/workstation/acp/processor/step.js +442 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +58 -0
- package/dist/workstation/acp/processor/test/harness.js +120 -0
- package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
- package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
- package/dist/workstation/acp/processor/transport/generations.js +76 -0
- package/dist/workstation/acp/processor/transport/keeper.js +130 -0
- package/dist/workstation/acp/processor/transport/local.js +269 -0
- package/dist/workstation/acp/processor/transport/types.js +78 -0
- package/dist/workstation/acp/processor/updates.js +390 -0
- package/dist/workstation/acp/processorSession.js +1348 -0
- package/dist/workstation/acp/registry/catalog.js +189 -41
- package/dist/workstation/acp/registry/launch.js +76 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +111 -0
- package/dist/workstation/acp/terminalLogin.js +16 -42
- package/dist/workstation/acp/terminalLoginSpec.js +41 -0
- package/dist/workstation/acp/test/adapterFixtures.js +256 -0
- package/dist/workstation/acp/test/daemonLife.js +106 -0
- package/dist/workstation/acp/test/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +295 -0
- package/dist/workstation/acp/test/memoryOutbox.js +55 -0
- package/dist/workstation/acp/test/raw/fixtures.js +196 -0
- package/dist/workstation/acp/test/raw/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +135 -0
- package/dist/workstation/acp/test/realAdapter.js +331 -0
- package/dist/workstation/acp/test/scriptAgent.js +214 -0
- package/dist/workstation/acp/test/sessionFrames.js +42 -0
- package/dist/workstation/acp/timedCommand.js +4 -2
- package/dist/workstation/acp/uvxInstall.js +72 -122
- package/dist/workstation/agentFlow.js +103 -0
- package/dist/workstation/agentSessions.js +1289 -544
- package/dist/workstation/channels/agent.js +7 -5
- package/dist/workstation/channels/computer.js +69 -21
- package/dist/workstation/channels/computerExecs.js +200 -0
- package/dist/workstation/channels/management.js +172 -8
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +16 -4
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +200 -0
- package/dist/workstation/control/commands.js +124 -0
- package/dist/workstation/control/daemonIdentity.js +53 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +63 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/runtimeClient.js +97 -0
- package/dist/workstation/control/runtimeHost.js +148 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/daemon.js +393 -12
- package/dist/workstation/daemonctl.js +103 -14
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/capabilities.js +92 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +353 -0
- package/dist/workstation/desktopControl/lobby.js +129 -0
- package/dist/workstation/desktopControl/macProvider.js +155 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/protocol.js +302 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1681 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +78 -0
- package/dist/workstation/exec/attach.js +126 -0
- package/dist/workstation/exec/command.js +40 -0
- package/dist/workstation/exec/durable.js +517 -0
- package/dist/workstation/exec/fallback.js +106 -0
- package/dist/workstation/exec/policy.js +837 -0
- package/dist/workstation/exec/shaping.js +211 -0
- package/dist/workstation/exec.js +3 -112
- package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
- package/dist/workstation/files/adapters/toolRequests.js +121 -0
- package/dist/workstation/files/client.js +455 -0
- package/dist/workstation/files/credentials.js +142 -0
- package/dist/workstation/files/errors.js +28 -0
- package/dist/workstation/files/fence.js +350 -0
- package/dist/workstation/files/find/fileIndex.js +153 -0
- package/dist/workstation/files/find/fuzzy.js +155 -0
- package/dist/workstation/files/host.js +122 -0
- package/dist/workstation/files/hostProtocol.js +2 -0
- package/dist/workstation/files/media/cache.js +620 -0
- package/dist/workstation/files/media/encoding.js +308 -0
- package/dist/workstation/files/media/estimator.js +100 -0
- package/dist/workstation/files/media/media.js +325 -0
- package/dist/workstation/files/media/packedAudio.js +23 -0
- package/dist/workstation/files/media/scheduler.js +551 -0
- package/dist/workstation/files/media/types.js +5 -0
- package/dist/workstation/files/scope.js +45 -0
- package/dist/workstation/files/structured.js +284 -0
- package/dist/workstation/{fileOps.js → files/text.js} +152 -39
- package/dist/workstation/files/uploads.js +351 -0
- package/dist/workstation/journal.js +65 -236
- package/dist/workstation/keepers/acp/client.js +101 -0
- package/dist/workstation/keepers/acp/envelope.js +62 -0
- package/dist/workstation/keepers/acp/host.js +419 -0
- package/dist/workstation/keepers/acp/main.js +18 -0
- package/dist/workstation/keepers/acp/memoryStream.js +121 -0
- package/dist/workstation/keepers/acp/params.js +137 -0
- package/dist/workstation/keepers/acp/protocol.js +66 -0
- package/dist/workstation/keepers/acp/recover.js +120 -0
- package/dist/workstation/keepers/acp/session.js +584 -0
- package/dist/workstation/keepers/acp/stderr.js +154 -0
- package/dist/workstation/keepers/kit/activity.js +103 -0
- package/dist/workstation/keepers/kit/bootstrap.js +164 -0
- package/dist/workstation/keepers/kit/buildVersion.js +51 -0
- package/dist/workstation/keepers/kit/bundle.js +124 -0
- package/dist/workstation/keepers/kit/client.js +432 -0
- package/dist/workstation/keepers/kit/identity.js +21 -0
- package/dist/workstation/keepers/kit/ipc.js +112 -0
- package/dist/workstation/keepers/kit/launchConfig.js +8 -0
- package/dist/workstation/keepers/kit/launcher.js +289 -0
- package/dist/workstation/keepers/kit/lock.js +123 -0
- package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
- package/dist/workstation/keepers/kit/paths.js +76 -0
- package/dist/workstation/keepers/kit/protocol.js +43 -0
- package/dist/workstation/keepers/kit/server.js +518 -0
- package/dist/workstation/keepers/kit/streamLog.js +697 -0
- package/dist/workstation/keepers/legacy.js +296 -0
- package/dist/workstation/keepers/policy.js +576 -0
- package/dist/workstation/keepers/stop.js +34 -0
- package/dist/workstation/keepers/terminal/client.js +213 -0
- package/dist/workstation/keepers/terminal/exec.js +981 -0
- package/dist/workstation/keepers/terminal/generations.js +129 -0
- package/dist/workstation/keepers/terminal/keeper.js +62 -0
- package/dist/workstation/keepers/terminal/main.js +43 -0
- package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
- package/dist/workstation/keepers/terminal/params.js +27 -0
- package/dist/workstation/keepers/terminal/protocol.js +56 -0
- package/dist/workstation/keepers/terminal/pty.js +330 -0
- package/dist/workstation/managedInstall.js +57 -0
- package/dist/workstation/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +50 -19
- package/dist/workstation/protocol/common.js +30 -0
- package/dist/workstation/protocol/computer.js +15 -4
- package/dist/workstation/protocol/management.js +141 -4
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +119 -0
- package/dist/workstation/rd/control.js +119 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/provider.js +222 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +45 -0
- package/dist/workstation/rd/update.js +171 -0
- package/dist/workstation/rd/upkeep.js +38 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/service/installer.js +104 -13
- package/dist/workstation/service/launchd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +154 -93
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +9 -1
- package/dist/workstation/tools/info.js +50 -0
- package/dist/workstation/tools/keeperProcess.js +15 -5
- package/dist/workstation/tools/router.js +55 -0
- package/dist/workstation/tools/terminalRouter.js +222 -0
- package/dist/workstation/tools/validation.js +37 -1
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +3 -0
- package/dist/workstation/update/globalInstall.js +116 -0
- package/dist/workstation/update/npmInstaller.js +49 -23
- package/dist/workstation/update/ptyPreflight.js +21 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/runUpdate.js +3 -2
- package/dist/workstation/update/taskRegistry.js +105 -11
- package/dist/workstation/update/watchdog.js +16790 -334
- package/package.json +11 -8
- package/skills/axom/SKILL.md +11 -11
- package/skills/axom-definition/SKILL.md +3 -3
- package/skills/axom-platform/SKILL.md +3 -3
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -49
- package/dist/axom-patch/cli/qoder-steering.json +0 -52
- package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
- package/dist/commands/guide.js +0 -38
- package/dist/workstation/acp/acpSession.js +0 -1475
- package/dist/workstation/acp/contract.js +0 -499
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/draftInterceptor.js +0 -163
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/sessionScope.js +0 -19
- package/dist/workstation/filePaths.js +0 -174
- package/dist/workstation/fileWorker.js +0 -81
- package/dist/workstation/fileWorkerClient.js +0 -121
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -106
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -262
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -30
- package/dist/workstation/qoderAdapter/history.js +0 -87
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -93
- package/dist/workstation/qoderAdapter/mapper.js +0 -117
- package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
- package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
- package/dist/workstation/qoderAdapter/permissions.js +0 -61
- package/dist/workstation/qoderAdapter/profile.js +0 -93
- package/dist/workstation/qoderAdapter/sdkDriver.js +0 -49
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -77
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- package/dist/workstation/tools/core.js +0 -50
- package/dist/workstation/tools/files.js +0 -525
- package/dist/workstation/tools/terminal.js +0 -214
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Peels off the `session/update` notifications the pinned ACP SDK would destroy, before it sees them.
|
|
3
|
-
*
|
|
4
|
-
* ## Why this exists at all
|
|
5
|
-
*
|
|
6
|
-
* Two classes of notification never reach a handler we are allowed to register, and both were found
|
|
7
|
-
* by running the thing rather than reading the types:
|
|
8
|
-
*
|
|
9
|
-
* 1. **Draft variants throw.** `ClientApp`'s constructor installs `client-session-update-router` as
|
|
10
|
-
* the *first* static handler (`sdk/dist/acp.js:869-872`), and that router parses every
|
|
11
|
-
* notification through `zSessionNotification` (`:680`), whose `update` is a closed 15-member
|
|
12
|
-
* union. A `subagent_spawned` fails that parse. The dispatch loop's catch treats a failed
|
|
13
|
-
* notification as `console.error` + return (`sdk/dist/jsonrpc.js:777-790`) — connection alive,
|
|
14
|
-
* event gone.
|
|
15
|
-
* 2. **A child session's ordinary frames are dropped.** The same router routes by
|
|
16
|
-
* `notification.sessionId` into a map of sessions *this client opened*. A subagent's session id
|
|
17
|
-
* was never opened here, so nothing is enqueued, the router answers `Handled.no`, and no later
|
|
18
|
-
* handler exists to catch it.
|
|
19
|
-
*
|
|
20
|
-
* `builder.withHandler` only ever appends, so there is no handler position ahead of the router. The
|
|
21
|
-
* stream is the only place left — and it is the right place anyway, because "which consumer do
|
|
22
|
-
* these bytes belong to" is a transport question.
|
|
23
|
-
*
|
|
24
|
-
* ## What it must not become
|
|
25
|
-
*
|
|
26
|
-
* Pure carriage. It answers two predicates and either diverts or forwards; it never reads a payload
|
|
27
|
-
* for meaning, and it never rewrites one. Every message it does not divert must reach the SDK
|
|
28
|
-
* byte-for-byte — a re-encode that changed key order or dropped an unknown field would make this
|
|
29
|
-
* layer a silent second normaliser, which is precisely what ruling L1 (normalise in one place,
|
|
30
|
-
* server ingest) exists to prevent.
|
|
31
|
-
*
|
|
32
|
-
* ## Lifetime
|
|
33
|
-
*
|
|
34
|
-
* Predicate 1 dies with the draft: when RFD #1992 ships in the SDK, delete it along with
|
|
35
|
-
* `draftSubagents.ts`. Predicate 2 outlives it — it can only go once the SDK has a concept of a
|
|
36
|
-
* session it did not open.
|
|
37
|
-
*/
|
|
38
|
-
import { DRAFT_SESSION_UPDATE_NAMES } from './draftSubagents.js';
|
|
39
|
-
/** ACP's client-bound notification for session updates. */
|
|
40
|
-
const SESSION_UPDATE_METHOD = 'session/update';
|
|
41
|
-
const DRAFT_NAMES = new Set(DRAFT_SESSION_UPDATE_NAMES);
|
|
42
|
-
/**
|
|
43
|
-
* Whether this raw JSON-RPC message is a `session/update` that the SDK cannot deliver.
|
|
44
|
-
*
|
|
45
|
-
* Returns the diversion reason, or `null` to forward. Exported for its own tests: the two
|
|
46
|
-
* predicates are the whole contract, and testing them through a stream would only obscure them.
|
|
47
|
-
*
|
|
48
|
-
* Deliberately total — any shape it does not recognise is forwarded. Being wrong in that direction
|
|
49
|
-
* costs nothing (the SDK handles what it always handled); being wrong the other way silently
|
|
50
|
-
* removes a message from the protocol.
|
|
51
|
-
*/
|
|
52
|
-
export function diversionReasonFor(message, isOwnSession) {
|
|
53
|
-
if (typeof message !== 'object' || message === null)
|
|
54
|
-
return null;
|
|
55
|
-
const record = message;
|
|
56
|
-
if (record.method !== SESSION_UPDATE_METHOD)
|
|
57
|
-
return null;
|
|
58
|
-
// A request carries an `id` and is owed a response; this notification does not. Anything with an
|
|
59
|
-
// id is left alone rather than answered by a layer that has no business answering.
|
|
60
|
-
if ('id' in record)
|
|
61
|
-
return null;
|
|
62
|
-
const params = record.params;
|
|
63
|
-
if (typeof params !== 'object' || params === null)
|
|
64
|
-
return null;
|
|
65
|
-
const { sessionId, update } = params;
|
|
66
|
-
if (typeof sessionId !== 'string' || sessionId.length === 0)
|
|
67
|
-
return null;
|
|
68
|
-
if (typeof update !== 'object' || update === null)
|
|
69
|
-
return null;
|
|
70
|
-
const updateRecord = update;
|
|
71
|
-
const variant = updateRecord.sessionUpdate;
|
|
72
|
-
if (typeof variant === 'string' && DRAFT_NAMES.has(variant)) {
|
|
73
|
-
return { sessionId, update: updateRecord, reason: 'draft-variant' };
|
|
74
|
-
}
|
|
75
|
-
if (!isOwnSession(sessionId)) {
|
|
76
|
-
return { sessionId, update: updateRecord, reason: 'foreign-session' };
|
|
77
|
-
}
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Wraps the child's stdout so diverted notifications never reach the SDK, and everything else
|
|
82
|
-
* arrives unchanged.
|
|
83
|
-
*
|
|
84
|
-
* Works on whole lines because that is the framing ACP uses over stdio and because it lets the
|
|
85
|
-
* forwarded half stay **the original bytes**: a line that is not diverted is enqueued exactly as it
|
|
86
|
-
* arrived, so no re-encoding can perturb it. Only a diverted line is ever parsed.
|
|
87
|
-
*
|
|
88
|
-
* A line that is not valid JSON is forwarded untouched — reporting a protocol error is the SDK's
|
|
89
|
-
* job, and a transport shim that swallowed malformed input would hide the failure from the layer
|
|
90
|
-
* equipped to describe it.
|
|
91
|
-
*/
|
|
92
|
-
export function interceptDraftUpdates(source, options) {
|
|
93
|
-
const decoder = new TextDecoder();
|
|
94
|
-
const encoder = new TextEncoder();
|
|
95
|
-
let buffered = '';
|
|
96
|
-
const handleLine = (line, enqueue) => {
|
|
97
|
-
if (line.trim().length === 0)
|
|
98
|
-
return;
|
|
99
|
-
let parsed;
|
|
100
|
-
try {
|
|
101
|
-
parsed = JSON.parse(line);
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
enqueue(encoder.encode(`${line}\n`));
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (options.captureUpdate && typeof parsed === 'object' && parsed !== null) {
|
|
108
|
-
const message = parsed;
|
|
109
|
-
const params = message.params;
|
|
110
|
-
if (message.method === SESSION_UPDATE_METHOD && !('id' in message) &&
|
|
111
|
-
params && typeof params.sessionId === 'string' && params.sessionId.length > 0 &&
|
|
112
|
-
typeof params.update === 'object' && params.update !== null &&
|
|
113
|
-
options.captureUpdate(params))
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const diverted = diversionReasonFor(parsed, options.isOwnSession);
|
|
117
|
-
if (!diverted) {
|
|
118
|
-
// The original text, not a re-serialisation of the parse — see the doc comment.
|
|
119
|
-
enqueue(encoder.encode(`${line}\n`));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
options.onDiverted(diverted);
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
// The sink is ours and is documented as non-throwing; if it throws anyway, losing one update
|
|
127
|
-
// is strictly better than wedging the stream that carries every other one.
|
|
128
|
-
console.error(`[acp] draft update sink threw for session ${diverted.sessionId}: ${String(err)}`);
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
return new ReadableStream({
|
|
132
|
-
async start(controller) {
|
|
133
|
-
const reader = source.getReader();
|
|
134
|
-
try {
|
|
135
|
-
for (;;) {
|
|
136
|
-
const { done, value } = await reader.read();
|
|
137
|
-
if (done)
|
|
138
|
-
break;
|
|
139
|
-
buffered += decoder.decode(value, { stream: true });
|
|
140
|
-
let newline = buffered.indexOf('\n');
|
|
141
|
-
while (newline >= 0) {
|
|
142
|
-
const line = buffered.slice(0, newline);
|
|
143
|
-
buffered = buffered.slice(newline + 1);
|
|
144
|
-
handleLine(line, (chunk) => controller.enqueue(chunk));
|
|
145
|
-
newline = buffered.indexOf('\n');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// A final line with no trailing newline is still a message: an agent that exits right after
|
|
149
|
-
// writing one would otherwise have its last word dropped.
|
|
150
|
-
buffered += decoder.decode();
|
|
151
|
-
if (buffered.length > 0)
|
|
152
|
-
handleLine(buffered, (chunk) => controller.enqueue(chunk));
|
|
153
|
-
controller.close();
|
|
154
|
-
}
|
|
155
|
-
catch (err) {
|
|
156
|
-
controller.error(err);
|
|
157
|
-
}
|
|
158
|
-
finally {
|
|
159
|
-
reader.releaseLock();
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { buildSessionMcpServers } from './delegationMcp.js';
|
|
6
|
-
export const QODER_MANAGED_VERSION = '1.1.61';
|
|
7
|
-
/** Owned by one process; the bearer is never an argument or environment variable. */
|
|
8
|
-
export async function prepareQoderStartup(launch, lease, cwd, env, agentEnv) {
|
|
9
|
-
buildSessionMcpServers([], lease);
|
|
10
|
-
if (launch.args.some((arg) => arg === '--' || /^(--mcp-config|--strict-mcp-config|--settings|--setting-sources)(=|$)/.test(arg))) {
|
|
11
|
-
throw new Error('Managed Qoder startup has conflicting configuration arguments');
|
|
12
|
-
}
|
|
13
|
-
const version = await new Promise((resolve, reject) => {
|
|
14
|
-
execFile(launch.command, [...launch.args, '--version'], {
|
|
15
|
-
cwd, env: { ...process.env, ...launch.env, ...env, ...agentEnv },
|
|
16
|
-
encoding: 'utf8', timeout: 10_000, maxBuffer: 64 * 1024, windowsHide: true,
|
|
17
|
-
}, (error, stdout) => error ? reject(new Error('Managed Qoder version could not be verified')) : resolve(stdout.trim()));
|
|
18
|
-
});
|
|
19
|
-
if (version !== QODER_MANAGED_VERSION)
|
|
20
|
-
throw new Error('Managed Qoder version is not certified');
|
|
21
|
-
const dir = await mkdtemp(join(tmpdir(), 'axom-qoder-mcp-'));
|
|
22
|
-
let disposal;
|
|
23
|
-
const dispose = () => disposal ??= rm(dir, { recursive: true, force: true });
|
|
24
|
-
try {
|
|
25
|
-
await chmod(dir, 0o700);
|
|
26
|
-
const config = join(dir, 'mcp.json');
|
|
27
|
-
const authorization = Object.values(lease.headers)[0];
|
|
28
|
-
await writeFile(config, JSON.stringify({ mcpServers: {
|
|
29
|
-
'axom-delegation': { type: 'http', url: lease.url, headers: { Authorization: authorization } },
|
|
30
|
-
} }), { mode: 0o600, flag: 'wx' });
|
|
31
|
-
// Validate again after the asynchronous version probe; an expired lease cannot start a process.
|
|
32
|
-
buildSessionMcpServers([], lease);
|
|
33
|
-
return {
|
|
34
|
-
launch: { ...launch, args: [...launch.args, '--strict-mcp-config', '--mcp-config', config,
|
|
35
|
-
'--setting-sources', '', '--settings', '{"autoMemoryEnabled":false}',
|
|
36
|
-
'--disallowed-tools', 'Agent', '--cwd', cwd,
|
|
37
|
-
'--append-system-prompt', 'Use the axom-delegation MCP tools for delegation. Native Agent execution is disabled in this session.'] },
|
|
38
|
-
secrets: [authorization, authorization.slice('Bearer '.length)], dispose,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
await dispose();
|
|
43
|
-
throw error;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { SessionMapperState } from './sessionUpdateMapper.js';
|
|
2
|
-
export function scopeKey(scope) {
|
|
3
|
-
return JSON.stringify([scope.rootRuntimeId, scope.nativeSessionId]);
|
|
4
|
-
}
|
|
5
|
-
export class SessionMapperScopes {
|
|
6
|
-
create;
|
|
7
|
-
states = new Map();
|
|
8
|
-
constructor(create = () => new SessionMapperState()) {
|
|
9
|
-
this.create = create;
|
|
10
|
-
}
|
|
11
|
-
get(scope) {
|
|
12
|
-
const key = scopeKey(scope);
|
|
13
|
-
let state = this.states.get(key);
|
|
14
|
-
if (!state)
|
|
15
|
-
this.states.set(key, state = this.create(scope));
|
|
16
|
-
return state;
|
|
17
|
-
}
|
|
18
|
-
clear() { this.states.clear(); }
|
|
19
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { lstat, realpath } from 'node:fs/promises';
|
|
2
|
-
import { isAbsolute, join } from 'node:path';
|
|
3
|
-
export class ComputerFileError extends Error {
|
|
4
|
-
code;
|
|
5
|
-
constructor(code, message) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.code = code;
|
|
8
|
-
this.name = 'ComputerFileError';
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function computerFileError(error) {
|
|
12
|
-
if (error instanceof ComputerFileError)
|
|
13
|
-
return error;
|
|
14
|
-
const code = error?.code;
|
|
15
|
-
if (code === 'ENOENT')
|
|
16
|
-
return new ComputerFileError('not_found', 'File or parent directory not found');
|
|
17
|
-
if (code === 'EACCES' || code === 'EPERM')
|
|
18
|
-
return new ComputerFileError('permission_denied', 'File access denied');
|
|
19
|
-
if (code === 'EEXIST')
|
|
20
|
-
return new ComputerFileError('conflict', 'Target already exists');
|
|
21
|
-
if (code === 'ELOOP')
|
|
22
|
-
return new ComputerFileError('invalid_path', 'Symbolic links are not allowed');
|
|
23
|
-
return new ComputerFileError('io_error', 'File operation failed');
|
|
24
|
-
}
|
|
25
|
-
/** Validate portable slash components before touching the filesystem. */
|
|
26
|
-
export function validateComputerFilePath(path) {
|
|
27
|
-
if (!path || path.includes('\\') || path.includes(':') || path.includes('\0') || isAbsolute(path)) {
|
|
28
|
-
throw new ComputerFileError('invalid_path', 'Path must be relative to the selected workspace');
|
|
29
|
-
}
|
|
30
|
-
const components = path.split('/');
|
|
31
|
-
if (components.some(component => !component || component === '.' || component === '..')) {
|
|
32
|
-
throw new ComputerFileError('invalid_path', 'Path contains an unsafe component');
|
|
33
|
-
}
|
|
34
|
-
return components;
|
|
35
|
-
}
|
|
36
|
-
/** The configured root may itself be a symlink; none of its descendants may be. */
|
|
37
|
-
export async function resolveComputerFilePath(root, path, allowMissingLeaf) {
|
|
38
|
-
const components = validateComputerFilePath(path);
|
|
39
|
-
let cursor;
|
|
40
|
-
try {
|
|
41
|
-
cursor = await realpath(root);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
throw computerFileError(error);
|
|
45
|
-
}
|
|
46
|
-
for (let index = 0; index < components.length; index++) {
|
|
47
|
-
cursor = join(cursor, components[index]);
|
|
48
|
-
let stat;
|
|
49
|
-
try {
|
|
50
|
-
stat = await lstat(cursor);
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
if (error.code === 'ENOENT' && allowMissingLeaf && index === components.length - 1)
|
|
54
|
-
return cursor;
|
|
55
|
-
throw computerFileError(error);
|
|
56
|
-
}
|
|
57
|
-
if (stat.isSymbolicLink())
|
|
58
|
-
throw new ComputerFileError('invalid_path', 'Symbolic links are not allowed');
|
|
59
|
-
if (index === components.length - 1) {
|
|
60
|
-
if (!stat.isFile())
|
|
61
|
-
throw new ComputerFileError('not_regular_file', 'Target is not a regular file');
|
|
62
|
-
}
|
|
63
|
-
else if (!stat.isDirectory()) {
|
|
64
|
-
throw new ComputerFileError('not_regular_file', 'Parent is not a directory');
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return cursor;
|
|
68
|
-
}
|
|
69
|
-
/** Resolve only a leaf relative to an already pinned process cwd; never turn it back into an absolute pathname. */
|
|
70
|
-
export async function resolvePinnedLeafPath(_root, path, allowMissingLeaf) {
|
|
71
|
-
const components = validateComputerFilePath(path);
|
|
72
|
-
if (components.length !== 1)
|
|
73
|
-
throw new ComputerFileError('invalid_path', 'Pinned file operation requires one path component');
|
|
74
|
-
try {
|
|
75
|
-
const entry = await lstat(path);
|
|
76
|
-
if (entry.isSymbolicLink())
|
|
77
|
-
throw new ComputerFileError('invalid_path', 'Symbolic links are not allowed');
|
|
78
|
-
if (!entry.isFile())
|
|
79
|
-
throw new ComputerFileError('not_regular_file', 'Target is not a regular file');
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
if (error.code !== 'ENOENT' || !allowMissingLeaf)
|
|
83
|
-
throw computerFileError(error);
|
|
84
|
-
}
|
|
85
|
-
return path;
|
|
86
|
-
}
|
|
87
|
-
/** Every component of a directory listing target must exist, be a directory, and not be a symlink; '' is the root. */
|
|
88
|
-
export async function resolveComputerDirPath(root, path) {
|
|
89
|
-
const components = path === '' ? [] : validateComputerFilePath(path);
|
|
90
|
-
let cursor;
|
|
91
|
-
try {
|
|
92
|
-
cursor = await realpath(root);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
throw computerFileError(error);
|
|
96
|
-
}
|
|
97
|
-
for (const component of components) {
|
|
98
|
-
cursor = join(cursor, component);
|
|
99
|
-
let entry;
|
|
100
|
-
try {
|
|
101
|
-
entry = await lstat(cursor);
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
throw computerFileError(error);
|
|
105
|
-
}
|
|
106
|
-
if (entry.isSymbolicLink())
|
|
107
|
-
throw new ComputerFileError('invalid_path', 'Symbolic links are not allowed');
|
|
108
|
-
if (!entry.isDirectory())
|
|
109
|
-
throw new ComputerFileError('not_regular_file', 'Target is not a directory');
|
|
110
|
-
}
|
|
111
|
-
return cursor;
|
|
112
|
-
}
|
|
113
|
-
/** Pinned variant for the file worker: '' lists the pinned cwd itself, otherwise exactly one component. */
|
|
114
|
-
export async function resolvePinnedDirPath(_root, path) {
|
|
115
|
-
const components = path === '' ? [] : validateComputerFilePath(path);
|
|
116
|
-
if (components.length > 1)
|
|
117
|
-
throw new ComputerFileError('invalid_path', 'Pinned directory operation requires at most one path component');
|
|
118
|
-
const target = path === '' ? '.' : join('.', path);
|
|
119
|
-
let entry;
|
|
120
|
-
try {
|
|
121
|
-
entry = await lstat(target);
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
throw computerFileError(error);
|
|
125
|
-
}
|
|
126
|
-
if (entry.isSymbolicLink())
|
|
127
|
-
throw new ComputerFileError('invalid_path', 'Symbolic links are not allowed');
|
|
128
|
-
if (!entry.isDirectory())
|
|
129
|
-
throw new ComputerFileError('not_regular_file', 'Target is not a directory');
|
|
130
|
-
return target;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Locate the deepest existing ancestor for a directory creation. Without parents the anchor must be the
|
|
134
|
-
* target's own parent (only the leaf may be missing); with parents any prefix may be missing. Existing
|
|
135
|
-
* components must be directories and never symlinks; an existing leaf counts as "already created".
|
|
136
|
-
*/
|
|
137
|
-
export async function resolveComputerDirCreatePath(root, path, parents) {
|
|
138
|
-
const components = validateComputerFilePath(path);
|
|
139
|
-
let cursor;
|
|
140
|
-
try {
|
|
141
|
-
cursor = await realpath(root);
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
throw computerFileError(error);
|
|
145
|
-
}
|
|
146
|
-
let anchorIndex = 0;
|
|
147
|
-
for (; anchorIndex < components.length; anchorIndex++) {
|
|
148
|
-
const candidate = join(cursor, components[anchorIndex]);
|
|
149
|
-
let entry;
|
|
150
|
-
try {
|
|
151
|
-
entry = await lstat(candidate);
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
if (error.code !== 'ENOENT')
|
|
155
|
-
throw computerFileError(error);
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
if (entry.isSymbolicLink())
|
|
159
|
-
throw new ComputerFileError('invalid_path', 'Symbolic links are not allowed');
|
|
160
|
-
if (!entry.isDirectory())
|
|
161
|
-
throw new ComputerFileError('not_found', 'Path is not a directory');
|
|
162
|
-
cursor = candidate;
|
|
163
|
-
}
|
|
164
|
-
const missing = components.slice(anchorIndex);
|
|
165
|
-
if (!parents && missing.length > 1) {
|
|
166
|
-
throw new ComputerFileError('not_found', 'Parent directory not found');
|
|
167
|
-
}
|
|
168
|
-
return { anchor: cursor, missing };
|
|
169
|
-
}
|
|
170
|
-
/** Pinned variant for the file worker: create the missing components below the pinned cwd, one leaf at a time. */
|
|
171
|
-
export async function resolvePinnedDirCreatePath(_root, path, _parents) {
|
|
172
|
-
const components = path === '' ? [] : validateComputerFilePath(path);
|
|
173
|
-
return { anchor: '.', missing: components };
|
|
174
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { realpath } from 'node:fs/promises';
|
|
2
|
-
import { isAbsolute, relative, sep } from 'node:path';
|
|
3
|
-
import { computerFileError, ComputerFileError, resolvePinnedDirCreatePath, resolvePinnedDirPath, resolvePinnedLeafPath, } from './filePaths.js';
|
|
4
|
-
import { createComputerDir, listComputerDir, readComputerFile, writeComputerFile, } from './fileOps.js';
|
|
5
|
-
const controller = new AbortController();
|
|
6
|
-
let started = false;
|
|
7
|
-
let pending;
|
|
8
|
-
/**
|
|
9
|
-
* This worker is a separate process so its cwd is a kernel-pinned directory reference, not a
|
|
10
|
-
* pathname re-resolved after validation. A swapped ancestor symlink may affect process launch,
|
|
11
|
-
* but the post-launch realpath check then rejects it before touching the leaf. Directory
|
|
12
|
-
* operations accept at most one pinned component: multi-component `parents` creation walks
|
|
13
|
-
* one leaf at a time from the pinned anchor instead of re-resolving full pathnames.
|
|
14
|
-
*/
|
|
15
|
-
process.on('message', (message) => {
|
|
16
|
-
if (message.kind === 'cancel') {
|
|
17
|
-
controller.abort();
|
|
18
|
-
if (pending) {
|
|
19
|
-
pending = undefined;
|
|
20
|
-
process.disconnect();
|
|
21
|
-
}
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
if (message.kind === 'continue') {
|
|
25
|
-
const work = pending;
|
|
26
|
-
if (!work)
|
|
27
|
-
return;
|
|
28
|
-
pending = undefined;
|
|
29
|
-
void (async () => {
|
|
30
|
-
try {
|
|
31
|
-
if (work.kind === 'read') {
|
|
32
|
-
const request = { ...work.request, root: '.' };
|
|
33
|
-
const data = await readComputerFile(request, controller.signal, resolvePinnedLeafPath);
|
|
34
|
-
process.send?.({ ok: true, data }, () => process.disconnect());
|
|
35
|
-
}
|
|
36
|
-
else if (work.kind === 'write') {
|
|
37
|
-
const request = { ...work.request, root: '.' };
|
|
38
|
-
const data = await writeComputerFile(request, controller.signal, resolvePinnedLeafPath);
|
|
39
|
-
process.send?.({ ok: true, data }, () => process.disconnect());
|
|
40
|
-
}
|
|
41
|
-
else if (work.kind === 'ls') {
|
|
42
|
-
const request = { ...work.request, root: '.' };
|
|
43
|
-
const data = await listComputerDir(request, controller.signal, resolvePinnedDirPath);
|
|
44
|
-
process.send?.({ ok: true, data }, () => process.disconnect());
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
const request = { ...work.request, root: '.' };
|
|
48
|
-
const data = await createComputerDir(request, controller.signal, resolvePinnedDirCreatePath);
|
|
49
|
-
process.send?.({ ok: true, data }, () => process.disconnect());
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
const mapped = computerFileError(error);
|
|
54
|
-
process.send?.({ ok: false, error: { code: mapped.code, message: mapped.message } }, () => process.disconnect());
|
|
55
|
-
}
|
|
56
|
-
})();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (started)
|
|
60
|
-
return;
|
|
61
|
-
started = true;
|
|
62
|
-
void (async () => {
|
|
63
|
-
try {
|
|
64
|
-
const parent = await realpath('.');
|
|
65
|
-
const rel = relative(message.root, parent);
|
|
66
|
-
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
67
|
-
throw new ComputerFileError('invalid_path', 'File parent escaped the configured workspace');
|
|
68
|
-
}
|
|
69
|
-
if (controller.signal.aborted) {
|
|
70
|
-
process.disconnect();
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
pending = message;
|
|
74
|
-
process.send?.({ kind: 'ready' });
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
const mapped = computerFileError(error);
|
|
78
|
-
process.send?.({ ok: false, error: { code: mapped.code, message: mapped.message } }, () => process.disconnect());
|
|
79
|
-
}
|
|
80
|
-
})();
|
|
81
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { fork } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { realpath } from 'node:fs/promises';
|
|
4
|
-
import { basename, dirname } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { ComputerFileError, computerFileError, resolveComputerDirCreatePath, resolveComputerDirPath, resolveComputerFilePath } from './filePaths.js';
|
|
7
|
-
const workerUrl = existsSync(new URL('./fileWorker.js', import.meta.url))
|
|
8
|
-
? new URL('./fileWorker.js', import.meta.url)
|
|
9
|
-
: new URL('../../dist/workstation/fileWorker.js', import.meta.url);
|
|
10
|
-
const writeLocks = new Map();
|
|
11
|
-
async function serialWrite(target, action) {
|
|
12
|
-
const previous = writeLocks.get(target) ?? Promise.resolve();
|
|
13
|
-
let release;
|
|
14
|
-
const gate = new Promise(resolve => { release = resolve; });
|
|
15
|
-
const queued = previous.then(() => gate);
|
|
16
|
-
writeLocks.set(target, queued);
|
|
17
|
-
await previous;
|
|
18
|
-
try {
|
|
19
|
-
return await action();
|
|
20
|
-
}
|
|
21
|
-
finally {
|
|
22
|
-
release();
|
|
23
|
-
if (writeLocks.get(target) === queued)
|
|
24
|
-
writeLocks.delete(target);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async function execute(kind, request, anchor, leaf, signal, hooks) {
|
|
28
|
-
if (signal?.aborted)
|
|
29
|
-
throw new ComputerFileError('workstation_offline', 'File operation was cancelled');
|
|
30
|
-
const root = await realpath(request.root).catch(error => { throw computerFileError(error); });
|
|
31
|
-
await hooks?.afterValidation?.();
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const child = fork(fileURLToPath(workerUrl), [], {
|
|
34
|
-
cwd: anchor, stdio: ['ignore', 'ignore', 'ignore', 'ipc'], execArgv: [],
|
|
35
|
-
});
|
|
36
|
-
let settled = false;
|
|
37
|
-
let killTimer;
|
|
38
|
-
const finish = (error, data) => {
|
|
39
|
-
if (settled)
|
|
40
|
-
return;
|
|
41
|
-
settled = true;
|
|
42
|
-
signal?.removeEventListener('abort', abort);
|
|
43
|
-
if (error)
|
|
44
|
-
reject(error);
|
|
45
|
-
else
|
|
46
|
-
resolve(data);
|
|
47
|
-
};
|
|
48
|
-
const abort = () => {
|
|
49
|
-
if (settled)
|
|
50
|
-
return;
|
|
51
|
-
child.send({ kind: 'cancel' }, () => { });
|
|
52
|
-
killTimer = setTimeout(() => child.kill(), 2_000);
|
|
53
|
-
killTimer.unref();
|
|
54
|
-
finish(new ComputerFileError(kind === 'write' || kind === 'mkdir' ? 'outcome_unknown' : 'workstation_offline', 'File operation was cancelled'));
|
|
55
|
-
};
|
|
56
|
-
child.on('message', (message) => {
|
|
57
|
-
if (typeof message !== 'object' || message === null)
|
|
58
|
-
return;
|
|
59
|
-
const reply = message;
|
|
60
|
-
if (reply.kind === 'ready') {
|
|
61
|
-
void (async () => {
|
|
62
|
-
try {
|
|
63
|
-
await hooks?.afterWorkerReady?.();
|
|
64
|
-
if (!settled)
|
|
65
|
-
child.send({ kind: 'continue' }, error => { if (error)
|
|
66
|
-
finish(computerFileError(error)); });
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
child.kill();
|
|
70
|
-
finish(computerFileError(error));
|
|
71
|
-
}
|
|
72
|
-
})();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (reply.ok === true)
|
|
76
|
-
finish(undefined, reply.data);
|
|
77
|
-
else if (reply.ok === false && reply.error)
|
|
78
|
-
finish(new ComputerFileError(reply.error.code, reply.error.message));
|
|
79
|
-
});
|
|
80
|
-
child.once('error', error => finish(computerFileError(error)));
|
|
81
|
-
child.once('exit', () => {
|
|
82
|
-
if (killTimer)
|
|
83
|
-
clearTimeout(killTimer);
|
|
84
|
-
finish(new ComputerFileError(kind === 'write' || kind === 'mkdir' ? 'outcome_unknown' : 'workstation_offline', 'File worker exited without a result'));
|
|
85
|
-
});
|
|
86
|
-
signal?.addEventListener('abort', abort, { once: true });
|
|
87
|
-
if (signal?.aborted) {
|
|
88
|
-
abort();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
child.send({ kind, root, request: { ...request, root: '.', path: leaf } }, error => {
|
|
92
|
-
if (error)
|
|
93
|
-
finish(computerFileError(error));
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
export async function readComputerFileSafely(request, signal, hooks) {
|
|
98
|
-
const root = await realpath(request.root).catch(error => { throw computerFileError(error); });
|
|
99
|
-
const target = await resolveComputerFilePath(root, request.path, false);
|
|
100
|
-
return execute('read', request, dirname(target), basename(target), signal, hooks);
|
|
101
|
-
}
|
|
102
|
-
export async function writeComputerFileSafely(request, signal, hooks) {
|
|
103
|
-
const root = await realpath(request.root).catch(error => { throw computerFileError(error); });
|
|
104
|
-
const target = await resolveComputerFilePath(root, request.path, request.mode === 'create');
|
|
105
|
-
return serialWrite(target, () => execute('write', request, dirname(target), basename(target), signal, hooks));
|
|
106
|
-
}
|
|
107
|
-
export async function listComputerDirSafely(request, signal, hooks) {
|
|
108
|
-
const root = await realpath(request.root).catch(error => { throw computerFileError(error); });
|
|
109
|
-
const target = await resolveComputerDirPath(root, request.path);
|
|
110
|
-
// Listing the workspace root pins the worker at the root itself; '' is the zero-component leaf.
|
|
111
|
-
const anchor = target === root ? root : dirname(target);
|
|
112
|
-
const leaf = target === root ? '' : basename(target);
|
|
113
|
-
const data = await execute('ls', request, anchor, leaf, signal, hooks);
|
|
114
|
-
return { ...data, path: request.path };
|
|
115
|
-
}
|
|
116
|
-
export async function createComputerDirSafely(request, signal, hooks) {
|
|
117
|
-
const root = await realpath(request.root).catch(error => { throw computerFileError(error); });
|
|
118
|
-
const plan = await resolveComputerDirCreatePath(root, request.path, request.parents);
|
|
119
|
-
const data = await execute('mkdir', request, plan.anchor, plan.missing.join('/'), signal, hooks);
|
|
120
|
-
return { ...data, path: request.path };
|
|
121
|
-
}
|