maka-agent 0.2.0-dev.25.20260908 → 0.2.0-dev.26.20260909

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 (65) hide show
  1. package/dist/mcp-capability-provider.js +4 -1
  2. package/dist/pi-tui-mcp-status.js +2 -2
  3. package/dist/pi-tui-pickers.js +19 -29
  4. package/dist/tui-autocomplete-layout.js +3 -1
  5. package/dist/tui-copy-catalog.js +6 -3
  6. package/dist/tui-editor-render.js +36 -0
  7. package/dist/tui-mcp-control.js +41 -6
  8. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  9. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  10. package/node_modules/@maka/core/dist/session.js +29 -4
  11. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  12. package/node_modules/@maka/core/package.json +2 -3
  13. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  14. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  15. package/node_modules/@maka/mcp/dist/index.js +337 -71
  16. package/node_modules/@maka/runtime/dist/agent-run.js +10 -1
  17. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  18. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +20 -1
  19. package/node_modules/@maka/runtime/dist/runtime-kernel.js +120 -13
  20. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  21. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  22. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +17 -1
  23. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  24. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  25. package/node_modules/@maka/runtime-host/dist/protocol/index.js +5 -1
  26. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  27. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  28. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +68 -168
  29. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  30. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  31. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  32. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  33. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +45 -20
  34. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  35. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  36. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  37. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  38. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  39. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  40. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +4 -1
  41. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  42. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  43. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  44. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  45. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  46. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  47. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  48. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  49. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -85
  50. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +120 -137
  51. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  52. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  53. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  54. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  55. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  56. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  57. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  58. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  59. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  60. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  61. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  62. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  63. package/package.json +1 -1
  64. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  65. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -530,6 +530,111 @@ export class RuntimeKernel {
530
530
  runId: continuation.runId,
531
531
  }, options, () => this.revalidateContinuationSafety(continuation), inheritedSandboxBoundaryDenied);
532
532
  }
