maka-agent 0.2.0-dev.14.20260902 → 0.2.0-dev.16.20260903

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 (160) hide show
  1. package/dist/cli-core.js +0 -1
  2. package/dist/pi-transcript.js +48 -0
  3. package/dist/pi-tui-form-interaction.js +497 -0
  4. package/dist/pi-tui-pickers.js +256 -55
  5. package/dist/pi-tui-runner.js +215 -12
  6. package/dist/runtime-host-cli.js +17 -42
  7. package/dist/runtime-host-lifecycle-transaction.js +31 -13
  8. package/dist/runtime-host-managed-lifecycle-manager.js +1 -1
  9. package/dist/runtime-host-onboarding.js +3 -1
  10. package/dist/runtime-host-openrc-service.js +469 -0
  11. package/dist/runtime-host-run-command.js +6 -2
  12. package/dist/runtime-host-service-command.js +3 -3
  13. package/dist/runtime-host-service-management-command.js +43 -15
  14. package/dist/runtime-host-session-channel.js +3 -0
  15. package/dist/runtime-host-session-driver.js +21 -6
  16. package/dist/runtime-host-setup-command.js +30 -31
  17. package/dist/tui-context-refresh.js +103 -0
  18. package/dist/tui-copy-catalog.js +73 -0
  19. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  20. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  21. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  22. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  23. package/node_modules/@maka/core/dist/chat-model-choice.js +4 -0
  24. package/node_modules/@maka/core/dist/computer-use.js +27 -92
  25. package/node_modules/@maka/core/dist/interaction.js +577 -3
  26. package/node_modules/@maka/core/dist/llm-connections.js +4 -0
  27. package/node_modules/@maka/core/dist/local-memory.js +2 -2
  28. package/node_modules/@maka/core/dist/model-call-usage-projection.js +3 -0
  29. package/node_modules/@maka/core/dist/model-metadata.js +26 -7
  30. package/node_modules/@maka/core/dist/model-thinking.js +17 -0
  31. package/node_modules/@maka/core/dist/provider-registry.js +1 -0
  32. package/node_modules/@maka/core/dist/runtime-event.js +23 -1
  33. package/node_modules/@maka/core/dist/scheduled-task.js +4 -0
  34. package/node_modules/@maka/core/dist/session.js +95 -2
  35. package/node_modules/@maka/core/dist/text-sanitize.js +19 -0
  36. package/node_modules/@maka/core/dist/usage-ledger-merge.js +3 -0
  37. package/node_modules/@maka/core/dist/usage-record-schema.js +11 -5
  38. package/node_modules/@maka/core/dist/workhub-creation-intent.js +75 -9
  39. package/node_modules/@maka/core/dist/workhub-session-resolver.js +50 -0
  40. package/node_modules/@maka/core/package.json +2 -0
  41. package/node_modules/@maka/eval/dist/harbor-external-subject.js +2 -1
  42. package/node_modules/@maka/eval/dist/harness-executor.js +3 -2
  43. package/node_modules/@maka/eval/dist/install-preflight.js +39 -10
  44. package/node_modules/@maka/eval/dist/process-termination.js +83 -0
  45. package/node_modules/@maka/mcp/dist/sep-2243.js +3 -3
  46. package/node_modules/@maka/runtime/dist/agent-run.js +6 -3
  47. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +332 -118
  48. package/node_modules/@maka/runtime/dist/ai-sdk-compaction-contract.js +6 -1
  49. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +171 -341
  50. package/node_modules/@maka/runtime/dist/bots/telegram-bridge.js +3 -26
  51. package/node_modules/@maka/runtime/dist/bundled-skill-catalog.generated.js +1 -1
  52. package/node_modules/@maka/runtime/dist/computer-use-codec.js +1 -141
  53. package/node_modules/@maka/runtime/dist/computer-use-tools.js +23 -105
  54. package/node_modules/@maka/runtime/dist/context-budget-policy.js +20 -26
  55. package/node_modules/@maka/runtime/dist/context-budget.js +18 -20
  56. package/node_modules/@maka/runtime/dist/conversation-copy.js +23 -19
  57. package/node_modules/@maka/runtime/dist/cua-frame-state.js +1 -69
  58. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +267 -0
  59. package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +6 -9
  60. package/node_modules/@maka/runtime/dist/history-compact-summarizer.js +67 -23
  61. package/node_modules/@maka/runtime/dist/history-compact-summary-validation.js +11 -18
  62. package/node_modules/@maka/runtime/dist/history-compaction.js +66 -73
  63. package/node_modules/@maka/runtime/dist/interaction-authority.js +69 -0
  64. package/node_modules/@maka/runtime/dist/mcp-tools.js +13 -1
  65. package/node_modules/@maka/runtime/dist/memory-extraction.js +11 -11
  66. package/node_modules/@maka/runtime/dist/model-adapter.js +24 -6
  67. package/node_modules/@maka/runtime/dist/model-factory.js +14 -5
  68. package/node_modules/@maka/runtime/dist/oauth-provider-contracts.js +28 -1
  69. package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +7 -6
  70. package/node_modules/@maka/runtime/dist/opencode-session-header.js +29 -0
  71. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +11 -1
  72. package/node_modules/@maka/runtime/dist/runtime-kernel.js +12 -11
  73. package/node_modules/@maka/runtime/dist/runtime-read-model.js +51 -13
  74. package/node_modules/@maka/runtime/dist/scheduled-task-tools.js +3 -0
  75. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +26 -0
  76. package/node_modules/@maka/runtime/dist/session-manager.js +1 -0
  77. package/node_modules/@maka/runtime/dist/session-projection-helpers.js +16 -1
  78. package/node_modules/@maka/runtime/dist/stream-graph-projection.js +6 -0
  79. package/node_modules/@maka/runtime/dist/stream-graph-read-model.js +5 -0
  80. package/node_modules/@maka/runtime/dist/stream-usage-fallback-fetch.js +74 -0
  81. package/node_modules/@maka/runtime/dist/subscription-credentials.js +51 -4
  82. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +47 -3
  83. package/node_modules/@maka/runtime/dist/system-prompt/main-session-prompt.js +20 -1
  84. package/node_modules/@maka/runtime/dist/test-connection.js +18 -11
  85. package/node_modules/@maka/runtime/dist/tool-runtime.js +183 -0
  86. package/node_modules/@maka/runtime/package.json +1 -0
  87. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +14 -0
  88. package/node_modules/@maka/runtime-host/dist/client/client-capability-channel.js +84 -1
  89. package/node_modules/@maka/runtime-host/dist/client/host-profile.js +68 -51
  90. package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
  91. package/node_modules/@maka/runtime-host/dist/client/owner-connection-code.js +8 -5
  92. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +342 -36
  93. package/node_modules/@maka/runtime-host/dist/client/reconnect-lifecycle.js +60 -4
  94. package/node_modules/@maka/runtime-host/dist/peer-mesh/index.js +3 -3
  95. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +1 -1
  96. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +48 -60
  97. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +847 -431
  98. package/node_modules/@maka/runtime-host/dist/peer-mesh/owner.js +33 -74
  99. package/node_modules/@maka/runtime-host/dist/peer-mesh/store.js +82 -69
  100. package/node_modules/@maka/runtime-host/dist/peer-reachability/index.js +21 -0
  101. package/node_modules/@maka/runtime-host/dist/peer-reachability/model.js +202 -0
  102. package/node_modules/@maka/runtime-host/dist/peer-reachability/owner.js +119 -0
  103. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +245 -0
  104. package/node_modules/@maka/runtime-host/dist/protocol/agent-graph.js +4 -1
  105. package/node_modules/@maka/runtime-host/dist/protocol/client-capability.js +68 -0
  106. package/node_modules/@maka/runtime-host/dist/protocol/connection-effects.js +15 -8
  107. package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +7 -23
  108. package/node_modules/@maka/runtime-host/dist/protocol/index.js +47 -1
  109. package/node_modules/@maka/runtime-host/dist/protocol/oauth.js +25 -1
  110. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +1 -0
  111. package/node_modules/@maka/runtime-host/dist/protocol/peer-mesh.js +14 -31
  112. package/node_modules/@maka/runtime-host/dist/protocol/scheduled-task.js +19 -6
  113. package/node_modules/@maka/runtime-host/dist/protocol/session-collaboration.js +36 -1
  114. package/node_modules/@maka/runtime-host/dist/protocol/session-revision.js +11 -2
  115. package/node_modules/@maka/runtime-host/dist/protocol/usage-pricing.js +16 -2
  116. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +51 -2
  117. package/node_modules/@maka/runtime-host/dist/server/access-authority.js +35 -2
  118. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +1 -0
  119. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +12 -10
  120. package/node_modules/@maka/runtime-host/dist/server/client-capability-invocation-broker.js +163 -30
  121. package/node_modules/@maka/runtime-host/dist/server/connection-effect-coordinator.js +38 -14
  122. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +45 -22
  123. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +78 -20
  124. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +4 -2
  125. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +2 -1
  126. package/node_modules/@maka/runtime-host/dist/server/execution-service.js +49 -28
  127. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -6
  128. package/node_modules/@maka/runtime-host/dist/server/interaction-coordinator.js +130 -27
  129. package/node_modules/@maka/runtime-host/dist/server/interaction-projection.js +30 -1
  130. package/node_modules/@maka/runtime-host/dist/server/listener-set.js +2 -4
  131. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +16 -3
  132. package/node_modules/@maka/runtime-host/dist/server/oauth-coordinator.js +66 -3
  133. package/node_modules/@maka/runtime-host/dist/server/oauth-execution-authority.js +20 -3
  134. package/node_modules/@maka/runtime-host/dist/server/peer-listener.js +8 -21
  135. package/node_modules/@maka/runtime-host/dist/server/peer-mesh-authority.js +1 -1
  136. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +6 -3
  137. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +72 -12
  138. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +48 -18
  139. package/node_modules/@maka/runtime-host/dist/server/session-turn-access-request-coordinator.js +43 -26
  140. package/node_modules/@maka/runtime-host/dist/server/usage-pricing-coordinator.js +15 -1
  141. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +229 -4
  142. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +146 -8
  143. package/node_modules/@maka/runtime-host/dist/transport/peer-native.js +32 -6
  144. package/node_modules/@maka/runtime-host/package.json +1 -0
  145. package/node_modules/@maka/storage/dist/agent-run-store.js +18 -5
  146. package/node_modules/@maka/storage/dist/codex-session-adapter.js +14 -0
  147. package/node_modules/@maka/storage/dist/execution-stores.js +5 -0
  148. package/node_modules/@maka/storage/dist/interaction-store.js +13 -1
  149. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +9 -3
  150. package/node_modules/@maka/storage/dist/runtime-policy/coordinator.js +49 -19
  151. package/node_modules/@maka/storage/dist/runtime-policy/oauth-login-receipt-document.js +3 -1
  152. package/node_modules/@maka/storage/dist/runtime-policy/onboarding-transaction.js +13 -5
  153. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +4 -2
  154. package/node_modules/@maka/storage/dist/session-store.js +39 -6
  155. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +16 -9
  156. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +30 -3
  157. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +117 -0
  158. package/node_modules/@maka/storage/dist/sqlite-usage-store.js +29 -6
  159. package/node_modules/@maka/storage/dist/usage-stores.js +3 -1
  160. package/package.json +1 -1
