maka-agent 0.2.0-dev.30.20260912 → 0.2.0-dev.31.20260913

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 (194) hide show
  1. package/THIRD_PARTY_NOTICES.txt +392 -0
  2. package/dist/acp/maka-acp-agent.js +8 -2
  3. package/dist/acp/prompt-content.js +225 -0
  4. package/dist/acp/session-event-mapper.js +103 -0
  5. package/dist/acp/session-registry.js +654 -17
  6. package/dist/acp/stdio-server.js +10 -1
  7. package/dist/cli-core.js +1 -1
  8. package/dist/pi-transcript-tools.js +3 -2
  9. package/dist/pi-transcript.js +12 -2
  10. package/dist/runtime-host-session-channel.js +63 -9
  11. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  12. package/node_modules/@maka/core/dist/artifacts.js +6 -0
  13. package/node_modules/@maka/core/dist/attachments.js +7 -2
  14. package/node_modules/@maka/core/dist/durable-tool-result-projection.js +3 -1
  15. package/node_modules/@maka/core/dist/events.js +21 -0
  16. package/node_modules/@maka/core/dist/goal.js +44 -2
  17. package/node_modules/@maka/core/dist/permission.js +0 -1
  18. package/node_modules/@maka/core/dist/runtime-boundary.js +73 -29
  19. package/node_modules/@maka/core/dist/runtime-event.js +6 -3
  20. package/node_modules/@maka/core/dist/runtime-handoff.js +10 -2
  21. package/node_modules/@maka/core/dist/runtime-logical-execution.js +39 -14
  22. package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +3 -1
  23. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  24. package/node_modules/@maka/core/dist/unified-diff.js +33 -26
  25. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +49 -178
  26. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +7 -2
  27. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +15 -53
  28. package/node_modules/@maka/runtime/dist/bash-model-output.js +7 -1
  29. package/node_modules/@maka/runtime/dist/bash-tail-buffer.js +13 -52
  30. package/node_modules/@maka/runtime/dist/builtin-tools.js +59 -111
  31. package/node_modules/@maka/runtime/dist/child-fd-input.js +3 -2
  32. package/node_modules/@maka/runtime/dist/context-budget-policy.js +1 -11
  33. package/node_modules/@maka/runtime/dist/context-budget.js +12 -1
  34. package/node_modules/@maka/runtime/dist/conversation-copy.js +104 -9
  35. package/node_modules/@maka/runtime/dist/filesystem-executor.js +10 -6
  36. package/node_modules/@maka/runtime/dist/filesystem-worker/client.js +77 -19
  37. package/node_modules/@maka/runtime/dist/filesystem-worker/launch-spec.js +66 -55
  38. package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +34 -97
  39. package/node_modules/@maka/runtime/dist/filesystem-worker/protocol.js +25 -10
  40. package/node_modules/@maka/runtime/dist/filesystem-worker/worker-entry.js +2 -0
  41. package/node_modules/@maka/runtime/dist/glob-search.js +74 -0
  42. package/node_modules/@maka/runtime/dist/goal-continuation.js +78 -8
  43. package/node_modules/@maka/runtime/dist/goal-state.js +7 -0
  44. package/node_modules/@maka/runtime/dist/grep-search.js +155 -0
  45. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +84 -94
  46. package/node_modules/@maka/runtime/dist/model-history.js +25 -3
  47. package/node_modules/@maka/runtime/dist/pipe-tail-collector.js +6 -9
  48. package/node_modules/@maka/runtime/dist/read-page.js +231 -0
  49. package/node_modules/@maka/runtime/dist/request-projection.js +2 -15
  50. package/node_modules/@maka/runtime/dist/ripgrep-executable.js +62 -0
  51. package/node_modules/@maka/runtime/dist/ripgrep-guidance.js +81 -0
  52. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +187 -20
  53. package/node_modules/@maka/runtime/dist/runtime-read-model.js +2 -1
  54. package/node_modules/@maka/runtime/dist/sandbox/linux-profile-path.js +7 -1
  55. package/node_modules/@maka/runtime/dist/sandbox/linux-sandbox.js +20 -5
  56. package/node_modules/@maka/runtime/dist/session-manager.js +6 -6
  57. package/node_modules/@maka/runtime/dist/session-recap.js +9 -5
  58. package/node_modules/@maka/runtime/dist/shell-exec.js +7 -25
  59. package/node_modules/@maka/runtime/dist/shell-run-manager.js +1 -1
  60. package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +1 -1
  61. package/node_modules/@maka/runtime/dist/shell-tools.js +6 -12
  62. package/node_modules/@maka/runtime/dist/tool-availability.js +0 -1
  63. package/node_modules/@maka/runtime/dist/tool-output.js +1 -1
  64. package/node_modules/@maka/runtime/dist/tool-result-archive-capability.js +24 -36
  65. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +32 -527
  66. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +17 -119
  67. package/node_modules/@maka/runtime/dist/tool-result-archive.js +7 -25
  68. package/node_modules/@maka/runtime/dist/tool-runtime.js +7 -5
  69. package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +6933 -3681
  70. package/node_modules/@maka/runtime/dist/workspace-executor.js +20 -24
  71. package/node_modules/@maka/runtime/package.json +2 -0
  72. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +185 -191
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +3 -0
  74. package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
  75. package/node_modules/@maka/runtime-host/dist/protocol/message.js +2 -2
  76. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  77. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +2 -2
  78. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +10 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +38 -0
  80. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +2 -8
  81. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +2 -0
  82. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +81 -7
  83. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +13 -13
  84. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +65 -0
  85. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +1 -0
  86. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +5 -2
  87. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +88 -72
  88. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +21 -4
  89. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +145 -1
  90. package/node_modules/@maka/storage/dist/agent-run-store.js +5 -2
  91. package/node_modules/@maka/storage/dist/execution-stores.js +2 -1
  92. package/node_modules/@maka/storage/dist/runtime-transcript-query.js +27 -14
  93. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +69 -3
  94. package/node_modules/balanced-match/LICENSE.md +23 -0
  95. package/node_modules/balanced-match/README.md +57 -0
  96. package/node_modules/balanced-match/dist/commonjs/index.js +59 -0
  97. package/node_modules/balanced-match/dist/commonjs/package.json +3 -0
  98. package/node_modules/balanced-match/dist/esm/index.js +54 -0
  99. package/node_modules/balanced-match/dist/esm/package.json +3 -0
  100. package/node_modules/balanced-match/package.json +68 -0
  101. package/node_modules/brace-expansion/LICENSE +23 -0
  102. package/node_modules/brace-expansion/README.md +105 -0
  103. package/node_modules/brace-expansion/dist/commonjs/index.js +289 -0
  104. package/node_modules/brace-expansion/dist/commonjs/package.json +3 -0
  105. package/node_modules/brace-expansion/dist/esm/index.js +285 -0
  106. package/node_modules/brace-expansion/dist/esm/package.json +3 -0
  107. package/node_modules/brace-expansion/package.json +64 -0
  108. package/node_modules/glob/LICENSE.md +63 -0
  109. package/node_modules/glob/README.md +1203 -0
  110. package/node_modules/glob/dist/commonjs/glob.js +248 -0
  111. package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
  112. package/node_modules/glob/dist/commonjs/ignore.js +119 -0
  113. package/node_modules/glob/dist/commonjs/index.js +68 -0
  114. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  115. package/node_modules/glob/dist/commonjs/package.json +3 -0
  116. package/node_modules/glob/dist/commonjs/pattern.js +223 -0
  117. package/node_modules/glob/dist/commonjs/processor.js +301 -0
  118. package/node_modules/glob/dist/commonjs/walker.js +387 -0
  119. package/node_modules/glob/dist/esm/glob.js +244 -0
  120. package/node_modules/glob/dist/esm/has-magic.js +23 -0
  121. package/node_modules/glob/dist/esm/ignore.js +115 -0
  122. package/node_modules/glob/dist/esm/index.js +55 -0
  123. package/node_modules/glob/dist/esm/index.min.js +4 -0
  124. package/node_modules/glob/dist/esm/package.json +3 -0
  125. package/node_modules/glob/dist/esm/pattern.js +219 -0
  126. package/node_modules/glob/dist/esm/processor.js +294 -0
  127. package/node_modules/glob/dist/esm/walker.js +381 -0
  128. package/node_modules/glob/package.json +98 -0
  129. package/node_modules/minimatch/LICENSE.md +55 -0
  130. package/node_modules/minimatch/README.md +528 -0
  131. package/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
  132. package/node_modules/minimatch/dist/commonjs/ast.js +845 -0
  133. package/node_modules/minimatch/dist/commonjs/brace-expressions.js +150 -0
  134. package/node_modules/minimatch/dist/commonjs/escape.js +30 -0
  135. package/node_modules/minimatch/dist/commonjs/index.js +1127 -0
  136. package/node_modules/minimatch/dist/commonjs/package.json +3 -0
  137. package/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
  138. package/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
  139. package/node_modules/minimatch/dist/esm/ast.js +841 -0
  140. package/node_modules/minimatch/dist/esm/brace-expressions.js +146 -0
  141. package/node_modules/minimatch/dist/esm/escape.js +26 -0
  142. package/node_modules/minimatch/dist/esm/index.js +1114 -0
  143. package/node_modules/minimatch/dist/esm/package.json +3 -0
  144. package/node_modules/minimatch/dist/esm/unescape.js +34 -0
  145. package/node_modules/minimatch/package.json +73 -0
  146. package/node_modules/minipass/LICENSE.md +55 -0
  147. package/node_modules/minipass/README.md +825 -0
  148. package/node_modules/minipass/dist/commonjs/index.js +1038 -0
  149. package/node_modules/minipass/dist/commonjs/package.json +3 -0
  150. package/node_modules/minipass/dist/esm/index.js +1020 -0
  151. package/node_modules/minipass/dist/esm/package.json +3 -0
  152. package/node_modules/minipass/package.json +77 -0
  153. package/node_modules/path-scurry/LICENSE.md +55 -0
  154. package/node_modules/path-scurry/README.md +636 -0
  155. package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
  156. package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
  157. package/node_modules/path-scurry/dist/esm/index.js +1983 -0
  158. package/node_modules/path-scurry/dist/esm/package.json +3 -0
  159. package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
  160. package/node_modules/path-scurry/node_modules/lru-cache/README.md +469 -0
  161. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
  162. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
  163. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
  164. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
  165. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
  166. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
  167. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1726 -0
  168. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
  169. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
  170. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
  171. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
  172. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
  173. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
  174. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
  175. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
  176. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
  177. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
  178. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
  179. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
  180. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
  181. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1722 -0
  182. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  183. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
  184. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
  185. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
  186. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
  187. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
  188. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
  189. package/node_modules/path-scurry/node_modules/lru-cache/package.json +154 -0
  190. package/node_modules/path-scurry/package.json +72 -0
  191. package/package.json +1 -1
  192. package/node_modules/@maka/runtime/dist/active-tool-result-prune.js +0 -248
  193. package/node_modules/@maka/runtime/dist/active-tool-result-working-set.js +0 -218
  194. package/node_modules/@maka/runtime/dist/archive-read-tool.js +0 -115
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { Readable, Writable } from 'node:stream';
20
20
  import { ndJsonStream } from '@agentclientprotocol/sdk';