533
+ /** Host coordination uses the same Run owner and terminal authority without a provider send. */
534
+ async *runCoordinationOperation(sessionId, input, options, execute) {
535
+ const execution = this.takeExecutionClaim(sessionId);
536
+ execution.hostOperation = true;
537
+ try {
538
+ await this.enterExecutionClaim(execution);
539
+ const header = await this.deps.store.readHeader(sessionId);
540
+ const run = new AgentRun({
541
+ sessionId,
542
+ header,
543
+ userInput: input,
544
+ runId: options.runId,
545
+ userMessageId: options.userMessageId,
546
+ durability: 'required',
547
+ runStore: this.deps.runStore,
548
+ runtimeEventStore: this.deps.runtimeEventStore,
549
+ newId: this.deps.newId,
550
+ now: this.deps.now,
551
+ effectiveOrchestration: resolveEffectiveOrchestration('default', undefined),
552
+ hooks: {
553
+ reserveRun: async (id, nextHeader, activeRun) => {
554
+ const active = await this.reserveParentRun(id, nextHeader, activeRun, execution);
555
+ this.reserveExecutionClaim(execution, active, activeRun);
556
+ return active;
557
+ },
558
+ unregisterRun: (active, activeRun) => this.unregisterParentRun(active, activeRun),
559
+ updateHeader: (id, patch) => this.updateHeader(id, patch),
560
+ updateStatus: (id, status, reason, ts) => this.updateStatus(id, status, reason, ts),
561
+ ...this.messageProjectionHook(),
562
+ },
563
+ });
564
+ this.attachExecutionClaim(execution, run);
565
+ const owners = this.createRunOwnerScope(run, execution);
566
+ try {
567
+ owners.bindMessage(this.deps.messageAuthority, {
568
+ sessionId,
569
+ turnId: input.turnId,
570
+ runId: run.runId,
571
+ });
572
+ // Keep the execution claim attached until finalization. Stop/drain can
573
+ // therefore cancel and await this Run without a provider generation.
574
+ await run.beginCoordination();
575
+ await options.onRunStarted?.(run.runId, header);
576
+ }
577
+ catch (error) {
578
+ await this.finalizeFailedRunStart(owners, run, execution, error);
579
+ return;
580
+ }
581
+ try {
582
+ if (run.isStopped())
583
+ return;
584
+ const executed = await execute();
585
+ const receipt = {
586
+ ...executed,
587
+ result: executed.result.disposition === 'clarify'
588
+ ? { ...executed.result, coordinationTurnId: input.turnId }
589
+ : executed.result,
590
+ };
591
+ const receiptEvent = {
592
+ id: this.deps.newId(),
593
+ sessionId,
594
+ turnId: input.turnId,
595
+ runId: run.runId,
596
+ invocationId: run.runId,
597
+ ts: this.deps.now(),
598
+ partial: false,
599
+ role: 'system',
600
+ author: 'host',
601
+ modelVisibility: 'hidden',
602
+ actions: { coordination: receipt },
603
+ };
604
+ await run.recordRuntimeEvents([receiptEvent], { requireDurableWrite: true });
605
+ if (run.isStopped())
606
+ return;
607
+ const complete = {
608
+ type: 'complete',
609
+ id: this.deps.newId(),
610
+ turnId: input.turnId,
611
+ ts: this.deps.now(),
612
+ stopReason: 'end_turn',
613
+ };
614
+ await run.acceptMappedEvent(complete, mapSessionEventToRuntimeEvent(complete, this.runtimeEventMapContext({
615
+ sessionId,
616
+ invocationId: run.runId,
617
+ runId: run.runId,
618
+ turnId: input.turnId,
619
+ })), { requireTerminalWrite: true });
620
+ yield complete;
621
+ }
622
+ catch (error) {
623
+ await run.recordFailure(error);
624
+ throw error;
625
+ }
626
+ finally {
627
+ const failures = new FailureCollector();
628
+ await failures.capture(() => owners.finalize());
629
+ await failures.capture(() => owners.releaseMessage());
630
+ failures.throwIfAny(`Coordination cleanup failed for ${run.runId}`);
631
+ }
632
+ }
633
+ finally {
634
+ this.releaseExecutionClaim(execution);
635
+ await this.flushBackendInvalidation(sessionId);
636
+ }
637
+ }
533
638
  async *compactSession(sessionId, input = {}) {
534
639
  const execution = this.takeExecutionClaim(sessionId);
535
640
  try {
@@ -1455,24 +1560,26 @@ export class RuntimeKernel {
1455
1560
  const generations = this.backendGenerationsFor(sessionId);
1456
1561
  await Promise.all(generations.map((active) => active.backend.respondToUserQuestion?.(response)));
1457
1562
  }
1563
+ activeRunsFor(sessionId) {
1564
+ const runs = new Set();
1565
+ for (const active of this.backendGenerationsFor(sessionId)) {
1566
+ for (const run of active.activeRuns.values())
1567
+ runs.add(run);
1568
+ }
1569
+ for (const claim of this.executionClaims.get(sessionId) ?? []) {
1570
+ if (claim.hostOperation && claim.run)
1571
+ runs.add(claim.run);
1572
+ }
1573
+ return [...runs];
1574
+ }
1458
1575
  hasActiveRuns(sessionId) {
1459
- return this.backendGenerationsFor(sessionId).some((active) => active.activeRuns.size > 0);
1576
+ return this.activeRunsFor(sessionId).length > 0;
1460
1577
  }
1461
1578
  runningTurnIds(sessionId) {
1462
- const turnIds = [];
1463
- for (const active of this.backendGenerationsFor(sessionId)) {
1464
- for (const run of active.activeRuns.values()) {
1465
- if (!turnIds.includes(run.turnId))
1466
- turnIds.push(run.turnId);
1467
- }
1468
- }
1469
- return turnIds;
1579
+ return [...new Set(this.activeRunsFor(sessionId).map((run) => run.turnId))];
1470
1580
  }
1471
1581
  hasActiveRun(sessionId, runId, turnId) {
1472
- return this.backendGenerationsFor(sessionId).some((active) => {
1473
- const run = active.activeRuns.get(runId);
1474
- return run !== undefined && (turnId === undefined || run.turnId === turnId);
1475
- });
1582
+ return this.activeRunsFor(sessionId).some((run) => run.runId === runId && (turnId === undefined || run.turnId === turnId));
1476
1583
  }
1477
1584
  requestRunHandoff(sessionId, runId, pause, signal) {
1478
1585
  if (!this.deps.inspectContinuationSafety)
@@ -16,17 +16,6 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- /**
20
- * SessionManager — the public Runtime API.
21
- *
22
- * Ties together:
23
- * SessionStore (storage) — SQLite persistence
24
- * AgentBackend (AiSdkBackend etc) — SDK adapter
25
- * ExecutionBoundary — session sandbox authority
26
- *
27
- * `SessionStore` comes from `@maka/storage`; its public interface owns
28
- * persistence and same-session serialization semantics.
29
- */
30
19
  import { createHash } from 'node:crypto';
31
20
  import { isDeepStrictEqual } from 'node:util';
32
21
  import { setTimeout as delay } from 'node:timers/promises';
@@ -1280,6 +1269,9 @@ export class SessionManager {
1280
1269
  // Operational telemetry must never alter resume correctness.
1281
1270
  }
1282
1271
  }
1272
+ runCoordinationOperation(sessionId, input, options, execute) {
1273
+ return this.runtimeKernel.runCoordinationOperation(sessionId, input, options, execute);
1274
+ }
1283
1275
  async *compactSession(sessionId, input = {}) {
1284
1276
  yield* this.runtimeKernel.compactSession(sessionId, input);
1285
1277
  }
@@ -2314,43 +2306,30 @@ export class SessionManager {
2314
2306
  const hostedAuthority = isRuntimeHostedRootAuthority(this.deps.messageAuthority)
2315
2307
  ? this.deps.messageAuthority
2316
2308
  : undefined;
2317
- const ownStop = hostedAuthority
2309
+ await this.#stopSessionTree(sessionId, hostedAuthority
2318
2310
  ? hostedAuthority.stopSession(sessionId, input)
2319
- : this.runtimeKernel.stopSession(sessionId, input);
2320
- let childStops = [];
2321
- let childLookupError;
2322
- try {
2323
- const children = await this.listChildSessions(sessionId);
2324
- childStops = await Promise.allSettled(children
2325
- .filter((child) => child.subagentParent?.lifecycle === 'foreground')
2326
- .map((child) => hostedAuthority
2327
- ? hostedAuthority.stopSession(child.id, input)
2328
- : this.runtimeKernel.stopSession(child.id, input)));
2329
- }
2330
- catch (error) {
2331
- childLookupError = error;
2332
- }
2333
- await ownStop;
2334
- const childStopError = childStops.find((result) => result.status === 'rejected')?.reason;
2335
- if (childLookupError !== undefined)
2336
- throw childLookupError;
2337
- if (childStopError !== undefined)
2338
- throw childStopError;
2311
+ : this.runtimeKernel.stopSession(sessionId, input), (childSessionId) => hostedAuthority
2312
+ ? hostedAuthority.stopSession(childSessionId, input)
2313
+ : this.runtimeKernel.stopSession(childSessionId, input));
2339
2314
  }
2340
2315
  async deliverHostedRootStop(sessionId, input = {}) {
2341
- const ownStop = this.runtimeKernel.stopSession(sessionId, input);
2342
2316
  const authority = isRuntimeHostedRootAuthority(this.deps.messageAuthority)
2343
2317
  ? this.deps.messageAuthority
2344
2318
  : undefined;
2319
+ await this.#stopSessionTree(sessionId, this.runtimeKernel.stopSession(sessionId, input), (childSessionId) => authority
2320
+ ? authority.stopSession(childSessionId, input)
2321
+ : this.runtimeKernel.stopSession(childSessionId, input));
2322
+ }
2323
+ async #stopSessionTree(sessionId, ownStop, stopChild) {
2324
+ // Observe immediately while child lookup runs; await below still propagates the original error.
2325
+ void ownStop.catch(() => undefined);
2345
2326
  let childStops = [];
