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
@@ -16,14 +16,13 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { truncateUtf8 } from '@maka/core/diagnostic-log';
20
- import { redactSecrets } from '@maka/core/redaction';
21
19
  import { HOST_OPERATION_SPECS, decodeOperationOutcome, } from '../protocol/index.js';
22
20
  import { HOST_BOOTSTRAP_OPERATION_SPECS } from '../protocol/host-status.js';
23
21
  import { HOST_RESOURCE_OPERATION_SPECS } from '../protocol/host-resources.js';
24
22
  import { ACCESS_AUTHORITY_OPERATION_SPECS } from '../protocol/access-authority.js';
25
23
  import { SESSION_COLLABORATION_OPERATION_SPECS } from '../protocol/session-collaboration.js';
26
24
  import { PEER_MESH_OPERATION_SPECS } from '../protocol/peer-mesh.js';
25
+ import { boundedFailureDiagnostic } from './failure-diagnostic.js';
27
26
  import { createPeerMeshOperationHandlers } from './peer-mesh-authority.js';
28
27
  /**
29
28
  * The spec objects a Runtime Host serves from its own core — operations that
@@ -144,7 +143,7 @@ async function dispatchTypedOperation(request, handlers, context) {
144
143
  outcome = decodeOperationOutcome(request.operation, await handler(request.input, context));
145
144
  }
146
145
  catch (error) {
147
- console.error(`[runtime-host] unexpected ${request.operation} failure: ${boundedUnexpectedFailure(error)}`);
146
+ console.error(`[runtime-host] unexpected ${request.operation} failure: ${boundedFailureDiagnostic(error)}`);
148
147
  return operationFailureResponse(request, 'internal_failure', 'Runtime Host operation failed');
149
148
  }
150
149
  return outcome.ok
@@ -161,7 +160,3 @@ async function dispatchTypedOperation(request, handlers, context) {
161
160
  error: outcome.error,
162
161
  };
163
162
  }
164
- function boundedUnexpectedFailure(error) {
165
- const details = error instanceof Error ? error.stack || `${error.name}: ${error.message}` : String(error);
166
- return truncateUtf8(redactSecrets(details), 8 * 1024, '\n<diagnostic truncated>');
167
- }
@@ -21,7 +21,7 @@ import { isDeepStrictEqual } from 'node:util';
21
21
  import { readLogicalRuntimeExecution, readLogicalRuntimeExecutionForRun, } from '@maka/core/runtime-logical-execution';
22
22
  import { runtimeHandoffPause } from '@maka/core/runtime-handoff';
23
23
  import { INLINE_REFERENCE_MAX_COUNT, messageContentDigest, messageContentsEqual, normalizeMessageContent, } from '@maka/core/events';
24
- import { isWorkHubCoordinationSessionId } from '@maka/core/session';
24
+ import { WORKHUB_COORDINATION_SESSION_ID, isWorkHubCoordinationSessionId, } from '@maka/core/session';
25
25
  import { resolveEffectiveOrchestration } from '@maka/core/orchestration';
26
26
  import { decodeSkillInvocationResult, } from '@maka/core/skill-invocation';
27
27
  import { agentGraphIdForRootSession } from '@maka/runtime/stream-graph-coordinator';
@@ -222,6 +222,10 @@ export class RootTurnCoordinator {
222
222
  throw error;
223
223
  }
224
224
  }
225
+ /** Called under Session admission before changing an execution's authority ceiling. */
226
+ isSessionExecutionIdle(sessionId) {
227
+ return (!this.#recoveryPlansBySession.has(sessionId) && this.readRootState(sessionId).kind === 'idle');
228
+ }
225
229
  readRootState(sessionId) {
226
230
  const active = this.#executions.get(sessionId);
227
231
  if (active) {
@@ -565,7 +569,10 @@ export class RootTurnCoordinator {
565
569
  if (isWorkHubCoordinationSessionId(input.sessionId)) {
566
570
  return Promise.reject(new RuntimeHostedRootUnavailableError(input.sessionId, WORKHUB_COORDINATION_EXECUTION_UNAVAILABLE_REASON));
567
571
  }
568
- return this.runCommand(async () => {
572
+ // Admission is active work even while queued behind a Session gate. Its
573
+ // residency overlaps the execution residency until admission settles.
574
+ const residency = this.acquireRecoveryResidency();
575
+ const admission = this.runCommand(async () => {
569
576
  const activeAtEntry = this.#executions.has(input.sessionId);
570
577
  let reservation = preparedReservation ?? (activeAtEntry ? undefined : this.reserveRootTurn(input.sessionId));
571
578
  if (preparedReservation && this.#admissions.get(input.sessionId) !== preparedReservation) {
@@ -674,6 +681,7 @@ export class RootTurnCoordinator {
674
681
  throw error.cause;
675
682
  throw error;
676
683
  });
684
+ return admission.finally(() => residency.release());
677
685
  }
678
686
  lookup(sessionId, turnId) {
679
687
  return this.runCommand(async () => {
@@ -1090,15 +1098,92 @@ export class RootTurnCoordinator {
1090
1098
  if (!isWorkHubCoordinationSessionId(request.sessionId)) {
1091
1099
  return Promise.resolve(operationUnavailable('WorkHub Coordination execution requires its reserved Session'));
1092
1100
  }
1101
+ if ((request.execution.operation === 'action') !== (request.operation !== undefined)) {
1102
+ return Promise.resolve(operationUnavailable('Coordination execution mode does not match its runner'));
1103
+ }
1093
1104
  return this.startRootMessage(request, context);
1094
1105
  }
1095
- /**
1096
- * Whether a durable root Turn already owns this identity. WorkHub also writes
1097
- * Coordination Turns outside this coordinator, and must not append a second
1098
- * triplet into a Turn this admission ledger already owns.
1099
- */
1100
- async hasRootTurnAdmission(sessionId, turnId) {
1101
- return (await this.stores.agentRunStore.readRootTurnAdmission(sessionId, turnId)) !== undefined;
1106
+ async runWorkHubCoordinationOperation(request, context) {
1107
+ if (request.execution.operation !== 'action' || !request.operation) {
1108
+ return operationUnavailable('Coordination action requires a Host operation');
1109
+ }
1110
+ let turnId = request.turnId;
1111
+ let recoveredReceipt;
1112
+ // A retry preserves the action identity, but never reopens a terminal Run.
1113
+ // The existing admission chain and terminal facts identify prior attempts.
1114
+ for (;;) {
1115
+ const admission = await this.stores.agentRunStore.readRootTurnAdmission(request.sessionId, turnId);
1116
+ if (!admission)
1117
+ break;
1118
+ if (!rootExecutionMatches(admission.execution, request.execution))
1119
+ return operationConflict('Coordination action identity belongs to different content');
1120
+ const run = await this.readRunIfPresent(request.sessionId, admission.runId);
1121
+ if (!run)
1122
+ break;
1123
+ const snapshot = await this.readCanonicalSnapshot(request.sessionId, turnId, admission.runId, run);
1124
+ if (!isTerminalSnapshot(snapshot) || snapshot.status === 'completed')
1125
+ break;
1126
+ // A crash after the receipt does not erase a completed Host effect.
1127
+ const events = await this.stores.runtimeEventStore.readImmutableRuntimeEvents(request.sessionId, admission.runId);
1128
+ recoveredReceipt ??= events.find((event) => event.actions?.coordination)?.actions
1129
+ ?.coordination;
1130
+ turnId = `whretry_${createHash('sha256').update(admission.runId).digest('hex').slice(0, 48)}`;
1131
+ }
1132
+ const receiptToReplay = recoveredReceipt;
1133
+ const started = await this.startWorkHubCoordinationMessage({
1134
+ ...request,
1135
+ turnId,
1136
+ ...(receiptToReplay ? { operation: async () => receiptToReplay } : {}),
1137
+ }, context);
1138
+ if (!started.ok)
1139
+ return started;
1140
+ const active = this.#executions.get(request.sessionId);
1141
+ if (active?.turnId === turnId)
1142
+ await active.done;
1143
+ const snapshot = await this.readCanonicalSnapshot(request.sessionId, turnId, started.result.runId);
1144
+ if (snapshot.status !== 'completed')
1145
+ return operationUnavailable('Coordination operation did not complete');
1146
+ const events = await this.stores.runtimeEventStore.readImmutableRuntimeEvents(request.sessionId, started.result.runId);
1147
+ const receipt = events.find((event) => event.actions?.coordination)?.actions?.coordination;
1148
+ return receipt
1149
+ ? { ok: true, result: receipt }
1150
+ : operationUnavailable('Coordination receipt is unavailable');
1151
+ }
1152
+ coordinationOperation(request, admission) {
1153
+ if (request.execution.kind !== 'workhub_coordination' ||
1154
+ !('operation' in request) ||
1155
+ !request.operation)
1156
+ return undefined;
1157
+ const execute = request.operation;
1158
+ const content = requireHostedExecutionMessageContent(admission);
1159
+ return {
1160
+ sessionId: admission.sessionId,
1161
+ turnId: admission.turnId,
1162
+ runId: admission.runId,
1163
+ userMessageId: admission.userMessageId,
1164
+ execution: admission.execution,
1165
+ content,
1166
+ start: ({ runId, userMessageId, onRunStarted }) => this.manager.runCoordinationOperation(admission.sessionId, { turnId: admission.turnId, ...content }, { runId, userMessageId, onRunStarted }, () => execute(admission.turnId)),
1167
+ };
1168
+ }
1169
+ /** Accepts a model action against the exact live WorkHub root and its durable user input. */
1170
+ readActiveWorkHubRequest(turnId) {
1171
+ const sessionId = WORKHUB_COORDINATION_SESSION_ID;
1172
+ return this.sessionAdmission.run(sessionId, async () => {
1173
+ const active = this.#executions.get(sessionId);
1174
+ if (!active || active.turnId !== turnId)
1175
+ return undefined;
1176
+ const [admission, header] = await Promise.all([
1177
+ this.stores.agentRunStore.readRootTurnAdmission(sessionId, turnId),
1178
+ this.stores.sessionStore.readHeaderSnapshot(sessionId),
1179
+ ]);
1180
+ if (this.#executions.get(sessionId) !== active ||
1181
+ admission?.runId !== active.runId ||
1182
+ admission.execution.kind !== 'workhub_coordination' ||
1183
+ header.toolProfile !== 'workhub-coordination-v2')
1184
+ return undefined;
1185
+ return requireHostedExecutionMessageContent(admission);
1186
+ });
1102
1187
  }
1103
1188
  startRootMessage(request, context) {
1104
1189
  return this.runCommand(async () => {
@@ -1115,7 +1200,7 @@ export class RootTurnCoordinator {
1115
1200
  if (existing.execution.kind !== request.execution.kind) {
1116
1201
  return completedStart(operationConflict('Turn identity belongs to a different execution kind'));
1117
1202
  }
1118
- if (!isDeepStrictEqual(existing.execution, request.execution)) {
1203
+ if (!rootExecutionMatches(existing.execution, request.execution)) {
1119
1204
  return completedStart(operationConflict('Turn identity belongs to a different execution payload'));
1120
1205
  }
1121
1206
  let content;
@@ -1141,7 +1226,7 @@ export class RootTurnCoordinator {
1141
1226
  return completedStart({ ok: true, result: snapshot });
1142
1227
  }
1143
1228
  }
1144
- return this.prepareAdmittedTurn(activationInputForAdmission(existing), existing, context.acquireResidency, lease, undefined, undefined, reservation);
1229
+ return this.prepareAdmittedTurn(activationInputForAdmission(existing), existing, context.acquireResidency, lease, undefined, this.coordinationOperation(request, existing), reservation);
1145
1230
  }
1146
1231
  reservation ??= this.reserveRootTurn(request.sessionId);
1147
1232
  if (!reservation) {
@@ -1186,7 +1271,8 @@ export class RootTurnCoordinator {
1186
1271
  const attachmentError = await this.attachmentValidator?.validateTurnAttachments(request.sessionId, attachments);
1187
1272
  if (attachmentError)
1188
1273
  return completedStart(operationConflict(attachmentError));
1189
- const binding = request.execution.kind === 'workhub_coordination'
1274
+ const binding = request.execution.kind === 'workhub_coordination' &&
1275
+ header.toolProfile === 'workhub-coordination-v1'
1190
1276
  ? undefined
1191
1277
  : prepared.commitCapabilityBinding
1192
1278
  ? await prepared.commitCapabilityBinding()
@@ -1226,7 +1312,7 @@ export class RootTurnCoordinator {
1226
1312
  if (!rootMessageAdmissionMatches(admitted.admission, request, canonicalContent.content, context.turnAdmissionAuthorization)) {
1227
1313
  return completedStart(operationConflict('Turn identity was already admitted with a different payload'));
1228
1314
  }
1229
- return this.prepareAdmittedTurn(activationInputForAdmission(admitted.admission), admitted.admission, context.acquireResidency, lease, undefined, undefined, reservation);
1315
+ return this.prepareAdmittedTurn(activationInputForAdmission(admitted.admission), admitted.admission, context.acquireResidency, lease, undefined, this.coordinationOperation(request, admitted.admission), reservation);
1230
1316
  });
1231
1317
  const disposition = await admissionTask.finally(() => {
1232
1318
  if (reservation)
@@ -2198,8 +2284,18 @@ function throwHostedStopError(sessionId, outcome) {
2198
2284
  throw new RuntimeHostedRootUnavailableError(sessionId, outcome.error.message);
2199
2285
  }
2200
2286
  }
2287
+ /** Compatibility may omit only the action identity absent from an older admission. */
2288
+ function rootExecutionMatches(stored, incoming) {
2289
+ if (stored.kind === 'workhub_coordination' &&
2290
+ incoming.kind === 'workhub_coordination' &&
2291
+ stored.actionId === undefined) {
2292
+ const { actionId: _actionId, ...legacyIncoming } = incoming;
2293
+ return isDeepStrictEqual(stored, legacyIncoming);
2294
+ }
2295
+ return isDeepStrictEqual(stored, incoming);
2296
+ }
2201
2297
  function rootMessageAdmissionMatches(admission, request, content, authorization) {
2202
- return (isDeepStrictEqual(admission.execution, request.execution) &&
2298
+ return (rootExecutionMatches(admission.execution, request.execution) &&
2203
2299
  (request.execution.kind === 'external_message' && request.execution.inputDigest
2204
2300
  ? true
2205
2301
  : messageContentsEqual(requireHostedExecutionMessageContent(admission), content)) &&
@@ -23,6 +23,7 @@ import { ShellRunPtyControlClosedError, isShellRunResourceRef, } from '@maka/run
23
23
  import { defaultShellPlan, ShellPreferenceError } from '@maka/runtime/shell-detect';
24
24
  import { isSessionNotFoundError } from '@maka/storage/execution-stores';
25
25
  import { decodeRuntimeResourceControllerAcquireResult, decodeRuntimeResourceControllerControlResult, decodeRuntimeResourceQueryResult, decodeRuntimeResourceStopResult, decodeRuntimeResourceStartResult, RUNTIME_RESOURCE_CONTROLLER_ACQUIRE_RESULT_MAX_BYTES, RUNTIME_RESOURCE_MAX_CONTROL_SEQUENCE, } from '../protocol/index.js';
26
+ import { boundedFailureDiagnostic } from './failure-diagnostic.js';
26
27
  import { boundedRuntimeResourceSnapshot, canonicalRuntimeResources, createRuntimeResourcePage, runtimeResourceRevision, runtimeResourceSnapshotFromResult, } from './runtime-resource-projection.js';
27
28
  const MAX_CONTROL_REPLAYS = 128;
28
29
  /** Owns Host Shell/PTY tools plus the connection-scoped Client controller fence. */
@@ -186,8 +187,7 @@ export class HostRuntimeResourceCoordinator {
186
187
  if (isSessionNotFoundError(error)) {
187
188
  return queryFailure('not_found', 'Session was not found');
188
189
  }
189
- this.#requestDrain();
190
- return queryFailure('internal_failure', 'Session state is unavailable');
190
+ return this.#canonicalReadFailure(error, 'Session state is unavailable');
191
191
  }
192
192
  if (input.kind === 'get') {
193
193
  try {
@@ -206,9 +206,8 @@ export class HostRuntimeResourceCoordinator {
206
206
  }),
207
207
  };
208
208
  }
209
- catch {
210
- this.#requestDrain();
211
- return queryFailure('internal_failure', 'Runtime Resource state is unavailable');
209
+ catch (error) {
210
+ return this.#canonicalReadFailure(error, 'Runtime Resource state is unavailable');
212
211
  }
213
212
  }
214
213
  let updates;
@@ -218,9 +217,8 @@ export class HostRuntimeResourceCoordinator {
218
217
  updates = updates.filter((update) => update.sessionId === input.sessionId);
219
218
  }
220
219
  }
221
- catch {
222
- this.#requestDrain();
223
- return queryFailure('internal_failure', 'Runtime Resource state is unavailable');
220
+ catch (error) {
221
+ return this.#canonicalReadFailure(error, 'Runtime Resource state is unavailable');
224
222
  }
225
223
  try {
226
224
  const resources = canonicalRuntimeResources(updates);
@@ -251,6 +249,11 @@ export class HostRuntimeResourceCoordinator {
251
249
  ? queryFailure('not_found', 'Session was not found')
252
250
  : outcome;
253
251
  }
252
+ #canonicalReadFailure(error, message) {
253
+ console.error(`[runtime-host] canonical Runtime Resource read failed: ${boundedFailureDiagnostic(error)}`);
254
+ this.#requestDrain();
255
+ return queryFailure('internal_failure', message);
256
+ }
254
257
  #guestObservationGrantId(context, sessionId) {
255
258
  if (context.principalKind !== 'session_guest')
256
259
  return;
@@ -418,28 +418,38 @@ export class HostScheduledTaskCoordinator {
418
418
  }
419
419
  }
420
420
  async #refresh() {
421
- await this.#exclusive(async () => {
422
- if (this.#handoffHeld || this.#draining)
423
- return;
424
- for (const claim of await this.#store.listPendingFires()) {
421
+ if (this.#handoffHeld || this.#draining)
422
+ return;
423
+ // Cover claim admission, native delivery, and persistence, while an idle
424
+ // schedule or a read-only catalog query does not claim active work.
425
+ const residency = this.#acquireResidency();
426
+ try {
427
+ await this.#exclusive(async () => {
425
428
  if (this.#handoffHeld || this.#draining)
426
- break;
427
- if (claim.nativeState === 'waiting_for_provider') {
428
- await this.#fulfill(claim, true);
429
+ return;
430
+ for (const claim of await this.#store.listPendingFires()) {
431
+ if (this.#handoffHeld || this.#draining)
432
+ break;
433
+ if (claim.nativeState === 'waiting_for_provider') {
434
+ await this.#fulfill(claim, true);
435
+ }
429
436
  }
430
- }
431
- while (!this.#draining && !this.#handoffHeld) {
432
- const scan = await this.#store.claimNextDue(this.#now());
433
- for (const expired of scan.expired)
434
- this.#publish('updated', expired.id);
435
- const claim = scan.claim;
436
- if (!claim)
437
- break;
438
- await this.#refreshResidency();
439
- await this.#fulfill(claim, false);
440
- }
441
- await this.#refreshSchedule();
442
- });
437
+ while (!this.#draining && !this.#handoffHeld) {
438
+ const scan = await this.#store.claimNextDue(this.#now());
439
+ for (const expired of scan.expired)
440
+ this.#publish('updated', expired.id);
441
+ const claim = scan.claim;
442
+ if (!claim)
443
+ break;
444
+ await this.#refreshResidency();
445
+ await this.#fulfill(claim, false);
446
+ }
447
+ await this.#refreshSchedule();
448
+ });
449
+ }
450
+ finally {
451
+ residency.release();
452
+ }
443
453
  }
444
454
  async #fulfill(claim, recovering) {
445
455
  const incognito = (await this.#runtimePolicy.runtimePolicy.getSnapshot()).policy.privacy
@@ -680,7 +690,7 @@ export class HostScheduledTaskCoordinator {
680
690
  (claims.length > 0 ||
681
691
  tasks.some((task) => task.status === 'active' && task.nextFireAt !== null));
682
692
  if (shouldHold && !this.#residency)
683
- this.#residency = this.#acquireResidency();
693
+ this.#residency = this.#acquireResidency('idle');
684
694
  if (!shouldHold)
685
695
  this.#releaseResidency();
686
696
  }
@@ -134,7 +134,7 @@ export class BoundedSerialOutboundWriter {
134
134
  if (this.#closed)
135
135
  return;
136
136
  this.close(error);
137
- this.#onFailure();
137
+ this.#onFailure(error);
138
138
  }
139
139
  #nextFrame() {
140
140
  // Close acknowledgements are fences: no lower-priority subscription
@@ -24,7 +24,7 @@ import { thinkingVariantsForConnection } from '@maka/core/model-thinking';
24
24
  import { executionBoundaryDisplayMode, } from '@maka/core/sandbox-boundary';
25
25
  import { DEFAULT_SESSION_NAME, normalizeUserSessionName } from '@maka/core/session-name';
26
26
  import { isSessionStartModeLabel as isExecutionSemanticLabel, sessionStartModeSpec, } from '@maka/core/session-start-mode';
27
- import { isWorkHubCoordinationSessionId, isWorkHubCoordinationSessionTarget, } from '@maka/core/session';
27
+ import { WORKHUB_COORDINATION_SESSION_ID, isWorkHubCoordinationSession, isWorkHubCoordinationSessionId, isWorkHubCoordinationSessionTarget, } from '@maka/core/session';
28
28
  import { isSessionNotFoundError, SessionMetadataConflictError, SessionMetadataVersionConflictError, } from '@maka/storage/execution-stores';
29
29
  import { isVisibleSessionMessage } from '@maka/storage/session-message-projection';
30
30
  import { SessionConfigurationRevisionConflictError, SessionConfigurationTransitionError, } from '@maka/runtime/session-manager';
@@ -438,15 +438,27 @@ export class HostSessionCatalogCoordinator {
438
438
  }
439
439
  });
440
440
  }
441
- async #updateConfiguration(input) {
442
- if (isWorkHubCoordinationSessionId(input.sessionId)) {
441
+ configureWorkHubModel(input) {
442
+ return this.#updateConfiguration({
443
+ sessionId: WORKHUB_COORDINATION_SESSION_ID,
444
+ expectedRevision: input.expectedRevision,
445
+ patch: { modelTarget: input.modelTarget },
446
+ }, 'workhub');
447
+ }
448
+ async #updateConfiguration(input, authority = 'ordinary') {
449
+ if (authority === 'ordinary' && isWorkHubCoordinationSessionId(input.sessionId)) {
443
450
  return configurationFailure('operation_conflict', 'WorkHub Coordination Session configuration requires WorkHub authority');
444
451
  }
445
452
  return this.#admission.run(input.sessionId, async (lease) => {
446
453
  let commitAttempted = false;
447
454
  try {
448
455
  const current = await this.#stores.readHeaderRecordSnapshot(input.sessionId);
449
- if (isWorkHubCoordinationSessionTarget(current.header)) {
456
+ if (authority === 'workhub' &&
457
+ (!isWorkHubCoordinationSessionId(current.header.id) ||
458
+ !isWorkHubCoordinationSession(current.header))) {
459
+ return configurationFailure('operation_conflict', 'WorkHub Coordination Session identity is unavailable');
460
+ }
461
+ if (authority === 'ordinary' && isWorkHubCoordinationSessionTarget(current.header)) {
450
462
  return configurationFailure('operation_conflict', 'WorkHub Coordination Session configuration requires WorkHub authority');
451
463
  }
452
464
  if (current.revision !== input.expectedRevision) {
@@ -461,7 +473,7 @@ export class HostSessionCatalogCoordinator {
461
473
  if (!clearsConnectionBlock && sessionConfigurationMatches(current.header, configuration)) {
462
474
  return configurationSuccess({
463
475
  kind: 'committed',
464
- session: projectSessionCatalogRecord(await this.#stores.readCatalogRecord(input.sessionId)),
476
+ session: projectSessionCatalogRecord(await this.#stores.readCatalogRecord(input.sessionId, authority === 'workhub' ? 'recoverable' : 'ordinary')),
465
477
  });
466
478
  }
467
479
  commitAttempted = true;
@@ -470,7 +482,7 @@ export class HostSessionCatalogCoordinator {
470
482
  clearConnectionBlock: input.patch.modelTarget !== undefined,
471
483
  configuration,
472
484
  });
473
- return configurationSuccess(await this.#committedUpdate(input.sessionId, lease));
485
+ return configurationSuccess(await this.#committedUpdate(input.sessionId, lease, authority === 'workhub' ? 'recoverable' : 'ordinary'));
474
486
  }
475
487
  catch (error) {
476
488
  if (!commitAttempted &&
@@ -597,11 +609,11 @@ export class HostSessionCatalogCoordinator {
597
609
  position = page.nextPosition;
598
610
  }
599
611
  }
600
- async #committedUpdate(sessionId, lease) {
612
+ async #committedUpdate(sessionId, lease, roleScope = 'ordinary') {
601
613
  await this.#continuity.refreshCanonical(sessionId, lease);
602
614
  return {
603
615
  kind: 'committed',
604
- session: projectSessionCatalogRecord(await this.#stores.readCatalogRecord(sessionId)),
616
+ session: projectSessionCatalogRecord(await this.#stores.readCatalogRecord(sessionId, roleScope)),
605
617
  };
606
618
  }
607
619
  #metadataMutationFailure(input, error, unknownMessage) {
@@ -46,18 +46,9 @@ const TRANSCRIPT_TURN_SCAN_LIMIT = 1;
46
46
  * as absent rather than searched for down the Session.
47
47
  */
48
48
  const TRANSCRIPT_LOOKUP_MAX_TURNS = 2;
49
- /** One storage round trip of Coordination rows, sized like one ledger Turn. */
50
- const COORDINATION_TRANSCRIPT_SCAN_LIMIT = 64;
51
- const COORDINATION_TRANSCRIPT_SCAN_MAX_BYTES = DURABLE_TRANSCRIPT_TURN_MAX_BYTES;
52
49
  export function createSessionTranscriptReader(input) {
53
50
  const ledger = createDurableLedgerTranscriptReader(input);
54
- const coordination = createCoordinationTranscriptReader(input.stores);
55
- const isCoordination = (sessionId) => sessionId === WORKHUB_COORDINATION_SESSION_ID;
56
- // Only a ledger-backed Session has a conversion; the Coordination Session's
57
- // rows are the transcript, not something a run left behind.
58
51
  const prepared = async (sessionId) => {
59
- if (isCoordination(sessionId))
60
- return coordination;
61
52
  await input.ensureTranscriptLedger?.(sessionId);
62
53
  return ledger;
63
54
  };
@@ -148,6 +139,12 @@ function createDurableLedgerTranscriptReader(input) {
148
139
  if (projected.diagnostics.some(isHardRuntimeEventReadModelDiagnostic)) {
149
140
  throw new Error('Durable RuntimeEvent transcript projection is incomplete');
150
141
  }
142
+ const admission = turn.invocation.sessionId === WORKHUB_COORDINATION_SESSION_ID
143
+ ? await input.stores.agentRunStore.readRootTurnAdmission(turn.invocation.sessionId, turn.invocation.turnId)
144
+ : undefined;
145
+ const actionId = admission?.execution.kind === 'workhub_coordination'
146
+ ? admission.execution.actionId
147
+ : undefined;
151
148
  const ordinals = new Map(turn.events.map((entry) => [entry.event.id, entry.ordinal]));
152
149
  const emitted = new Map();
153
150
  return projected.messages.map((message, index) => {
@@ -160,7 +157,12 @@ function createDurableLedgerTranscriptReader(input) {
160
157
  throw new Error('RuntimeEvent exceeds its transcript sequence stride');
161
158
  }
162
159
  emitted.set(ordinal, offset + 1);
163
- return { sequence: ordinal * EVENT_SEQUENCE_STRIDE + offset, message };
160
+ return {
161
+ sequence: ordinal * EVENT_SEQUENCE_STRIDE + offset,
162
+ message: message.type === 'user' && actionId
163
+ ? { ...message, coordinationActionId: actionId }
164
+ : message,
165
+ };
164
166
  });
165
167
  };
166
168
  const readTurns = async (sessionId, request) => store.readTranscriptInvocations(sessionId, {
@@ -221,9 +223,11 @@ function createDurableLedgerTranscriptReader(input) {
221
223
  ordinal = older ? low - 1 : high + 1;
222
224
  }
223
225
  };
226
+ const source = { readHighWater: highWater, scan };
224
227
  return {
228
+ source,
225
229
  readHighWater: highWater,
226
- ...pagedTranscriptReads({ readHighWater: highWater, scan }),
230
+ ...pagedTranscriptReads(source),
227
231
  /** One row per Turn, folded from the Turn's own projected messages. */
228
232
  async readTurnContributions(sessionId, throughSequence, position, maxContributions) {
229
233
  const watermark = throughSequence ?? (await highWater(sessionId));
@@ -395,99 +399,6 @@ function pagedTranscriptReads(source) {
395
399
  },
396
400
  };
397
401
  }
398
- /**
399
- * The WorkHub Coordination Session's transcript, read from the rows the WorkHub
400
- * writes.
401
- *
402
- * Every other Session's transcript is what its runs did, so the ledger holds
403
- * all of it. The Coordination Session's is not: a delegation, a stop and a
404
- * routing summary are appended under a Turn id that no root Turn admission ever
405
- * minted, so there is no invocation for the ledger to hang them on and no
406
- * conversion that could lift them. `workhub.coordination.answer` is the one
407
- * path that would admit a real Turn and nothing in the renderer calls it.
408
- *
409
- * Delete this source once the WorkHub admits a Coordination Turn for every
410
- * action, which its own ADR already requires (#3492,
411
- * `docs/architecture/workhub-coordination-session-adr.md`): the Session then
412
- * reads like any other and this reader has nothing left to do.
413
- */
414
- function createCoordinationTranscriptReader(stores) {
415
- const store = stores.sessionStore;
416
- const highWater = (sessionId) => store.readTranscriptHighWaterSnapshot(sessionId);
417
- const scan = async function* (sessionId, request) {
418
- const throughSequence = request.throughSequence === undefined ? await highWater(sessionId) : request.throughSequence;
419
- if (throughSequence === null)
420
- return;
421
- const older = request.direction === 'older';
422
- const position = request.position ?? (older ? throughSequence : 0);
423
- let cursor = older ? Math.min(position, throughSequence) + 1 : position - 1;
424
- for (;;) {
425
- const page = await store.readMessagesAfter(sessionId, {
426
- ...(older ? { beforeSequence: cursor } : { afterSequence: cursor }),
427
- maxMessages: COORDINATION_TRANSCRIPT_SCAN_LIMIT,
428
- maxStoredBytes: COORDINATION_TRANSCRIPT_SCAN_MAX_BYTES,
429
- });
430
- if (page.records.length === 0)
431
- return;
432
- for (const record of page.records) {
433
- if (!older && record.sequence > throughSequence)
434
- return;
435
- yield record;
436
- }
437
- cursor = page.records.at(-1).sequence;
438
- }
439
- };
440
- const source = { readHighWater: highWater, scan };
441
- return {
442
- readHighWater: highWater,
443
- ...pagedTranscriptReads(source),
444
- /** Folded from the rows themselves; these Turns have nothing else. */
445
- async readTurnContributions(sessionId, throughSequence, position, maxContributions) {
446
- const watermark = throughSequence ?? (await highWater(sessionId));
447
- if (watermark === null) {
448
- return { throughSequence: null, contributions: [], nextPosition: null };
449
- }
450
- const byTurn = new Map();
451
- let nextPosition = null;
452
- for await (const { sequence, message } of scan(sessionId, {
453
- direction: 'newer',
454
- throughSequence: watermark,
455
- position,
456
- })) {
457
- const turnId = message.turnId;
458
- if (turnId === undefined)
459
- continue;
460
- if (!byTurn.has(turnId) && byTurn.size === maxContributions) {
461
- nextPosition = sequence;
462
- break;
463
- }
464
- byTurn.set(turnId, foldTurnContribution(byTurn.get(turnId), turnId, sequence, message));
465
- }
466
- return { throughSequence: watermark, contributions: [...byTurn.values()], nextPosition };
467
- },
468
- /** Every prompt, in order: this transcript has no index to sample from. */
469
- async readTurnLandmarks(sessionId, maxLandmarks) {
470
- const throughSequence = await highWater(sessionId);
471
- if (throughSequence === null)
472
- return { throughSequence: null, landmarks: [] };
473
- const landmarks = [];
474
- for await (const { sequence, message } of scan(sessionId, {
475
- direction: 'newer',
476
- throughSequence,
477
- })) {
478
- if (message.type !== 'user' || message.turnId === undefined)
479
- continue;
480
- const label = (message.displayText ?? message.text ?? '').trim();
481
- if (!label)
482
- continue;
483
- landmarks.push({ turnId: message.turnId, sequence, label });
484
- if (landmarks.length === maxLandmarks)
485
- break;
486
- }
487
- return { throughSequence, landmarks };
488
- },
489
- };
490
- }
491
402
  function ordinalOf(sequence) {
492
403
  return Math.floor(sequence / EVENT_SEQUENCE_STRIDE);
493
404
  }
@@ -44,6 +44,9 @@ export function projectSharedSessionTranscriptMessage(message, sessionId) {
44
44
  ? {}
45
45
  : { steeringEventId: message.steeringEventId }),
46
46
  ...(message.origin === undefined ? {} : { origin: message.origin }),
47
+ ...(message.coordinationActionId === undefined
48
+ ? {}
49
+ : { coordinationActionId: message.coordinationActionId }),
47
50
  };
48
51
  }
49
52
  case 'assistant':