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

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 (194) hide show
  1. package/dist/acp/maka-acp-agent.js +2 -1
  2. package/dist/acp/session-configuration.js +143 -0
  3. package/dist/acp/session-registry.js +90 -4
  4. package/dist/cli-core.js +2 -0
  5. package/dist/pi-transcript.js +5 -12
  6. package/dist/pi-tui-runner.js +74 -4
  7. package/dist/runtime-host-cli-context.js +167 -78
  8. package/dist/runtime-host-cli-installation.js +8 -2
  9. package/dist/runtime-host-cli.js +31 -1
  10. package/dist/runtime-host-handoff-surface.js +81 -0
  11. package/dist/runtime-host-installed-update-bootstrap.js +13 -0
  12. package/dist/runtime-host-installed-update-coordinator.js +33 -0
  13. package/dist/runtime-host-lifecycle-transaction.js +9 -1
  14. package/dist/runtime-host-local-handoff.js +37 -4
  15. package/dist/runtime-host-local-managed-activation.js +84 -0
  16. package/dist/runtime-host-local-source-retirement.js +18 -2
  17. package/dist/runtime-host-managed-lifecycle-manager.js +1 -0
  18. package/dist/runtime-host-operator-retirement.js +50 -0
  19. package/dist/runtime-host-service-manager.js +3 -0
  20. package/dist/runtime-host-session-driver.js +4 -25
  21. package/dist/runtime-host-session-update.js +62 -0
  22. package/dist/runtime-host-tui-command.js +38 -73
  23. package/dist/runtime-host-tui-context.js +1 -0
  24. package/dist/runtime-host-tui-owner.js +226 -0
  25. package/dist/tui-copy-catalog.js +140 -3
  26. package/dist/tui-host-owner-copy.js +24 -0
  27. package/native/runtime-host-peer/prebuilds/darwin-arm64/maka_runtime_host_peer.node +0 -0
  28. package/native/runtime-host-peer/prebuilds/linux-arm64/maka_runtime_host_peer.node +0 -0
  29. package/native/runtime-host-peer/prebuilds/linux-x64/maka_runtime_host_peer.node +0 -0
  30. package/native/runtime-host-peer/prebuilds/win32-x64/maka_runtime_host_peer.node +0 -0
  31. package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
  32. package/node_modules/@maka/core/dist/bot-events.js +18 -63
  33. package/node_modules/@maka/core/dist/canonical-runtime-event.js +3 -1
  34. package/node_modules/@maka/core/dist/model-call-attempt.js +0 -82
  35. package/node_modules/@maka/core/dist/model-call-usage-projection.js +0 -186
  36. package/node_modules/@maka/core/dist/model-failure.js +40 -0
  37. package/node_modules/@maka/core/dist/model-metadata.generated.js +4 -4
  38. package/node_modules/@maka/core/dist/model-metadata.js +3 -3
  39. package/node_modules/@maka/core/dist/model-web-search.js +5 -4
  40. package/node_modules/@maka/core/dist/provider-registry.js +45 -4
  41. package/node_modules/@maka/core/dist/record-schema.js +22 -2
  42. package/node_modules/@maka/core/dist/redaction.js +41 -58
  43. package/node_modules/@maka/core/dist/runtime-boundary.js +31 -11
  44. package/node_modules/@maka/core/dist/runtime-event.js +37 -6
  45. package/node_modules/@maka/core/dist/runtime-handoff.js +98 -0
  46. package/node_modules/@maka/core/dist/runtime-invocation.js +1 -1
  47. package/node_modules/@maka/core/dist/runtime-logical-execution.js +112 -0
  48. package/node_modules/@maka/core/dist/session-trace.js +5 -1
  49. package/node_modules/@maka/core/dist/session.js +54 -52
  50. package/node_modules/@maka/core/dist/slash-command-catalog.js +2 -0
  51. package/node_modules/@maka/core/dist/tool-result-archive-evidence.js +20 -0
  52. package/node_modules/@maka/core/dist/usage-ledger-merge.js +7 -9
  53. package/node_modules/@maka/core/dist/usage-record-schema.js +26 -35
  54. package/node_modules/@maka/core/dist/workhub-creation-intent.js +34 -20
  55. package/node_modules/@maka/core/package.json +4 -0
  56. package/node_modules/@maka/eval/harbor/egress-proxy/entrypoint.sh +2 -1
  57. package/node_modules/@maka/mcp/dist/index.js +131 -1
  58. package/node_modules/@maka/runtime/dist/agent-run.js +272 -105
  59. package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +13 -5
  60. package/node_modules/@maka/runtime/dist/ai-sdk-compaction.js +109 -34
  61. package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +155 -134
  62. package/node_modules/@maka/runtime/dist/bots/base-adapter.js +17 -0
  63. package/node_modules/@maka/runtime/dist/bots/bot-test.js +38 -22
  64. package/node_modules/@maka/runtime/dist/bots/dingtalk-bridge.js +7 -5
  65. package/node_modules/@maka/runtime/dist/bots/discord-bridge.js +4 -4
  66. package/node_modules/@maka/runtime/dist/bots/feishu-bridge.js +4 -5
  67. package/node_modules/@maka/runtime/dist/bots/qq-bridge.js +6 -4
  68. package/node_modules/@maka/runtime/dist/bots/slack-bridge.js +3 -4
  69. package/node_modules/@maka/runtime/dist/bots/telegram-bridge.js +4 -5
  70. package/node_modules/@maka/runtime/dist/bots/wechat-bridge.js +17 -19
  71. package/node_modules/@maka/runtime/dist/bots/wecom-bridge.js +5 -6
  72. package/node_modules/@maka/runtime/dist/bots/ws-bridge-base.js +2 -3
  73. package/node_modules/@maka/runtime/dist/context-budget.js +14 -9
  74. package/node_modules/@maka/runtime/dist/conversation-copy.js +5 -4
  75. package/node_modules/@maka/runtime/dist/goal-continuation.js +36 -4
  76. package/node_modules/@maka/runtime/dist/history-compact-checkpoint.js +18 -9
  77. package/node_modules/@maka/runtime/dist/history-compact-summarizer.js +30 -22
  78. package/node_modules/@maka/runtime/dist/history-compaction.js +29 -4
  79. package/node_modules/@maka/runtime/dist/ledger-tool-result-archive-reader.js +106 -0
  80. package/node_modules/@maka/runtime/dist/message-authority.js +9 -0
  81. package/node_modules/@maka/runtime/dist/model-adapter.js +19 -98
  82. package/node_modules/@maka/runtime/dist/model-factory.js +19 -81
  83. package/node_modules/@maka/runtime/dist/model-fetcher.js +7 -0
  84. package/node_modules/@maka/runtime/dist/model-runtime.js +85 -111
  85. package/node_modules/@maka/runtime/dist/native-web-search-tool.js +2 -1
  86. package/node_modules/@maka/runtime/dist/openai-codex-history-compactor.js +2 -2
  87. package/node_modules/@maka/runtime/dist/opencode-session-header.js +4 -2
  88. package/node_modules/@maka/runtime/dist/provider-error-classification.js +84 -123
  89. package/node_modules/@maka/runtime/dist/provider-request-telemetry.js +68 -108
  90. package/node_modules/@maka/runtime/dist/run-handoff-gate.js +97 -0
  91. package/node_modules/@maka/runtime/dist/runtime-event-backfill.js +82 -54
  92. package/node_modules/@maka/runtime/dist/runtime-event-read-model.js +117 -159
  93. package/node_modules/@maka/runtime/dist/runtime-kernel.js +150 -112
  94. package/node_modules/@maka/runtime/dist/runtime-ledger-repair.js +160 -79
  95. package/node_modules/@maka/runtime/dist/runtime-read-model.js +30 -89
  96. package/node_modules/@maka/runtime/dist/runtime-resume.js +80 -4
  97. package/node_modules/@maka/runtime/dist/session-event-runtime-mapper.js +5 -0
  98. package/node_modules/@maka/runtime/dist/session-manager.js +171 -288
  99. package/node_modules/@maka/runtime/dist/session-projection-helpers.js +0 -27
  100. package/node_modules/@maka/runtime/dist/session-trace-projection.js +3 -30
  101. package/node_modules/@maka/runtime/dist/subscription-model-fetch.js +3 -4
  102. package/node_modules/@maka/runtime/dist/test-connection.js +16 -16
  103. package/node_modules/@maka/runtime/dist/tool-result-archive-encoding.js +38 -0
  104. package/node_modules/@maka/runtime/dist/tool-runtime.js +3 -61
  105. package/node_modules/@maka/runtime/package.json +1 -0
  106. package/node_modules/@maka/runtime-host/dist/adapter/session-projector.js +35 -0
  107. package/node_modules/@maka/runtime-host/dist/candidate-entry.js +4 -1
  108. package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +9 -1
  109. package/node_modules/@maka/runtime-host/dist/client/connection.js +3 -0
  110. package/node_modules/@maka/runtime-host/dist/client/host-handoff-copy.js +156 -0
  111. package/node_modules/@maka/runtime-host/dist/client/host-handoff.js +273 -0
  112. package/node_modules/@maka/runtime-host/dist/client/host-retirement.js +26 -5
  113. package/node_modules/@maka/runtime-host/dist/client/index.js +3 -1
  114. package/node_modules/@maka/runtime-host/dist/client/launcher.js +3 -0
  115. package/node_modules/@maka/runtime-host/dist/client/peer-client.js +13 -0
  116. package/node_modules/@maka/runtime-host/dist/client/registered-host-termination.js +0 -59
  117. package/node_modules/@maka/runtime-host/dist/client/remote-compatibility-error.js +2 -0
  118. package/node_modules/@maka/runtime-host/dist/client/session-subscription.js +27 -5
  119. package/node_modules/@maka/runtime-host/dist/operator/index.js +2 -1
  120. package/node_modules/@maka/runtime-host/dist/operator/retirement-cancellation.js +20 -0
  121. package/node_modules/@maka/runtime-host/dist/peer-mesh/node.js +28 -16
  122. package/node_modules/@maka/runtime-host/dist/protocol/external-session.js +4 -0
  123. package/node_modules/@maka/runtime-host/dist/protocol/host-status.js +29 -12
  124. package/node_modules/@maka/runtime-host/dist/protocol/index.js +37 -1
  125. package/node_modules/@maka/runtime-host/dist/protocol/operations.js +2 -0
  126. package/node_modules/@maka/runtime-host/dist/protocol/session-collaboration.js +28 -1
  127. package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +40 -20
  128. package/node_modules/@maka/runtime-host/dist/protocol/session-transcript.js +0 -5
  129. package/node_modules/@maka/runtime-host/dist/protocol/session-turns.js +29 -58
  130. package/node_modules/@maka/runtime-host/dist/protocol/turn.js +11 -1
  131. package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +33 -0
  132. package/node_modules/@maka/runtime-host/dist/server/access-authority.js +26 -1
  133. package/node_modules/@maka/runtime-host/dist/server/access-credential-store.js +6 -1
  134. package/node_modules/@maka/runtime-host/dist/server/canonical-turn-snapshot.js +19 -4
  135. package/node_modules/@maka/runtime-host/dist/server/canonical-usage-reader.js +24 -11
  136. package/node_modules/@maka/runtime-host/dist/server/connection-session.js +7 -6
  137. package/node_modules/@maka/runtime-host/dist/server/daily-review-coordinator.js +7 -4
  138. package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +168 -28
  139. package/node_modules/@maka/runtime-host/dist/server/execution-model-authority.js +5 -5
  140. package/node_modules/@maka/runtime-host/dist/server/execution-model-composition.js +3 -2
  141. package/node_modules/@maka/runtime-host/dist/server/external-session-coordinator.js +6 -3
  142. package/node_modules/@maka/runtime-host/dist/server/goal-coordinator.js +40 -1
  143. package/node_modules/@maka/runtime-host/dist/server/host-kernel.js +108 -23
  144. package/node_modules/@maka/runtime-host/dist/server/host-residency-registry.js +13 -7
  145. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-admission-registry.js +3 -1
  146. package/node_modules/@maka/runtime-host/dist/server/hosted-execution-recovery.js +124 -62
  147. package/node_modules/@maka/runtime-host/dist/server/message-coordinator.js +85 -8
  148. package/node_modules/@maka/runtime-host/dist/server/root-admission-owner.js +7 -0
  149. package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +344 -71
  150. package/node_modules/@maka/runtime-host/dist/server/scheduled-task-coordinator.js +37 -2
  151. package/node_modules/@maka/runtime-host/dist/server/serial-outbound-writer.js +45 -3
  152. package/node_modules/@maka/runtime-host/dist/server/session-catalog-coordinator.js +201 -12
  153. package/node_modules/@maka/runtime-host/dist/server/session-continuity-coordinator.js +87 -25
  154. package/node_modules/@maka/runtime-host/dist/server/session-revision-coordinator.js +8 -55
  155. package/node_modules/@maka/runtime-host/dist/server/session-sidecar-purge.js +1 -16
  156. package/node_modules/@maka/runtime-host/dist/server/session-transcript-pager.js +0 -1
  157. package/node_modules/@maka/runtime-host/dist/server/session-transcript-reader.js +460 -70
  158. package/node_modules/@maka/runtime-host/dist/server/shared-session-transcript.js +1 -1
  159. package/node_modules/@maka/runtime-host/dist/server/storage-maintenance.js +109 -0
  160. package/node_modules/@maka/runtime-host/dist/server/usage-pricing-coordinator.js +4 -11
  161. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +44 -15
  162. package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +6 -2
  163. package/node_modules/@maka/storage/dist/agent-run-store.js +44 -0
  164. package/node_modules/@maka/storage/dist/artifact-store.js +21 -10
  165. package/node_modules/@maka/storage/dist/artifact-stores.js +1 -1
  166. package/node_modules/@maka/storage/dist/claude-code-session-adapter.js +0 -4
  167. package/node_modules/@maka/storage/dist/codex-session-adapter.js +0 -2
  168. package/node_modules/@maka/storage/dist/context-offload-snapshot.js +325 -0
  169. package/node_modules/@maka/storage/dist/conversation-operational-state.js +4 -0
  170. package/node_modules/@maka/storage/dist/execution-stores.js +11 -7
  171. package/node_modules/@maka/storage/dist/file-session-repository.js +1012 -0
  172. package/node_modules/@maka/storage/dist/model-call-ledger.js +199 -32
  173. package/node_modules/@maka/storage/dist/model-call-usage-sql.js +136 -0
  174. package/node_modules/@maka/storage/dist/opencode-session-adapter.js +0 -4
  175. package/node_modules/@maka/storage/dist/operational-state-backup.js +9 -5
  176. package/node_modules/@maka/storage/dist/operational-state-store-public.js +1 -1
  177. package/node_modules/@maka/storage/dist/operational-state-store.js +5 -3
  178. package/node_modules/@maka/storage/dist/runtime-event-persistence.js +1 -0
  179. package/node_modules/@maka/storage/dist/runtime-transcript-query.js +308 -0
  180. package/node_modules/@maka/storage/dist/session-bundle-policy.js +24 -4
  181. package/node_modules/@maka/storage/dist/session-message-projection.js +15 -0
  182. package/node_modules/@maka/storage/dist/session-repository.js +767 -0
  183. package/node_modules/@maka/storage/dist/session-store.js +25 -99
  184. package/node_modules/@maka/storage/dist/sqlite-artifact-metadata.js +10 -3
  185. package/node_modules/@maka/storage/dist/sqlite-context-offload-store.js +24 -8
  186. package/node_modules/@maka/storage/dist/sqlite-core-execution-schema.js +17 -1
  187. package/node_modules/@maka/storage/dist/sqlite-runtime-schema.js +44 -1
  188. package/node_modules/@maka/storage/dist/sqlite-runtime-store.js +129 -25
  189. package/node_modules/@maka/storage/dist/sqlite-session-metadata-store.js +109 -892
  190. package/node_modules/@maka/storage/dist/sqlite-usage-schema.js +200 -20
  191. package/node_modules/@maka/storage/dist/tool-result-archive-evidence.js +111 -0
  192. package/node_modules/@maka/storage/dist/usage-stores.js +6 -2
  193. package/node_modules/@maka/storage/package.json +2 -0
  194. package/package.json +1 -1
