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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +38 -19
  2. package/README.zh-CN.md +34 -19
  3. package/dist/mcp-capability-provider.js +4 -1
  4. package/dist/pi-transcript-tools.js +6 -0
  5. package/dist/pi-tui-layout.js +56 -10
  6. package/dist/pi-tui-mcp-status.js +2 -2
  7. package/dist/pi-tui-pickers.js +121 -30
  8. package/dist/pi-tui-runner.js +156 -42
  9. package/dist/pi-tui-todo.js +221 -0
  10. package/dist/pi-tui-transcript-viewer.js +134 -7
  11. package/dist/runtime-host-cli-context.js +70 -13
  12. package/dist/runtime-host-handoff-surface.js +51 -26
  13. package/dist/runtime-host-local-handoff.js +11 -1
  14. package/dist/runtime-host-service-manager.js +0 -3
  15. package/dist/runtime-host-session-channel.js +13 -0
  16. package/dist/runtime-host-session-driver.js +42 -1
  17. package/dist/tui-autocomplete-layout.js +3 -1
  18. package/dist/tui-copy-catalog.js +85 -6
  19. package/dist/tui-editor-render.js +36 -0
  20. package/dist/tui-mcp-control.js +41 -6
  21. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  22. package/node_modules/@maka/core/dist/agent-run.js +1 -0
  23. package/node_modules/@maka/core/dist/attachments.js +3 -0
  24. package/node_modules/@maka/core/dist/canonical-json.js +27 -0
  25. package/node_modules/@maka/core/dist/capabilities.js +35 -0
  26. package/node_modules/@maka/core/dist/computer-use.js +0 -1
  27. package/node_modules/@maka/core/dist/display-redaction.js +18 -0
  28. package/node_modules/@maka/core/dist/llm-connections.js +13 -0
  29. package/node_modules/@maka/core/dist/model-call-attempt.js +2 -0
  30. package/node_modules/@maka/core/dist/model-metadata.js +4 -0
  31. package/node_modules/@maka/core/dist/redaction.js +35 -0
  32. package/node_modules/@maka/core/dist/run-composition.js +77 -2
  33. package/node_modules/@maka/core/dist/runtime-event.js +3 -0
  34. package/node_modules/@maka/core/dist/session.js +57 -6
  35. package/node_modules/@maka/core/dist/slash-command-catalog.js +1 -0
  36. package/node_modules/@maka/core/dist/tool-result-record-schema.js +2 -1
  37. package/node_modules/@maka/core/dist/workhub-action-result.js +68 -0
  38. package/node_modules/@maka/core/package.json +3 -3
  39. package/node_modules/@maka/eval/dist/experiment-directory.js +4 -13
  40. package/node_modules/@maka/eval/dist/toolchain-verification.js +1 -1
  41. package/node_modules/@maka/mcp/dist/form-elicitation.js +213 -0
  42. package/node_modules/@maka/mcp/dist/index.js +337 -71
  43. package/node_modules/@maka/runtime/dist/agent-run-inspect.js +0 -13
  44. package/node_modules/@maka/runtime/dist/agent-run.js +90 -2
  45. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +16 -8
  46. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +57 -50
  47. package/node_modules/@maka/runtime/dist/archive-read-tool.js +2 -2
  48. package/node_modules/@maka/runtime/dist/context-budget-helpers.js +0 -16
  49. package/node_modules/@maka/runtime/dist/conversation-copy.js +153 -11
  50. package/node_modules/@maka/runtime/dist/github-copilot-oauth-enrollment.js +3 -3
  51. package/node_modules/@maka/runtime/dist/goal-continuation.js +5 -1
  52. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +69 -1
  53. package/node_modules/@maka/runtime/dist/mcp-tools.js +7 -2
  54. package/node_modules/@maka/runtime/dist/model-fetcher.js +42 -2
  55. package/node_modules/@maka/runtime/dist/model-history.js +0 -112
  56. package/node_modules/@maka/runtime/dist/plugin-runtime.js +20 -10
  57. package/node_modules/@maka/runtime/dist/plugin-tool-service.js +205 -0
  58. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +8 -1
  59. package/node_modules/@maka/runtime/dist/request-shape.js +23 -0
  60. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +1 -1
  61. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +24 -1
  62. package/node_modules/@maka/runtime/dist/runtime-kernel.js +127 -13
  63. package/node_modules/@maka/runtime/dist/session-manager.js +20 -36
  64. package/node_modules/@maka/runtime/dist/stream-graph-coordinator.js +21 -0
  65. package/node_modules/@maka/runtime/dist/stream-graph-supervisor-tools.js +0 -5
  66. package/node_modules/@maka/runtime/dist/test-connection.js +0 -1
  67. package/node_modules/@maka/runtime/dist/tool-activation-identity.js +41 -0
  68. package/node_modules/@maka/runtime/dist/tool-availability.js +11 -1
  69. package/node_modules/@maka/runtime/dist/tool-result-archive-resource.js +63 -1
  70. package/node_modules/@maka/runtime/dist/tool-result-archive-transition.js +42 -12
  71. package/node_modules/@maka/runtime/dist/tool-result-archive.js +27 -4
  72. package/node_modules/@maka/runtime/package.json +1 -0
  73. package/node_modules/@maka/runtime-host/dist/client/connection.js +30 -5
  74. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +52 -13
  75. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +2 -0
  76. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +4 -2
  77. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +18 -1
  78. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +19 -0
  79. package/node_modules/@maka/runtime-host/dist/control/endpoint.js +1 -3
  80. package/node_modules/@maka/runtime-host/dist/node-error.js +22 -0
  81. package/node_modules/@maka/runtime-host/dist/operator/managed-deployment.js +1 -3
  82. package/node_modules/@maka/runtime-host/dist/peer-mesh/limits.js +0 -1
  83. package/node_modules/@maka/runtime-host/dist/peer-mesh/model.js +1 -1
  84. package/node_modules/@maka/runtime-host/dist/peer-reachability/publisher.js +1 -3
  85. package/node_modules/@maka/runtime-host/dist/protocol/index.js +9 -1
  86. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +3 -2
  87. package/node_modules/@maka/runtime-host/dist/protocol/plan.js +1 -0
  88. package/node_modules/@maka/runtime-host/dist/protocol/plugin-platform.js +27 -5
  89. package/node_modules/@maka/runtime-host/dist/protocol/project-catalog.js +2 -2
  90. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -0
  91. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +86 -157
  92. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +4 -0
  93. package/node_modules/@maka/runtime-host/dist/server/agent-graph-coordinator.js +0 -3
  94. package/node_modules/@maka/runtime-host/dist/server/canonical-session-projection.js +2 -1
  95. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +28 -8
  96. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +112 -56
  97. package/node_modules/@maka/runtime-host/dist/server/execution-artifacts.js +32 -32
  98. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +85 -25
  99. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +42 -14
  100. package/node_modules/@maka/runtime-host/dist/server/failure-diagnostic.js +24 -0
  101. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +7 -3
  102. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +3 -3
  103. package/node_modules/@maka/runtime-host/dist/server/host-session-availability.js +5 -2
  104. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +3 -2
  105. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +34 -4
  106. package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +51 -23
  107. package/node_modules/@maka/runtime-host/dist/server/operation-dispatcher.js +2 -7
  108. package/node_modules/@maka/runtime-host/dist/server/plugin-platform-coordinator.js +6 -0
  109. package/node_modules/@maka/runtime-host/dist/server/plugin-platform.js +6 -0
  110. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +110 -14
  111. package/node_modules/@maka/runtime-host/dist/server/runtime-resource-coordinator.js +11 -8
  112. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +31 -21
  113. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +1 -1
  114. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +20 -8
  115. package/node_modules/@maka/runtime-host/dist/server/session-retirement-coordinator.js +5 -2
  116. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +4 -0
  117. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +15 -104
  118. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +3 -0
  119. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +59 -82
  120. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +132 -139
  121. package/node_modules/@maka/runtime-host/dist/server/workhub-message-attachments.js +46 -0
  122. package/node_modules/@maka/storage/dist/agent-run-store.js +14 -1
  123. package/node_modules/@maka/storage/dist/artifact-store.js +40 -3
  124. package/node_modules/@maka/storage/dist/atomic-file-write.js +74 -0
  125. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -1
  126. package/node_modules/@maka/storage/dist/codex-session-adapter.js +284 -196
  127. package/node_modules/@maka/storage/dist/credential-store.js +12 -63
  128. package/node_modules/@maka/storage/dist/execution-stores.js +4 -1
  129. package/node_modules/@maka/storage/dist/git-exec.js +61 -0
  130. package/node_modules/@maka/storage/dist/git-worktree-child-executor.js +8 -35
  131. package/node_modules/@maka/storage/dist/goal-authority.js +2 -2
  132. package/node_modules/@maka/storage/dist/interaction-store.js +2 -2
  133. package/node_modules/@maka/storage/dist/mcp-config-store.js +10 -25
  134. package/node_modules/@maka/storage/dist/project-catalog.js +2 -23
  135. package/node_modules/@maka/storage/dist/runtime-event-invariants.js +2 -2
  136. package/node_modules/@maka/storage/dist/runtime-policy/connection-catalog-document.js +8 -3
  137. package/node_modules/@maka/storage/dist/scheduled-task-store.js +249 -283
  138. package/node_modules/@maka/storage/dist/session-bundle-manifest.js +5 -17
  139. package/node_modules/@maka/storage/dist/session-bundle-policy.js +2 -1
  140. package/node_modules/@maka/storage/dist/session-copy-cleanup.js +2 -1
  141. package/node_modules/@maka/storage/dist/session-store.js +14 -2
  142. package/node_modules/@maka/storage/dist/settings-store.js +8 -4
  143. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +11 -0
  144. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +1 -2
  145. package/node_modules/@maka/storage/dist/sqlite-session-metadata-schema.js +12 -1
  146. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +67 -3
  147. package/node_modules/@maka/storage/dist/stable-storage.js +14 -1
  148. package/node_modules/@maka/storage/dist/storage-id.js +28 -0
  149. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +31 -4
  150. package/node_modules/@maka/storage/dist/workspace-identity.js +2 -15
  151. package/node_modules/@maka/storage/dist/workspace-version-authority-internal.js +1 -8
  152. package/package.json +1 -1
  153. package/node_modules/@maka/core/dist/workhub-creation-intent.js +0 -990
  154. package/node_modules/@maka/core/dist/workhub-session-resolver.js +0 -50
