maka-agent 0.2.0-dev.46.20260922 → 0.2.0-dev.48.20260923

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 (131) hide show
  1. package/DISCLAIMER-WIP +6 -1
  2. package/README.md +6 -1
  3. package/dist/acp/maka-acp-agent.js +1 -1
  4. package/dist/acp/session-mcp.js +212 -0
  5. package/dist/acp/session-registry.js +52 -8
  6. package/dist/acp/stdio-server.js +3 -0
  7. package/dist/cli-core.js +1 -1
  8. package/dist/mcp-capability-provider.js +6 -1
  9. package/dist/mcp-capability-publication.js +139 -0
  10. package/dist/pi-tui-layout.js +46 -17
  11. package/dist/pi-tui-pickers.js +25 -14
  12. package/dist/pi-tui-runner.js +24 -15
  13. package/dist/runtime-host-capability-provider-command.js +25 -6
  14. package/dist/runtime-host-service-manager.js +26 -2
  15. package/dist/tui-context-refresh.js +15 -26
  16. package/dist/tui-mcp-control.js +66 -112
  17. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  18. package/node_modules/@maka/acp-executor-plugin/dist/acp-errors.js +26 -0
  19. package/node_modules/@maka/acp-executor-plugin/dist/acp-filesystem.js +103 -0
  20. package/node_modules/@maka/acp-executor-plugin/dist/acp-process.js +119 -0
  21. package/node_modules/@maka/acp-executor-plugin/dist/acp-projection.js +146 -0
  22. package/node_modules/@maka/acp-executor-plugin/dist/index.js +653 -0
  23. package/node_modules/@maka/acp-executor-plugin/dist/plugin.mjs +24446 -0
  24. package/node_modules/@maka/acp-executor-plugin/package.json +15 -0
  25. package/node_modules/@maka/antigravity-acp-plugin/dist/index.js +88 -0
  26. package/node_modules/@maka/antigravity-acp-plugin/dist/model-presentation.js +53 -0
  27. package/node_modules/@maka/antigravity-acp-plugin/dist/plugin.mjs +115 -0
  28. package/node_modules/@maka/antigravity-acp-plugin/package.json +15 -0
  29. package/node_modules/@maka/core/dist/client-capability-grant.js +2 -2
  30. package/node_modules/@maka/core/dist/executor-catalog.js +106 -0
  31. package/node_modules/@maka/core/dist/mcp.js +4 -1
  32. package/node_modules/@maka/core/dist/model-metadata.js +28 -1
  33. package/node_modules/@maka/core/dist/model-thinking.js +36 -1
  34. package/node_modules/@maka/core/dist/provider-registry.js +0 -1
  35. package/node_modules/@maka/core/dist/refresh-read-coordinator.js +62 -0
  36. package/node_modules/@maka/core/dist/runtime-event-store.js +25 -0
  37. package/node_modules/@maka/core/dist/runtime-invocation.js +5 -0
  38. package/node_modules/@maka/core/dist/runtime-policy/connection-catalog-codec.js +4 -0
  39. package/node_modules/@maka/core/dist/runtime-policy/credential-vault-codec.js +6 -0
  40. package/node_modules/@maka/core/dist/runtime-policy/policy-codec.js +21 -0
  41. package/node_modules/@maka/core/dist/session.js +19 -1
  42. package/node_modules/@maka/core/dist/settings.js +3 -0
  43. package/node_modules/@maka/core/dist/tool-ledger-scanner.js +365 -104
  44. package/node_modules/@maka/core/dist/turn-origin.js +14 -0
  45. package/node_modules/@maka/core/package.json +3 -0
  46. package/node_modules/@maka/mcp/dist/credential-coordinator.js +6 -1
  47. package/node_modules/@maka/mcp/dist/index.js +31 -5
  48. package/node_modules/@maka/mcp/dist/oauth.js +54 -6
  49. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +1 -1
  50. package/node_modules/@maka/runtime/dist/ai-sdk-message-projection.js +15 -60
  51. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +18 -0
  52. package/node_modules/@maka/runtime/dist/apply-patch-profile.js +64 -14
  53. package/node_modules/@maka/runtime/dist/builtin-tools.js +3 -2
  54. package/node_modules/@maka/runtime/dist/codex-v4a-patch.js +18 -0
  55. package/node_modules/@maka/runtime/dist/conversation-copy.js +56 -1
  56. package/node_modules/@maka/runtime/dist/mcp-tools.js +9 -2
  57. package/node_modules/@maka/runtime/dist/model-adapter.js +80 -3
  58. package/node_modules/@maka/runtime/dist/model-runtime.js +6 -0
  59. package/node_modules/@maka/runtime/dist/openai-apply-patch.js +5 -0
  60. package/node_modules/@maka/runtime/dist/plugin-executor-backend.js +122 -3
  61. package/node_modules/@maka/runtime/dist/plugin-executor-service.js +212 -16
  62. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +2 -2
  63. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +3 -0
  64. package/node_modules/@maka/runtime/dist/session-manager.js +184 -126
  65. package/node_modules/@maka/runtime/dist/shell-run-manager.js +3 -0
  66. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +29 -4
  67. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +8 -5
  68. package/node_modules/@maka/runtime-host/dist/client/capability-provider-service.js +2 -0
  69. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +59 -20
  70. package/node_modules/@maka/runtime-host/dist/client/connection.js +6 -4
  71. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  72. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +8 -1
  73. package/node_modules/@maka/runtime-host/dist/client/reconnecting-connection.js +4 -4
  74. package/node_modules/@maka/runtime-host/dist/client/session-catalog-summary.js +6 -1
  75. package/node_modules/@maka/runtime-host/dist/composition-identity.js +19 -0
  76. package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +20 -3
  77. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +8 -5
  78. package/node_modules/@maka/runtime-host/dist/protocol/index.js +20 -2
  79. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +34 -0
  80. package/node_modules/@maka/runtime-host/dist/protocol/session-catalog.js +75 -5
  81. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +1 -5
  82. package/node_modules/@maka/runtime-host/dist/server/builtin-external-agent-plugins.js +188 -0
  83. package/node_modules/@maka/runtime-host/dist/server/candidate.js +12 -7
  84. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +234 -70
  85. package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +14 -8
  86. package/node_modules/@maka/runtime-host/dist/server/client-capability-recovery.js +44 -46
  87. package/node_modules/@maka/runtime-host/dist/server/context-coordinator.js +2 -0
  88. package/node_modules/@maka/runtime-host/dist/server/execution-composition-factory.js +1 -1
  89. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +188 -21
  90. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +6 -0
  91. package/node_modules/@maka/runtime-host/dist/server/extension-bundle.js +4 -0
  92. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +29 -7
  93. package/node_modules/@maka/runtime-host/dist/server/host-composition-source.js +47 -0
  94. package/node_modules/@maka/runtime-host/dist/server/host-composition.js +1 -29
  95. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +2 -9
  96. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +75 -9
  97. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +5 -1
  98. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +45 -0
  99. package/node_modules/@maka/runtime-host/dist/server/jev-routing-model.js +157 -0
  100. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -2
  101. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +4 -0
  102. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +11 -1
  103. package/node_modules/@maka/runtime-host/dist/server/process-lifecycle.js +1 -1
  104. package/node_modules/@maka/runtime-host/dist/server/process-termination-error.js +27 -0
  105. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +91 -12
  106. package/node_modules/@maka/runtime-host/dist/server/runtime-policy-coordinator.js +2 -0
  107. package/node_modules/@maka/runtime-host/dist/server/session-bundle-coordinator.js +4 -1
  108. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +128 -13
  109. package/node_modules/@maka/runtime-host/dist/server/session-effect-coordinator.js +12 -6
  110. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +2 -2
  111. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +33 -0
  112. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +4 -0
  113. package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +173 -0
  114. package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +293 -0
  115. package/node_modules/@maka/runtime-host/package.json +2 -0
  116. package/node_modules/@maka/storage/dist/agent-run-store-contract.js +8 -0
  117. package/node_modules/@maka/storage/dist/execution-stores.js +15 -3
  118. package/node_modules/@maka/storage/dist/mcp-config-store.js +91 -2
  119. package/node_modules/@maka/storage/dist/operational-state-store.js +15 -1
  120. package/node_modules/@maka/storage/dist/plan-authority.js +2 -0
  121. package/node_modules/@maka/storage/dist/plan-store.js +30 -0
  122. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  123. package/node_modules/@maka/storage/dist/runtime-policy/credential-vault-document.js +4 -1
  124. package/node_modules/@maka/storage/dist/runtime-policy/policy-document.js +2 -0
  125. package/node_modules/@maka/storage/dist/session-store-values.js +16 -4
  126. package/node_modules/@maka/storage/dist/shell-run-authority.js +1 -0
  127. package/node_modules/@maka/storage/dist/shell-run-store.js +15 -0
  128. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +74 -1
  129. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +724 -84
  130. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +16 -0
  131. package/package.json +1 -1
