@platless/axom-cli 0.5.9 → 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.
Files changed (163) hide show
  1. package/README.md +18 -5
  2. package/dist/axom-patch/cli/README.md +100 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  7. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  8. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  9. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  10. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  11. package/dist/axom-patch/cli/nightly-task.md +55 -13
  12. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  13. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  14. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
  15. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
  16. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  17. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  18. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  19. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  20. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  21. package/dist/axom-patch/cli/runtime.json +16 -18
  22. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  23. package/dist/axom-patch/cli/zcode-models.json +1 -2
  24. package/dist/axom-patch/cli/zcode-subagent-subwork.json +9 -10
  25. package/dist/cli.js +4 -36
  26. package/dist/commands/knowledge.js +47 -0
  27. package/dist/commands/update.js +65 -3
  28. package/dist/commands/workstation.js +73 -7
  29. package/dist/install/claude/commands.js +13 -13
  30. package/dist/mcp.js +1 -1
  31. package/dist/native/darwin-arm64/axom-quic +0 -0
  32. package/dist/workstation/acp/acpAgents.js +102 -98
  33. package/dist/workstation/acp/acpClient.js +26 -0
  34. package/dist/workstation/acp/builds/install/npm.js +99 -10
  35. package/dist/workstation/acp/initializeResponse.js +6 -1
  36. package/dist/workstation/acp/managedPatches.js +189 -25
  37. package/dist/workstation/acp/managedRuntime.js +314 -0
  38. package/dist/workstation/acp/patchAssets.js +40 -0
  39. package/dist/workstation/acp/processor/checkpoint.js +25 -34
  40. package/dist/workstation/acp/processor/control.js +5 -78
  41. package/dist/workstation/acp/processor/runtime.js +5 -81
  42. package/dist/workstation/acp/processor/session.js +51 -80
  43. package/dist/workstation/acp/processor/state.js +6 -13
  44. package/dist/workstation/acp/processor/step.js +3 -20
  45. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  46. package/dist/workstation/acp/processor/updates.js +131 -105
  47. package/dist/workstation/acp/processorSession.js +8 -172
  48. package/dist/workstation/acp/registry/catalog.js +65 -39
  49. package/dist/workstation/acp/registry/launch.js +16 -1
  50. package/dist/workstation/acp/switching.js +0 -3
  51. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  52. package/dist/workstation/acp/test/keeperWorld.js +28 -0
  53. package/dist/workstation/acp/test/raw/sources.js +16 -130
  54. package/dist/workstation/acp/test/realAdapter.js +1 -47
  55. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  56. package/dist/workstation/agentFlow.js +1 -1
  57. package/dist/workstation/agentSessions.js +38 -143
  58. package/dist/workstation/channels/management.js +95 -11
  59. package/dist/workstation/channels/url.js +1 -1
  60. package/dist/workstation/config.js +16 -4
  61. package/dist/workstation/connection/encryptedChannel.js +4 -1
  62. package/dist/workstation/control/bindingStore.js +200 -0
  63. package/dist/workstation/control/commands.js +124 -0
  64. package/dist/workstation/control/daemonIdentity.js +53 -0
  65. package/dist/workstation/control/enrollment.js +106 -0
  66. package/dist/workstation/control/inspect.js +71 -0
  67. package/dist/workstation/control/locks.js +63 -0
  68. package/dist/workstation/control/protocol.js +40 -0
  69. package/dist/workstation/control/restartGuard.js +46 -0
  70. package/dist/workstation/control/runtimeClient.js +97 -0
  71. package/dist/workstation/control/runtimeHost.js +148 -0
  72. package/dist/workstation/control/serverIdentity.js +35 -0
  73. package/dist/workstation/daemon.js +106 -6
  74. package/dist/workstation/daemonctl.js +77 -14
  75. package/dist/workstation/desktopControl/audio.js +99 -0
  76. package/dist/workstation/desktopControl/capabilities.js +92 -0
  77. package/dist/workstation/desktopControl/captureModes.js +9 -0
  78. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  79. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  80. package/dist/workstation/desktopControl/helperProcess.js +353 -0
  81. package/dist/workstation/desktopControl/lobby.js +129 -0
  82. package/dist/workstation/desktopControl/macProvider.js +155 -0
  83. package/dist/workstation/desktopControl/media.js +331 -0
  84. package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
  85. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  86. package/dist/workstation/desktopControl/overview.js +58 -0
  87. package/dist/workstation/desktopControl/protocol.js +302 -0
  88. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  89. package/dist/workstation/desktopControl/rateController.js +226 -0
  90. package/dist/workstation/desktopControl/supervisor.js +1681 -0
  91. package/dist/workstation/desktopControl/transport.js +27 -0
  92. package/dist/workstation/desktopControl/wallpaper.js +78 -0
  93. package/dist/workstation/files/credentials.js +142 -0
  94. package/dist/workstation/files/text.js +42 -8
  95. package/dist/workstation/keepers/kit/bundle.js +21 -17
  96. package/dist/workstation/keepers/kit/launcher.js +47 -7
  97. package/dist/workstation/keepers/kit/paths.js +2 -1
  98. package/dist/workstation/managedInstall.js +57 -0
  99. package/dist/workstation/protocol/agent.js +2 -15
  100. package/dist/workstation/protocol/management.js +93 -4
  101. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  102. package/dist/workstation/rd/artifact.js +119 -0
  103. package/dist/workstation/rd/control.js +119 -0
  104. package/dist/workstation/rd/display.js +29 -0
  105. package/dist/workstation/rd/provider.js +222 -0
  106. package/dist/workstation/rd/sessions.js +391 -0
  107. package/dist/workstation/rd/state.js +45 -0
  108. package/dist/workstation/rd/update.js +171 -0
  109. package/dist/workstation/rd/upkeep.js +38 -0
  110. package/dist/workstation/register.js +27 -37
  111. package/dist/workstation/service/installer.js +104 -13
  112. package/dist/workstation/service/launchd.js +1 -0
  113. package/dist/workstation/storageMetrics.js +118 -0
  114. package/dist/workstation/systemMetrics.js +44 -92
  115. package/dist/workstation/toolchain.js +94 -0
  116. package/dist/workstation/tools/browser.js +28 -4
  117. package/dist/workstation/update/artifact.js +2 -1
  118. package/dist/workstation/update/globalInstall.js +116 -0
  119. package/dist/workstation/update/npmInstaller.js +49 -23
  120. package/dist/workstation/update/rdRollback.js +145 -0
  121. package/dist/workstation/update/runUpdate.js +3 -2
  122. package/dist/workstation/update/watchdog.js +16790 -334
  123. package/package.json +5 -5
  124. package/skills/axom/SKILL.md +11 -11
  125. package/skills/axom-definition/SKILL.md +3 -3
  126. package/skills/axom-platform/SKILL.md +3 -3
  127. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  128. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  129. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  130. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  131. package/dist/commands/guide.js +0 -38
  132. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  133. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  134. package/dist/workstation/acp/delegationMcp.js +0 -24
  135. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  136. package/dist/workstation/acp/qoderStartup.js +0 -45
  137. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  138. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  139. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  140. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  141. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  142. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  143. package/dist/workstation/qoderAdapter/contract.js +0 -78
  144. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  145. package/dist/workstation/qoderAdapter/history.js +0 -85
  146. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  147. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  148. package/dist/workstation/qoderAdapter/main.js +0 -95
  149. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  150. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  151. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  152. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  153. package/dist/workstation/qoderAdapter/profile.js +0 -93
  154. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  155. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  156. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  157. package/dist/workstation/qoderAdapter/session.js +0 -319
  158. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  159. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  160. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  161. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  162. package/dist/workstation/qoderAdapter/usage.js +0 -37
  163. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ import { DesktopProtocolError, parseDesktopJson } from './protocol.js';
