@platless/axom-cli 0.1.0 → 0.3.13

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 (109) hide show
  1. package/README.md +1 -1
  2. package/dist/authScopes.js +19 -0
  3. package/dist/axom-patch/cli/README.md +110 -0
  4. package/dist/axom-patch/cli/pi-steering.json +37 -0
  5. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +34 -0
  6. package/dist/axom-patch/cli/probes/pi-steering.mjs +80 -0
  7. package/dist/axom-patch/cli/probes/qoder-steering.mjs +89 -0
  8. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +49 -0
  9. package/dist/axom-patch/cli/qoder-background-terminal.md +81 -0
  10. package/dist/axom-patch/cli/qoder-foreground-output.md +41 -0
  11. package/dist/axom-patch/cli/qoder-steering.json +52 -0
  12. package/dist/axom-patch/cli/qoder-steering.md +30 -0
  13. package/dist/axom-patch/cli/qoder-subagent-origin.json +73 -0
  14. package/dist/axom-patch/cli/qoder-subagent-subwork.md +73 -0
  15. package/dist/axom-patch/cli/runtime.json +37 -0
  16. package/dist/axom-patch/cli/steering/claude-steering-js.patch +89 -0
  17. package/dist/axom-patch/cli/steering/claude-steering-types.patch +28 -0
  18. package/dist/axom-patch/cli/steering/codex-steering.patch +57 -0
  19. package/dist/axom-patch/cli/steering/pi-steering.js +79 -0
  20. package/dist/cli.js +9 -1
  21. package/dist/commands/auth.js +4 -13
  22. package/dist/commands/execute.js +4 -4
  23. package/dist/commands/record.js +6 -6
  24. package/dist/commands/struct.js +4 -4
  25. package/dist/commands/workstation.js +2 -2
  26. package/dist/credentials.js +2 -2
  27. package/dist/install/claude/commands.js +13 -13
  28. package/dist/mcp.js +32 -14
  29. package/dist/workstation/acp/acpAgents.js +447 -166
  30. package/dist/workstation/acp/acpClient.js +232 -18
  31. package/dist/workstation/acp/acpSession.js +502 -74
  32. package/dist/workstation/acp/activityBusy.js +43 -0
  33. package/dist/workstation/acp/activityReporting.js +54 -0
  34. package/dist/workstation/acp/adoption.js +107 -49
  35. package/dist/workstation/acp/authUrlSniffer.js +36 -0
  36. package/dist/workstation/acp/contract.js +11 -6
  37. package/dist/workstation/acp/delegationMcp.js +24 -0
  38. package/dist/workstation/acp/draftInterceptor.js +9 -0
  39. package/dist/workstation/acp/managedPatches.js +228 -0
  40. package/dist/workstation/acp/patchAssets.js +63 -0
  41. package/dist/workstation/acp/permission.js +300 -34
  42. package/dist/workstation/acp/qoderStartup.js +45 -0
  43. package/dist/workstation/acp/registry/catalog.js +263 -4
  44. package/dist/workstation/acp/registry/detect.js +55 -0
  45. package/dist/workstation/acp/sessionScope.js +19 -0
  46. package/dist/workstation/acp/sessionUpdateMapper.js +39 -21
  47. package/dist/workstation/acp/terminalLogin.js +197 -0
  48. package/dist/workstation/acp/test/fakeAgent.js +185 -9
  49. package/dist/workstation/acp/validate.js +33 -5
  50. package/dist/workstation/agentSessions.js +532 -44
  51. package/dist/workstation/channels/agent.js +7 -1
  52. package/dist/workstation/channels/computer.js +152 -4
  53. package/dist/workstation/channels/management.js +3 -3
  54. package/dist/workstation/config.js +3 -1
  55. package/dist/workstation/connection/encryptedChannel.js +88 -21
  56. package/dist/workstation/daemon.js +25 -3
  57. package/dist/workstation/desktop/binding.js +36 -0
  58. package/dist/workstation/desktop/bridge.js +212 -0
  59. package/dist/workstation/desktop/flow-control.js +52 -0
  60. package/dist/workstation/desktop/focused-text.js +130 -0
  61. package/dist/workstation/desktop/protocol.js +35 -0
  62. package/dist/workstation/fileOps.js +277 -0
  63. package/dist/workstation/filePaths.js +174 -0
  64. package/dist/workstation/fileWorker.js +81 -0
  65. package/dist/workstation/fileWorkerClient.js +121 -0
  66. package/dist/workstation/oauth/deviceAuthorization.js +21 -12
  67. package/dist/workstation/protocol/agent.js +154 -5
  68. package/dist/workstation/protocol/common.js +6 -0
  69. package/dist/workstation/protocol/computer.js +92 -3
  70. package/dist/workstation/qoderAdapter/backgroundProcess.js +106 -0
  71. package/dist/workstation/qoderAdapter/backgroundTerminal.js +262 -0
  72. package/dist/workstation/qoderAdapter/completedContinuation.js +139 -0
  73. package/dist/workstation/qoderAdapter/contract.js +78 -0
  74. package/dist/workstation/qoderAdapter/foregroundOutput.js +30 -0
  75. package/dist/workstation/qoderAdapter/history.js +87 -0
  76. package/dist/workstation/qoderAdapter/identityIndex.js +191 -0
  77. package/dist/workstation/qoderAdapter/inputQueue.js +42 -0
  78. package/dist/workstation/qoderAdapter/main.js +93 -0
  79. package/dist/workstation/qoderAdapter/mapper.js +117 -0
  80. package/dist/workstation/qoderAdapter/observerPlugin.js +245 -0
  81. package/dist/workstation/qoderAdapter/observerReceiver.js +187 -0
  82. package/dist/workstation/qoderAdapter/permissions.js +61 -0
  83. package/dist/workstation/qoderAdapter/profile.js +93 -0
  84. package/dist/workstation/qoderAdapter/sdkDriver.js +49 -0
  85. package/dist/workstation/qoderAdapter/session.js +319 -0
  86. package/dist/workstation/qoderAdapter/subagentMeta.js +28 -0
  87. package/dist/workstation/qoderAdapter/subwork.js +77 -0
  88. package/dist/workstation/qoderAdapter/taskControl.js +38 -0
  89. package/dist/workstation/qoderAdapter/taskEvent.js +26 -0
  90. package/dist/workstation/qoderAdapter/taskLedger.js +163 -0
  91. package/dist/workstation/qoderAdapter/types.js +1 -0
  92. package/dist/workstation/qoderAdapter/usage.js +37 -0
  93. package/dist/workstation/register.js +4 -0
  94. package/dist/workstation/tools/browser.js +394 -0
  95. package/dist/workstation/tools/client.js +269 -0
  96. package/dist/workstation/tools/core.js +50 -0
  97. package/dist/workstation/tools/files.js +525 -0
  98. package/dist/workstation/tools/keeperProcess.js +114 -0
  99. package/dist/workstation/tools/terminal.js +214 -0
  100. package/dist/workstation/tools/validation.js +50 -0
  101. package/dist/workstation/update/coordinator.js +29 -0
  102. package/dist/workstation/update/npmInstaller.js +2 -0
  103. package/dist/workstation/update/ptyPreflight.js +24 -0
  104. package/dist/workstation/update/taskRegistry.js +44 -20
  105. package/dist/workstation/update/watchdog.js +66 -0
  106. package/package.json +14 -4
  107. package/skills/axom/SKILL.md +55 -17
  108. package/skills/axom-definition/SKILL.md +32 -32
  109. package/skills/axom-platform/SKILL.md +39 -39
