@inneranimalmedia/agentsam-sdk 2.4.1 → 2.6.0

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 (233) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +17 -12
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/AGENTSAM_WORKBENCH.md +30 -0
  6. package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
  7. package/docs/CAPABILITIES.md +5 -3
  8. package/docs/CLI_SHELL.md +167 -80
  9. package/docs/CMS_STUDIO.md +101 -0
  10. package/docs/CONTEXT.md +170 -0
  11. package/docs/LOCAL_OLLAMA.md +58 -0
  12. package/docs/MERKLE.md +19 -19
  13. package/docs/PORTABLE_CONTEXT.md +4 -3
  14. package/docs/PROJECT_CONFIG.md +72 -0
  15. package/docs/RELEASES.md +35 -2
  16. package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
  17. package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
  18. package/docs/SDK_WORKER.md +86 -0
  19. package/docs/SECURITY.md +59 -22
  20. package/docs/client-cms-editor.md +15 -0
  21. package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
  22. package/package.json +35 -10
  23. package/packages/connectors/cloudflare/package.json +10 -0
  24. package/packages/connectors/cloudflare/src/index.js +127 -0
  25. package/packages/connectors/cloudflare/src/owner.js +76 -0
  26. package/packages/connectors/cloudflare/src/routes.js +223 -0
  27. package/packages/connectors/cloudflare/src/vault.js +80 -0
  28. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  29. package/packages/identity/package.json +10 -1
  30. package/packages/identity/src/contracts/auth-config.js +109 -0
  31. package/packages/identity/src/index.js +1 -0
  32. package/packages/identity/src/oauth/README.md +2 -2
  33. package/packages/identity/src/oauth/credentials.js +11 -4
  34. package/packages/identity/src/oauth/iam-platform.js +3 -3
  35. package/packages/identity/src/providers/iam/index.js +7 -7
  36. package/packages/identity/src/providers/iam/oauth.js +6 -4
  37. package/packages/identity/src/providers/iam/profile.js +5 -5
  38. package/packages/identity/tests/auth-config.test.mjs +61 -0
  39. package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
  40. package/protocol/FILEMETA_V1.md +95 -0
  41. package/protocol/INSPECT_VIEWS_V1.md +40 -0
  42. package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
  43. package/protocol/MERKLE_V1.md +3 -1
  44. package/protocol/README.md +1 -0
  45. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  46. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  47. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  48. package/protocol/capabilities/manifest.json +79 -3
  49. package/protocol/capabilities/repository-snapshot.schema.json +1 -0
  50. package/protocol/context/context-budget.schema.json +36 -0
  51. package/protocol/context/context-item.schema.json +19 -0
  52. package/protocol/context/resolved-context-pack.schema.json +47 -0
  53. package/protocol/context/result-policy.schema.json +17 -0
  54. package/protocol/knowledge/context-pack.schema.json +33 -0
  55. package/protocol/knowledge/retrieval-query.schema.json +68 -13
  56. package/protocol/models/README.md +373 -0
  57. package/protocol/models/model-inventory-v2.schema.json +212 -0
  58. package/python/README.md +15 -7
  59. package/python/agentsam_sdk/cli.py +0 -21
  60. package/python/agentsam_sdk/tui/README.md +17 -12
  61. package/python/agentsam_sdk/tui/bootstrap.py +2 -2
  62. package/python/agentsam_sdk/tui/demo.py +25 -10
  63. package/python/agentsam_sdk/tui/onboarding.py +208 -0
  64. package/python/tests/test_tui_cli.py +7 -6
  65. package/skills/README.md +21 -0
  66. package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
  67. package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
  68. package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
  69. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  70. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  71. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  72. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  73. package/skills/agentsam-jr-dev/SKILL.md +232 -0
  74. package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
  75. package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
  76. package/skills/agentsam-progression-guard/SKILL.md +197 -0
  77. package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
  78. package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
  79. package/skills/catalog.json +71 -0
  80. package/src/agent/capability-adapter.js +25 -13
  81. package/src/agent/index.js +1 -0
  82. package/src/agent/responses-runner.js +325 -0
  83. package/src/capabilities/index.js +7 -0
  84. package/src/capabilities/repository-snapshot-view.js +238 -0
  85. package/src/capabilities/repository-snapshot.js +28 -14
  86. package/src/cli.js +198 -76
  87. package/src/cloudflare/cpu-profile.js +115 -0
  88. package/src/cloudflare/index.js +14 -0
  89. package/src/cloudflare/wrangler.js +132 -0
  90. package/src/commands/account-auth.js +47 -0
  91. package/src/commands/cloudflare.js +58 -0
  92. package/src/commands/connections.js +93 -0
  93. package/src/commands/context-economics.js +114 -0
  94. package/src/commands/context.js +14 -2
  95. package/src/commands/db.js +4 -7
  96. package/src/commands/deploy.js +54 -32
  97. package/src/commands/eval.js +63 -0
  98. package/src/commands/interactive.js +18 -0
  99. package/src/commands/knowledge.js +27 -4
  100. package/src/commands/merkle-persist.js +118 -0
  101. package/src/commands/merkle.js +32 -17
  102. package/src/commands/models.js +152 -0
  103. package/src/commands/ollama.js +259 -0
  104. package/src/commands/preferences.js +144 -0
  105. package/src/commands/product.js +86 -16
  106. package/src/commands/resume.js +67 -0
  107. package/src/commands/security.js +8 -6
  108. package/src/commands/shell.js +405 -100
  109. package/src/commands/skills.js +66 -0
  110. package/src/commands/start-local.js +1 -1
  111. package/src/commands/tunnel.js +5 -4
  112. package/src/commands/whoami.js +86 -0
  113. package/src/context/budget.js +120 -0
  114. package/src/context/compact.js +28 -0
  115. package/src/context/index.js +7 -0
  116. package/src/context/rehydrate.js +35 -0
  117. package/src/context/resolve.js +116 -0
  118. package/src/context/result-policy.js +66 -0
  119. package/src/errors/diagnostic.js +160 -0
  120. package/src/errors/index.js +9 -0
  121. package/src/eval/context.js +191 -0
  122. package/src/eval/index.js +1 -0
  123. package/src/index.js +70 -0
  124. package/src/indexing/execution-boundary.js +144 -0
  125. package/src/indexing/index.js +8 -0
  126. package/src/indexing/provider.js +41 -0
  127. package/src/knowledge/config.js +2 -2
  128. package/src/knowledge/context-pack.js +12 -1
  129. package/src/knowledge/contracts.js +9 -4
  130. package/src/knowledge/engine.js +7 -3
  131. package/src/knowledge/service/server.js +1 -1
  132. package/src/lib/account-session.js +98 -0
  133. package/src/lib/agent-instructions.js +73 -0
  134. package/src/lib/auth.js +4 -0
  135. package/src/lib/bridge-client.js +7 -5
  136. package/src/lib/cli-preferences.js +78 -0
  137. package/src/lib/core-client.js +8 -8
  138. package/src/lib/deploy/git-guard.js +69 -0
  139. package/src/lib/deploy/health.js +57 -0
  140. package/src/lib/deploy/local-studio.js +283 -0
  141. package/src/lib/deploy/secret-scan.js +65 -0
  142. package/src/lib/deploy-receipt/index.js +5 -2
  143. package/src/lib/detect-context.js +6 -5
  144. package/src/lib/execution-approvals.js +59 -0
  145. package/src/lib/identity-scaffold.js +1 -1
  146. package/src/lib/local-scaffold.js +35 -33
  147. package/src/lib/local-sessions.js +127 -0
  148. package/src/lib/local-status.js +9 -17
  149. package/src/lib/merkle/cloudflare-persistence.js +321 -0
  150. package/src/lib/merkle/filemeta.js +43 -0
  151. package/src/lib/merkle/git-ignore.js +24 -0
  152. package/src/lib/merkle/hash.js +1 -0
  153. package/src/lib/merkle/index.js +22 -0
  154. package/src/lib/merkle/persistence.js +72 -0
  155. package/src/lib/merkle/semantic.js +359 -0
  156. package/src/lib/merkle/snapshot.js +9 -3
  157. package/src/lib/merkle/tree.js +11 -6
  158. package/src/lib/project-config.js +227 -0
  159. package/src/lib/project-rules.js +68 -0
  160. package/src/lib/provider-credentials.js +83 -0
  161. package/src/lib/save-sdk-token.js +1 -1
  162. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  163. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  164. package/src/lib/slash-commands.js +22 -15
  165. package/src/lib/tools.js +11 -5
  166. package/src/models/catalog.js +135 -0
  167. package/src/models/index.js +7 -0
  168. package/src/providers/index.js +5 -0
  169. package/src/providers/openai-responses.js +275 -0
  170. package/src/security/index.js +1 -0
  171. package/src/security/inventory.js +4 -1
  172. package/src/security/process.js +35 -9
  173. package/src/security/render.js +27 -5
  174. package/src/security/scan.js +24 -9
  175. package/src/security/trust-boundary.js +24 -0
  176. package/src/skills/index.js +64 -0
  177. package/src/telemetry/contracts.js +203 -0
  178. package/src/telemetry/events.js +48 -0
  179. package/src/telemetry/index.js +8 -0
  180. package/src/tools/hydrate.js +35 -0
  181. package/src/tools/index.js +2 -0
  182. package/src/tools/search.js +70 -0
  183. package/src/ui/ansi.js +1 -1
  184. package/src/ui/boot.js +54 -0
  185. package/src/ui/merkle/render.js +1 -0
  186. package/src/ui/runtime-activity.js +192 -0
  187. package/src/ui/theme.js +19 -18
  188. package/test/account-session.test.mjs +36 -0
  189. package/test/app-building-skills.test.mjs +61 -0
  190. package/test/apps-scaffold-contract.test.mjs +56 -0
  191. package/test/capabilities.test.mjs +53 -4
  192. package/test/cli-preferences.test.mjs +46 -0
  193. package/test/cloudflare-connector.test.mjs +96 -0
  194. package/test/cloudflare-runtime.test.mjs +75 -0
  195. package/test/context.test.mjs +144 -0
  196. package/test/deploy-health-scan.test.mjs +67 -0
  197. package/test/error-diagnostics.test.mjs +59 -0
  198. package/test/eval-context.test.mjs +37 -0
  199. package/test/execution-approvals.test.mjs +27 -0
  200. package/test/indexing-provider.test.mjs +29 -0
  201. package/test/jr-dev-skill.test.mjs +26 -0
  202. package/test/knowledge-context-pack.test.mjs +19 -0
  203. package/test/knowledge.test.mjs +1 -1
  204. package/test/local-sessions.test.mjs +42 -0
  205. package/test/local-studio-deploy.test.mjs +83 -0
  206. package/test/merkle-persistence.test.mjs +91 -0
  207. package/test/merkle.test.mjs +77 -3
  208. package/test/model-catalog.test.mjs +43 -0
  209. package/test/models.test.mjs +51 -0
  210. package/test/npm10-lock.test.mjs +29 -0
  211. package/test/ollama.test.mjs +94 -0
  212. package/test/openai-responses.test.mjs +95 -0
  213. package/test/project-config.test.mjs +81 -0
  214. package/test/project-rules.test.mjs +44 -0
  215. package/test/provider-credentials.test.mjs +52 -0
  216. package/test/rehydrate.test.mjs +25 -0
  217. package/test/release-hygiene.test.mjs +34 -0
  218. package/test/repository-snapshot-view.test.mjs +112 -0
  219. package/test/responses-runner.test.mjs +148 -0
  220. package/test/runtime-activity.test.mjs +98 -0
  221. package/test/sdk-worker-contract.test.mjs +68 -0
  222. package/test/security.test.mjs +46 -0
  223. package/test/shell.test.mjs +55 -21
  224. package/test/skills.test.mjs +22 -0
  225. package/test/smoke.mjs +5 -2
  226. package/test/telemetry.test.mjs +79 -0
  227. package/test/theme-portability.test.mjs +14 -0
  228. package/test/tools-search.test.mjs +40 -0
  229. package/test/whoami-resume.test.mjs +56 -0
  230. package/examples/agentsam-tui-ansi.mjs +0 -149
  231. package/src/commands/tui.js +0 -120
  232. package/src/ui/splash-xterm.js +0 -290
  233. package/src/ui/splash.js +0 -426
