maka-agent 0.2.0-dev.76.20261009 → 0.2.0-dev.78.20261010

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 (78) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +3 -3
  3. package/dist/pi-tui-pickers.js +5 -2
  4. package/dist/pi-tui-runner.js +211 -33
  5. package/dist/runtime-host-session-driver.js +91 -13
  6. package/dist/session-catalog-limits.js +22 -0
  7. package/dist/session-driver.js +10 -0
  8. package/dist/tui-copy-catalog.js +15 -0
  9. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  10. package/node_modules/@maka/core/dist/bot-chat-settings.js +3 -0
  11. package/node_modules/@maka/core/dist/bot-events.js +1 -8
  12. package/node_modules/@maka/core/dist/local-memory.js +38 -1
  13. package/node_modules/@maka/core/dist/scheduled-task.js +14 -15
  14. package/node_modules/@maka/core/dist/session.js +3 -0
  15. package/node_modules/@maka/core/dist/text-sanitize.js +8 -3
  16. package/node_modules/@maka/runtime/dist/agent-graph-supervisor-wake.js +115 -17
  17. package/node_modules/@maka/runtime/dist/agent-run.js +11 -0
  18. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +11 -0
  19. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +13 -5
  20. package/node_modules/@maka/runtime/dist/bots/proxied-fetch.js +29 -1
  21. package/node_modules/@maka/runtime/dist/computer-use-observation-text.js +22 -0
  22. package/node_modules/@maka/runtime/dist/conversation-copy-agent-output.js +227 -0
  23. package/node_modules/@maka/runtime/dist/conversation-copy.js +176 -46
  24. package/node_modules/@maka/runtime/dist/file-stable-write.js +15 -3
  25. package/node_modules/@maka/runtime/dist/filesystem-worker/resource-resolver.js +3 -1
  26. package/node_modules/@maka/runtime/dist/model-adapter.js +20 -1
  27. package/node_modules/@maka/runtime/dist/network/proxy-test.js +28 -1
  28. package/node_modules/@maka/runtime/dist/network/scoped-fetch-transport.js +13 -2
  29. package/node_modules/@maka/runtime/dist/owned-child-process.js +209 -0
  30. package/node_modules/@maka/runtime/dist/owned-process-main.js +183 -0
  31. package/node_modules/@maka/runtime/dist/pipe-process-driver.js +19 -25
  32. package/node_modules/@maka/runtime/dist/runtime-kernel.js +216 -8
  33. package/node_modules/@maka/runtime/dist/sandbox-boundary-tool.js +12 -0
  34. package/node_modules/@maka/runtime/dist/session-manager.js +156 -17
  35. package/node_modules/@maka/runtime/dist/shell-exec.js +18 -20
  36. package/node_modules/@maka/runtime/dist/shell-run-manager.js +2 -1
  37. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +279 -8
  38. package/node_modules/@maka/runtime/dist/stream-graph-schedule-reconcile.js +209 -37
  39. package/node_modules/@maka/runtime/dist/tool-availability.js +55 -18
  40. package/node_modules/@maka/runtime/dist/tool-call-snapshot.js +142 -0
  41. package/node_modules/@maka/runtime/dist/tool-runtime.js +27 -84
  42. package/node_modules/@maka/runtime/dist/workers/{filesystem-worker.js → filesystem-worker.mjs} +12 -1
  43. package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +30 -6
  44. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  45. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +44 -13
  46. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -0
  47. package/node_modules/@maka/runtime-host/dist/client/wsl-environment.js +4 -0
  48. package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
  49. package/node_modules/@maka/runtime-host/dist/protocol/message.js +1 -13
  50. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +46 -9
  51. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -13
  52. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +1 -13
  53. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +4 -7
  54. package/node_modules/@maka/runtime-host/dist/server/archive-retention-coordinator.js +81 -105
  55. package/node_modules/@maka/runtime-host/dist/server/candidate.js +47 -40
  56. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +87 -67
  57. package/node_modules/@maka/runtime-host/dist/server/execution-service.js +5 -1
  58. package/node_modules/@maka/runtime-host/dist/server/host-maintenance-guards.js +95 -0
  59. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +18 -8
  60. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +32 -3
  61. package/node_modules/@maka/runtime-host/dist/server/project-catalog-coordinator.js +2 -0
  62. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +109 -8
  63. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +20 -3
  64. package/node_modules/@maka/runtime-host/dist/server/session-background-activity.js +71 -0
  65. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +8 -4
  66. package/node_modules/@maka/runtime-host/dist/server/session-interaction-activity.js +78 -0
  67. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +56 -0
  68. package/node_modules/@maka/runtime-host/dist/server/session-revision-graph-references.js +16 -2
  69. package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +2 -0
  70. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +19 -24
  71. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +74 -19
  72. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +1 -1
  73. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +6 -99
  74. package/node_modules/@maka/runtime-host/dist/server/workhub-target-execution-authority.js +14 -126
  75. package/node_modules/@maka/storage/dist/context-offload-store.js +8 -3
  76. package/node_modules/@maka/storage/dist/git-entry.js +142 -5
  77. package/node_modules/@maka/storage/dist/session-bundle-policy.js +54 -19
  78. package/package.json +2 -2