@@ -29,9 +29,18 @@ import { PluginPackageStoreError } from './plugin-package-store.js';
29
29
  import { HostPluginPlatformError } from './plugin-platform.js';
30
30
  export class HostPluginPlatformCoordinator {
31
31
  platform;
32
+ queryExecutors;
32
33
  #streams = new Map();
33
34
  #pendingStreamOpens = new Map();
34
35
  handlers = {
36
+ 'plugin.executor.query': async (input) => {
37
+ try {
38
+ return { ok: true, result: { items: await this.queryExecutors(input) } };
39
+ }
40
+ catch (error) {
41
+ return failure(error);
42
+ }
43
+ },
35
44
  'plugin.client.query': (input) => this.#client(input),
36
45
  'plugin.client.remote.call': (input, context) => this.#remoteCall(input, context),
37
46
  'plugin.client.remote.stream.open': (input, context) => this.#remoteStreamOpen(input, context),
@@ -45,8 +54,9 @@ export class HostPluginPlatformCoordinator {
45
54
  'plugin.composition.apply': (input) => this.#apply(input),
46
55
  'plugin.platform.reconcile': () => this.#reconcile(),
47
56
  };
48
- constructor(platform) {
57
+ constructor(platform, queryExecutors = async () => []) {
49
58
  this.platform = platform;
59
+ this.queryExecutors = queryExecutors;
50
60
  }
51
61
  releaseConnection(connectionId) {
52
62
  for (const [streamId, stream] of this.#streams) {
@@ -16,7 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { RuntimeHostProcessTerminationRequiredError, } from './host-kernel.js';
19
+ import { RuntimeHostProcessTerminationRequiredError } from './process-termination-error.js';
20
20
  export async function runRuntimeHostProcessLifecycle(host, options = {}) {
21
21
  let closing = false;
22
22
  const close = () => {
@@ -0,0 +1,27 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ export class RuntimeHostProcessTerminationRequiredError extends Error {
20
+ shutdownGraceMs;
21
+ code = 'process_termination_required';
22
+ constructor(shutdownGraceMs) {
23
+ super(`Runtime Host did not shut down within ${shutdownGraceMs} ms`);
24
+ this.shutdownGraceMs = shutdownGraceMs;
25
+ this.name = 'RuntimeHostProcessTerminationRequiredError';
26
+ }
27
+ }
@@ -966,10 +966,10 @@ export class RootTurnCoordinator {
966
966
  };
967
967
  if (!(await this.bindRecoveryCapabilities(input.sessionId, execution)))
968
968
  return { deferred: true };
969
- execution = await this.prepareFreshWorkHubExecution(header, turnId, input.content, execution);
970
969
  const unavailableReason = runtimeHostExecutionUnavailableReason(header, execution);
971
970
  if (unavailableReason)
972
971
  return { error: unavailableReason };
972
+ execution = await this.prepareFreshWorkHubExecution(header, turnId, input.content, execution);
973
973
  const reservation = this.reserveRootTurn(input.sessionId);
974
974
  if (!reservation)
975
975
  return { error: 'Another root Turn is being admitted' };
@@ -1011,18 +1011,19 @@ export class RootTurnCoordinator {
1011
1011
  });
1012
1012
  }
1013
1013
  async prepareFreshWorkHubExecution(header, turnId, content, execution, inputClosedSignal) {
1014
- if (execution.kind !== 'workhub_coordination' || !this.prepareWorkHubRoutingDecision) {
1014
+ if (execution.kind !== 'workhub_coordination' ||
1015
+ execution.feedback ||
1016
+ execution.operation === 'action' ||
1017
+ !this.prepareWorkHubRoutingDecision) {
1015
1018
  return execution;
1016
1019
  }
1017
- return {
1018
- ...execution,
1019
- routingDecision: await this.prepareWorkHubRoutingDecision({
1020
- header,
1021
- turnId,
1022
- content,
1023
- ...(inputClosedSignal ? { inputClosedSignal } : {}),
1024
- }),
1025
- };
1020
+ const routingDecision = await this.prepareWorkHubRoutingDecision({
1021
+ header,
1022
+ turnId,
1023
+ content,
1024
+ ...(inputClosedSignal ? { inputClosedSignal } : {}),
1025
+ });
1026
+ return routingDecision === undefined ? execution : { ...execution, routingDecision };
1026
1027
  }
1027
1028
  prepareMessage(input) {
1028
1029
  return this.runCommand(async () => {
@@ -1160,6 +1161,70 @@ export class RootTurnCoordinator {
1160
1161
  }
1161
1162
  return this.startRootMessage(request, context);
1162
1163
  }
1164
+ /** Result delivery uses the same durable root admission as ordinary WorkHub turns.
1165
+ * The deterministic Turn identity is the delivery receipt, including after a crash.
1166
+ * Content is re-read under both Session admissions immediately before committing.
1167
+ */
1168
+ startWorkHubResult(origin, prepareContent) {
1169
+ return this.runCommand(async () => {
1170
+ const sessionId = WORKHUB_COORDINATION_SESSION_ID;
1171
+ const turnId = origin.eventId;
1172
+ let disposition;
1173
+ const result = await this.sessionAdmission.runMany([sessionId, origin.targetSessionId], async (lease) => {
1174
+ const previousDelivery = await this.stores.agentRunStore.readRootTurnAdmission(sessionId, turnId);
1175
+ if (previousDelivery) {
1176
+ if (previousDelivery.execution.kind !== 'workhub_coordination' ||
1177
+ !isDeepStrictEqual(previousDelivery.execution.feedback, origin))
1178
+ throw new Error('WorkHub feedback identity conflict');
1179
+ return 'delivered';
1180
+ }
1181
+ if (!this.isSessionExecutionIdle(sessionId))
1182
+ return 'pending';
1183
+ const content = await prepareContent(lease);
1184
+ if (!content)
1185
+ return 'obsolete';
1186
+ const previous = this.rootAdmissionOwner.latestAdmission(sessionId)?.execution;
1187
+ const execution = {
1188
+ kind: 'workhub_coordination',
1189
+ inputDigest: messageContentDigest(content),
1190
+ feedback: origin,
1191
+ ...(previous?.kind === 'workhub_coordination' && previous.capabilityBinding
1192
+ ? { capabilityBinding: previous.capabilityBinding }
1193
+ : {}),
1194
+ };
1195
+ if (!(await this.bindRecoveryCapabilities(sessionId, execution)))
1196
+ return 'pending';
1197
+ const header = await this.stores.sessionStore.readHeaderSnapshot(sessionId);
1198
+ if (runtimeHostExecutionUnavailableReason(header, execution))
1199
+ return 'pending';
1200
+ const reservation = this.reserveRootTurn(sessionId);
1201
+ if (!reservation)
1202
+ return 'pending';
1203
+ try {
1204
+ if (!this.beginRootAdmission(reservation))
1205
+ return 'pending';
1206
+ const admitted = await this.rootAdmissionOwner.admitRootTurn({
1207
+ sessionId,
1208
+ turnId,
1209
+ proposedRunId: randomUUID(),
1210
+ proposedUserMessageId: turnId,
1211
+ execution,
1212
+ normalizedInput: content,
1213
+ sourceMessages: [],
1214
+ admittedAt: Date.now(),
1215
+ });
1216
+ disposition = await this.prepareAdmittedTurn(activationInputForAdmission(admitted.admission), admitted.admission, this.acquireRecoveryResidency, lease, undefined, undefined, reservation);
1217
+ return 'delivered';
1218
+ }
1219
+ finally {
1220
+ this.releaseRootReservation(reservation);
1221
+ }
1222
+ });
1223
+ if (disposition)
1224
+ await this.resolveStartDisposition({ sessionId, turnId }, disposition);
1225
+ return result;
1226
+ });
1227
+ }
1163
1228
  async runWorkHubCoordinationOperation(request, context) {
1164
1229
  if (request.execution.operation !== 'action' || !request.operation) {
1165
1230
  return operationUnavailable('Coordination action requires a Host operation');
@@ -1243,9 +1308,23 @@ export class RootTurnCoordinator {
1243
1308
  admission.execution.kind !== 'workhub_coordination' ||
1244
1309
  header.toolProfile !== 'workhub-coordination-v2')
1245
1310
  return undefined;
1311
+ let content = requireHostedExecutionMessageContent(admission);
1312
+ if (admission.execution.feedback) {
1313
+ const feedback = admission.execution.feedback;
1314
+ const assignments = await this.stores.sessionStore.readActiveWorkHubAssignmentsByTarget([
1315
+ feedback.targetSessionId,
1316
+ ]);
1317
+ const assignment = assignments.find((a) => a.actionId === feedback.actionId && a.delegationId === feedback.delegationId);
1318
+ if (!assignment)
1319
+ return undefined;
1320
+ content = normalizeMessageContent({
1321
+ text: assignment.userText,
1322
+ ...(assignment.attachments ? { attachments: assignment.attachments } : {}),
1323
+ });
1324
+ }
1246
1325
  return {
1247
1326
  runId: active.runId,
1248
- content: requireHostedExecutionMessageContent(admission),
1327
+ content,
1249
1328
  ...(admission.execution.routingDecision
1250
1329
  ? { decision: admission.execution.routingDecision }
1251
1330
  : {}),
@@ -453,6 +453,8 @@ function sameLocator(left, right) {
453
453
  return right.scope === 'connection' && left.connectionId === right.connectionId;
454
454
  case 'web_search':
455
455
  return right.scope === 'web_search' && left.provider === right.provider;
456
+ case 'jev':
457
+ return right.scope === 'jev';
456
458
  case 'network_proxy':
457
459
  return right.scope === 'network_proxy';
458
460
  }
@@ -71,7 +71,10 @@ export class HostSessionBundleCoordinator {
71
71
  }
72
72
  async #fenced(sessionId, operation) {
73
73
  try {
74
- return await this.#options.fenceSubtree(sessionId, operation);
74
+ return await this.#options.fenceSubtree(sessionId, async (fencedSessionIds) => {
75
+ await this.#options.recoverInterruptedSessions(fencedSessionIds);
76
+ return operation(fencedSessionIds);
77
+ });
75
78
  }
76
79
  catch (error) {
77
80
  // Two types, because the Session manager translates on the way out: the
@@ -16,6 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
+ import { isExecutorConfiguration } from '@maka/core/executor-catalog';
19
20
  import { JsonArrayPageBudget } from './json-array-page-budget.js';
20
21
  import { RuntimeHostProtocolError } from '../protocol/errors.js';
21
22
  import { createHash } from 'node:crypto';
@@ -150,6 +151,8 @@ export class HostSessionCatalogCoordinator {
150
151
  #continuity;
151
152
  #workspaceResolver;
152
153
  #requestDrain;
154
+ #configureExecutor;
155
+ #retireExecutor;
153
156
  #assertExecutorAvailable;
154
157
  #sessionAccessAuthority;
155
158
  constructor(options) {
@@ -161,6 +164,8 @@ export class HostSessionCatalogCoordinator {
161
164
  this.#continuity = options.continuity;
162
165
  this.#workspaceResolver = options.workspaceResolver;
163
166
  this.#requestDrain = options.requestDrain;
167
+ this.#configureExecutor = options.configureExecutor;
168
+ this.#retireExecutor = options.retireExecutor;
164
169
  this.#assertExecutorAvailable = options.assertExecutorAvailable;
165
170
  this.#sessionAccessAuthority = options.sessionAccessAuthority;
166
171
  }
@@ -221,7 +226,7 @@ export class HostSessionCatalogCoordinator {
221
226
  const prepared = await prepareCreate(input);
222
227
  return this.#workspaceResolver.runWithUsageRecorded(input.workspace, async (workspace) => {
223
228
  const [model, policy] = await Promise.all([
224
- this.#resolveCreateExecution(input),
229
+ this.#resolveCreateExecution(input, workspace.cwd),
225
230
  this.#readRuntimePolicy(),
226
231
  ]);
227
232
  return {
@@ -232,7 +237,12 @@ export class HostSessionCatalogCoordinator {
232
237
  ...(workspace.projectId === null ? {} : { projectId: workspace.projectId }),
233
238
  name: prepared.name,
234
239
  labels: [...prepared.labels],
235
- ...(model.executorId ? { executorId: model.executorId } : {}),
240
+ ...(model.executorId
241
+ ? {
242
+ executorId: model.executorId,
243
+ ...(model.executorConfig ? { executorConfig: model.executorConfig } : {}),
244
+ }
245
+ : {}),
236
246
  ...(model.connectionId ? { llmConnectionId: model.connectionId } : {}),
237
247
  llmConnectionSlug: model.connectionSlug,
238
248
  model: model.model,
@@ -394,7 +404,7 @@ export class HostSessionCatalogCoordinator {
394
404
  }
395
405
  return await this.#workspaceResolver.runWithUsageRecorded(input.workspace, async (workspace) => {
396
406
  const [model, policy] = await Promise.all([
397
- this.#resolveCreateExecution(input),
407
+ this.#resolveCreateExecution(input, workspace.cwd),
398
408
  this.#readRuntimePolicy(),
399
409
  ]);
400
410
  const createInput = {
@@ -402,7 +412,12 @@ export class HostSessionCatalogCoordinator {
402
412
  ...(workspace.projectId === null ? {} : { projectId: workspace.projectId }),
403
413
  name: prepared.name,
404
414
  labels: [...prepared.labels],
405
- ...(model.executorId ? { executorId: model.executorId } : {}),
415
+ ...(model.executorId
416
+ ? {
417
+ executorId: model.executorId,
418
+ ...(model.executorConfig ? { executorConfig: model.executorConfig } : {}),
419
+ }
420
+ : {}),
406
421
  ...(model.connectionId ? { llmConnectionId: model.connectionId } : {}),
407
422
  llmConnectionSlug: model.connectionSlug,
408
423
  model: model.model,
@@ -480,6 +495,7 @@ export class HostSessionCatalogCoordinator {
480
495
  }
481
496
  return this.#admission.run(input.sessionId, async (lease) => {
482
497
  let commitAttempted = false;
498
+ let confirmedExecutor;
483
499
  try {
484
500
  const current = await this.#stores.readHeaderRecordSnapshot(input.sessionId);
485
501
  if (authority === 'workhub' &&
@@ -499,12 +515,28 @@ export class HostSessionCatalogCoordinator {
499
515
  const configuration = await this.#mergeConfigurationPatch(current.header, input.patch);
500
516
  const clearsConnectionBlock = input.patch.modelTarget !== undefined &&
501
517
  current.header.blockedReason === 'NO_REAL_CONNECTION';
502
- if (!clearsConnectionBlock && sessionConfigurationMatches(current.header, configuration)) {
518
+ if (!clearsConnectionBlock &&
519
+ !input.patch.executorConfig &&
520
+ sessionConfigurationMatches(current.header, configuration)) {
503
521
  return configurationSuccess({
504
522
  kind: 'committed',
505
523
  session: projectSessionCatalogRecord(await this.#stores.readCatalogRecord(input.sessionId, authority === 'workhub' ? 'recoverable' : 'ordinary')),
506
524
  });
507
525
  }
526
+ if (input.patch.executorConfig) {
527
+ if (this.#manager.runningTurnIds(input.sessionId).length ||
528
+ current.header.status === 'waiting_for_user')
529
+ throw new SessionOperationFailure('operation_conflict', 'Executor model can only change while idle');
530
+ if (!this.#configureExecutor)
531
+ throw new SessionOperationFailure('operation_unavailable', 'Executor configuration is unavailable');
532
+ try {
533
+ await this.#configureExecutor(current.header, input.patch.executorConfig);
534
+ confirmedExecutor = current.header;
535
+ }
536
+ catch {
537
+ throw new SessionOperationFailure('operation_unavailable', 'The external agent did not confirm the model change. Retry while idle or start a new task.');
538
+ }
539
+ }
508
540
  commitAttempted = true;
509
541
  await this.#manager.transitionSessionConfiguration(input.sessionId, {
510
542
  expectedRevision: input.expectedRevision,
@@ -515,6 +547,8 @@ export class HostSessionCatalogCoordinator {
515
547
  return configurationSuccess(await this.#committedUpdate(input.sessionId, lease, authority === 'workhub' ? 'recoverable' : 'ordinary'));
516
548
  }
517
549
  catch (error) {
550
+ if (confirmedExecutor)
551
+ await this.#reconcileExecutorAfterFailedCommit(confirmedExecutor);
518
552
  if (!commitAttempted &&
519
553
  !isNotFound(error) &&
520
554
  !(error instanceof SessionMetadataVersionConflictError) &&
@@ -529,6 +563,28 @@ export class HostSessionCatalogCoordinator {
529
563
  }
530
564
  });
531
565
  }
566
+ async #reconcileExecutorAfterFailedCommit(previous) {
567
+ try {
568
+ const actual = (await this.#stores.readHeaderRecordSnapshot(previous.id)).header;
569
+ if (actual.executorId !== previous.executorId ||
570
+ !actual.executorConfig?.model ||
571
+ !this.#configureExecutor)
572
+ throw new Error('Confirmed executor model is unavailable');
573
+ await this.#configureExecutor(actual, actual.executorConfig);
574
+ }
575
+ catch {
576
+ // When the durable value or rollback cannot be confirmed, never leave a
577
+ // live external Session that might answer on a different model.
578
+ try {
579
+ if (!this.#retireExecutor)
580
+ throw new Error('Executor retirement is unavailable');
581
+ await this.#retireExecutor(previous.id);
582
+ }
583
+ catch {
584
+ this.#requestDrain();
585
+ }
586
+ }
587
+ }
532
588
  async #relocateWorkspace(input) {
533
589
  if (isWorkHubCoordinationSessionId(input.sessionId)) {
534
590
  return workspaceFailure('operation_conflict', 'WorkHub Coordination Session workspace requires WorkHub authority');
@@ -839,17 +895,50 @@ export class HostSessionCatalogCoordinator {
839
895
  };
840
896
  }
841
897
  async #mergeConfigurationPatch(current, patch) {
842
- if (current.backend === 'fake' && patch.modelTarget === undefined) {
898
+ if (current.executorConfig && Object.keys(patch).some((key) => key !== 'executorConfig'))
899
+ throw new SessionOperationFailure('operation_unavailable', 'Native execution settings require a new Maka task');
900
+ if (patch.executorConfig &&
901
+ (!current.executorId ||
902
+ patch.executorTarget !== undefined ||
903
+ patch.modelTarget !== undefined ||
904
+ !isExecutorConfiguration(patch.executorConfig)))
905
+ throw new SessionOperationFailure('invalid_request', 'Invalid executor configuration');
906
+ if (current.backend === 'fake' &&
907
+ patch.modelTarget === undefined &&
908
+ patch.executorTarget === undefined) {
843
909
  throw new SessionOperationFailure('operation_conflict', 'Legacy test backend configuration requires an explicit account selection');
844
910
  }
845
911
  if (current.backend !== 'plugin-executor' &&
846
912
  current.llmConnectionId === undefined &&
847
- patch.modelTarget === undefined) {
913
+ patch.modelTarget === undefined &&
914
+ patch.executorTarget === undefined) {
848
915
  throw new SessionOperationFailure('operation_conflict', 'Legacy Session configuration requires an explicit account selection');
849
916
  }
850
917
  const thinkingLevel = patch.thinkingLevel === undefined
851
918
  ? current.thinkingLevel
852
919
  : (patch.thinkingLevel ?? undefined);
920
+ if (patch.executorTarget !== undefined) {
921
+ let executorConfig;
922
+ try {
923
+ executorConfig = await this.#assertExecutorAvailable?.(current.id, patch.executorTarget.executorId, patch.executorTarget.model ? { model: patch.executorTarget.model } : undefined, current.cwd);
924
+ }
925
+ catch {
926
+ throw new SessionOperationFailure('operation_unavailable', `Plugin executor is unavailable: ${patch.executorTarget.executorId}`);
927
+ }
928
+ return {
929
+ backend: 'plugin-executor',
930
+ executorId: patch.executorTarget.executorId,
931
+ ...(executorConfig ? { executorConfig } : {}),
932
+ llmConnectionId: undefined,
933
+ llmConnectionSlug: `executor:${patch.executorTarget.executorId}`,
934
+ model: patch.executorTarget.model ?? patch.executorTarget.executorId,
935
+ thinkingLevel,
936
+ connectionLocked: current.connectionLocked,
937
+ permissionMode: patch.permissionMode ?? current.permissionMode,
938
+ collaborationMode: patch.collaborationMode ?? current.collaborationMode ?? 'agent',
939
+ orchestrationMode: patch.orchestrationMode ?? current.orchestrationMode ?? 'default',
940
+ };
941
+ }
853
942
  let model = {
854
943
  ...(current.llmConnectionId === undefined ? {} : { connectionId: current.llmConnectionId }),
855
944
  connectionSlug: current.llmConnectionSlug,
@@ -870,9 +959,10 @@ export class HostSessionCatalogCoordinator {
870
959
  return {
871
960
  backend: patch.modelTarget || current.backend === 'ai-sdk' ? 'ai-sdk' : 'plugin-executor',
872
961
  executorId: patch.modelTarget ? undefined : current.executorId,
962
+ executorConfig: patch.executorConfig ?? current.executorConfig,
873
963
  llmConnectionId: model.connectionId,
874
964
  llmConnectionSlug: model.connectionSlug,
875
- model: model.model,
965
+ model: patch.executorConfig?.model ?? model.model,
876
966
  thinkingLevel,
877
967
  connectionLocked: patch.modelTarget === undefined ? current.connectionLocked : true,
878
968
  permissionMode: patch.permissionMode ?? current.permissionMode,
@@ -880,18 +970,24 @@ export class HostSessionCatalogCoordinator {
880
970
  orchestrationMode: patch.orchestrationMode ?? current.orchestrationMode ?? 'default',
881
971
  };
882
972
  }
883
- async #resolveCreateExecution(input) {
973
+ async #resolveCreateExecution(input, cwd) {
884
974
  if (input.executorId) {
975
+ const requestedModel = input.executorConfig?.model ?? input.executorModel;
976
+ let executorConfig;
885
977
  try {
886
- this.#assertExecutorAvailable?.(input.sessionId, input.executorId);
978
+ executorConfig = await this.#assertExecutorAvailable?.(input.sessionId, input.executorId, requestedModel ? { model: requestedModel } : input.executorConfig, cwd);
887
979
  }
888
980
  catch {
889
981
  throw new SessionOperationFailure('operation_unavailable', `Plugin executor is unavailable: ${input.executorId}`);
890
982
  }
891
983
  return {
892
984
  executorId: input.executorId,
985
+ ...((executorConfig ?? input.executorConfig)
986
+ ? { executorConfig: executorConfig ?? input.executorConfig }
987
+ : {}),
893
988
  connectionSlug: `executor:${input.executorId}`,
894
- model: input.executorId,
989
+ model: input.executorConfig?.model ?? input.executorModel ?? input.executorId,
990
+ thinkingLevel: input.thinkingLevel ?? undefined,
895
991
  };
896
992
  }
897
993
  if (!input.modelTarget) {
@@ -911,6 +1007,7 @@ export class HostSessionCatalogCoordinator {
911
1007
  function sessionConfigurationMatches(header, configuration) {
912
1008
  return (header.backend === configuration.backend &&
913
1009
  header.executorId === configuration.executorId &&
1010
+ header.executorConfig?.model === configuration.executorConfig?.model &&
914
1011
  header.llmConnectionId === configuration.llmConnectionId &&
915
1012
  header.llmConnectionSlug === configuration.llmConnectionSlug &&
916
1013
  header.model === configuration.model &&
@@ -923,17 +1020,30 @@ function sessionConfigurationMatches(header, configuration) {
923
1020
  function isPermissionModeOnlyPatch(patch) {
924
1021
  return (patch.permissionMode !== undefined &&
925
1022
  patch.modelTarget === undefined &&
1023
+ patch.executorTarget === undefined &&
1024
+ patch.executorConfig === undefined &&
926
1025
  patch.thinkingLevel === undefined &&
927
1026
  patch.collaborationMode === undefined &&
928
1027
  patch.orchestrationMode === undefined);
929
1028
  }
930
1029
  async function prepareCreate(input) {
1030
+ if (input.executorConfig !== undefined &&
1031
+ (!input.executorId || !isExecutorConfiguration(input.executorConfig)))
1032
+ throw new SessionOperationFailure('invalid_request', 'Invalid executor configuration');
931
1033
  if ((input.executorId === undefined) === (input.modelTarget === undefined)) {
932
1034
  throw new SessionOperationFailure('invalid_request', 'Session creation requires exactly one model target or executor id');
933
1035
  }
934
1036
  if (input.executorId !== undefined && !isExecutorId(input.executorId)) {
935
1037
  throw new SessionOperationFailure('invalid_request', 'Session executor id is invalid');
936
1038
  }
1039
+ if (input.executorConfig?.model &&
1040
+ input.executorModel !== undefined &&
1041
+ input.executorConfig.model !== input.executorModel) {
1042
+ throw new SessionOperationFailure('invalid_request', 'Conflicting executor models');
1043
+ }
1044
+ if (input.executorModel !== undefined && input.executorId === undefined) {
1045
+ throw new SessionOperationFailure('invalid_request', 'Executor model requires an executor id');
1046
+ }
937
1047
  if (input.labels?.some(isExecutionSemanticLabel)) {
938
1048
  throw new SessionOperationFailure('invalid_request', 'Session creation cannot set reserved execution labels');
939
1049
  }
@@ -960,7 +1070,7 @@ function createRequestFingerprint(input, prepared) {
960
1070
  prepared.name,
961
1071
  prepared.labels,
962
1072
  input.executorId
963
- ? ['executor', input.executorId]
1073
+ ? ['executor', input.executorId, input.executorConfig?.model ?? input.executorModel ?? null]
964
1074
  : input.modelTarget?.kind === 'default'
965
1075
  ? ['default']
966
1076
  : [
@@ -1036,7 +1146,12 @@ export function projectSessionCatalogRecord(record, liveRunState) {
1036
1146
  ...(header.revisionIndex === undefined ? {} : { revisionIndex: header.revisionIndex }),
1037
1147
  ...(header.revisionState === undefined ? {} : { revisionState: header.revisionState }),
1038
1148
  backend: header.backend,
1039
- ...(header.executorId ? { executorId: header.executorId } : {}),
1149
+ ...(header.executorId
1150
+ ? {
1151
+ executorId: header.executorId,
1152
+ ...(header.executorConfig ? { executorConfig: header.executorConfig } : {}),
1153
+ }
1154
+ : {}),
1040
1155
  llmConnectionId: header.llmConnectionId ?? null,
1041
1156
  llmConnectionSlug: header.llmConnectionSlug,
1042
1157
  connectionLocked: header.connectionLocked,
@@ -90,12 +90,15 @@ export class HostSessionEffectCoordinator {
90
90
  const header = await this.#readSessionHeader(sessionId);
91
91
  if (header.titleIsManual || header.name !== DEFAULT_SESSION_NAME)
92
92
  return;
93
- generated = await this.#model.generateTitle({
94
- sessionId,
95
- header,
96
- sourceText,
97
- abortSignal,
98
- });
93
+ generated =
94
+ header.backend === 'plugin-executor'
95
+ ? undefined
96
+ : await this.#model.generateTitle({
97
+ sessionId,
98
+ header,
99
+ sourceText,
100
+ abortSignal,
101
+ });
99
102
  }
100
103
  catch {
101
104
  // An unreachable title model is not a Session failure; the fallback name
@@ -195,6 +198,9 @@ export class HostSessionEffectCoordinator {
195
198
  if (header.isArchived) {
196
199
  return settledRecap(recapFailure('session_archived', 'Archived Session cannot generate a recap'));
197
200
  }
201
+ if (header.backend === 'plugin-executor') {
202
+ return settledRecap(recapFailure('operation_unavailable', 'Plugin executor Sessions do not support native recap generation'));
203
+ }
198
204
  const view = await this.#readModel.getSessionView(input.sessionId);
199
205
  if (abort.signal.aborted) {
200
206
  return settledRecap(recapFailure('host_draining', 'Runtime Host is draining'));
@@ -146,7 +146,7 @@ export class HostSessionRetirementCoordinator {
146
146
  handles.goal.commit();
147
147
  handles.scheduledTasks.commit();
148
148
  await this.#graphWake.retireSessions(family.sessionIds);
149
- this.#capabilities.retireSessions(family.sessionIds);
149
+ await this.#capabilities.retireSessions(family.sessionIds);
150
150
  this.#messages.retireSessions(family.sessionIds);
151
151
  await this.#refreshFamily(family);
152
152
  return lifecycleSuccess(projectSessionCatalogRecord(await this.#stores.readCatalogRecord(target.header.id)));
@@ -215,7 +215,7 @@ export class HostSessionRetirementCoordinator {
215
215
  await this.#graphWake.retireSessions(allSessionIds);
216
216
  this.#rememberRetiredWorktrees(committableRemove, removedSessionIds);
217
217
  this.#scheduleCleanup(removedSessionIds);
218
- this.#capabilities.retireSessions(allSessionIds);
218
+ await this.#capabilities.retireSessions(allSessionIds);
219
219
  this.#messages.retireSessions(allSessionIds);
220
220
  await this.#continuity.retireSessions(plan.remove.sessionIds, plan.remove.admission);
221
221
  await this.#refreshFamily(plan.archive);
@@ -47,6 +47,16 @@ export function createSessionTranscriptReader(input) {
47
47
  readDurableTurnLandmarks: async (sessionId, request) => (await prepared(sessionId)).readTurnLandmarks(sessionId, request),
48
48
  };
49
49
  }
50
+ /** Reads only the delegated Turn's projected assistant output. The indexed
51
+ * Turn extent avoids materializing every earlier message in its Session.
52
+ */
53
+ export function createTurnResultReader(input) {
54
+ const ledger = createDurableLedgerTranscriptReader(input);
55
+ return async (sessionId, turnId) => {
56
+ await input.ensureTranscriptLedger?.(sessionId);
57
+ return ledger.readLatestAssistantForTurn(sessionId, turnId);
58
+ };
59
+ }
50
60
  /**
51
61
  * Pages seek immutable Session event ordinals before decoding payloads. One
52
62
  * Turn is projected at a time, so a page costs one Turn rather than the
@@ -149,6 +159,29 @@ function createDurableLedgerTranscriptReader(input) {
149
159
  source,
150
160
  readHighWater: highWater,
151
161
  ...pagedTranscriptReads(source),
162
+ async readLatestAssistantForTurn(sessionId, turnId) {
163
+ const extents = await store.readTranscriptTurns(sessionId, { turnId });
164
+ let result = '';
165
+ for (const extent of extents) {
166
+ for (let ordinal = extent.firstOrdinal; ordinal <= extent.lastOrdinal;) {
167
+ const run = await readRun(sessionId, {
168
+ direction: 'newer',
169
+ throughOrdinal: extent.lastOrdinal,
170
+ position: ordinal,
171
+ });
172
+ if (!run)
173
+ break;
174
+ if (run.invocation.turnId === turnId) {
175
+ for (const { message } of await projectTurn(run)) {
176
+ if (message.type === 'assistant' && message.text.trim())
177
+ result = message.text;
178
+ }
179
+ }
180
+ ordinal = run.lastOrdinal + 1;
181
+ }
182
+ }
183
+ return result;
184
+ },
152
185
  /** One row per Turn, folded from the Turn's own projected messages. */
153
186
  async readTurnContributions(sessionId, throughSequence, position, maxContributions) {
154
187
  const watermark = throughSequence ?? (await highWater(sessionId));
@@ -731,6 +731,10 @@ export class HostWorkHubCoordinationCoordinator {
731
731
  catch {
732
732
  // Invalid output, unavailable candidates, or provider failure cannot
733
733
  // silently become creation or bind an arbitrary existing Session.
734
+ // Preparation failures (for example transcript reads) and injected-model
735
+ // throws bind this admission to clarify, preventing actions for this turn.
736
+ // This differs intentionally from Jev's internal provider/candidate errors:
737
+ // that opt-in adapter returns undefined to preserve the legacy unbound path.
734
738
  return { kind: 'routing', disposition: 'clarify' };
735
739
  }
736
740
  }