maka-agent 0.2.0-dev.22.20260905 → 0.2.0-dev.23.20260906

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 (63) hide show
  1. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  2. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  3. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  4. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  5. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  6. package/node_modules/@maka/core/dist/bot-events.js +18 -63
  7. package/node_modules/@maka/core/dist/model-call-attempt.js +0 -82
  8. package/node_modules/@maka/core/dist/model-call-usage-projection.js +0 -186
  9. package/node_modules/@maka/core/dist/record-schema.js +13 -0
  10. package/node_modules/@maka/core/dist/redaction.js +41 -58
  11. package/node_modules/@maka/core/dist/session-trace.js +5 -1
  12. package/node_modules/@maka/core/dist/usage-ledger-merge.js +7 -9
  13. package/node_modules/@maka/core/dist/workhub-creation-intent.js +34 -20
  14. package/node_modules/@maka/eval/harbor/egress-proxy/entrypoint.sh +2 -1
  15. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +51 -24
  16. package/node_modules/@maka/runtime/dist/bots/base-adapter.js +17 -0
  17. package/node_modules/@maka/runtime/dist/bots/bot-test.js +38 -22
  18. package/node_modules/@maka/runtime/dist/bots/dingtalk-bridge.js +7 -5
  19. package/node_modules/@maka/runtime/dist/bots/discord-bridge.js +4 -4
  20. package/node_modules/@maka/runtime/dist/bots/feishu-bridge.js +4 -5
  21. package/node_modules/@maka/runtime/dist/bots/qq-bridge.js +6 -4
  22. package/node_modules/@maka/runtime/dist/bots/slack-bridge.js +3 -4
  23. package/node_modules/@maka/runtime/dist/bots/telegram-bridge.js +4 -5
  24. package/node_modules/@maka/runtime/dist/bots/wechat-bridge.js +17 -19
  25. package/node_modules/@maka/runtime/dist/bots/wecom-bridge.js +5 -6
  26. package/node_modules/@maka/runtime/dist/bots/ws-bridge-base.js +2 -3
  27. package/node_modules/@maka/runtime/dist/session-trace-projection.js +3 -30
  28. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +4 -1
  29. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  30. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +13 -0
  31. package/node_modules/@maka/runtime-host/dist/client/registered-host-termination.js +0 -59
  32. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +28 -16
  33. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +4 -0
  34. package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +8 -0
  35. package/node_modules/@maka/runtime-host/dist/protocol/index.js +13 -1
  36. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  37. package/node_modules/@maka/runtime-host/dist/protocol/session-collaboration.js +28 -1
  38. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +33 -0
  39. package/node_modules/@maka/runtime-host/dist/server/access-authority.js +26 -1
  40. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +5 -1
  41. package/node_modules/@maka/runtime-host/dist/server/canonical-usage-reader.js +24 -11
  42. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +7 -4
  43. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +70 -6
  44. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +6 -3
  45. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +12 -6
  46. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +9 -2
  47. package/node_modules/@maka/runtime-host/dist/server/root-admission-owner.js +7 -0
  48. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +21 -0
  49. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +151 -5
  50. package/node_modules/@maka/runtime-host/dist/server/usage-pricing-coordinator.js +4 -11
  51. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -15
  52. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +6 -1
  53. package/node_modules/@maka/storage/dist/agent-run-store.js +44 -0
  54. package/node_modules/@maka/storage/dist/conversation-operational-state.js +4 -0
  55. package/node_modules/@maka/storage/dist/execution-stores.js +2 -0
  56. package/node_modules/@maka/storage/dist/model-call-ledger.js +199 -32
  57. package/node_modules/@maka/storage/dist/model-call-usage-sql.js +136 -0
  58. package/node_modules/@maka/storage/dist/operational-state-store-public.js +1 -1
  59. package/node_modules/@maka/storage/dist/operational-state-store.js +5 -3
  60. package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +11 -1
  61. package/node_modules/@maka/storage/dist/sqlite-usage-schema.js +200 -20
  62. package/node_modules/@maka/storage/dist/usage-stores.js +6 -2
  63. package/package.json +1 -1
@@ -274,6 +274,27 @@ export function decodeWorkHubCoordinationActResult(value) {
274
274
  }),
275
275
  };
276
276
  }