@@ -23,7 +23,7 @@ import { resolveCollaborationPermissionMode } from '@maka/core/collaboration';
23
23
  import { serializedByteLength } from '@maka/core/serialized-byte-length';
24
24
  import { encodeToolStepProgress, ToolOutcomeUnknownError } from '@maka/core/events';
25
25
  import { isInteractionAnswerValidForRequest, projectInteractionFormRequest, } from '@maka/core/interaction';
26
- import { computerUseModelCallArgs } from '@maka/core/computer-use';
26
+ import { buildToolCallArgs, snapshotToolArgs } from './tool-call-snapshot.js';
27
27
  import { redactSecrets } from '@maka/core/redaction';
28
28
  import { TOOL_BOUNDARY_PROTOCOL_V1 } from '@maka/core/runtime-event';
29
29
  import { recordToolArtifactsSafely } from './tool-artifacts.js';
@@ -544,36 +544,30 @@ export class ToolRuntime {
544
544
  ? `Tool ${tool.name} is direct-only and cannot run inside exec.`
545
545
  : undefined;
546
546
  const admissionFailure = directOnlyFailure ?? this.admitToolForStep(tool, stepId);
547
- let permissionArgs = executionArgs;
548
- let permissionArgsError;
549
- if (directOnlyFailure === undefined) {
550
- try {
551
- // An unavailable sandbox-boundary surface rejects the expansion before
552
- // interpreting it, including legacy shapes whose validation stays deferred.
553
- const sandboxBoundaryUnavailable = tool.name === 'request_sandbox_boundary' &&
554
- !this.interactionRun() &&
555
- (!this.input.createSandboxBoundaryRequest || !this.input.settleSandboxBoundaryRequest);
556
- if (!sandboxBoundaryUnavailable) {
557
- await validateDeclaredToolArgs(tool.parameters, executionArgs);
558
- }
559
- permissionArgs = tool.permissionArgs
560
- ? snapshotToolArgs(tool.permissionArgs(structuredClone(executionArgs), {
561
- sessionId: this.input.sessionId,
562
- turnId,
563
- toolCallId: toolUseId,
564
- }))
565
- : executionArgs;
566
- }
567
- catch (error) {
568
- permissionArgsError = error;
569
- }
570
- }
571
- // Permission args are a policy/UI projection. Persistence and model replay
572
- // share one canonical tool-dialect projection; Computer Use also strips
573
- // screen-derived and user-typed values at this boundary.
574
- const persistedArgs = tool.categoryHint === 'computer_use'
575
- ? snapshotToolArgs(computerUseModelCallArgs(permissionArgs))
576
- : permissionArgs;
547
+ // An unavailable sandbox-boundary surface cannot carry the expansion the
548
+ // tool requests, and that availability contract holds even when an older
549
+ // caller sends a legacy shape — validation stays deferred for it.
550
+ const sandboxBoundaryUnavailable = tool.name === 'request_sandbox_boundary' &&
551
+ !this.interactionRun() &&
552
+ (!this.input.createSandboxBoundaryRequest || !this.input.settleSandboxBoundaryRequest);
553
+ const callArgs = await buildToolCallArgs({
554
+ toolName: tool.name,
555
+ parameters: tool.parameters,
556
+ categoryHint: tool.categoryHint,
557
+ permissionArgs: tool.permissionArgs,
558
+ // Upstream renamed rawExecutionArgs to executionArgs after this
559
+ // refactor; buildToolCallArgs passes it through unchanged, and the
560
+ // entry snapshot above already holds that same value.
561
+ executionArgs,
562
+ sessionId: this.input.sessionId,
563
+ turnId,
564
+ toolCallId: toolUseId,
565
+ directOnlyRejected: directOnlyFailure !== undefined,
566
+ validationDeferred: sandboxBoundaryUnavailable,
567
+ });
568
+ const permissionArgs = callArgs.permissionArgs;
569
+ const persistedArgs = callArgs.persistedArgs;
570
+ const permissionArgsError = callArgs.permissionArgsError;
577
571
  const now = this.input.now();
578
572
  const trace = this.input.getRunTrace?.() ?? null;
579
573
  const runId = this.input.runId;
@@ -2068,36 +2062,6 @@ export class ToolRuntime {
2068
2062
  });
2069
2063
  }
2070
2064
  }