@@ -0,0 +1,226 @@
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 { isDeepStrictEqual } from 'node:util';
21
+ import { formatUiMessage } from '@maka/core/ui-locale';
22
+ import { connectExistingRuntimeHost, } from '@maka/runtime-host/client';
23
+ import { readLocalHostDeploymentRecord, resolveRuntimeHostManagedDeploymentAuthority, withLocalHostDeploymentAuthority, } from '@maka/runtime-host/operator';
24
+ import { isProductReleaseVersion } from '@maka/runtime-host/operator/update-package-evidence';
25
+ import { INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID, RUNTIME_HOST_PROTOCOL_VERSION, } from '@maka/runtime-host/protocol';
26
+ import { resolveRuntimeHostNpmGlobalInstallation } from './runtime-host-cli-installation.js';
27
+ import { runRuntimeHostInstalledUpdateBootstrap } from './runtime-host-installed-update-bootstrap.js';
28
+ import { retireRuntimeHostLifecycleOwner } from './runtime-host-lifecycle-transaction.js';
29
+ import { openRuntimeHostNpmGlobalStagedDeployment } from './runtime-host-local-handoff.js';
30
+ import { launchRuntimeHostTargetActivator } from './runtime-host-local-target-activation.js';
31
+ import { getTuiHostOwnerCopy } from './tui-host-owner-copy.js';
32
+ /** A thin owner adapter. The caller disconnects its reconnecting client before execute. */
33
+ export async function prepareTuiHostOwnerAction(input, overrides = {}) {
34
+ const locale = input.locale ?? 'en';
35
+ const copy = getTuiHostOwnerCopy(locale);
36
+ if (input.profile.kind !== 'local') {
37
+ throw new Error(copy.foreign);
38
+ }
39
+ const deps = {
40
+ resolveInstallation: resolveRuntimeHostNpmGlobalInstallation,
41
+ readRecord: readLocalHostDeploymentRecord,
42
+ resolveManagedAuthority: resolveRuntimeHostManagedDeploymentAuthority,
43
+ connectExisting: connectExistingRuntimeHost,
44
+ withAuthority: withLocalHostDeploymentAuthority,
45
+ retire: retireRuntimeHostLifecycleOwner,
46
+ openStaged: openRuntimeHostNpmGlobalStagedDeployment,
47
+ activate: launchRuntimeHostTargetActivator,
48
+ update: runRuntimeHostInstalledUpdateBootstrap,
49
+ ...overrides,
50
+ };
51
+ const installation = await deps.resolveInstallation();
52
+ const rootId = input.connection.rootId;
53
+ const hostEpoch = input.connection.hostEpoch;
54
+ if (await deps.resolveManagedAuthority(rootId))
55
+ throw new Error(copy.notOwner);
56
+ const record = await deps.readRecord(rootId);
57
+ if (record?.state.kind !== 'owned' ||
58
+ record.state.owner.kind !== 'cli' ||
59
+ record.state.owner.installationId !== installation.owner.installationId) {
60
+ throw new Error(copy.notOwner);
61
+ }
62
+ const observed = await deps.connectExisting({
63
+ rootPath: input.rootPath,
64
+ protocol: { min: RUNTIME_HOST_PROTOCOL_VERSION, max: RUNTIME_HOST_PROTOCOL_VERSION },
65
+ compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
66
+ });
67
+ try {
68
+ if (observed.kind !== 'connected' ||
69
+ observed.registration.rootId !== rootId ||
70
+ observed.registration.hostEpoch !== hostEpoch ||
71
+ observed.registration.lifecycleMode !== 'ephemeral') {
72
+ throw new Error(copy.hostChanged);
73
+ }
74
+ }
75
+ finally {
76
+ if (observed.kind === 'connected')
77
+ await observed.connection.close();
78
+ }
79
+ const diagnostics = await input.connection
80
+ .request('host.diagnostics.query', {})
81
+ .catch(() => undefined);
82
+ const fingerprint = activityFingerprint(diagnostics);
83
+ let allowInterruptActiveTasks = false;
84
+ if (!diagnostics || diagnostics.upgradeBlockingActivity) {
85
+ const choice = await input.confirm([
86
+ formatUiMessage(copy.actionSummary, { action: copy[`action_${input.action.action}`], name: input.profile.name }, locale),
87
+ formatUiMessage(copy.owner, { owner: installation.owner.installationId }, locale),
88
+ formatUiMessage(copy.root, { root: rootId }, locale),
89
+ formatUiMessage(copy.epoch, { epoch: hostEpoch }, locale),
90
+ diagnostics
91
+ ? formatUiMessage(copy.activity, {
92
+ connections: diagnostics.connections,
93
+ operations: diagnostics.activeOperations,
94
+ residencies: JSON.stringify(diagnostics.residencies),
95
+ }, locale)
96
+ : copy.unknownActivity,
97
+ input.action.action === 'update' ? copy.updateSafeOnly : '',
98
+ ]
99
+ .filter(Boolean)
100
+ .join('\n'));
101
+ if (choice === 'cancel')
102
+ return undefined;
103
+ allowInterruptActiveTasks = choice === 'interrupt';
104
+ }
105
+ const target = input.action.target ?? 'latest';
106
+ if (input.action.action === 'update' &&
107
+ target !== 'latest' &&
108
+ target !== 'next' &&
109
+ !isProductReleaseVersion(target))
110
+ throw new Error(copy.invalidTarget);
111
+ if (input.action.action === 'update' && allowInterruptActiveTasks) {
112
+ throw new Error(copy.updateRequiresSafe);
113
+ }
114
+ return async () => {
115
+ if (!isDeepStrictEqual(await deps.resolveInstallation(), installation)) {
116
+ throw new Error(copy.installationChanged);
117
+ }
118
+ if (input.action.action === 'update') {
119
+ if (await deps.resolveManagedAuthority(rootId))
120
+ throw new Error(copy.notOwner);
121
+ return deps.update({
122
+ rootPath: input.rootPath,
123
+ selector: target === 'latest' || target === 'next'
124
+ ? { kind: 'channel', channel: target }
125
+ : { kind: 'exact', version: target },
126
+ allowInterruptActiveTasks: false,
127
+ expectedSource: {
128
+ rootId,
129
+ deploymentRevision: record.revision,
130
+ ownerInstallationId: installation.owner.installationId,
131
+ hostEpoch,
132
+ },
133
+ });
134
+ }
135
+ return deps.withAuthority(rootId, async (authority, inheritableAuthorityLeaseFd) => {
136
+ if (!isDeepStrictEqual(await authority.read(), record)) {
137
+ throw new Error(copy.ownerChanged);
138
+ }
139
+ // Same-artifact restart is a process lifecycle operation, not an update.
140
+ // Open the already verified selected package; never restage/reinstall it.
141
+ const staged = input.action.action === 'restart'
142
+ ? await deps.openStaged({
143
+ rootId,
144
+ owner: installation.owner,
145
+ target: record.state.selected,
146
+ transactionId: `tui-restart:${randomUUID()}`,
147
+ })
148
+ : undefined;
149
+ if (await deps.resolveManagedAuthority(rootId))
150
+ throw new Error(copy.notOwner);
151
+ const retired = await deps.retire({
152
+ rootPath: input.rootPath,
153
+ rootId,
154
+ allowInterruptActiveTasks,
155
+ connectExisting: async (request) => {
156
+ const current = await deps.connectExisting(request);
157
+ if (current.kind !== 'connected' ||
158
+ current.registration.rootId !== rootId ||
159
+ current.registration.hostEpoch !== hostEpoch ||
160
+ current.registration.lifecycleMode !== 'ephemeral') {
161
+ if (current.kind === 'connected')
162
+ await current.connection.close();
163
+ throw new Error(copy.retirementHostChanged);
164
+ }
165
+ const currentDiagnostics = await current.connection
166
+ .request('host.diagnostics.query', {})
167
+ .catch(() => undefined);
168
+ if (allowInterruptActiveTasks &&
169
+ activityFingerprint(currentDiagnostics) !== fingerprint) {
170
+ await current.connection.close();
171
+ throw new Error(copy.activityChanged);
172
+ }
173
+ return current;
174
+ },
175
+ });
176
+ if (retired.kind === 'active_tasks') {
177
+ throw new Error(formatUiMessage(copy.stillActive, { action: input.action.action }, locale));
178
+ }
179
+ await retired.owner.close();
180
+ if (staged) {
181
+ const activated = await deps.activate({
182
+ rootPath: input.rootPath,
183
+ rootId,
184
+ staged,
185
+ ownerInstallationId: installation.owner.installationId,
186
+ target: record.state.selected,
187
+ inheritableAuthorityLeaseFd,
188
+ });
189
+ if (activated.kind !== 'ready')
190
+ throw new Error(formatUiMessage(copy.restartFailed, { detail: activated.kind }, locale));
191
+ await activated.settle();
192
+ }
193
+ return 0;
194
+ });
195
+ };
196
+ }
197
+ function activityFingerprint(diagnostics) {
198
+ if (!diagnostics)
199
+ return 'unknown';
200
+ return JSON.stringify([
201
+ diagnostics.hostEpoch,
202
+ diagnostics.pid,
203
+ diagnostics.state,
204
+ diagnostics.connections,
205
+ diagnostics.activeOperations,
206
+ diagnostics.residencies,
207
+ diagnostics.upgradeBlockingActivity,
208
+ ]);
209
+ }
210
+ export async function describeTuiHost(input) {
211
+ const locale = input.locale ?? 'en';
212
+ const copy = getTuiHostOwnerCopy(locale);
213
+ const status = await input.connection.status();
214
+ return [
215
+ formatUiMessage(copy.status, { name: input.profile.name, state: copy[`state_${status.state}`] }, locale),
216
+ formatUiMessage(copy.root, { root: input.connection.rootId }, locale),
217
+ formatUiMessage(copy.epoch, { epoch: status.hostEpoch }, locale),
218
+ formatUiMessage(copy.composition, { composition: status.compositionId, revision: status.compositionRevision }, locale),
219
+ formatUiMessage(copy.activity, {
220
+ connections: status.connections,
221
+ operations: status.activeOperations,
222
+ residencies: status.activeResidencies,
223
+ }, locale),
224
+ input.profile.kind === 'local' ? copy.localOwnerHint : copy.attachedOwnerHint,
225
+ ].join('\n');
226
+ }
@@ -17,6 +17,137 @@
17
17
  * under the License.