@@ -0,0 +1,203 @@
1
+ export const RUNTIME_RECEIPT_SCHEMA_VERSION = 1;
2
+
3
+ const RUN_MODES = new Set(['ask', 'plan', 'agent', 'debug', 'multitask']);
4
+ const RUN_STATUSES = new Set(['queued', 'running', 'completed', 'failed', 'partial', 'cancelled']);
5
+ const APPROVAL_STATUSES = new Set(['pending', 'approved', 'denied', 'expired']);
6
+ const TERMINAL_STATUSES = new Set(['queued', 'running', 'completed', 'failed', 'cancelled', 'unknown']);
7
+
8
+ function clean(value) {
9
+ return value == null ? '' : String(value).trim();
10
+ }
11
+
12
+ function required(value, label) {
13
+ const text = clean(value);
14
+ if (!text) throw new TypeError(`${label} is required`);
15
+ return text;
16
+ }
17
+
18
+ function optional(value) {
19
+ const text = clean(value);
20
+ return text || null;
21
+ }
22
+
23
+ function nonNegativeInteger(value, fallback = 0) {
24
+ const number = Number(value ?? fallback);
25
+ if (!Number.isFinite(number) || number < 0) throw new RangeError('expected a non-negative number');
26
+ return Math.floor(number);
27
+ }
28
+
29
+ function optionalNonNegativeInteger(value) {
30
+ if (value == null || value === '') return null;
31
+ return nonNegativeInteger(value);
32
+ }
33
+
34
+ function nonNegativeNumber(value, fallback = 0) {
35
+ const number = Number(value ?? fallback);
36
+ if (!Number.isFinite(number) || number < 0) throw new RangeError('expected a non-negative number');
37
+ return number;
38
+ }
39
+
40
+ function canonicalMode(value) {
41
+ const mode = clean(value || 'agent').toLowerCase();
42
+ if (!RUN_MODES.has(mode)) throw new RangeError(`unsupported run mode: ${mode}`);
43
+ return mode;
44
+ }
45
+
46
+ function canonicalRunStatus(value) {
47
+ const status = clean(value || 'queued').toLowerCase();
48
+ if (!RUN_STATUSES.has(status)) throw new RangeError(`unsupported run status: ${status}`);
49
+ return status;
50
+ }
51
+
52
+ function jsonText(value, fallback) {
53
+ if (value == null || value === '') return JSON.stringify(fallback);
54
+ if (typeof value === 'string') {
55
+ JSON.parse(value);
56
+ return value;
57
+ }
58
+ return JSON.stringify(value);
59
+ }
60
+
61
+ /**
62
+ * Provider-neutral execution receipt. The authenticated host supplies account_id;
63
+ * tenant/workspace/user aliases are deliberately not part of this contract.
64
+ */
65
+ export function createRunReceipt(value = {}) {
66
+ return Object.freeze({
67
+ schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
68
+ id: required(value.id ?? value.run_id, 'id'),
69
+ account_id: required(value.account_id ?? value.accountId, 'account_id'),
70
+ conversation_id: optional(value.conversation_id ?? value.conversationId),
71
+ external_agent_id: optional(value.external_agent_id ?? value.externalAgentId),
72
+ parent_run_id: optional(value.parent_run_id ?? value.parentRunId),
73
+ source_client: optional(value.source_client ?? value.sourceClient),
74
+ surface: optional(value.surface),
75
+ mode: canonicalMode(value.mode),
76
+ model_key: optional(value.model_key ?? value.modelKey),
77
+ reasoning_effort: optional(value.reasoning_effort ?? value.reasoningEffort),
78
+ requested_service_tier: optional(value.requested_service_tier ?? value.requestedServiceTier),
79
+ actual_service_tier: optional(value.actual_service_tier ?? value.actualServiceTier),
80
+ selected_by: optional(value.selected_by ?? value.selectedBy),
81
+ routing_arm_id: optional(value.routing_arm_id ?? value.routingArmId),
82
+ status: canonicalRunStatus(value.status),
83
+ cancel_requested: value.cancel_requested === true || value.cancel_requested === 1 ? 1 : 0,
84
+ error_code: optional(value.error_code ?? value.errorCode),
85
+ error_message: optional(value.error_message ?? value.errorMessage),
86
+ model_call_count: nonNegativeInteger(value.model_call_count ?? value.modelCallCount),
87
+ tool_call_count: nonNegativeInteger(value.tool_call_count ?? value.toolCallCount),
88
+ input_tokens: nonNegativeInteger(value.input_tokens ?? value.inputTokens),
89
+ cached_input_tokens: nonNegativeInteger(value.cached_input_tokens ?? value.cachedInputTokens),
90
+ output_tokens: nonNegativeInteger(value.output_tokens ?? value.outputTokens),
91
+ reasoning_tokens: nonNegativeInteger(value.reasoning_tokens ?? value.reasoningTokens),
92
+ cost_usd: nonNegativeNumber(value.cost_usd ?? value.costUsd),
93
+ created_at_unix: optionalNonNegativeInteger(value.created_at_unix ?? value.createdAtUnix),
94
+ started_at_unix: optionalNonNegativeInteger(value.started_at_unix ?? value.startedAtUnix),
95
+ completed_at_unix: optionalNonNegativeInteger(value.completed_at_unix ?? value.completedAtUnix),
96
+ updated_at_unix: optionalNonNegativeInteger(value.updated_at_unix ?? value.updatedAtUnix),
97
+ latency_ms: optionalNonNegativeInteger(value.latency_ms ?? value.latencyMs),
98
+ });
99
+ }
100
+
101
+ /** One authoritative provider/model call receipt. */
102
+ export function createUsageReceipt(value = {}) {
103
+ const inputTokens = nonNegativeInteger(value.input_tokens ?? value.inputTokens);
104
+ const cachedInputTokens = nonNegativeInteger(value.cached_input_tokens ?? value.cachedInputTokens);
105
+ const cacheWriteTokens = nonNegativeInteger(value.cache_write_tokens ?? value.cacheWriteTokens);
106
+ const outputTokens = nonNegativeInteger(value.output_tokens ?? value.outputTokens);
107
+ const reasoningTokens = nonNegativeInteger(value.reasoning_tokens ?? value.reasoningTokens);
108
+ const totalTokens = value.total_tokens == null && value.totalTokens == null
109
+ ? inputTokens + outputTokens
110
+ : nonNegativeInteger(value.total_tokens ?? value.totalTokens);
111
+
112
+ return Object.freeze({
113
+ schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
114
+ id: required(value.id, 'id'),
115
+ account_id: required(value.account_id ?? value.accountId, 'account_id'),
116
+ agent_run_id: optional(value.agent_run_id ?? value.agentRunId),
117
+ conversation_id: optional(value.conversation_id ?? value.conversationId),
118
+ repository_id: optional(value.repository_id ?? value.repositoryId),
119
+ source_client: optional(value.source_client ?? value.sourceClient),
120
+ usage_kind: optional(value.usage_kind ?? value.usageKind) || 'model',
121
+ provider: required(value.provider, 'provider'),
122
+ model_key: required(value.model_key ?? value.modelKey, 'model_key'),
123
+ model_call_index: optionalNonNegativeInteger(value.model_call_index ?? value.modelCallIndex),
124
+ provider_request_id: optional(value.provider_request_id ?? value.providerRequestId),
125
+ requested_service_tier: optional(value.requested_service_tier ?? value.requestedServiceTier),
126
+ actual_service_tier: optional(value.actual_service_tier ?? value.actualServiceTier),
127
+ input_tokens: inputTokens,
128
+ cached_input_tokens: cachedInputTokens,
129
+ cache_write_tokens: cacheWriteTokens,
130
+ output_tokens: outputTokens,
131
+ reasoning_tokens: reasoningTokens,
132
+ total_tokens: totalTokens,
133
+ cost_usd: nonNegativeNumber(value.cost_usd ?? value.costUsd),
134
+ cost_basis: optional(value.cost_basis ?? value.costBasis),
135
+ duration_ms: optionalNonNegativeInteger(value.duration_ms ?? value.durationMs),
136
+ status: optional(value.status) || 'ok',
137
+ error_code: optional(value.error_code ?? value.errorCode),
138
+ ref_table: optional(value.ref_table ?? value.refTable),
139
+ ref_id: optional(value.ref_id ?? value.refId),
140
+ created_at_unix: optionalNonNegativeInteger(value.created_at_unix ?? value.createdAtUnix),
141
+ });
142
+ }
143
+
144
+ /** Approval receipt linked to execution/tool/process lineage without ownership aliases. */
145
+ export function createApprovalReceipt(value = {}) {
146
+ const status = clean(value.status || 'pending').toLowerCase();
147
+ if (!APPROVAL_STATUSES.has(status)) throw new RangeError(`unsupported approval status: ${status}`);
148
+ return Object.freeze({
149
+ schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
150
+ id: required(value.id, 'id'),
151
+ account_id: required(value.account_id ?? value.accountId, 'account_id'),
152
+ agent_run_id: optional(value.agent_run_id ?? value.agentRunId),
153
+ tool_call_id: optional(value.tool_call_id ?? value.toolCallId),
154
+ terminal_job_id: optional(value.terminal_job_id ?? value.terminalJobId),
155
+ conversation_id: optional(value.conversation_id ?? value.conversationId),
156
+ capability_key: optional(value.capability_key ?? value.capabilityKey),
157
+ tool_key: optional(value.tool_key ?? value.toolKey),
158
+ action_summary: required(value.action_summary ?? value.actionSummary, 'action_summary'),
159
+ sanitized_input_json: jsonText(value.sanitized_input_json ?? value.sanitizedInput, {}),
160
+ risk_level: optional(value.risk_level ?? value.riskLevel) || 'medium',
161
+ approval_type: optional(value.approval_type ?? value.approvalType) || 'tool',
162
+ status,
163
+ response_json: jsonText(value.response_json ?? value.response, {}),
164
+ approved_by: optional(value.approved_by ?? value.approvedBy),
165
+ created_at: optionalNonNegativeInteger(value.created_at ?? value.createdAt),
166
+ expires_at: optionalNonNegativeInteger(value.expires_at ?? value.expiresAt),
167
+ decided_at: optionalNonNegativeInteger(value.decided_at ?? value.decidedAt),
168
+ metadata_json: jsonText(value.metadata_json ?? value.metadata, {}),
169
+ });
170
+ }
171
+
172
+ /** Thin durable process-control receipt; full terminal transcripts stay elsewhere. */
173
+ export function createTerminalJobReceipt(value = {}) {
174
+ const status = clean(value.status || 'queued').toLowerCase();
175
+ if (!TERMINAL_STATUSES.has(status)) throw new RangeError(`unsupported terminal job status: ${status}`);
176
+ return Object.freeze({
177
+ schema_version: RUNTIME_RECEIPT_SCHEMA_VERSION,
178
+ id: required(value.id, 'id'),
179
+ account_id: required(value.account_id ?? value.accountId, 'account_id'),
180
+ instance_id: required(value.instance_id ?? value.instanceId, 'instance_id'),
181
+ connection_id: required(value.connection_id ?? value.connectionId, 'connection_id'),
182
+ session_id: optional(value.session_id ?? value.sessionId),
183
+ source_run_id: optional(value.source_run_id ?? value.sourceRunId),
184
+ tool_call_id: optional(value.tool_call_id ?? value.toolCallId),
185
+ execos_run_id: optional(value.execos_run_id ?? value.execosRunId),
186
+ status,
187
+ cwd: optional(value.cwd),
188
+ timeout_ms: optionalNonNegativeInteger(value.timeout_ms ?? value.timeoutMs),
189
+ exit_code: value.exit_code == null && value.exitCode == null ? null : Number(value.exit_code ?? value.exitCode),
190
+ failure_code: optional(value.failure_code ?? value.failureCode),
191
+ log_ref: optional(value.log_ref ?? value.logRef),
192
+ output_artifact_ref: optional(value.output_artifact_ref ?? value.outputArtifactRef),
193
+ artifact_refs_json: jsonText(value.artifact_refs_json ?? value.artifactRefs, []),
194
+ idempotency_key: optional(value.idempotency_key ?? value.idempotencyKey),
195
+ attempt: nonNegativeInteger(value.attempt),
196
+ max_attempts: Math.max(1, nonNegativeInteger(value.max_attempts ?? value.maxAttempts, 1)),
197
+ last_observed_at: optionalNonNegativeInteger(value.last_observed_at ?? value.lastObservedAt),
198
+ created_at: optionalNonNegativeInteger(value.created_at ?? value.createdAt),
199
+ started_at: optionalNonNegativeInteger(value.started_at ?? value.startedAt),
200
+ finished_at: optionalNonNegativeInteger(value.finished_at ?? value.finishedAt),
201
+ updated_at: optionalNonNegativeInteger(value.updated_at ?? value.updatedAt),
202
+ });
203
+ }
@@ -0,0 +1,48 @@
1
+ export const AGENT_EVENT_TYPES = Object.freeze([
2
+ 'run.started', 'run.status', 'run.completed', 'run.failed',
3
+ 'error.observed',
4
+ 'model.started', 'model.delta', 'model.completed',
5
+ 'usage.snapshot', 'cost.snapshot',
6
+ 'context.snapshot', 'context.compaction.started', 'context.compaction.completed',
7
+ 'tool.search', 'tool.started', 'tool.completed', 'tool.failed',
8
+ 'task.updated',
9
+ ]);
10
+
11
+ function integer(value, label) {
12
+ const number = Number(value ?? 0);
13
+ if (!Number.isFinite(number) || number < 0) throw new RangeError(`${label} must be a non-negative number`);
14
+ return Math.floor(number);
15
+ }
16
+
17
+ export function createAgentEvent(type, payload = {}, options = {}) {
18
+ if (!AGENT_EVENT_TYPES.includes(type)) throw new RangeError(`unsupported AgentEvent type: ${type}`);
19
+ const event = {
20
+ schema_version: 1,
21
+ type,
22
+ timestamp: options.timestamp || new Date().toISOString(),
23
+ ...(options.runId ? { run_id: String(options.runId) } : {}),
24
+ ...(Number.isInteger(options.sequence) && options.sequence >= 0 ? { sequence: options.sequence } : {}),
25
+ payload: payload && typeof payload === 'object' ? payload : { value: payload },
26
+ };
27
+ JSON.stringify(event);
28
+ return Object.freeze(event);
29
+ }
30
+
31
+ export function createUsageSnapshot(value = {}) {
32
+ const estimateKind = value.estimate_kind === 'provider' ? 'provider' : 'local';
33
+ return Object.freeze({
34
+ current_context: Object.freeze({
35
+ input_tokens: integer(value.current_context?.input_tokens ?? value.currentContextTokens, 'current_context.input_tokens'),
36
+ window_tokens: integer(value.current_context?.window_tokens ?? value.windowTokens, 'current_context.window_tokens'),
37
+ }),
38
+ cumulative: Object.freeze({
39
+ input_tokens: integer(value.cumulative?.input_tokens ?? value.inputTokens, 'cumulative.input_tokens'),
40
+ output_tokens: integer(value.cumulative?.output_tokens ?? value.outputTokens, 'cumulative.output_tokens'),
41
+ cached_input_tokens: integer(value.cumulative?.cached_input_tokens ?? value.cachedInputTokens, 'cumulative.cached_input_tokens'),
42
+ cache_write_tokens: integer(value.cumulative?.cache_write_tokens ?? value.cacheWriteTokens, 'cumulative.cache_write_tokens'),
43
+ reasoning_tokens: integer(value.cumulative?.reasoning_tokens ?? value.reasoningTokens, 'cumulative.reasoning_tokens'),
44
+ }),
45
+ estimate_kind: estimateKind,
46
+ provider_authoritative: estimateKind === 'provider',
47
+ });
48
+ }
@@ -0,0 +1,8 @@
1
+ export { AGENT_EVENT_TYPES, createAgentEvent, createUsageSnapshot } from './events.js';
2
+ export {
3
+ RUNTIME_RECEIPT_SCHEMA_VERSION,
4
+ createRunReceipt,
5
+ createUsageReceipt,
6
+ createApprovalReceipt,
7
+ createTerminalJobReceipt,
8
+ } from './contracts.js';
@@ -0,0 +1,35 @@
1
+ function clean(value) { return value == null ? '' : String(value).trim(); }
2
+
3
+ export function hydrateToolSchemas(catalog = [], selected = [], options = {}) {
4
+ if (!Array.isArray(catalog)) throw new TypeError('catalog must be an array');
5
+ if (!Array.isArray(selected)) throw new TypeError('selected must be an array');
6
+ const maxTools = Number.isInteger(options.maxTools) && options.maxTools > 0 ? options.maxTools : 8;
7
+ const maxChars = Number.isInteger(options.maxChars) && options.maxChars > 0 ? options.maxChars : 40_000;
8
+ const wanted = [...new Set(selected.map(clean).filter(Boolean))].slice(0, maxTools);
9
+ const byName = new Map(catalog.map((tool) => [clean(tool.tool || tool.name), tool]).filter(([name]) => name));
10
+ const tools = [];
11
+ const missing = [];
12
+ const deferred = [];
13
+ let schemaChars = 0;
14
+
15
+ for (const name of wanted) {
16
+ const tool = byName.get(name);
17
+ if (!tool) { missing.push(name); continue; }
18
+ const size = JSON.stringify(tool).length;
19
+ if (schemaChars + size > maxChars) { deferred.push(name); continue; }
20
+ tools.push(Object.freeze({ ...tool }));
21
+ schemaChars += size;
22
+ }
23
+
24
+ return Object.freeze({
25
+ tools: Object.freeze(tools),
26
+ receipt: Object.freeze({
27
+ catalog_items: catalog.length,
28
+ requested_tools: wanted.length,
29
+ hydrated_tools: tools.length,
30
+ schema_chars: schemaChars,
31
+ missing_tools: Object.freeze(missing),
32
+ deferred_tools: Object.freeze(deferred),
33
+ }),
34
+ });
35
+ }
@@ -0,0 +1,2 @@
1
+ export { searchToolCards, toToolCard } from './search.js';
2
+ export { hydrateToolSchemas } from './hydrate.js';
@@ -0,0 +1,70 @@
1
+ import { DEFAULT_RESULT_POLICY, normalizeResultPolicy } from '../context/result-policy.js';
2
+
3
+ function text(value) { return value == null ? '' : String(value).trim(); }
4
+
5
+ export function toToolCard(tool = {}) {
6
+ const required = Array.isArray(tool.required)
7
+ ? tool.required
8
+ : Array.isArray(tool.input_schema?.required)
9
+ ? tool.input_schema.required
10
+ : [];
11
+ return Object.freeze({
12
+ tool: text(tool.tool || tool.name),
13
+ summary: text(tool.summary || tool.description),
14
+ category: text(tool.category) || undefined,
15
+ risk: text(tool.risk) || 'read',
16
+ required: Object.freeze([...required]),
17
+ result_class: text(tool.result_class) || 'bounded_evidence',
18
+ });
19
+ }
20
+
21
+ function score(card, query) {
22
+ if (!query) return 1;
23
+ const q = query.toLowerCase();
24
+ const terms = q.split(/\s+/).filter(Boolean);
25
+ const haystack = `${card.tool} ${card.summary} ${card.category || ''}`.toLowerCase();
26
+ let total = 0;
27
+ if (card.tool.toLowerCase() === q) total += 100;
28
+ if (card.tool.toLowerCase().includes(q)) total += 30;
29
+ if (card.summary.toLowerCase().includes(q)) total += 15;
30
+ for (const term of terms) {
31
+ if (card.tool.toLowerCase().includes(term)) total += 8;
32
+ if (card.summary.toLowerCase().includes(term)) total += 4;
33
+ if ((card.category || '').toLowerCase().includes(term)) total += 2;
34
+ }
35
+ return total;
36
+ }
37
+
38
+ export function searchToolCards(catalog = [], query = '', options = {}) {
39
+ if (!Array.isArray(catalog)) throw new TypeError('catalog must be an array');
40
+ const policy = normalizeResultPolicy({
41
+ max_items: options.maxItems ?? DEFAULT_RESULT_POLICY.max_items,
42
+ max_chars: options.maxChars ?? DEFAULT_RESULT_POLICY.max_chars,
43
+ detail: 'card',
44
+ }, { ceiling: DEFAULT_RESULT_POLICY });
45
+ const cards = catalog.map(toToolCard)
46
+ .map((card, index) => ({ card, score: score(card, text(query)), index }))
47
+ .filter((row) => row.card.tool && row.score > 0)
48
+ .sort((a, b) => (b.score - a.score) || (a.index - b.index))
49
+ .slice(0, policy.max_items)
50
+ .map((row) => row.card);
51
+
52
+ let chars = 0;
53
+ const bounded = [];
54
+ for (const card of cards) {
55
+ const size = JSON.stringify(card).length;
56
+ if (chars + size > policy.max_chars) break;
57
+ bounded.push(card);
58
+ chars += size;
59
+ }
60
+ return Object.freeze({
61
+ query: text(query),
62
+ cards: Object.freeze(bounded),
63
+ receipt: Object.freeze({
64
+ catalog_items: catalog.length,
65
+ returned_items: bounded.length,
66
+ chars,
67
+ result_policy: policy,
68
+ }),
69
+ });
70
+ }
package/src/ui/ansi.js CHANGED
@@ -40,7 +40,7 @@ export function renderLocalStatus(status) {
40
40
  return lines.join('\n');
41
41
  }
