@platless/axom-cli 0.6.36 → 0.6.52

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 (159) hide show
  1. package/README.md +7 -1
  2. package/dist/axom-patch/cli/README.md +31 -2
  3. package/dist/axom-patch/cli/account-limits/generate.py +2 -2
  4. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  5. package/dist/axom-patch/cli/codex-terminal-output.json +2 -2
  6. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  7. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +1 -1
  8. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  9. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  10. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +137 -41
  11. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +5 -88
  12. package/dist/axom-patch/cli/qoder-native-acp.json +23 -23
  13. package/dist/axom-patch/cli/runtime.json +3 -1
  14. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  15. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  16. package/dist/axom-patch/cli/zcode-account-limits.json +7 -7
  17. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  18. package/dist/axom-patch/cli/zcode-models.json +9 -9
  19. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -10
  20. package/dist/cli.js +9 -11
  21. package/dist/commands/auth.js +4 -1
  22. package/dist/commands/update.js +68 -7
  23. package/dist/commands/workstation.js +94 -223
  24. package/dist/commands/workstationRegistrations.js +116 -0
  25. package/dist/credentialBroker.js +13 -2
  26. package/dist/credentials.js +16 -0
  27. package/dist/install/claude/commands.js +2 -2
  28. package/dist/keepers/terminal/keeper.mjs +1089 -673
  29. package/dist/keepers/terminal/manifest.json +4 -4
  30. package/dist/native/darwin-x64/axom-quic +0 -0
  31. package/dist/workstation/acp/acpAgents.js +11 -4
  32. package/dist/workstation/acp/outbox.js +12 -5
  33. package/dist/workstation/acp/permission.js +2 -2
  34. package/dist/workstation/acp/processor/checkpoint.js +4 -3
  35. package/dist/workstation/acp/processor/control.js +2 -2
  36. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  37. package/dist/workstation/acp/processor/session.js +3 -2
  38. package/dist/workstation/acp/processor/state.js +1 -0
  39. package/dist/workstation/acp/processor/transport/local.js +19 -3
  40. package/dist/workstation/acp/processor/updates.js +25 -0
  41. package/dist/workstation/acp/processorSession.js +12 -1
  42. package/dist/workstation/acp/registry/catalog.js +6 -6
  43. package/dist/workstation/acp/test/keeperWorld.js +4 -3
  44. package/dist/workstation/agentSessions.js +76 -23
  45. package/dist/workstation/channels/agent.js +9 -1
  46. package/dist/workstation/channels/computer.js +27 -4
  47. package/dist/workstation/channels/management.js +36 -5
  48. package/dist/workstation/config.js +18 -6
  49. package/dist/workstation/control/bindingStore.js +4 -0
  50. package/dist/workstation/control/commands.js +1 -0
  51. package/dist/workstation/control/daemonIdentity.js +90 -0
  52. package/dist/workstation/control/deviceCommands.js +191 -0
  53. package/dist/workstation/control/deviceProtocol.js +68 -0
  54. package/dist/workstation/control/locks.js +51 -4
  55. package/dist/workstation/control/rootFormat.js +16 -0
  56. package/dist/workstation/control/runtimeClient.js +60 -5
  57. package/dist/workstation/control/runtimeHost.js +96 -9
  58. package/dist/workstation/control/serverIdentity.js +2 -2
  59. package/dist/workstation/control/updateTransition.js +91 -0
  60. package/dist/workstation/daemon.js +262 -360
  61. package/dist/workstation/daemonctl.js +150 -12
  62. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  63. package/dist/workstation/desktopControl/automationJson.js +74 -0
  64. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  65. package/dist/workstation/desktopControl/automationPreparation.js +1 -0
  66. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  67. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  68. package/dist/workstation/desktopControl/automationSession.js +81 -0
  69. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  70. package/dist/workstation/desktopControl/capabilities.js +2 -1
  71. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  72. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  73. package/dist/workstation/desktopControl/deviceSockets.js +2 -2
  74. package/dist/workstation/desktopControl/helperProcess.js +250 -1
  75. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  76. package/dist/workstation/desktopControl/lobby.js +4 -2
  77. package/dist/workstation/desktopControl/macProvider.js +41 -40
  78. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  79. package/dist/workstation/desktopControl/nativeProvider.js +31 -5
  80. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  81. package/dist/workstation/desktopControl/protocol.js +15 -6
  82. package/dist/workstation/desktopControl/supervisor.js +110 -17
  83. package/dist/workstation/desktopControl/wallpaper.js +26 -6
  84. package/dist/workstation/exec/policy.js +5 -0
  85. package/dist/workstation/files/client.js +10 -1
  86. package/dist/workstation/files/host.js +9 -2
  87. package/dist/workstation/journal.js +5 -2
  88. package/dist/workstation/keepers/acp/client.js +38 -2
  89. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  90. package/dist/workstation/keepers/kit/client.js +2 -2
  91. package/dist/workstation/keepers/kit/launcher.js +80 -4
  92. package/dist/workstation/keepers/legacy.js +16 -2
  93. package/dist/workstation/keepers/policy.js +5 -34
  94. package/dist/workstation/keepers/terminal/client.js +115 -23
  95. package/dist/workstation/keepers/terminal/exec.js +6 -3
  96. package/dist/workstation/keepers/terminal/frames.js +73 -0
  97. package/dist/workstation/keepers/terminal/generations.js +5 -8
  98. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  99. package/dist/workstation/keepers/terminal/input.js +146 -0
  100. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  101. package/dist/workstation/keepers/terminal/main.js +1 -1
  102. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  103. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  104. package/dist/workstation/keepers/terminal/pty.js +1 -330
  105. package/dist/workstation/keepers/terminal/screen.js +246 -0
  106. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  107. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  108. package/dist/workstation/protocol/management.js +23 -4
  109. package/dist/workstation/rd/artifact.js +4 -3
  110. package/dist/workstation/rd/control.js +3 -5
  111. package/dist/workstation/rd/host.js +213 -0
  112. package/dist/workstation/rd/migrationReadiness.js +46 -0
  113. package/dist/workstation/rd/provider.js +44 -10
  114. package/dist/workstation/rd/state.js +14 -2
  115. package/dist/workstation/rd/update.js +5 -8
  116. package/dist/workstation/rd/upkeep.js +44 -2
  117. package/dist/workstation/registrations/enrollment.js +250 -0
  118. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  119. package/dist/workstation/registrations/migration.js +356 -0
  120. package/dist/workstation/registrations/model.js +34 -0
  121. package/dist/workstation/registrations/removal.js +222 -0
  122. package/dist/workstation/registrations/store.js +737 -0
  123. package/dist/workstation/runtime/desktopBinding.js +20 -0
  124. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  125. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  126. package/dist/workstation/runtime/deviceServices.js +117 -0
  127. package/dist/workstation/runtime/events.js +20 -0
  128. package/dist/workstation/runtime/ownership.js +100 -0
  129. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  130. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  131. package/dist/workstation/runtime/removalActivity.js +184 -0
  132. package/dist/workstation/service/installer.js +93 -1
  133. package/dist/workstation/service/launchd.js +1 -0
  134. package/dist/workstation/service/systemd.js +1 -0
  135. package/dist/workstation/systemMetrics.js +3 -0
  136. package/dist/workstation/tools/client.js +8 -27
  137. package/dist/workstation/tools/info.js +6 -13
  138. package/dist/workstation/tools/terminalRouter.js +17 -172
  139. package/dist/workstation/tools/validation.js +55 -1
  140. package/dist/workstation/update/archiveEvidence.js +206 -0
  141. package/dist/workstation/update/coordinator.js +34 -13
  142. package/dist/workstation/update/deviceCompatibility.js +119 -0
  143. package/dist/workstation/update/deviceCoordinator.js +369 -0
  144. package/dist/workstation/update/deviceHealth.js +104 -0
  145. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  146. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  147. package/dist/workstation/update/deviceRollback.js +78 -0
  148. package/dist/workstation/update/globalInstall.js +80 -10
  149. package/dist/workstation/update/legacyBridge.js +414 -0
  150. package/dist/workstation/update/legacyOwner.js +39 -0
  151. package/dist/workstation/update/npmInstaller.js +2 -2
  152. package/dist/workstation/update/privateDurability.js +61 -0
  153. package/dist/workstation/update/recoveryPlan.js +124 -0
  154. package/dist/workstation/update/runUpdate.js +7 -1
  155. package/dist/workstation/update/taskRegistry.js +161 -3
  156. package/dist/workstation/update/watchdog.js +27427 -14410
  157. package/dist/workstation/update/watchdogProcess.js +72 -0
  158. package/package.json +7 -3
  159. package/dist/native/darwin-arm64/axom-quic +0 -0
