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

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 (125) hide show
  1. package/LICENSE +106 -0
  2. package/dist/cli-core.js +28 -0
  3. package/dist/runtime-host-cli.js +43 -3
  4. package/dist/runtime-host-handoff-surface.js +10 -8
  5. package/dist/runtime-host-lifecycle-transaction.js +28 -3
  6. package/dist/runtime-host-local-source-retirement.js +1 -1
  7. package/dist/runtime-host-run-command.js +15 -7
  8. package/dist/runtime-host-setup-command.js +29 -6
  9. package/dist/runtime-host-update-command.js +49 -5
  10. package/dist/session-export-command.js +72 -0
  11. package/dist/session-import-command.js +69 -0
  12. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  13. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  14. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  15. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  16. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  17. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -4
  18. package/node_modules/@maka/core/dist/diagnostic-log.js +18 -7
  19. package/node_modules/@maka/core/dist/text-sanitize.js +8 -2
  20. package/node_modules/@maka/core/dist/usage-stats/types.js +2 -0
  21. package/node_modules/@maka/core/dist/workhub-routing.js +126 -0
  22. package/node_modules/@maka/core/package.json +2 -1
  23. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +15 -11
  24. package/node_modules/@maka/runtime/dist/archive-read-tool.js +3 -1
  25. package/node_modules/@maka/runtime/dist/bots/bot-test.js +2 -1
  26. package/node_modules/@maka/runtime/dist/bots/feishu-bridge.js +24 -20
  27. package/node_modules/@maka/runtime/dist/bots/slack-bridge.js +5 -3
  28. package/node_modules/@maka/runtime/dist/bots/wechat-bridge.js +87 -29
  29. package/node_modules/@maka/runtime/dist/bots/wecom-bridge.js +40 -36
  30. package/node_modules/@maka/runtime/dist/builtin-tools.js +21 -2
  31. package/node_modules/@maka/runtime/dist/computer-use-tools.js +4 -1
  32. package/node_modules/@maka/runtime/dist/deep-research-tools.js +11 -5
  33. package/node_modules/@maka/runtime/dist/edit-replace.js +2 -2
  34. package/node_modules/@maka/runtime/dist/filesystem-worker/operations.js +5 -6
  35. package/node_modules/@maka/runtime/dist/memory-extraction-evidence.js +7 -1
  36. package/node_modules/@maka/runtime/dist/memory-extraction.js +12 -7
  37. package/node_modules/@maka/runtime/dist/openai-responses-websocket.js +2 -5
  38. package/node_modules/@maka/runtime/dist/plugin-agent-service.js +131 -0
  39. package/node_modules/@maka/runtime/dist/plugin-approval-service.js +33 -0
  40. package/node_modules/@maka/runtime/dist/plugin-attachment-service.js +53 -0
  41. package/node_modules/@maka/runtime/dist/plugin-command-service.js +100 -0
  42. package/node_modules/@maka/runtime/dist/plugin-composition-loader.js +11 -2
  43. package/node_modules/@maka/runtime/dist/plugin-data-services.js +332 -0
  44. package/node_modules/@maka/runtime/dist/plugin-fs-service.js +62 -0
  45. package/node_modules/@maka/runtime/dist/plugin-goal-service.js +61 -0
  46. package/node_modules/@maka/runtime/dist/plugin-invocation-signal.js +24 -0
  47. package/node_modules/@maka/runtime/dist/plugin-llm-service.js +85 -0
  48. package/node_modules/@maka/runtime/dist/plugin-lsp-service.js +94 -0
  49. package/node_modules/@maka/runtime/dist/plugin-scope-registry.js +91 -0
  50. package/node_modules/@maka/runtime/dist/plugin-session-query-service.js +71 -0
  51. package/node_modules/@maka/runtime/dist/plugin-shell-env-service.js +87 -0
  52. package/node_modules/@maka/runtime/dist/plugin-shell-service.js +74 -0
  53. package/node_modules/@maka/runtime/dist/plugin-skill-service.js +93 -0
  54. package/node_modules/@maka/runtime/dist/plugin-system-prompt-service.js +346 -0
  55. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +18 -52
  56. package/node_modules/@maka/runtime/dist/plugin-user-question-service.js +43 -0
  57. package/node_modules/@maka/runtime/dist/plugin-web-service.js +69 -0
  58. package/node_modules/@maka/runtime/dist/request-customization-fetch.js +14 -7
  59. package/node_modules/@maka/runtime/dist/session-export.js +178 -0
  60. package/node_modules/@maka/runtime/dist/session-import.js +100 -0
  61. package/node_modules/@maka/runtime/dist/session-manager.js +6 -1
  62. package/node_modules/@maka/runtime/dist/shell-run-manager.js +9 -6
  63. package/node_modules/@maka/runtime/dist/shell-run-tool-result.js +13 -6
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +43 -10
  65. package/node_modules/@maka/runtime/dist/stream-graph-handoff.js +6 -1
  66. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +1 -4
  67. package/node_modules/@maka/runtime/dist/system-prompt/workspace-instructions.js +11 -6
  68. package/node_modules/@maka/runtime/dist/tavily-search.js +8 -2
  69. package/node_modules/@maka/runtime/dist/text-line-window.js +49 -0
  70. package/node_modules/@maka/runtime/dist/tool-output.js +35 -12
  71. package/node_modules/@maka/runtime/dist/unified-diff.js +40 -10
  72. package/node_modules/@maka/runtime/dist/web-fetch-tool.js +4 -2
  73. package/node_modules/@maka/runtime/dist/workers/filesystem-worker.js +82 -33
  74. package/node_modules/@maka/runtime/dist/workspace-executor.js +2 -6
  75. package/node_modules/@maka/runtime/package.json +20 -1
  76. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +73 -24
  77. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +46 -12
  78. package/node_modules/@maka/runtime-host/dist/operator/setup-frame.js +31 -21
  79. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +7 -4
  80. package/node_modules/@maka/runtime-host/dist/protocol/artifact.js +2 -2
  81. package/node_modules/@maka/runtime-host/dist/protocol/index.js +10 -1
  82. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +30 -5
  83. package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +3 -2
  84. package/node_modules/@maka/runtime-host/dist/protocol/skill-catalog.js +2 -1
  85. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +25 -23
  86. package/node_modules/@maka/runtime-host/dist/server/artifact-coordinator.js +2 -0
  87. package/node_modules/@maka/runtime-host/dist/server/client-capability-coordinator.js +62 -4
  88. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +481 -11
  89. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +74 -0
  90. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +6 -1
  91. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +2 -2
  92. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +30 -0
  93. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +114 -20
  94. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +49 -12
  95. package/node_modules/@maka/runtime-host/dist/server/plugin-data-runtime.js +234 -0
  96. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  97. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +12 -0
  98. package/node_modules/@maka/runtime-host/dist/server/root-admission-owner.js +17 -8
  99. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +189 -92
  100. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-projection.js +3 -9
  101. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +20 -21
  102. package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +24 -2
  103. package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +18 -2
  104. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -35
  105. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +92 -10
  106. package/node_modules/@maka/runtime-host/dist/transport/framed-byte-stream-transport.js +4 -1
  107. package/node_modules/@maka/runtime-host/dist/transport/local-ipc-framing.js +4 -2
  108. package/node_modules/@maka/runtime-host/dist/transport/peer-native.js +9 -8
  109. package/node_modules/@maka/runtime-host/dist/transport/resumable-peer-stream.js +14 -9
  110. package/node_modules/@maka/storage/dist/agent-run-store.js +54 -10
  111. package/node_modules/@maka/storage/dist/context-offload-snapshot.js +32 -10
  112. package/node_modules/@maka/storage/dist/context-value-mutation-gate.js +59 -0
  113. package/node_modules/@maka/storage/dist/execution-stores.js +1 -1
  114. package/node_modules/@maka/storage/dist/session-bundle-policy.js +909 -34
  115. package/node_modules/@maka/storage/dist/session-message-projection.js +8 -4
  116. package/node_modules/@maka/storage/dist/session-store.js +2 -2
  117. package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +18 -4
  118. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +12 -5
  119. package/node_modules/@maka/storage/dist/stable-storage.js +2 -1
  120. package/node_modules/@maka/storage/package.json +2 -0
  121. package/node_modules/@modelcontextprotocol/client/dist/src-D_zzAWoS.mjs +22 -5
  122. package/node_modules/@modelcontextprotocol/client/dist/src-NAgB4Mp8.cjs +22 -5
  123. package/node_modules/zod/v4/core/memoizer.cjs +12 -16
  124. package/node_modules/zod/v4/core/memoizer.js +12 -16
  125. package/package.json +1 -1