3
+ const sessionId = z.string().regex(/^[0-9a-f]{32}$/);
4
+ const generation = z.number().int().min(1).max(Number.MAX_SAFE_INTEGER);
5
+ const secret = z.string().regex(/^[A-Za-z0-9_-]{43}$/).refine(s => Buffer.from(s, 'base64url').toString('base64url') === s);
6
+ const role = z.enum(['viewer_control', 'viewer_media', 'device_control', 'device_media']);
7
+ const quic = z.strictObject({ port: z.number().int().min(1).max(65535), certificate: z.string().min(64).max(2048).refine(s => Buffer.from(s, 'base64').toString('base64') === s) });
8
+ const schema = z.discriminatedUnion('type', [
9
+ z.strictObject({ type: z.literal('device_attach'), sessionId, grant: secret }),
10
+ z.strictObject({ type: z.literal('viewer_attach'), sessionId, nonce: secret }),
11
+ z.strictObject({ type: z.literal('attached'), sessionId, role, controlGeneration: generation, overviewToken: secret.optional(), audioToken: secret.optional() }),
12
+ z.strictObject({ type: z.literal('media_grant'), sessionId, controlGeneration: generation, grant: secret, quic: quic.optional() }),
13
+ z.strictObject({ type: z.literal('media_nonce'), sessionId, controlGeneration: generation, nonce: secret, quic: quic.optional() }),
14
+ ]);
15
+ export function decodeDesktopTransport(source) {
16
+ try {
17
+ return schema.parse(parseDesktopJson(source, 4096));
18
+ }
19
+ catch {
20
+ throw new DesktopProtocolError('Invalid desktop transport exchange');
21
+ }
22
+ }
23
+ export function encodeDesktopTransport(message) {
24
+ const text = JSON.stringify(message);
25
+ decodeDesktopTransport(text);
26
+ return text;
27
+ }
@@ -0,0 +1,78 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { wallpaperAssetSchema } from './lobby.js';
3
+ export function decodeWallpaper(text) {
4
+ if (Buffer.byteLength(text) > 100_000)
5
+ throw new Error('wallpaper_unavailable');
6
+ const result = JSON.parse(text);
7
+ const asset = wallpaperAssetSchema.parse(result.asset);
8
+ if (typeof result.jpeg !== 'string' || result.jpeg.length > 87384)
9
+ throw new Error('wallpaper_unavailable');
10
+ const jpeg = Buffer.from(result.jpeg, 'base64');
11
+ if (jpeg.length < 4 || jpeg.length > 65536 || jpeg.toString('base64') !== result.jpeg || jpeg[0] !== 255 || jpeg[1] !== 216)
12
+ throw new Error('wallpaper_unavailable');
13
+ const version = createHash('sha256').update(JSON.stringify(asset)).update(jpeg).digest('hex');
14
+ return { asset: { ...asset, version }, jpeg };
15
+ }
16
+ export class XfceWallpaperReader {
17
+ execute;
18
+ constructor(execute) {
19
+ this.execute = execute;
20
+ }
21
+ async discover() {
22
+ const workspaceValue = await this.execute('xprop', ['-root', '_NET_CURRENT_DESKTOP']);
23
+ let workspace = /_NET_CURRENT_DESKTOP\(CARDINAL\)\s*=\s*(\d+)/.exec(workspaceValue)?.[1];
24
+ if (workspace == null || Number(workspace) > 1024)
25
+ throw new Error('wallpaper_unavailable');
26
+ const listed = await this.execute('xfconf-query', ['-c', 'xfce4-desktop', '-l']);
27
+ const read = (property) => this.execute('xfconf-query', ['-c', 'xfce4-desktop', '-p', property]);
28
+ if (listed.split(/\r?\n/).some(line => line.trim() === '/backdrop/single-workspace-mode') &&
29
+ (await read('/backdrop/single-workspace-mode')).trim() === 'true') {
30
+ workspace = (await read('/backdrop/single-workspace-number')).trim();
31
+ if (!/^\d{1,4}$/.test(workspace) || Number(workspace) > 1024)
32
+ throw new Error('wallpaper_unavailable');
33
+ }
34
+ const available = listed.split(/\r?\n/).map(line => line.trim());
35
+ const properties = available
36
+ .filter(line => new RegExp(`^/backdrop/screen0/monitor[^/]+/workspace${workspace}/last-image$`).test(line));
37
+ let selected = properties[0];
38
+ let defaultImage = false;
39
+ if (properties.length === 0) {
40
+ const version = await this.execute('xfdesktop', ['--version']);
41
+ const monitors = await this.execute('xrandr', ['--listactivemonitors']);
42
+ if (!/xfdesktop version 4\.20(?:\.\d+)?\b/.test(version) || !/^Monitors:\s*1\s*$/m.test(monitors))
43
+ throw new Error('wallpaper_unavailable');
44
+ const output = /^\s*0:\s+\S+\s+\S+\s+(\S+)\s*$/m.exec(monitors)?.[1];
45
+ if (!output || !/^[A-Za-z0-9_.:-]+$/.test(output))
46
+ throw new Error('wallpaper_unavailable');
47
+ selected = `/backdrop/screen0/monitor${output}/workspace${workspace}/last-image`;
48
+ defaultImage = true;
49
+ }
50
+ if (properties.length > 1)
51
+ throw new Error('wallpaper_unavailable');
52
+ const file = defaultImage ? '/usr/share/backgrounds/xfce/xfce-x.svg' : (await read(selected)).trim();
53
+ if (!file.startsWith('/') || file.length > 4096 || /[\r\n\0]/.test(file))
54
+ throw new Error('wallpaper_unavailable');
55
+ const prefix = selected.slice(0, -'last-image'.length);
56
+ const style = available.includes(`${prefix}image-style`) ? (await read(`${prefix}image-style`)).trim() : '5';
57
+ const placement = { '1': 'center', '2': 'tile', '3': 'stretch', '4': 'fit', '5': 'cover' }[style];
58
+ if (!placement)
59
+ throw new Error('wallpaper_unavailable');
60
+ let background = '#000000';
61
+ let fidelity = 'exact';
62
+ if (placement !== 'cover' && placement !== 'stretch') {
63
+ try {
64
+ if ((await read(`${prefix}color-style`)).trim() !== '0')
65
+ throw new Error('unsupported_gradient');
66
+ const values = (await read(`${prefix}rgba1`)).split(/\r?\n/).map(line => line.trim())
67
+ .filter(line => /^\d+(?:\.\d+)?$/.test(line)).map(Number);
68
+ if (values.length !== 4 || values.some(value => value < 0 || value > 1) || values[3] !== 1)
69
+ throw new Error('unknown_fill');
70
+ background = '#' + values.slice(0, 3).map(value => Math.round(value * 255).toString(16).padStart(2, '0')).join('');
71
+ }
72
+ catch {
73
+ fidelity = 'approximate';
74
+ }
75
+ }
76
+ return { file, placement, background, fidelity };
77
+ }
78
+ }
@@ -0,0 +1,142 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { FileError } from './errors.js';
5
+ /*
6
+ * Credential storage the platform agent file tools (`file_*_request`, the request-root scope) never read, list or
7
+ * change. This is a security boundary, not a policy an approval can lift: an agent that lacks a capability must not go
8
+ * looking for another client's identity, a token or a key to work around it.
9
+ *
10
+ * The check runs on canonical paths (the request root after realpath, components below it already refused when they
11
+ * are links), and again inside the `pinned()` section on what the kernel resolves: the pinned directory's realpath and
12
+ * each existing leaf's realpath, which return the names stored on disk. A relative path, a symlinked root, a directory
13
+ * swapped for a link after validation, or a leaf spelled so that a case-insensitive file system resolves it to a store
14
+ * (`.git-credentialſ`, `.ßh`) cannot bypass it. Names are compared folded: NFC, then full case folding (`ſ`→`s`,
15
+ * `ß`→`ss`, `K`→`k`), so a missing leaf created under such a spelling is refused too.
16
+ *
17
+ * The stores are named below the process's home, below any other home-like directory (`/Users/<name>`, `/home/<name>`,
18
+ * `/root`, `/var/root`: a daemon whose HOME is not the workspace owner's), and below the configured client
19
+ * directories. The lists equal `contracts/credential-stores/credential-stores.json` (a test checks it), which the
20
+ * server's early refusal (axom-core `ProtectedCredentialPaths`) matches too.
21
+ *
22
+ * Only the user-facing Files surfaces (configured roots, read-only previews, the owner's file manager) are not fenced
23
+ * here: they act for the user at the keyboard, not for an agent.
24
+ */
25
+ export const PROTECTED_CREDENTIAL_MESSAGE = 'This path is protected credential storage. Agents may not read, list or change it by any means. Tell the user what is needed instead of looking for another way.';
26
+ /** Below a home directory; each entry covers itself and everything below it; a trailing `*` matches a name prefix. */
27
+ export const HOME_ENTRIES = [
28
+ // Axomwork's own identities: the desktop/app clients, the CLI's auth stores and config.
29
+ '.axom/clients', '.axom/auth', '.axom/config.json', '.axom/device-id',
30
+ // SSH, GPG, password managers and desktop keyrings.
31
+ '.ssh', '.gnupg', '.password-store', '.local/share/keyrings', '.config/op',
32
+ // Agent CLIs' credentials and session data (their instructions and settings stay readable).
33
+ '.claude.json', '.claude/.credentials.json', '.claude/projects', '.claude/sessions', '.claude/shell-snapshots',
34
+ '.claude/history.jsonl', '.claude/backups', '.claude/session-env', '.claude/file-history', '.claude/ide',
35
+ '.codex/auth.json', '.codex/sessions', '.codex/history.jsonl',
36
+ '.acpx',
37
+ '.gemini/oauth_creds.json', '.gemini/jetski-standalone-oauth-token', '.gemini/antigravity*',
38
+ '.commandcode/auth.json', '.commandcode/projects', '.commandcode/history.jsonl', '.commandcode/file-history',
39
+ '.qoder/.auth', '.kimi', '.zcode',
40
+ // Developer, database and cloud credentials.
41
+ '.config/gh/hosts.yml', '.git-credentials', '.netrc', '.npmrc', '.pypirc', '.docker/config.json',
42
+ '.pgpass', '.my.cnf', '.vault-token', '.cargo/credentials', '.cargo/credentials.toml', '.gem/credentials',
43
+ '.m2/settings-security.xml', '.terraform.d/credentials.tfrc.json', '.gradle/gradle.properties',
44
+ '.aws', '.azure', '.kube', '.config/gcloud',
45
+ // macOS keychains and the Axomwork credential broker's state.
46
+ 'Library/Keychains', 'Library/Caches/com.platless.axom-credentials',
47
+ ];
48
+ /**
49
+ * The workstation client directory holds the binding to the server (keys, identity, provider tokens, keeper sockets,
50
+ * the agent browser's profiles). Its logs, caches and update state are operational and stay readable.
51
+ */
52
+ export const WORKSTATION_ENTRIES = [
53
+ 'client.key', 'server.pub', 'config.yml', 'binding-history.json', 'operation.json', 'desktop-provider.json',
54
+ 'desktop-preferences.json', 'control', 'keepers', 'agents', 'files-host', 'browser-profiles',
55
+ ];
56
+ export const ABSOLUTE_ENTRIES = ['/Library/Keychains'];
57
+ /** Home-like directories besides the process's own; `*` is one path component. */
58
+ export const HOME_ROOTS = ['/Users/*', '/home/*', '/root', '/var/root', '/private/var/root'];
59
+ /** `/tmp/com.platless.axom-credentials.<uid>.sock` (credentialBroker.ts) and anything else the broker names so. */
60
+ export const BROKER_SOCKET_PREFIX = 'com.platless.axom-credentials.';
61
+ export const TEMP_DIRECTORIES = ['/tmp', '/private/tmp'];
62
+ /** NFC, then full case folding the way a case-insensitive file system compares (`ſ`→`s`, `ß`→`ss`, `K`→`k`). */
63
+ export function foldName(value) { return value.normalize('NFC').toUpperCase().toLowerCase(); }
64
+ function segments(absolute) { return foldName(path.resolve(absolute)).split(path.sep).filter(Boolean); }
65
+ function canonical(target) {
66
+ try {
67
+ return realpathSync(target);
68
+ }
69
+ catch {
70
+ return path.resolve(target);
71
+ }
72
+ }
73
+ const patterns = (entries) => entries.map(entry => foldName(entry).split('/').filter(Boolean));
74
+ const HOME_PATTERNS = patterns(HOME_ENTRIES);
75
+ const WORKSTATION_PATTERNS = patterns(WORKSTATION_ENTRIES);
76
+ const ABSOLUTE_PATTERNS = patterns(ABSOLUTE_ENTRIES);
77
+ const HOME_ROOT_PATTERNS = patterns(HOME_ROOTS);
78
+ const WORKSTATION_BELOW_HOME = ['.axom', 'workstation'];
79
+ /** [target] starts with [pattern]; `*` alone is any one component, a trailing `*` a name prefix. */
80
+ function startsWith(target, pattern) {
81
+ if (target.length < pattern.length)
82
+ return false;
83
+ return pattern.every((part, index) => part === '*' || (part.endsWith('*') ? target[index].startsWith(part.slice(0, -1)) : target[index] === part));
84
+ }
85
+ function coveredBelow(target, base, entries) {
86
+ if (!startsWith(target, base))
87
+ return false;
88
+ const rest = target.slice(base.length);
89
+ return entries.some(entry => startsWith(rest, entry));
90
+ }
91
+ let cached;
92
+ /** Both spellings of [value]: as given (a relative one resolved as the client resolves it) and canonical. */
93
+ function spellings(value) {
94
+ if (!value)
95
+ return [];
96
+ const unique = new Map();
97
+ for (const spelling of [path.resolve(value), canonical(value)])
98
+ unique.set(spelling, segments(spelling));
99
+ return [...unique.values()];
100
+ }
101
+ function bases(home) {
102
+ const key = JSON.stringify([home, process.env.AXOM_WORKSTATION_CONFIG_DIR ?? null, process.env.AXOM_CONFIG_DIR ?? null]);
103
+ if (cached?.key !== key) {
104
+ cached = {
105
+ key,
106
+ bases: {
107
+ homes: spellings(home),
108
+ workstations: spellings(process.env.AXOM_WORKSTATION_CONFIG_DIR),
109
+ // AXOM_CONFIG_DIR stands in for `~/.axom`: its config.json and device-id are the CLI's.
110
+ cliConfigs: spellings(process.env.AXOM_CONFIG_DIR),
111
+ },
112
+ };
113
+ }
114
+ return cached.bases;
115
+ }
116
+ const CLI_CONFIG_PATTERNS = patterns(['config.json', 'device-id']);
117
+ const TEMP_PATTERNS = patterns(TEMP_DIRECTORIES);
118
+ const BROKER_PREFIX = foldName(BROKER_SOCKET_PREFIX);
119
+ /** True when [target] (absolute) is, or lies below, protected credential storage. */
120
+ export function isCredentialPath(target, home = os.homedir()) {
121
+ const folded = segments(target);
122
+ if (ABSOLUTE_PATTERNS.some(entry => startsWith(folded, entry)))
123
+ return true;
124
+ if (TEMP_PATTERNS.some(dir => folded.length > dir.length && startsWith(folded, dir) && folded[dir.length].startsWith(BROKER_PREFIX)))
125
+ return true;
126
+ const { homes, workstations, cliConfigs } = bases(home);
127
+ // Any home-like directory the target is below, besides the process's own (a daemon running as another user).
128
+ const homeLike = HOME_ROOT_PATTERNS.filter(root => startsWith(folded, root)).map(root => folded.slice(0, root.length));
129
+ for (const base of [...homes, ...homeLike]) {
130
+ if (coveredBelow(folded, base, HOME_PATTERNS))
131
+ return true;
132
+ if (coveredBelow(folded, [...base, ...WORKSTATION_BELOW_HOME], WORKSTATION_PATTERNS))
133
+ return true;
134
+ }
135
+ if (workstations.some(base => coveredBelow(folded, base, WORKSTATION_PATTERNS)))
136
+ return true;
137
+ return cliConfigs.some(base => coveredBelow(folded, base, CLI_CONFIG_PATTERNS));
138
+ }
139
+ export function assertNotCredentialPath(target) {
140
+ if (isCredentialPath(target))
141
+ throw new FileError('permission_denied', PROTECTED_CREDENTIAL_MESSAGE);
142
+ }
@@ -1,8 +1,9 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { constants } from 'node:fs';
3
- import { link, lstat, mkdir, open, readdir, rename, stat, unlink } from 'node:fs/promises';
3
+ import { link, lstat, mkdir, open, readdir, realpath, rename, stat, unlink } from 'node:fs/promises';
4
4
  import { basename, dirname, join } from 'node:path';
