@platless/axom-cli 0.3.13 → 0.5.9
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/dist/axom-patch/cli/README.md +30 -8
- 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 +120 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +64 -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/commandcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/nightly-task.md +152 -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/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +1 -1
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +20 -8
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +2 -1
- package/dist/axom-patch/cli/qoder-foreground-output.md +4 -3
- package/dist/axom-patch/cli/qoder-steering.json +15 -15
- package/dist/axom-patch/cli/qoder-subagent-origin.json +21 -21
- package/dist/axom-patch/cli/runtime.json +34 -8
- package/dist/axom-patch/cli/zcode-account-limits.json +31 -0
- package/dist/axom-patch/cli/zcode-models.json +38 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +73 -0
- package/dist/cli.js +33 -6
- 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/runtime-deps/qoder-sdk/package-lock.json +1189 -0
- package/dist/runtime-deps/qoder-sdk/package.json +8 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2163 -611
- package/dist/workstation/acp/acpClient.js +60 -497
- 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 +235 -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 +285 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +76 -160
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +20 -3
- package/dist/workstation/acp/permission.js +42 -10
- package/dist/workstation/acp/processor/checkpoint.js +87 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +355 -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 +181 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +525 -0
- package/dist/workstation/acp/processor/sideChannel.js +80 -0
- package/dist/workstation/acp/processor/state.js +74 -0
- package/dist/workstation/acp/processor/step.js +459 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +65 -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 +364 -0
- package/dist/workstation/acp/processorSession.js +1512 -0
- package/dist/workstation/acp/qoderStartup.js +1 -1
- package/dist/workstation/acp/registry/catalog.js +154 -32
- package/dist/workstation/acp/registry/launch.js +61 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +114 -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/keeperWorld.js +267 -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/qoderCaptures.js +125 -0
- package/dist/workstation/acp/test/raw/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +249 -0
- package/dist/workstation/acp/test/realAdapter.js +377 -0
- package/dist/workstation/acp/test/scriptAgent.js +206 -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 +1324 -474
- 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 +84 -4
- package/dist/workstation/daemon.js +288 -7
- package/dist/workstation/daemonctl.js +28 -2
- 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/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} +117 -38
- 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 +120 -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 +249 -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 +75 -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/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +48 -4
- package/dist/workstation/protocol/common.js +30 -0
- package/dist/workstation/protocol/computer.js +15 -4
- package/dist/workstation/protocol/management.js +50 -2
- package/dist/workstation/qoderAdapter/backgroundProcess.js +18 -6
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +78 -147
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +372 -0
- package/dist/workstation/qoderAdapter/callbackContext.js +76 -0
- package/dist/workstation/qoderAdapter/foregroundOutput.js +15 -9
- package/dist/workstation/qoderAdapter/history.js +1 -3
- package/dist/workstation/qoderAdapter/main.js +4 -2
- package/dist/workstation/qoderAdapter/sdkDriver.js +1 -2
- package/dist/workstation/qoderAdapter/sdkRuntime.js +93 -0
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +145 -0
- package/dist/workstation/qoderAdapter/session.js +1 -1
- package/dist/workstation/qoderAdapter/subwork.js +15 -7
- package/dist/workstation/systemMetrics.js +110 -1
- 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/coordinator.js +3 -0
- package/dist/workstation/update/ptyPreflight.js +21 -0
- package/dist/workstation/update/taskRegistry.js +105 -11
- package/package.json +10 -7
- package/dist/workstation/acp/acpSession.js +0 -1475
- package/dist/workstation/acp/contract.js +0 -499
- package/dist/workstation/acp/draftInterceptor.js +0 -163
- 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/taskEvent.js +0 -26
- package/dist/workstation/tools/core.js +0 -50
- package/dist/workstation/tools/files.js +0 -525
- package/dist/workstation/tools/terminal.js +0 -214
|
@@ -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
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const id = (value) => typeof value === 'string' && value.length > 0 &&
|
|
2
|
-
Buffer.byteLength(value, 'utf8') <= 256 && !/[\x00-\x1f\x7f]/.test(value);
|
|
3
|
-
const states = new Set(['pending', 'paused', 'running', 'completed', 'stopped', 'failed', 'unknown']);
|
|
4
|
-
const terminal = new Set(['completed', 'stopped', 'failed']);
|
|
5
|
-
export function parseTaskObservation(value) {
|
|
6
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
7
|
-
return undefined;
|
|
8
|
-
const input = value;
|
|
9
|
-
if (['rootSessionId', 'runtimeGeneration', 'taskId', 'uuid'].some((key) => !id(input[key])) ||
|
|
10
|
-
!states.has(input.state) || typeof input.live !== 'boolean' ||
|
|
11
|
-
typeof input.stale !== 'boolean' || (terminal.has(input.state) && input.live) ||
|
|
12
|
-
['agentId', 'launchingToolUseId'].some((key) => input[key] !== undefined && !id(input[key])) ||
|
|
13
|
-
(input.taskType !== undefined && (!id(input.taskType) || Buffer.byteLength(input.taskType, 'utf8') > 128)) ||
|
|
14
|
-
(input.background !== undefined && typeof input.background !== 'boolean') ||
|
|
15
|
-
(input.status !== undefined && (!terminal.has(input.status) || input.status !== input.state)) ||
|
|
16
|
-
(input.summary !== undefined && (typeof input.summary !== 'string' ||
|
|
17
|
-
Buffer.byteLength(input.summary, 'utf8') > 8192 || input.status === undefined)))
|
|
18
|
-
return undefined;
|
|
19
|
-
return Object.fromEntries(['rootSessionId', 'runtimeGeneration', 'taskId', 'uuid', 'state', 'live', 'stale',
|
|
20
|
-
'taskType', 'background', 'agentId', 'launchingToolUseId', 'status', 'summary']
|
|
21
|
-
.filter((key) => input[key] !== undefined).map((key) => [key, input[key]]));
|
|
22
|
-
}
|
|
23
|
-
export function taskEventMatchesHost(event, host) {
|
|
24
|
-
return host.selected && host.available && host.confirmed &&
|
|
25
|
-
event.rootSessionId === host.rootSessionId && event.runtimeGeneration === host.runtimeGeneration;
|
|
26
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import { StructuredFiles } from './files.js';
|
|
3
|
-
import { PersistentTerminals } from './terminal.js';
|
|
4
|
-
import { keys, validateRequest, TOOL_RESULT_BYTES } from './validation.js';
|
|
5
|
-
/** All mutations are serialized across clients; shell processes continue independently. */
|
|
6
|
-
export class ToolsCore {
|
|
7
|
-
files;
|
|
8
|
-
terminals;
|
|
9
|
-
queue = Promise.resolve();
|
|
10
|
-
disposed = false;
|
|
11
|
-
timer;
|
|
12
|
-
constructor(files, terminals, ttl) {
|
|
13
|
-
this.files = files;
|
|
14
|
-
this.terminals = terminals;
|
|
15
|
-
this.timer = setInterval(() => { this.queue = this.queue.then(() => this.files.expire()).catch(() => { }); }, Math.min(ttl, 60_000));
|
|
16
|
-
this.timer.unref();
|
|
17
|
-
}
|
|
18
|
-
static async create(options = {}) {
|
|
19
|
-
const files = await StructuredFiles.create(options.home ?? os.homedir(), options.roots ?? [], options.uploadTtlMs, options.uploadStateDir);
|
|
20
|
-
return new ToolsCore(files, new PersistentTerminals(files, options), options.uploadTtlMs ?? 15 * 60_000);
|
|
21
|
-
}
|
|
22
|
-
dispatch(method, params = {}) {
|
|
23
|
-
const task = this.queue.then(async () => {
|
|
24
|
-
if (this.disposed)
|
|
25
|
-
throw new Error('Workstation tools keeper stopped');
|
|
26
|
-
validateRequest(method, params);
|
|
27
|
-
let result;
|
|
28
|
-
if (method === 'tools.info') {
|
|
29
|
-
keys(params, []);
|
|
30
|
-
result = { version: 1, terminal: this.terminals.available, browser: false, files: true, home: this.files.home, roots: this.files.roots, downloads: this.files.downloads, ...(this.terminals.error ? { terminalError: this.terminals.error } : {}), browserError: 'Browser tools are not enabled in this release' };
|
|
31
|
-
}
|
|
32
|
-
else if (method.startsWith('terminal.'))
|
|
33
|
-
result = await this.terminals.dispatch(method, params);
|
|
34
|
-
else
|
|
35
|
-
result = await this.files.dispatch(method, params);
|
|
36
|
-
if (Buffer.byteLength(JSON.stringify(result)) > TOOL_RESULT_BYTES - 1024)
|
|
37
|
-
throw new Error('Tool result too large');
|
|
38
|
-
return result;
|
|
39
|
-
});
|
|
40
|
-
this.queue = task.catch(() => { });
|
|
41
|
-
return task;
|
|
42
|
-
}
|
|
43
|
-
async dispose() {
|
|
44
|
-
this.disposed = true;
|
|
45
|
-
clearInterval(this.timer);
|
|
46
|
-
await this.queue;
|
|
47
|
-
await this.terminals.dispose();
|
|
48
|
-
await this.files.dispose();
|
|
49
|
-
}
|
|
50
|
-
}
|