2071
- async function validateDeclaredToolArgs(parameters, args) {
2072
- if (!parameters || (typeof parameters !== 'object' && typeof parameters !== 'function')) {
2073
- return;
2074
- }
2075
- const schema = parameters;
2076
- if (typeof schema.safeParseAsync === 'function') {
2077
- const parsed = await schema.safeParseAsync(args);
2078
- if (parsed.success)
2079
- return;
2080
- throw parsed.error;
2081
- }
2082
- if (typeof schema.safeParse === 'function') {
2083
- const parsed = schema.safeParse(args);
2084
- if (parsed.success)
2085
- return;
2086
- throw parsed.error;
2087
- }
2088
- if (typeof schema.validate === 'function') {
2089
- const parsed = await schema.validate(args);
2090
- if (parsed.success)
2091
- return;
2092
- throw parsed.error;
2093
- }
2094
- if (typeof schema['~standard']?.validate === 'function') {
2095
- const parsed = await schema['~standard'].validate(args);
2096
- if ('value' in parsed)
2097
- return;
2098
- throw new Error('Tool arguments failed declared schema validation', { cause: parsed.issues });
2099
- }
2100
- }
2101
2065
  function isInteractionControlError(error) {
2102
2066
  return (error instanceof RuntimeInteractionAdmissionRejectedError ||
2103
2067
  error instanceof RuntimeInteractionClosedError ||
@@ -2152,7 +2116,8 @@ function racePromiseWithAbort(promise, signal) {
2152
2116
  */
2153
2117
  export function formatDeferredNotLoadedText(toolName) {
2154
2118
  return (`Tool "${toolName}" is available but not loaded yet. ` +
2155
- `Call tool_search to activate it first, then call "${toolName}" on a later step.`);
2119
+ `Call tool_search with query "${toolName}" to activate it first, ` +
2120
+ `then call "${toolName}" on a later step.`);
2156
2121
  }
2157
2122
  /**
2158
2123
  * Canonical key for a tool call's args; order-independent so identical calls
@@ -2597,25 +2562,3 @@ function byteLength(value) {
2597
2562
  const text = typeof value === 'string' ? value : JSON.stringify(value ?? null);
2598
2563
  return Buffer.byteLength(text, 'utf8');
2599
2564
  }
2600
- function snapshotToolArgs(value) {
2601
- return snapshotJsonValue(value, new WeakSet());
2602
- }
2603
- function snapshotJsonValue(value, seen) {
2604
- if (value === null || typeof value !== 'object')
2605
- return value;
2606
- if (seen.has(value))
2607
- throw new Error('Tool arguments must not contain cycles');
2608
- seen.add(value);
2609
- if (Array.isArray(value)) {
2610
- return Object.freeze(value.map((entry) => snapshotJsonValue(entry, seen)));
2611
- }
2612
- const output = {};
2613
- for (const key of Object.keys(value)) {
2614
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
2615
- if (!descriptor || !('value' in descriptor)) {
2616
- throw new Error(`Tool argument ${key} must be a plain data property`);
2617
- }
2618
- output[key] = snapshotJsonValue(descriptor.value, seen);
2619
- }
2620
- return Object.freeze(output);
2621
- }
@@ -24203,9 +24203,20 @@ async function openExistingNoTruncate(path2) {
24203
24203
  }
24204
24204
  }
24205
24205
  async function writeThroughHandle(handle, content) {
24206
+ const bytes = Buffer.from(content, "utf8");
24206
24207
  try {
24207
24208
  await handle.truncate(0);
24208
- await handle.write(content, 0, "utf8");
24209
+ let offset = 0;
24210
+ while (offset < bytes.length) {
24211
+ const { bytesWritten } = await handle.write(bytes, offset, bytes.length - offset, offset);
24212
+ if (bytesWritten <= 0) {
24213
+ throw new StableWriteFailure(
24214
+ "outcome_unknown",
24215
+ "The write stopped making progress; the file may be truncated. Re-read the file before writing to it again."
24216
+ );
24217
+ }
24218
+ offset += bytesWritten;
24219
+ }
24209
24220
  } catch (error62) {
24210
24221
  const code = error62.code;
24211
24222
  if (code === "ENOSPC" || code === "EIO" || code === "EDQUOT" || code === "EFBIG") {
@@ -30,7 +30,7 @@ import { abortable, waitForRuntimeHostReady } from './wait-for-ready.js';
30
30
  // Candidate readiness includes the Windows named-pipe ACL helper, whose
31
31
  // fail-closed ceiling is 60s. Leave enough room for election and connection
32
32
  // bookkeeping after that helper returns.
33
- const DEFAULT_ELECTION_DEADLINE_MS = 75_000;
33
+ export const DEFAULT_ELECTION_DEADLINE_MS = 75_000;
34
34
  const DEFAULT_BACKOFF_MIN_MS = 20;
35
35
  const DEFAULT_BACKOFF_MAX_MS = 250;
36
36
  const MIN_CANDIDATE_INTERVAL_MS = 250;
@@ -251,10 +251,24 @@ export async function connectOrSpawnRuntimeHostWithDependencies(input, dependenc
251
251
  return result;
252
252
  }
253
253
  if (isPermanentCandidateStartupFailure(startupFailure) && pendingCandidateReports === 0) {
254
- const selectedFailure = startupFailure;
255
- electionSettled = true;
256
- await selectCandidateStartupDiagnostic(capability.rootId, selectedFailure.startupAttemptId).catch(() => undefined);
257
- return { kind: 'failed', reason: selectedFailure.reason };
254
+ if (isLaunchElectionLoss(startupFailure)) {
255
+ // Losing the launch election is not a Host failure. It is proof that
256
+ // another candidate holds this root and is still coming up, so the
257
+ // only honest reading is "keep waiting for the one that won".
258
+ // Returning here is what made a slow first startup look permanently
259
+ // unavailable: the desktop saw a failure a second after it asked,
260
+ // while the candidate that owned the root was still loading its
261
+ // transcript store, and every retry looked the same (issue #5843).
262
+ // The loser's startup diagnostic stays on disk, because that trace is
263
+ // what made the storm visible at all; it is simply no longer terminal.
264
+ startupFailure = undefined;
265
+ }
266
+ else {
267
+ const selectedFailure = startupFailure;
268
+ electionSettled = true;
269
+ await selectCandidateStartupDiagnostic(capability.rootId, selectedFailure.startupAttemptId).catch(() => undefined);
270
+ return { kind: 'failed', reason: selectedFailure.reason };
271
+ }
258
272
  }
259
273
  const now = performance.now();
260
274
  if (shouldLaunchCandidate(result) &&
@@ -362,7 +376,7 @@ export async function connectOrSpawnRuntimeHostWithDependencies(input, dependenc
362
376
  await sleep(Math.min(remaining, Math.max(1, Math.round(backoffMs * jitter))), input.signal);
363
377
  backoffMs = Math.min(DEFAULT_BACKOFF_MAX_MS, backoffMs * 2);
364
378
  }
365
- if (startupFailure) {
379
+ if (startupFailure && !isLaunchElectionLoss(startupFailure)) {
366
380
  const selectedFailure = startupFailure;
367
381
  electionSettled = true;
368
382
  await selectCandidateStartupDiagnostic(capability.rootId, selectedFailure.startupAttemptId).catch(() => undefined);
@@ -387,6 +401,16 @@ export async function connectOrSpawnRuntimeHostWithDependencies(input, dependenc
387
401
  electionSettled = true;
388
402
  }
389
403
  }
404
+ /**
405
+ * A Candidate that lost the launch election is evidence about *another*
406
+ * Candidate - it holds the root and is still coming up - so the loss must never
407
+ * decide how this election ends, whether the loop breaks on it or the window
408
+ * simply runs out. Failing on it made a slow first startup look like a
409
+ * permanently unavailable Host (issue #5843).
410
+ */
411
+ function isLaunchElectionLoss(failure) {
412
+ return failure?.reason === 'launch_election_lost';
413
+ }
390
414
  function recordElectionResult(result, observations) {
391
415
  observations.totalResults += 1;
392
416
  const registration = 'registration' in result ? result.registration : undefined;
@@ -34,7 +34,7 @@ export { RuntimeHostSubscriptionError, SessionRemovedSubscriptionError, subscrip
34
34
  export { RuntimeHostStartupError, runtimeHostStartupError, } from './startup-error.js';
35
35
  export { connectRuntimeHostWslEnvironment, listRuntimeHostWslDistributions, normalizeRuntimeHostWslDistribution, resolveSystemRuntimeHostWslExecutable, } from './wsl-environment.js';
36
36
  export { RuntimeHostCatalogReadError, RuntimeHostSessionCatalogRevisionChangedError, readRuntimeHostConnectionCatalog, readRuntimeHostInvocableSkills, readRuntimeHostProjectDetails, readRuntimeHostResources, readRuntimeHostProjects, readRuntimeHostSessionCatalogPage, readRuntimeHostSessions, readRuntimeHostSkillCatalog, } from './catalog-reader.js';
37
- export { IDLE_GRACE_MS_ENV_VAR, connectOrSpawnRuntimeHost, } from './connect-or-spawn.js';
37
+ export { DEFAULT_ELECTION_DEADLINE_MS, ELECTION_DEADLINE_MS_ENV_VAR, IDLE_GRACE_MS_ENV_VAR, connectOrSpawnRuntimeHost, electionDeadlineMsFromEnvironment, } from './connect-or-spawn.js';
38
38
  export { abortable, waitForRuntimeHostReady } from './wait-for-ready.js';
39
39
  export { createRuntimeHostCandidateLaunchBarrier, } from './candidate-launch-barrier.js';
40
40
  export { runHostedExecution } from './hosted-execution.js';
@@ -63,6 +63,7 @@ class RuntimeHostReconnectLifecycleImpl {
63
63
  #closed = false;
64
64
  #quiesced = false;
65
65
  #terminalError;
66
+ #startTask;
66
67
  #reconnectTask;
67
68
  #reconnectAbort;
68
69
  #discardTask = Promise.resolve();
@@ -96,10 +97,22 @@ class RuntimeHostReconnectLifecycleImpl {
96
97
  get current() {
97
98
  return this.#current;
98
99
  }
99
- async start() {
100
+ start() {
101
+ if (this.#closed)
102
+ return Promise.reject(new Error('Runtime Host reconnect lifecycle is closed'));
103
+ if (this.#startTask)
104
+ return this.#startTask;
105
+ const task = deferredTask();
106
+ this.#startTask = task.promise;
107
+ void this.#start().then(task.resolve, task.reject);
108
+ return task.promise;
109
+ }
110
+ async #start() {
100
111
  try {
101
- if (this.#initial) {
102
- this.#install(this.#initial);
112
+ const initial = this.#initial;
113
+ this.#initial = undefined;
114
+ if (initial) {
115
+ this.#install(initial);
103
116
  return;
104
117
  }
105
118
  this.#initialSignal?.throwIfAborted();
@@ -109,6 +122,8 @@ class RuntimeHostReconnectLifecycleImpl {
109
122
  this.#install(await this.#connect(signal));
110
123
  }
111
124
  catch (error) {
125
+ if (this.#closed)
126
+ throw error;
112
127
  const failure = asError(error);
113
128
  if ((typeof this.#retryInitialFailure === 'function'
114
129
  ? this.#retryInitialFailure(failure)
@@ -198,8 +213,12 @@ class RuntimeHostReconnectLifecycleImpl {
198
213
  return this.#suspension(current);
199
214
  }
200
215
  close() {
201
- this.#closeTask ??= this.#close();
202
- return this.#closeTask;
216
+ if (this.#closeTask)
217
+ return this.#closeTask;
218
+ const task = deferredTask();
219
+ this.#closeTask = task.promise;
220
+ void this.#close().then(task.resolve, task.reject);
221
+ return task.promise;
203
222
  }
204
223
  async #close() {
205
224
  if (this.#closed)
@@ -210,9 +229,11 @@ class RuntimeHostReconnectLifecycleImpl {
210
229
  this.#abort.abort();
211
230
  const error = new Error('Runtime Host reconnect lifecycle is closed');
212
231
  this.#rejectWaiters(error);
213
- const current = this.#current;
232
+ const current = this.#current ?? this.#initial;
233
+ this.#initial = undefined;
214
234
  this.#setCurrent(undefined);
215
235
  await current?.close().catch(() => undefined);
236
+ await this.#startTask?.catch(() => undefined);
216
237
  await this.#reconnectTask?.catch(() => undefined);
217
238
  await this.#discardTask;
218
239
  this.#resolveClosed();
@@ -248,17 +269,18 @@ class RuntimeHostReconnectLifecycleImpl {
248
269
  this.#reconnectTask)
249
270
  return;
250
271
  const reconnectAbort = new AbortController();
251
- const task = this.#reconnect(AbortSignal.any([this.#abort.signal, reconnectAbort.signal]));
272
+ const task = deferredTask();
252
273
  this.#reconnectAbort = reconnectAbort;
253
- this.#reconnectTask = task;
274
+ this.#reconnectTask = task.promise;
275
+ void this.#reconnect(AbortSignal.any([this.#abort.signal, reconnectAbort.signal])).then(task.resolve, task.reject);
254
276
  const finalize = () => {
255
- if (this.#reconnectTask === task) {
277
+ if (this.#reconnectTask === task.promise) {
256
278
  this.#reconnectTask = undefined;
257
279
  this.#reconnectAbort = undefined;
258
280
  }
259
281
  this.#scheduleReconnect();
260
282
  };
261
- void task.then(finalize, finalize);
283
+ void task.promise.then(finalize, finalize);
262
284
  }
263
285
  async #reconnect(signal) {
264
286
  let attemptedWakeGeneration = this.#wakeGeneration;
@@ -348,13 +370,13 @@ class RuntimeHostReconnectLifecycleImpl {
348
370
  }
349
371
  }
350
372
  #failPermanently(error) {
351
- if (this.#terminalError)
373
+ if (this.#closed || this.#terminalError)
352
374
  return;
353
375
  this.#terminalError = error;
354
- this.#abort.abort();
355
376
  this.#rejectWaiters(error);
377
+ // Acquisition tasks must settle before close can finish joining them.
378
+ void this.close();
356
379
  notifyError(this.#onFatalError, error);
357
- this.#resolveClosed();
358
380
  }
359
381
  #rejectWaiters(error) {
360
382
  for (const waiter of this.#waiters) {
@@ -364,6 +386,15 @@ class RuntimeHostReconnectLifecycleImpl {
364
386
  this.#waiters.clear();
365
387
  }
366
388
  }
389
+ function deferredTask() {
390
+ let resolve;
391
+ let reject;
392
+ const promise = new Promise((resolveTask, rejectTask) => {
393
+ resolve = resolveTask;
394
+ reject = rejectTask;
395
+ });
396
+ return { promise, resolve, reject };
397
+ }
367
398
  function notifyError(listener, error) {
368
399
  try {
369
400
  listener?.(error);
@@ -35,6 +35,12 @@ export function projectSessionCatalogSummary(session) {
35
35
  ? {}
36
36
  : { lastMessagePreview: session.lastMessagePreview }),
37
37
  status: session.status,
38
+ ...(session.backgroundActivity === undefined
39
+ ? {}
40
+ : { backgroundActivity: session.backgroundActivity }),
41
+ ...(session.backgroundActivityVersion === undefined
42
+ ? {}
43
+ : { backgroundActivityVersion: session.backgroundActivityVersion }),
38
44
  ...(session.blockedReason === undefined ? {} : { blockedReason: session.blockedReason }),
39
45
  ...(session.statusUpdatedAt === undefined ? {} : { statusUpdatedAt: session.statusUpdatedAt }),
40
46
  ...(session.liveRunState === undefined
@@ -112,6 +112,10 @@ class WslProcessByteStream {
112
112
  }
113
113
  onError(listener) {
114
114
  this.child.once('error', listener);
115
+ // Pipe errors are emitted independently of the write callback and the
116
+ // ChildProcess error event. Route them through the transport failure fence.
117
+ this.child.stdin.on('error', listener);
118
+ this.child.stdout.on('error', listener);
115
119
  }
116
120
  write(chunk) {
117
121
  return new Promise((resolve, reject) => {
@@ -65,7 +65,11 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
65
65
  export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
66
66
  // Increment when the same protocol version no longer guarantees safe Client-Host
67
67
  // interoperability. Mismatches are rejected before domain commands are admitted.
68
- export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 208;
68
+ export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 215;
69
+ // 215: WorkHub coordination uses canonical configuration and scoped delegated permissions.
70
+ // 212: Session catalogs carry `backgroundActivity` and `backgroundActivityVersion`
71
+ // for Host-owned activity and generation-scoped ordering independent of Session
72
+ // revisions and Turn epochs. Older closed decoders reject these fields.
69
73
  // 208: Usage queries filter by model call kind. Epoch-207 peers reject the
70
74
  // filter or ignore it.
71
75
  // 206: `storage.retention.query` and `storage.retention.set` read and change the
@@ -17,7 +17,7 @@
17
17
  * under the License.
18
18
  */
19
19
  import { invalidProtocolFrame } from './errors.js';
20
- import { assertExactKeys, requireCount, requireEntityId, requireExactRecord, requireId, requireRecord, requireShapedRecord, } from './codec.js';
20
+ import { assertExactKeys, requireCount, requireEncodedByteLimit, requireEntityId, requireExactRecord, requireId, requireRecord, requireShapedRecord, } from './codec.js';
21
21
  import { defineOperation } from './operation-spec.js';
22
22
  import { decodeMessageAdmissionContent, decodeSkillIds, decodeTurnOrchestration, decodeTurnSnapshot, } from './turn.js';
23
23
  import { decodeSkillInvocationResult } from '@maka/core/skill-invocation';
@@ -377,15 +377,3 @@ function assertUniqueQueueEntries(entries, label) {
377
377
  messageIds.add(entry.messageId);
378
378
  }
379
379
  }
380
- function requireEncodedByteLimit(value, label, maxBytes) {
381
- let encoded;
382
- try {
383
- encoded = JSON.stringify(value);
384
- }
385
- catch {
386
- throw invalidProtocolFrame(`Invalid ${label}`);
387
- }
388
- if (encoded === undefined || Buffer.byteLength(encoded, 'utf8') > maxBytes) {
389
- throw invalidProtocolFrame(`Invalid ${label}`);
390
- }
391
- }
@@ -101,6 +101,8 @@ const PROJECTION_FIELDS = [
101
101
  'thinkingLevel',
102
102
  'lastReadMessageId',
103
103
  'liveRunState',
104
+ 'backgroundActivity',
105
+ 'backgroundActivityVersion',
104
106
  ];
105
107
  export const SESSION_CATALOG_OPERATION_SPECS = {
106
108
  'session.shared.query': defineOperation({
@@ -176,7 +178,15 @@ function decodeSharedSessionCatalogQueryResult(value) {
176
178
  return { session };
177
179
  }
178
180
  export function decodeSharedSessionCatalogProjection(value) {
179
- const exact = requireShapedRecord(value, 'shared Session catalog projection', ['kind', 'id', 'revision', 'createdAt', 'activityAt', 'name', 'status'], ['lastMessageAt', 'lastMessagePreview', 'liveRunState', 'blockedReason', 'statusUpdatedAt']);
181
+ const exact = requireShapedRecord(value, 'shared Session catalog projection', ['kind', 'id', 'revision', 'createdAt', 'activityAt', 'name', 'status'], [
182
+ 'lastMessageAt',
183
+ 'lastMessagePreview',
184
+ 'liveRunState',
185
+ 'backgroundActivity',
186
+ 'backgroundActivityVersion',
187
+ 'blockedReason',
188
+ 'statusUpdatedAt',
189
+ ]);
180
190
  if (exact.kind !== 'shared_session')
181
191
  throw invalidProtocolFrame('Invalid shared Session kind');
182
192
  return {
@@ -190,6 +200,7 @@ export function decodeSharedSessionCatalogProjection(value) {
190
200
  ...optionalText(exact, 'lastMessagePreview', SESSION_CATALOG_PREVIEW_MAX_BYTES),
191
201
  status: decodeSessionStatus(exact.status),
192
202
  ...optionalLiveRunState(exact),
203
+ ...optionalBackgroundActivity(exact),
193
204
  ...optionalBlockedReason(exact),
194
205
  ...optionalTimestamp(exact, 'statusUpdatedAt'),
195
206
  };
@@ -518,6 +529,7 @@ export function decodeSessionCatalogProjection(value) {
518
529
  ...optionalText(record, 'lastMessagePreview', SESSION_CATALOG_PREVIEW_MAX_BYTES),
519
530
  status: decodeSessionStatus(record.status),
520
531
  ...optionalLiveRunState(record),
532
+ ...optionalBackgroundActivity(record),
521
533
  ...optionalBlockedReason(record),
522
534
  ...optionalTimestamp(record, 'statusUpdatedAt'),
523
535
  ...optionalEntityId(record, 'parentSessionId'),
@@ -640,6 +652,29 @@ function optionalBlockedReason(record) {
640
652
  }
641
653
  return { blockedReason: record.blockedReason };
642
654
  }
655
+ function optionalBackgroundActivity(record) {
656
+ const activity = record.backgroundActivity;
657
+ if (activity === undefined) {
658
+ if (record.backgroundActivityVersion !== undefined)
659
+ throw invalidProtocolFrame('Session background activity version requires activity');
660
+ return {};
661
+ }
662
+ if (activity !== 'idle' &&
663
+ activity !== 'running' &&
664
+ activity !== 'waiting_for_user' &&
665
+ activity !== 'blocked')
666
+ throw invalidProtocolFrame('Invalid Session background activity');
667
+ if (record.backgroundActivityVersion === undefined)
668
+ return { backgroundActivity: activity };
669
+ const version = requireExactRecord(record.backgroundActivityVersion, 'Session background activity version', ['hostGeneration', 'revision']);
670
+ return {
671
+ backgroundActivity: activity,
672
+ backgroundActivityVersion: {
673
+ hostGeneration: decodeHostGeneration(version.hostGeneration),
674
+ revision: requireCount(version.revision, 'Session background activity revision'),
675
+ },
676
+ };
677
+ }
643
678
  function optionalLiveRunState(record) {
644
679
  if (record.liveRunState === undefined)
645
680
  return {};
@@ -671,13 +706,6 @@ function optionalLiveRunState(record) {
671
706
  liveRunState.runEpoch < 0)) {
672
707
  throw invalidProtocolFrame('Invalid Session catalog run epoch');
673
708
  }
674
- if (liveRunState.hostGeneration !== undefined &&
675
- (typeof liveRunState.hostGeneration !== 'string' ||
676
- liveRunState.hostGeneration.length === 0 ||
677
- liveRunState.hostGeneration.length > SESSION_CATALOG_HOST_GENERATION_MAX_CHARS ||
678
- /[\u0000-\u001f\u007f]/.test(liveRunState.hostGeneration))) {
679
- throw invalidProtocolFrame('Invalid Session catalog host generation');
680
- }
681
709
  return {
682
710
  liveRunState: {
683
711
  schemaVersion: SESSION_CATALOG_LIVE_RUN_STATE_SCHEMA_VERSION,
@@ -685,10 +713,19 @@ function optionalLiveRunState(record) {
685
713
  ...(liveRunState.runEpoch === undefined ? {} : { runEpoch: liveRunState.runEpoch }),
686
714
  ...(liveRunState.hostGeneration === undefined
687
715
  ? {}
688
- : { hostGeneration: liveRunState.hostGeneration }),
716
+ : { hostGeneration: decodeHostGeneration(liveRunState.hostGeneration) }),
689
717
  },
690
718
  };
691
719
  }
720
+ function decodeHostGeneration(value) {
721
+ if (typeof value !== 'string' ||
722
+ value.length === 0 ||
723
+ value.length > SESSION_CATALOG_HOST_GENERATION_MAX_CHARS ||
724
+ /[\u0000-\u001f\u007f]/.test(value)) {
725
+ throw invalidProtocolFrame('Invalid Session catalog host generation');
726
+ }
727
+ return value;
728
+ }
692
729
  function optionalSubagent(record) {
693
730
  if (!Object.hasOwn(record, 'subagent'))
694
731
  return {};
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { TOOL_ACTIVITY_KINDS, TOOL_OUTPUT_DELTA_MAX_CHARS } from '@maka/core/events';
20
20
  import { decodeToolResultPreviewContent } from '@maka/core/tool-result-preview';
21
- import { assertExactKeys, requireCount, requireEntityId, requireExactRecord, requireId, requireOpaqueIdentity, requireRecord, } from './codec.js';
21
+ import { assertExactKeys, requireCount, requireEncodedByteLimit, requireEntityId, requireExactRecord, requireId, requireOpaqueIdentity, requireRecord, } from './codec.js';
22
22
  import { invalidProtocolFrame } from './errors.js';
23
23
  import { decodeSessionStatus } from './session-status.js';
24
24
  import { decodeSessionInteractionProjection, } from './interaction.js';
@@ -773,18 +773,6 @@ function requireUtf8BoundedString(value, label, maxBytes) {
773
773
  }
774
774
  return value;
775
775
  }
776
- function requireEncodedByteLimit(value, label, maxBytes) {
777
- let encoded;
778
- try {
779
- encoded = JSON.stringify(value);
780
- }
781
- catch {
782
- throw invalidProtocolFrame(`Invalid ${label}`);
783
- }
784
- if (encoded === undefined || Buffer.byteLength(encoded, 'utf8') > maxBytes) {
785
- throw invalidProtocolFrame(`Invalid ${label}`);
786
- }
787
- }
788
776
  /**
789
777
  * Read against the one list rather than a copy of it.
790
778
  *
@@ -22,7 +22,7 @@ import { isOrchestrationMode, isTurnOrchestrationSource, } from '@maka/core/orch
22
22
  import { decodeSkillInvocationResult, } from '@maka/core/skill-invocation';
23
23
  import { decodeTurnOrigin } from '@maka/core/turn-origin';
24
24
  import { invalidProtocolFrame } from './errors.js';
25
- import { assertExactKeys, requireCount, requireEntityId, requireExactRecord, requireShapedRecord, requireId, requireRecord, requireString, } from './codec.js';
25
+ import { assertExactKeys, requireCount, requireEncodedByteLimit, requireEntityId, requireExactRecord, requireShapedRecord, requireId, requireRecord, requireString, } from './codec.js';
26
26
  import { defineOperation } from './operation-spec.js';
27
27
  import { decodeTurnProviderRetry } from './turn-provider-retry.js';
28
28
  export { decodeTurnProviderRetry } from './turn-provider-retry.js';
@@ -281,18 +281,6 @@ function requireUtf8String(value, label, maxBytes, allowEmpty) {
281
281
  }
282
282
  return value;
283
283
  }
284
- function requireEncodedByteLimit(value, label, maxBytes) {
285
- let encoded;
286
- try {
287
- encoded = JSON.stringify(value);
288
- }
289
- catch {
290
- throw invalidProtocolFrame(`Invalid ${label}`);
291
- }
292
- if (encoded === undefined || Buffer.byteLength(encoded, 'utf8') > maxBytes) {
293
- throw invalidProtocolFrame(`Invalid ${label}`);
294
- }
295
- }
296
284
  function decodeTurnQueryInput(value) {
297
285
  const record = requireExactRecord(value, 'turn.query input', ['sessionId', 'turnId']);
298
286
  return {
@@ -190,13 +190,7 @@ export function decodeWorkHubCoordinationCandidatesResult(value) {
190
190
  };
191
191
  }
192
192
  export function decodeWorkHubCoordinationSelectAndDelegateInput(value) {
193
- const input = requireExactRecord(value, 'WorkHub selection input', [
194
- 'turnId',
195
- 'actionId',
196
- 'candidateSetId',
197
- 'candidateRefs',
198
- 'delegationText',
199
- ]);
193
+ const input = requireShapedRecord(value, 'WorkHub selection input', ['turnId', 'actionId', 'candidateSetId', 'candidateRefs', 'delegationText'], ['create']);
200
194
  if (!Array.isArray(input.candidateRefs) ||
201
195
  input.candidateRefs.length < 1 ||
202
196
  input.candidateRefs.length > WORKHUB_COORDINATION_CANDIDATE_MAX_ITEMS) {
@@ -206,6 +200,9 @@ export function decodeWorkHubCoordinationSelectAndDelegateInput(value) {
206
200
  if (new Set(candidateRefs).size !== candidateRefs.length)
207
201
  throw invalidProtocolFrame('Duplicate WorkHub selection candidates');
208
202
  return {
203
+ ...(input.create !== undefined
204
+ ? { create: decodeWorkHubCoordinationCreateContext(input.create) }
205
+ : {}),
209
206
  turnId: requireEntityId(input.turnId, 'WorkHub Coordination Turn id'),
210
207
  actionId: requireEntityId(input.actionId, 'WorkHub action id'),
211
208
  candidateSetId: requireUtf8String(input.candidateSetId, 'WorkHub candidate set', 256),