2346
2327
  let childLookupError;
2347
2328
  try {
2348
2329
  const children = await this.listChildSessions(sessionId);
2349
2330
  childStops = await Promise.allSettled(children
2350
2331
  .filter((child) => child.subagentParent?.lifecycle === 'foreground')
2351
- .map((child) => authority
2352
- ? authority.stopSession(child.id, input)
2353
- : this.runtimeKernel.stopSession(child.id, input)));
2332
+ .map((child) => stopChild(child.id)));
2354
2333
  }
2355
2334
  catch (error) {
2356
2335
  childLookupError = error;
@@ -2385,6 +2364,11 @@ export class SessionManager {
2385
2364
  goalId: input.execution.goalId,
2386
2365
  };
2387
2366
  }
2367
+ else if (input.execution.kind === 'workhub_coordination' &&
2368
+ input.execution.operation === 'action') {
2369
+ recoveryReason = 'coordination_action_admission_without_run';
2370
+ diagnostic = { executionKind: input.execution.kind, operation: input.execution.operation };
2371
+ }
2388
2372
  else if (input.execution.kind === 'legacy_automation') {
2389
2373
  root = { kind: 'legacy_automation', legacyAutomationId: input.execution.automationId };
2390
2374
  recoveryReason = 'legacy_automation_authority_removed';
@@ -78,6 +78,12 @@ export class RuntimeHostRequestInterruptedError extends Error {
78
78
  this.retryable = mode === 'query';
79
79
  }
80
80
  }