21
+ import { isRuntimeHostReconnectingConnection, } from '@maka/runtime-host/client';
21
22
  import { createMakaAcpAgent } from './maka-acp-agent.js';
22
23
  import { AcpSessionRegistry } from './session-registry.js';
23
24
  import { connectRuntimeHostCliConnection } from '../runtime-host-cli-context.js';
@@ -29,8 +30,16 @@ export async function runMakaAcpStdioServer(input, dependencies = {}) {
29
30
  clientDataRoot: input.clientDataRoot,
30
31
  signal,
31
32
  });
33
+ const connection = context.connection;
34
+ if (!isRuntimeHostReconnectingConnection(connection)) {
35
+ await context.close().catch(() => undefined);
36
+ throw new Error('ACP requires a reconnecting Runtime Host connection');
37
+ }
32
38
  return {
33
- request: context.connection.request.bind(context.connection),
39
+ reconnecting: true,
40
+ request: connection.request.bind(connection),
41
+ openSessionSubscription: connection.openSessionSubscription.bind(connection),
42
+ openSessionSubscriptionOnce: connection.openSessionSubscriptionOnce.bind(connection),
34
43
  close: () => context.close(),
35
44
  };
36
45
  },
package/dist/cli-core.js CHANGED
@@ -101,7 +101,7 @@ function helpText(cliCommand) {
101
101
  'Commands:',
102
102
  ...[
103
103
  ['', 'Start the TUI'],
104
- ['--acp', 'Serve ACP v1 over stdio (initialize, session/new, session/list)'],
104
+ ['--acp', 'Serve ACP v1 over stdio (sessions, prompts, streaming, cancellation)'],
105
105
  ['run ...', 'Run one non-interactive model turn'],
106
106
  ['-p ...', `Alias for ${cliCommand} run`],
107
107
  ['activate ...', 'Run one Cloud Session activation and emit JSONL'],
@@ -378,11 +378,12 @@ function readInputRef(entry) {
378
378
  }
379
379
  /** A Read using the filesystem branch. */
380
380
  function isFilesystemReadPath(entry) {
381
- return readInputPath(entry) !== undefined;
381
+ const path = readInputPath(entry);
382
+ return path !== undefined && !path.startsWith('maka:');
382
383
  }
383
384
  /** A Read using the runtime-resource branch (background-task output, etc.). */
384
385
  function isRuntimeResourceRead(entry) {
385
- return readInputRef(entry)?.startsWith('maka://runtime/') ?? false;
386
+ return (readInputPath(entry) ?? readInputRef(entry))?.startsWith('maka:') ?? false;
386
387
  }
387
388
  /**
388
389
  * True only for the result shapes a filesystem Read uses to carry actual file
@@ -17,6 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { Markdown, visibleWidth } from '@earendil-works/pi-tui';
20
+ import { resolveReadInput } from '@maka/runtime/read-page';
20
21
  import { deriveTurnRecords, isRuntimeSystemNoteKind, STEP_LIMIT_NOTICE_TEXT, } from '@maka/core/session';
21
22
  import { providerRetryDisplaySeconds } from '@maka/core/provider-retry-countdown';
22
23
  import { defineUiMessageCatalog, resolveUiMessageCatalog, } from '@maka/core/ui-locale';
@@ -1578,8 +1579,17 @@ function findShellRunParent(state, ref, childToolUseId) {
1578
1579
  }
1579
1580
  /** The runtime-resource ref a tool call is aimed at, when the args carry one. */
1580
1581
  function readArgsRef(args) {
1581
- const ref = args !== null && typeof args === 'object' ? args.ref : undefined;
1582
- return typeof ref === 'string' && ref.length > 0 ? ref : undefined;
1582
+ const ref = args !== null && typeof args === 'object'
1583
+ ? (args.path ?? args.ref)
1584
+ : undefined;
1585
+ if (typeof ref !== 'string' || !ref)
1586
+ return undefined;
1587
+ try {
1588
+ return resolveReadInput({ path: ref }).path;
1589
+ }
1590
+ catch {
1591
+ return undefined;
1592
+ }
1583
1593
  }
1584
1594
  /**
1585
1595
  * A card whose run resource is still `running`. The transition is keyed on the
@@ -59,6 +59,8 @@ export class RuntimeHostSessionChannel {
59
59
  #activated = false;
60
60
  #startedTurnBarrier;
61
61
  #closing = false;
62
+ #closeController = new AbortController();
63
+ #closeTask;
62
64
  #failure;
63
65
  #recoveryTask;
64
66
  #recoveryAttemptsWithoutLiveFrame = 0;
@@ -93,18 +95,27 @@ export class RuntimeHostSessionChannel {
93
95
  });
94
96
  }
95
97
  static async open(options) {
96
- const subscription = await options.connection.openSessionSubscription({
98
+ const openInitial = options.openInitialSessionSubscription ??
99
+ options.connection.openSessionSubscription.bind(options.connection);
100
+ const subscription = await runChannelOperation(() => openInitial({
97
101
  sessionId: options.sessionId,
98
102
  transcript: {
99
103
  kind: 'tail',
100
104
  maxBytes: SESSION_TRANSCRIPT_BOOTSTRAP_MAX_BYTES,
101
105
  },
102
- });
106
+ }), options.signal, (lateSubscription) => lateSubscription.close());
103
107
  const initialRoot = structuredClone(subscription.snapshot.rootTurn);
104
108
  const channel = new RuntimeHostSessionChannel(subscription, [], options, options.connection);
109
+ const onAbort = () => {
110
+ void channel.close().catch(() => undefined);
111
+ };
112
+ options.signal?.addEventListener('abort', onAbort, { once: true });
113
+ if (options.signal?.aborted)
114
+ onAbort();
105
115
  void channel.#pump(subscription);
106
116
  try {
107
117
  const recovered = await channel.#hydrateInitial(subscription);
118
+ options.signal?.throwIfAborted();
108
119
  const root = recovered ? structuredClone(channel.snapshot.rootTurn) : initialRoot;
109
120
  return {
110
121
  channel,
@@ -117,11 +128,15 @@ export class RuntimeHostSessionChannel {
117
128
  await channel.close().catch(() => undefined);
118
129
  throw error;
119
130
  }
131
+ finally {
132
+ options.signal?.removeEventListener('abort', onAbort);
133
+ }
120
134
  }
121
135
  async #hydrateInitial(subscription) {
122
136
  let messages;
123
137
  try {
124
- messages = await subscription.loadTranscript(decodeStoredMessage);
138
+ messages = await runChannelOperation(() => subscription.loadTranscript(decodeStoredMessage), this.#closeController.signal);
139
+ this.#closeController.signal.throwIfAborted();
125
140
  }
126
141
  catch (error) {
127
142
  if (!this.#canRecover(error))
@@ -242,10 +257,13 @@ export class RuntimeHostSessionChannel {
242
257
  });
243
258
  }
244
259
  }
245
- async close() {
246
- if (this.#closing)
247
- return;
260
+ close() {
261
+ this.#closeTask ??= this.#close();
262
+ return this.#closeTask;
263
+ }
264
+ async #close() {
248
265
  this.#closing = true;
266
+ this.#closeController.abort(new Error('Runtime Host Session channel is closed'));
249
267
  this.#clearRecoveryStableTimer();
250
268
  this.#recoveryAwaitingLiveFrame = undefined;
251
269
  this.#pendingStartedTurns.clear();
@@ -337,13 +355,13 @@ export class RuntimeHostSessionChannel {
337
355
  return;
338
356
  let replacement;
339
357
  try {
340
- replacement = await this.#connection.openSessionSubscription({
358
+ replacement = await runChannelOperation(() => this.#connection.openSessionSubscription({
341
359
  sessionId: this.sessionId,
342
360
  transcript: {
343
361
  kind: 'tail',
344
362
  maxBytes: SESSION_TRANSCRIPT_BOOTSTRAP_MAX_BYTES,
345
363
  },
346
- });
364
+ }), this.#closeController.signal, (lateSubscription) => lateSubscription.close());
347
365
  }
348
366
  catch (error) {
349
367
  if (this.#canRecover(error))
@@ -360,7 +378,7 @@ export class RuntimeHostSessionChannel {
360
378
  this.#pendingFrames.length = 0;
361
379
  void this.#pump(replacement);
362
380
  try {
363
- const messages = await replacement.loadTranscript(decodeStoredMessage);
381
+ const messages = await runChannelOperation(() => replacement.loadTranscript(decodeStoredMessage), this.#closeController.signal);
364
382
  if (this.#failedSubscriptions.has(replacement)) {
365
383
  throw new RuntimeHostSubscriptionError('connection_closed', 'Runtime Host Session subscription closed during catch-up');
366
384
  }
@@ -759,6 +777,42 @@ function isGuaranteedOutcome(event) {
759
777
  function isTurnTerminalOutcome(event) {
760
778
  return event.type === 'complete' || event.type === 'abort' || event.type === 'error';
761
779
  }
780
+ function runChannelOperation(operation, signal, discard) {
781
+ if (!signal)
782
+ return operation();
783
+ if (signal.aborted)
784
+ return Promise.reject(signal.reason);
785
+ return new Promise((resolve, reject) => {
786
+ let aborted = false;
787
+ const onAbort = () => {
788
+ aborted = true;
789
+ signal.removeEventListener('abort', onAbort);
790
+ reject(signal.reason);
791
+ };
792
+ signal.addEventListener('abort', onAbort, { once: true });
793
+ let running;
794
+ try {
795
+ running = operation();
796
+ }
797
+ catch (error) {
798
+ signal.removeEventListener('abort', onAbort);
799
+ reject(error);
800
+ return;
801
+ }
802
+ void running.then((value) => {
803
+ signal.removeEventListener('abort', onAbort);
804
+ // The connection may finish opening after this channel has gone away.
805
+ // A late subscription still belongs to the operation and must be closed.
806
+ if (aborted)
807
+ void discard?.(value).catch(() => undefined);
808
+ else
809
+ resolve(value);
810
+ }, (error) => {
811
+ signal.removeEventListener('abort', onAbort);
812
+ reject(error);
813
+ });
814
+ });
815
+ }
762
816
  /**
763
817
  * Goal identity + revision: GoalManager.commit bumps the revision on every
764
818
  * accepted transition, so this pair detects every set/settle/pause/resume/
@@ -96,6 +96,12 @@ const CHILD_RESULT_OUTPUT_SOURCES = new Set([
96
96
  'deep_research',
97
97
  ]);
98
98
  export function isArtifactUserVisible(record) {
99
+ // A directly written HTML file is an intentional user-facing deliverable:
100
+ // the Artifact Pane must be able to preview and open it without requiring a
101
+ // child-workspace writeback. Other tool results remain internal to avoid
102
+ // flooding the Generated Files tab with command output and diffs.
103
+ if (record.source === 'tool_result' && record.kind === 'html')
104
+ return true;
99
105
  return ARTIFACT_SOURCE_POLICIES[record.source].userVisible;
100
106
  }
101
107
  /** Visibility does not grant permission to destroy evidence owned by a runtime workflow. */
@@ -251,6 +251,11 @@ export function attachmentKindFromMimeType(mimeType, fileName) {
251
251
  }
252
252
  return 'other';
253
253
  }
254
- export function attachmentIngestBlocked(code) {
255
- return new Error(`attachment_ingest:${code}`);
254
+ export class AttachmentIngestBlockedError extends Error {
255
+ code;
256
+ constructor(code) {
257
+ super('Attachment ingest was blocked');
258
+ this.code = code;
259
+ this.name = 'AttachmentIngestBlockedError';
260
+ }
256
261
  }
@@ -21,7 +21,9 @@ import { isCanonicalArtifactEntityId, normalizeArtifactImagePreviewMime } from '
21
21
  import { hasExactShape, isRecord } from './record-schema.js';
22
22
  import { serializedByteLength } from './serialized-byte-length.js';
23
23
  export const DURABLE_TOOL_RESULT_PROJECTION_VERSION = 1;
24
- export const DURABLE_TOOL_RESULT_PROJECTION_MAX_BYTES = 256 * 1024;
24
+ // Storage capacity, not a provider context budget. Two executor-retained Bash
25
+ // streams can require 12 MiB after JSON escaping; Read bounds their presentation.
26
+ export const DURABLE_TOOL_RESULT_PROJECTION_MAX_BYTES = 16 * 1024 * 1024;
25
27
  export const DURABLE_TOOL_RESULT_PROJECTION_MAX_PARTS = 64;
26
28
  export const DURABLE_TOOL_RESULT_PROJECTION_MAX_JSON_DEPTH = 32;
27
29
  export const DURABLE_TOOL_RESULT_PROJECTION_MAX_JSON_NODES = 20_000;
@@ -57,6 +57,27 @@ export function isDirectoryReference(value) {
57
57
  isCanonicalAbsolutePath(value.path));
58
58
  }
59
59
  const MESSAGE_CONTENT_SHAPE = defineObjectShape()(['text'], ['displayText', 'attachments', 'directoryReferences', 'quotes', 'inlineReferences']);
60
+ /**
61
+ * A Turn message is meaningful when at least one of its four content carriers
62
+ * is present: inline text, an inline excerpt, an attachment reference, or a
63
+ * directory reference. Admission, compaction estimates, replay visibility,
64
+ * and recap projection must share this one predicate (#4804) — restating it
65
+ * per layer is how a quote-only message ends up admitted by one boundary and
66
+ * silently dropped by the next.
67
+ *
68
+ * The inline text is deliberately NOT trimmed. Admission asks "is this frame
69
+ * legal"; replay visibility asks "will the model see this already-persisted
70
+ * event", and that answer must stay compatible with everything admission has
71
+ * ever accepted — trimming here retroactively re-reads stored history as
72
+ * invisible and blocks replay on it (#4815 review). Surfaces that want the
73
+ * trimmed judgement (the desktop guard) trim at their own boundary.
74
+ */
75
+ export function hasMeaningfulMessageContent(content) {
76
+ return (content.text.length > 0 ||
77
+ (content.quotes?.length ?? 0) > 0 ||
78
+ (content.attachments?.length ?? 0) > 0 ||
79
+ (content.directoryReferences?.length ?? 0) > 0);
80
+ }
60
81
  const ATTACHMENT_REF_SHAPE = defineObjectShape()(['kind', 'name', 'mimeType', 'bytes', 'ref'], []);
61
82
  const QUOTE_REF_SHAPE = defineObjectShape()(['text'], ['label', 'sourceTurnId']);
62
83
  const INLINE_REFERENCE_SHAPE = defineObjectShape()(['kind', 'value', 'label', 'start'], []);
@@ -68,10 +68,14 @@ const GOAL_CONTROL_LEASE_SHAPE = defineObjectShape()(['goalId', 'generation'], [
68
68
  const GOAL_EXECUTION_REF_SHAPE = defineObjectShape()(['sessionId', 'turnId', 'runId'], []);
69
69
  const GOAL_CHECKPOINT_SHAPE = defineObjectShape()(['goalId', 'revision'], []);
70
70
  const GOAL_CURRENT_EXECUTION_SHAPE = defineObjectShape()(['execution', 'checkpoint', 'controlLease'], []);
71
- const GOAL_AUTHORITY_RECORD_SHAPE = defineObjectShape()(['schemaVersion', 'goal', 'controlLease', 'currentExecution'], []);
71
+ const GOAL_PENDING_CONTINUATION_SHAPE = defineObjectShape()(['checkpoint', 'controlLease', 'prompt'], ['triggeringTurnId']);
72
+ const GOAL_AUTHORITY_RECORD_SHAPE = defineObjectShape()(['schemaVersion', 'goal', 'controlLease', 'currentExecution', 'pendingContinuation'], []);
73
+ const GOAL_AUTHORITY_RECORD_V1_SHAPE = defineObjectShape()(['schemaVersion', 'goal', 'controlLease', 'currentExecution'], []);
72
74
  const GOAL_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
73
75
  export function decodeGoalAuthorityRecord(value) {
74
- if (!isRecord(value) || !hasExactShape(value, GOAL_AUTHORITY_RECORD_SHAPE)) {
76
+ if (!isRecord(value) ||
77
+ (!hasExactShape(value, GOAL_AUTHORITY_RECORD_SHAPE) &&
78
+ !hasExactShape(value, GOAL_AUTHORITY_RECORD_V1_SHAPE))) {
75
79
  throw new TypeError('Invalid Goal authority record');
76
80
  }
77
81
  if (value.schemaVersion !== 1)
@@ -81,6 +85,12 @@ export function decodeGoalAuthorityRecord(value) {
81
85
  const currentExecution = value.currentExecution === null
82
86
  ? null
83
87
  : decodeGoalCurrentExecution(value.currentExecution, goal, controlLease);
88
+ const pendingContinuation = value.pendingContinuation === undefined || value.pendingContinuation === null
89
+ ? null
90
+ : decodeGoalPendingContinuation(value.pendingContinuation, goal, controlLease);
91
+ if (currentExecution && pendingContinuation) {
92
+ throw new TypeError('Goal authority cannot have current and pending execution together');
93
+ }
84
94
  if (controlLease.goalId !== goal.id) {
85
95
  throw new TypeError('Goal control lease does not match Goal identity');
86
96
  }
@@ -89,6 +99,30 @@ export function decodeGoalAuthorityRecord(value) {
89
99
  goal,
90
100
  controlLease,
91
101
  currentExecution,
102
+ pendingContinuation,
103
+ });
104
+ }
105
+ function decodeGoalPendingContinuation(value, goal, authorityLease) {
106
+ if (!isRecord(value) || !hasExactShape(value, GOAL_PENDING_CONTINUATION_SHAPE)) {
107
+ throw new TypeError('Invalid Goal pending continuation');
108
+ }
109
+ const checkpoint = decodeGoalCheckpoint(value.checkpoint);
110
+ const controlLease = decodeGoalControlLease(value.controlLease);
111
+ if (checkpoint.goalId !== goal.id ||
112
+ checkpoint.revision !== goal.revision ||
113
+ controlLease.goalId !== authorityLease.goalId ||
114
+ controlLease.generation !== authorityLease.generation ||
115
+ typeof value.prompt !== 'string' ||
116
+ !value.prompt.trim() ||
117
+ !isGoalTextWithinLimit(value.prompt, GOAL_PENDING_PROMPT_TEXT_LIMIT) ||
118
+ !isOptionalGoalId(value.triggeringTurnId)) {
119
+ throw new TypeError('Goal pending continuation does not match Goal authority');
120
+ }
121
+ return Object.freeze({
122
+ checkpoint,
123
+ controlLease,
124
+ prompt: value.prompt,
125
+ ...(value.triggeringTurnId !== undefined ? { triggeringTurnId: value.triggeringTurnId } : {}),
92
126
  });
93
127
  }
94
128
  function decodeGoalState(value) {
@@ -166,6 +200,9 @@ function decodeGoalExecutionRef(value) {
166
200
  function isGoalId(value) {
167
201
  return typeof value === 'string' && GOAL_ID_PATTERN.test(value);
168
202
  }
203
+ function isOptionalGoalId(value) {
204
+ return value === undefined || isGoalId(value);
205
+ }
169
206
  function isNonnegativeInteger(value) {
170
207
  return Number.isSafeInteger(value) && value >= 0;
171
208
  }
@@ -206,3 +243,8 @@ export const GOAL_REASON_TEXT_LIMIT = Object.freeze({
206
243
  codeUnits: 500,
207
244
  utf8Bytes: 1_500,
208
245
  });
246
+ /** Durable continuation prompts include bounded evaluator and Goal context. */
247
+ export const GOAL_PENDING_PROMPT_TEXT_LIMIT = Object.freeze({
248
+ codeUnits: 4_000,
249
+ utf8Bytes: 12_000,
250
+ });
@@ -77,7 +77,6 @@ export function isToolCategory(value) {
77
77
  export const BUILTIN_TOOL_CATEGORY = {
78
78
  // read
79
79
  Read: 'read',
80
- ArchiveRead: 'read',
81
80
  search_files: 'read',
82
81
  Grep: 'read',
83
82
  Glob: 'read',
@@ -22,30 +22,30 @@ import { isRecord } from './record-schema.js';
22
22
  import { decodeRuntimeInvocationOpened, TOOL_BOUNDARY_PROTOCOL_V1 } from './runtime-event.js';
23
23
  import { stableJsonStringify } from './tool-args-identity.js';
24
24
  export function buildImmutableRuntimePrefix(identity, rows) {
25
- const canonicalRows = canonicalizePrefixRows(identity, rows);
26
- const last = canonicalRows.at(-1);
27
- if (!last)
28
- throw new Error('immutable RuntimeEvent prefix is empty');
25
+ const events = [];
26
+ const proof = foldImmutableRuntimePrefix(identity, rows, (event) => events.push(event));
29
27
  return {
30
28
  protocol: 'immutable_runtime_prefix_v1',
31
- identity: { ...identity },
32
- position: {
33
- lastEventSeq: last.eventSeq,
34
- eventCount: canonicalRows.length,
35
- lastEventId: last.event.id,
36
- },
37
- prefixDigest: digestCanonicalRuntimePrefix(identity, canonicalRows),
38
- events: canonicalRows.map((row) => row.event),
29
+ identity: proof.identity,
30
+ position: proof.position,
31
+ prefixDigest: proof.prefixDigest,
32
+ events,
39
33
  };
40
34
  }
35
+ export function buildImmutableRuntimePrefixProof(identity, rows) {
36
+ return foldImmutableRuntimePrefix(identity, rows);
37
+ }
41
38
  export function digestRuntimePrefix(identity, rows) {
42
- return digestCanonicalRuntimePrefix(identity, canonicalizePrefixRows(identity, rows));
39
+ return foldImmutableRuntimePrefix(identity, rows).prefixDigest;
43
40
  }
44
41
  export function runtimePrefixSegment(prefix) {
45
- if (prefix.protocol !== 'immutable_runtime_prefix_v1') {
42
+ if (prefix.protocol !== 'immutable_runtime_prefix_v1' &&
43
+ prefix.protocol !== 'immutable_runtime_prefix_proof_v1') {
46
44
  throw new Error('Invalid immutable RuntimeEvent prefix protocol');
47
45
  }
48
- const rebuilt = buildImmutableRuntimePrefix(prefix.identity, prefix.events.map((event, index) => ({ eventSeq: index + 1, event })));
46
+ const rebuilt = prefix.protocol === 'immutable_runtime_prefix_v1'
47
+ ? buildImmutableRuntimePrefix(prefix.identity, prefix.events.map((event, index) => ({ eventSeq: index + 1, event })))
48
+ : validateImmutableRuntimePrefixProof(prefix);
49
49
  if (stableJsonStringify(rebuilt.position) !== stableJsonStringify(prefix.position)) {
50
50
  throw new Error('Immutable RuntimeEvent prefix position mismatch');
51
51
  }
@@ -290,11 +290,16 @@ startKind) {
290
290
  prefixDigest: source.prefixDigest,
291
291
  }));
292
292
  }
293
- function canonicalizePrefixRows(identity, rows) {
293
+ function foldImmutableRuntimePrefix(identity, rows, visit) {
294
294
  const canonicalIdentity = decodePrefixIdentity(identity);
295
- const canonicalRows = [];
296
- for (const [index, row] of rows.entries()) {
297
- const expectedEventSeq = index + 1;
295
+ const hash = nodeCrypto.createHash('sha256');
296
+ updateLengthPrefixed(hash, Buffer.from('maka.runtime-prefix.v1', 'utf8'));
297
+ updateLengthPrefixed(hash, Buffer.from(stableJsonStringify(canonicalIdentity), 'utf8'));
298
+ let eventCount = 0;
299
+ let firstEvent;
300
+ let lastEvent;
301
+ for (const row of rows) {
302
+ const expectedEventSeq = eventCount + 1;
298
303
  if (!Number.isSafeInteger(row.eventSeq) ||
299
304
  row.eventSeq <= 0 ||
300
305
  row.eventSeq !== expectedEventSeq) {
@@ -310,19 +315,58 @@ function canonicalizePrefixRows(identity, rows) {
310
315
  event.turnId !== canonicalIdentity.turnId) {
311
316
  throw new Error(`immutable RuntimeEvent identity mismatch for ${event.id}`);
312
317
  }
313
- canonicalRows.push({ eventSeq: row.eventSeq, event });
318
+ hash.update(uint64be(row.eventSeq));
319
+ updateLengthPrefixed(hash, Buffer.from(encodeRuntimePrefixV1Event(event), 'utf8'));
320
+ firstEvent ??= event;
321
+ lastEvent = event;
322
+ eventCount += 1;
323
+ visit?.(event);
314
324
  }
315
- return canonicalRows;
325
+ if (!firstEvent || !lastEvent)
326
+ throw new Error('immutable RuntimeEvent prefix is empty');
327
+ return {
328
+ protocol: 'immutable_runtime_prefix_proof_v1',
329
+ identity: canonicalIdentity,
330
+ position: {
331
+ lastEventSeq: eventCount,
332
+ eventCount,
333
+ lastEventId: lastEvent.id,
334
+ },
335
+ prefixDigest: `sha256:${hash.digest('hex')}`,
336
+ firstEvent,
337
+ lastEvent,
338
+ };
316
339
  }
317
- function digestCanonicalRuntimePrefix(identity, rows) {
318
- const hash = nodeCrypto.createHash('sha256');
319
- updateLengthPrefixed(hash, Buffer.from('maka.runtime-prefix.v1', 'utf8'));
320
- updateLengthPrefixed(hash, Buffer.from(stableJsonStringify(identity), 'utf8'));
321
- for (const row of rows) {
322
- hash.update(uint64be(row.eventSeq));
323
- updateLengthPrefixed(hash, Buffer.from(encodeRuntimePrefixV1Event(row.event), 'utf8'));
340
+ function validateImmutableRuntimePrefixProof(proof) {
341
+ const identity = decodePrefixIdentity(proof.identity);
342
+ const position = decodePrefixPosition(proof.position);
343
+ const prefixDigest = decodeBoundaryDigest(proof.prefixDigest);
344
+ const firstEvent = encodeCanonicalRuntimeEvent(proof.firstEvent).event;
345
+ const lastEvent = encodeCanonicalRuntimeEvent(proof.lastEvent).event;
346
+ for (const event of [firstEvent, lastEvent]) {
347
+ if (event.partial === true)
348
+ throw new Error('Immutable RuntimeEvent proof contains a partial endpoint');
349
+ if (event.sessionId !== identity.sessionId ||
350
+ event.invocationId !== identity.invocationId ||
351
+ event.runId !== identity.runId ||
352
+ event.turnId !== identity.turnId) {
353
+ throw new Error(`Immutable RuntimeEvent proof identity mismatch for ${event.id}`);
354
+ }
324
355
  }
325
- return `sha256:${hash.digest('hex')}`;
356
+ if (position.lastEventId !== lastEvent.id) {
357
+ throw new Error('Immutable RuntimeEvent prefix position mismatch');
358
+ }
359
+ if (position.eventCount === 1 && firstEvent.id !== lastEvent.id) {
360
+ throw new Error('Immutable RuntimeEvent proof endpoints mismatch');
361
+ }
362
+ return {
363
+ protocol: 'immutable_runtime_prefix_proof_v1',
364
+ identity,
365
+ position,
366
+ prefixDigest,
367
+ firstEvent,
368
+ lastEvent,
369
+ };
326
370
  }
327
371
  function encodeRuntimePrefixV1Event(event) {
328
372
  const canonical = encodeCanonicalRuntimeEvent(event);
@@ -34,7 +34,7 @@
34
34
  import { isWorkHubActionReceipt } from './workhub-action-result.js';
35
35
  import { isModelRetryDecision } from './model-failure.js';
36
36
  import { isRuntimeHandoffPause, runtimeHandoffPause, } from './runtime-handoff.js';
37
- import { isMessageContent, normalizeMessageContent, } from './events.js';
37
+ import { hasMeaningfulMessageContent, isMessageContent, normalizeMessageContent, } from './events.js';
38
38
  import { INTERACTION_ID_MAX_BYTES, INTERACTION_TOOL_NAME_MAX_BYTES, decodeInteractionRequest, } from './interaction.js';
39
39
  import { isPermissionMode, } from './permission.js';
40
40
  import { isCollaborationMode } from './collaboration.js';
@@ -818,7 +818,10 @@ export function isPartialRuntimeEvent(event) {
818
818
  /**
819
819
  * True if the event carries content whose kind is eligible for model
820
820
  * history projection: text, thinking, function_call, or function_response.
821
- * Error-only content and pure action/refs events are NOT model-visible.
821
+ * A user-authored text event with structured context (quotes or attachments)
822
+ * is model-visible even when the inline text is empty — the structured part
823
+ * is what carries the turn (#4804). Error-only content and pure action/refs
824
+ * events are NOT model-visible.
822
825
  *
823
826
  * This is a content-kind check only. Callers still apply `partial`
824
827
  * filtering (partial chunks are never replayed into the next model call).
@@ -831,7 +834,7 @@ export function runtimeEventHasModelVisibleContent(event) {
831
834
  return false;
832
835
  switch (content.kind) {
833
836
  case 'text':
834
- return content.text.length > 0;
837
+ return hasMeaningfulMessageContent(content);
835
838
  case 'thinking':
836
839
  case 'function_call':
837
840
  case 'function_response':
@@ -69,15 +69,23 @@ export function runtimeHandoffPause(event) {
69
69
  }
70
70
  /** Verify against source facts, not the proposed claim's description of them. */
71
71
  export function assertHandoffClaimSource(claim, prefix) {
72
- const last = prefix.events.at(-1);
72
+ const first = prefix.protocol === 'immutable_runtime_prefix_v1' ? prefix.events[0] : prefix.firstEvent;
73
+ const last = prefix.protocol === 'immutable_runtime_prefix_v1' ? prefix.events.at(-1) : prefix.lastEvent;
73
74
  const pause = last && runtimeHandoffPause(last);
74
75
  const source = claim.targetOpening.source;
76
+ const claimedPrefix = claim.boundary.segments.at(-1);
77
+ if (!claimedPrefix ||
78
+ claimedPrefix.prefixDigest !== prefix.prefixDigest ||
79
+ stableJsonStringify(claimedPrefix.identity) !== stableJsonStringify(prefix.identity) ||
80
+ stableJsonStringify(claimedPrefix.position) !== stableJsonStringify(prefix.position)) {
81
+ throw new Error('Handoff claim does not preserve the sealed source prefix');
82
+ }
75
83
  if (source.kind !== 'handoff') {
76
84
  if (pause)
77
85
  throw new Error('A handoff pause is reserved for its authorized successor');
78
86
  return;
79
87
  }
80
- const opening = prefix.events[0]?.content;
88
+ const opening = first?.content;
81
89
  if (!pause ||
82
90
  opening?.kind !== 'invocation_opened' ||
83
91
  prefix.identity.sessionId !== claim.target.sessionId ||