@@ -0,0 +1,146 @@
1
+ import { KeeperMethodError } from '../kit/protocol.js';
2
+ import { TERMINAL_GRID_LIMITS } from './gridProtocol.js';
3
+ const esc = '\x1b';
4
+ const modifiers = (mask) => 1 + (mask & 1) + (mask & 2) + (mask & 4) + (mask & 8);
5
+ const arrows = { 79: 'C', 80: 'D', 81: 'B', 82: 'A', 74: 'H', 77: 'F' };
6
+ const tilde = { 73: 2, 76: 3, 75: 5, 78: 6, 62: 15, 63: 17, 64: 18, 65: 19, 66: 20, 67: 21, 68: 23, 69: 24 };
7
+ const keypad = {
8
+ 84: ['/', 'o'], 85: ['*', 'j'], 86: ['-', 'm'], 87: ['+', 'k'], 88: ['\r', 'M'],
9
+ 89: ['1', 'q'], 90: ['2', 'r'], 91: ['3', 's'], 92: ['4', 't'], 93: ['5', 'u'], 94: ['6', 'v'],
10
+ 95: ['7', 'w'], 96: ['8', 'x'], 97: ['9', 'y'], 98: ['0', 'p'], 99: ['.', 'n'], 103: ['=', 'X'],
11
+ };
12
+ /** One attachment's only held input is its mouse buttons; modifiers are explicit per event. */
13
+ export class TerminalInputEncoder {
14
+ buttons = new Set();
15
+ release() { this.buttons.clear(); }
16
+ encode(event, screen) {
17
+ switch (event.kind) {
18
+ case 'release':
19
+ this.release();
20
+ return '';
21
+ case 'text': return event.text;
22
+ case 'paste': return screen.modes.bracketedPaste ? `${esc}[200~${event.text}${esc}[201~` : event.text;
23
+ case 'key': return event.down ? encodeKey(event.usage, event.modifiers, screen.modes) : '';
24
+ case 'button': {
25
+ if (event.down)
26
+ this.buttons.add(event.button);
27
+ else
28
+ this.buttons.delete(event.button);
29
+ if (screen.modes.mouseTracking === 'none' || (!event.down && screen.modes.mouseTracking === 'x10'))
30
+ return '';
31
+ return mouse(event.button, event.x, event.y, event.modifiers, screen.modes, !event.down);
32
+ }
33
+ case 'pointer': {
34
+ const tracking = screen.modes.mouseTracking;
35
+ if (tracking !== 'any' && !(tracking === 'drag' && this.buttons.size))
36
+ return '';
37
+ return mouse(32 + (this.buttons.values().next().value ?? 3), event.x, event.y, event.modifiers, screen.modes);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ export function createTerminalInput(screen) {
43
+ const clients = new Map();
44
+ const encoder = (id) => { let value = clients.get(id); if (!value) {
45
+ value = new TerminalInputEncoder();
46
+ clients.set(id, value);
47
+ } return value; };
48
+ return {
49
+ input(event, clientId, context) { return encoder(clientId).encode(event, context); },
50
+ scroll(gesture, _clientId, context) {
51
+ if (!gesture.lines)
52
+ return '';
53
+ const state = screen.state();
54
+ if (context.modes.mouseTracking === 'x10')
55
+ return '';
56
+ if (context.modes.mouseTracking !== 'none')
57
+ return repeat(mouse(gesture.lines < 0 ? 64 : 65, gesture.x, gesture.y, gesture.modifiers, context.modes), Math.abs(gesture.lines));
58
+ if (state.buffer === 'normal')
59
+ return undefined;
60
+ if (!context.modes.alternateScroll)
61
+ return '';
62
+ return repeat(encodeKey(gesture.lines < 0 ? 82 : 81, gesture.modifiers, context.modes), Math.abs(gesture.lines));
63
+ },
64
+ release(clientId) { clients.get(clientId)?.release(); clients.delete(clientId); },
65
+ dispose() { clients.clear(); },
66
+ };
67
+ }
68
+ function repeat(bytes, count) {
69
+ if (!bytes)
70
+ return '';
71
+ if (count > Math.floor(TERMINAL_GRID_LIMITS.maxInputBytes / Buffer.byteLength(bytes)))
72
+ throw new KeeperMethodError('bad_request', 'Terminal input exceeds limit');
73
+ if (typeof bytes === 'string')
74
+ return bytes.repeat(count);
75
+ return Buffer.concat(Array.from({ length: count }, () => bytes));
76
+ }
77
+ function mouse(button, x, y, mask, modes, release = false) {
78
+ // The wire carries cells, so pixel encoding cannot be represented faithfully.
79
+ if (modes.mouseEncoding === 'sgr-pixels')
80
+ return '';
81
+ const mods = modes.mouseTracking === 'x10' ? 0 : (mask & 1 ? 4 : 0) + (mask & 2 ? 8 : 0) + (mask & 4 ? 16 : 0);
82
+ if (modes.mouseEncoding === 'sgr')
83
+ return `${esc}[<${button + mods};${x + 1};${y + 1}${release ? 'm' : 'M'}`;
84
+ const code = (release ? 3 : button) + mods + 32;
85
+ if (modes.mouseEncoding === 'urxvt')
86
+ return `${esc}[${code};${x + 1};${y + 1}M`;
87
+ if (modes.mouseEncoding === 'default' && (x > 222 || y > 222))
88
+ return '';
89
+ if (modes.mouseEncoding === 'default')
90
+ return Buffer.from([27, 91, 77, code, x + 33, y + 33]);
91
+ return `${esc}[M${String.fromCharCode(code, x + 33, y + 33)}`;
92
+ }
93
+ function encodeKey(usage, mask, modes) {
94
+ const mod = modifiers(mask);
95
+ if (arrows[usage])
96
+ return mod > 1 ? `${esc}[1;${mod}${arrows[usage]}` : `${esc}${modes.applicationCursor ? 'O' : '['}${arrows[usage]}`;
97
+ if (tilde[usage])
98
+ return `${esc}[${tilde[usage]}${mod > 1 ? `;${mod}` : ''}~`;
99
+ if (usage >= 58 && usage <= 61)
100
+ return mod > 1 ? `${esc}[1;${mod}${String.fromCharCode(80 + usage - 58)}` : `${esc}O${String.fromCharCode(80 + usage - 58)}`;
101
+ if (keypad[usage]) {
102
+ if (mask & 8 && usage !== 88)
103
+ return '';
104
+ return modes.applicationKeypad ? mod > 1 ? `${esc}[1;${mod}${keypad[usage][1]}` : `${esc}O${keypad[usage][1]}` : prefixed(keypad[usage][0], mask);
105
+ }
106
+ if (usage === 40)
107
+ return prefixed('\r', mask);
108
+ if (usage === 41)
109
+ return esc;
110
+ if (usage === 42)
111
+ return prefixed(mask & 4 ? '\b' : '\x7f', mask);
112
+ if (usage === 43)
113
+ return mask & 1 ? `${esc}[Z` : prefixed('\t', mask);
114
+ if (mask & 8)
115
+ return ''; // Native clients handle local Meta shortcuts.
116
+ let text = '';
117
+ if (usage >= 4 && usage <= 29)
118
+ text = String.fromCharCode((mask & 1 ? 65 : 97) + usage - 4);
119
+ else if (usage >= 30 && usage <= 39)
120
+ text = (mask & 1 ? '!@#$%^&*()' : '1234567890')[usage - 30];
121
+ else {
122
+ const punctuation = { 44: [' ', ' '], 45: ['-', '_'], 46: ['=', '+'], 47: ['[', '{'], 48: [']', '}'], 49: ['\\', '|'], 50: ['#', '~'], 51: [';', ':'], 52: ["'", '"'], 53: ['`', '~'], 54: [',', '<'], 55: ['.', '>'], 56: ['/', '?'] };
123
+ text = punctuation[usage]?.[mask & 1 ? 1 : 0] ?? '';
124
+ }
125
+ if (mask & 4) {
126
+ const upper = text.toUpperCase();
127
+ if (upper >= '@' && upper <= '_')
128
+ text = String.fromCharCode(upper.charCodeAt(0) - 64);
129
+ else if (text === ' ' || text === '2' || text === '`')
130
+ text = '\0';
131
+ else if (text === '3')
132
+ text = esc;
133
+ else if (text === '4')
134
+ text = '\x1c';
135
+ else if (text === '5')
136
+ text = '\x1d';
137
+ else if (text === '6')
138
+ text = '\x1e';
139
+ else if (text === '7' || text === '/')
140
+ text = '\x1f';
141
+ else if (text === '8' || text === '?')
142
+ text = '\x7f';
143
+ }
144
+ return prefixed(text, mask);
145
+ }
146
+ function prefixed(text, mask) { return text && mask & 2 ? esc + text : text; }
@@ -1,8 +1,9 @@
1
- // keeper-terminal's methods on the kit server: `pty.*` over PtySessions, `exec.*` over ExecHost, plus their activity samplers.
1
+ // keeper-terminal's methods on the kit server: `pty.*` over NativePtySessions, `exec.*` over ExecHost, plus their activity samplers.
2
2
  // Keeper-side module: node builtins and kit siblings only (the import guard enforces it).
3
3
  import { constants, promises as fs } from 'node:fs';
4
4
  import { absolutePath, badRequest as bad, environment } from './params.js';
5
- import { CREATE_KEYS, PtySessions } from './pty.js';
5
+ import { createRequire } from 'node:module';
6
+ import { NativePtySessions, NATIVE_PTY_OPERATIONS } from './sessions.js';
6
7
  import { KeeperMethodError } from '../kit/protocol.js';
7
8
  import { EXEC_CAPABILITY, ExecErrorCode, PTY_CAPABILITY, execStream } from './protocol.js';
8
9
  /**
@@ -21,18 +22,31 @@ async function launchOf(p) {
21
22
  bad('Terminal shell is not an executable file');
22
23
  return { cwd, shell, env };
23
24
  }
24
- export const KEEPER_CREATE_KEYS = [...CREATE_KEYS, 'shell', 'env'];
25
+ export const KEEPER_CREATE_KEYS = ['id', 'cwd', 'shell', 'env', 'registrationId'];
25
26
  export function createTerminalKeeper(options = {}) {
26
- const sessions = new PtySessions(options);
27
+ let available = false;
28
+ const sessions = new NativePtySessions({ ...options, loadPty: () => {
29
+ const module = (options.loadPty ?? (() => createRequire(import.meta.url)('node-pty')))();
30
+ available = true;
31
+ return module;
32
+ } });
27
33
  const exec = options.exec;
28
34
  return {
29
35
  sessions,
30
36
  exec,
31
- capabilities: [...(sessions.available ? [PTY_CAPABILITY] : []), ...(exec ? [EXEC_CAPABILITY] : [])],
37
+ capabilities: [...(available ? [PTY_CAPABILITY] : []), ...(exec ? [EXEC_CAPABILITY] : [])],
32
38
  install(server) {
33
- server.register('pty.create', (params) => sessions.create(params, KEEPER_CREATE_KEYS, () => launchOf(params)), { startsWork: true });
34
- server.register('pty.list', (params) => sessions.list(params));
35
- for (const op of ['attach', 'poll', 'write', 'resize', 'takeover', 'detach', 'close'])
39
+ server.register('pty.create', (params) => {
40
+ if (Object.keys(params).some(key => !KEEPER_CREATE_KEYS.includes(key)))
41
+ bad('Unexpected tool parameter');
42
+ return sessions.create({ id: params.id, ...(params.cwd !== undefined ? { cwd: params.cwd } : {}), ...(params.registrationId !== undefined ? { registrationId: params.registrationId } : {}) }, () => launchOf(params));
43
+ }, { startsWork: true });
44
+ server.register('pty.list', (params) => {
45
+ if (Object.keys(params).length)
46
+ bad('Unexpected tool parameter');
47
+ return sessions.list();
48
+ });
49
+ for (const op of NATIVE_PTY_OPERATIONS)
36
50
  server.register(`pty.${op}`, (params) => sessions.operate(op, params));
37
51
  server.addActivitySampler(() => sessions.activity());
38
52
  if (!exec)
@@ -8,7 +8,7 @@ import path, { sep } from 'node:path';
8
8
  import { runKeeperMain } from '../kit/bootstrap.js';
9
9
  import { ExecHost } from './exec.js';
10
10
  import { createTerminalKeeper } from './keeper.js';
11
- export const protocolVersion = 1;
11
+ export const protocolVersion = 2;
12
12
  /**
13
13
  * node-pty from the bundle snapshot's own `node_modules` (copied next to keeper.mjs), never one found further up the
14
14
  * directory tree: the snapshot is what the build hashed. Missing or foreign → PTYs unavailable (`pty` capability absent).
@@ -0,0 +1,138 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readlink } from 'node:fs/promises';
3
+ import { hostname as localHostname } from 'node:os';
4
+ import path from 'node:path';
5
+ const clean = (value) => [...value.replace(/[\x00-\x1f\x7f-\x9f]/g, '').trim()].slice(0, 256).join('');
6
+ const validCwd = (value) => value.startsWith('/') && !/[\x00-\x1f\x7f-\x9f]/.test(value);
7
+ /** Resolved display metadata; native xterm hooks are the only OSC parser. */
8
+ export class TerminalMetadata {
9
+ options;
10
+ cwd;
11
+ cwdSource = 'launch';
12
+ explicitTitle = '';
13
+ listeners;
14
+ now;
15
+ hostname;
16
+ lastLookup = -Infinity;
17
+ oscRevision = 0;
18
+ closed = false;
19
+ pending;
20
+ cancelLookup;
21
+ timer;
22
+ constructor(options) {
23
+ this.options = options;
24
+ this.cwd = options.cwd;
25
+ this.now = options.now ?? Date.now;
26
+ this.hostname = (options.hostname ?? localHostname()).toLowerCase();
27
+ this.listeners = [options.screen.onTitleChange(title => {
28
+ if (this.closed)
29
+ return;
30
+ const previous = this.title;
31
+ this.explicitTitle = clean(title);
32
+ if (this.title !== previous)
33
+ options.changed();
34
+ }), options.screen.onCwdChange(uri => this.osc7(uri))];
35
+ }
36
+ get title() { return this.explicitTitle || clean(path.posix.basename(this.cwd) || this.cwd) || 'Terminal'; }
37
+ read() { return { title: this.title, cwd: this.cwd, cwdSource: this.cwdSource }; }
38
+ /** Called once by the host factory, independently of output and attachments. */
39
+ start() {
40
+ if (this.closed || this.timer)
41
+ return;
42
+ void this.refresh();
43
+ this.timer = setInterval(() => { void this.refresh(); }, 2000);
44
+ this.timer.unref();
45
+ }
46
+ refresh() {
47
+ if (this.closed || this.options.live?.() === false)
48
+ return Promise.resolve();
49
+ if (this.pending)
50
+ return this.pending;
51
+ if (this.now() - this.lastLookup < 2000)
52
+ return Promise.resolve();
53
+ this.lastLookup = this.now();
54
+ const revision = this.oscRevision;
55
+ let timeout;
56
+ const bounded = new Promise(resolve => {
57
+ timeout = setTimeout(() => resolve(null), 1000);
58
+ this.cancelLookup = () => resolve(null);
59
+ // Normalize synchronous failures too; no failed lookup invalidates the last cwd.
60
+ try {
61
+ void (this.options.lookup ?? lookupProcessCwd)(this.options.pid).then(resolve, () => resolve(null));
62
+ }
63
+ catch {
64
+ resolve(null);
65
+ }
66
+ });
67
+ this.pending = bounded.then(cwd => {
68
+ if (!this.closed && this.options.live?.() !== false && revision === this.oscRevision && cwd && validCwd(cwd))
69
+ this.updateCwd(cwd, 'process');
70
+ }).finally(() => { clearTimeout(timeout); this.cancelLookup = undefined; this.pending = undefined; });
71
+ return this.pending;
72
+ }
73
+ dispose() {
74
+ if (this.closed)
75
+ return;
76
+ this.closed = true;
77
+ clearInterval(this.timer);
78
+ this.timer = undefined;
79
+ this.cancelLookup?.();
80
+ for (const listener of this.listeners)
81
+ listener.dispose();
82
+ }
83
+ osc7(uri) {
84
+ if (this.closed)
85
+ return;
86
+ try {
87
+ if (/[\x00-\x1f\x7f-\x9f]/.test(uri))
88
+ return;
89
+ const url = new URL(uri);
90
+ if (url.protocol !== 'file:' || url.username || url.password || url.port || uri.includes('?') || uri.includes('#'))
91
+ return;
92
+ const host = url.hostname.toLowerCase();
93
+ if (host && host !== 'localhost' && host !== this.hostname)
94
+ return;
95
+ const cwd = decodeURIComponent(url.pathname);
96
+ if (!validCwd(cwd))
97
+ return;
98
+ this.oscRevision++;
99
+ this.updateCwd(cwd, 'osc7');
100
+ }
101
+ catch { /* malformed/untrusted OSC payload is data, never executable */ }
102
+ }
103
+ updateCwd(cwd, source) {
104
+ if (cwd === this.cwd && source === this.cwdSource)
105
+ return;
106
+ this.cwd = cwd;
107
+ this.cwdSource = source;
108
+ this.options.changed();
109
+ }
110
+ }
111
+ /** No shell interpolation: lsof receives the shell pid only as an argv element. */
112
+ export async function lookupProcessCwd(pid, platform = process.platform) {
113
+ if (!Number.isSafeInteger(pid) || pid <= 0)
114
+ return null;
115
+ if (platform === 'linux') {
116
+ try {
117
+ return await readlink(`/proc/${pid}/cwd`);
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ if (platform !== 'darwin')
124
+ return null;
125
+ return new Promise(resolve => {
126
+ execFile('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], { timeout: 1000, maxBuffer: 64 * 1024 }, (error, stdout) => {
127
+ resolve(error ? null : stdout.split('\n').find(line => line.startsWith('n/'))?.slice(1) ?? null);
128
+ });
129
+ });
130
+ }
131
+ export function createTerminalMetadata(context) {
132
+ // Capture initial values before installing metadata; read() must never call session().
133
+ const initial = context.session();
134
+ const metadata = new TerminalMetadata({ screen: context.screen, cwd: initial.cwd, pid: context.pty.pid,
135
+ changed: context.changed, live: () => !context.session().exited });
136
+ metadata.start();
137
+ return metadata;
138
+ }
@@ -1,32 +1,8 @@
1
- // keeper-terminal's wire vocabulary, shared by the keeper (`main.ts`, `pty.ts`) and the daemon (`client.ts`).
2
- // Keeper-side module: node builtins and kit siblings only (the import guard enforces it). Spec §4.1.
3
- /** The keeper protocol this code speaks. `main.ts` declares the same number for the bundler (a test keeps them equal). */
4
- export const TERMINAL_PROTOCOL_VERSION = 1;
5
- /** `keeper.hello` capability: node-pty loaded from the bundle snapshot, so `pty.*` works. Absent = PTYs unavailable. */
6
- export const PTY_CAPABILITY = 'pty';
7
- /** The legacy keeper's wording (tools/terminal.ts at the merge-base) when node-pty does not load; every `pty.*` call but `pty.list` answers it. */
1
+ // Terminal transport is a direct native-grid protocol replacement. Durable exec vocabulary follows.
2
+ export const TERMINAL_PROTOCOL_VERSION = 2;
3
+ export const PTY_CAPABILITY = 'pty-grid-v1';
8
4
  export const PTY_UNAVAILABLE_ERROR = 'Terminal native dependency unavailable; reinstall CLI with optional node-pty support';
9
- /** Ring of rendered output kept per session, the largest poll delta, the scrollback serialised into a snapshot. */
10
- export const PTY_HISTORY_BYTES = 1024 * 1024;
11
- export const PTY_MAX_POLL_BYTES = 64 * 1024;
12
- export const PTY_SCROLLBACK = 1000;
13
- /** A snapshot must fit one tool result (2 MiB, the same as keeper-terminal's outbound frame cap). */
14
- export const PTY_RESULT_BYTES = 2 * 1024 * 1024;
15
- export const PTY_SESSION_LIMIT = 8;
16
- export const PTY_INPUT_BYTES = 64 * 1024;
17
- /** Longest activity label (the foreground process basename; never its arguments). */
18
- export const PTY_LABEL_CHARS = 64;
19
- export const PTY_METHODS = ['pty.create', 'pty.attach', 'pty.poll', 'pty.write', 'pty.resize', 'pty.takeover', 'pty.detach', 'pty.close', 'pty.list'];
20
- /** Typed error codes of `pty.*` (the message keeps today's `terminal.*` wording). */
21
- export const PtyErrorCode = {
22
- unavailable: 'pty_unavailable',
23
- badRequest: 'bad_request',
24
- notFound: 'pty_not_found',
25
- notController: 'pty_not_controller',
26
- limit: 'pty_limit',
27
- exited: 'pty_exited',
28
- snapshotTooLarge: 'pty_snapshot_too_large',
29
- };
5
+ export const PTY_METHODS = ['pty.list', 'pty.create', 'pty.attach', 'pty.poll', 'pty.resize', 'pty.input', 'pty.scroll', 'pty.detach', 'pty.close'];
30
6
  // ---- exec (spec §4.2): non-interactive commands with durable per-exec output logs ----------------------------------
31
7
  /** `keeper.hello` capability: `exec.*` is served. */
32
8
  export const EXEC_CAPABILITY = 'exec';