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
@@ -154,7 +154,9 @@ function decodeIdentity(value) {
154
154
  }
155
155
  function decodeOAuthProvider(value) {
156
156
  const providerType = decodePersistedDomain(() => decodeProviderType(value));
157
- if (providerType !== 'openai-codex' && providerType !== 'xai-oauth') {
157
+ if (providerType !== 'openai-codex' &&
158
+ providerType !== 'xai-oauth' &&
159
+ providerType !== 'github-copilot') {
158
160
  throw codecError('invalid_document', 'OAuth login receipt provider is invalid');
159
161
  }
160
162
  return providerType;
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { unlink } from 'node:fs/promises';
20
20
  import { join } from 'node:path';
21
- import { decodeProviderType, decodeCanonicalConnectionCatalogEntry, decodeCredentialVersionBasis, decodeConnectionSlug, decodeRuntimePolicyEntityId, normalizeCatalogConnectionBaseUrl, normalizeConnectionCatalogEntryUpdateForProvider, normalizeConnectionModelDiscoveryResult, normalizeCredentialSecret, } from '@maka/core/runtime-policy';
21
+ import { decodeProviderType, decodeCanonicalConnectionCatalogEntry, decodeCredentialVersionBasis, decodeConnectionName, decodeConnectionSlug, decodeRuntimePolicyEntityId, normalizeCatalogConnectionBaseUrl, normalizeConnectionCatalogEntryUpdateForProvider, normalizeConnectionModelDiscoveryResult, normalizeCredentialSecret, } from '@maka/core/runtime-policy';
22
22
  import { deriveConnectionSlug, PROVIDER_REGISTRY, providerAuthSupportsApiKey, } from '@maka/core/llm-connections';
23
23
  import { syncDirectory } from '../stable-storage.js';
24
24
  import { record } from './codec.js';
@@ -31,10 +31,10 @@ const MAX_BYTES = 5 * 1024 * 1024;
31
31
  export function prepareConnectionOnboardingIntent(input, source = 'input') {
32
32
  const decode = source === 'persisted' ? decodePersistedDomain : decodeConnectionInput;
33
33
  const providerType = decode(() => decodeProviderType(input.providerType));
34
- if (!providerAuthSupportsApiKey(providerType)) {
35
- throw codecError(source === 'persisted' ? 'invalid_document' : 'invalid_connection_input', 'Onboarding requires an API-key provider');
36
- }
37
34
  const definition = PROVIDER_REGISTRY[providerType];
35
+ if (!providerAuthSupportsApiKey(providerType) && definition.authKind !== 'oauth_token') {
36
+ throw codecError(source === 'persisted' ? 'invalid_document' : 'invalid_connection_input', 'Onboarding requires a provider with a connection credential');
37
+ }
38
38
  const discovery = decode(() => normalizeConnectionModelDiscoveryResult(input.discovery));
39
39
  // Non-empty is the requirement; `source` is write provenance, not a
40
40
  // quality bar. A provider without a model-list endpoint runs discovery by
@@ -72,6 +72,9 @@ export function prepareConnectionOnboardingIntent(input, source = 'input') {
72
72
  connectionId: decode(() => decodeRuntimePolicyEntityId(input.connectionId)),
73
73
  slug: decode(() => decodeConnectionSlug(input.slug)),
74
74
  providerType,
75
+ name: input.name === undefined || input.name === null
76
+ ? null
77
+ : decode(() => decodeConnectionName(input.name)),
75
78
  suppliedSecret,
76
79
  baseUrl,
77
80
  enabledModelIds: normalized.enabledModelIds,
@@ -95,6 +98,7 @@ export async function readConnectionOnboardingIntent(root) {
95
98
  'connectionId',
96
99
  'slug',
97
100
  'providerType',
101
+ 'name',
98
102
  'suppliedSecret',
99
103
  'baseUrl',
100
104
  'enabledModelIds',
@@ -110,6 +114,7 @@ export async function readConnectionOnboardingIntent(root) {
110
114
  'connectionId',
111
115
  'slug',
112
116
  'providerType',
117
+ 'name',
113
118
  'suppliedSecret',
114
119
  'baseUrl',
115
120
  'enabledModelIds',
@@ -131,6 +136,7 @@ export async function readConnectionOnboardingIntent(root) {
131
136
  providerType: raw.providerType,
132
137
  connectionId: raw.connectionId,
133
138
  slug: raw.schemaVersion === 1 ? deriveLegacyIntentPlaceholderSlug(raw.providerType) : raw.slug,
139
+ name: raw.name,
134
140
  suppliedSecret: raw.suppliedSecret,
135
141
  baseUrl: raw.baseUrl,
136
142
  enabledModelIds: raw.enabledModelIds,
@@ -231,7 +237,9 @@ function decodeOAuthTarget(value) {
231
237
  throw codecError('invalid_document', 'OAuth enrollment target kind is invalid');
232
238
  }
233
239
  function isOAuthProvider(providerType) {
234
- return providerType === 'openai-codex' || providerType === 'xai-oauth';
240
+ return (providerType === 'openai-codex' ||
241
+ providerType === 'xai-oauth' ||
242
+ providerType === 'github-copilot');
235
243
  }
236
244
  function decodeOAuthAttemptId(value, source) {
237
245
  if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value)) {
@@ -261,7 +261,7 @@ class SqliteSessionCopyCleanupStore {
261
261
  creation: {
262
262
  kind: creation.kind,
263
263
  sourceSessionId: creation.sourceSessionId,
264
- sourceTurnId: creation.sourceTurnId,
264
+ ...(creation.sourceTurnId === undefined ? {} : { sourceTurnId: creation.sourceTurnId }),
265
265
  ...(creation.intent ? { intent: creation.intent } : {}),
266
266
  },
267
267
  };
@@ -365,7 +365,9 @@ function normalizeCreationLease(creation) {
365
365
  sessionId: normalizeSessionId(creation.sessionId),
366
366
  kind: creation.kind,
367
367
  sourceSessionId: normalizeSessionId(creation.sourceSessionId),
368
- sourceTurnId: normalizeSessionId(creation.sourceTurnId),
368
+ ...(creation.sourceTurnId === undefined
369
+ ? {}
370
+ : { sourceTurnId: normalizeSessionId(creation.sourceTurnId) }),
369
371
  ...(creation.intent === 'side_conversation' ? { intent: creation.intent } : {}),
370
372
  ownerId: normalizeOwnerId(creation.ownerId),
371
373
  };
@@ -221,6 +221,26 @@ class SqliteSessionStore {
221
221
  ? message
222
222
  : undefined;
223
223
  }
224
+ async readWorkHubStopRequest(delegationId) {
225
+ const message = await this.readWorkHubCoordinationMessage(`whq_${workHubIdentitySuffix(delegationId)}`);
226
+ return message?.type === 'workhub_coordination' && message.kind === 'delegation_stop_requested'
227
+ ? message
228
+ : undefined;
229
+ }
230
+ async readWorkHubStopResolution(delegationId) {
231
+ const message = await this.readWorkHubCoordinationMessage(`whz_${workHubIdentitySuffix(delegationId)}`);
232
+ return message?.type === 'workhub_coordination' && message.kind === 'delegation_stop_resolved'
233
+ ? message
234
+ : undefined;
235
+ }
236
+ async claimWorkHubAction(claim) {
237
+ await this.ensureReady();
238
+ return this.metadata.claimWorkHubAction(claim);
239
+ }
240
+ async readWorkHubActionClaim(actionId) {
241
+ await this.ensureReady();
242
+ return this.metadata.readWorkHubActionClaim(actionId);
243
+ }
224
244
  async readWorkHubCoordinationMessage(messageId) {
225
245
  await this.ensureReady();
226
246
  const throughSequence = await this.metadata.readTranscriptHighWater(WORKHUB_COORDINATION_SESSION_ID);
@@ -443,6 +463,10 @@ class SqliteSessionStore {
443
463
  await this.ensureReady();
444
464
  return this.metadata.hasCancelledMessageAdmission(sessionId, messageId);
445
465
  }
466
+ async claimMessageAdmissionCancellation(sessionId, messageId, claimId) {
467
+ await this.ensureReady();
468
+ return this.metadata.claimMessageAdmissionCancellation(sessionId, messageId, claimId);
469
+ }
446
470
  async listMessageAdmissions(sessionId) {
447
471
  await this.ensureReady();
448
472
  return this.metadata.listMessageAdmissions(sessionId);
@@ -784,15 +808,24 @@ function isValidConversationCopyLineage(header) {
784
808
  return false;
785
809
  }
786
810
  if (copy.kind === 'branch') {
787
- return (header.parentSessionId === copy.sourceSessionId &&
788
- header.branchOfTurnId === copy.sourceTurnId &&
789
- header.revisionRootSessionId === undefined &&
811
+ const revisionClear = header.revisionRootSessionId === undefined &&
790
812
  header.revisionParentSessionId === undefined &&
791
813
  header.revisionOfTurnId === undefined &&
792
814
  header.revisionIndex === undefined &&
793
- header.revisionState === undefined);
794
- }
795
- return (header.revisionParentSessionId === copy.sourceSessionId &&
815
+ header.revisionState === undefined;
816
+ if (!revisionClear || header.parentSessionId !== copy.sourceSessionId) {
817
+ return false;
818
+ }
819
+ // An empty copy (absent `sourceTurnId`) records provenance
820
+ // (`parentSessionId`) but must not fabricate a `branchOfTurnId`, and is only
821
+ // valid for a side conversation; a through-turn copy must anchor to it.
822
+ return copy.sourceTurnId === undefined
823
+ ? header.branchOfTurnId === undefined && copy.intent === 'side_conversation'
824
+ : header.branchOfTurnId === copy.sourceTurnId;
825
+ }
826
+ // Revision copies always carry a turn boundary (enforced at decode).
827
+ return (copy.sourceTurnId !== undefined &&
828
+ header.revisionParentSessionId === copy.sourceSessionId &&
796
829
  header.revisionOfTurnId === copy.sourceTurnId);
797
830
  }
798
831
  function isValidSubagentSessionLineage(header) {
@@ -216,19 +216,17 @@ export class SqliteRuntimeStore {
216
216
  }
217
217
  async importConversationCopyRuntimeEvents(sessionId, batches) {
218
218
  assertRuntimeStorageSafeId(sessionId, 'Invalid session id');
219
- const runIds = new Set();
220
219
  const canonicalBatches = batches.map(({ runId, events }) => {
221
220
  assertRuntimeStorageSafeId(runId, 'Invalid run id');
222
- if (runIds.has(runId)) {
223
- throw new Error(`Conversation copy contains duplicate run ${runId}`);
224
- }
225
- runIds.add(runId);
226
221
  return {
227
222
  runId,
228
223
  events: events.map(canonicalizeRuntimeEventForStorage),
229
224
  };
230
225
  });
231
226
  const canonicalEvents = canonicalBatches.flatMap(({ events }) => events);
227
+ if (new Set(canonicalEvents.map(({ id }) => id)).size !== canonicalEvents.length) {
228
+ throw new Error('Conversation copy contains duplicate RuntimeEvents');
229
+ }
232
230
  for (const { runId, events } of canonicalBatches) {
233
231
  for (const event of events) {
234
232
  assertNoReservedWorkspaceAuthorityAppend(event);
@@ -245,7 +243,12 @@ export class SqliteRuntimeStore {
245
243
  throw new Error(`Conversation copy RuntimeEvent ledger is corrupt: ${scan.issues[0]?.code ?? 'unknown'}`);
246
244
  }
247
245
  this.transaction(() => {
246
+ const eventsByRun = new Map();
248
247
  for (const { runId, events } of canonicalBatches) {
248
+ eventsByRun.set(runId, [...(eventsByRun.get(runId) ?? []), ...events]);
249
+ }
250
+ const newRunIds = new Set();
251
+ for (const [runId, events] of eventsByRun) {
249
252
  const existing = this.db
250
253
  .prepare(`
251
254
  SELECT event_id, session_id, invocation_id, run_id, turn_id, payload_json
@@ -257,10 +260,14 @@ export class SqliteRuntimeStore {
257
260
  if (existing.length > 0 && !isDeepStrictEqual(existing, events)) {
258
261
  throw new Error(`Conversation copy RuntimeEvent identity conflict for run ${runId}`);
259
262
  }
260
- if (existing.length === 0) {
261
- for (const event of events)
262
- this.insertRuntimeEvent(event, event.ts, true);
263
- }
263
+ if (existing.length === 0)
264
+ newRunIds.add(runId);
265
+ }
266
+ for (const { runId, events } of canonicalBatches) {
267
+ if (!newRunIds.has(runId))
268
+ continue;
269
+ for (const event of events)
270
+ this.insertRuntimeEvent(event, event.ts, true);
264
271
  }
265
272
  if (canonicalEvents.some(isToolLedgerBearingEvent)) {
266
273
  this.rebuildToolProjectionsFromRuntimeEventsSync(sessionId);
@@ -16,7 +16,7 @@
16
16
  * specific language governing permissions and limitations
17
17
  * under the License.
18
18
  */
19
- export const SQLITE_SESSION_METADATA_SCHEMA_VERSION = 36;
19
+ export const SQLITE_SESSION_METADATA_SCHEMA_VERSION = 38;
20
20
  export const SQLITE_SESSION_MESSAGE_CHUNK_BYTES = 64 * 1024;
21
21
  export const SQLITE_SESSION_MESSAGE_CHUNK_MARKER = '{"$maka":"session-message-chunks-v1"}';
22
22
  export const SQLITE_AGENT_GRAPH_CONTROL_TABLES = [
@@ -1237,6 +1237,32 @@ const MIGRATIONS = new Map([
1237
1237
  SELECT 1;
1238
1238
  `,
1239
1239
  ],
1240
+ [
1241
+ 37,
1242
+ `
1243
+ ALTER TABLE cancelled_message_admissions
1244
+ ADD COLUMN cancellation_claim_id TEXT;
1245
+ `,
1246
+ ],
1247
+ [
1248
+ 38,
1249
+ `
1250
+ -- The one global owner of a WorkHub action identity. It deliberately has no
1251
+ -- Session foreign key: the claim must outlive removal of the target Session
1252
+ -- so a committed destructive claim still converges after that removal.
1253
+ CREATE TABLE IF NOT EXISTS workhub_action_claims (
1254
+ action_id TEXT PRIMARY KEY,
1255
+ operation TEXT NOT NULL CHECK (
1256
+ operation IN (
1257
+ 'answer_here', 'clarify', 'delegate_existing', 'create_new', 'replace', 'stop'
1258
+ )
1259
+ ),
1260
+ action_fingerprint TEXT NOT NULL,
1261
+ subject TEXT NOT NULL,
1262
+ claimed_at INTEGER NOT NULL CHECK (claimed_at >= 0)
1263
+ );
1264
+ `,
1265
+ ],
1240
1266
  ]);
1241
1267
  if (MIGRATIONS.size !== SQLITE_SESSION_METADATA_SCHEMA_VERSION) {
1242
1268
  throw new Error('SQLite session metadata migrations contain a duplicate or missing version');
@@ -1282,12 +1308,13 @@ export function migrateSqliteSessionMetadataDatabase(db, options = {}) {
1282
1308
  const sql = MIGRATIONS.get(version);
1283
1309
  if (!sql)
1284
1310
  throw new Error(`Missing SQLite session metadata migration ${version}`);
1285
- // Versions 32 and 35 each add one column, and the post-merge convergence
1311
+ // Versions 32, 35, and 37 each add one column, and the post-merge convergence
1286
1312
  // path can replay them onto a database that already carries the current
1287
1313
  // table shape. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the guards
1288
1314
  // live here.
1289
1315
  const columnAlreadyPresent = (version === 32 && hasColumn(db, 'message_admissions', 'submitted_intent_json')) ||
1290
- (version === 35 && hasColumn(db, 'message_admissions', 'skill_invocation_json'));
1316
+ (version === 35 && hasColumn(db, 'message_admissions', 'skill_invocation_json')) ||
1317
+ (version === 37 && hasColumn(db, 'cancelled_message_admissions', 'cancellation_claim_id'));
1291
1318
  if (!columnAlreadyPresent) {
1292
1319
  db.exec(sql);
1293
1320
  }
@@ -1250,6 +1250,15 @@ export class SqliteSessionMetadataStore {
1250
1250
  .update(assignment.replacesDelegationId)
1251
1251
  .digest('hex')
1252
1252
  .slice(0, 48);
1253
+ const stopRequest = this.readMessageByIdSync(WORKHUB_COORDINATION_SESSION_ID, `whq_${abortSuffix}`);
1254
+ if (stopRequest) {
1255
+ const stopResolution = this.readMessageByIdSync(WORKHUB_COORDINATION_SESSION_ID, `whz_${abortSuffix}`);
1256
+ if (stopResolution?.type !== 'workhub_coordination' ||
1257
+ stopResolution.kind !== 'delegation_stop_resolved' ||
1258
+ stopResolution.outcome !== 'not_owned') {
1259
+ throw new SessionMetadataConflictError('WorkHub delegation already has a stop claim');
1260
+ }
1261
+ }
1253
1262
  const existingAbort = this.readMessageByIdSync(WORKHUB_COORDINATION_SESSION_ID, `whb_${abortSuffix}`);
1254
1263
  if (existingAbort) {
1255
1264
  throw new SessionMetadataConflictError('WorkHub delegation replacement is aborted');
@@ -1348,6 +1357,106 @@ export class SqliteSessionMetadataStore {
1348
1357
  return row !== undefined;
1349
1358
  });
1350
1359
  }
1360
+ /**
1361
+ * Binds one WorkHub action identity to one exact operation, for good.
1362
+ *
1363
+ * Every other durable WorkHub record is keyed by what it is about, so none of
1364
+ * them can see an action id that moved to a second delegation or a second
1365
+ * disposition. This row is the global owner that rejects both, and it is
1366
+ * written before the action's effect so a rejected or recovering attempt can
1367
+ * never leak its identity into a different operation.
1368
+ */
1369
+ async claimWorkHubAction(claim) {
1370
+ this.assertOpen();
1371
+ assertSafeSessionId(claim.actionId);
1372
+ assertSafeSessionId(claim.subject);
1373
+ if (!/^sha256:[a-f0-9]{64}$/u.test(claim.actionFingerprint)) {
1374
+ throw new SessionMetadataConflictError('Invalid WorkHub action fingerprint');
1375
+ }
1376
+ return this.transaction(() => {
1377
+ const existing = this.readWorkHubActionClaimSync(claim.actionId);
1378
+ if (existing) {
1379
+ return existing.operation === claim.operation &&
1380
+ existing.actionFingerprint === claim.actionFingerprint &&
1381
+ existing.subject === claim.subject
1382
+ ? 'same_claim'
1383
+ : 'conflict';
1384
+ }
1385
+ this.db
1386
+ .prepare(`
1387
+ INSERT INTO workhub_action_claims(
1388
+ action_id, operation, action_fingerprint, subject, claimed_at
1389
+ ) VALUES (?, ?, ?, ?, ?)
1390
+ `)
1391
+ .run(claim.actionId, claim.operation, claim.actionFingerprint, claim.subject, this.now());
1392
+ return 'claimed';
1393
+ });
1394
+ }
1395
+ async readWorkHubActionClaim(actionId) {
1396
+ this.assertOpen();
1397
+ assertSafeSessionId(actionId);
1398
+ return this.readTransaction(() => this.readWorkHubActionClaimSync(actionId));
1399
+ }
1400
+ readWorkHubActionClaimSync(actionId) {
1401
+ const row = this.db
1402
+ .prepare('SELECT operation, action_fingerprint, subject FROM workhub_action_claims WHERE action_id = ?')
1403
+ .get(actionId);
1404
+ if (!row)
1405
+ return undefined;
1406
+ if (!isWorkHubActionOperation(row.operation) ||
1407
+ typeof row.action_fingerprint !== 'string' ||
1408
+ !/^sha256:[a-f0-9]{64}$/u.test(row.action_fingerprint) ||
1409
+ typeof row.subject !== 'string') {
1410
+ throw new SessionMetadataConflictError('Invalid WorkHub action claim row');
1411
+ }
1412
+ return {
1413
+ actionId,
1414
+ operation: row.operation,
1415
+ actionFingerprint: row.action_fingerprint,
1416
+ subject: row.subject,
1417
+ };
1418
+ }
1419
+ async claimMessageAdmissionCancellation(sessionId, messageId, claimId) {
1420
+ this.assertOpen();
1421
+ assertSafeSessionId(sessionId);
1422
+ assertSafeSessionId(messageId);
1423
+ assertSafeSessionId(claimId);
1424
+ return this.transaction(() => {
1425
+ const cancelled = this.db
1426
+ .prepare('SELECT cancellation_claim_id FROM cancelled_message_admissions WHERE session_id = ? AND message_id = ?')
1427
+ .get(sessionId, messageId);
1428
+ if (cancelled) {
1429
+ return cancelled.cancellation_claim_id === claimId ? 'same_claim' : 'already_cancelled';
1430
+ }
1431
+ const admission = this.db
1432
+ .prepare(`
1433
+ SELECT submitted_content_digest, submitted_placement
1434
+ FROM message_admissions
1435
+ WHERE session_id = ? AND message_id = ?
1436
+ `)
1437
+ .get(sessionId, messageId);
1438
+ if (typeof admission?.submitted_content_digest !== 'string' ||
1439
+ (admission.submitted_placement !== 'current_turn' &&
1440
+ admission.submitted_placement !== 'next_turn')) {
1441
+ throw new SessionMetadataConflictError('Message admission cancellation identity conflict');
1442
+ }
1443
+ this.db
1444
+ .prepare(`
1445
+ INSERT INTO cancelled_message_admissions(
1446
+ session_id, message_id, submitted_content_digest, submitted_placement,
1447
+ cancellation_claim_id
1448
+ ) VALUES (?, ?, ?, ?, ?)
1449
+ `)
1450
+ .run(sessionId, messageId, admission.submitted_content_digest, admission.submitted_placement, claimId);
1451
+ const deleted = this.db
1452
+ .prepare('DELETE FROM message_admissions WHERE session_id = ? AND message_id = ?')
1453
+ .run(sessionId, messageId);
1454
+ if (deleted.changes !== 1) {
1455
+ throw new SessionMetadataConflictError('Message admission cancellation identity conflict');
1456
+ }
1457
+ return 'cancelled_by_claim';
1458
+ });
1459
+ }
1351
1460
  async listMessageAdmissions(sessionId) {
1352
1461
  this.assertOpen();
1353
1462
  assertSafeSessionId(sessionId);
@@ -4938,6 +5047,14 @@ function readStoredMessageRecordJson(db, sessionId, sequence, row) {
4938
5047
  }
4939
5048
  return recordJson;
4940
5049
  }
5050
+ function isWorkHubActionOperation(value) {
5051
+ return (value === 'answer_here' ||
5052
+ value === 'clarify' ||
5053
+ value === 'delegate_existing' ||
5054
+ value === 'create_new' ||
5055
+ value === 'replace' ||
5056
+ value === 'stop');
5057
+ }
4941
5058
  function sameWorkHubAssignmentRequest(existing, requested) {
4942
5059
  return isDeepStrictEqual({
4943
5060
  actionId: existing.actionId,
@@ -117,6 +117,7 @@ class SqliteTelemetryRepo {
117
117
  range: { from, to },
118
118
  totalRequests: rows.length,
119
119
  totalCostUsd: sum(rows.map((row) => row.costUsd)),
120
+ totalDurationMs: sum(rows.map((row) => row.latencyMs)),
120
121
  totalTokens: {
121
122
  input: sum(rows.map((row) => row.inputTokens)),
122
123
  output: sum(rows.map((row) => row.outputTokens)),
@@ -131,6 +132,15 @@ class SqliteTelemetryRepo {
131
132
  errorRequests: rows.filter((row) => row.status === 'error').length,
132
133
  });
133
134
  }
135
+ toolSummary(query) {
136
+ this.assertReady();
137
+ const { from, to } = resolveRange(query.range);
138
+ const rows = this.filteredToolRows(query, from, to);
139
+ return detached({
140
+ requests: rows.length,
141
+ durationMs: sum(rows.map((row) => row.durationMs)),
142
+ });
143
+ }
134
144
  buckets(query, groupBy) {
135
145
  this.assertReady();
136
146
  const { from, to } = resolveRange(query.range);
@@ -226,13 +236,22 @@ class SqliteTelemetryRepo {
226
236
  });
227
237
  }
228
238
  filteredToolRows(query, from, to) {
229
- return this.readToolRows().filter((row) => {
230
- if (row.ts < from || row.ts > to)
231
- return false;
239
+ // One filter policy for every tool read — summary, buckets, and logs — so
240
+ // two views of one query cannot disagree. Fields the narrower query types
241
+ // do not carry simply never match a row out of range.
242
+ return this.readToolRows(from, to).filter((row) => {
232
243
  if (query.toolName && row.toolName !== query.toolName)
233
244
  return false;
234
245
  if (query.status && query.status !== 'all' && row.status !== query.status)
235
246
  return false;
247
+ if ('sessionId' in query && query.sessionId && row.sessionId !== query.sessionId) {
248
+ return false;
249
+ }
250
+ if ('providerId' in query && query.providerId && row.providerId !== query.providerId) {
251
+ return false;
252
+ }
253
+ if ('modelId' in query && query.modelId && row.modelId !== query.modelId)
254
+ return false;
236
255
  return true;
237
256
  });
238
257
  }
@@ -245,10 +264,14 @@ class SqliteTelemetryRepo {
245
264
  WHERE ts >= ? AND ts <= ?`)
246
265
  .all(...(sessionId ? [sessionId, from, to] : [from, to])).map((row) => decodePersistedLlmCallRecord(JSON.parse(row.record_json)));
247
266
  }
248
- readToolRows() {
267
+ // The ts range goes into the query, not the decode: the invocation ledger
268
+ // has no retention, and a full-table decode per summary read would only grow
269
+ // with it. The `(ts, id)` index answers the range; the remaining filters run
270
+ // over the few decoded rows it returns.
271
+ readToolRows(from, to) {
249
272
  return this.#lease.database
250
- .prepare('SELECT record_json FROM usage_tool_invocations')
251
- .all().map((row) => decodePersistedToolInvocationRecord(JSON.parse(row.record_json)));
273
+ .prepare('SELECT record_json FROM usage_tool_invocations WHERE ts >= ? AND ts <= ?')
274
+ .all(from, to).map((row) => decodePersistedToolInvocationRecord(JSON.parse(row.record_json)));
252
275
  }
253
276
  enqueueMutation(operation) {
254
277
  this.assertReady();
@@ -282,12 +282,13 @@ function createWriterFacade(lease, telemetry, modelCalls, pricing) {
282
282
  [writerBrand]: true,
283
283
  telemetry: {
284
284
  summary: (query) => read(() => telemetry.summary(query)),
285
+ toolSummary: (query) => read(() => telemetry.toolSummary(query)),
285
286
  buckets: (query, groupBy) => read(() => telemetry.buckets(query, groupBy)),
286
287
  logs: (query, offset, limit) => read(() => telemetry.logs(query, offset, limit)),
287
288
  toolLogs: (query, offset, limit) => read(() => telemetry.toolLogs(query, offset, limit)),
288
289
  latestLlmRuntimeProbe: (connectionSlug, modelId) => read(() => telemetry.latestLlmRuntimeProbe(connectionSlug, modelId)),
289
290
  recordLlmCall: (record) => admitSessionUsageMutation(record.sessionId, () => telemetry.insertLlmCall(record)),
290
- recordToolInvocation: (record) => admit(() => run(() => telemetry.insertToolInvocation(record))),
291
+ recordToolInvocation: (record) => admitSessionUsageMutation(record.sessionId, () => telemetry.insertToolInvocation(record)),
291
292
  },
292
293
  modelCalls: {
293
294
  modelCallAttempts: (range, sessionId) => read(() => modelCalls.read(range, sessionId)),
@@ -313,6 +314,7 @@ function createWriterFacade(lease, telemetry, modelCalls, pricing) {
313
314
  function telemetryReader(repo, run) {
314
315
  return Object.freeze({
315
316
  summary: (query) => run(() => repo.summary(query)),
317
+ toolSummary: (query) => run(() => repo.toolSummary(query)),
316
318
  buckets: (query, groupBy) => run(() => repo.buckets(query, groupBy)),
317
319
  logs: (query, offset, limit) => run(() => repo.logs(query, offset, limit)),
318
320
  toolLogs: (query, offset, limit) => run(() => repo.toolLogs(query, offset, limit)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maka-agent",
3
- "version": "0.2.0-dev.14.20260902",
3
+ "version": "0.2.0-dev.16.20260903",
4
4
  "description": "Apache Maka (Incubating) developer snapshot; not an Apache release.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",