@@ -0,0 +1,319 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { isAbsolute } from 'node:path';
3
+ import { InputQueue } from './inputQueue.js';
4
+ import { createObserverPlugin } from './observerPlugin.js';
5
+ import { ObserverReceiver } from './observerReceiver.js';
6
+ import { PermissionBridge } from './permissions.js';
7
+ import { QoderMessageMapper } from './mapper.js';
8
+ import { TaskLedger } from './taskLedger.js';
9
+ import { TaskControl } from './taskControl.js';
10
+ import { readChildHistory } from './history.js';
11
+ import { supportsQoderSdkSteering } from './sdkDriver.js';
12
+ export class QoderAdapterSession {
13
+ dependencies;
14
+ query;
15
+ input = new InputQueue();
16
+ receiver;
17
+ permissions;
18
+ mapper;
19
+ taskLedger;
20
+ taskControl;
21
+ resultIds = new Set();
22
+ earlyObservations = [];
23
+ degraded = false;
24
+ updateTail = Promise.resolve();
25
+ taskTail = Promise.resolve();
26
+ queuedUpdates = 0;
27
+ pump;
28
+ shutdown;
29
+ closed = false;
30
+ root;
31
+ cwd;
32
+ profileState;
33
+ active;
34
+ constructor(dependencies) {
35
+ this.dependencies = dependencies;
36
+ }
37
+ get currentProfile() {
38
+ return this.profileState ? { ...this.profileState, capabilities: [...this.profileState.capabilities] } : undefined;
39
+ }
40
+ /** Public-SDK child history is exposed through the live adapter, never through private files. */
41
+ async readChildHistory(agentId, page) {
42
+ const root = this.root;
43
+ const dir = page?.dir ?? this.cwd;
44
+ if (!root || !dir) {
45
+ return { status: 'unknown', entries: [], rootSessionId: root ?? 'unknown', agentId,
46
+ offset: page?.offset ?? 0, limit: page?.limit ?? 50, hasMore: false, snapshotStable: false,
47
+ reason: 'invalid-page' };
48
+ }
49
+ return readChildHistory(root, agentId, { ...page, dir });
50
+ }
51
+ async publishProfile() {
52
+ const value = this.currentProfile;
53
+ if (value)
54
+ await this.dependencies.profile(value);
55
+ }
56
+ degrade() {
57
+ if (this.degraded)
58
+ return;
59
+ this.degraded = true;
60
+ for (const task of this.taskLedger?.markStale() ?? [])
61
+ void this.queueTask(task, `degraded-${this.profileState?.runtimeGeneration}`).catch(() => { });
62
+ if (this.profileState)
63
+ this.profileState.observerHealthy = false;
64
+ void this.publishProfile().catch(() => { });
65
+ }
66
+ publishTask(task, uuid, summary) {
67
+ return this.dependencies.taskObservation?.({ rootSessionId: task.origin.rootSessionId,
68
+ runtimeGeneration: task.origin.runtimeGeneration, taskId: task.origin.taskId, uuid,
69
+ state: task.state, live: task.live, stale: task.stale,
70
+ ...(task.taskType ? { taskType: task.taskType } : {}),
71
+ ...(task.background !== undefined ? { background: task.background } : {}),
72
+ ...(task.origin.agentId ? { agentId: task.origin.agentId } : {}),
73
+ ...(task.origin.launchingToolUseId ? { launchingToolUseId: task.origin.launchingToolUseId } : {}),
74
+ ...(summary !== undefined ? { summary, status: task.state } : {}) }) ?? Promise.resolve();
75
+ }
76
+ queueTask(task, uuid, summary) {
77
+ const pending = this.taskTail.then(() => this.publishTask(task, uuid, summary));
78
+ this.taskTail = pending.catch(() => this.degrade());
79
+ return pending;
80
+ }
81
+ sendUpdate(update) {
82
+ if (this.closed)
83
+ return Promise.resolve();
84
+ if (this.queuedUpdates >= 256) {
85
+ this.degrade();
86
+ return Promise.resolve();
87
+ }
88
+ this.queuedUpdates++;
89
+ const pending = this.updateTail.then(async () => {
90
+ if (!this.closed)
91
+ await this.dependencies.update({ sessionId: this.root, update });
92
+ }).finally(() => { this.queuedUpdates--; });
93
+ this.updateTail = pending.catch(() => { this.degrade(); });
94
+ return pending;
95
+ }
96
+ observe(observation) {
97
+ if (this.closed)
98
+ return;
99
+ if (!this.profileState?.rootConfirmed) {
100
+ if (this.earlyObservations.length >= 64)
101
+ this.degrade();
102
+ else
103
+ this.earlyObservations.push(observation);
104
+ return;
105
+ }
106
+ if (observation.origin)
107
+ this.mapper?.observe(observation.origin);
108
+ if (observation.launch) {
109
+ this.mapper?.observe(observation.launch);
110
+ if (observation.launch.launchingToolUseId && observation.launch.agentId)
111
+ for (const task of this.taskLedger?.observeLaunch(observation.launch.launchingToolUseId, observation.launch.agentId) ?? [])
112
+ void this.queueTask(task, `launch-${observation.launch.launchingToolUseId}`).catch(() => this.degrade());
113
+ }
114
+ if (observation.conflict)
115
+ this.mapper?.conflict(observation.conflict);
116
+ if (this.profileState)
117
+ this.profileState.observerHealthy = !this.degraded && this.receiver?.healthy === true;
118
+ void this.publishProfile().catch(() => { });
119
+ }
120
+ async newSession(request) {
121
+ if (this.root || this.closed)
122
+ throw new Error('Session already opened or closed');
123
+ if (!isAbsolute(request.cwd))
124
+ throw new Error('Session cwd must be absolute');
125
+ if (request.mcpServers.length)
126
+ throw new Error('MCP server mapping is not supported');
127
+ this.root = randomUUID();
128
+ this.cwd = request.cwd;
129
+ const generation = randomUUID();
130
+ this.taskLedger = new TaskLedger(this.root, generation);
131
+ this.profileState = { cliVersion: this.dependencies.driver.cliVersion, sdkVersion: this.dependencies.driver.sdkVersion,
132
+ executable: this.dependencies.driver.executable, persistent: true, mode: 'sdk-adapter', agentMessaging: false,
133
+ transport: 'process', capabilities: [], observerHealthy: false, completedContinuationEnabled: false,
134
+ sendMessageExposed: false, initialized: false, rootConfirmed: false, platform: process.platform,
135
+ runtimeSource: 'explicit-local', rootSessionId: this.root, runtimeGeneration: generation };
136
+ try {
137
+ const plugin = await createObserverPlugin(this.dependencies.runDir, { rootSessionId: this.root,
138
+ runtimeGeneration: generation, token: randomUUID() });
139
+ this.mapper = new QoderMessageMapper({ rootSessionId: this.root, runtimeGeneration: generation }, {
140
+ correction: (update) => { void this.sendUpdate(update).catch(() => { }); }, degraded: () => this.degrade()
141
+ });
142
+ this.receiver = new ObserverReceiver(plugin, { onObservation: (observation) => this.observe(observation), onDegraded: () => this.degrade() });
143
+ this.permissions = new PermissionBridge({ rootSessionId: this.root, runtimeGeneration: generation,
144
+ request: this.dependencies.permission ?? (async () => ({ outcome: { outcome: 'cancelled' } })) });
145
+ this.query = this.dependencies.driver.create({ prompt: this.input, options: { cwd: request.cwd,
146
+ sessionId: this.root, plugins: [{ type: 'local', path: plugin.pluginDir }],
147
+ canUseTool: this.permissions.canUseTool, onElicitation: this.permissions.onElicitation } });
148
+ this.taskControl = new TaskControl(this.taskLedger, () => this.currentProfile, this.query, this.dependencies.certifications ?? [], this.dependencies.backendBinding);
149
+ this.pump = this.readMessages();
150
+ const initialized = await this.query.initializationResult();
151
+ if (this.closed)
152
+ throw new Error('Session closed during initialization');
153
+ this.profileState.initialized = true;
154
+ this.profileState.capabilities = initialized.capabilities ?? [];
155
+ await this.publishProfile();
156
+ this.receiver.start();
157
+ return { sessionId: this.root, _meta: { qoderProfile: this.currentProfile } };
158
+ }
159
+ catch (error) {
160
+ await this.close();
161
+ throw error;
162
+ }
163
+ }
164
+ async readMessages() {
165
+ try {
166
+ for await (const message of this.query) {
167
+ if (this.closed)
168
+ break;
169
+ if (message.type === 'system' && message.subtype === 'init') {
170
+ if (message.session_id !== this.root || message.cwd !== this.cwd ||
171
+ message.qodercli_version !== this.dependencies.driver.cliVersion ||
172
+ message.protocol_version !== '1.5.0')
173
+ throw new Error('SDK root identity/profile mismatch');
174
+ this.profileState.rootConfirmed = true;
175
+ this.profileState.protocolVersion = 1;
176
+ for (const observation of this.earlyObservations.splice(0))
177
+ this.observe(observation);
178
+ await this.publishProfile();
179
+ continue;
180
+ }
181
+ if (!this.profileState?.rootConfirmed || message.session_id !== this.root)
182
+ continue;
183
+ await this.receive(message);
184
+ }
185
+ if (!this.closed)
186
+ throw new Error('SDK stream ended');
187
+ }
188
+ catch (error) {
189
+ this.active?.reject(error instanceof Error ? error : new Error('SDK transport failed'));
190
+ this.active = undefined;
191
+ if (this.profileState)
192
+ this.profileState.rootConfirmed = false;
193
+ void this.close();
194
+ }
195
+ }
196
+ async receive(message) {
197
+ if (message.type === 'result') {
198
+ if (this.resultIds.has(message.uuid))
199
+ return;
200
+ if (this.resultIds.size >= 4096)
201
+ throw new Error('SDK result identity capacity exceeded');
202
+ this.resultIds.add(message.uuid);
203
+ if (message.origin !== undefined && message.origin.kind !== 'human')
204
+ return;
205
+ const active = this.active;
206
+ if (message.subtype === 'error_during_execution' &&
207
+ (message.terminal_reason === 'aborted_streaming' || message.terminal_reason === 'aborted_tools') &&
208
+ active?.steeringPending) {
209
+ // Qoder reports the interrupted prefix as a terminal error before it
210
+ // starts the injected `now` message. The 1.1.61 SDK uses
211
+ // `aborted_tools` when the current foreground tool is cancelled,
212
+ // while newer builds may use `aborted_streaming`. Both are an
213
+ // internal hand-off, not the host prompt's final result.
214
+ active.steeringPending--;
215
+ return;
216
+ }
217
+ this.active = undefined;
218
+ if (message.is_error || message.subtype !== 'success')
219
+ active?.reject(new Error(`SDK result error: ${message.error_code ?? message.subtype}`));
220
+ else
221
+ active?.resolve({ stopReason: message.terminal_reason === 'interrupted' ? 'cancelled' : 'end_turn' });
222
+ }
223
+ else if (message.type === 'system' && ['task_started', 'task_progress', 'task_updated',
224
+ 'background_tasks_changed', 'task_notification'].includes(message.subtype)) {
225
+ const changed = this.taskLedger?.apply(message) ?? [];
226
+ if (this.taskLedger?.degraded)
227
+ this.degrade();
228
+ for (const task of changed)
229
+ await this.queueTask(task, message.uuid, message.subtype === 'task_notification' && task.origin.taskId === message.task_id
230
+ ? message.summary.slice(0, 8192) : undefined);
231
+ }
232
+ else {
233
+ for (const update of this.mapper?.map(message, this.active?.runId) ?? [])
234
+ await this.sendUpdate(update);
235
+ }
236
+ }
237
+ async prompt(request) {
238
+ if (this.closed || !this.query || request.sessionId !== this.root || !this.profileState?.initialized)
239
+ throw new Error('Session unavailable');
240
+ if (this.active)
241
+ throw new Error('Session prompt already active');
242
+ if (request.prompt.some((block) => block.type !== 'text'))
243
+ throw new Error('Prompt media/resource mapping is unsupported');
244
+ const text = request.prompt.map((block) => block.type === 'text' ? block.text : '').join('\n');
245
+ if (Buffer.byteLength(text, 'utf8') > 131072)
246
+ throw new Error('Prompt capacity exceeded');
247
+ return new Promise((resolve, reject) => {
248
+ const runId = randomUUID();
249
+ this.active = { runId, resolve, reject, steeringPending: 0 };
250
+ try {
251
+ this.input.push({ type: 'user', uuid: runId, session_id: this.root,
252
+ parent_tool_use_id: null, message: { role: 'user', content: text } });
253
+ }
254
+ catch (error) {
255
+ this.active = undefined;
256
+ reject(error);
257
+ }
258
+ });
259
+ }
260
+ async steer(blocks, priority) {
261
+ if (this.closed || !this.query || !this.profileState?.initialized || !this.profileState.rootConfirmed ||
262
+ !supportsQoderSdkSteering(this.dependencies.driver.cliVersion) || !this.active)
263
+ return 'promptRequired';
264
+ if (blocks.some((block) => block.type !== 'text'))
265
+ throw new Error('Steering media/resource mapping is unsupported');
266
+ const text = blocks.map((block) => block.type === 'text' ? block.text : '').join('\n');
267
+ if (!text.trim())
268
+ throw new Error('Steering prompt must contain text');
269
+ if (Buffer.byteLength(text, 'utf8') > 131072)
270
+ throw new Error('Steering capacity exceeded');
271
+ const active = this.active;
272
+ active.steeringPending++;
273
+ try {
274
+ this.input.push({ type: 'user', uuid: randomUUID(), session_id: this.root,
275
+ parent_tool_use_id: null, origin: { kind: 'human' }, shouldQuery: true,
276
+ ...(priority === undefined ? {} : { priority }), message: { role: 'user', content: text } });
277
+ return 'injected';
278
+ }
279
+ catch (error) {
280
+ active.steeringPending--;
281
+ throw error;
282
+ }
283
+ }
284
+ async cancel() { if (!this.closed && this.query)
285
+ await this.query.interrupt(); }
286
+ async stopTask(request) {
287
+ if (this.closed || !this.taskControl)
288
+ throw new Error('Task stop unavailable');
289
+ return this.taskControl.stop(request);
290
+ }
291
+ close() {
292
+ if (this.shutdown)
293
+ return this.shutdown;
294
+ this.closed = true;
295
+ this.input.close();
296
+ for (const task of this.taskLedger?.markStale() ?? [])
297
+ void this.queueTask(task, `closed-${this.profileState?.runtimeGeneration}`).catch(() => { });
298
+ this.permissions?.close();
299
+ this.active?.reject(new Error('Session closed'));
300
+ this.active = undefined;
301
+ this.shutdown = (async () => {
302
+ try {
303
+ await this.query?.close();
304
+ }
305
+ finally {
306
+ await this.pump;
307
+ await this.updateTail;
308
+ await this.taskTail;
309
+ await this.receiver?.dispose();
310
+ this.mapper?.dispose();
311
+ if (this.profileState) {
312
+ this.profileState.observerHealthy = false;
313
+ await this.publishProfile();
314
+ }
315
+ }
316
+ })();
317
+ return this.shutdown;
318
+ }
319
+ }
@@ -0,0 +1,28 @@
1
+ export const QODER_SUBAGENT_META_VERSION = 1;
2
+ export function qoderSubagentMeta(value) {
3
+ return { qoder: { version: QODER_SUBAGENT_META_VERSION, ...value } };
4
+ }
5
+ export function qoderChildMeta(parentToolUseId, origin, extra = { source: 'sdk' }) {
6
+ return qoderSubagentMeta({
7
+ ...extra,
8
+ parentToolUseId,
9
+ route: 'child',
10
+ ...(origin?.agentId ? { agentId: origin.agentId } : {}),
11
+ ...(origin?.launchingToolUseId ? { launchingToolUseId: origin.launchingToolUseId } : {}),
12
+ });
13
+ }
14
+ export function qoderSpawnMeta(toolUseId) {
15
+ return qoderSubagentMeta({ source: 'sdk', toolName: 'Agent', subagent: true, toolUseId });
16
+ }
17
+ export function qoderCorrectionMeta(source, origin) {
18
+ const base = {
19
+ source: 'correction', toolUseId: source.toolUseId,
20
+ ...(origin?.agentId ? { agentId: origin.agentId } : {}),
21
+ ...(origin?.launchingToolUseId ? { launchingToolUseId: origin.launchingToolUseId } : {}),
22
+ };
23
+ if (source.parentToolUseId && origin)
24
+ return qoderSubagentMeta({
25
+ ...base, parentToolUseId: source.parentToolUseId, route: 'child',
26
+ });
27
+ return qoderSubagentMeta({ ...base, ...(origin ? {} : { route: 'unknown' }) });
28
+ }
@@ -0,0 +1,77 @@
1
+ const object = (v) => v && typeof v === 'object' && !Array.isArray(v) ? v : {};
2
+ const id = (v) => typeof v === 'string' && v.length > 0 && v.length <= 256 && !/[\x00-\x1f\x7f]/.test(v);
3
+ export function supportsSubworkMeta(info) {
4
+ return (info?.name === 'qoder-cli' && info.version === '1.1.61') ||
5
+ (info?.name === 'axom-qoder-sdk' && info.version === '1.0.50');
6
+ }
7
+ function resultText(value) {
8
+ if (typeof value === 'string')
9
+ return value;
10
+ if (Array.isArray(value)) {
11
+ const texts = value.filter(v => object(v).type === 'text').map(v => object(v).text).filter(v => typeof v === 'string');
12
+ return texts.length ? texts.join('\n') : undefined;
13
+ }
14
+ const v = object(value);
15
+ if (v.kind === 'agent-result')
16
+ return resultText(v.content ?? v.result);
17
+ return undefined;
18
+ }
19
+ export class SubworkBoundary {
20
+ root;
21
+ calls = new Map();
22
+ constructor(root) {
23
+ this.root = root;
24
+ }
25
+ child(toolId) { return `qoder-agent:${this.root}:${toolId}`; }
26
+ map(event) {
27
+ if (event.acpSessionId && event.acpSessionId !== this.root)
28
+ return [event];
29
+ const p = event.payload, q = object(object(p._meta).qoder), input = object(p.rawInput);
30
+ const marked = q.version === 1 && ['native-acp-patch', 'sdk', 'correction'].includes(String(q.source));
31
+ const toolId = p.toolCallId;
32
+ let call = id(toolId) ? this.calls.get(toolId) : undefined;
33
+ const parent = marked && q.route !== 'unknown' && id(q.parentToolUseId) ? q.parentToolUseId : undefined;
34
+ const events = [];
35
+ if (!call && marked && p.kind === 'think' && q.toolName === 'Agent' && id(toolId) &&
36
+ typeof input.prompt === 'string' && (input.subagent_type == null || typeof input.subagent_type === 'string') &&
37
+ (input.run_in_background === undefined || typeof input.run_in_background === 'boolean') &&
38
+ ['tool_call', 'tool_call_update'].includes(event.eventName) && this.calls.size < 4096) {
39
+ call = { child: this.child(toolId), ...(parent ? { parent: this.child(parent) } : {}),
40
+ background: input.run_in_background === true, ended: false, streamedText: '' };
41
+ this.calls.set(toolId, call);
42
+ events.push({ eventName: 'subagent_spawned', payload: { subagentSessionId: call.child,
43
+ name: typeof input.description === 'string' && input.description.trim() ? input.description : input.subagent_type ?? 'Agent',
44
+ task: input.prompt, capabilities: {} }, ...(call.parent ? { acpSessionId: call.parent } : {}) });
45
+ events.push({ eventName: 'user_message_chunk', payload: { content: { type: 'text', text: input.prompt } }, acpSessionId: call.child });
46
+ }
47
+ if (call && ['tool_call', 'tool_call_update'].includes(event.eventName)) {
48
+ if (call.ended)
49
+ return events;
50
+ const output = object(p.rawOutput);
51
+ const state = output.kind === 'agent-result' ? output.state : undefined;
52
+ const terminal = state === 'completed' ? 'completed' : ['error', 'failed'].includes(String(state)) ? 'failed' :
53
+ state === 'cancelled' ? 'cancelled' : ['failed', 'cancelled'].includes(String(p.status)) ? p.status :
54
+ !call.background && p.status === 'completed' ? 'completed' : undefined;
55
+ if (terminal) {
56
+ const text = resultText(p.rawOutput);
57
+ if (text !== undefined && !call.streamedText.endsWith(text))
58
+ events.push({ eventName: 'agent_message_chunk',
59
+ payload: { content: { type: 'text', text } }, acpSessionId: call.child });
60
+ events.push({ eventName: 'subagent_state_update', payload: { subagentSessionId: call.child, state: terminal },
61
+ ...(call.parent ? { acpSessionId: call.parent } : {}) });
62
+ call.ended = true;
63
+ }
64
+ // The Agent invocation becomes a dispatch/result in subwork. Keeping the vendor tool here
65
+ // would also create the old delegation card for the very same task.
66
+ return events;
67
+ }
68
+ if (parent) {
69
+ const owner = this.calls.get(parent), content = object(p.content);
70
+ if (owner && event.eventName === 'agent_message_chunk' && content.type === 'text' && typeof content.text === 'string') {
71
+ owner.streamedText += content.text;
72
+ }
73
+ return [{ ...event, acpSessionId: this.child(parent) }];
74
+ }
75
+ return [event];
76
+ }
77
+ }
@@ -0,0 +1,38 @@
1
+ import { capabilitiesFor } from './profile.js';
2
+ const validId = (value) => value.length > 0 && Buffer.byteLength(value, 'utf8') <= 256 &&
3
+ !/[\x00-\x1f\x7f]/.test(value);
4
+ export class TaskControl {
5
+ ledger;
6
+ profile;
7
+ query;
8
+ certifications;
9
+ binding;
10
+ requests = new Map();
11
+ constructor(ledger, profile, query, certifications, binding) {
12
+ this.ledger = ledger;
13
+ this.profile = profile;
14
+ this.query = query;
15
+ this.certifications = certifications;
16
+ this.binding = binding;
17
+ }
18
+ async stop(request) {
19
+ if (![request.sessionId, request.taskId, request.requestId].every(validId))
20
+ throw new Error('Invalid task stop identity');
21
+ const previous = this.requests.get(request.requestId);
22
+ if (previous) {
23
+ if (previous.sessionId !== request.sessionId || previous.taskId !== request.taskId)
24
+ throw new Error('Task stop request identity conflict');
25
+ return previous.receipt;
26
+ }
27
+ const profile = this.profile(), task = this.ledger.get(request.taskId);
28
+ if (!profile || !task || request.sessionId !== profile.rootSessionId ||
29
+ !task.origin.agentId || !task.origin.launchingToolUseId || task.taskType !== 'local_agent' ||
30
+ task.background !== true || !capabilitiesFor(profile, task, this.certifications, this.binding).stopActiveRun)
31
+ throw new Error('Task stop unavailable');
32
+ if (this.requests.size >= 4096)
33
+ throw new Error('Task stop receipt capacity exceeded');
34
+ const receipt = this.query.stopTask(request.taskId).then(() => ({ requestId: request.requestId, state: 'accepted' }), () => ({ requestId: request.requestId, state: 'unknown' }));
35
+ this.requests.set(request.requestId, { sessionId: request.sessionId, taskId: request.taskId, receipt });
36
+ return receipt;
37
+ }
38
+ }
@@ -0,0 +1,26 @@
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
+ }
@@ -0,0 +1,163 @@
1
+ const identifier = (value) => typeof value === 'string' && value.length > 0 &&
2
+ Buffer.byteLength(value, 'utf8') <= 256 && !/[\x00-\x1f\x7f]/.test(value);
3
+ const terminal = (state) => state === 'completed' || state === 'failed' || state === 'stopped';
4
+ const defaultMaxTasks = 4096;
5
+ const sameTask = (a, b) => a.state === b.state && a.live === b.live && a.stale === b.stale && a.taskType === b.taskType &&
6
+ a.background === b.background && JSON.stringify(a.origin) === JSON.stringify(b.origin);
7
+ export class TaskLedger {
8
+ rootSessionId;
9
+ runtimeGeneration;
10
+ maxTasks;
11
+ tasks = new Map();
12
+ seen = new Set();
13
+ actors = new Map();
14
+ launchTasks = new Map();
15
+ conflicts = new Set();
16
+ notified = new Set();
17
+ failed = false;
18
+ constructor(rootSessionId, runtimeGeneration, maxTasks = defaultMaxTasks) {
19
+ this.rootSessionId = rootSessionId;
20
+ this.runtimeGeneration = runtimeGeneration;
21
+ this.maxTasks = maxTasks;
22
+ if (!identifier(rootSessionId) || !identifier(runtimeGeneration))
23
+ throw new Error('Invalid task ledger binding');
24
+ if (!Number.isInteger(maxTasks) || maxTasks < 1 || maxTasks > defaultMaxTasks)
25
+ throw new Error('Invalid task ledger capacity');
26
+ }
27
+ get(taskId) {
28
+ const task = this.tasks.get(taskId);
29
+ return task && { ...task, origin: { ...task.origin } };
30
+ }
31
+ all() { return [...this.tasks.keys()].map((id) => this.get(id)); }
32
+ get launchObservationCount() { return this.actors.size; }
33
+ get degraded() { return this.failed; }
34
+ markStale() {
35
+ for (const [id, task] of this.tasks)
36
+ this.tasks.set(id, { ...task, live: false, stale: true });
37
+ return this.all();
38
+ }
39
+ degrade() {
40
+ this.failed = true;
41
+ return this.markStale();
42
+ }
43
+ observeLaunch(toolUseId, agentId) {
44
+ if (this.failed || !identifier(toolUseId) || !identifier(agentId))
45
+ return [];
46
+ const prior = this.actors.get(toolUseId);
47
+ if (prior !== undefined && prior !== agentId)
48
+ this.actors.set(toolUseId, null);
49
+ else if (prior === undefined && this.actors.size < this.maxTasks)
50
+ this.actors.set(toolUseId, agentId);
51
+ const changed = [];
52
+ for (const [id, task] of this.tasks) {
53
+ if (task.origin.launchingToolUseId !== toolUseId)
54
+ continue;
55
+ const actor = this.actors.get(toolUseId);
56
+ const next = { ...task, origin: { ...task.origin,
57
+ agentId: this.conflicts.has(id) ? undefined : actor ?? undefined },
58
+ stale: actor === null || this.conflicts.has(id) || task.stale };
59
+ this.tasks.set(id, next);
60
+ changed.push(next);
61
+ }
62
+ return changed;
63
+ }
64
+ ensure(taskId) {
65
+ if (!identifier(taskId))
66
+ return undefined;
67
+ const existing = this.tasks.get(taskId);
68
+ if (existing)
69
+ return existing;
70
+ if (this.tasks.size >= this.maxTasks) {
71
+ this.degrade();
72
+ return undefined;
73
+ }
74
+ const task = { origin: { rootSessionId: this.rootSessionId, runtimeGeneration: this.runtimeGeneration,
75
+ taskId }, state: 'unknown', live: false, stale: false };
76
+ this.tasks.set(taskId, task);
77
+ return task;
78
+ }
79
+ apply(message) {
80
+ if (this.failed || message.type !== 'system' || message.session_id !== this.rootSessionId ||
81
+ !identifier(message.uuid) || this.seen.has(message.uuid))
82
+ return [];
83
+ if (this.seen.size >= this.maxTasks * 4)
84
+ return this.degrade();
85
+ if (message.subtype === 'background_tasks_changed' && message.tasks.length > this.maxTasks)
86
+ return this.degrade();
87
+ if (message.subtype === 'background_tasks_changed') {
88
+ const added = new Set();
89
+ for (const entry of message.tasks) {
90
+ if (identifier(entry.task_id) && !this.tasks.has(entry.task_id))
91
+ added.add(entry.task_id);
92
+ if (this.tasks.size + added.size > this.maxTasks)
93
+ return this.degrade();
94
+ }
95
+ }
96
+ const previous = new Map(this.tasks);
97
+ this.seen.add(message.uuid);
98
+ if (message.subtype === 'background_tasks_changed') {
99
+ const current = new Map(message.tasks.filter((entry) => identifier(entry.task_id))
100
+ .map((entry) => [entry.task_id, entry]));
101
+ for (const [id, task] of this.tasks) {
102
+ if (!terminal(task.state) && task.background) {
103
+ const next = { ...task, live: current.has(id),
104
+ stale: current.has(id) ? this.conflicts.has(id) : task.stale };
105
+ if (!sameTask(task, next))
106
+ this.tasks.set(id, next);
107
+ }
108
+ }
109
+ for (const [id, entry] of current) {
110
+ const task = this.ensure(id);
111
+ if (task && !terminal(task.state)) {
112
+ const next = { ...task, state: 'running', live: true,
113
+ stale: this.conflicts.has(id), taskType: entry.task_type, background: true };
114
+ if (!sameTask(task, next))
115
+ this.tasks.set(id, next);
116
+ }
117
+ }
118
+ }
119
+ else if (message.subtype === 'task_started' || message.subtype === 'task_progress') {
120
+ const task = this.ensure(message.task_id);
121
+ if (task && !terminal(task.state)) {
122
+ const toolId = identifier(message.tool_use_id) ? message.tool_use_id : task.origin.launchingToolUseId;
123
+ if (toolId && !this.launchTasks.has(toolId) && this.launchTasks.size < this.maxTasks)
124
+ this.launchTasks.set(toolId, message.task_id);
125
+ else if (toolId && this.launchTasks.get(toolId) !== message.task_id) {
126
+ this.conflicts.add(message.task_id);
127
+ const other = this.launchTasks.get(toolId);
128
+ if (other) {
129
+ this.conflicts.add(other);
130
+ const previous = this.tasks.get(other);
131
+ if (previous)
132
+ this.tasks.set(other, { ...previous, stale: true,
133
+ origin: { ...previous.origin, agentId: undefined } });
134
+ }
135
+ }
136
+ const actor = toolId && this.actors.get(toolId);
137
+ this.tasks.set(message.task_id, { ...task, state: 'running', stale: this.conflicts.has(message.task_id),
138
+ taskType: 'task_type' in message ? message.task_type : task.taskType,
139
+ origin: { ...task.origin, launchingToolUseId: toolId,
140
+ agentId: this.conflicts.has(message.task_id) ? undefined : actor ?? task.origin.agentId } });
141
+ }
142
+ }
143
+ else if (message.subtype === 'task_updated') {
144
+ const task = this.ensure(message.task_id);
145
+ if (task && !terminal(task.state)) {
146
+ const status = message.patch.status;
147
+ const state = status === 'killed' ? 'stopped' : status ?? task.state;
148
+ this.tasks.set(message.task_id, { ...task, state, live: terminal(state) ? false : task.live,
149
+ stale: this.conflicts.has(message.task_id), background: message.patch.is_backgrounded ?? task.background });
150
+ }
151
+ }
152
+ else if (message.subtype === 'task_notification') {
153
+ const task = this.ensure(message.task_id);
154
+ if (task && (!this.notified.has(message.task_id) ||
155
+ (task.state === 'stopped' && message.status === 'completed'))) {
156
+ this.notified.add(message.task_id);
157
+ this.tasks.set(message.task_id, { ...task, state: message.status,
158
+ live: false, stale: this.conflicts.has(message.task_id) });
159
+ }
160
+ }
161
+ return [...this.tasks].filter(([id, task]) => task !== previous.get(id)).map(([id]) => this.get(id));
162
+ }
163
+ }
@@ -0,0 +1 @@
1
+ export {};