@@ -22,34 +22,35 @@ import { join } from 'node:path';
22
22
  import { isDeepStrictEqual } from 'node:util';
23
23
  import { normalizeMessageContent } from '@maka/core/events';
24
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
- import { WORKHUB_COORDINATION_SUMMARY_MAX_BYTES, WORKHUB_COORDINATION_TEXT_MAX_BYTES, } from '../protocol/index.js';
26
- import { SessionOperationFailure } from './session-catalog-coordinator.js';
25
+ import { WORKHUB_COORDINATION_TEXT_MAX_BYTES } from '../protocol/index.js';
26
+ import { SessionOperationFailure, projectSessionCatalogRecord, } from './session-catalog-coordinator.js';
27
27
  import { WorkHubActionEffectFailure, WorkHubActionGateFailure, WorkHubCoordinationActionGate, } from './workhub-coordination-action-gate.js';
28
28
  const CREATE_FINGERPRINT = `sha256:${createHash('sha256')
29
29
  .update('maka:workhub-coordination-session:v1', 'utf8')
30
30
  .digest('hex')}`;
31
31
  const COORDINATION_CWD_DIRECTORY = 'workhub-coordination';
32
- const COORDINATION_TOOL_PROFILE = 'workhub-coordination-v1';
33
- const SYNTHETIC_COORDINATION_MODEL_ID = 'maka-workhub-coordination';
34
- const COORDINATION_PERMISSION_MODE = 'explore';
32
+ const COORDINATION_TOOL_PROFILE = 'workhub-coordination-v2';
33
+ const COORDINATION_PERMISSION_MODE = 'bypass';
35
34
  const COORDINATION_COLLABORATION_MODE = 'agent';
