maka-agent 0.2.0-dev.75.20261008 → 0.2.0-dev.77.20261009

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 (47) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  4. package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +115 -17
  5. package/node_modules/@maka/runtime/dist/agent-run.js +11 -0
  6. package/node_modules/@maka/runtime/dist/computer-use-observation-text.js +22 -0
  7. package/node_modules/@maka/runtime/dist/conversation-copy-agent-output.js +227 -0
  8. package/node_modules/@maka/runtime/dist/conversation-copy.js +176 -46
  9. package/node_modules/@maka/runtime/dist/filesystem-worker/resource-resolver.js +3 -1
  10. package/node_modules/@maka/runtime/dist/owned-child-process.js +209 -0
  11. package/node_modules/@maka/runtime/dist/owned-process-main.js +183 -0
  12. package/node_modules/@maka/runtime/dist/pipe-process-driver.js +19 -25
  13. package/node_modules/@maka/runtime/dist/runtime-kernel.js +124 -3
  14. package/node_modules/@maka/runtime/dist/session-manager.js +125 -12
  15. package/node_modules/@maka/runtime/dist/shell-exec.js +18 -20
  16. package/node_modules/@maka/runtime/dist/shell-run-manager.js +2 -1
  17. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +279 -8
  18. package/node_modules/@maka/runtime/dist/stream-graph-schedule-reconcile.js +209 -37
  19. package/node_modules/@maka/runtime/dist/tool-availability.js +37 -6
  20. package/node_modules/@maka/runtime/dist/tool-runtime.js +2 -1
  21. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +44 -13
  22. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -0
  23. package/node_modules/@maka/runtime-host/dist/client/wsl-environment.js +4 -0
  24. package/node_modules/@maka/runtime-host/dist/protocol/index.js +4 -1
  25. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +46 -9
  26. package/node_modules/@maka/runtime-host/dist/server/candidate.js +47 -40
  27. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +71 -24
  28. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +54 -13
  29. package/node_modules/@maka/runtime-host/dist/server/execution-service.js +5 -1
  30. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-runner.js +13 -6
  31. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +41 -5
  32. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +20 -3
  33. package/node_modules/@maka/runtime-host/dist/server/session-background-activity.js +71 -0
  34. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +8 -4
  35. package/node_modules/@maka/runtime-host/dist/server/session-interaction-activity.js +78 -0
  36. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +56 -0
  37. package/node_modules/@maka/runtime-host/dist/server/session-revision-graph-references.js +16 -2
  38. package/node_modules/@maka/runtime-host/dist/server/usage-pricing-coordinator.js +10 -0
  39. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +3 -1
  40. package/node_modules/@maka/storage/dist/context-offload-store.js +8 -3
  41. package/node_modules/@maka/storage/dist/model-call-ledger.js +59 -1
  42. package/node_modules/@maka/storage/dist/model-call-usage-sql.js +23 -0
  43. package/node_modules/@maka/storage/dist/session-bundle-policy.js +54 -19
  44. package/node_modules/@maka/storage/dist/sqlite-usage-schema.js +17 -2
  45. package/node_modules/@maka/storage/dist/usage-stores.js +3 -0
  46. package/package.json +2 -2
  47. /package/node_modules/@maka/runtime/dist/workers/{filesystem-worker.js → filesystem-worker.mjs} +0 -0
package/README.md CHANGED
@@ -65,7 +65,7 @@ https://incubator.apache.org/projects/maka.html
65
65
 
66
66
  ## Requirements
67
67
 
68
- - Node.js 22.19.0 or newer;
68
+ - Node.js 22.19.0 or newer on the 22 line, or 24.0.0 or newer (Node 23 is not supported);
69
69
  - a terminal with interactive input for the TUI;
70
70
  - a configured model connection for agent turns; first-run setup currently supports API-key
71
71
  providers.