42
42
 
43
- lines.push(row('lane', `${status.lane} · ${status.agent}`, width));
43
+ lines.push(row('profile', `${status.lane || 'custom'} · ${status.agent || 'default'}`, width));
44
44
  lines.push(
45
45
  row(
46
46
  'git',
package/src/ui/boot.js ADDED
@@ -0,0 +1,54 @@
1
+ import path from 'node:path';
2
+ import pc from 'picocolors';
3
+
4
+ const FRAMES = ['◔', '◑', '◕', '●'];
5
+ const CLEAR_LINE = '\x1b[2K';
6
+ const HIDE_CURSOR = '\x1b[?25l';
7
+ const SHOW_CURSOR = '\x1b[?25h';
8
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
9
+
10
+ function compactHome(value) {
11
+ const home = process.env.HOME || process.env.USERPROFILE || '';
12
+ if (home && value.startsWith(home)) return `~${value.slice(home.length)}`;
13
+ return value;
14
+ }
15
+
16
+ function modelLine(preferences) {
17
+ const model = preferences.modelPreference && preferences.modelPreference !== 'auto' ? preferences.modelPreference : 'auto';
18
+ const reasoning = preferences.reasoningEffort && preferences.reasoningEffort !== 'auto' ? preferences.reasoningEffort : 'auto';
19
+ const tier = preferences.serviceTier || 'default';
20
+ return `${model} · ${reasoning} · ${tier}`;
21
+ }
22
+
23
+ export function renderBootSummary({ identity, preferences }) {
24
+ const branch = identity.branch ? ` · ${pc.cyan(identity.branch)}` : '';
25
+ const runtime = preferences.runtime || 'local';
26
+ const terminal = preferences.terminal || path.basename(process.env.SHELL || '') || 'shell';
27
+ return [
28
+ '',
29
+ ` ${pc.bold('Agent Sam')} ${pc.green('●')}`,
30
+ ` ${pc.cyan(identity.project)}${branch}`,
31
+ ` ${pc.white(modelLine(preferences))}`,
32
+ ` ${pc.dim(compactHome(identity.root))}`,
33
+ ` ${pc.green('✓')} ${pc.dim(runtime)} · ${pc.dim(terminal)} · ready`,
34
+ ` ${pc.dim('Tip: /model changes model + reasoning + processing; / shows the command menu.')}`,
35
+ '',
36
+ ].join('\n');
37
+ }
38
+
39
+ export async function runBootScene({ identity, preferences, animate = true, write = process.stdout.write.bind(process.stdout) }) {
40
+ const interactive = Boolean(animate && process.stdout.isTTY);
41
+ if (!interactive) { write(renderBootSummary({ identity, preferences })); return; }
42
+ write(HIDE_CURSOR);
43
+ try {
44
+ write(`\n ${pc.bold('Agent Sam')}\n ${pc.cyan(identity.project)}${identity.branch ? ` · ${pc.cyan(identity.branch)}` : ''}\n ${pc.white(modelLine(preferences))}\n\n`);
45
+ for (const label of ['directory trust', 'runtime', 'model policy']) {
46
+ for (let i = 0; i < FRAMES.length; i += 1) {
47
+ write(`\r${CLEAR_LINE} ${pc.cyan(FRAMES[i])} ${pc.dim(`checking ${label}`)}`);
48
+ await sleep(i === FRAMES.length - 1 ? 45 : 55);
49
+ }
50
+ write(`\r${CLEAR_LINE} ${pc.green('✓')} ${pc.dim(label)}\n`);
51
+ }
52
+ write(`\n ${pc.green('●')} ${pc.bold('ready')} ${pc.dim(compactHome(identity.root))}\n ${pc.dim('Type / and press Enter for the scrollable command menu.')}\n\n`);
53
+ } finally { write(SHOW_CURSOR); }
54
+ }
@@ -79,6 +79,7 @@ export function renderSummary(result, { inspect = false, color = false } = {}) {
79
79
  const { tree, diff } = result;
80
80
  const lines = [paint(`Agent Sam · ${safeText(result.title)}`, '1;36', color), ` path: ${safeText(tree.rootPath)}`,
81
81
  ` files: ${tree.stats.files} links: ${tree.stats.symlinks} bytes: ${tree.stats.bytes}`, ` root: ${tree.rootHash}`];
82
+ if (tree.semantic) lines.push(` meta: ${tree.semantic.rootHash} (${tree.semantic.classifier.format}/v${tree.semantic.classifier.version})`);
82
83
  if (result.output) lines.push(` saved: ${safeText(result.output)}`);
83
84
  if (diff) {
84
85
  lines.push(` ${diff.equal ? 'MATCH' : 'CHANGED'} ` + Object.entries(diff.stats).map(([key, value]) => `${value} ${key}`).join(' · '));
@@ -0,0 +1,192 @@
1
+ import pc from 'picocolors';
2
+
3
+ const CLEAR_LINE = '\x1b[2K';
4
+ const HIDE_CURSOR = '\x1b[?25l';
5
+ const SHOW_CURSOR = '\x1b[?25h';
6
+ const SPINNER = ['◔', '◑', '◕', '●'];
7
+ const PANEL_WIDTH = 34;
8
+ const PANEL_INNER = PANEL_WIDTH - 2;
9
+ const PANEL_LINES = 5;
10
+
11
+ const THINK_FRAMES = [
12
+ [' · ·', ' ○', ' · ·'],
13
+ [' ✦ ·', ' (○)', ' · ✦'],
14
+ [' · ✦', ' (●)', ' ✦ ·'],
15
+ [' ✦', ' ( ◯ )', ' ✦'],
16
+ [' ✦ ✦', ' (●)', ' ✦ ✦'],
17
+ [' · ✦', ' (○)', ' ✦ ·'],
18
+ ];
19
+
20
+ function elapsedLabel(ms) {
21
+ if (ms < 1000) return `${Math.max(0, Math.round(ms))}ms`;
22
+ return `${(ms / 1000).toFixed(ms < 10000 ? 1 : 0)}s`;
23
+ }
24
+
25
+ function truncate(value, max) {
26
+ const text = String(value || '');
27
+ if (text.length <= max) return text;
28
+ return `${text.slice(0, Math.max(0, max - 1))}…`;
29
+ }
30
+
31
+ function center(value, width) {
32
+ const text = truncate(value, width);
33
+ const left = Math.max(0, Math.floor((width - text.length) / 2));
34
+ const right = Math.max(0, width - text.length - left);
35
+ return `${' '.repeat(left)}${text}${' '.repeat(right)}`;
36
+ }
37
+
38
+ function titledBorder(title, width, left, right) {
39
+ const label = ` ${title} `;
40
+ const fill = Math.max(0, width - left.length - right.length - label.length);
41
+ return `${left}${label}${'─'.repeat(fill)}${right}`;
42
+ }
43
+
44
+ function subtitledBorder(subtitle, width, left, right) {
45
+ const label = ` ${truncate(subtitle, width - 4)} `;
46
+ const fill = Math.max(0, width - left.length - right.length - label.length);
47
+ return `${left}${label}${'─'.repeat(fill)}${right}`;
48
+ }
49
+
50
+ export function canAnimateTerminal({ stdout = process.stdout, env = process.env } = {}) {
51
+ if (!stdout?.isTTY) return false;
52
+ if (String(env.TERM || '').toLowerCase() === 'dumb') return false;
53
+ if (String(env.CI || '').trim()) return false;
54
+ return true;
55
+ }
56
+
57
+ export function renderRuntimeActivityFrame({
58
+ label = 'Agent Sam',
59
+ phase = 'thinking',
60
+ tick = 0,
61
+ elapsedMs = 0,
62
+ } = {}) {
63
+ const frame = SPINNER[Math.abs(Number(tick) || 0) % SPINNER.length];
64
+ return ` ${pc.cyan(frame)} ${pc.bold(label)} ${pc.dim(`· ${phase}`)} ${pc.dim(`· ${elapsedLabel(elapsedMs)}`)}`;
65
+ }
66
+
67
+ export function renderRuntimeActivityPanel({
68
+ label = 'Agent Sam',
69
+ phase = 'thinking',
70
+ tick = 0,
71
+ elapsedMs = 0,
72
+ status = 'active',
73
+ locality = 'working locally',
74
+ } = {}) {
75
+ const frame = THINK_FRAMES[Math.abs(Number(tick) || 0) % THINK_FRAMES.length];
76
+ const border = status === 'success' ? pc.green : status === 'error' ? pc.red : pc.cyan;
77
+ const body = status === 'error' ? pc.red : pc.cyan;
78
+ const title = titledBorder(label, PANEL_WIDTH, '╭─', '╮');
79
+ const subtitle = status === 'active'
80
+ ? `${phase} · ${locality}`
81
+ : `${phase} · ${elapsedLabel(elapsedMs)}`;
82
+ return [
83
+ border(title),
84
+ `${border('│')}${body(center(frame[0], PANEL_INNER))}${border('│')}`,
85
+ `${border('│')}${body(center(frame[1], PANEL_INNER))}${border('│')}`,
86
+ `${border('│')}${body(center(frame[2], PANEL_INNER))}${border('│')}`,
87
+ border(subtitledBorder(subtitle, PANEL_WIDTH, '╰─', '╯')),
88
+ ].join('\n');
89
+ }
90
+
91
+ export function renderRuntimeActivityResult({
92
+ label = 'Agent Sam',
93
+ status = 'success',
94
+ phase = status === 'success' ? 'done' : 'failed',
95
+ elapsedMs = 0,
96
+ } = {}) {
97
+ const ok = status === 'success';
98
+ const symbol = ok ? pc.green('✓') : pc.red('✗');
99
+ return ` ${symbol} ${pc.bold(label)} ${pc.dim(`· ${phase}`)} ${pc.dim(`· ${elapsedLabel(elapsedMs)}`)}`;
100
+ }
101
+
102
+ /**
103
+ * Live terminal activity for product runtime operations.
104
+ *
105
+ * This is shipped product UI, not a preview renderer. It remains quiet in pipes/CI,
106
+ * and only uses cursor redraw when stdout is an interactive terminal.
107
+ */
108
+ export function createRuntimeActivity(options = {}) {
109
+ const write = options.write || process.stdout.write.bind(process.stdout);
110
+ const now = options.now || (() => Date.now());
111
+ const setTimer = options.setInterval || globalThis.setInterval;
112
+ const clearTimer = options.clearInterval || globalThis.clearInterval;
113
+ const interactive = options.interactive ?? canAnimateTerminal(options);
114
+ const intervalMs = Math.max(40, Number(options.intervalMs) || 90);
115
+ const label = String(options.label || 'Agent Sam');
116
+ const locality = String(options.locality || 'working locally');
117
+
118
+ let phase = String(options.phase || 'thinking');
119
+ let startedAt = 0;
120
+ let tick = 0;
121
+ let timer = null;
122
+ let started = false;
123
+ let finished = false;
124
+ let drawn = false;
125
+
126
+ const writePanel = (status = 'active') => {
127
+ if (!interactive || !started) return;
128
+ const elapsedMs = Math.max(0, now() - startedAt);
129
+ if (drawn) write(`\x1b[${PANEL_LINES}A`);
130
+ const panel = renderRuntimeActivityPanel({ label, phase, tick, elapsedMs, status, locality });
131
+ for (const line of panel.split('\n')) write(`\r${CLEAR_LINE}${line}\n`);
132
+ drawn = true;
133
+ tick += 1;
134
+ };
135
+
136
+ const draw = () => {
137
+ if (finished) return;
138
+ writePanel('active');
139
+ };
140
+
141
+ const start = (nextPhase = phase) => {
142
+ if (started || finished) return;
143
+ phase = String(nextPhase || phase);
144
+ started = true;
145
+ startedAt = now();
146
+ if (!interactive) return;
147
+ write(HIDE_CURSOR);
148
+ draw();
149
+ timer = setTimer(draw, intervalMs);
150
+ };
151
+
152
+ const update = (nextPhase) => {
153
+ if (nextPhase) phase = String(nextPhase);
154
+ if (!started) start(phase);
155
+ draw();
156
+ };
157
+
158
+ const finish = ({ status = 'success', resultPhase } = {}) => {
159
+ if (finished) return;
160
+ if (timer) clearTimer(timer);
161
+ timer = null;
162
+ phase = resultPhase || (status === 'success' ? 'done' : 'failed');
163
+ if (interactive && started) {
164
+ writePanel(status === 'success' ? 'success' : 'error');
165
+ write(SHOW_CURSOR);
166
+ }
167
+ finished = true;
168
+ };
169
+
170
+ return {
171
+ get interactive() { return interactive; },
172
+ start,
173
+ update,
174
+ succeed(phaseName = 'done') { finish({ status: 'success', resultPhase: phaseName }); },
175
+ fail(phaseName = 'failed') { finish({ status: 'error', resultPhase: phaseName }); },
176
+ stop() { finish({ status: 'success', resultPhase: 'done' }); },
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Maps portable runtime events onto the customer terminal without making the renderer
182
+ * the authority for execution. Hosts may feed richer telemetry into the same seam.
183
+ */
184
+ export function phaseForRuntimeEvent(event = {}) {
185
+ const type = String(event.type || '');
186
+ if (type === 'model.started' || type === 'model.delta') return 'thinking';
187
+ if (type === 'tool.started') return event.tool ? `using ${event.tool}` : 'using tool';
188
+ if (type === 'context.compaction.started') return 'tidying context';
189
+ if (type === 'context.compaction.completed') return 'context ready';
190
+ if (type === 'run.started') return 'working';
191
+ return null;
192
+ }