5
5
  import { canonicalRoot, FileError, pinned, resolvePinnedDir, resolvePinnedDirCreate, resolvePinnedLeaf, resolveRelativeDir, resolveRelativeDirCreate, resolveRelativeFile, throwIfAborted, toFileError, withinRoot, } from './fence.js';
6
+ import { assertNotCredentialPath } from './credentials.js';
6
7
  import { assertScopeAllows } from './scope.js';
7
8
  /** Text file semantics of the platform agent tools (`file_*_request`): request-root scope only. */
8
9
  export const MAX_READ_BYTES = 8 * 1024 * 1024;
@@ -306,14 +307,37 @@ function requireRequestRoot(scope, operation) {
306
307
  * keeps the file worker's contract: a request cancelled once it is under way reports `outcome_unknown` for mutations
307
308
  * and `workstation_offline` for reads.
308
309
  */
309
- async function anchored(operation, root, anchor, signal, hooks, run) {
310
+ async function anchored(operation, root, anchor, leaves, signal, hooks, run) {
311
+ const insideRoot = withinRoot(root);
310
312
  try {
311
313
  await hooks?.afterValidation?.();
312
314
  return await pinned(anchor, async () => {
313
315
  await hooks?.afterPinned?.();
314
316
  throwIfAborted(signal);
317
+ // What each existing leaf resolves to, by the name stored on disk: a spelling a case-insensitive file system
318
+ // maps onto a store (`.git-credentialſ`) is judged by the store it opens, not by how it was written.
319
+ for (const leaf of leaves) {
320
+ let stored;
321
+ try {
322
+ stored = await realpath(leaf);
323
+ }
324
+ catch {
325
+ continue;
326
+ }
327
+ assertNotCredentialPath(stored);
328
+ }
315
329
  return run();
316
- }, { verify: withinRoot(root), signal });
330
+ }, {
331
+ // The directory the kernel now holds, and what the operation touches below it, are checked again: a directory
332
+ // swapped for a link after validation must not reach credential storage (credentials.ts).
333
+ verify: (pinnedReal, parent) => {
334
+ insideRoot(pinnedReal, parent);
335
+ assertNotCredentialPath(pinnedReal);
336
+ for (const leaf of leaves)
337
+ assertNotCredentialPath(join(pinnedReal, leaf));
338
+ },
339
+ signal,
340
+ });
317
341
  }
318
342
  catch (error) {
319
343
  if (signal?.aborted)
@@ -325,32 +349,42 @@ export async function readText(scope, request, signal, hooks) {
325
349
  requireRequestRoot(scope, 'read');
326
350
  const root = await canonicalRoot(scope.root);
327
351
  const target = await resolveRelativeFile(root, request.path, false);
352
+ assertNotCredentialPath(target);
328
353
  throwIfAborted(signal);
329
- // As the file worker did, the result names the leaf it read.
330
- return anchored('read', root, dirname(target), signal, hooks, () => readTextFile({ ...request, root: '.', path: basename(target) }, signal, resolvePinnedLeaf));
354
+ // The pinned read sees only the leaf; the result names the workspace-relative path it was asked for.
355
+ const data = await anchored('read', root, dirname(target), [basename(target)], signal, hooks, () => readTextFile({ ...request, root: '.', path: basename(target) }, signal, resolvePinnedLeaf));
356
+ return { ...data, path: request.path };
331
357
  }
332
358
  export async function writeText(scope, request, signal, hooks) {
333
359
  requireRequestRoot(scope, 'write');
334
360
  const root = await canonicalRoot(scope.root);
335
361
  const target = await resolveRelativeFile(root, request.path, request.mode === 'create');
336
- return serialTarget(target, () => throwIfAbortedThen(signal, () => anchored('write', root, dirname(target), signal, hooks, () => writeTextFile({ ...request, root: '.', path: basename(target) }, signal, resolvePinnedLeaf))));
362
+ assertNotCredentialPath(target);
363
+ const data = await serialTarget(target, () => throwIfAbortedThen(signal, () => anchored('write', root, dirname(target), [basename(target)], signal, hooks, () => writeTextFile({ ...request, root: '.', path: basename(target) }, signal, resolvePinnedLeaf))));
364
+ return { ...data, path: request.path };
337
365
  }
338
366
  export async function listDir(scope, request, signal, hooks) {
339
367
  requireRequestRoot(scope, 'list');
340
368
  const root = await canonicalRoot(scope.root);
341
369
  const target = await resolveRelativeDir(root, request.path);
370
+ assertNotCredentialPath(target);
342
371
  throwIfAborted(signal);
343
372
  // Listing the root pins the root itself; '' is the zero-component leaf.
344
373
  const anchor = target === root ? root : dirname(target);
345
374
  const leaf = target === root ? '' : basename(target);
346
- const data = await anchored('list', root, anchor, signal, hooks, () => listTextDir({ ...request, root: '.', path: leaf }, signal, resolvePinnedDir));
375
+ const data = await anchored('list', root, anchor, leaf === '' ? [] : [leaf], signal, hooks, () => listTextDir({ ...request, root: '.', path: leaf }, signal, resolvePinnedDir));
347
376
  return { ...data, path: request.path };
348
377
  }
349
378
  export async function makeDir(scope, request, signal, hooks) {
350
379
  requireRequestRoot(scope, 'mkdir');
351
380
  const root = await canonicalRoot(scope.root);
352
381
  const plan = await resolveRelativeDirCreate(root, request.path, request.parents);
382
+ // Every directory this would create, not only the last one.
383
+ const created = plan.missing.map((_, index) => plan.missing.slice(0, index + 1).join('/'));
384
+ assertNotCredentialPath(plan.anchor);
385
+ for (const leaf of created)
386
+ assertNotCredentialPath(join(plan.anchor, leaf));
353
387
  throwIfAborted(signal);
354
- const data = await anchored('mkdir', root, plan.anchor, signal, hooks, () => createTextDir({ ...request, root: '.', path: plan.missing.join('/') }, signal, resolvePinnedDirCreate));
388
+ const data = await anchored('mkdir', root, plan.anchor, created, signal, hooks, () => createTextDir({ ...request, root: '.', path: plan.missing.join('/') }, signal, resolvePinnedDirCreate));
355
389
  return { ...data, path: request.path };
356
390
  }
@@ -97,23 +97,27 @@ export async function gcBundles(options) {
97
97
  const keep = new Set(options.keep ?? []);
98
98
  const removed = [];
99
99
  for (const kind of KEEPER_KINDS) {
100
- const root = path.join(path.resolve(options.configDir), 'keepers', kind);
101
- const referenced = new Set(keep);
102
- for (const gen of await fs.readdir(path.join(root, 'run')).catch(() => [])) {
103
- const manifest = await readPidManifest(path.join(root, 'run', gen, 'keeper.pid.json')).catch(() => null);
104
- if (manifest && await manifestProcessLive(manifest))
105
- referenced.add(manifest.buildVersion);
106
- }
107
- const bundles = path.join(root, 'bundles');
108
- for (const name of await fs.readdir(bundles).catch(() => [])) {
109
- if (referenced.has(name))
110
- continue;
111
- const dir = path.join(bundles, name);
112
- const stat = await fs.lstat(dir).catch(() => null);
113
- if (!stat || (minAge > 0 && now - Math.max(stat.mtimeMs, stat.ctimeMs) < minAge))
114
- continue;
115
- await fs.rm(dir, { recursive: true, force: true });
116
- removed.push(dir);
100
+ const kindRoot = path.join(path.resolve(options.configDir), 'keepers', kind);
101
+ const entries = await fs.readdir(kindRoot, { withFileTypes: true }).catch(() => []);
102
+ const roots = [kindRoot, ...entries.filter(entry => entry.isDirectory() && /^[0-9a-f]{32}$/.test(entry.name)).map(entry => path.join(kindRoot, entry.name))];
103
+ for (const root of roots) {
104
+ const referenced = new Set(keep);
105
+ for (const gen of await fs.readdir(path.join(root, 'run')).catch(() => [])) {
106
+ const manifest = await readPidManifest(path.join(root, 'run', gen, 'keeper.pid.json')).catch(() => null);
107
+ if (manifest && await manifestProcessLive(manifest))
108
+ referenced.add(manifest.buildVersion);
109
+ }
110
+ const bundles = path.join(root, 'bundles');
111
+ for (const name of await fs.readdir(bundles).catch(() => [])) {
112
+ if (referenced.has(name))
113
+ continue;
114
+ const dir = path.join(bundles, name);
115
+ const stat = await fs.lstat(dir).catch(() => null);
116
+ if (!stat || (minAge > 0 && now - Math.max(stat.mtimeMs, stat.ctimeMs) < minAge))
117
+ continue;
118
+ await fs.rm(dir, { recursive: true, force: true });
119
+ removed.push(dir);
120
+ }
117
121
  }
118
122
  }
119
123
  return removed;
@@ -1,6 +1,7 @@
1
1
  // Daemon-side: starts, probes and stops keepers. Exempt from the keeper-side import guard.
2
2
  import { spawn } from 'node:child_process';
3
3
  import { promises as fs } from 'node:fs';
4
+ import { constants, openSync, fstatSync, readFileSync, closeSync, lstatSync } from 'node:fs';
4
5
  import path from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  import { keeperOneShot, readKeeperToken } from './client.js';
@@ -49,17 +50,55 @@ export class KeeperLauncher {
49
50
  get distDir() { return this.options.distDir ?? defaultDistDir(); }
50
51
  probe(paths, timeoutMs) { return this.options.probe ? this.options.probe(paths) : pingKeeper(paths, timeoutMs); }
51
52
  /** Paths of generation `gen` of `kind` (no build needed). */
52
- pathsFor(kind, gen) { return keeperPaths(this.options, kind, gen); }
53
+ pathsFor(kind, gen) {
54
+ const scoped = keeperPaths(this.options, kind, gen);
55
+ try {
56
+ lstatSync(scoped.pidFile);
57
+ return scoped;
58
+ }
59
+ catch (error) {
60
+ if (error.code !== 'ENOENT')
61
+ throw error;
62
+ }
63
+ const legacy = keeperPaths(this.options, kind, gen, { legacyLayout: true });
64
+ let descriptor;
65
+ try {
66
+ descriptor = openSync(legacy.pidFile, constants.O_RDONLY | constants.O_NOFOLLOW);
67
+ const stat = fstatSync(descriptor);
68
+ if (!stat.isFile() || stat.size > 4096 || process.getuid && stat.uid !== process.getuid())
69
+ throw new Error('Unsafe keeper PID manifest');
70
+ const manifest = JSON.parse(readFileSync(descriptor, 'utf8'));
71
+ if (manifest.identity === scoped.identity && manifest.kind === kind && manifest.gen === gen)
72
+ return legacy;
73
+ }
74
+ catch (error) {
75
+ if (error.code !== 'ENOENT')
76
+ throw error;
77
+ }
78
+ finally {
79
+ if (descriptor !== undefined)
80
+ closeSync(descriptor);
81
+ }
82
+ return scoped;
83
+ }
53
84
  /** The generation the current build of `kind` runs as (`dist/keepers/<kind>/manifest.json`); starts nothing. */
54
85
  async currentGen(kind) {
55
86
  return keeperGen((await readKeeperManifest(path.join(this.distDir, kind))).buildVersion);
56
87
  }
57
88
  /** Generations of `kind` whose pid manifest names a live keeper of that generation (`<root>/run/<gen>`); starts nothing. */
58
89
  async liveGenerations(kind) {
59
- const runRoot = path.join(this.pathsFor(kind, 'probe').root, 'run');
60
- const gens = await fs.readdir(runRoot).catch(() => []);
90
+ const scoped = keeperPaths(this.options, kind, 'probe');
91
+ const legacy = keeperPaths(this.options, kind, 'probe', { legacyLayout: true });
92
+ const roots = [scoped.root, legacy.root];
93
+ const gens = new Set();
94
+ for (const root of roots)
95
+ for (const gen of await fs.readdir(path.join(root, 'run')).catch(() => [])) {
96
+ const manifest = await readPidManifest(path.join(root, 'run', gen, 'keeper.pid.json')).catch(() => null);
97
+ if (manifest?.identity === scoped.identity && manifest.kind === kind && manifest.gen === gen)
98
+ gens.add(gen);
99
+ }
61
100
  const live = [];
62
- for (const gen of gens.sort())
101
+ for (const gen of [...gens].sort())
63
102
  if (await liveManifest(this.pathsFor(kind, gen)).catch(() => false))
64
103
  live.push(gen);
65
104
  return live;
@@ -76,7 +115,7 @@ export class KeeperLauncher {
76
115
  async start(kind) {
77
116
  const deadline = Date.now() + (this.options.startTimeoutMs ?? 7500);
78
117
  const manifest = await readKeeperManifest(path.join(this.distDir, kind));
79
- const paths = keeperPaths(this.options, kind, keeperGen(manifest.buildVersion));
118
+ const paths = this.pathsFor(kind, keeperGen(manifest.buildVersion));
80
119
  await ensureKeeperDirectories(paths);
81
120
  // A keeper that already answers needs no snapshot check: it runs from the snapshot it was started with.
82
121
  const alive = await this.probe(paths);
@@ -165,9 +204,10 @@ export class KeeperLauncher {
165
204
  * socket. Sockets under configDir are left alone. Returns how many were touched; never throws.
166
205
  */
167
206
  async touchSockets(kind) {
168
- const runRoot = path.join(this.pathsFor(kind, 'probe').root, 'run');
207
+ const root = keeperPaths(this.options, kind, 'probe').root;
208
+ const gens = new Set([...await fs.readdir(path.join(root, 'run')).catch(() => []), ...await this.liveGenerations(kind)]);
169
209
  let touched = 0;
170
- for (const gen of await fs.readdir(runRoot).catch(() => [])) {
210
+ for (const gen of gens) {
171
211
  const paths = this.pathsFor(kind, gen);
172
212
  if (paths.socketFallback && await touchSocket(paths).catch(() => false))
173
213
  touched += 1;
@@ -16,7 +16,8 @@ export function keeperLockPort(identity, gen) {
16
16
  }
17
17
  export function keeperPaths(input, kind, gen, options = {}) {
18
18
  const identity = keeperIdentity(input, kind);
19
- const root = path.join(path.resolve(input.configDir), 'keepers', kind);
19
+ const kindRoot = path.join(path.resolve(input.configDir), 'keepers', kind);
20
+ const root = options.legacyLayout ? kindRoot : path.join(kindRoot, identity);
20
21
  const runDir = path.join(root, 'run', gen);
21
22
  let socket = path.join(runDir, 'keeper.sock');
22
23
  let socketDir = runDir;
@@ -0,0 +1,57 @@
1
+ import { lstatSync, readFileSync, realpathSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { z } from 'zod';
4
+ import { LocalControlError } from './control/protocol.js';
5
+ const schema = z.object({ schemaVersion: z.literal(1), source: z.literal('managed'), prefix: z.string(), nodePath: z.string(), npmPath: z.string(), cliPath: z.string(), entryPath: z.string(), runtimeVersion: z.string().regex(/^24\.\d+\.\d+$/), architecture: z.enum(['arm64', 'x64']) }).strict();
6
+ function plannedRealPath(value) {
7
+ try {
8
+ return realpathSync(value);
9
+ }
10
+ catch (error) {
11
+ if (error.code !== 'ENOENT' || path.dirname(value) === value)
12
+ throw error;
13
+ return path.join(plannedRealPath(path.dirname(value)), path.basename(value));
14
+ }
15
+ }
16
+ /** This synchronous variant is shared by the existing synchronous service-context constructors. */
17
+ export function loadManagedCliInstallSync(packageRoot) {
18
+ let root;
19
+ try {
20
+ root = realpathSync(packageRoot);
21
+ }
22
+ catch (error) {
23
+ if (error.code === 'ENOENT')
24
+ return null;
25
+ throw error;
26
+ }
27
+ const prefix = path.resolve(root, '../../../..');
28
+ if (root !== path.join(prefix, 'lib/node_modules/@platless/axom-cli'))
29
+ return null;
30
+ const file = path.join(prefix, 'install.json');
31
+ let found = false;
32
+ try {
33
+ const stat = lstatSync(file);
34
+ found = true;
35
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 16384 || (process.getuid && stat.uid !== process.getuid()) || (stat.mode & 0o077))
36
+ throw Error('Unsafe metadata');
37
+ const value = schema.parse(JSON.parse(readFileSync(file, 'utf8')));
38
+ const home = path.resolve(prefix, '../..');
39
+ const runtime = path.join(home, '.axom/runtime/node', `${value.runtimeVersion}-darwin-${value.architecture}`);
40
+ if (prefix !== path.join(home, '.axom/cli') || realpathSync(value.prefix) !== prefix || realpathSync(value.cliPath) !== path.join(root, 'dist/cli.js') || realpathSync(value.nodePath) !== path.join(runtime, 'bin/node') || realpathSync(value.npmPath) !== path.join(runtime, 'bin/npm') || plannedRealPath(value.entryPath) !== path.join(home, '.local/bin/axom'))
41
+ throw Error('Metadata does not describe this installation');
42
+ return value;
43
+ }
44
+ catch (error) {
45
+ if (!found && error.code === 'ENOENT')
46
+ return null;
47
+ throw new LocalControlError('UNSUPPORTED_INSTALLATION', 'The managed CLI installation needs repair');
48
+ }
49
+ }
50
+ export async function loadManagedCliInstall(packageRoot) { return loadManagedCliInstallSync(packageRoot); }
51
+ export function npmExecutionContext(install, inherited) {
52
+ if (!install)
53
+ return { ...inherited };
54
+ const result = { ...inherited, NPM_CONFIG_PREFIX: install.prefix, PATH: `${path.dirname(install.nodePath)}:${inherited.PATH ?? '/usr/bin:/bin'}` };
55
+ delete result.npm_config_prefix;
56
+ return result;
57
+ }
@@ -1,5 +1,5 @@
1
1
  import { accountLimitsSchema } from './accountLimits.js';
2
- import { isRuntimeDiagnostics } from '../qoderAdapter/profile.js';
2
+ import { isRuntimeDiagnostics } from './runtimeDiagnostics.js';
3
3
  import { decodeChannelFrame, hasOptional, hasRequired, isBoolean, isBridgeInfoArray, isNullableAuthMethodInfoArray, isNullableString, isNumber, isObject, isString, isStringArray, isStringRecord, } from './common.js';
4
4
  /** Current version and feature floors for the independently negotiated agent protocol. */
5
5
  export const AGENT_WIRE_VERSION = 27;
@@ -14,9 +14,7 @@ export const AGENT_WIRE_SWITCH_IDLE_VERSION = 26;
14
14
  export const AGENT_WIRE_NATIVE_DELETE_VERSION = 24;
15
15
  export const AGENT_WIRE_PROVIDER_TASK_VERSION = 23;
16
16
  export const AGENT_WIRE_RUNTIME_PROFILE_VERSION = 22;
17
- export const AGENT_WIRE_DELEGATION_RENEW_VERSION = 21;
18
17
  export const AGENT_WIRE_TURN_IDENTITY_VERSION = 20;
19
- export const AGENT_WIRE_DELEGATION_MCP_VERSION = 19;
20
18
  /** Scoped replay/history is enabled only when both peers preserve native origin. */
21
19
  export const AGENT_WIRE_SCOPED_ORIGIN_VERSION = 18;
22
20
  /** agent_open.requireSessionLoad: reject unsupported/failed load without session/new fallback. */
@@ -246,9 +244,6 @@ export const AGENT_FRAME_SHAPES = {
246
244
  env: isStringRecord, pathPrepend: isStringArray, agentEnv: isStringRecord, command: isNullableString,
247
245
  args: isStringArray, nativeSessionId: isNullableString,
248
246
  resumeFromSeq: isNumber, backfill: isBoolean, requireSessionLoad: isBoolean,
249
- delegationMcp: (value) => isObject(value) &&
250
- isString(value.url) && isStringRecord(value.headers) && isNumber(value.expiresAtEpochMs) &&
251
- (value.bindingId === undefined || isString(value.bindingId)),
252
247
  },
253
248
  },
254
249
  agent_opened: {
@@ -265,9 +260,6 @@ export const AGENT_FRAME_SHAPES = {
265
260
  // `reloaded` is `true`-or-absent for the same reason as `supportsSteering`: a `false` would
266
261
  // be a second spelling of what its absence already says.
267
262
  reloaded: isVerifiedTrue,
268
- delegationExpiresAtEpochMs: isNumber,
269
- delegationRefreshPending: isVerifiedTrue,
270
- delegationInstanceId: isString, delegationBindingId: isString, delegationRenewalRevision: isNumber,
271
263
  agentInfo: isAgentInfo, supportsImageInput: isVerifiedTrue, configOptions: isNullableSessionConfigOptionArray,
272
264
  // Same guard as `agent_verified.authMethods`, and the same absent-vs-`[]` reading: a
273
265
  // half-decoded method table is worse than none, because the reader would offer a login the
@@ -286,10 +278,6 @@ export const AGENT_FRAME_SHAPES = {
286
278
  },
287
279
  },
288
280
  agent_prompt: { required: { sessionRef: isString, text: isString }, optional: { images: isImageBlockArray, platformTurnId: isString } },
289
- agent_delegation_renew: { required: { id: isString, sessionRef: isString, nativeSessionId: isString,
290
- instanceId: isString, bindingId: isString, renewalRevision: isNumber, expiresAtEpochMs: isNumber } },
291
- agent_delegation_renewed: { required: { id: isString, ok: isBoolean }, optional: { error: isString,
292
- nativeSessionId: isString, instanceId: isString, bindingId: isString, renewalRevision: isNumber, expiresAtEpochMs: isNumber } },
293
281
  agent_answer: { required: { sessionRef: isString, requestId: isString, outcome: isString } },
294
282
  // `content`'s interior is never validated here -- it is ACP's free-form elicitation payload, and
295
283
  // this table is transport, not translator. `isRecord` only rejects a non-object.
@@ -773,7 +761,6 @@ export function outboundFailureReply(frame, reason) {
773
761
  case 'agent_steered': return { t: 'agent_steered', id, ok: false, error };
774
762
  case 'agent_account_read_result': return { t: 'agent_account_read_result', id, ok: false, error };
775
763
  case 'agent_config_option_set': return { t: 'agent_config_option_set', id, ok: false, error };
776
- case 'agent_delegation_renewed': return { t: 'agent_delegation_renewed', id, ok: false, error };
777
764
  case 'agent_switch_idle_result': return { t: 'agent_switch_idle_result', id, ok: false, error };
778
765
  case 'agent_provider_task_stopped': return { t: 'agent_provider_task_stopped', id, state: 'unknown', error };
779
766
  case 'agent_backfill_done':
@@ -790,7 +777,7 @@ export function decodeAgentFrame(text) {
790
777
  if ((frame.state === 'completed' || frame.state === 'failed' || frame.state === 'stopped') && frame.live)
791
778
  throw new TypeError('Invalid provider task terminal live state');
792
779
  if (frame.status !== undefined &&
793
- (frame.status !== frame.state || !['completed', 'failed', 'stopped'].includes(frame.status)))
780
+ (frame.status !== frame.state || !['completed', 'failed', 'stopped'].some(status => status === frame.status)))
794
781
  throw new TypeError('Invalid provider task terminal status');
795
782
  if (frame.summary !== undefined && frame.status === undefined)
796
783
  throw new TypeError('Provider task summary requires terminal status');