@@ -21,7 +21,7 @@ import { mkdir } from 'node:fs/promises';
21
21
  import { join } from 'node:path';
22
22
  import { isDeepStrictEqual } from 'node:util';
23
23
  import { normalizeMessageContent } from '@maka/core/events';
24
- import { WORKHUB_COORDINATION_SESSION_ID, WORKHUB_COORDINATION_SESSION_ROLE, WORKHUB_COORDINATION_REPLACEMENT_SCHEMA_VERSION, isWorkHubCoordinationSession, isWorkHubCoordinationSessionId, } from '@maka/core/session';
24
+ import { WORKHUB_COORDINATION_SESSION_ID, WORKHUB_COORDINATION_SESSION_ROLE, WORKHUB_COORDINATION_REPLACEMENT_SCHEMA_VERSION, WORKHUB_COORDINATION_STOP_SCHEMA_VERSION, isWorkHubCoordinationSession, isWorkHubCoordinationSessionId, } from '@maka/core/session';
25
25
  import { WORKHUB_COORDINATION_SUMMARY_MAX_BYTES, WORKHUB_COORDINATION_TEXT_MAX_BYTES, } from '../protocol/index.js';
26
26
  import { SessionOperationFailure } from './session-catalog-coordinator.js';
27
27
  import { WorkHubActionEffectFailure, WorkHubActionGateFailure, WorkHubCoordinationActionGate, } from './workhub-coordination-action-gate.js';