81
+ // A Host response can reach the Client before the Host's transport write
82
+ // promise resumes and retires that request. Leave one slot free so replacing
83
+ // the observed response cannot transiently cross the Host's hard limit. The
84
+ // Host serializes outbound writes, so at most one response occupies this
85
+ // acknowledgement window.
86
+ const CLIENT_MAX_IN_FLIGHT_DOMAIN_REQUESTS = RUNTIME_HOST_MAX_IN_FLIGHT_DOMAIN_REQUESTS - 1;
81
87
  class RuntimeHostConnectionImpl {
82
88
  cooperativeHandoff;
83
89
  rootId;
@@ -212,7 +218,7 @@ class RuntimeHostConnectionImpl {
212
218
  }
213
219
  #drainDomainRequests() {
214
220
  while (!this.#terminalError &&
215
- this.#inFlightDomainRequests < RUNTIME_HOST_MAX_IN_FLIGHT_DOMAIN_REQUESTS) {
221
+ this.#inFlightDomainRequests < CLIENT_MAX_IN_FLIGHT_DOMAIN_REQUESTS) {
216
222
  const queued = this.#queuedDomainFrames.shift();
217
223
  if (!queued)
218
224
  return;
@@ -243,9 +249,28 @@ class RuntimeHostConnectionImpl {
243
249
  }
244
250
  return status;
245
251
  }
246
- openSessionSubscription(input, timeoutMs) {
252
+ async openSessionSubscription(input, timeoutMs) {
253
+ const deadline = Date.now() + (timeoutMs === undefined ? 30_000 : requireTimeout(timeoutMs, 'timeoutMs'));
254
+ for (;;) {
255
+ try {
256
+ return await this.#openSessionSubscription(input, Math.max(1, deadline - Date.now()), timeoutMs);
257
+ }
258
+ catch (error) {
259
+ if (!(error instanceof RuntimeHostOperationError) ||
260
+ error.code !== 'transcript_preparing' ||
261
+ input.transcript.kind !== 'tail' ||
262
+ this.#terminalError ||
263
+ Date.now() >= deadline)
264
+ throw error;
265
+ // Each refusal committed a bounded, resumable index batch. Keep the
266
+ // caller in its loading state and yield before requesting more work.
267
+ await new Promise((resolve) => setTimeout(resolve, 25));
268
+ }
269
+ }
270
+ }
271
+ #openSessionSubscription(input, openTimeoutMs, requestTimeoutMs) {
247
272
  const expectedSessionId = input.sessionId;
248
- return this.#requestOperation('subscription.open', input, timeoutMs, (result) => {
273
+ return this.#requestOperation('subscription.open', input, openTimeoutMs, (result) => {
249
274
  if (result.hostEpoch !== this.hostEpoch) {
250
275
  throw new RuntimeHostSubscriptionError('host_epoch_changed', 'Session subscription opened for a different Host Epoch');
251
276
  }
@@ -255,9 +280,9 @@ class RuntimeHostConnectionImpl {
255
280
  if (this.#subscriptions.has(result.subscriptionId)) {
256
281
  throw new RuntimeHostSubscriptionError('correlation_changed', 'Runtime Host returned a duplicate subscription identity');
257
282
  }
258
- const subscription = new ClientSessionSubscription(result, () => this.#closeSessionSubscription(result.subscriptionId), (query) => this.request('session.transcript.page', query, timeoutMs), async () => {
283
+ const subscription = new ClientSessionSubscription(result, () => this.#closeSessionSubscription(result.subscriptionId), (query) => this.request('session.transcript.page', query, requestTimeoutMs), async () => {
259
284
  try {
260
- await this.request('session.transcript.overlay.release', { subscriptionId: result.subscriptionId }, timeoutMs);
285
+ await this.request('session.transcript.overlay.release', { subscriptionId: result.subscriptionId }, requestTimeoutMs);
261
286
  }
262
287
  catch (error) {
263
288
  this.#fail(asError(error));
@@ -90,6 +90,20 @@ export function formatHostHandoff(view, locale) {
90
90
  descriptions.operator_required = guidance[view.recoveryBlocker];
91
91
  }
92
92
  const activity = view.activity;
93
+ const background = activity?.drainResidencies;
94
+ const backgroundFacts = !activity
95
+ ? ''
96
+ : background !== undefined
97
+ ? tw
98
+ ? `${background} 個背景工作`
99
+ : zh
100
+ ? `${background} 个后台工作`
101
+ : `${background} background activities`
102
+ : tw
103
+ ? '背景工作數量未知'
104
+ : zh
105
+ ? '后台工作数量未知'
106
+ : 'Background activity count unknown';
93
107
  const facts = activity
94
108
  ? tw
95
109
  ? `${activity.connections} 個連線 · ${activity.activeOperations} 個進行中的操作`
@@ -173,7 +187,9 @@ export function formatHostHandoff(view, locale) {
173
187
  : zh
174
188
  ? '正在完成交接或安全收尾,请稍候。'
175
189
  : 'Finishing the handoff or its safe recovery. Please wait.'
176
- : [facts, waiting, repairNotice, view.operatorStep].filter(Boolean).join('\n'),
190
+ : [facts, backgroundFacts, waiting, repairNotice, view.operatorStep]
191
+ .filter(Boolean)
192
+ .join('\n'),
177
193
  actions: view.actions.map((action) => ({ action, label: labels[action] })),
178
194
  };
179
195
  }
@@ -23,7 +23,7 @@
23
23
  * transport detail here so lifecycle owners can model the operation as
24
24
  * retirement instead of spreading update-specific authority.
25
25
  */
26
- export async function prepareConnectedRuntimeHostRetirement(connection, mode, timeoutMs, signal) {
26
+ export async function prepareConnectedRuntimeHostRetirement(connection, mode, timeoutMs, signal, options) {
27
27
  signal?.throwIfAborted();
28
28
  // Retirement uses a dedicated lifecycle connection. Closing it cancels the
29
29
  // Host's reversible preparation; a committed retirement remains committed.
@@ -35,7 +35,9 @@ export async function prepareConnectedRuntimeHostRetirement(connection, mode, ti
35
35
  return await connection.request('host.upgrade.prepare', {
36
36
  expectedHostEpoch: connection.hostEpoch,
37
37
  allowInterruptActiveTasks: mode === 'interrupt_active_work',
38
- ...(mode === 'refuse_active_work' && connection.cooperativeHandoff
38
+ ...(mode === 'refuse_active_work' &&
39
+ connection.cooperativeHandoff &&
40
+ options?.allowCooperativeHandoff !== false
39
41
  ? { allowCooperativeHandoff: true }
40
42
  : {}),
41
43
  }, timeoutMs);
@@ -149,6 +149,23 @@ class RuntimeHostPeerClientImpl {
149
149
  };
150
150
  }
151
151
  subscribeRoutes(peerId, listener) {
152
+ const snapshot = () => this.#connectionResolution({ peerId, routeHints: [], directDeadlineMs: 0 }, 'application', false);
153
+ let previous = snapshot();
154
+ let connected = this.isConnected(peerId);
155
+ return this.#subscribeRouteResolution(peerId, () => {
156
+ const next = snapshot();
157
+ const nextConnected = this.isConnected(peerId);
158
+ const available = next.state === 'available' &&
159
+ (!sameCandidates(previous, next) || (nextConnected && !connected));
160
+ previous = next;
161
+ connected = nextConnected;
162
+ // A dial's own recovery sweep toggles recovering/exhausted. Waking its
163
+ // reconnect owner for those transitions would bypass every backoff.
164
+ if (available)
165
+ listener();
166
+ });
167
+ }
168
+ #subscribeRouteResolution(peerId, listener) {
152
169
  const listeners = this.#routeListeners.get(peerId) ?? new Set();
153
170
  const first = listeners.size === 0;
154
171
  listeners.add(listener);
@@ -362,7 +379,7 @@ class RuntimeHostPeerClientImpl {
362
379
  updateTail = updateTail.then(() => updatePeerConnect(endpoint, requestId, candidates, () => settled), () => updatePeerConnect(endpoint, requestId, candidates, () => settled));
363
380
  void updateTail.catch(() => undefined);
364
381
  };
365
- const unsubscribe = kind === 'application' ? this.subscribeRoutes(input.peerId, update) : undefined;
382
+ const unsubscribe = kind === 'application' ? this.#subscribeRouteResolution(input.peerId, update) : undefined;
366
383
  let connection;
367
384
  try {
368
385
  connection = endpoint[kind === 'application' ? 'connect' : 'connectMeshControl']({
@@ -62,7 +62,11 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
62
62
  export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
63
63
  // Increment when the same protocol version no longer guarantees safe Client-Host
64
64
  // interoperability. Mismatches are rejected before domain commands are admitted.
65
- export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 133;
65
+ export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 135;
66
+ // 135: WorkHub model Turns replace direct action proposals with active-Turn task tools.
67
+ // 134: Coordination actions own real Runtime Turns. Removes the synthetic record
68
+ // operation, projects typed action receipts and admitted action identities, and
69
+ // distinguishes stale candidate refusals and resumable transcript preparation.
66
70
  // 133: WorkHub actions carry attachments and new-Work model/permission defaults.
67
71
  // Epoch-132 peers reject these additional fields on strict action shapes.
68
72
  // 132: new Tool Result archives use versioned ledger references, not Artifact payloads.
@@ -218,9 +218,10 @@ export const REMOTE_OWNER_OPERATION_GRANTS = Object.freeze([
218
218
  'usage.query',
219
219
  'web-search.execute',
220
220
  'workhub.coordination.answer',
221
- 'workhub.coordination.act',
221
+ 'workhub.coordination.actFromTurn',
222
222
  'workhub.coordination.candidates',
223
- 'workhub.coordination.record',
223
+ 'workhub.coordination.configureModel',
224
+ 'workhub.coordination.query',
224
225
  'workhub.coordination.resolve',
225
226
  ]);
226
227
  const REMOTE_OWNER_OPERATION_GRANT_SET = new Set(REMOTE_OWNER_OPERATION_GRANTS);
@@ -56,6 +56,7 @@ export const SESSION_DOMAINS = [
56
56
  ];
57
57
  export const SESSION_RUNTIME_RESOURCE_CHANGES_MAX = 64;
58
58
  const SUBSCRIPTION_OPEN_ERRORS = [
59
+ 'transcript_preparing',
59
60
  'host_not_ready',
60
61
  'host_draining',
61
62
  'operation_unavailable',