277
+ if (result.disposition === 'resume_work') {
278
+ const exact = requireShapedRecord(result, 'WorkHub Coordination resume result', ['disposition', 'outcome', 'targetSessionId'], ['targetTurnId']);
279
+ if (exact.outcome !== 'resume_started' && exact.outcome !== 'already_running') {
280
+ throw invalidProtocolFrame('Invalid WorkHub resume outcome');
281
+ }
282
+ // Only a started continuation names a Turn: the Host has one to name, and
283
+ // the other two outcomes changed nothing that could carry an identity.
284
+ if ((exact.outcome === 'resume_started') !== (exact.targetTurnId !== undefined)) {
285
+ throw invalidProtocolFrame('Invalid WorkHub resume target Turn');
286
+ }
287
+ return {
288
+ disposition: 'resume_work',
289
+ outcome: exact.outcome,
290
+ targetSessionId: requireEntityId(exact.targetSessionId, 'WorkHub target Session id'),
291
+ ...(exact.targetTurnId === undefined
292
+ ? {}
293
+ : {
294
+ targetTurnId: requireEntityId(exact.targetTurnId, 'WorkHub target Turn id'),
295
+ }),
296
+ };
297
+ }
277
298
  throw invalidProtocolFrame('Invalid WorkHub Coordination action disposition');
278
299
  }
279
300
  function decodeWorkHubCoordinationCandidate(value) {
@@ -372,6 +393,18 @@ function decodeWorkHubCoordinationProposal(value) {
372
393
  expects: decodeWorkHubCoordinationStopPreconditions(exact.expects),
373
394
  };
374
395
  }
396
+ if (proposal.disposition === 'resume_work') {
397
+ const exact = requireExactRecord(proposal, 'WorkHub resume proposal', [
398
+ 'disposition',
399
+ 'expects',
400
+ 'resumesActionId',
401
+ ]);
402
+ return {
403
+ disposition: 'resume_work',
404
+ resumesActionId: requireEntityId(exact.resumesActionId, 'WorkHub resume assignment'),
405
+ expects: decodeWorkHubCoordinationStopPreconditions(exact.expects),
406
+ };
407
+ }
375
408
  throw invalidProtocolFrame('Invalid WorkHub Coordination proposal disposition');
376
409
  }
377
410
  function decodeWorkHubCoordinationStopPreconditions(value) {
@@ -20,7 +20,7 @@ import { randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
20
20
  import { join } from 'node:path';
21
21
  import { isDeepStrictEqual } from 'node:util';
22
22
  import { runtimeHostAccessCredentialHash } from '../access-credential-identity.js';
23
- import { encodeCollaborationInvitationCode, } from '../protocol/index.js';
23
+ import { decodeCollaborationDisplayName, encodeCollaborationInvitationCode, } from '../protocol/index.js';
24
24
  import { createRuntimeHostConnectionAuthority, } from './connection-authority.js';
25
25
  import { createAccessCredentialDelivery, discardAccessCredentialDelivery, purgeAccessCredentialDeliveries, } from '../control/access-credential-delivery.js';
26
26
  import { ACCESS_FILE_NAME, assertAccessCredentialFileCapacity, CAPABILITY_PROVIDER_OPERATION_GRANTS, createAccessCredentialFile, effectiveOperationGrants, issuedAccessGrants, readAccessCredentialFile, RuntimeHostAccessCommitOutcomeUnknownError, RuntimeHostAccessInputError, SESSION_GUEST_OPERATION_GRANTS, writeAccessCredentialFile, } from './access-credential-store.js';
@@ -152,6 +152,9 @@ class FileRuntimeHostAccessAuthority {
152
152
  principalId,
153
153
  status: credential.status,
154
154
  createdAt: credential.createdAt,
155
+ ...(credential.displayName === undefined
156
+ ? {}
157
+ : { displayName: credential.displayName }),
155
158
  ...(credential.expiresAt ? { expiresAt: credential.expiresAt } : {}),
156
159
  },
157
160
  ]
@@ -176,6 +179,18 @@ class FileRuntimeHostAccessAuthority {
176
179
  principalId,
177
180
  });
178
181
  }
182
+ renameCollaborationPrincipal(input) {
183
+ const displayName = decodeCollaborationDisplayName(input.displayName);
184
+ return this.#mutate(async () => {
185
+ const matches = (credential) => credential.principalKind === 'session_guest' &&
186
+ credential.principalId === input.principalId &&
187
+ credential.status !== 'revoked';
188
+ if (!this.#file.credentials.some(matches))
189
+ return { renamed: false };
190
+ await this.#commit(createNextAccessCredentialFile(this.#file, this.#file.credentials.map((credential) => matches(credential) ? { ...credential, displayName } : credential), this.#file.sessionGrants), []);
191
+ return { renamed: true };
192
+ });
193
+ }
179
194
  createTurnAccessRequest(principalId, input) {
180
195
  return this.#mutate(async () => {
181
196
  const grant = this.activeSessionGrant(principalId, input.intent.sessionId, 'session_turn_request');
@@ -960,6 +975,16 @@ export async function revokeCollaborationPrincipal(authority, principalId) {
960
975
  return accessPersistenceFailure(error, 'Collaboration Guest revocation outcome is unknown', 'Collaboration Guest could not be revoked');
961
976
  }
962
977
  }