@@ -55,6 +55,8 @@ export async function runHostHandoff(input) {
55
55
  // (or a broken successor) into an automatic replacement storm.
56
56
  const attempted = new Set();
57
57
  let automaticAttempts = 0;
58
+ let refusedWork;
59
+ let replacementCompleted = false;
58
60
  let recovery;
59
61
  const publish = (next) => {
60
62
  view = next;
@@ -102,7 +104,10 @@ export async function runHostHandoff(input) {
102
104
  target: view.target,
103
105
  reason: 'unavailable',
104
106
  mayExitNaturally: false,
105
- diagnostic: boundedDiagnostic(error),
107
+ ...(view.manualRecheck ? { manualRecheck: true } : {}),
108
+ diagnostic: replacementCompleted
109
+ ? `Host replacement completed, but reconnection failed: ${boundedDiagnostic(error)}`
110
+ : boundedDiagnostic(error),
106
111
  },
107
112
  };
108
113
  }
@@ -124,11 +129,13 @@ export async function runHostHandoff(input) {
124
129
  const blocker = observed.blocker;
125
130
  if (recovery?.identity !== blocker.identity)
126
131
  recovery = undefined;
132
+ if (refusedWork !== blocker.identity)
133
+ refusedWork = undefined;
127
134
  const nextSignature = blockerSignature(blocker);
128
135
  if (signature !== nextSignature) {
129
136
  signature = nextSignature;
130
137
  choice = undefined;
131
- publish(projectBlocker(blocker, randomUUID(), recovery?.diagnostic));
138
+ publish(projectBlocker(blocker, randomUUID(), recovery?.diagnostic, refusedWork === blocker.identity));
132
139
  }
133
140
  const current = view;
134
141
  const selected = choice;
@@ -142,12 +149,19 @@ export async function runHostHandoff(input) {
142
149
  const automatic = !recovery &&
143
150
  automaticAttempts < 3 &&
144
151
  !attempted.has(nextSignature) &&
152
+ !blocker.replacement?.requiresExplicitSelection &&
145
153
  blocker.replacement?.canReplaceIdle &&
146
154
  blocker.activity &&
147
155
  (isHostActivityIdle(blocker.activity) || cooperative);
148
156
  const interrupt = selected?.revision === current.revision && selected.action === 'interrupt';
157
+ const replace = selected?.revision === current.revision && selected.action === 'replace';
149
158
  const retry = selected?.revision === current.revision && selected.action === 'retry';
150
- if ((automatic || interrupt || (retry && blocker.replacement?.canReplaceIdle)) &&
159
+ if ((automatic ||
160
+ interrupt ||
161
+ replace ||
162
+ (retry &&
163
+ !blocker.replacement?.requiresExplicitSelection &&
164
+ blocker.replacement?.canReplaceIdle)) &&
151
165
  blocker.replacement) {
152
166
  if (automatic) {
153
167
  attempted.add(nextSignature);
@@ -179,6 +193,8 @@ export async function runHostHandoff(input) {
179
193
  finally {
180
194
  attemptAbort = undefined;
181
195
  }
196
+ replacementCompleted = result.kind === 'completed';
197
+ refusedWork = result.kind === 'active_work' ? blocker.identity : undefined;
182
198
  recovery =
183
199
  result.kind === 'recovery_required'
184
200
  ? { identity: blocker.identity, diagnostic: result.diagnostic.slice(0, 8_192) }
@@ -195,7 +211,7 @@ export async function runHostHandoff(input) {
195
211
  signature = undefined;
196
212
  continue;
197
213
  }
198
- const attention = projectBlocker(blocker, current.revision, recovery?.diagnostic);
214
+ const attention = projectBlocker(blocker, current.revision, recovery?.diagnostic, refusedWork === blocker.identity);
199
215
  if (!input.openSurface)
200
216
  throw new HostHandoffRequiredError(attention);
201
217
  surface ??= input.openSurface(submit);
@@ -211,7 +227,7 @@ export async function runHostHandoff(input) {
211
227
  resolve();
212
228
  };
213
229
  const abort = () => finish(input.signal?.reason);
214
- const timer = setTimeout(finish, interval);
230
+ const timer = blocker.manualRecheck ? undefined : setTimeout(finish, interval);
215
231
  wake = () => finish();
216
232
  input.signal?.addEventListener('abort', abort, { once: true });
217
233
  if (input.signal?.aborted)
@@ -225,7 +241,7 @@ export async function runHostHandoff(input) {
225
241
  surface?.close();
226
242
  }
227
243
  }
228
- function projectBlocker(blocker, revision, recovery) {
244
+ function projectBlocker(blocker, revision, recovery, activeWorkRefused = false) {
229
245
  const replacement = blocker.replacement;
230
246
  return {
231
247
  revision,
@@ -237,15 +253,30 @@ function projectBlocker(blocker, revision, recovery) {
237
253
  ? 'repair_required'
238
254
  : !replacement
239
255
  ? 'operator_required'
240
- : !blocker.activity
241
- ? 'activity_unknown'
242
- : isHostActivityIdle(blocker.activity)
243
- ? 'retry_required'
244
- : 'busy',
256
+ : replacement.requiresExplicitSelection
257
+ ? activeWorkRefused
258
+ ? 'busy'
259
+ : 'replacement_required'
260
+ : !blocker.activity
261
+ ? 'activity_unknown'
262
+ : isHostActivityIdle(blocker.activity)
263
+ ? 'retry_required'
264
+ : 'busy',
245
265
  ...(blocker.activity ? { activity: blocker.activity } : {}),
246
266
  mayExitNaturally: blocker.mayExitNaturally,
267
+ ...(blocker.manualRecheck ? { manualRecheck: true } : {}),
268
+ ...(blocker.packageChange ? { packageChange: blocker.packageChange } : {}),
247
269
  ...(replacement ? { operation: replacement.kind } : {}),
248
- actions: replacement?.canInterrupt ? ['cancel', 'retry', 'interrupt'] : ['cancel', 'retry'],
270
+ actions: replacement?.requiresExplicitSelection
271
+ ? [
272
+ 'cancel',
273
+ 'retry',
274
+ 'replace',
275
+ ...(activeWorkRefused && replacement.canInterrupt ? ['interrupt'] : []),
276
+ ]
277
+ : replacement?.canInterrupt
278
+ ? ['cancel', 'retry', 'interrupt']
279
+ : ['cancel', 'retry'],
249
280
  defaultAction: 'cancel',
250
281
  ...(blocker.recoveryBlocker ? { recoveryBlocker: blocker.recoveryBlocker } : {}),
251
282
  ...(blocker.operatorStep ? { operatorStep: blocker.operatorStep } : {}),
@@ -265,6 +296,9 @@ function blockerSignature(blocker) {
265
296
  blocker.replacement?.kind,
266
297
  blocker.replacement?.canReplaceIdle,
267
298
  blocker.replacement?.canInterrupt,
299
+ blocker.replacement?.requiresExplicitSelection,
300
+ blocker.manualRecheck,
301
+ blocker.packageChange,
268
302
  blocker.operatorStep,
269
303
  blocker.recoveryBlocker,
270
304
  blocker.diagnostic,
@@ -41,6 +41,29 @@ const frameBase = {
41
41
  schemaVersion: z.literal(1),
42
42
  sequence: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
43
43
  };
44
+ const environmentBindingFields = {
45
+ version: boundedString(128),
46
+ serviceId: z.string().regex(/^[a-f0-9]{64}$/u),
47
+ deploymentId: z.string().uuid(),
48
+ operator: z.unknown().transform((value, context) => {
49
+ try {
50
+ const command = decodeRuntimeHostOperatorCommand(value);
51
+ if (command.kind !== 'node') {
52
+ throw new Error('Runtime Host setup operator must be a Node command');
53
+ }
54
+ return command;
55
+ }
56
+ catch (error) {
57
+ context.addIssue({
58
+ code: 'custom',
59
+ message: error instanceof Error ? error.message : 'Runtime Host operator is invalid',
60
+ });
61
+ return z.NEVER;
62
+ }
63
+ }),
64
+ rootPath: boundedString(4 * 1024).refine((value) => !/[\u0000-\u001f\u007f]/u.test(value)),
65
+ rootId: z.string().regex(/^[a-f0-9]{64}$/u),
66
+ };
44
67
  const SETUP_FRAME_SCHEMA = z.discriminatedUnion('kind', [
45
68
  z
46
69
  .object({
@@ -53,27 +76,7 @@ const SETUP_FRAME_SCHEMA = z.discriminatedUnion('kind', [
53
76
  .object({
54
77
  ...frameBase,
55
78
  kind: z.literal('complete'),
56
- version: boundedString(128),
57
- serviceId: z.string().regex(/^[a-f0-9]{64}$/u),
58
- deploymentId: z.string().uuid(),
59
- operator: z.unknown().transform((value, context) => {
60
- try {
61
- const command = decodeRuntimeHostOperatorCommand(value);
62
- if (command.kind !== 'node') {
63
- throw new Error('Runtime Host setup operator must be a Node command');
64
- }
65
- return command;
66
- }
67
- catch (error) {
68
- context.addIssue({
69
- code: 'custom',
70
- message: error instanceof Error ? error.message : 'Runtime Host operator is invalid',
71
- });
72
- return z.NEVER;
73
- }
74
- }),
75
- rootPath: boundedString(4 * 1024).refine((value) => !/[\u0000-\u001f\u007f]/u.test(value)),
76
- rootId: z.string().regex(/^[a-f0-9]{64}$/u),
79
+ ...environmentBindingFields,
77
80
  endpoint: boundedString(SETUP_FIELD_MAX_BYTES).refine((value) => parseRuntimeHostSetupEndpoint(value) !== undefined),
78
81
  credentialId: boundedString(SETUP_FIELD_MAX_BYTES),
79
82
  credential: boundedString(SETUP_CREDENTIAL_MAX_BYTES),
@@ -87,6 +90,13 @@ const SETUP_FRAME_SCHEMA = z.discriminatedUnion('kind', [
87
90
  .optional(),
88
91
  })
89
92
  .strict(),
93
+ z
94
+ .object({
95
+ ...frameBase,
96
+ kind: z.literal('existing_environment'),
97
+ ...environmentBindingFields,
98
+ })
99
+ .strict(),
90
100
  z
91
101
  .object({
92
102
  ...frameBase,
@@ -17,7 +17,6 @@
17
17
  * under the License.
18
18
  */
19
19
  import { createHash } from 'node:crypto';
20
- import { setTimeout as delay } from 'node:timers/promises';
21
20
  import { performance } from 'node:perf_hooks';
22
21
  import { canonicalPeerMeshMemberAdvertisement, canonicalPeerMeshRoster, createPeerMeshInvitationSecret, validatePeerMeshInvitation, decodeSignedPeerMeshMemberAdvertisement, decodeSignedPeerMeshRoster, generatePeerMeshAuthorityKeyPair, matchesPeerMeshInvitationSecret, PEER_MESH_MAX_MEMBERS, PEER_MESH_MAX_MESHES, PEER_MESH_MAX_INVITATION_RECORDS, PEER_MESH_MAX_PENDING_INVITATIONS, PEER_MESH_MAX_TRANSIT_ADDRESSES_PER_RELAY, PEER_MESH_MAX_TRANSIT_RELAY_ADDRESSES, peerMeshMemberAdvertisementSigningBytes, peerMeshId, peerMeshInvitationSecretDigest, signPeerMeshRoster, } from './model.js';
23
22
  import { canonicalPeerMeshDisplayName } from './display-name.js';
@@ -793,19 +792,23 @@ class PeerMeshNodeImpl {
793
792
  }
794
793
  }
795
794
  async #waitForReconciliationTrigger(observedGeneration, signal) {
796
- if (this.#reconcileGeneration !== observedGeneration)
795
+ if (this.#reconcileGeneration !== observedGeneration || signal.aborted)
797
796
  return;
798
797
  let wake;
799
798
  const triggered = new Promise((resolve) => {
800
799
  wake = resolve;
801
800
  this.#reconcileWaiters.add(wake);
802
801
  });
803
- if (this.#reconcileGeneration !== observedGeneration)
802
+ const timeout = setTimeout(wake, RECONCILE_INTERVAL_MS);
803
+ signal.addEventListener('abort', wake, { once: true });
804
+ if (this.#reconcileGeneration !== observedGeneration || signal.aborted)
804
805
  wake();
805
806
  try {
806
- await Promise.race([triggered, delay(RECONCILE_INTERVAL_MS, undefined, { signal })]);
807
+ await triggered;
807
808
  }
808
809
  finally {
810
+ clearTimeout(timeout);
811
+ signal.removeEventListener('abort', wake);
809
812
  this.#reconcileWaiters.delete(wake);
810
813
  }
811
814
  }
@@ -345,7 +345,7 @@ export function decodeArtifactQueryResult(value) {
345
345
  if (!isCanonicalBase64(chunkBase64)) {
346
346
  throw invalidProtocolFrame('Invalid artifact chunk');
347
347
  }
348
- const chunkBytes = Buffer.from(chunkBase64, 'base64').byteLength;
348
+ const chunkBytes = Buffer.byteLength(chunkBase64, 'base64');
349
349
  if (chunkBytes > ARTIFACT_READ_CHUNK_MAX_BYTES || offset + chunkBytes > totalBytes) {
350
350
  throw invalidProtocolFrame('Invalid artifact chunk bounds');
351
351
  }
@@ -466,7 +466,7 @@ function decodeBinaryPreview(value) {
466
466
  ]);
467
467
  const base64 = boundedText(exact.base64, 'artifact binary base64', base64MaxBytes(), true);
468
468
  if (!isCanonicalBase64(base64) ||
469
- Buffer.from(base64, 'base64').byteLength > ARTIFACT_PREVIEW_MAX_BYTES) {
469
+ Buffer.byteLength(base64, 'base64') > ARTIFACT_PREVIEW_MAX_BYTES) {
470
470
  throw invalidProtocolFrame('Invalid artifact binary base64');
471
471
  }
472
472
  return {
@@ -62,7 +62,16 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1;
62
62
  export const RUNTIME_HOST_PROTOCOL_VERSION = 0;
63
63
  // Increment when the same protocol version no longer guarantees safe Client-Host
64
64
  // interoperability. Mismatches are rejected before domain commands are admitted.
65
- export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 135;
65
+ export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 142;
66
+ // 142: Invocable Skill queries expose missing and archived Session refusals explicitly.
67
+ // 141: WorkHub root admissions bind model Intent/Recall decisions before actions.
68
+ // 140: Plugin Platform queries expose scoped Command contribution projections.
69
+ // Epoch-139 peers reject the added query view and result shape.
70
+ // 139: WorkHub recovery preserves the Host-authenticated Desktop capability binding.
71
+ // 138: Removes the unused steering display anchor from canonical MessageContent.
72
+ // 137: Reserved by the former display anchor contract.
73
+ // 136: WorkHub transient proposals distinguish routing dispositions from linked
74
+ // operations. Older peers expect replace/stop_work/resume_work dispositions.
66
75
  // 135: WorkHub model Turns replace direct action proposals with active-Turn task tools.
67
76
  // 134: Coordination actions own real Runtime Turns. Removes the synthetic record
68
77
  // operation, projects typed action receipts and admitted action identities, and
@@ -108,7 +108,7 @@ export const PLUGIN_PLATFORM_OPERATION_SPECS = {
108
108
  };
109
109
  function decodePluginPlatformQueryInput(value) {
110
110
  const input = requireShapedRecord(value, 'Plugin Platform query input', ['view'], ['rootId', 'cursor', 'limit']);
111
- if (!['status', 'packages', 'entries', 'tools', 'failures'].includes(input.view)) {
111
+ if (!['status', 'packages', 'entries', 'tools', 'commands', 'failures'].includes(input.view)) {
112
112
  throw invalidProtocolFrame('Invalid Plugin Platform query view');
113
113
  }
114
114
  const view = input.view;
@@ -125,8 +125,8 @@ function decodePluginPlatformQueryInput(value) {
125
125
  (input.rootId !== undefined || cursor !== undefined || limit !== undefined)) {
126
126
  throw invalidProtocolFrame('Plugin Platform status query does not accept paging');
127
127
  }
128
- if (input.rootId !== undefined && view !== 'entries' && view !== 'tools') {
129
- throw invalidProtocolFrame('Plugin root identity is only valid for Entry and Tool queries');
128
+ if (input.rootId !== undefined && view !== 'entries' && view !== 'tools' && view !== 'commands') {
129
+ throw invalidProtocolFrame('Plugin root identity is only valid for Entry, Tool, and Command queries');
130
130
  }
131
131
  let rootId;
132
132
  if (input.rootId !== undefined) {
@@ -190,7 +190,7 @@ function decodePluginPlatformQueryResult(value) {
190
190
  ]);
191
191
  if (!Array.isArray(output.items) ||
192
192
  output.items.length > 64 ||
193
- !['packages', 'entries', 'tools', 'failures'].includes(view)) {
193
+ !['packages', 'entries', 'tools', 'commands', 'failures'].includes(view)) {
194
194
  throw invalidProtocolFrame('Invalid Plugin Platform page');
195
195
  }
196
196
  const nextCursor = output.nextCursor === null
@@ -203,11 +203,36 @@ function decodePluginPlatformQueryResult(value) {
203
203
  ? { view, items: decodeInspections(output.items), nextCursor }
204
204
  : view === 'tools'
205
205
  ? { view, items: output.items.map(decodeToolInspection), nextCursor }
206
- : { view: 'failures', items: output.items.map(decodePlatformFailure), nextCursor };
206
+ : view === 'commands'
207
+ ? { view, items: output.items.map(decodeCommandInspection), nextCursor }
208
+ : { view: 'failures', items: output.items.map(decodePlatformFailure), nextCursor };
207
209
  }
208
210
  requireEncodedByteLimit(decoded, 'Plugin Platform query result', PLUGIN_PLATFORM_QUERY_RESULT_MAX_BYTES);
209
211
  return decoded;
210
212
  }
213
+ function decodeCommandInspection(value) {
214
+ const item = requireExactRecord(value, 'Plugin Command inspection', [
215
+ 'entryId',
216
+ 'scopeId',
217
+ 'extensionId',
218
+ 'generation',
219
+ 'name',
220
+ 'description',
221
+ 'aliases',
222
+ ]);
223
+ if (!Array.isArray(item.aliases) || item.aliases.length > 64) {
224
+ throw invalidProtocolFrame('Invalid Plugin Command aliases');
225
+ }
226
+ return {
227
+ entryId: requireId(item.entryId, 'Plugin Entry identity'),
228
+ scopeId: requireString(item.scopeId, 'Plugin scope identity', 256),
229
+ extensionId: requireId(item.extensionId, 'Plugin package identity'),
230
+ generation: requireCount(item.generation, 'Plugin generation'),
231
+ name: requireString(item.name, 'Plugin Command name', 128),
232
+ description: requireString(item.description, 'Plugin Command description', 4096),
233
+ aliases: item.aliases.map((alias) => requireString(alias, 'Plugin Command alias', 128)),
234
+ };
235
+ }
211
236
  function decodePlatformFailure(value) {
212
237
  const failure = requireShapedRecord(value, 'Plugin Platform failure', ['diagnostic'], ['entryId', 'extensionId']);
213
238
  if (failure.entryId === undefined && failure.extensionId === undefined) {
@@ -165,7 +165,8 @@ export function decodeSessionTranscriptPage(value) {
165
165
  assertFragmentOrder(fragments, direction);
166
166
  const rawBytes = requireCount(result.rawBytes, 'Session transcript page bytes');
167
167
  if (rawBytes > SESSION_TRANSCRIPT_PAGE_MAX_BYTES ||
168
- fragments.reduce((total, fragment) => total + Buffer.from(fragment.data, 'base64').byteLength, 0) !== rawBytes) {
168
+ fragments.reduce((total, fragment) => total + Buffer.byteLength(fragment.data, 'base64'), 0) !==
169
+ rawBytes) {
169
170
  throw invalidProtocolFrame('Invalid Session transcript page byte count');
170
171
  }
171
172
  const nextCursor = result.nextCursor === null
@@ -231,7 +232,7 @@ function decodeSessionTranscriptFragment(value, source, throughSequence) {
231
232
  const byteOffset = requireCount(exact.byteOffset, 'Session transcript fragment byte offset');
232
233
  const totalBytes = requireCount(exact.totalBytes, 'Session transcript fragment total bytes');
233
234
  const data = requireBase64Fragment(exact.data);
234
- const dataBytes = Buffer.from(data, 'base64').byteLength;
235
+ const dataBytes = Buffer.byteLength(data, 'base64');
235
236
  if (totalBytes === 0 ||
236
237
  dataBytes === 0 ||
237
238
  byteOffset >= totalBytes ||
@@ -42,6 +42,7 @@ const QUERY_ERRORS = [
42
42
  'persistence_failed',
43
43
  'internal_failure',
44
44
  ];
45
+ const INVOCABLE_QUERY_ERRORS = [...QUERY_ERRORS, 'not_found', 'session_archived'];
45
46
  const MUTATION_ERRORS = [...QUERY_ERRORS, 'commit_outcome_unknown'];
46
47
  export const SKILL_CATALOG_OPERATION_SPECS = {
47
48
  'skill.catalog.query': defineHostPathOperation({
@@ -54,7 +55,7 @@ export const SKILL_CATALOG_OPERATION_SPECS = {
54
55
  'skill.catalog.invocable.query': defineHostPathOperation({
55
56
  mode: 'query',
56
57
  availability: 'ready',
57
- errors: QUERY_ERRORS,
58
+ errors: INVOCABLE_QUERY_ERRORS,
58
59
  decodeInput: decodeInvocableQueryInput,
59
60
  decodeOutput: decodeInvocableQueryResult,
60
61
  }, (input) => input.target.kind === 'new_session' && input.target.context.workspace.kind === 'host_path'),
@@ -182,8 +182,10 @@ function decodeWorkHubCoordinationActionFields(input) {
182
182
  const proposal = decodeWorkHubCoordinationProposal(input.proposal);
183
183
  if (input.newWorkDefaults !== undefined &&
184
184
  (!isWorkHubCreateDefaults(input.newWorkDefaults) ||
185
- !(proposal.disposition === 'create_new' ||
186
- (proposal.disposition === 'replace' && proposal.target.disposition === 'create_new')))) {
185
+ !(('disposition' in proposal && proposal.disposition === 'create_new') ||
186
+ ('operation' in proposal &&
187
+ proposal.operation === 'correct' &&
188
+ proposal.target.disposition === 'create_new')))) {
187
189
  throw invalidProtocolFrame('Invalid WorkHub creation defaults');
188
190
  }
189
191
  const delegationText = input.delegationText === undefined
@@ -191,9 +193,7 @@ function decodeWorkHubCoordinationActionFields(input) {
191
193
  : requireUtf8String(input.delegationText, 'WorkHub delegation text', WORKHUB_COORDINATION_TEXT_MAX_BYTES);
192
194
  if (delegationText !== undefined &&
193
195
  (!delegationText.trim() ||
194
- (proposal.disposition !== 'delegate_existing' &&
195
- proposal.disposition !== 'create_new' &&
196
- proposal.disposition !== 'replace'))) {
196
+ !('disposition' in proposal || ('operation' in proposal && proposal.operation === 'correct')))) {
197
197
  throw invalidProtocolFrame('Invalid WorkHub delegation text');
198
198
  }
199
199
  const base = {
@@ -204,19 +204,19 @@ function decodeWorkHubCoordinationActionFields(input) {
204
204
  : {}),
205
205
  ...(delegationText === undefined ? {} : { delegationText }),
206
206
  };
207
- if (proposal.disposition === 'delegate_existing') {
207
+ if ('disposition' in proposal && proposal.disposition === 'delegate_existing') {
208
208
  if (input.create !== undefined || input.candidateSetId === undefined) {
209
209
  throw invalidProtocolFrame('Invalid WorkHub delegation context');
210
210
  }
211
211
  return { ...base, candidateSetId: candidateSetId(input.candidateSetId) };
212
212
  }
213
- if (proposal.disposition === 'create_new') {
213
+ if ('disposition' in proposal && proposal.disposition === 'create_new') {
214
214
  if (input.candidateSetId !== undefined || input.create === undefined) {
215
215
  throw invalidProtocolFrame('Invalid WorkHub creation context');
216
216
  }
217
217
  return { ...base, create: decodeWorkHubCoordinationCreateContext(input.create) };
218
218
  }
219
- if (proposal.disposition === 'replace') {
219
+ if ('operation' in proposal && proposal.operation === 'correct') {
220
220
  if (proposal.target.disposition === 'delegate_existing') {
221
221
  if (input.candidateSetId === undefined || input.create !== undefined) {
222
222
  throw invalidProtocolFrame('Invalid WorkHub replacement context');
@@ -285,9 +285,9 @@ function decodeWorkHubCoordinationProposal(value) {
285
285
  title: requireUtf8String(exact.title, 'WorkHub Session title', COORDINATION_TITLE_MAX_BYTES),
286
286
  };
287
287
  }
288
- if (proposal.disposition === 'replace') {
288
+ if (proposal.operation === 'correct') {
289
289
  const exact = requireExactRecord(proposal, 'WorkHub replacement proposal', [
290
- 'disposition',
290
+ 'operation',
291
291
  'replacesActionId',
292
292
  'target',
293
293
  ]);
@@ -298,7 +298,7 @@ function decodeWorkHubCoordinationProposal(value) {
298
298
  'candidateRef',
299
299
  ]);
300
300
  return {
301
- disposition: 'replace',
301
+ operation: 'correct',
302
302
  replacesActionId: requireEntityId(exact.replacesActionId, 'WorkHub replaced action id'),
303
303
  target: {
304
304
  disposition: 'delegate_existing',
@@ -312,7 +312,7 @@ function decodeWorkHubCoordinationProposal(value) {
312
312
  'title',
313
313
  ]);
314
314
  return {
315
- disposition: 'replace',
315
+ operation: 'correct',
316
316
  replacesActionId: requireEntityId(exact.replacesActionId, 'WorkHub replaced action id'),
317
317
  target: {
318
318
  disposition: 'create_new',
@@ -322,29 +322,31 @@ function decodeWorkHubCoordinationProposal(value) {
322
322
  }
323
323
  throw invalidProtocolFrame('Invalid WorkHub replacement target');
324
324
  }
325
- if (proposal.disposition === 'stop_work') {
326
- const exact = requireExactRecord(proposal, 'WorkHub stop proposal', ['disposition', 'expects']);
325
+ if (proposal.operation === 'stop') {
326
+ const exact = requireExactRecord(proposal, 'WorkHub stop proposal', ['operation', 'expects']);
327
327
  return {
328
- disposition: 'stop_work',
329
- expects: decodeWorkHubCoordinationStopPreconditions(exact.expects),
328
+ operation: 'stop',
329
+ expects: decodeWorkHubCoordinationLinkedTargetPreconditions(exact.expects),
330
330
  };
331
331
  }
332
- if (proposal.disposition === 'resume_work') {
332
+ if (proposal.operation === 'resume') {
333
333
  const exact = requireExactRecord(proposal, 'WorkHub resume proposal', [
334
- 'disposition',
334
+ 'operation',
335
335
  'expects',
336
336
  'resumesActionId',
337
337
  ]);
338
338
  return {
339
- disposition: 'resume_work',
339
+ operation: 'resume',
340
340
  resumesActionId: requireEntityId(exact.resumesActionId, 'WorkHub resume assignment'),
341
- expects: decodeWorkHubCoordinationStopPreconditions(exact.expects),
341
+ expects: decodeWorkHubCoordinationLinkedTargetPreconditions(exact.expects),
342
342
  };
343
343
  }
344
- throw invalidProtocolFrame('Invalid WorkHub Coordination proposal disposition');
344
+ throw invalidProtocolFrame('Invalid WorkHub Coordination proposal');
345
345
  }
346
- function decodeWorkHubCoordinationStopPreconditions(value) {
347
- const expects = requireExactRecord(value, 'WorkHub stop preconditions', ['targetSessionId']);
346
+ function decodeWorkHubCoordinationLinkedTargetPreconditions(value) {
347
+ const expects = requireExactRecord(value, 'WorkHub linked-target preconditions', [
348
+ 'targetSessionId',
349
+ ]);
348
350
  return {
349
351
  targetSessionId: requireEntityId(expects.targetSessionId, 'WorkHub target Session id'),
350
352
  };
@@ -85,6 +85,8 @@ export class HostArtifactCoordinator {
85
85
  async #ingest(input, context) {
86
86
  try {
87
87
  if ((await this.#sessions.probeSessionRemoval(input.sessionId)).kind !== 'present') {
88
+ // Session removal can race an upload; release only this owner's staged bytes.
89
+ this.#uploads.abort(uploadKey(input.sessionId, input.uploadId), context);
88
90
  return ingestFailure('not_found', 'Session was not found');
89
91
  }
90
92
  switch (input.kind) {
@@ -101,8 +101,8 @@ export class HostClientCapabilityCoordinator {
101
101
  },
102
102
  };
103
103
  }
104
- async bindSession(sessionId, initiatingConnectionId) {
105
- return this.#bindSession(sessionId, initiatingConnectionId, 'strict');
104
+ async bindSession(sessionId, initiatingConnectionId, requiredToolNames) {
105
+ return this.#bindSession(sessionId, initiatingConnectionId, 'strict', requiredToolNames);
106
106
  }
107
107
  async bindSessionSuccessor(sessionId) {
108
108
  const result = await this.#bindSession(sessionId, '', 'degrade');
@@ -129,17 +129,61 @@ export class HostClientCapabilityCoordinator {
129
129
  this.#onModelToolsChanged();
130
130
  });
131
131
  }
132
- async #bindSession(sessionId, initiatingConnectionId, mode) {
132
+ async #bindSession(sessionId, initiatingConnectionId, mode, requiredToolNames) {
133
133
  return this.#activation.runMutation(async () => {
134
134
  const selection = this.#selectSessionState(sessionId, initiatingConnectionId, mode);
135
135
  if (!selection.ok)
136
136
  return selection;
137
+ const capabilityBinding = requiredToolNames
138
+ ? this.#toolProviderBinding(selection.state, requiredToolNames)
139
+ : undefined;
140
+ if (requiredToolNames && !capabilityBinding) {
141
+ return {
142
+ ok: false,
143
+ message: 'Required Client Capability tools do not have one available provider',
144
+ };
145
+ }
137
146
  this.#storeSessionState(sessionId, selection.state);
138
147
  if (selection.modelToolsChanged)
139
148
  this.#onModelToolsChanged();
140
- return { ok: true };
149
+ return { ok: true, ...(capabilityBinding ? { capabilityBinding } : {}) };
150
+ });
151
+ }
152
+ async bindRecoveredSession(sessionId, binding, toolNames) {
153
+ return this.#activation.runMutation(() => {
154
+ const provider = [...this.#providers.values()].find((candidate) => providerAuthorityDigest(candidate) === binding && this.#activeConnection(candidate));
155
+ if (!provider)
156
+ return false;
157
+ const connection = this.#activeConnection(provider);
158
+ const selection = this.#selectSessionState(sessionId, connection.connectionId, 'strict');
159
+ if (!selection.ok || this.#toolProviderBinding(selection.state, toolNames) !== binding)
160
+ return false;
161
+ this.#storeSessionState(sessionId, selection.state);
162
+ if (selection.modelToolsChanged)
163
+ this.#onModelToolsChanged();
164
+ return true;
141
165
  });
142
166
  }
167
+ #toolProviderBinding(state, toolNames) {
168
+ const missing = new Set(toolNames);
169
+ let selected;
170
+ for (const [contractId, binding] of state?.sessionBindings ?? []) {
171
+ if (binding.kind !== 'bound')
172
+ continue;
173
+ const provider = this.#providers.get(binding.providerId);
174
+ const offer = provider?.current?.offersByContract.get(contractId);
175
+ if (!provider || !this.#activeConnection(provider) || !offer)
176
+ continue;
177
+ for (const tool of offer.offer.tools) {
178
+ if (!missing.delete(mcpProxyToolName(tool.serverId, tool.name)))
179
+ continue;
180
+ if (selected && selected !== provider)
181
+ return undefined;
182
+ selected = provider;
183
+ }
184
+ }
185
+ return selected && missing.size === 0 ? providerAuthorityDigest(selected) : undefined;
186
+ }
143
187
  async runWithSessionBindingPreview(sessionId, initiatingConnectionId, operation) {
144
188
  return this.#activation.runReadActivation(async () => {
145
189
  const registryRevision = this.#revision;
@@ -1274,3 +1318,17 @@ function sessionCapabilityStatesEqual(left, right) {
1274
1318
  function asError(value) {
1275
1319
  return value instanceof Error ? value : new Error(String(value));
1276
1320
  }
1321
+ /** Registration IDs and connections rotate; authenticated provider authority must not. */
1322
+ function providerAuthorityDigest(provider) {
1323
+ return `sha256:${createHash('sha256')
1324
+ .update(JSON.stringify([
1325
+ 'maka.client-capability-authority.v1',
1326
+ provider.principalKind,
1327
+ provider.principalId,
1328
+ provider.clientInstanceId,
1329
+ provider.credentialBoundClientInstanceId ?? null,
1330
+ provider.capabilityOwner?.principalId ?? null,
1331
+ provider.capabilityOwner?.clientInstanceId ?? null,
1332
+ ]))
1333
+ .digest('hex')}`;
1334
+ }