18
18
  */
19
19
  export const TUI_COPY_RESOURCES = {
20
+ 'host-owner': {
21
+ en: {
22
+ unavailable: 'Runtime Host owner controls are unavailable.',
23
+ usage: 'Usage: /host [status|stop|restart] or /update [latest|next|version]',
24
+ pickerTitle: 'Runtime Host owner action',
25
+ cancel: 'Cancel',
26
+ cancelDescription: 'Keep this Host and its work running',
27
+ safe: 'Safe handoff',
28
+ safeDescription: 'Continue only at a verified safe boundary',
29
+ interrupt: 'Interrupt active work',
30
+ interruptDescription: 'May stop work from every attached Surface',
31
+ pending: 'A Host owner action is already pending.',
32
+ disconnected: 'The TUI has already disconnected.',
33
+ foreign: 'This TUI is attached to another Host. Run its owner controls on that Host; /exit only disconnects.',
34
+ notOwner: 'This npm-global installation does not own the local Host. Use its actual operator; /exit only disconnects.',
35
+ hostChanged: 'The local Host changed or requires its managed-service operator.',
36
+ actionSummary: '{action}: {name}',
37
+ action_stop: 'Stop',
38
+ action_restart: 'Restart',
39
+ action_update: 'Update',
40
+ owner: 'Owner: {owner}',
41
+ root: 'Root: {root}',
42
+ epoch: 'Epoch: {epoch}',
43
+ composition: 'Composition: {composition} ({revision})',
44
+ activity: 'Connections: {connections}; operations: {operations}; residencies: {residencies}',
45
+ unknownActivity: 'Activity is unknown. Other Surfaces and autonomous tasks may be affected.',
46
+ updateSafeOnly: 'Update supports safe handoff only.',
47
+ invalidTarget: 'Update target must be latest, next, or an exact release version.',
48
+ updateRequiresSafe: 'TUI update uses safe handoff only. Stop active work explicitly before updating.',
49
+ installationChanged: 'The npm-global installation changed. Reopen Maka before retrying.',
50
+ ownerChanged: 'The local Host installation owner changed. Reopen Maka before retrying.',
51
+ retirementHostChanged: 'The Host changed before owner retirement; no replacement was interrupted.',
52
+ activityChanged: 'Host activity changed after confirmation. Reopen Maka and confirm the new work before retrying.',
53
+ stillActive: 'The Host still has active work. Reopen Maka and retry /host {action}; interruption requires a new confirmation.',
54
+ restartFailed: 'Host restart did not complete: {detail}',
55
+ status: '{name}: {state}',
56
+ state_starting: 'Starting',
57
+ state_containing: 'Containing',
58
+ state_recovering: 'Recovering',
59
+ state_ready: 'Ready',
60
+ state_draining: 'Draining',
61
+ localOwnerHint: 'Owner controls require this npm-global installation to own the Host. /exit only disconnects.',
62
+ attachedOwnerHint: 'Attached Host: lifecycle changes require its operator. /exit only disconnects.',
63
+ },
64
+ 'zh-CN': {
65
+ unavailable: 'Runtime Host 所有者控制不可用。',
66
+ usage: '用法:/host [status|stop|restart] 或 /update [latest|next|version]',
67
+ pickerTitle: 'Runtime Host 所有者操作',
68
+ cancel: '取消',
69
+ cancelDescription: '保持此 Host 及其任务运行',
70
+ safe: '安全交接',
71
+ safeDescription: '仅在已验证的安全边界继续',
72
+ interrupt: '中断活动任务',
73
+ interruptDescription: '可能停止所有已连接界面的任务',
74
+ pending: '已有 Host 所有者操作等待执行。',
75
+ disconnected: 'TUI 已断开连接。',
76
+ foreign: '此 TUI 连接的是其他 Host。请在该 Host 上使用其所有者控制;/exit 仅断开连接。',
77
+ notOwner: '此 npm-global 安装不是本地 Host 的所有者。请使用实际所有者的控制工具;/exit 仅断开连接。',
78
+ hostChanged: '本地 Host 已变更,或需要由其托管服务控制工具操作。',
79
+ actionSummary: '{action}:{name}',
80
+ action_stop: '停止',
81
+ action_restart: '重启',
82
+ action_update: '更新',
83
+ owner: '所有者:{owner}',
84
+ root: '根标识:{root}',
85
+ epoch: '实例标识:{epoch}',
86
+ composition: '运行组成:{composition}({revision})',
87
+ activity: '连接数:{connections};操作数:{operations};常驻任务:{residencies}',
88
+ unknownActivity: '活动状态未知。其他界面和自主任务可能受到影响。',
89
+ updateSafeOnly: '更新仅支持安全交接。',
90
+ invalidTarget: '更新目标必须为 latest、next 或确切的发布版本。',
91
+ updateRequiresSafe: 'TUI 更新仅使用安全交接。更新前请先显式停止活动任务。',
92
+ installationChanged: 'npm-global 安装已变更。请重新打开 Maka 后重试。',
93
+ ownerChanged: '本地 Host 的安装所有者已变更。请重新打开 Maka 后重试。',
94
+ retirementHostChanged: '停止旧实例前 Host 已变更;未中断替代实例。',
95
+ activityChanged: '确认后 Host 活动已变更。请重新打开 Maka,确认新的任务后重试。',
96
+ stillActive: 'Host 仍有活动任务。请重新打开 Maka 并重试 /host {action};中断需要重新确认。',
97
+ restartFailed: 'Host 重启未完成:{detail}',
98
+ status: '{name}:{state}',
99
+ state_starting: '启动中',
100
+ state_containing: '故障隔离中',
101
+ state_recovering: '恢复中',
102
+ state_ready: '就绪',
103
+ state_draining: '排空中',
104
+ localOwnerHint: '仅当此 npm-global 安装拥有该 Host 时才能使用所有者控制。/exit 仅断开连接。',
105
+ attachedOwnerHint: '已连接的 Host:生命周期变更需由其所有者操作。/exit 仅断开连接。',
106
+ },
107
+ 'zh-TW': {
108
+ unavailable: 'Runtime Host 擁有者控制無法使用。',
109
+ usage: '用法:/host [status|stop|restart] 或 /update [latest|next|version]',
110
+ pickerTitle: 'Runtime Host 擁有者操作',
111
+ cancel: '取消',
112
+ cancelDescription: '保持此 Host 及其工作執行',
113
+ safe: '安全交接',
114
+ safeDescription: '僅在已驗證的安全邊界繼續',
115
+ interrupt: '中斷進行中的工作',
116
+ interruptDescription: '可能停止所有已連線介面的工作',
117
+ pending: '已有 Host 擁有者操作等待執行。',
118
+ disconnected: 'TUI 已中斷連線。',
119
+ foreign: '此 TUI 連線的是其他 Host。請在該 Host 上使用其擁有者控制;/exit 僅中斷連線。',
120
+ notOwner: '此 npm-global 安裝不是本機 Host 的擁有者。請使用實際擁有者的控制工具;/exit 僅中斷連線。',
121
+ hostChanged: '本機 Host 已變更,或需要由其託管服務控制工具操作。',
122
+ actionSummary: '{action}:{name}',
123
+ action_stop: '停止',
124
+ action_restart: '重新啟動',
125
+ action_update: '更新',
126
+ owner: '擁有者:{owner}',
127
+ root: '根識別碼:{root}',
128
+ epoch: '執行個體識別碼:{epoch}',
129
+ composition: '執行組成:{composition}({revision})',
130
+ activity: '連線數:{connections};操作數:{operations};常駐工作:{residencies}',
131
+ unknownActivity: '活動狀態未知。其他介面和自主工作可能受到影響。',
132
+ updateSafeOnly: '更新僅支援安全交接。',
133
+ invalidTarget: '更新目標必須為 latest、next 或確切的發行版本。',
134
+ updateRequiresSafe: 'TUI 更新僅使用安全交接。更新前請先明確停止進行中的工作。',
135
+ installationChanged: 'npm-global 安裝已變更。請重新開啟 Maka 後重試。',
136
+ ownerChanged: '本機 Host 的安裝擁有者已變更。請重新開啟 Maka 後重試。',
137
+ retirementHostChanged: '停止舊執行個體前 Host 已變更;未中斷替代執行個體。',
138
+ activityChanged: '確認後 Host 活動已變更。請重新開啟 Maka,確認新的工作後重試。',
139
+ stillActive: 'Host 仍有進行中的工作。請重新開啟 Maka 並重試 /host {action};中斷需要重新確認。',
140
+ restartFailed: 'Host 重新啟動未完成:{detail}',
141
+ status: '{name}:{state}',
142
+ state_starting: '啟動中',
143
+ state_containing: '故障隔離中',
144
+ state_recovering: '復原中',
145
+ state_ready: '就緒',
146
+ state_draining: '排空中',
147
+ localOwnerHint: '僅當此 npm-global 安裝擁有該 Host 時才能使用擁有者控制。/exit 僅中斷連線。',
148
+ attachedOwnerHint: '已連線的 Host:生命週期變更需由其擁有者操作。/exit 僅中斷連線。',
149
+ },
150
+ },
20
151
  copy: {
21
152
  en: {
22
153
  copiedLast: 'Sent the last reply to the terminal via OSC 52 · {count, plural, one {# char} other {# chars}} — it reaches the clipboard only if your terminal (and tmux, if used) allows OSC 52 writes.',
@@ -711,7 +842,9 @@ export const TUI_COPY_RESOURCES = {
711
842
  compact: 'Compact session context',
712
843
  context: 'Show latest request context usage',
713
844
  copy: 'Copy the last reply (or /copy all) to the clipboard',
714
- exit: 'Exit Maka',
845
+ exit: 'Disconnect this TUI; leave the attached Host running',
846
+ host: 'Inspect the Host or stop/restart its local owner',
847
+ update: 'Update the npm-global CLI and its local Host',
715
848
  goal: 'Show autonomous goal status',
716
849
  graph: 'Show, enable, disable, or run one Graph turn',
717
850
  help: 'Show commands and keybindings',
@@ -762,7 +895,9 @@ export const TUI_COPY_RESOURCES = {
762
895
  compact: '压缩会话上下文',
763
896
  context: '查看最近一次请求的上下文用量',
764
897
  copy: '复制最后一条回复(或 /copy all)到剪贴板',
765
- exit: '退出 Maka',
898
+ exit: '断开此 TUI,保留已连接的 Host',
899
+ host: '查看 Host 状态或停止、重启本地所有者',
900
+ update: '更新 npm 全局 CLI 及其本地 Host',
766
901
  goal: '查看自主目标状态',
767
902
  graph: '查看、启用、停用 Graph 模式,或执行一次 Graph 任务',
768
903
  help: '查看命令和快捷键',
@@ -813,7 +948,9 @@ export const TUI_COPY_RESOURCES = {
813
948
  compact: '壓縮會話上下文',
814
949
  context: '檢視最近一次請求的上下文用量',
815
950
  copy: '複製最後一則回覆(或 /copy all)到剪貼簿',
816
- exit: '退出 Maka',
951
+ exit: '中斷此 TUI,保留已連線的 Host',
952
+ host: '查看 Host 狀態或停止、重新啟動本機擁有者',
953
+ update: '更新 npm 全域 CLI 及其本機 Host',
817
954
  goal: '檢視自主目標狀態',
818
955
  graph: '檢視、啟用、停用 Graph 模式,或執行一次 Graph 任務',
819
956
  help: '檢視命令和快捷鍵',
@@ -0,0 +1,24 @@
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 { defineUiMessageCatalog, resolveUiMessageCatalog, } from '@maka/core/ui-locale';
20
+ import { TUI_COPY_RESOURCES } from './tui-copy-catalog.js';
21
+ const HOST_OWNER_COPY = resolveUiMessageCatalog(defineUiMessageCatalog()(TUI_COPY_RESOURCES['host-owner']));
22
+ export function getTuiHostOwnerCopy(locale) {
23
+ return HOST_OWNER_COPY[locale];
24
+ }
@@ -22,6 +22,7 @@
22
22
  * different copy without diluting the core message. Exported so the
23
23
  * handler can use it AND a contract test can pin it.
24
24
  */
25
+ // bot-channel notices follow the bot audience language; localization tracked under #2672
25
26
  export function nonTextMessageAck(kind) {
26
27
  switch (kind) {
27
28
  case 'photo':
@@ -139,74 +140,28 @@ function sanitizeBotUserName(value) {
139
140
  .trim() || 'unknown');
140
141
  }
141
142
  /**
142
- * PR-BOT-LASTERROR-FROM-SEND-0 (external bot research): translate the bridge's
143
- * machine-readable `BotStatus.reason` into a short user-readable string
144
- * suitable for persistence in `BotChannelSettings.lastError`. The Settings
145
- * page reads `lastError` from persisted settings (not live status), so
146
- * without this persistence step the user sees stale connection-test
147
- * errors instead of the actual send-path failure that happened minutes
148
- * ago.
149
- *
150
- * Returns `undefined` for non-error reasons (disabled/stopped/missing
151
- * credentials — those have their own UI surface) and for unrecognized
152
- * inputs whose pass-through risks leaking unredacted payloads.
153
- *
154
- * Length-capped at 200 chars defensively; a real Telegram error
155
- * description is typically well under 80 chars.
143
+ * Non-error `BotStatus.reason` values. These states have their own UI surface,
144
+ * so an error readout must not repeat them as failures.
156
145
  */
157
- const BOT_REASON_HUMANIZE = {
158
- 'rate-limited': '发送被节流(429);上一条回复可能截断,可以请用户再发一次',
159
- 'polling-timeout': '事件轮询超时;可能是网络抖动或代理失效',
160
- 'send-failed': '上一次发送失败,详细原因 Telegram 没有返回',
161
- 'get-me-failed': '凭据探测失败;请检查 Bot Token',
162
- // Non-error states surface elsewhere in the UI — return undefined so
163
- // we do not overwrite a real lastError with a benign status change.
164
- disabled: undefined,
165
- stopped: undefined,
166
- 'no-token': undefined,
167
- 'missing-feishu-credentials': undefined,
168
- 'feishu-domain-required': undefined,
169
- 'feishu-events-not-connected': undefined,
170
- 'scaffold-only': undefined,
171
- unimplemented: undefined,
172
- };
146
+ const BOT_BENIGN_STATUS_REASONS = new Set([
147
+ 'disabled',
148
+ 'stopped',
149
+ 'token_missing',
150
+ 'feishu_credentials_missing',
151
+ 'feishu-domain-required',
152
+ 'feishu-events-not-connected',
153
+ 'scaffold-only',
154
+ 'unimplemented',
155
+ ]);
173
156
  /**
174
- * PR-BOT-RUNTIME-REASON-HUMANIZE-0: Discord / DingTalk / QQ bridges
175
- * emit parameterized reason strings like `gateway-closed-4004` and
176
- * `connections-open-500`. Without these patterns the user sees the
177
- * raw machine code in `lastError`; with them they get a translated
178
- * description plus the diagnostic code preserved in parentheses.
179
- *
180
- * Each entry is a regex with one numeric capture group; the matched
181
- * code is preserved verbatim so support diagnostics still survive.
157
+ * `BotStatus.reason` stays a stable code; presenters own the copy. Benign
158
+ * states yield `undefined` so they never overwrite a real error readout.
182
159
  */
183
- const BOT_REASON_HUMANIZE_PATTERNS = [
184
- { pattern: /^gateway-bot-(\d+)$/, format: (code) => `获取 Gateway 失败(HTTP ${code})` },
185
- { pattern: /^gateway-closed-(\d+)$/, format: (code) => `Gateway 连接关闭(${code});正在重连` },
186
- { pattern: /^connections-open-(\d+)$/, format: (code) => `Stream 订阅打开失败(HTTP ${code})` },
187
- { pattern: /^stream-closed-(\d+)$/, format: (code) => `Stream 连接关闭(${code});正在重连` },
188
- { pattern: /^send-failed-(\d+)$/, format: (code) => `发送失败(HTTP ${code})` },
189
- {
190
- pattern: /^getAppAccessToken-(\d+)$/,
191
- format: (code) => `获取 access_token 失败(HTTP ${code})`,
192
- },
193
- ];
194
- export function humanizeBotStatusReason(reason) {
195
- if (typeof reason !== 'string' || reason.length === 0)
160
+ export function botStatusErrorReason(reason) {
161
+ if (typeof reason !== 'string')
196
162
  return undefined;
197
- if (reason in BOT_REASON_HUMANIZE) {
198
- return BOT_REASON_HUMANIZE[reason];
199
- }
200
- for (const { pattern, format } of BOT_REASON_HUMANIZE_PATTERNS) {
201
- const match = pattern.exec(reason);
202
- if (match)
203
- return format(match[1]);
204
- }
205
- // Pass-through for platform-supplied descriptions ("Bad Request:
206
- // chat not found", etc.). Trim + length-cap to keep `lastError`
207
- // bounded.
208
163
  const trimmed = reason.trim();
209
- if (trimmed.length === 0)
164
+ if (trimmed.length === 0 || BOT_BENIGN_STATUS_REASONS.has(trimmed))
210
165
  return undefined;
211
166
  return trimmed.length > 200 ? trimmed.slice(0, 200) : trimmed;
212
167
  }
@@ -93,7 +93,9 @@ function omitUndefinedEnvelopeFields(value) {
93
93
  }
94
94
  if (descriptor.value === undefined)
95
95
  continue;
96
- Object.defineProperty(result, key, descriptor);
96
+ // This is our normalization copy. Frozen snapshots constrain their owner,
97
+ // not this copy; retaining non-configurable flags prevents normalizing it.
98
+ Object.defineProperty(result, key, { ...descriptor, configurable: true, writable: true });
97
99
  }
98
100
  return result;
99
101
  }
@@ -349,15 +349,6 @@ export function decodeModelCallAttempt(value) {
349
349
  }
350
350
  return value;
351
351
  }
352
- export function isModelCallAttempt(value) {
353
- try {
354
- decodeModelCallAttempt(value);
355
- return true;
356
- }
357
- catch {
358
- return false;
359
- }
360
- }
361
352
  /**
362
353
  * Collapses re-appended records by `attemptId`, keeping the last occurrence.
363
354
  *
@@ -389,76 +380,3 @@ export function groupModelCallAttempts(attempts) {
389
380
  }
390
381
  return [...groups.values()];
391
382
  }
392
- /**
393
- * The attempt that settled a logical call: the highest `attempt` ordinal that
394
- * reached a provider outcome. Terminality is a projection concern, not a stored
395
- * field, so it is derived rather than recorded.
396
- */
397
- export function settledAttempt(group) {
398
- let settled;
399
- for (const attempt of group.attempts) {
400
- if (!settled || attempt.attempt > settled.attempt)
401
- settled = attempt;
402
- }
403
- return settled;
404
- }
405
- /**
406
- * Extracts the canonical attempts a run committed, from that run's AgentRun
407
- * events. This is the projection the Usage read model is rebuilt through, so it
408
- * has to be total: an event that cannot be decoded is counted, not thrown, or
409
- * one bad record would block every later one in the same run from ever being
410
- * projected.
411
- */
412
- export function modelCallAttemptsFromRunEvents(events) {
413
- const attempts = [];
414
- let unreadableEvents = 0;
415
- for (const event of events) {
416
- if (event.type !== MODEL_CALL_ATTEMPT_EVENT_TYPE)
417
- continue;
418
- try {
419
- attempts.push(decodeModelCallAttempt(event.data));
420
- }
421
- catch {
422
- unreadableEvents += 1;
423
- }
424
- }
425
- return { attempts, unreadableEvents };
426
- }
427
- export function summarizeModelCallCoverage(attempts) {
428
- const unique = dedupeModelCallAttempts(attempts);
429
- const coverage = {
430
- attempts: unique.length,
431
- pricedAttempts: 0,
432
- unpricedAttempts: 0,
433
- usageReportedAttempts: 0,
434
- usagePartialAttempts: 0,
435
- usageMissingAttempts: 0,
436
- };
437
- for (const attempt of unique) {
438
- if (attempt.costBasis === 'priced')
439
- coverage.pricedAttempts += 1;
440
- else
441
- coverage.unpricedAttempts += 1;
442
- if (attempt.usageBasis === 'reported')
443
- coverage.usageReportedAttempts += 1;
444
- else if (attempt.usageBasis === 'partial')
445
- coverage.usagePartialAttempts += 1;
446
- else
447
- coverage.usageMissingAttempts += 1;
448
- }
449
- return coverage;
450
- }
451
- /**
452
- * Sums cost across attempts. Returns the total alongside the coverage that
453
- * qualifies it, because a bare number cannot express "plus an unknown amount
454
- * from unpriced calls".
455
- */
456
- export function sumModelCallCostUsd(attempts) {
457
- const unique = dedupeModelCallAttempts(attempts);
458
- let costUsd = 0;
459
- for (const attempt of unique) {
460
- if (attempt.costBasis === 'priced' && attempt.costUsd !== undefined)
461
- costUsd += attempt.costUsd;
462
- }
463
- return { costUsd, coverage: summarizeModelCallCoverage(unique) };
464
- }