36
35
  const COORDINATION_ORCHESTRATION_MODE = 'default';
37
36
  const COORDINATION_SUMMARY_MESSAGE_KINDS = ['user', 'assistant', 'state'];
38
37
  const TURN_IDENTITY_CONFLICT_MESSAGE = 'WorkHub Coordination Turn identity belongs to a different operation';
39
38
  // A one-byte control character can occupy six bytes as a JSON `\u0000` escape.
40
39
  const JSON_ESCAPE_MAX_BYTES_PER_INPUT_BYTE = 6;
41
- const COORDINATION_SUMMARY_READ_MAX_BYTES = JSON_ESCAPE_MAX_BYTES_PER_INPUT_BYTE *
42
- (WORKHUB_COORDINATION_TEXT_MAX_BYTES + WORKHUB_COORDINATION_SUMMARY_MAX_BYTES) +
40
+ const COORDINATION_SUMMARY_READ_MAX_BYTES = JSON_ESCAPE_MAX_BYTES_PER_INPUT_BYTE * (WORKHUB_COORDINATION_TEXT_MAX_BYTES + 8 * 1024) +
43
41
  16 * 1024;
44
42
  /** Resolves the one durable Coordination Session owned by this Runtime Host. */
45
43
  export class HostWorkHubCoordinationCoordinator {
46
44
  handlers = {
47
45
  'workhub.coordination.resolve': () => this.#resolve(),
46
+ 'workhub.coordination.query': () => this.#query(),
47
+ 'workhub.coordination.configureModel': (input) => this.#configureModel(input),
48
48
  'workhub.coordination.answer': (input, context) => this.#answer(input, context),
49
- 'workhub.coordination.record': (input) => this.#record(input),
50
49
  'workhub.coordination.candidates': () => this.#candidates(),
51
- 'workhub.coordination.act': (input, context) => this.#act(input, context),
50
+ 'workhub.coordination.actFromTurn': (input, context) => this.#actFromTurn(input, context),
52
51
  };
52
+ #transitionConfiguration;
53
+ #configureModel;
53
54
  #coordinationCwd;
54
55
  #stores;
55
56
  #admission;
@@ -60,6 +61,8 @@ export class HostWorkHubCoordinationCoordinator {
60
61
  #actionGate;
61
62
  #readDelegationRetirement;
62
63
  constructor(options) {
64
+ this.#configureModel = options.configureModel;
65
+ this.#transitionConfiguration = options.transitionConfiguration;
63
66
  this.#coordinationCwd = join(options.stateRoot, COORDINATION_CWD_DIRECTORY);
64
67
  this.#stores = options.stores;
65
68
  this.#readDelegationRetirement = options.sessionActions.readDelegationRetirement;
@@ -87,22 +90,6 @@ export class HostWorkHubCoordinationCoordinator {
87
90
  readSupersession: (delegationId) => this.#stores.readWorkHubSupersession(delegationId),
88
91
  readStopRequest: (delegationId) => this.#stores.readWorkHubStopRequest(delegationId),
89
92
  readStopResolution: (delegationId) => this.#stores.readWorkHubStopResolution(delegationId),
90
- answer: async (input, context) => {
91
- const outcome = await this.#answer({ turnId: input.turnId, text: input.text }, context);
92
- if (!outcome.ok) {
93
- throw new WorkHubActionEffectFailure(outcome.error.code, outcome.error.message);
94
- }
95
- },
96
- clarify: async (input) => {
97
- const outcome = await this.#record({
98
- turnId: input.turnId,
99
- userText: input.userText,
100
- assistantText: input.assistantText,
101
- });
102
- if (!outcome.ok) {
103
- throw new WorkHubActionEffectFailure(outcome.error.code, outcome.error.message);
104
- }
105
- },
106
93
  assign: options.sessionActions.assign,
107
94
  prepareReplacement: (input) => this.#prepareReplacement(input),
108
95
  abortReplacement: (input) => this.#abortReplacement(input),
@@ -113,7 +100,7 @@ export class HostWorkHubCoordinationCoordinator {
113
100
  resume: async (input, context) => ({
114
101
  disposition: 'resume_work',
115
102
  targetSessionId: input.source.targetSessionId,
116
- ...(await options.sessionActions.resumeDelegation(input.source, context, input.actionId)),
103
+ ...(await options.sessionActions.resumeDelegation(input.source, context, input.actionId, input.validateFreshTarget)),
117
104
  }),
118
105
  });
119
106
  }