package/README.zh-CN.md CHANGED
@@ -39,7 +39,7 @@ release commit 中的 [DISCLAIMER-WIP](https://github.com/apache/maka/blob/main/
39
39
 
40
40
  ## 环境要求
41
41
 
42
- - Node.js 22.19.0 或更高版本;
42
+ - Node.js 22.19.0 或更高的 22.x 版本,或 24.0.0 及以上版本(不支持 Node 23);
43
43
  - 使用 TUI 时需要支持交互输入的终端;
44
44
  - 执行 Agent Turn 时需要已经配置的模型连接;首次设置目前支持使用 API Key 的供应商。
45
45
 
@@ -83,6 +83,10 @@ export class AgentGraphSupervisorWakeCoordinator {
83
83
  #tasks = new Set();
84
84
  #tasksBySession = new Map();
85
85
  #pendingWakeIds = new Set();
86
+ #sessionAttention = new Map();
87
+ #parkedWakeIds = new Map();
88
+ #activityVersions = new Map();
89
+ #publishedActivity = new Map();
86
90
  #abortController = new AbortController();
87
91
  #sessionAbortControllers = new Map();
88
92
  #sessionWakeSuppressions = new Map();
@@ -95,18 +99,45 @@ export class AgentGraphSupervisorWakeCoordinator {
95
99
  throw new Error('Agent graph supervisor wake attempts must be a positive safe integer');
96
100
  }
97
101
  }
102
+ /** Includes queued checkpoint delivery before a supervisor Turn is admitted. */
103
+ readSessionActivity(rootSessionId) {
104
+ if (this.#closed || this.#sessionWakesSuppressed(rootSessionId))
105
+ return 'idle';
106
+ const attention = this.#sessionAttention.get(rootSessionId);
107
+ if (attention === 'waiting_for_user')
108
+ return attention;
109
+ if (this.#tasksBySession.has(rootSessionId))
110
+ return 'running';
111
+ return attention ?? 'idle';
112
+ }
113
+ #publishSessionActivity(rootSessionId) {
114
+ const activity = this.readSessionActivity(rootSessionId);
115
+ if ((this.#publishedActivity.get(rootSessionId) ?? 'idle') === activity)
116
+ return;
117
+ if (activity === 'idle')
118
+ this.#publishedActivity.delete(rootSessionId);
119
+ else
120
+ this.#publishedActivity.set(rootSessionId, activity);
121
+ try {
122
+ this.#input.onSessionActivityChanged?.(rootSessionId);
123
+ }
124
+ catch {
125
+ // A catalog observer never controls wake delivery.
126
+ }
127
+ }
98
128
  notify(rootSessionId, result) {
99
129
  if (this.#closed ||
100
130
  this.#sessionWakesSuppressed(rootSessionId) ||
101
131
  (!this.#input.shouldWake && (!result || !isAgentGraphSupervisorMilestone(result)))) {
102
132
  return undefined;
103
133
  }
104
- return this.#runTracked(rootSessionId, async (abortSignal) => {
134
+ return this.#runTracked(rootSessionId, async (abortSignal, setAttention) => {
105
135
  try {
106
- await this.#wake(rootSessionId, abortSignal, result);
136
+ await this.#wake(rootSessionId, abortSignal, setAttention, result);
107
137
  }
108
138
  catch (error) {
109
139
  if (!this.#closed && !isAbortError(error)) {
140
+ setAttention('blocked');
110
141
  await notifyError(this.#input.onError, rootSessionId, error);
111
142
  }
112
143
  }
@@ -122,12 +153,15 @@ export class AgentGraphSupervisorWakeCoordinator {
122
153
  notifyPermissionResponse(rootSessionId) {
123
154
  if (this.#closed || this.#sessionWakesSuppressed(rootSessionId))
124
155
  return undefined;
125
- return this.#runTracked(rootSessionId, async (abortSignal) => {
156
+ this.#sessionAttention.delete(rootSessionId);
157
+ this.#parkedWakeIds.delete(rootSessionId);
158
+ return this.#runTracked(rootSessionId, async (abortSignal, setAttention) => {
126
159
  try {
127
- await this.#settlePermissionResponse(rootSessionId, abortSignal);
160
+ await this.#settlePermissionResponse(rootSessionId, abortSignal, setAttention);
128
161
  }
129
162
  catch (error) {
130
163
  if (!this.#closed && !isAbortError(error)) {
164
+ setAttention('blocked');
131
165
  await notifyError(this.#input.onError, rootSessionId, error);
132
166
  }
133
167
  }
@@ -174,6 +208,11 @@ export class AgentGraphSupervisorWakeCoordinator {
174
208
  return this.#input.activityRegistry.whenIdle(rootSessionId) !== undefined;
175
209
  }
176
210
  async retireSessions(rootSessionIds) {
211
+ for (const rootSessionId of rootSessionIds) {
212
+ this.#sessionAttention.delete(rootSessionId);
213
+ this.#parkedWakeIds.delete(rootSessionId);
214
+ this.#publishSessionActivity(rootSessionId);
215
+ }
177
216
  return this.#input.wakeStore.supersedeAgentGraphSupervisorWakes({
178
217
  rootSessionIds,
179
218
  reason: 'session_retired',
@@ -184,19 +223,30 @@ export class AgentGraphSupervisorWakeCoordinator {
184
223
  return;
185
224
  this.#closed = true;
186
225
  this.#abortController.abort();
226
+ for (const sessionId of [...this.#publishedActivity.keys()])
227
+ this.#publishSessionActivity(sessionId);
187
228
  }
188
229
  async close() {
189
230
  this.beginDrain();
190
231
  await this.waitForIdle();
191
232
  this.#sessionAbortControllers.clear();
192
233
  this.#tasksBySession.clear();
234
+ this.#sessionAttention.clear();
235
+ this.#parkedWakeIds.clear();
236
+ this.#activityVersions.clear();
193
237
  }
194
- async #wake(rootSessionId, abortSignal, result) {
238
+ async #wake(rootSessionId, abortSignal, setAttention, result) {
195
239
  abortSignal.throwIfAborted();
196
240
  if (!(await this.#isSessionDeliverable(rootSessionId)))
197
241
  return;
198
242
  const snapshot = await this.#input.readSnapshot(rootSessionId);
199
243
  abortSignal.throwIfAborted();
244
+ const parkedWakeId = this.#parkedWakeIds.get(rootSessionId);
245
+ if (parkedWakeId &&
246
+ (snapshot.closed || parkedWakeId !== `${snapshot.graphId}:${snapshot.snapshotVersion}`)) {
247
+ this.#parkedWakeIds.delete(rootSessionId);
248
+ setAttention('idle');
249
+ }
200
250
  const wakeDecision = await this.#input.shouldWake?.(rootSessionId, result, snapshot);
201
251
  abortSignal.throwIfAborted();
202
252
  if (wakeDecision === false ||
@@ -217,12 +267,19 @@ export class AgentGraphSupervisorWakeCoordinator {
217
267
  snapshotVersion: snapshot.snapshotVersion,
218
268
  rootSessionId,
219
269
  });
220
- if (this.#closed ||
221
- claimed.wake.status === 'delivered' ||
222
- claimed.wake.status === 'exhausted')
270
+ if (this.#closed || claimed.wake.status === 'exhausted')
271
+ return;
272
+ if (claimed.wake.status === 'delivered') {
273
+ this.#parkedWakeIds.delete(rootSessionId);
274
+ setAttention('idle');
223
275
  return;
276
+ }
277
+ if (claimed.wake.status === 'waiting_permission') {
278
+ this.#parkedWakeIds.set(rootSessionId, wakeId);
279
+ setAttention('waiting_for_user');
280
+ }
224
281
  abortSignal.throwIfAborted();
225
- await this.#deliverWake(claimed.wake, snapshot, abortSignal, result);
282
+ await this.#deliverWake(claimed.wake, snapshot, abortSignal, setAttention, result);
226
283
  }
227
284
  finally {
228
285
  this.#pendingWakeIds.delete(wakeId);
@@ -232,12 +289,13 @@ export class AgentGraphSupervisorWakeCoordinator {
232
289
  if (this.#closed || this.#pendingWakeIds.has(wake.wakeId))
233
290
  return;
234
291
  this.#pendingWakeIds.add(wake.wakeId);
235
- void this.#runTracked(wake.rootSessionId, async (abortSignal) => {
292
+ void this.#runTracked(wake.rootSessionId, async (abortSignal, setAttention) => {
236
293
  try {
237
- await this.#resumeWake(wake, abortSignal);
294
+ await this.#resumeWake(wake, abortSignal, setAttention);
238
295
  }
239
296
  catch (error) {
240
297
  if (!this.#closed && !isAbortError(error)) {
298
+ setAttention('blocked');
241
299
  await notifyError(this.#input.onError, wake.rootSessionId, error);
242
300
  }
243
301
  }
@@ -247,19 +305,39 @@ export class AgentGraphSupervisorWakeCoordinator {
247
305
  });
248
306
  }
249
307
  #runTracked(rootSessionId, operation) {
308
+ // Duplicate checkpoint notifications share the in-flight generation. They
309
+ // cannot invalidate the outcome of the attempt already holding the wake.
310
+ const version = (this.#activityVersions.get(rootSessionId) ?? 0) +
311
+ (this.#tasksBySession.has(rootSessionId) ? 0 : 1);
312
+ this.#activityVersions.set(rootSessionId, version);
313
+ if (this.#sessionAttention.get(rootSessionId) !== 'waiting_for_user') {
314
+ this.#sessionAttention.delete(rootSessionId);
315
+ }
316
+ const setAttention = (activity) => {
317
+ if (this.#closed ||
318
+ this.#sessionWakesSuppressed(rootSessionId) ||
319
+ this.#activityVersions.get(rootSessionId) !== version)
320
+ return;
321
+ if (activity === 'idle')
322
+ this.#sessionAttention.delete(rootSessionId);
323
+ else
324
+ this.#sessionAttention.set(rootSessionId, activity);
325
+ this.#publishSessionActivity(rootSessionId);
326
+ };
250
327
  const residency = this.#input.acquireResidency?.(rootSessionId);
251
328
  const abortSignal = this.#sessionAbortSignal(rootSessionId);
252
329
  const task = Promise.resolve()
253
- .then(() => operation(abortSignal))
330
+ .then(() => operation(abortSignal, setAttention))
254
331
  .finally(() => residency?.release());
255
332
  this.#tasks.add(task);
256
333
  const sessionTasks = this.#tasksBySession.get(rootSessionId) ?? new Set();
257
334
  sessionTasks.add(task);
258
335
  this.#tasksBySession.set(rootSessionId, sessionTasks);
336
+ this.#publishSessionActivity(rootSessionId);
259
337
  void task.then(() => this.#forgetTask(rootSessionId, task), () => this.#forgetTask(rootSessionId, task));
260
338
  return task;
261
339
  }
262
- async #resumeWake(wake, abortSignal) {
340
+ async #resumeWake(wake, abortSignal, setAttention) {
263
341
  abortSignal.throwIfAborted();
264
342
  if (!(await this.#isSessionDeliverable(wake.rootSessionId))) {
265
343
  await this.#supersedeSession(wake.rootSessionId, 'session_unavailable');
@@ -278,9 +356,9 @@ export class AgentGraphSupervisorWakeCoordinator {
278
356
  return;
279
357
  }
280
358
  abortSignal.throwIfAborted();
281
- await this.#deliverWake(wake, snapshot, abortSignal);
359
+ await this.#deliverWake(wake, snapshot, abortSignal, setAttention);
282
360
  }
283
- async #deliverWake(wake, snapshot, abortSignal, result) {
361
+ async #deliverWake(wake, snapshot, abortSignal, setAttention, result) {
284
362
  if (wake.status === 'exhausted')
285
363
  return;
286
364
  if (wake.attemptCount >= this.#maxDeliveryAttempts) {
@@ -323,6 +401,8 @@ export class AgentGraphSupervisorWakeCoordinator {
323
401
  }
324
402
  return;
325
403
  }
404
+ this.#parkedWakeIds.delete(wake.rootSessionId);
405
+ setAttention('idle');
326
406
  if (this.#sessionWakesSuppressed(wake.rootSessionId))
327
407
  return;
328
408
  if (this.#closed) {
@@ -351,6 +431,8 @@ export class AgentGraphSupervisorWakeCoordinator {
351
431
  attemptId,
352
432
  status: 'delivered',
353
433
  });
434
+ this.#parkedWakeIds.delete(wake.rootSessionId);
435
+ setAttention('idle');
354
436
  return;
355
437
  }
356
438
  if (outcome.kind === 'superseded') {
@@ -370,6 +452,8 @@ export class AgentGraphSupervisorWakeCoordinator {
370
452
  attemptId,
371
453
  status: 'waiting_permission',
372
454
  });
455
+ this.#parkedWakeIds.set(wake.rootSessionId, wake.wakeId);
456
+ setAttention('waiting_for_user');
373
457
  return;
374
458
  }
375
459
  lastFailure = wakeOutcomeFailure(outcome);
@@ -501,6 +585,9 @@ export class AgentGraphSupervisorWakeCoordinator {
501
585
  `${snapshot.graphId}:${snapshot.snapshotVersion}` === wake.wakeId);
502
586
  }
503
587
  #supersedeSession(rootSessionId, reason) {
588
+ this.#sessionAttention.delete(rootSessionId);
589
+ this.#parkedWakeIds.delete(rootSessionId);
590
+ this.#publishSessionActivity(rootSessionId);
504
591
  return this.#input.wakeStore.supersedeAgentGraphSupervisorWakes({
505
592
  rootSessionIds: [rootSessionId],
506
593
  reason,
@@ -527,7 +614,7 @@ export class AgentGraphSupervisorWakeCoordinator {
527
614
  : `host_restart:${runStatus}`);
528
615
  return 1;
529
616
  }
530
- async #settlePermissionResponse(rootSessionId, abortSignal) {
617
+ async #settlePermissionResponse(rootSessionId, abortSignal, setAttention) {
531
618
  const activity = await this.#input.activityRegistry.acquire(rootSessionId, abortSignal);
532
619
  const retryable = [];
533
620
  try {
@@ -548,6 +635,8 @@ export class AgentGraphSupervisorWakeCoordinator {
548
635
  attemptId,
549
636
  status: 'delivered',
550
637
  });
638
+ this.#parkedWakeIds.delete(rootSessionId);
639
+ setAttention('idle');
551
640
  continue;
552
641
  }
553
642
  retryable.push(await this.#markRetryable(wake.graphId, wake.wakeId, attemptId, `permission_waiter_lost:${runStatus}`));
@@ -562,6 +651,10 @@ export class AgentGraphSupervisorWakeCoordinator {
562
651
  #beginSessionWakeSuppression(rootSessionId) {
563
652
  const count = this.#sessionWakeSuppressions.get(rootSessionId) ?? 0;
564
653
  this.#sessionWakeSuppressions.set(rootSessionId, count + 1);
654
+ this.#activityVersions.set(rootSessionId, (this.#activityVersions.get(rootSessionId) ?? 0) + 1);
655
+ this.#sessionAttention.delete(rootSessionId);
656
+ this.#parkedWakeIds.delete(rootSessionId);
657
+ this.#publishSessionActivity(rootSessionId);
565
658
  if (count === 0) {
566
659
  const controller = this.#sessionAbortControllers.get(rootSessionId) ?? new AbortController();
567
660
  this.#sessionAbortControllers.set(rootSessionId, controller);
@@ -577,6 +670,7 @@ export class AgentGraphSupervisorWakeCoordinator {
577
670
  return;
578
671
  }
579
672
  this.#sessionWakeSuppressions.delete(rootSessionId);
673
+ this.#publishSessionActivity(rootSessionId);
580
674
  if (!this.#tasksBySession.has(rootSessionId)) {
581
675
  this.#sessionAbortControllers.delete(rootSessionId);
582
676
  }
@@ -601,11 +695,15 @@ export class AgentGraphSupervisorWakeCoordinator {
601
695
  this.#tasks.delete(task);
602
696
  const sessionTasks = this.#tasksBySession.get(rootSessionId);
603
697
  sessionTasks?.delete(task);
604
- if (sessionTasks?.size === 0)
698
+ if (sessionTasks?.size === 0) {
605
699
  this.#tasksBySession.delete(rootSessionId);
700
+ if (!this.#sessionAttention.has(rootSessionId))
701
+ this.#activityVersions.delete(rootSessionId);
702
+ }
606
703
  if (!this.#sessionWakesSuppressed(rootSessionId) && !this.#tasksBySession.has(rootSessionId)) {
607
704
  this.#sessionAbortControllers.delete(rootSessionId);
608
705
  }
706
+ this.#publishSessionActivity(rootSessionId);
609
707
  }
610
708
  }
611
709
  export function isAgentGraphSupervisorMilestone(result) {
@@ -328,6 +328,17 @@ export class AgentRun {
328
328
  const runStore = this.input.runStore;
329
329
  if (!this.input.runtimeEventStore || !runStore)
330
330
  return;
331
+ // A stream may have attempted finalization after an earlier stop failed.
332
+ // Join that write, but let this explicit retry reuse its reserved fact if
333
+ // the write failed; a rejected promise is not durable terminal evidence.
334
+ const previousWrite = this.terminalClaim.write;
335
+ try {
336
+ await previousWrite;
337
+ }
338
+ catch {
339
+ if (this.terminalClaim.write === previousWrite)
340
+ this.terminalClaim.write = undefined;
341
+ }
331
342
  await this.flushRuntimePartialBuffer(true);
332
343
  // The claim only fences writers inside this Run. Another owner — a Host
333
344
  // recovery, a resumed continuation — may have sealed the ledger already,
@@ -366,8 +366,30 @@ function header(observation, elementCount, query, shownCount) {
366
366
  if (observation.truncated === true) {
367
367
  parts.push('truncated=true(the tree was cut short; an element you expect may exist but not be listed)');
368
368
  }
369
+ const selectedText = selectedTextCaption(observation);
370
+ if (selectedText)
371
+ parts.push(selectedText);
369
372
  return parts.join(' ');
370
373
  }
374
+ /**
375
+ * Window selection is application text. A password field that currently holds
376
+ * keys must not leak that text through this caption; otherwise truncate like
377
+ * `value` and keep the executor's own truncated flag.
378
+ */
379
+ function selectedTextCaption(observation) {
380
+ const selected = observation.selectedText;
381
+ if (!selected)
382
+ return undefined;
383
+ const focused = observation.elements.find((element) => element.focused === true);
384
+ const role = `${focused?.role ?? ''} ${focused?.subrole ?? ''}`;
385
+ if (/secure/i.test(role)) {
386
+ return 'selected_text=withheld(secure_field)';
387
+ }
388
+ const shown = truncate(selected.text);
389
+ return selected.truncated === true
390
+ ? `selected_text=${quote(shown)}(truncated=true)`
391
+ : `selected_text=${quote(shown)}`;
392
+ }
371
393
  /**
372
394
  * A subrole earns its place when it is telling the model something the rest of
373
395
  * the line does not.
@@ -0,0 +1,227 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { isRecord, isStringArray } from '@maka/core/record-schema';
20
+ import { decodeAgentRunEvent } from '@maka/core/agent-run';
21
+ import { decodeRuntimeEvent, decodeRuntimeInvocationOpened, } from '@maka/core/runtime-event';
22
+ import { runtimeInvocationOutcome } from '@maka/core/runtime-invocation';
23
+ import { stableJsonStringify } from '@maka/core/tool-args-identity';
24
+ import { runtimeInvocationFailureClass } from './runtime-event-read-model.js';
25
+ export function isConversationCopyAgentOutputSnapshot(value) {
26
+ return (isRecord(value) &&
27
+ value.kind === 'maka.agent_output_snapshot' &&
28
+ isResultPayload(value) &&
29
+ Object.keys(value).every((key) => [
30
+ 'kind',
31
+ 'schemaVersion',
32
+ 'status',
33
+ 'text',
34
+ 'textTruncated',
35
+ 'artifactIds',
36
+ 'omittedArtifactIds',
37
+ 'failureClass',
38
+ ].includes(key)));
39
+ }
40
+ function isResultPayload(value) {
41
+ return (value.schemaVersion === 1 &&
42
+ resultStatus(value.status) &&
43
+ isStringArray(value.artifactIds) &&
44
+ value.artifactIds.every(nonempty) &&
45
+ typeof value.textTruncated === 'boolean' &&
46
+ typeof value.omittedArtifactIds === 'number' &&
47
+ Number.isSafeInteger(value.omittedArtifactIds) &&
48
+ value.omittedArtifactIds >= 0 &&
49
+ (value.text === undefined || typeof value.text === 'string') &&
50
+ (value.failureClass === undefined || typeof value.failureClass === 'string'));
51
+ }
52
+ /**
53
+ * Recognize the defined result envelope, never arbitrary nested JSON ids.
54
+ * These are reference claims, not authority: the Host still checks them against
55
+ * the retained child's metadata, invocation ledger, and Artifact ownership.
56
+ */
57
+ export function conversationCopyAgentOutput(value) {
58
+ if (!isRecord(value))
59
+ return undefined;
60
+ const { execution, invocation, budget } = value;
61
+ if (!isRecord(execution) ||
62
+ execution.kind !== 'child_session' ||
63
+ !nonempty(execution.sessionId) ||
64
+ !isRecord(invocation) ||
65
+ invocation.sessionId !== execution.sessionId ||
66
+ !nonempty(invocation.runId) ||
67
+ !nonempty(invocation.turnId) ||
68
+ !nonempty(invocation.invocationId) ||
69
+ (execution.currentRunId !== undefined && execution.currentRunId !== invocation.runId) ||
70
+ !isRecord(budget) ||
71
+ typeof budget.view !== 'string' ||
72
+ !['result', 'events', 'runtime_events', 'all'].includes(budget.view) ||
73
+ !Array.isArray(value.events) ||
74
+ !Array.isArray(value.runtimeEvents) ||
75
+ !Array.isArray(value.diagnostics) ||
76
+ !Array.isArray(value.artifacts))
77
+ return undefined;
78
+ try {
79
+ const opening = decodeRuntimeInvocationOpened(invocation.opening);
80
+ const terminal = invocation.terminalEvent === undefined
81
+ ? undefined
82
+ : decodeRuntimeEvent(invocation.terminalEvent);
83
+ if (terminal &&
84
+ (terminal.sessionId !== execution.sessionId ||
85
+ terminal.runId !== invocation.runId ||
86
+ terminal.turnId !== invocation.turnId ||
87
+ terminal.invocationId !== invocation.invocationId ||
88
+ terminal.partial))
89
+ return undefined;
90
+ let result = value.result;
91
+ if (budget.view === 'result') {
92
+ if (value.events.length ||
93
+ value.runtimeEvents.length ||
94
+ value.diagnostics.length ||
95
+ value.artifacts.length)
96
+ return undefined;
97
+ }
98
+ else {
99
+ // Diagnostic views have no committed-result field. Their invocation
100
+ // still identifies the observation, and their Artifact list can contain
101
+ // outputs omitted by a separately bounded view=result read.
102
+ if (result !== undefined ||
103
+ (budget.view === 'events' && value.runtimeEvents.length > 0) ||
104
+ (budget.view === 'runtime_events' && value.events.length > 0))
105
+ return undefined;
106
+ const sameRun = (event) => event.sessionId === execution.sessionId &&
107
+ event.runId === invocation.runId &&
108
+ event.turnId === invocation.turnId;
109
+ const events = value.events.map(decodeAgentRunEvent);
110
+ const runtimeEvents = value.runtimeEvents.map(decodeRuntimeEvent);
111
+ if (!events.every(sameRun) ||
112
+ !runtimeEvents.every((event) => sameRun(event) && event.invocationId === invocation.invocationId) ||
113
+ !value.diagnostics.every((item) => isRecord(item) &&
114
+ item.runId === invocation.runId &&
115
+ item.turnId === invocation.turnId &&
116
+ nonempty(item.code) &&
117
+ typeof item.message === 'string') ||
118
+ !value.artifacts.every((item) => isRecord(item) &&
119
+ nonempty(item.id) &&
120
+ item.sessionId === execution.sessionId &&
121
+ item.turnId === invocation.turnId))
122
+ return undefined;
123
+ const text = [
124
+ ...runtimeEvents.flatMap(diagnosticRuntimeEventText),
125
+ ...events.flatMap((event) => (event.message ? [event.message] : [])),
126
+ ...value.diagnostics.map((item) => item.message),
127
+ ].join('\n');
128
+ const failureClass = runtimeInvocationFailureClass({ terminalEvent: terminal });
129
+ result = {
130
+ schemaVersion: 1,
131
+ status: runtimeInvocationOutcome({ terminalEvent: terminal }) ?? 'running',
132
+ ...(text ? { text } : {}),
133
+ // The bounded diagnostic view cannot establish a complete final text
134
+ // or count omitted Artifacts. Retain its readable excerpt and known ids.
135
+ textTruncated: true,
136
+ artifactIds: value.artifacts.map((item) => item.id),
137
+ omittedArtifactIds: 0,
138
+ ...(failureClass ? { failureClass } : {}),
139
+ };
140
+ }
141
+ if (!isRecord(result) || !isResultPayload(result))
142
+ return undefined;
143
+ if (terminal &&
144
+ (runtimeInvocationOutcome({ terminalEvent: terminal }) !== result.status ||
145
+ (result.terminalRuntimeEventId !== undefined &&
146
+ result.terminalRuntimeEventId !== terminal.id)))
147
+ return undefined;
148
+ if (!terminal && result.status !== 'running' && result.status !== 'waiting_for_user')
149
+ return undefined;
150
+ let graph;
151
+ if (result.graph !== undefined) {
152
+ if (!isRecord(result.graph) ||
153
+ !nonempty(result.graph.graphId) ||
154
+ !nonempty(result.graph.workId) ||
155
+ !nonempty(result.graph.operatorId))
156
+ return undefined;
157
+ graph = {
158
+ graphId: result.graph.graphId,
159
+ workId: result.graph.workId,
160
+ operatorId: result.graph.operatorId,
161
+ };
162
+ }
163
+ return {
164
+ reference: {
165
+ childSessionId: execution.sessionId,
166
+ runId: invocation.runId,
167
+ turnId: invocation.turnId,
168
+ status: result.status,
169
+ artifactIds: result.artifactIds,
170
+ ...(result.failureClass ? { failureClass: result.failureClass } : {}),
171
+ ...(opening.lineage?.resumedFromRunId
172
+ ? { resumedFromRunId: opening.lineage.resumedFromRunId }
173
+ : {}),
174
+ ...(terminal ? { terminalEventId: terminal.id } : {}),
175
+ ...(graph ? { graph } : {}),
176
+ },
177
+ // An independent Side Conversation keeps a result snapshot, without the
178
+ // original execution, invocation, Graph, or event identities.
179
+ snapshot: {
180
+ kind: 'maka.agent_output_snapshot',
181
+ schemaVersion: 1,
182
+ status: result.status,
183
+ ...(typeof result.text === 'string' ? { text: result.text } : {}),
184
+ textTruncated: result.textTruncated,
185
+ artifactIds: result.artifactIds,
186
+ omittedArtifactIds: result.omittedArtifactIds,
187
+ ...(result.failureClass ? { failureClass: result.failureClass } : {}),
188
+ },
189
+ };
190
+ }
191
+ catch {
192
+ return undefined;
193
+ }
194
+ }
195
+ /** Keep tool evidence as static text without copying event or tool-call identities. */
196
+ function diagnosticRuntimeEventText(event) {
197
+ if (event.partial)
198
+ return [];
199
+ const content = event.content;
200
+ switch (content?.kind) {
201
+ case 'text':
202
+ return [content.text];
203
+ case 'function_call':
204
+ return [stableJsonStringify({ kind: content.kind, name: content.name, args: content.args })];
205
+ case 'function_response':
206
+ return [
207
+ stableJsonStringify({
208
+ kind: content.kind,
209
+ name: content.name,
210
+ result: content.result,
211
+ ...(content.isError !== undefined ? { isError: content.isError } : {}),
212
+ }),
213
+ ];
214
+ default:
215
+ return [];
216
+ }
217
+ }
218
+ function nonempty(value) {
219
+ return typeof value === 'string' && value.length > 0;
220
+ }
221
+ function resultStatus(value) {
222
+ return (value === 'completed' ||
223
+ value === 'failed' ||
224
+ value === 'cancelled' ||
225
+ value === 'running' ||
226
+ value === 'waiting_for_user');
227
+ }