@@ -58,9 +58,11 @@ export class HostWorkHubCoordinationCoordinator {
58
58
  #resolveCreateTarget;
59
59
  #requestDrain;
60
60
  #actionGate;
61
+ #readDelegationRetirement;
61
62
  constructor(options) {
62
63
  this.#coordinationCwd = join(options.stateRoot, COORDINATION_CWD_DIRECTORY);
63
64
  this.#stores = options.stores;
65
+ this.#readDelegationRetirement = options.sessionActions.readDelegationRetirement;
64
66
  this.#admission = options.admission;
65
67
  this.#continuity = options.continuity;
66
68
  this.#executions = options.executions;
@@ -68,10 +70,21 @@ export class HostWorkHubCoordinationCoordinator {
68
70
  this.#requestDrain = options.requestDrain;
69
71
  this.#actionGate = new WorkHubCoordinationActionGate({
70
72
  listSessions: () => this.#stores.listHeaders(),
73
+ // The global action owner is committed under the same Coordination
74
+ // admission that serializes every durable Coordination fact, so a
75
+ // concurrent action cannot slip between the claim and the fact it owns.
76
+ claimAction: (claim) => this.#admission.run(WORKHUB_COORDINATION_SESSION_ID, () => this.#stores.claimWorkHubAction(claim)),
77
+ // Read without the admission lease: it is a durable point lookup by
78
+ // primary key, and the claim it finds was committed under that lease.
79
+ readActionClaim: (actionId) => this.#stores.readWorkHubActionClaim(actionId),
80
+ probeTargetRemoval: async (sessionId) => (await this.#stores.probeSessionRemoval(sessionId)).kind,
71
81
  readAssignment: (actionId) => this.#stores.readWorkHubAssignment(actionId),
82
+ listActiveAssignments: () => this.#listActiveAssignments(),
72
83
  readReplacement: (delegationId) => this.#stores.readWorkHubReplacement(delegationId),
73
84
  readReplacementAbort: (delegationId) => this.#stores.readWorkHubReplacementAbort(delegationId),
74
85
  readSupersession: (delegationId) => this.#stores.readWorkHubSupersession(delegationId),
86
+ readStopRequest: (delegationId) => this.#stores.readWorkHubStopRequest(delegationId),
87
+ readStopResolution: (delegationId) => this.#stores.readWorkHubStopResolution(delegationId),
75
88
  answer: async (input, context) => {
76
89
  const outcome = await this.#answer({ turnId: input.turnId, text: input.text }, context);
77
90
  if (!outcome.ok) {
@@ -91,13 +104,15 @@ export class HostWorkHubCoordinationCoordinator {
91
104
  assign: options.sessionActions.assign,
92
105
  prepareReplacement: (input) => this.#prepareReplacement(input),
93
106
  abortReplacement: (input) => this.#abortReplacement(input),
107
+ prepareStop: (input) => this.#prepareStop(input),
108
+ resolveStop: (input) => this.#resolveStop(input),
94
109
  readDelegationRetirement: options.sessionActions.readDelegationRetirement,
95
110
  retireDelegation: options.sessionActions.retireDelegation,
96
111
  });
97
112
  }
98
113
  #prepareReplacement(input) {
99
- const suffix = workHubReplacementIdentitySuffix(input.replacesDelegationId);
100
- return this.#commitReplacementFact({
114
+ const suffix = workHubDestructiveClaimIdentitySuffix(input.replacesDelegationId);
115
+ return this.#commitCoordinationFact({
101
116
  read: () => this.#stores.readWorkHubReplacement(input.replacesDelegationId),
102
117
  build: (existing) => ({
103
118
  type: 'workhub_coordination',
@@ -121,6 +136,13 @@ export class HostWorkHubCoordinationCoordinator {
121
136
  }),
122
137
  conflictMessage: 'WorkHub action identity belongs to a different replacement',
123
138
  beforeAppend: async () => {
139
+ const stopRequest = await this.#stores.readWorkHubStopRequest(input.replacesDelegationId);
140
+ if (stopRequest) {
141
+ const resolution = await this.#stores.readWorkHubStopResolution(input.replacesDelegationId);
142
+ if (resolution?.outcome !== 'not_owned') {
143
+ throw new WorkHubActionGateFailure('action_conflict', 'WorkHub delegation already has a stop claim');
144
+ }
145
+ }
124
146
  const header = await this.#stores.readHeaderSnapshot(WORKHUB_COORDINATION_SESSION_ID);
125
147
  if (!validCoordinationHeader(header)) {
126
148
  throw new WorkHubActionEffectFailure('operation_conflict', 'WorkHub Coordination Session identity is unavailable');
@@ -129,10 +151,105 @@ export class HostWorkHubCoordinationCoordinator {
129
151
  unknownOutcomeMessage: 'WorkHub replacement intent outcome is unknown',
130
152
  });
131
153
  }
154
+ async #prepareStop(input) {
155
+ const suffix = workHubDestructiveClaimIdentitySuffix(input.stopsDelegationId);
156
+ return this.#commitCoordinationFact({
157
+ // Only the two Sessions this stop can change: the one whose delegation
158
+ // ends, and the Coordination Session that records it. Holding a lane for
159
+ // every Session with an active delegation would serialize unrelated
160
+ // delegation traffic behind one stop, and the proof below needs no lane
161
+ // it does not already hold.
162
+ admissionSessionIds: [WORKHUB_COORDINATION_SESSION_ID, input.targetSessionId],
163
+ read: () => this.#stores.readWorkHubStopRequest(input.stopsDelegationId),
164
+ build: (existing) => ({
165
+ type: 'workhub_coordination',
166
+ id: `whq_${suffix}`,
167
+ turnId: input.actionId,
168
+ ts: existing?.ts ?? Date.now(),
169
+ schemaVersion: WORKHUB_COORDINATION_STOP_SCHEMA_VERSION,
170
+ kind: 'delegation_stop_requested',
171
+ actionId: input.actionId,
172
+ actionFingerprint: input.actionFingerprint,
173
+ coordinationTurnId: input.actionId,
174
+ stopsActionId: input.stopsActionId,
175
+ stopsDelegationId: input.stopsDelegationId,
176
+ targetSessionId: input.targetSessionId,
177
+ targetMessageId: input.targetMessageId,
178
+ targetSessionName: input.targetSessionName,
179
+ userText: input.userText,
180
+ }),
181
+ conflictMessage: 'WorkHub delegation already has a different stop claim',
182
+ beforeAppend: async () => {
183
+ const [replacement, supersession, messages] = await Promise.all([
184
+ this.#stores.readWorkHubReplacement(input.stopsDelegationId),
185
+ this.#stores.readWorkHubSupersession(input.stopsDelegationId),
186
+ this.#stores.readMessagesSnapshot(WORKHUB_COORDINATION_SESSION_ID),
187
+ ]);
188
+ if (replacement || supersession) {
189
+ throw new WorkHubActionGateFailure('action_conflict', 'WorkHub delegation is already being replaced');
190
+ }
191
+ const activeAssignments = activeWorkHubAssignments(messages);
192
+ // Held lanes make this the last moment the one-target proof can change.
193
+ // It is proved from opaque delegation identity, so a concurrent rename
194
+ // is harmless while a concurrent delegation to the same Session is not.
195
+ const targetActive = activeAssignments.filter((assignment) => assignment.targetSessionId === input.targetSessionId);
196
+ const source = targetActive.find((assignment) => assignment.actionId === input.stopsActionId &&
197
+ assignment.delegationId === input.stopsDelegationId);
198
+ if (!source) {
199
+ throw new WorkHubActionGateFailure('action_conflict', 'WorkHub stop target does not identify one active durable delegation');
200
+ }
201
+ // A delegation whose work already finished stays linked but competes
202
+ // for nothing; only work that could still be stopped makes the target
203
+ // ambiguous.
204
+ for (const competitor of targetActive) {
205
+ if (competitor.delegationId === source.delegationId)
206
+ continue;
207
+ if ((await this.#readDelegationRetirement(competitor)) !== 'retired') {
208
+ throw new WorkHubActionGateFailure('action_conflict', 'WorkHub stop target does not identify one active durable delegation');
209
+ }
210
+ }
211
+ },
212
+ unknownOutcomeMessage: 'WorkHub stop request outcome is unknown',
213
+ });
214
+ }
215
+ async #listActiveAssignments() {
216
+ return activeWorkHubAssignments(await this.#stores.readMessagesSnapshot(WORKHUB_COORDINATION_SESSION_ID));
217
+ }
218
+ #resolveStop(input) {
219
+ const request = input.request;
220
+ const suffix = workHubDestructiveClaimIdentitySuffix(request.stopsDelegationId);
221
+ return this.#commitCoordinationFact({
222
+ read: () => this.#stores.readWorkHubStopResolution(request.stopsDelegationId),
223
+ build: (existing) => ({
224
+ type: 'workhub_coordination',
225
+ id: `whz_${suffix}`,
226
+ turnId: request.actionId,
227
+ ts: existing?.ts ?? Date.now(),
228
+ schemaVersion: WORKHUB_COORDINATION_STOP_SCHEMA_VERSION,
229
+ kind: 'delegation_stop_resolved',
230
+ actionId: request.actionId,
231
+ actionFingerprint: request.actionFingerprint,
232
+ coordinationTurnId: request.coordinationTurnId,
233
+ stopsActionId: request.stopsActionId,
234
+ stopsDelegationId: request.stopsDelegationId,
235
+ targetSessionId: request.targetSessionId,
236
+ outcome: input.outcome,
237
+ ...(input.targetTurnId ? { targetTurnId: input.targetTurnId } : {}),
238
+ }),
239
+ conflictMessage: 'WorkHub stop already has a different resolution',
240
+ beforeAppend: async () => {
241
+ const durable = await this.#stores.readWorkHubStopRequest(request.stopsDelegationId);
242
+ if (!durable || !isDeepStrictEqual(durable, request)) {
243
+ throw new WorkHubActionGateFailure('action_conflict', 'WorkHub stop request identity changed');
244
+ }
245
+ },
246
+ unknownOutcomeMessage: 'WorkHub stop resolution outcome is unknown',
247
+ });
248
+ }
132
249
  #abortReplacement(input) {