@@ -125,17 +112,19 @@ export class HostWorkHubCoordinationCoordinator {
125
112
  build: (existing) => ({
126
113
  type: 'workhub_coordination',
127
114
  id: `whp_${suffix}`,
128
- turnId: input.actionId,
115
+ turnId: existing?.turnId ?? input.coordinationTurnId ?? input.actionId,
129
116
  ts: existing?.ts ?? Date.now(),
130
117
  schemaVersion: WORKHUB_COORDINATION_REPLACEMENT_SCHEMA_VERSION,
131
118
  kind: 'delegation_replacement_requested',
132
119
  actionId: input.actionId,
133
120
  actionFingerprint: input.actionFingerprint,
134
- coordinationTurnId: input.actionId,
121
+ coordinationTurnId: existing?.coordinationTurnId ?? input.coordinationTurnId ?? input.actionId,
135
122
  targetSessionId: input.targetSessionId,
136
123
  targetSessionName: input.targetSessionName,
137
124
  disposition: input.disposition,
138
125
  userText: input.userText,
126
+ ...(input.attachments ? { attachments: input.attachments } : {}),
127
+ ...(input.delegationText === undefined ? {} : { delegationText: input.delegationText }),
139
128
  replacesActionId: input.replacesActionId,
140
129
  replacesDelegationId: input.replacesDelegationId,
141
130
  replacedTargetSessionId: input.replacedTargetSessionId,
@@ -177,13 +166,13 @@ export class HostWorkHubCoordinationCoordinator {
177
166
  build: (existing) => ({
178
167
  type: 'workhub_coordination',
179
168
  id: `whq_${suffix}`,
180
- turnId: input.actionId,
169
+ turnId: existing?.turnId ?? input.coordinationTurnId ?? input.actionId,
181
170
  ts: existing?.ts ?? Date.now(),
182
171
  schemaVersion: WORKHUB_COORDINATION_STOP_SCHEMA_VERSION,
183
172
  kind: 'delegation_stop_requested',
184
173
  actionId: input.actionId,
185
174
  actionFingerprint: input.actionFingerprint,
186
- coordinationTurnId: input.actionId,
175
+ coordinationTurnId: existing?.coordinationTurnId ?? input.coordinationTurnId ?? input.actionId,
187
176
  stopsActionId: input.stopsActionId,
188
177
  stopsDelegationId: input.stopsDelegationId,
189
178
  targetSessionId: input.targetSessionId,
@@ -231,7 +220,7 @@ export class HostWorkHubCoordinationCoordinator {
231
220
  build: (existing) => ({
232
221
  type: 'workhub_coordination',
233
222
  id: `whz_${suffix}`,
234
- turnId: request.actionId,
223
+ turnId: request.coordinationTurnId,
235
224
  ts: existing?.ts ?? Date.now(),
236
225
  schemaVersion: WORKHUB_COORDINATION_STOP_SCHEMA_VERSION,
237
226
  kind: 'delegation_stop_resolved',
@@ -262,13 +251,13 @@ export class HostWorkHubCoordinationCoordinator {
262
251
  build: (existing) => ({
263
252
  type: 'workhub_coordination',
264
253
  id: `whb_${suffix}`,
265
- turnId: replacement.actionId,
254
+ turnId: replacement.coordinationTurnId,
266
255
  ts: existing?.ts ?? Date.now(),
267
256
  schemaVersion: WORKHUB_COORDINATION_REPLACEMENT_SCHEMA_VERSION,
268
257
  kind: 'delegation_replacement_aborted',
269
258
  actionId: replacement.actionId,
270
259
  actionFingerprint: replacement.actionFingerprint,
271
- coordinationTurnId: replacement.actionId,
260
+ coordinationTurnId: replacement.coordinationTurnId,
272
261
  abortedActionId: replacement.replacesActionId,
273
262
  abortedDelegationId: replacement.replacesDelegationId,
274
263
  targetSessionId: replacement.targetSessionId,
@@ -338,9 +327,44 @@ export class HostWorkHubCoordinationCoordinator {
338
327
  };
339
328
  }
340
329
  }
341
- async #act(input, context) {
330
+ async #actFromTurn(input, context) {
331
+ let content;
342
332
  try {
343
- return { ok: true, result: await this.#actionGate.act(input, context) };
333
+ content = await this.#executions.readActiveWorkHubRequest(input.turnId);
334
+ }
335
+ catch {
336
+ return {
337
+ ok: false,
338
+ error: {
339
+ code: 'persistence_failed',
340
+ message: 'WorkHub active Turn authority is unavailable',
341
+ },
342
+ };
343
+ }
344
+ if (!content) {
345
+ return {
346
+ ok: false,
347
+ error: {
348
+ code: 'operation_conflict',
349
+ message: 'WorkHub action requires its currently active model Turn',
350
+ },
351
+ };
352
+ }
353
+ const { turnId: _turnId, ...action } = input;
354
+ const carriesAttachments = action.proposal.disposition === 'delegate_existing' ||
355
+ action.proposal.disposition === 'create_new' ||
356
+ action.proposal.disposition === 'replace';
357
+ try {
358
+ return {
359
+ ok: true,
360
+ result: await this.#actionGate.act({
361
+ ...action,
362
+ userText: content.text,
363
+ ...(carriesAttachments && content.attachments
364
+ ? { attachments: content.attachments }
365
+ : {}),
366
+ }, context, input.turnId),
367
+ };
344
368
  }
345
369
  catch (error) {
346
370
  if (error instanceof WorkHubActionEffectFailure) {
@@ -356,7 +380,11 @@ export class HostWorkHubCoordinationCoordinator {
356
380
  return {
357
381
  ok: false,
358
382
  error: {
359
- code: error.code === 'target_waiting_for_user' ? 'session_busy' : 'operation_conflict',
383
+ code: error.code === 'target_waiting_for_user'
384
+ ? 'session_busy'
385
+ : error.code === 'candidate_set_stale'
386
+ ? 'candidate_set_stale'
387
+ : 'operation_conflict',
360
388
  message: error.message,
361
389
  },
362
390
  };
@@ -370,6 +398,23 @@ export class HostWorkHubCoordinationCoordinator {
370
398
  };
371
399
  }
372
400
  }
401
+ async #query() {
402
+ try {
403
+ return {
404
+ ok: true,
405
+ result: projectSessionCatalogRecord(await this.#stores.readCatalogRecord(WORKHUB_COORDINATION_SESSION_ID, 'recoverable')),
406
+ };
407
+ }
408
+ catch {
409
+ return {
410
+ ok: false,
411
+ error: {
412
+ code: 'persistence_failed',
413
+ message: 'WorkHub Coordination Session state is unavailable',
414
+ },
415
+ };
416
+ }
417
+ }
373
418
  #resolve() {
374
419
  return this.#admission.run(WORKHUB_COORDINATION_SESSION_ID, async (lease) => {
375
420
  // The workspace exists for provisioning and for reuse alike: a directory
@@ -436,19 +481,21 @@ export class HostWorkHubCoordinationCoordinator {
436
481
  }
437
482
  async #answer(input, context) {
438
483
  if (!input.text.trim()) {
439
- return turnFailure('workhub.coordination.answer', 'operation_conflict', 'WorkHub answer text is empty');
484
+ return turnFailure('operation_conflict', 'WorkHub answer text is empty');
440
485
  }
441
486
  const outcome = await this.#executions.startWorkHubCoordinationMessage({
442
487
  sessionId: WORKHUB_COORDINATION_SESSION_ID,
443
488
  turnId: input.turnId,
444
489
  execution: {
445
490
  kind: 'workhub_coordination',
446
- inputDigest: digest({ text: input.text }),
491
+ inputDigest: digest({
492
+ text: input.text,
493
+ ...(input.attachments ? { attachments: input.attachments } : {}),
494
+ }),
447
495
  },
448
496
  archivedMessage: 'WorkHub Coordination Session is unavailable',
449
- // A recorded summary owns its Turn identity durably but is admitted
450
- // outside this ledger, so the probe runs under the admission lease: a
451
- // concurrent `record` cannot slip a second triplet into this Turn.
497
+ // Historical v1 summaries still own their Turn identities. Reject a
498
+ // fresh answer that would reuse one, even though new summaries are no longer written.
452
499
  prepareFreshContent: async () => {
453
500
  let recorded;
454
501
  try {
@@ -462,51 +509,18 @@ export class HostWorkHubCoordinationCoordinator {
462
509
  }
463
510
  return recorded.length > 0
464
511
  ? { kind: 'rejected', outcome: turnIdentityConflict() }
465
- : { kind: 'ready', content: normalizeMessageContent({ text: input.text }) };
512
+ : {
513
+ kind: 'ready',
514
+ content: normalizeMessageContent({
515
+ text: input.text,
516
+ ...(input.attachments ? { attachments: input.attachments } : {}),
517
+ }),
518
+ };
466
519
  },
467
520
  }, context);
468
521
  return outcome.ok ? { ok: true, result: { turnId: input.turnId } } : outcome;
469
522
  }
470
- #record(input) {
471
- if (!input.userText.trim() || !input.assistantText.trim()) {
472
- return Promise.resolve(turnFailure('workhub.coordination.record', 'operation_conflict', 'WorkHub Coordination summary text is empty'));
473
- }
474
- return this.#admission.run(WORKHUB_COORDINATION_SESSION_ID, async (lease) => {
475
- let header;
476
- try {
477
- header = await this.#stores.readHeaderSnapshot(WORKHUB_COORDINATION_SESSION_ID);
478
- }
479
- catch {
480
- return turnFailure('workhub.coordination.record', 'persistence_failed', 'WorkHub Coordination Session state is unavailable');
481
- }
482
- if (!validCoordinationHeader(header)) {
483
- return turnFailure('workhub.coordination.record', 'operation_conflict', 'WorkHub Coordination Session identity is unavailable');
484
- }
485
- const messages = coordinationSummaryMessages(input);
486
- try {
487
- const existing = await this.#readSummaryMessages(input.turnId);
488
- if (existing.length > 0) {
489
- return coordinationSummaryMatches(existing, input)
490
- ? { ok: true, result: { turnId: input.turnId } }
491
- : turnFailure('workhub.coordination.record', 'operation_conflict', 'WorkHub Coordination Turn identity belongs to different content');
492
- }
493
- // An answer owns its Turn identity in the root admission ledger. Both
494
- // operations take the same Session admission, so this probe settles the
495
- // race in one direction and the answer's own probe settles the other.
496
- if (await this.#executions.hasRootTurnAdmission(WORKHUB_COORDINATION_SESSION_ID, input.turnId)) {
497
- return turnFailure('workhub.coordination.record', 'operation_conflict', TURN_IDENTITY_CONFLICT_MESSAGE);
498
- }
499
- await this.#stores.appendMessages(WORKHUB_COORDINATION_SESSION_ID, messages);
500
- await this.#continuity.refreshCanonical(WORKHUB_COORDINATION_SESSION_ID, lease);
501
- return { ok: true, result: { turnId: input.turnId } };
502
- }
503
- catch {
504
- this.#requestDrain();
505
- return turnFailure('workhub.coordination.record', 'commit_outcome_unknown', 'WorkHub Coordination summary outcome is unknown');
506
- }
507
- });
508
- }
509
- /** Reads the durable summary triplet a `record` would own for this Turn. */
523
+ /** Reads a historical v1 summary to preserve its durable Turn identity. */
510
524
  async #readSummaryMessages(turnId) {
511
525
  const throughSequence = await this.#stores.readTranscriptHighWaterSnapshot(WORKHUB_COORDINATION_SESSION_ID);
512
526
  if (throughSequence === null)
@@ -526,32 +540,52 @@ export class HostWorkHubCoordinationCoordinator {
526
540
  */
527
541
  async #alignSession(record) {
528
542
  if (record.header.toolProfile !== undefined &&
543
+ record.header.toolProfile !== 'workhub-coordination-v1' &&
529
544
  record.header.toolProfile !== COORDINATION_TOOL_PROFILE) {
530
545
  return identityConflict();
531
546
  }
532
- if (record.header.cwd === this.#coordinationCwd && validCoordinationHeader(record.header)) {
547
+ if (record.header.cwd === this.#coordinationCwd &&
548
+ record.header.toolProfile === COORDINATION_TOOL_PROFILE &&
549
+ validCoordinationHeader(record.header)) {
533
550
  return success();
534
551
  }
552
+ // Resolving an old Session is the explicit durable upgrade boundary. Active
553
+ // or recovering v1 Turns must finish under their original zero-tool ceiling.
554
+ if (!this.#executions.isSessionExecutionIdle(WORKHUB_COORDINATION_SESSION_ID)) {
555
+ return validCoordinationHeader(record.header)
556
+ ? success()
557
+ : failure('operation_unavailable', 'WorkHub Coordination Session is still executing or recovering');
558
+ }
535
559
  let repaired;
536
560
  try {
561
+ // Permission mode is a projection of the execution boundary. Transition
562
+ // through Runtime authority to update both and retire cached backends
563
+ // before changing the profile. An interrupted upgrade remains closed to
564
+ // execution until a later resolve completes this versioned repair.
565
+ const configured = await this.#transitionConfiguration({
566
+ expectedRevision: record.revision,
567
+ clearConnectionBlock: false,
568
+ configuration: {
569
+ backend: record.header.backend,
570
+ llmConnectionId: record.header.llmConnectionId,
571
+ llmConnectionSlug: record.header.llmConnectionSlug,
572
+ connectionLocked: record.header.connectionLocked,
573
+ model: record.header.model,
574
+ thinkingLevel: record.header.thinkingLevel,
575
+ permissionMode: COORDINATION_PERMISSION_MODE,
576
+ collaborationMode: COORDINATION_COLLABORATION_MODE,
577
+ orchestrationMode: COORDINATION_ORCHESTRATION_MODE,
578
+ },
579
+ });
537
580
  repaired = await this.#stores.updateHeaderVersioned(WORKHUB_COORDINATION_SESSION_ID, {
538
581
  ...(record.header.cwd === this.#coordinationCwd ? {} : { cwd: this.#coordinationCwd }),
539
582
  ...(record.header.toolProfile === COORDINATION_TOOL_PROFILE
540
583
  ? {}
541
584
  : { toolProfile: COORDINATION_TOOL_PROFILE }),
542
- ...(record.header.permissionMode === COORDINATION_PERMISSION_MODE
543
- ? {}
544
- : { permissionMode: COORDINATION_PERMISSION_MODE }),
545
- ...((record.header.collaborationMode ?? 'agent') === COORDINATION_COLLABORATION_MODE
546
- ? {}
547
- : { collaborationMode: COORDINATION_COLLABORATION_MODE }),
548
- ...((record.header.orchestrationMode ?? 'default') === COORDINATION_ORCHESTRATION_MODE
549
- ? {}
550
- : { orchestrationMode: COORDINATION_ORCHESTRATION_MODE }),
551
- }, record.revision);
585
+ }, configured.revision);
552
586
  }
553
587
  catch {
554
- return failure('persistence_failed', 'WorkHub Coordination Session workspace could not be relocated');
588
+ return failure('persistence_failed', 'WorkHub Coordination Session configuration could not be aligned');
555
589
  }
556
590
  return validCoordinationHeader(repaired.header) && repaired.header.cwd === this.#coordinationCwd
557
591
  ? success()
@@ -577,8 +611,9 @@ function validCoordinationIdentityHeader(header) {
577
611
  }
578
612
  function validCoordinationHeader(header) {
579
613
  return (validCoordinationIdentityHeader(header) &&
580
- header.toolProfile === COORDINATION_TOOL_PROFILE &&
581
- header.permissionMode === COORDINATION_PERMISSION_MODE &&
614
+ ((header.toolProfile === COORDINATION_TOOL_PROFILE &&
615
+ header.permissionMode === COORDINATION_PERMISSION_MODE) ||
616
+ (header.toolProfile === 'workhub-coordination-v1' && header.permissionMode === 'explore')) &&
582
617
  (header.collaborationMode ?? 'agent') === COORDINATION_COLLABORATION_MODE &&
583
618
  (header.orchestrationMode ?? 'default') === COORDINATION_ORCHESTRATION_MODE);
584
619
  }
@@ -594,48 +629,6 @@ function coordinationSummaryMessageId(turnId, kind) {
594
629
  .digest('hex')
595
630
  .slice(0, 48)}`;
596
631
  }
597
- function coordinationSummaryMessages(input) {
598
- const ts = Date.now();
599
- const messageId = (kind) => coordinationSummaryMessageId(input.turnId, kind);
600
- return [
601
- {
602
- type: 'user',
603
- id: messageId('user'),
604
- turnId: input.turnId,
605
- ts,
606
- text: input.userText,
607
- },
608
- {
609
- type: 'assistant',
610
- id: messageId('assistant'),
611
- turnId: input.turnId,
612
- ts: ts + 1,
613
- text: input.assistantText,
614
- modelId: SYNTHETIC_COORDINATION_MODEL_ID,
615
- },
616
- {
617
- type: 'turn_state',
618
- id: messageId('state'),
619
- turnId: input.turnId,
620
- ts: ts + 2,
621
- status: 'completed',
622
- },
623
- ];
624
- }
625
- function coordinationSummaryMatches(existing, input) {
626
- if (existing.length !== 3)
627
- return false;
628
- const user = existing.find((message) => message.type === 'user');
629
- const assistant = existing.find((message) => message.type === 'assistant');
630
- const state = existing.find((message) => message.type === 'turn_state');
631
- return (user?.turnId === input.turnId &&
632
- user.text === input.userText &&
633
- assistant?.turnId === input.turnId &&
634
- assistant.text === input.assistantText &&
635
- assistant.modelId === SYNTHETIC_COORDINATION_MODEL_ID &&
636
- state?.turnId === input.turnId &&
637
- state.status === 'completed');
638
- }
639
632
  function success() {
640
633
  return {
641
634
  ok: true,
@@ -658,6 +651,6 @@ function turnIdentityConflict() {
658
651
  function operationUnavailable(message) {
659
652
  return { ok: false, error: { code: 'operation_unavailable', message } };
660
653
  }
661
- function turnFailure(_operation, code, message) {
654
+ function turnFailure(code, message) {
662
655
  return { ok: false, error: { code, message } };
663
656
  }
@@ -0,0 +1,46 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { WORKHUB_COORDINATION_SESSION_ID } from '@maka/core/session';
20
+ import { WorkHubActionEffectFailure } from './workhub-coordination-action-gate.js';
21
+ /** Preserve canonical ownership when a Coordination message is delegated. */
22
+ export async function copyWorkHubAttachmentsToTarget(store, artifacts, targetSessionId, attachments) {
23
+ const invalid = await artifacts.validateTurnAttachments(WORKHUB_COORDINATION_SESSION_ID, attachments);
24
+ if (invalid)
25
+ throw new WorkHubActionEffectFailure('operation_conflict', invalid);
26
+ const ids = attachments.map((attachment) => {
27
+ if (attachment.ref.kind !== 'session_file')
28
+ throw new Error('Invalid WorkHub attachment');
29
+ return attachment.ref.relativePath;
30
+ });
31
+ const copied = await store.copyConversationArtifacts({
32
+ sourceSessionId: WORKHUB_COORDINATION_SESSION_ID,
33
+ targetSessionId,
34
+ turnIds: [],
35
+ includeArtifactIds: ids,
36
+ });
37
+ return attachments.map((attachment, index) => {
38
+ const relativePath = copied.artifactIds.get(ids[index]);
39
+ if (!relativePath)
40
+ throw new WorkHubActionEffectFailure('operation_conflict', 'WorkHub attachment was not copied');
41
+ return {
42
+ ...attachment,
43
+ ref: { kind: 'session_file', sessionId: targetSessionId, relativePath },
44
+ };
45
+ });
46
+ }
@@ -1347,12 +1347,25 @@ function normalizeRootExecutionDescriptor(value) {
1347
1347
  });
1348
1348
  }
1349
1349
  if (value.kind === 'workhub_coordination') {
1350
- if (!hasExactKeys(value, ['kind', 'inputDigest']) || !isSha256Digest(value.inputDigest)) {
1350
+ if (!hasExactKeys(value, value.operation === undefined
1351
+ ? ['kind', 'inputDigest']
1352
+ : [
1353
+ 'kind',
1354
+ 'inputDigest',
1355
+ 'operation',
1356
+ ...(value.actionId === undefined ? [] : ['actionId']),
1357
+ ]) ||
1358
+ (value.operation !== undefined && value.operation !== 'action') ||
1359
+ (value.actionId !== undefined &&
1360
+ (typeof value.actionId !== 'string' || !isSafeId(value.actionId))) ||
1361
+ !isSha256Digest(value.inputDigest)) {
1351
1362
  throw new Error('Invalid root execution descriptor');
1352
1363
  }
1353
1364
  return Object.freeze({
1354
1365
  kind: 'workhub_coordination',
1366
+ ...(value.operation === 'action' ? { operation: 'action' } : {}),
1355
1367
  inputDigest: value.inputDigest,
1368
+ ...(typeof value.actionId === 'string' ? { actionId: value.actionId } : {}),
1356
1369
  });
1357
1370
  }
1358
1371
  if (value.kind === 'regenerate') {
@@ -252,6 +252,7 @@ class SqliteArtifactStore {
252
252
  const directories = new Set();
253
253
  const discharged = [];
254
254
  let failedPaths = 0;
255
+ let realArtifactRoot;
255
256
  try {
256
257
  for (const relativePath of selected) {
257
258
  if (this.metadataRepository.hasRelativePath(relativePath) ||
@@ -259,10 +260,28 @@ class SqliteArtifactStore {
259
260
  discharged.push(relativePath);
260
261
  continue;
261
262
  }
262
- const target = join(this.artifactRoot, relativePath);
263
+ const entry = await resolveArtifactRemovalEntry(this.artifactRoot, relativePath);
264
+ if (!entry) {
265
+ discharged.push(relativePath);
266
+ continue;
267
+ }
268
+ realArtifactRoot ??= await ensureRealDirectory(this.artifactRoot);
269
+ if (!isInsideOrSamePath(realArtifactRoot, dirname(entry.unlinkPath))) {
270
+ failedPaths += 1;
271
+ continue;
272
+ }
273
+ const artifactIds = new Set([
274
+ ...artifactIdsFromUpgradeOrphanPath(relativePath),
275
+ ...artifactIdsFromUpgradeOrphanPath(entry.unlinkPath),
276
+ ]);
277
+ if (artifactIds.size > 0 &&
278
+ (await this.hasClaimedRemovalIdentityUnlocked([...artifactIds], entry.comparisonIdentity))) {
279
+ discharged.push(relativePath);
280
+ continue;
281
+ }
263
282
  try {
264
- await unlink(target);
265
- directories.add(dirname(target));
283
+ await unlink(entry.unlinkPath);
284
+ directories.add(dirname(entry.unlinkPath));
266
285
  }
267
286
  catch (error) {
268
287
  if (!isNotFound(error)) {
@@ -286,6 +305,14 @@ class SqliteArtifactStore {
286
305
  };
287
306
  });
288
307
  }
308
+ async hasClaimedRemovalIdentityUnlocked(artifactIds, comparisonIdentity) {
309
+ for (const relativePath of this.metadataRepository.readRelativePathsByCaseFoldedArtifactIds(artifactIds)) {
310
+ const entry = await resolveArtifactRemovalEntry(this.artifactRoot, relativePath);
311
+ if (entry?.comparisonIdentity === comparisonIdentity)
312
+ return true;
313
+ }
314
+ return false;
315
+ }
289
316
  async replayExistingArtifactUnlocked(existing, input, canonical) {
290
317
  const expectedBytes = Buffer.from(input.content);
291
318
  if (existing.id !== canonical.id ||
@@ -835,6 +862,16 @@ function symlinkEntryIdentity(entryStat) {
835
862
  entryStat.mtimeNs,
836
863
  ].join(':');
837
864
  }
865
+ function artifactIdsFromUpgradeOrphanPath(relativePath) {
866
+ const name = basename(relativePath);
867
+ const artifactIds = [];
868
+ for (let separator = name.indexOf('-'); separator > 0; separator = name.indexOf('-', separator + 1)) {
869
+ const artifactId = name.slice(0, separator);
870
+ if (isCanonicalArtifactEntityId(artifactId))
871
+ artifactIds.push(artifactId);
872
+ }
873
+ return artifactIds;
874
+ }
838
875
  function isInsideOrSamePath(root, target) {
839
876
  if (target === root)
840
877
  return true;
@@ -0,0 +1,74 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+ import { randomUUID } from 'node:crypto';
20
+ import { open, rename, rm } from 'node:fs/promises';
21
+ import { basename, dirname, join } from 'node:path';
22
+ import { syncDirectory } from './stable-storage.js';
23
+ const defaultDependencies = {
24
+ open,
25
+ randomUUID,
26
+ syncDirectory,
27
+ };
28
+ export class AtomicFileWriteCommitUnknownError extends Error {
29
+ published = true;
30
+ constructor(options) {
31
+ super('Atomic file commit outcome is unknown; reload before retrying', options);
32
+ this.name = 'AtomicFileWriteCommitUnknownError';
33
+ }
34
+ }
35
+ export async function writeAtomicFile(path, contents, options, dependencies = {}) {
36
+ const deps = { ...defaultDependencies, ...dependencies };
37
+ const { fileMode } = options;
38
+ const tempPath = join(dirname(path), `.${basename(path)}.${deps.randomUUID()}.tmp`);
39
+ // Only the entry this call created — and hasn't renamed away — may be
40
+ // cleaned up. A pre-existing file or planted symlink the 'wx' open refused
41
+ // must not be deleted as "our" temp.
42
+ let tempCreated = false;
43
+ let published = false;
44
+ try {
45
+ const handle = await deps.open(tempPath, 'wx', fileMode);
46
+ tempCreated = true;
47
+ try {
48
+ await handle.writeFile(contents, 'utf8');
49
+ if (process.platform !== 'win32')
50
+ await handle.chmod(fileMode);
51
+ await handle.sync();
52
+ await handle.close();
53
+ }
54
+ catch (error) {
55
+ // Release the descriptor best-effort; never let a close failure mask
56
+ // the error that actually aborted the write.
57
+ await handle.close().catch(() => { });
58
+ throw error;
59
+ }
60
+ await rename(tempPath, path);
61
+ published = true;
62
+ tempCreated = false;
63
+ await deps.syncDirectory(dirname(path));
64
+ }
65
+ catch (error) {
66
+ if (tempCreated) {
67
+ // Cleanup must never mask the original failure.
68
+ await rm(tempPath, { force: true }).catch(() => { });
69
+ }
70
+ if (published)
71
+ throw new AtomicFileWriteCommitUnknownError({ cause: error });
72
+ throw error;
73
+ }
74
+ }