978
+ export async function renameCollaborationPrincipal(authority, input) {
979
+ if (!authority)
980
+ return collaborationUnavailable('collaboration.principal.rename');
981
+ try {
982
+ return { ok: true, result: await authority.renameCollaborationPrincipal(input) };
983
+ }
984
+ catch (error) {
985
+ return accessPersistenceFailure(error, 'Guest alias commit outcome is unknown', 'Guest alias could not be saved');
986
+ }
987
+ }
963
988
  function collaborationUnavailable(operation) {
964
989
  return {
965
990
  ok: false,
@@ -19,7 +19,7 @@
19
19
  import { randomUUID } from 'node:crypto';
20
20
  import { chmod, open, rename, rm } from 'node:fs/promises';
21
21
  import { dirname } from 'node:path';
22
- import { HOST_OPERATION_SPECS, operationAllowsRemoteOwner, decodeSessionTurnAccessRequest, } from '../protocol/index.js';
22
+ import { HOST_OPERATION_SPECS, operationAllowsRemoteOwner, decodeSessionTurnAccessRequest, decodeCollaborationDisplayName, } from '../protocol/index.js';
23
23
  // Schema 4 makes provider ownership downgrade-safe once an association exists.
24
24
  // Ordinary access files remain schema 3 so this feature does not fence a
25
25
  // downgrade before there is an owner association to preserve.
@@ -225,6 +225,7 @@ function encodeAccessCredentialFile(file) {
225
225
  schemaVersion: file.schemaVersion,
226
226
  credentials: file.credentials.map((credential) => ({
227
227
  credentialId: credential.credentialId,
228
+ ...(credential.displayName === undefined ? {} : { displayName: credential.displayName }),
228
229
  credentialHash: credential.credentialHash,
229
230
  principalId: credential.principalId,
230
231
  principalKind: credential.principalKind,
@@ -391,6 +392,9 @@ function decodeStoredCredential(value) {
391
392
  ...(typeof clientInstanceId === 'string' ? { clientInstanceId } : {}),
392
393
  ...(typeof expiresAt === 'string' ? { expiresAt } : {}),
393
394
  ...(revokedAt === undefined ? {} : { revokedAt }),
395
+ ...(value.displayName === undefined
396
+ ? {}
397
+ : { displayName: decodeCollaborationDisplayName(value.displayName) }),
394
398
  };
395
399
  }
396
400
  function decodeStoredSessionGrant(value) {
@@ -16,33 +16,46 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- import { resolveUsageRange } from '@maka/core/model-call-usage-projection';
20
19
  export class CanonicalUsageProjectionIncompleteError extends Error {
21
20
  constructor() {
22
21
  super('Canonical Usage projection is incomplete');
23
22
  this.name = 'CanonicalUsageProjectionIncompleteError';
24
23
  }
25
24
  }
26
- /** Reads and repairs the canonical usage source shared by Host-owned projections. */
27
- export async function readCanonicalUsage(stores, query, now, repair = true) {
28
- // `catchUpModelCallProjection` is a write. Paged log reads call this once per
29
- // page, so a caller that already repaired on its first page can pass
30
- // `repair: false` on later pages to avoid a redundant repair write per page.
25
+ /**
26
+ * Repairs the projection, then asks the ledger for one answer.
27
+ *
28
+ * `catchUpModelCallProjection` is a write. Paged log reads call this once per
29
+ * page, so a caller that already repaired on its first page passes
30
+ * `repair: false` on later pages to avoid a redundant repair write per page.
31
+ */
32
+ async function readCanonical(stores, query, repair, ask) {
31
33
  const repairOutcome = repair
32
34
  ? await stores.modelCalls
33
35
  .catchUpModelCallProjection(query.sessionId === undefined ? undefined : { sessionId: query.sessionId })
34
36
  .catch(() => ({ pendingRuns: 1, unreadableEvents: 0 }))
35
37
  : { pendingRuns: 0, unreadableEvents: 0 };
36
- const page = await stores.modelCalls.modelCallAttempts(resolveUsageRange(query.range, now), query.sessionId);
38
+ const answer = await ask();
37
39
  return {
38
- attempts: page.attempts,
39
- unreadableRecords: page.unreadableRecords + repairOutcome.unreadableEvents,
40
+ projection: answer.projection,
41
+ unreadableRecords: answer.unreadableRecords + repairOutcome.unreadableEvents,
40
42
  pendingRepairs: repairOutcome.pendingRuns,
41
43
  };
42
44
  }
45
+ export function readCanonicalUsageSummary(stores, query, now, repair = true) {
46
+ return readCanonical(stores, query, repair, () => stores.modelCalls.modelCallSummary(query, now));
47
+ }
48
+ export function readCanonicalUsageBuckets(stores, query, groupBy, now, repair = true) {
49
+ return readCanonical(stores, query, repair, () => stores.modelCalls.modelCallBuckets(query, groupBy, now));
50
+ }
51
+ export function readCanonicalUsageLogs(stores, query, now, limit, repair = true) {
52
+ // Both sources are newest-first, so a merged page can only be drawn from each
53
+ // source's own first `offset + limit` rows.
54
+ return readCanonical(stores, query, repair, () => stores.modelCalls.modelCallLogs(query, now, 0, limit));
55
+ }
43
56
  /** Runs one bounded repair pass and rejects data still unsafe for durable derivatives. */
44
- export async function readCompleteCanonicalUsage(stores, query, now) {
45
- const source = await readCanonicalUsage(stores, query, now);
57
+ export async function readCompleteCanonicalUsageSummary(stores, query, now) {
58
+ const source = await readCanonicalUsageSummary(stores, query, now);
46
59
  if (source.unreadableRecords > 0 || source.pendingRepairs > 0) {
47
60
  throw new CanonicalUsageProjectionIncompleteError();
48
61
  }
@@ -22,7 +22,7 @@ import { mergeUsageBuckets, mergeUsageSummary } from '@maka/core/usage-ledger-me
22
22
  import { authenticateInteractiveDailyReviewAuthorityWriter, } from '@maka/storage/daily-review-authority';
23
23
  import { authenticateInteractiveUsageStoresWriter, } from '@maka/storage/usage-stores';
24
24
  import { DAILY_REVIEW_RESULT_MAX_BYTES, } from '../protocol/index.js';
25
- import { CanonicalUsageProjectionIncompleteError, readCompleteCanonicalUsage, } from './canonical-usage-reader.js';
25
+ import { CanonicalUsageProjectionIncompleteError, readCanonicalUsageBuckets, readCompleteCanonicalUsageSummary, } from './canonical-usage-reader.js';
26
26
  const ARCHIVE_LIMIT = 180;
27
27
  const SCHEDULER_INTERVAL_MS = 60_000;
28
28
  /** Host-owned Daily Review data, generation, archive, and scheduling boundary. */
@@ -196,7 +196,10 @@ export class HostDailyReviewCoordinator {
196
196
  }
197
197
  async #buildSummary(range, now) {
198
198
  const query = dailyUsageQuery(range);
199
- const canonical = await readCompleteCanonicalUsage(this.#usage, query, now);
199
+ // The summary read repairs the projection and refuses an incomplete one; the
200
+ // bucket read that follows reuses that pass rather than repairing again.
201
+ const canonical = await readCompleteCanonicalUsageSummary(this.#usage, query, now);
202
+ const canonicalModels = await readCanonicalUsageBuckets(this.#usage, query, 'model', now, false);
200
203
  const [usageSummary, toolBuckets, modelBuckets, sessions] = await Promise.all([
201
204
  this.#usage.telemetry.summary(query),
202
205
  this.#usage.telemetry.buckets(query, 'tool'),
@@ -205,10 +208,10 @@ export class HostDailyReviewCoordinator {
205
208
  ]);
206
209
  return buildDailyReviewSummary({
207
210
  day: range,
208
- usageSummary: mergeUsageSummary(usageSummary, canonical, query, now),
211
+ usageSummary: mergeUsageSummary(usageSummary, canonical),
209
212
  sessions: pickDailyReviewSessions(collapseSessionRevisions(sessions), range, DAILY_REVIEW_LIST_LIMIT),
210
213
  topTools: pickDailyReviewTopEntries(toolBuckets, DAILY_REVIEW_LIST_LIMIT),
211
- topModels: pickDailyReviewTopEntries(mergeUsageBuckets(modelBuckets, canonical, query, 'model', now).buckets, DAILY_REVIEW_LIST_LIMIT),
214
+ topModels: pickDailyReviewTopEntries(mergeUsageBuckets(modelBuckets, canonicalModels).buckets, DAILY_REVIEW_LIST_LIMIT),
212
215
  });
213
216
  }
214
217
  async #run(input) {
@@ -119,7 +119,7 @@ import { HostTurnControlCoordinator } from './turn-control-coordinator.js';
119
119
  import { HostUsagePricingCoordinator } from './usage-pricing-coordinator.js';
120
120
  import { HostWebSearchCoordinator } from './web-search-coordinator.js';
121
121
  import { HostWorkHubCoordinationCoordinator } from './workhub-coordination-coordinator.js';
122
- import { WorkHubActionEffectFailure } from './workhub-coordination-action-gate.js';
122
+ import { WorkHubActionEffectFailure, workHubResumedTurnId, } from './workhub-coordination-action-gate.js';
123
123
  import { createHostWebSearchService, createHostWebSearchToolFromService, resolveHostTavilyWebSearchReadiness, shouldResolveHostTavilyWebSearchReadiness, } from './web-search-tool.js';
124
124
  import { createHostWebFetchService, createHostWebFetchToolFromService } from './web-fetch-tool.js';
125
125
  import { createHostExecutionArtifactServices } from './execution-artifacts.js';
@@ -391,6 +391,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
391
391
  let deepResearch;
392
392
  let dailyReview;
393
393
  const rootPort = {
394
+ readLatestRootTurnLineage: (identity) => requireRootCoordinator(rootCoordinator).readLatestRootTurnLineage(identity),
394
395
  readSessionHeader: (sessionId) => requireRootCoordinator(rootCoordinator).readSessionHeader(sessionId),
395
396
  readRootState: (sessionId) => requireRootCoordinator(rootCoordinator).readRootState(sessionId),
396
397
  claimStopFence: (input, commitQueueFence, admission) => requireRootCoordinator(rootCoordinator).claimStopFence(input, commitQueueFence, admission),
@@ -1054,13 +1055,76 @@ export async function createExecutionRuntimeHostComposition(context, options = {
1054
1055
  turnId: disposition.turnId,
1055
1056
  runId: disposition.runId,
1056
1057
  };
1057
- if (isActiveWorkHubRoot(coordinator, identity))
1058
+ const latest = await coordinator.readLatestRootTurnLineage(identity);
1059
+ if (isActiveWorkHubRoot(coordinator, latest))
1058
1060
  return 'not_retired';
1059
1061
  // The same restart window as `stopOwnedWorkHubRoot`: an unregistered
1060
1062
  // root is not evidence that its work ended.
1061
- const snapshot = await coordinator.read(identity);
1063
+ const snapshot = await coordinator.read(latest);
1062
1064
  return isHostedExecutionTerminal(snapshot) ? 'retired' : 'recovering';
1063
1065
  },
1066
+ // Resolve and resume only the execution lineage owned by this
1067
+ // delegation. A Session-wide latest-failure query could otherwise
1068
+ // continue unrelated work started directly in the same Session.
1069
+ resumeDelegation: async (assignment, context, actionId) => {
1070
+ const disposition = await messages.readMessageExecutionDisposition(assignment.targetSessionId, assignment.targetMessageId);
1071
+ if (disposition.kind === 'recovering') {
1072
+ throw new WorkHubActionEffectFailure('host_not_ready', 'WorkHub is still recovering the delegated execution');
1073
+ }
1074
+ if (disposition.kind !== 'owned_root') {
1075
+ throw new WorkHubActionEffectFailure('operation_conflict', 'WorkHub delegated execution is not resumable');
1076
+ }
1077
+ const source = await coordinator.readLatestRootTurnLineage({
1078
+ sessionId: assignment.targetSessionId,
1079
+ turnId: disposition.turnId,
1080
+ runId: disposition.runId,
1081
+ });
1082
+ if (isActiveWorkHubRoot(coordinator, source)) {
1083
+ return { outcome: 'already_running' };
1084
+ }
1085
+ const snapshot = await coordinator.read(source);
1086
+ if (!isHostedExecutionTerminal(snapshot)) {
1087
+ throw new WorkHubActionEffectFailure('host_not_ready', 'WorkHub is still recovering the delegated execution');
1088
+ }
1089
+ if (snapshot.status !== 'failed' && snapshot.status !== 'cancelled') {
1090
+ throw new WorkHubActionEffectFailure('operation_conflict', 'WorkHub delegated execution is not resumable');
1091
+ }
1092
+ const plan = await coordinator.handlers['turn.resume.query']({ sessionId: assignment.targetSessionId, sourceRunId: source.runId }, context);
1093
+ if (!plan.ok)
1094
+ throw new WorkHubActionEffectFailure(plan.error.code, plan.error.message);
1095
+ if (plan.result.disposition === 'parked') {
1096
+ throw new WorkHubActionEffectFailure(plan.result.reason === 'resume_feature_disabled'
1097
+ ? 'operation_unavailable'
1098
+ : 'operation_conflict', plan.result.reason === 'resume_feature_disabled'
1099
+ ? 'Safe-boundary resume is disabled for this Runtime Host'
1100
+ : 'WorkHub delegated execution is not resumable');
1101
+ }
1102
+ if (plan.result.sourceRunId !== source.runId ||
1103
+ plan.result.sourceTurnId !== source.turnId) {
1104
+ throw new WorkHubActionEffectFailure('operation_conflict', 'WorkHub resume source lineage changed during planning');
1105
+ }
1106
+ const targetTurnId = workHubResumedTurnId(actionId);
1107
+ const started = await coordinator.handlers['turn.resume.start']({
1108
+ sessionId: assignment.targetSessionId,
1109
+ turnId: targetTurnId,
1110
+ sourceRunId: plan.result.sourceRunId,
1111
+ sourceRuntimeEventHighWater: plan.result.sourceRuntimeEventHighWater,
1112
+ }, context);
1113
+ if (!started.ok) {
1114
+ throw new WorkHubActionEffectFailure(started.error.code, started.error.message);
1115
+ }
1116
+ if (started.result.kind === 'parked') {
1117
+ throw new WorkHubActionEffectFailure(started.result.plan.reason === 'resume_feature_disabled'
1118
+ ? 'operation_unavailable'
1119
+ : 'operation_conflict', started.result.plan.reason === 'resume_feature_disabled'
1120
+ ? 'Safe-boundary resume is disabled for this Runtime Host'
1121
+ : 'WorkHub delegated execution is not resumable');
1122
+ }
1123
+ return {
1124
+ outcome: 'resume_started',
1125
+ targetTurnId: started.result.turn.turnId,
1126
+ };
1127
+ },
1064
1128
  retireDelegation: async (assignment, retirement) => {
1065
1129
  const disposition = await messages.cancelMessageIfPending(assignment.targetSessionId, assignment.targetMessageId, retirement.cancellationClaimId);
1066
1130
  if (disposition.kind === 'recovering') {
@@ -1076,11 +1140,11 @@ export async function createExecutionRuntimeHostComposition(context, options = {
1076
1140
  return { outcome: 'not_owned', targetTurnId: disposition.turnId };
1077
1141
  }
1078
1142
  if (disposition.kind === 'owned_root') {
1079
- const identity = {
1143
+ const identity = await coordinator.readLatestRootTurnLineage({
1080
1144
  sessionId: assignment.targetSessionId,
1081
1145
  turnId: disposition.turnId,
1082
1146
  runId: disposition.runId,
1083
- };
1147
+ });
1084
1148
  return retirement.cause === 'direct_stop'
1085
1149
  ? stopOwnedWorkHubRoot(coordinator, identity, retirement.cancellationClaimId)
1086
1150
  : stopReplacedWorkHubRoot(coordinator, identity);
@@ -1205,7 +1269,7 @@ export async function createExecutionRuntimeHostComposition(context, options = {
1205
1269
  },
1206
1270
  },
1207
1271
  resolveCreateTarget: async () => {
1208
- const { projectId: _projectId, ...target } = await sessionCatalog.resolveExternalSessionImportTarget();
1272
+ const { projectId: _projectId, ...target } = await sessionCatalog.resolveDefaultCreateTarget();
1209
1273
  return { ...target, permissionMode: 'explore' };
1210
1274
  },
1211
1275
  requestDrain: context.requestDrain,
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { ExternalSessionImporter } from '@maka/storage/external-sessions';
20
20
  import { EXTERNAL_SESSION_CWD_MAX_BYTES, EXTERNAL_SESSION_IMPORTED_SESSION_IDS_MAX_ITEMS, EXTERNAL_SESSION_NAME_MAX_BYTES, EXTERNAL_SESSION_PAGE_MAX_ITEMS, EXTERNAL_SESSION_RESULT_MAX_BYTES, EXTERNAL_SESSION_SOURCE_MAX_ITEMS, EXTERNAL_SESSION_SOURCE_SESSION_ID_MAX_BYTES, } from '../protocol/index.js';
21
- import { projectSessionCatalogRecord, SessionOperationFailure, } from './session-catalog-coordinator.js';
21
+ import { projectSessionCatalogRecord, SessionOperationFailure, NoUsableImportModelError, } from './session-catalog-coordinator.js';
22
22
  import { WorkspaceResolutionError } from './workspace-resolver.js';
23
23
  /** Source discovery/conversion stays host-side so raw transcripts never cross the wire. */
24
24
  export class HostExternalSessionCoordinator {
@@ -172,6 +172,9 @@ export class HostExternalSessionCoordinator {
172
172
  target = await this.#resolveTarget();
173
173
  }
174
174
  catch (error) {
175
+ if (error instanceof NoUsableImportModelError) {
176
+ return importFailure('model_unavailable', error.message);
177
+ }
175
178
  if (error instanceof SessionOperationFailure) {
176
179
  return importFailure(error.code, error.message);
177
180
  }
@@ -194,9 +197,9 @@ export class HostExternalSessionCoordinator {
194
197
  }
195
198
  catch (error) {
196
199
  if (!commitAttempted) {
197
- return importFailure(isSourceSessionNotFound(error) ? 'not_found' : 'invalid_request', isSourceSessionNotFound(error)
200
+ return importFailure(isSourceSessionNotFound(error) ? 'not_found' : 'source_unreadable', isSourceSessionNotFound(error)
198
201
  ? 'External Session does not exist'
199
- : 'External Session could not be converted');
202
+ : 'External Session could not be read or converted');
200
203
  }
201
204
  this.#requestDrain();
202
205
  return importFailure('commit_outcome_unknown', 'External Session import outcome is unknown; check the Session list before retrying');
@@ -25,7 +25,7 @@ import { removeHostRegistration, writeHostRegistration } from '../control/regist
25
25
  import { decodeClientFrame, encodeProtocolMessage, HOST_OPERATION_SPECS, negotiateProtocol, RUNTIME_HOST_COMPATIBILITY_EPOCH, RUNTIME_HOST_PROTOCOL_VERSION, RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION, requireHostGeneration, } from '../protocol/index.js';
26
26
  import { RuntimeHostConnectionSession, } from './connection-session.js';
27
27
  import { composeOperationHandlers, createUnavailableDomainOperationHandlers, } from './operation-dispatcher.js';
28
- import { issueAccessCredential, acknowledgeCollaborationTurnRequest, createCollaborationTurnRequest, decideCollaborationTurnRequest, withdrawCollaborationTurnRequest, finalizeAccessCredential, prepareCollaborationInvitation, queryCollaborationTurnRequests, prepareAccessCredential, prepareAccessCredentialRotation, replaceAccessCredential, revokeAccessCredential, revokeAccessPrincipal, revokeAccessCredentialRotation, revokeCollaborationGrant, revokeCollaborationPrincipal, } from './access-authority.js';
28
+ import { issueAccessCredential, acknowledgeCollaborationTurnRequest, createCollaborationTurnRequest, decideCollaborationTurnRequest, withdrawCollaborationTurnRequest, finalizeAccessCredential, prepareCollaborationInvitation, queryCollaborationTurnRequests, prepareAccessCredential, prepareAccessCredentialRotation, replaceAccessCredential, revokeAccessCredential, revokeAccessPrincipal, revokeAccessCredentialRotation, revokeCollaborationGrant, revokeCollaborationPrincipal, renameCollaborationPrincipal, } from './access-authority.js';
29
29
  import { runtimeHostLogBuffer } from '../process-diagnostics.js';
30
30
  import { startLocalRuntimeHostListenerSet, } from './listener-set.js';
31
31
  import { HostResidencyRegistry } from './host-residency-registry.js';
@@ -164,7 +164,8 @@ export class RuntimeHostKernel {
164
164
  get compositionDescriptor() {
165
165
  return this.#options.composition.descriptor;
166
166
  }
167
- close() {
167
+ close(input) {
168
+ this.#shutdownReason ??= input?.reason;
168
169
  this.#requestDrain();
169
170
  return this.closed;
170
171
  }
@@ -502,6 +503,7 @@ export class RuntimeHostKernel {
502
503
  ok: true,
503
504
  result: {
504
505
  ...this.#statusSnapshot(),
506
+ upgradeBlockingActivity: this.#hasUpgradeBlockingActivity(0),
505
507
  compositionModules: this.#composition?.moduleIds ?? [],
506
508
  residencies: this.#residencies.snapshot(),
507
509
  protocolVersion: RUNTIME_HOST_PROTOCOL_VERSION,
@@ -531,7 +533,7 @@ export class RuntimeHostKernel {
531
533
  },
532
534
  };
533
535
  }
534
- if (!input.allowInterruptActiveTasks && this.#hasUpgradeBlockingActivity()) {
536
+ if (!input.allowInterruptActiveTasks && this.#hasUpgradeBlockingActivity(1)) {
535
537
  return { ok: true, result: { kind: 'active_tasks' } };
536
538
  }
537
539
  this.#shutdownReason = 'retirement';
@@ -560,6 +562,7 @@ export class RuntimeHostKernel {
560
562
  },
561
563
  },
562
564
  'collaboration.grant.revoke': async (input) => this.#settleAccessCredentialMutation(revokeCollaborationGrant(this.#options.accessAuthority, input)),
565
+ 'collaboration.principal.rename': async (input) => renameCollaborationPrincipal(this.#options.accessAuthority, input),
563
566
  'collaboration.principal.revoke': async (input) => this.#settleAccessCredentialMutation(revokeCollaborationPrincipal(this.#options.accessAuthority, input.principalId)),
564
567
  'collaboration.turn-request.create': async (input, context) => this.#settleAccessCredentialMutation(createCollaborationTurnRequest(this.#options.accessAuthority, context.principal, input)),
565
568
  'collaboration.turn-request.query': async (input, context) => queryCollaborationTurnRequests(this.#options.accessAuthority, {
@@ -605,13 +608,16 @@ export class RuntimeHostKernel {
605
608
  residencies: this.#residencies.snapshot(),
606
609
  };
607
610
  }
608
- #hasUpgradeBlockingActivity() {
611
+ #hasUpgradeBlockingActivity(selfCommands) {
609
612
  // The request's own accepted transport is expected. Any other live
610
613
  // connection arrived after discovery or remained attached and therefore
611
- // requires explicit interruption authority before retirement.
614
+ // requires explicit interruption authority before retirement. Callers
615
+ // pass how many of the in-flight commands are their own: the
616
+ // `host.upgrade.prepare` command counts itself, while the diagnostics
617
+ // query path runs outside the command counter.
612
618
  if (this.#acceptedTransports.size > 1)
613
619
  return true;
614
- if (this.#activeCommandOperations > 1)
620
+ if (this.#activeCommandOperations > selfCommands)
615
621
  return true;
616
622
  return this.#residencies.drainCount > 0;
617
623
  }
@@ -112,11 +112,18 @@ export class HostMessageCoordinator {
112
112
  for (const messageId of input.messageIds) {
113
113
  const disposition = await this.#resolveMessageExecution(input.sessionId, messageId);
114
114
  if (disposition.kind === 'owned_root' || disposition.kind === 'shared_turn') {
115
+ // This read projects current execution, including safe-boundary
116
+ // continuations. The Message's durable admission ownership is unchanged.
117
+ const latest = await this.#root.readLatestRootTurnLineage({
118
+ sessionId: input.sessionId,
119
+ turnId: disposition.turnId,
120
+ runId: disposition.runId,
121
+ });
115
122
  resolutions.push({
116
123
  messageId,
117
124
  state: 'owned',
118
- turnId: disposition.turnId,
119
- runId: disposition.runId,
125
+ turnId: latest.turnId,
126
+ runId: latest.runId,
120
127
  });
121
128
  continue;
122
129
  }
@@ -62,6 +62,13 @@ export class RootAdmissionOwner {
62
62
  previousRootTurnId: current?.turnId ?? null,
63
63
  });
64
64
  const admission = result.admission;
65
+ if (result.kind === 'conflict') {
66
+ const known = this.#admissionsBySession.get(input.sessionId)?.get(admission.turnId);
67
+ if (!known || !sameRootAdmission(known, admission)) {
68
+ throw new Error('Durable Root Turn conflict is outside the owned chain');
69
+ }
70
+ return Object.freeze({ kind: 'conflict', admission: known });
71
+ }
65
72
  if (admission.sessionId !== input.sessionId ||
66
73
  admission.turnId !== input.turnId ||
67
74
  admission.previousRootTurnId !== (current?.turnId ?? null)) {
@@ -223,6 +223,21 @@ export class RootTurnCoordinator {
223
223
  }
224
224
  return this.#admissions.has(sessionId) ? { kind: 'reserved' } : { kind: 'idle' };
225
225
  }
226
+ /** Returns the newest Host-admitted continuation descended from one root execution. */
227
+ async readLatestRootTurnLineage(identity) {
228
+ const origin = await this.stores.agentRunStore.readRootTurnAdmission(identity.sessionId, identity.turnId);
229
+ if (!origin || origin.runId !== identity.runId) {
230
+ throw new RuntimeMessageAuthorityInvariantError(`Root execution ${identity.turnId}/${identity.runId} has no durable admission`);
231
+ }
232
+ let latest = origin;
233
+ while (true) {
234
+ const continuation = await this.stores.agentRunStore.readRootTurnContinuationAdmission(identity.sessionId, latest.turnId, latest.runId);
235
+ if (!continuation)
236
+ break;
237
+ latest = continuation;
238
+ }
239
+ return { sessionId: latest.sessionId, turnId: latest.turnId, runId: latest.runId };
240
+ }
226
241
  startHostedExternalTransition(input, context) {
227
242
  return this.startInteractiveRootMessage({
228
243
  sessionId: input.sessionId,
@@ -1291,6 +1306,12 @@ export class RootTurnCoordinator {
1291
1306
  sourceMessages: [],
1292
1307
  admittedAt: Date.now(),
1293
1308
  });
1309
+ if (admitted.kind === 'conflict') {
1310
+ return {
1311
+ kind: 'complete',
1312
+ outcome: operationConflict('This root execution already has a different continuation Turn'),
1313
+ };
1314
+ }
1294
1315
  if (admitted.admission.runId !== continuation.runId ||
1295
1316
  !isDeepStrictEqual(admitted.admission.execution, execution)) {
1296
1317
  throw new RuntimeMessageAuthorityInvariantError('Safe-boundary continuation admission changed identity');