133
250
  const replacement = input.replacement;
134
- const suffix = workHubReplacementIdentitySuffix(replacement.replacesDelegationId);
135
- return this.#commitReplacementFact({
251
+ const suffix = workHubDestructiveClaimIdentitySuffix(replacement.replacesDelegationId);
252
+ return this.#commitCoordinationFact({
136
253
  read: () => this.#stores.readWorkHubReplacementAbort(replacement.replacesDelegationId),
137
254
  build: (existing) => ({
138
255
  type: 'workhub_coordination',
@@ -159,8 +276,8 @@ export class HostWorkHubCoordinationCoordinator {
159
276
  unknownOutcomeMessage: 'WorkHub replacement abort outcome is unknown',
160
277
  });
161
278
  }
162
- #commitReplacementFact(options) {
163
- return this.#admission.run(WORKHUB_COORDINATION_SESSION_ID, async (lease) => {
279
+ #commitCoordinationFact(options) {
280
+ return this.#admission.runMany(options.admissionSessionIds ?? [WORKHUB_COORDINATION_SESSION_ID], async (lease) => {
164
281
  const existing = await options.read();
165
282
  const requested = options.build(existing);
166
283
  if (existing) {
@@ -445,7 +562,28 @@ function validCoordinationHeader(header) {
445
562
  function digest(value) {
446
563
  return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
447
564
  }
448
- function workHubReplacementIdentitySuffix(delegationId) {
565
+ function activeWorkHubAssignments(messages) {
566
+ const terminalDelegationIds = new Set();
567
+ const assignments = [];
568
+ for (const message of messages) {
569
+ if (message.type !== 'workhub_coordination')
570
+ continue;
571
+ if (message.kind === 'delegation_assigned') {
572
+ assignments.push(message);
573
+ }
574
+ else if (message.kind === 'delegation_superseded') {
575
+ terminalDelegationIds.add(message.supersededDelegationId);
576
+ }
577
+ else if (message.kind === 'delegation_replacement_aborted') {
578
+ terminalDelegationIds.add(message.abortedDelegationId);
579
+ }
580
+ else if (message.kind === 'delegation_stop_resolved' && message.outcome !== 'not_owned') {
581
+ terminalDelegationIds.add(message.stopsDelegationId);
582
+ }
583
+ }
584
+ return assignments.filter(({ delegationId }) => !terminalDelegationIds.has(delegationId));
585
+ }
586
+ function workHubDestructiveClaimIdentitySuffix(delegationId) {
449
587
  return createHash('sha256').update(delegationId, 'utf8').digest('hex').slice(0, 48);
450
588
  }
451
589
  function coordinationSummaryMessageId(turnId, kind) {
@@ -69,6 +69,7 @@ export function startRuntimeHostPeerEndpoint(input) {
69
69
  try {
70
70
  const endpoint = loadNativeModule(input.nativePath).startPeerEndpoint({
71
71
  keyPath: input.keyPath,
72
+ ...(input.relayAnchorPath ? { relayAnchorPath: input.relayAnchorPath } : {}),
72
73
  ...(input.expectedPeerId ? { expectedPeerId: input.expectedPeerId } : {}),
73
74
  ...(input.listenAddresses ? { listenAddresses: input.listenAddresses } : {}),
74
75
  ...(input.coordinationRelays ? { coordinationRelays: input.coordinationRelays } : {}),
@@ -281,20 +282,24 @@ function isPeerNativeEndpoint(value) {
281
282
  value !== null &&
282
283
  'peerId' in value &&
283
284
  isPeerId(value.peerId) &&
284
- 'listenAddresses' in value &&
285
- Array.isArray(value.listenAddresses) &&
286
- value.listenAddresses.every((address) => typeof address === 'string') &&
287
- 'activeCoordinationRelays' in value &&
288
- Array.isArray(value.activeCoordinationRelays) &&
289
- value.activeCoordinationRelays.every((address) => typeof address === 'string') &&
285
+ 'reachabilitySnapshot' in value &&
286
+ isPeerReachabilitySnapshot(value.reachabilitySnapshot) &&
287
+ 'connectivitySnapshot' in value &&
288
+ isPeerConnectivitySnapshot(value.connectivitySnapshot) &&
290
289
  'transitSnapshot' in value &&
291
290
  isPeerTransitSnapshot(value.transitSnapshot) &&
292
291
  'connect' in value &&
293
292
  typeof value.connect === 'function' &&
294
293
  'connectMeshControl' in value &&
295
294
  typeof value.connectMeshControl === 'function' &&
295
+ 'updateConnect' in value &&
296
+ typeof value.updateConnect === 'function' &&
296
297
  'configureTransit' in value &&
297
298
  typeof value.configureTransit === 'function' &&
299
+ 'watchReachability' in value &&
300
+ typeof value.watchReachability === 'function' &&
301
+ 'watchConnectivity' in value &&
302
+ typeof value.watchConnectivity === 'function' &&
298
303
  'cancelConnect' in value &&
299
304
  typeof value.cancelConnect === 'function' &&
300
305
  'accept' in value &&
@@ -304,6 +309,27 @@ function isPeerNativeEndpoint(value) {
304
309
  'close' in value &&
305
310
  typeof value.close === 'function');
306
311
  }
312
+ function isPeerConnectivitySnapshot(value) {
313
+ return (typeof value === 'object' &&
314
+ value !== null &&
315
+ 'generation' in value &&
316
+ isCount(value.generation) &&
317
+ 'connectedPeerIds' in value &&
318
+ Array.isArray(value.connectedPeerIds) &&
319
+ value.connectedPeerIds.every((peerId) => typeof peerId === 'string'));
320
+ }
321
+ function isPeerReachabilitySnapshot(value) {
322
+ return (typeof value === 'object' &&
323
+ value !== null &&
324
+ 'generation' in value &&
325
+ isCount(value.generation) &&
326
+ 'listenAddresses' in value &&
327
+ Array.isArray(value.listenAddresses) &&
328
+ value.listenAddresses.every((address) => typeof address === 'string') &&
329
+ 'activeCoordinationRelays' in value &&
330
+ Array.isArray(value.activeCoordinationRelays) &&
331
+ value.activeCoordinationRelays.every((address) => typeof address === 'string'));
332
+ }
307
333
  function isPeerTransitSnapshot(value) {
308
334
  return (typeof value === 'object' &&
309
335
  value !== null &&
@@ -9,6 +9,7 @@
9
9
  "./protocol": "./dist/protocol/index.js",
10
10
  "./client": "./dist/client/index.js",
11
11
  "./webrtc-stun-policy": "./dist/webrtc-stun-policy.js",
12
+ "./peer-reachability": "./dist/peer-reachability/index.js",
12
13
  "./peer-mesh": "./dist/peer-mesh/index.js",
13
14
  "./operator": "./dist/operator/index.js",
14
15
  "./operator/update-package-evidence": "./dist/operator/update-package-evidence.js",
@@ -1249,8 +1249,10 @@ function assertRootTurnAdmissionContract(admission) {
1249
1249
  if (admission.skillInvocation && execution.kind !== 'external_message') {
1250
1250
  throw new Error('Invalid root turn admission contract: Skill invocation requires external message execution');
1251
1251
  }
1252
- if (admission.authorization && execution.kind !== 'external_message') {
1253
- throw new Error('Invalid root turn admission contract: authorization proof requires external message execution');
1252
+ if (admission.authorization &&
1253
+ execution.kind !== 'external_message' &&
1254
+ execution.kind !== 'regenerate') {
1255
+ throw new Error('Invalid root turn admission contract: authorization proof requires external message or regenerate execution');
1254
1256
  }
1255
1257
  if (execution.kind === 'claimed_agent_graph_intent') {
1256
1258
  if (execution.claim.targetSessionId !== admission.sessionId ||
@@ -1410,12 +1412,23 @@ function normalizeRootExecutionDescriptor(value) {
1410
1412
  return Object.freeze({ kind: 'context_compact' });
1411
1413
  }
1412
1414
  if (value.kind === 'scheduled_task') {
1413
- if (!hasExactKeys(value, ['kind', 'scheduledTaskId']) ||
1415
+ if (!hasExactKeys(value, [
1416
+ 'kind',
1417
+ 'scheduledTaskId',
1418
+ ...(Object.hasOwn(value, 'executionFingerprint') ? ['executionFingerprint'] : []),
1419
+ ]) ||
1414
1420
  typeof value.scheduledTaskId !== 'string' ||
1415
- !isSafeId(value.scheduledTaskId)) {
1421
+ !isSafeId(value.scheduledTaskId) ||
1422
+ (value.executionFingerprint !== undefined && !isSha256Digest(value.executionFingerprint))) {
1416
1423
  throw new Error('Invalid root execution descriptor');
1417
1424
  }
1418
- return Object.freeze({ kind: 'scheduled_task', scheduledTaskId: value.scheduledTaskId });
1425
+ return Object.freeze({
1426
+ kind: 'scheduled_task',
1427
+ scheduledTaskId: value.scheduledTaskId,
1428
+ ...(value.executionFingerprint !== undefined
1429
+ ? { executionFingerprint: value.executionFingerprint }
1430
+ : {}),
1431
+ });
1419
1432
  }
1420
1433
  if (value.kind === 'automation' || value.kind === 'legacy_automation') {
1421
1434
  if (!hasExactKeys(value, ['kind', 'automationId']) ||
@@ -260,6 +260,7 @@ function convertCodexRollout(text, expectedSessionId, fallbackName, fallbackCwd)
260
260
  const text = codexCompletedItemText(item);
261
261
  if (text.length === 0)
262
262
  continue;
263
+ const providerOptions = codexAssistantProviderOptions(item);
263
264
  messages.push({
264
265
  type: 'assistant',
265
266
  id: stringField(item, 'id') ??
@@ -267,6 +268,7 @@ function convertCodexRollout(text, expectedSessionId, fallbackName, fallbackCwd)
267
268
  turnId: ensureTurnId(record.line),
268
269
  ts: timestampFor(record),
269
270
  text,
271
+ ...(providerOptions !== undefined ? { providerOptions } : {}),
270
272
  modelId: activeModel,
271
273
  contentOrder: ['text'],
272
274
  });
@@ -313,12 +315,14 @@ function convertCodexRollout(text, expectedSessionId, fallbackName, fallbackCwd)
313
315
  const text = stringField(payload, 'message');
314
316
  if (!text)
315
317
  continue;
318
+ const providerOptions = codexAssistantProviderOptions(payload);
316
319
  messages.push({
317
320
  type: 'assistant',
318
321
  id: generatedCodexId(expectedSessionId, 'assistant', record.line),
319
322
  turnId: ensureTurnId(record.line),
320
323
  ts: timestampFor(record),
321
324
  text,
325
+ ...(providerOptions !== undefined ? { providerOptions } : {}),
322
326
  modelId: activeModel,
323
327
  contentOrder: ['text'],
324
328
  });
@@ -698,6 +702,16 @@ function stringField(record, field) {
698
702
  const value = record?.[field];
699
703
  return typeof value === 'string' && value.length > 0 ? value : undefined;
700
704
  }
705
+ function codexAssistantProviderOptions(record) {
706
+ const phase = record?.phase;
707
+ if (phase !== 'commentary' && phase !== 'final_answer')
708
+ return undefined;
709
+ return {
710
+ openai: {
711
+ phase,
712
+ },
713
+ };
714
+ }
701
715
  function isSafeCodexSessionId(value) {
702
716
  return typeof value === 'string' && CODEX_SESSION_ID_PATTERN.test(value);
703
717
  }
@@ -127,6 +127,10 @@ async function createExecutionStoresForWrite(lease, kind, extension) {
127
127
  readWorkHubReplacement: (delegationId) => run(() => sessionStore.readWorkHubReplacement(delegationId)),
128
128
  readWorkHubReplacementAbort: (delegationId) => run(() => sessionStore.readWorkHubReplacementAbort(delegationId)),
129
129
  readWorkHubSupersession: (delegationId) => run(() => sessionStore.readWorkHubSupersession(delegationId)),
130
+ readWorkHubStopRequest: (delegationId) => run(() => sessionStore.readWorkHubStopRequest(delegationId)),
131
+ readWorkHubStopResolution: (delegationId) => run(() => sessionStore.readWorkHubStopResolution(delegationId)),
132
+ claimWorkHubAction: (claim) => run(() => sessionStore.claimWorkHubAction(claim)),
133
+ readWorkHubActionClaim: (actionId) => run(() => sessionStore.readWorkHubActionClaim(actionId)),
130
134
  discardStableConversationCopy: (sessionId, requestFingerprint) => run(() => sessionStore.discardStableConversationCopy(sessionId, requestFingerprint)),
131
135
  createSubagent: (input, initialBoundary) => run(() => sessionStore.createSubagent(input, initialBoundary)),
132
136
  createAgentGraphOperator: (input, request, expectedRevision, initialBoundary) => run(() => sessionStore.createAgentGraphOperator(input, request, expectedRevision, initialBoundary)),
@@ -162,6 +166,7 @@ async function createExecutionStoresForWrite(lease, kind, extension) {
162
166
  commitMessageAdmission: (admission) => run(() => sessionStore.commitMessageAdmission(admission)),
163
167
  readMessageAdmission: (sessionId, messageId) => run(() => sessionStore.readMessageAdmission(sessionId, messageId)),
164
168
  hasCancelledMessageAdmission: (sessionId, messageId) => run(() => sessionStore.hasCancelledMessageAdmission(sessionId, messageId)),
169
+ claimMessageAdmissionCancellation: (sessionId, messageId, claimId) => run(() => sessionStore.claimMessageAdmissionCancellation(sessionId, messageId, claimId)),
165
170
  listMessageAdmissions: (sessionId) => run(() => sessionStore.listMessageAdmissions(sessionId)),
166
171
  markMessagesHandedOff: (input) => run(() => sessionStore.markMessagesHandedOff(input)),
167
172
  updateMessageAdmission: (admission) => run(() => sessionStore.updateMessageAdmission(admission)),
@@ -19,7 +19,7 @@
19
19
  import { resolve } from 'node:path';
20
20
  import { isDeepStrictEqual } from 'node:util';
21
21
  import { clientCapabilityScopeIdentity, decodeClientCapabilitySessionGrant, decodeClientCapabilitySessionGrantKey, } from '@maka/core/client-capability-grant';
22
- import { decodeInteractionCanonicalOutcome, decodeInteractionRequest, interactionCanonicalOutcomesEquivalent, isInteractionCanonicalOutcomeValidForRequest, projectInteractionQuestionRequest, } from '@maka/core/interaction';
22
+ import { decodeInteractionCanonicalOutcome, decodeInteractionRequest, interactionCanonicalOutcomesEquivalent, isInteractionCanonicalOutcomeValidForRequest, projectInteractionFormRequest, projectInteractionQuestionRequest, } from '@maka/core/interaction';
23
23
  import { assertStorageRootLease, runWithStorageRootLease, StorageRootAuthorityError, } from './root-authority.js';
24
24
  import { acquireOperationalStateDatabase, } from './operational-state-store.js';
25
25
  const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
@@ -420,6 +420,18 @@ function normalizeRequest(value, source) {
420
420
  decodeFailure(source, 'Interaction question request is not canonical safe text');
421
421
  request = canonical;
422
422
  }
423
+ else if (request.kind === 'form') {
424
+ const canonical = projectInteractionFormRequest({
425
+ toolUseId: request.toolUseId,
426
+ message: request.message,
427
+ requester: request.requester,
428
+ fields: request.fields,
429
+ });
430
+ if (!isDeepStrictEqual(request, canonical)) {
431
+ decodeFailure(source, 'Interaction form request is not canonical');
432
+ }
433
+ request = canonical;
434
+ }
423
435
  }
424
436
  catch (error) {
425
437
  if (error instanceof InteractionStoreError)
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { randomUUID } from 'node:crypto';
20
20
  import { isDeepStrictEqual } from 'node:util';
21
- import { CONNECTION_CATALOG_MAX_CONNECTIONS, decodeCanonicalConnectionCatalogEntry, decodeConnectionSlug, decodeConnectionTarget, decodeConnectionTestSummary, decodeProviderType, decodeRuntimePolicyEntityId, normalizeConnectionCatalogEntryUpdateForProvider, normalizeConnectionModelDiscoveryResult, normalizeCreateCatalogConnectionInput, normalizeRemoveCatalogConnectionInput, normalizeSetDefaultConnectionTargetInput, normalizeUpdateCatalogConnectionInput, } from '@maka/core/runtime-policy';
21
+ import { CONNECTION_CATALOG_MAX_CONNECTIONS, decodeCanonicalConnectionCatalogEntry, decodeConnectionName, decodeConnectionSlug, decodeConnectionTarget, decodeConnectionTestSummary, decodeProviderType, decodeRuntimePolicyEntityId, normalizeConnectionCatalogEntryUpdateForProvider, normalizeConnectionModelDiscoveryResult, normalizeCreateCatalogConnectionInput, normalizeRemoveCatalogConnectionInput, normalizeSetDefaultConnectionTargetInput, normalizeUpdateCatalogConnectionInput, } from '@maka/core/runtime-policy';
22
22
  import { PROVIDER_REGISTRY, reconcileConnectionAfterModelFetch } from '@maka/core/llm-connections';
23
23
  import { modelIdAliasesForProvider } from '@maka/core/model-metadata';
24
24
  import { isRetiredProvider } from '@maka/core/provider-registry';
@@ -354,10 +354,11 @@ export class ConnectionCatalogDocumentOwner {
354
354
  const { lastTest: _lastTest, ...discoveredWithoutLastTest } = discovered;
355
355
  return this.writePatchedResult(root, current, index, testBasisChanged ? discoveredWithoutLastTest : discovered, defaultTarget);
356
356
  }
357
- prepareOnboardingUpsert(current, rawConnectionId, rawSlug, rawProviderType, rawBaseUrl, rawEnabledModelIds, rawResult, invalidateLastTest) {
357
+ prepareOnboardingUpsert(current, rawConnectionId, rawSlug, rawProviderType, rawName, rawBaseUrl, rawEnabledModelIds, rawResult, invalidateLastTest) {
358
358
  const connectionId = decodeConnectionInput(() => decodeRuntimePolicyEntityId(rawConnectionId));
359
359
  const slug = decodeConnectionInput(() => decodeConnectionSlug(rawSlug));
360
360
  const providerType = decodeConnectionInput(() => decodeProviderType(rawProviderType));
361
+ const requestedName = rawName === null ? null : decodeConnectionInput(() => decodeConnectionName(rawName));
361
362
  const definition = PROVIDER_REGISTRY[providerType];
362
363
  // Identity first: the intent's connectionId names the connection being
363
364
  // edited, whatever slug it lives under — a relay created in Desktop under
@@ -389,7 +390,9 @@ export class ConnectionCatalogDocumentOwner {
389
390
  // existing override or the registry default (blank-reuse, like the key).
390
391
  const effectiveBaseUrl = rawBaseUrl ?? previous?.baseUrl ?? definition.baseUrl;
391
392
  const changes = decodeConnectionInput(() => normalizeConnectionCatalogEntryUpdateForProvider({
392
- name: previous?.name ?? definition.label,
393
+ // An edit keeps the stored name; a create takes the caller's choice
394
+ // when the wizard collected one, else the provider label.
395
+ name: previous?.name ?? requestedName ?? definition.label,
393
396
  ...(effectiveBaseUrl ? { baseUrl: effectiveBaseUrl } : {}),
394
397
  enabled: true,
395
398
  enabledModelIds: rawEnabledModelIds,
@@ -427,6 +430,9 @@ export class ConnectionCatalogDocumentOwner {
427
430
  const { relayModelProfiles: _staleProfiles, ...baseWithoutProfiles } = base;
428
431
  const finalized = {
429
432
  ...baseWithoutProfiles,
433
+ // `changes.name` carries the edit-preserving / create-requested / provider
434
+ // -label resolution — `base` alone would keep the provider label forever.
435
+ name: changes.name,
430
436
  ...(relayModelProfiles ? { relayModelProfiles } : {}),
431
437
  ...(changes.baseUrl !== undefined ? { baseUrl: changes.baseUrl } : {}),
432
438
  revision: previous ? nextRevision(previous.revision) : 1,
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { randomUUID } from 'node:crypto';
20
20
  import { isDeepStrictEqual } from 'node:util';
21
- import { CONNECTION_CATALOG_MAX_CONNECTIONS, decodeConnectionModelId, connectionCredentialTarget, decodeConnectionCredentialTarget, decodeConnectionSlug, decodeProviderType, decodeRuntimePolicyEntityId, decodeCredentialLocator, normalizeDeleteCredentialInput, normalizeRemoveCatalogConnectionInput, normalizeRequestHeaderUpdates, normalizeRequestHeaders, normalizeSetCredentialInput, parseRequestHeaders, serializeRequestHeaders, RequestCustomizationValidationError, normalizeCredentialSecret, normalizeCatalogConnectionBaseUrl, normalizeNetworkProxyUpdate, networkProxyCredentialTarget, } from '@maka/core/runtime-policy';
21
+ import { CONNECTION_CATALOG_MAX_CONNECTIONS, decodeConnectionModelId, connectionCredentialTarget, decodeConnectionCredentialTarget, decodeConnectionName, decodeConnectionSlug, decodeProviderType, decodeRuntimePolicyEntityId, decodeCredentialLocator, normalizeDeleteCredentialInput, normalizeRemoveCatalogConnectionInput, normalizeRequestHeaderUpdates, normalizeRequestHeaders, normalizeSetCredentialInput, parseRequestHeaders, serializeRequestHeaders, RequestCustomizationValidationError, normalizeCredentialSecret, normalizeCatalogConnectionBaseUrl, normalizeNetworkProxyUpdate, networkProxyCredentialTarget, } from '@maka/core/runtime-policy';
22
22
  import { applyModelFactOverridesToConnection, applyModelFactOverridesToCatalogSnapshot, } from '@maka/core/model-facts';
23
23
  import { deriveProviderAuthContract } from '@maka/core/provider-auth';
24
24
  import { isRetiredProvider } from '@maka/core/provider-registry';
@@ -794,13 +794,21 @@ export class RuntimePolicyCoordinator {
794
794
  const requestedTarget = input.target;
795
795
  if (requestedTarget.kind === 'create') {
796
796
  const providerType = decodeConnectionInput(() => decodeProviderType(requestedTarget.providerType));
797
+ const requestedSlug = requestedTarget.slug === undefined
798
+ ? null
799
+ : decodeConnectionInput(() => decodeConnectionSlug(requestedTarget.slug));
797
800
  target = {
798
801
  kind: 'create',
799
802
  candidate: {
800
803
  connectionId: randomUUID(),
801
- slug: deriveConnectionSlug(providerType, catalog.connections.map((connection) => connection.slug)),
804
+ slug: requestedSlug ??
805
+ deriveConnectionSlug(providerType, catalog.connections.map((connection) => connection.slug)),
802
806
  providerType,
803
807
  },
808
+ slugRequested: requestedSlug !== null,
809
+ name: requestedTarget.name === undefined
810
+ ? null
811
+ : decodeConnectionInput(() => decodeConnectionName(requestedTarget.name)),
804
812
  };
805
813
  }
806
814
  else if (requestedTarget.kind === 'existing') {
@@ -822,16 +830,22 @@ export class RuntimePolicyCoordinator {
822
830
  throw codecError('invalid_connection_input', 'Unknown connection onboarding target');
823
831
  }
824
832
  const providerType = target.candidate.providerType;
825
- // Onboarding guards the api_key credential slot; a provider whose auth
826
- // never uses one has no business here (the Host gates on the same
827
- // predicate, this keeps the storage API honest on its own).
828
- if (!providerAuthSupportsApiKey(providerType)) {
833
+ // Onboarding may adopt either an API key or canonical serialized OAuth
834
+ // material. Providers without a connection credential slot have no
835
+ // business here (the Host applies the same gate before discovery).
836
+ if (!providerAuthSupportsApiKey(providerType) &&
837
+ PROVIDER_REGISTRY[providerType].authKind !== 'oauth_token') {
829
838
  return deepFreeze({ kind: 'provider_unsupported' });
830
839
  }
831
840
  if (target.kind === 'create' &&
832
841
  catalog.connections.length >= CONNECTION_CATALOG_MAX_CONNECTIONS) {
833
842
  return deepFreeze({ kind: 'catalog_full' });
834
843
  }
844
+ if (target.kind === 'create' &&
845
+ target.slugRequested &&
846
+ catalog.connections.some((connection) => connection.slug === target.candidate.slug)) {
847
+ return deepFreeze({ kind: 'slug_taken' });
848
+ }
835
849
  const baseUrl = input.baseUrl === null
836
850
  ? null
837
851
  : (decodeConnectionInput(() => normalizeCatalogConnectionBaseUrl(input.baseUrl, providerType)) ?? null);
@@ -910,6 +924,9 @@ export class RuntimePolicyCoordinator {
910
924
  if (checked.kind === 'catalog_full') {
911
925
  return deepFreeze({ kind: 'catalog_full' });
912
926
  }
927
+ if (checked.kind === 'slug_taken') {
928
+ return deepFreeze({ kind: 'slug_taken' });
929
+ }
913
930
  return deepFreeze(checked.kind === 'target_missing'
914
931
  ? { kind: 'target_missing' }
915
932
  : { kind: 'superseded', changed: checked.changed });
@@ -943,6 +960,15 @@ export class RuntimePolicyCoordinator {
943
960
  }
944
961
  else if (catalog.connections.some((connection) => connection.connectionId === basis.target.candidate.connectionId ||
945
962
  connection.slug === basis.target.candidate.slug)) {
963
+ // A caller-chosen slug losing the race is the caller's to fix, so it
964
+ // reports distinctly instead of as a generic basis change. A derived
965
+ // slug colliding still resolves by re-running the wizard, which simply
966
+ // derives again.
967
+ if (basis.target.slugRequested &&
968
+ catalog.connections.some((connection) => connection.slug === basis.target.candidate.slug &&
969
+ connection.connectionId !== basis.target.candidate.connectionId)) {
970
+ return { kind: 'slug_taken' };
971
+ }
946
972
  changed.push('connection');
947
973
  }
948
974
  else if (catalog.connections.length >= CONNECTION_CATALOG_MAX_CONNECTIONS) {
@@ -974,11 +1000,10 @@ export class RuntimePolicyCoordinator {
974
1000
  const connectionId = candidate.connectionId;
975
1001
  let invalidateLastTest = false;
976
1002
  if (input.suppliedSecret !== null) {
977
- const locator = {
978
- scope: 'connection',
979
- connectionId,
980
- kind: 'api_key',
981
- };
1003
+ const locator = connectionCredentialLocator(connectionId, PROVIDER_REGISTRY[candidate.providerType].authKind);
1004
+ if (!locator) {
1005
+ throw codecError('invalid_document', 'Onboarding provider has no credential locator');
1006
+ }
982
1007
  const vault = await this.vault.read(root);
983
1008
  const credential = findCredential(vault, locator);
984
1009
  if (credential?.secret !== input.suppliedSecret) {
@@ -1000,11 +1025,15 @@ export class RuntimePolicyCoordinator {
1000
1025
  connectionId,
1001
1026
  slug: candidate.slug,
1002
1027
  providerType: candidate.providerType,
1028
+ name: basis.target.kind === 'create' ? basis.target.name : null,
1003
1029
  baseUrl: basis.baseUrl,
1004
1030
  invalidateLastTest,
1005
1031
  });
1006
- const catalogPreflight = this.catalog.prepareOnboardingUpsert(catalog, intent.connectionId, intent.slug, intent.providerType, intent.baseUrl, intent.enabledModelIds, intent.discovery, intent.invalidateLastTest);
1032
+ const catalogPreflight = this.catalog.prepareOnboardingUpsert(catalog, intent.connectionId, intent.slug, intent.providerType, intent.name, intent.baseUrl, intent.enabledModelIds, intent.discovery, intent.invalidateLastTest);
1007
1033
  if (catalogPreflight.kind === 'slug_conflict') {
1034
+ if (basis.target.kind === 'create' && basis.target.slugRequested) {
1035
+ return deepFreeze({ kind: 'slug_taken' });
1036
+ }
1008
1037
  return deepFreeze({ kind: 'superseded', changed: ['connection'] });
1009
1038
  }
1010
1039
  if (catalogPreflight.kind === 'catalog_full') {
@@ -1438,7 +1467,7 @@ export class RuntimePolicyCoordinator {
1438
1467
  // Validate the durable identity and final catalog shape before touching
1439
1468
  // the vault. A damaged v2 intent must not rotate a real connection's
1440
1469
  // credential before discovering that its ID/slug pair cannot commit.
1441
- const prepared = this.catalog.prepareOnboardingUpsert(catalog, intent.connectionId, slug, intent.providerType, intent.baseUrl, intent.enabledModelIds, intent.discovery, intent.invalidateLastTest);
1470
+ const prepared = this.catalog.prepareOnboardingUpsert(catalog, intent.connectionId, slug, intent.providerType, intent.name, intent.baseUrl, intent.enabledModelIds, intent.discovery, intent.invalidateLastTest);
1442
1471
  if (prepared.kind === 'slug_conflict') {
1443
1472
  throw codecError('invalid_document', intent.schemaVersion === 1
1444
1473
  ? 'Legacy onboarding intent conflicts with the connection id'
@@ -1448,11 +1477,10 @@ export class RuntimePolicyCoordinator {
1448
1477
  throw codecError('invalid_document', 'Onboarding intent exceeds the connection catalog');
1449
1478
  }
1450
1479
  if (intent.suppliedSecret !== null) {
1451
- const locator = {
1452
- scope: 'connection',
1453
- connectionId: intent.connectionId,
1454
- kind: 'api_key',
1455
- };
1480
+ const locator = connectionCredentialLocator(intent.connectionId, PROVIDER_REGISTRY[intent.providerType].authKind);
1481
+ if (!locator) {
1482
+ throw codecError('invalid_document', 'Onboarding provider has no credential locator');
1483
+ }
1456
1484
  const vault = await this.vault.read(root);
1457
1485
  const existing = findCredential(vault, locator);
1458
1486
  if (existing?.secret !== intent.suppliedSecret) {
@@ -1696,7 +1724,9 @@ function requiresNetworkProxyCredential(networkProxy) {
1696
1724
  return networkProxy.enabled && networkProxy.authEnabled;
1697
1725
  }
1698
1726
  function isInteractiveOAuthLoginProvider(providerType) {
1699
- return providerType === 'openai-codex' || providerType === 'xai-oauth';
1727
+ return (providerType === 'openai-codex' ||
1728
+ providerType === 'xai-oauth' ||
1729
+ providerType === 'github-copilot');
1700
1730
  }
1701
1731
  function normalizeInteractiveOAuthLoginInput(input) {
1702
1732
  const attemptId = decodeInteractiveOAuthAttemptId(input?.attemptId, 'invalid_connection_input');