@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.1

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 (211) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +12 -8
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/CLI_SHELL.md +163 -53
  6. package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
  7. package/docs/RELEASES.md +16 -7
  8. package/docs/SOURCE_ARCHITECTURE.md +58 -0
  9. package/docs/TEST_TIERS.md +26 -0
  10. package/migrations/runtime/0001_cli_runtime.sql +298 -0
  11. package/package.json +45 -12
  12. package/packages/agentsam-repository/README.md +15 -0
  13. package/packages/agentsam-repository/package.json +25 -0
  14. package/packages/agentsam-repository/src/contracts.js +113 -0
  15. package/packages/agentsam-repository/src/index.js +3 -0
  16. package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
  17. package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
  18. package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
  19. package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
  20. package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
  21. package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
  22. package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
  23. package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
  24. package/packages/connectors/cloudflare/package.json +10 -0
  25. package/packages/connectors/cloudflare/src/index.js +127 -0
  26. package/packages/connectors/cloudflare/src/owner.js +76 -0
  27. package/packages/connectors/cloudflare/src/routes.js +223 -0
  28. package/packages/connectors/cloudflare/src/vault.js +80 -0
  29. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  30. package/packages/identity/package.json +2 -2
  31. package/packages/identity/src/contracts/auth-config.js +18 -7
  32. package/packages/identity/tests/auth-config.test.mjs +9 -5
  33. package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
  34. package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
  35. package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
  36. package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
  37. package/protocol/README.md +1 -0
  38. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  39. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  40. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  41. package/protocol/capabilities/manifest.json +47 -0
  42. package/protocol/context/context-budget.schema.json +10 -15
  43. package/protocol/context/context-item.schema.json +4 -5
  44. package/protocol/context/resolved-context-pack.schema.json +19 -14
  45. package/protocol/models/README.md +373 -0
  46. package/protocol/models/model-inventory-v2.schema.json +212 -0
  47. package/protocol/repository/repository-contract.schema.json +24 -0
  48. package/protocol/repository/repository-dependency.schema.json +24 -0
  49. package/protocol/repository/repository-identity.schema.json +17 -0
  50. package/protocol/rpc/v1/common.proto +16 -0
  51. package/protocol/rpc/v1/errors.proto +35 -0
  52. package/protocol/rpc/v1/knowledge.proto +77 -0
  53. package/services/knowledge/package-lock.json +333 -0
  54. package/services/knowledge/package.json +5 -1
  55. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  56. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  57. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  58. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  59. package/skills/catalog.json +18 -0
  60. package/src/agent/capability-adapter.js +25 -13
  61. package/src/agent/index.js +1 -0
  62. package/src/agent/responses-runner.js +353 -0
  63. package/src/capabilities/repository-snapshot.js +3 -3
  64. package/src/cli.js +118 -31
  65. package/src/cloudflare/cpu-profile.js +115 -0
  66. package/src/cloudflare/index.js +14 -0
  67. package/src/cloudflare/wrangler.js +132 -0
  68. package/src/commands/account-auth.js +47 -0
  69. package/src/commands/cloudflare.js +58 -0
  70. package/src/commands/connections.js +93 -0
  71. package/src/commands/context-economics.js +129 -0
  72. package/src/commands/context.js +1 -1
  73. package/src/commands/db.js +20 -3
  74. package/src/commands/deploy.js +39 -3
  75. package/src/commands/env.js +90 -0
  76. package/src/commands/eval.js +63 -0
  77. package/src/commands/interactive.js +2 -5
  78. package/src/commands/knowledge.js +12 -4
  79. package/src/commands/merkle-persist.js +30 -11
  80. package/src/commands/merkle.js +1 -1
  81. package/src/commands/models.js +149 -46
  82. package/src/commands/ollama.js +26 -0
  83. package/src/commands/preferences.js +130 -61
  84. package/src/commands/resume.js +67 -0
  85. package/src/commands/security.js +5 -3
  86. package/src/commands/shell.js +568 -119
  87. package/src/commands/tunnel.js +2 -2
  88. package/src/commands/whoami.js +86 -0
  89. package/src/context/budget.js +68 -6
  90. package/src/context/index.js +3 -1
  91. package/src/context/rehydrate.js +35 -0
  92. package/src/context/resolve.js +44 -12
  93. package/src/errors/contract.js +236 -0
  94. package/src/errors/diagnostic.js +160 -0
  95. package/src/errors/index.js +23 -0
  96. package/src/eval/context.js +191 -0
  97. package/src/eval/index.js +1 -0
  98. package/src/index.js +68 -2
  99. package/src/knowledge/service/auth.js +13 -0
  100. package/src/knowledge/service/grpc-client.js +115 -0
  101. package/src/knowledge/service/grpc-codec.js +237 -0
  102. package/src/knowledge/service/grpc-server.js +83 -0
  103. package/src/knowledge/service/job-engine.js +248 -0
  104. package/src/knowledge/service/server.js +87 -135
  105. package/src/knowledge/source.js +1 -1
  106. package/src/lib/account-session.js +98 -0
  107. package/src/lib/agent-instructions.js +73 -0
  108. package/src/lib/auth.js +4 -0
  109. package/src/lib/cli-preferences.js +55 -24
  110. package/src/lib/deploy/git-guard.js +69 -0
  111. package/src/lib/deploy/health.js +57 -0
  112. package/src/lib/deploy/local-studio.js +283 -0
  113. package/src/lib/deploy/secret-scan.js +65 -0
  114. package/src/lib/deploy-receipt/index.js +2 -2
  115. package/src/lib/detect-context.js +2 -2
  116. package/src/lib/execution-approvals.js +59 -0
  117. package/src/lib/knowledge-docker.js +6 -3
  118. package/src/lib/local-sessions.js +148 -0
  119. package/src/lib/local-status.js +1 -1
  120. package/src/lib/project-config.js +1 -1
  121. package/src/lib/provider-credentials.js +183 -0
  122. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  123. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  124. package/src/lib/slash-commands.js +23 -16
  125. package/src/local/migrations.js +93 -0
  126. package/src/local/runtime-store.js +141 -0
  127. package/src/local/sqlite.js +2 -0
  128. package/src/local-pty/server.js +113 -51
  129. package/src/models/catalog.js +135 -0
  130. package/src/models/discovery.js +292 -0
  131. package/src/models/index.js +7 -0
  132. package/src/providers/anthropic-messages.js +192 -0
  133. package/src/providers/cloudflare-chat.js +183 -0
  134. package/src/providers/factory.js +69 -0
  135. package/src/providers/gemini-generate-content.js +208 -0
  136. package/src/providers/index.js +10 -0
  137. package/src/providers/ollama-chat.js +148 -0
  138. package/src/providers/openai-responses.js +426 -0
  139. package/src/repository/index.js +14 -2
  140. package/src/rpc/generated/common_grpc_pb.js +1 -0
  141. package/src/rpc/generated/common_pb.js +536 -0
  142. package/src/rpc/generated/errors_grpc_pb.js +1 -0
  143. package/src/rpc/generated/errors_pb.js +482 -0
  144. package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
  145. package/src/rpc/generated/knowledge_pb.js +2168 -0
  146. package/src/rpc/generated/package.json +3 -0
  147. package/src/security/process.js +35 -9
  148. package/src/security/trust-boundary.js +2 -2
  149. package/src/telemetry/contracts.js +203 -0
  150. package/src/telemetry/events.js +51 -0
  151. package/src/telemetry/index.js +8 -0
  152. package/src/tools/hydrate.js +35 -0
  153. package/src/tools/index.js +1 -0
  154. package/src/ui/boot.js +15 -17
  155. package/src/ui/cli/activity.js +76 -0
  156. package/src/ui/cli/compaction.js +15 -0
  157. package/src/ui/cli/footer.js +39 -0
  158. package/src/ui/cli/help.js +192 -0
  159. package/src/ui/cli/plan.js +20 -0
  160. package/src/ui/cli/runtime-events.js +110 -0
  161. package/src/ui/cli/waiting.js +16 -0
  162. package/src/ui/merkle/render.js +1 -1
  163. package/test/account-session.test.mjs +36 -0
  164. package/test/cli/preferences-runtime.test.mjs +11 -0
  165. package/test/cli/runtime-ui.test.mjs +74 -0
  166. package/test/cli-preferences.test.mjs +26 -5
  167. package/test/cloudflare-connector.test.mjs +96 -0
  168. package/test/cloudflare-runtime.test.mjs +75 -0
  169. package/test/context.test.mjs +61 -12
  170. package/test/deploy-health-scan.test.mjs +67 -0
  171. package/test/error-diagnostics.test.mjs +115 -0
  172. package/test/eval-context.test.mjs +37 -0
  173. package/test/execution-approvals.test.mjs +27 -0
  174. package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
  175. package/test/integration/cli-help.test.mjs +37 -0
  176. package/test/integration/knowledge-rpc.test.mjs +112 -0
  177. package/test/integration/merkle-cli.test.mjs +61 -0
  178. package/test/integration/merkle-persistence-identity.test.mjs +48 -0
  179. package/test/integration/provider-env-cli.test.mjs +49 -0
  180. package/test/integration/provider-factory.test.mjs +197 -0
  181. package/test/integration/repository-company-graph.test.mjs +90 -0
  182. package/test/integration/runtime-migrations.test.mjs +82 -0
  183. package/test/knowledge-service.test.mjs +5 -0
  184. package/test/knowledge.test.mjs +16 -0
  185. package/test/live/terminal-transport.live.test.mjs +24 -0
  186. package/test/local-sessions.test.mjs +48 -0
  187. package/test/local-studio-deploy.test.mjs +83 -0
  188. package/test/model-catalog.test.mjs +43 -0
  189. package/test/models.test.mjs +127 -16
  190. package/test/npm10-lock.test.mjs +29 -0
  191. package/test/ollama.test.mjs +21 -0
  192. package/test/openai-responses.test.mjs +95 -0
  193. package/test/portable-context.test.mjs +1 -1
  194. package/test/provider-credentials.test.mjs +96 -0
  195. package/test/rehydrate.test.mjs +25 -0
  196. package/test/release-hygiene.test.mjs +13 -5
  197. package/test/responses-runner.test.mjs +150 -0
  198. package/test/shell.test.mjs +92 -23
  199. package/test/smoke.mjs +4 -1
  200. package/test/telemetry.test.mjs +79 -0
  201. package/test/terminal/local-pty.mock.test.mjs +151 -0
  202. package/test/tools-search.test.mjs +14 -1
  203. package/test/whoami-resume.test.mjs +56 -0
  204. /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
  205. /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
  206. /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
  207. /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
  208. /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
  209. /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
  210. /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
  211. /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -1,31 +1,57 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { createProcessDiagnosticError } from '../errors/index.js';
2
3
 
3
4
  export function runProcess(command, args, { cwd, timeoutMs = 300_000, signal, env = process.env, maxBytes = 8 * 1024 * 1024 } = {}) {
4
5
  return new Promise((resolve, reject) => {
5
- if (signal?.aborted) return reject(new Error('Command cancelled'));
6
- const child = spawn(command, args, { cwd, env, shell: false, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'] });
6
+ const safeArgs = Array.isArray(args) ? args : [];
7
+ const diagnostic = (code, message, extra = {}) => createProcessDiagnosticError({
8
+ code,
9
+ message,
10
+ command,
11
+ args: safeArgs,
12
+ cwd,
13
+ stdout,
14
+ stderr,
15
+ ...extra,
16
+ });
7
17
  let stdout = '', stderr = '', size = 0, failure, hardKill;
18
+ if (signal?.aborted) return reject(createProcessDiagnosticError({ code: 'process_cancelled', message: 'Command cancelled', command, args: safeArgs, cwd }));
19
+ const child = spawn(command, safeArgs, { cwd, env, shell: false, detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'] });
8
20
  function kill(sig) {
9
21
  try { process.kill(process.platform === 'win32' ? child.pid : -child.pid, sig); } catch { /* already exited */ }
10
22
  }
11
- function stop(reason) {
23
+ function stop(error) {
12
24
  if (failure) return;
13
- failure = reason;
25
+ failure = error;
14
26
  kill('SIGTERM');
15
27
  hardKill = setTimeout(() => kill('SIGKILL'), 1000);
16
28
  }
17
- const timer = setTimeout(() => stop('Command timed out'), timeoutMs);
18
- const abort = () => stop('Command cancelled');
29
+ const timer = setTimeout(() => stop(diagnostic('process_timeout', 'Command timed out', { retriable: false })), timeoutMs);
30
+ const abort = () => stop(diagnostic('process_cancelled', 'Command cancelled'));
19
31
  signal?.addEventListener('abort', abort, { once: true });
20
32
  const receive = (key) => chunk => {
21
33
  size += chunk.length;
22
- if (size > maxBytes) return stop('Command output exceeded 8 MiB');
34
+ if (size > maxBytes) return stop(diagnostic('process_output_limit', `Command output exceeded ${maxBytes} bytes`));
23
35
  if (key === 'stdout') stdout += chunk.toString(); else stderr += chunk.toString();
24
36
  };
25
37
  child.stdout.on('data', receive('stdout'));
26
38
  child.stderr.on('data', receive('stderr'));
27
39
  const cleanup = () => { clearTimeout(timer); clearTimeout(hardKill); signal?.removeEventListener('abort', abort); };
28
- child.on('error', () => { cleanup(); reject(new Error('Cannot start requested command')); });
29
- child.on('close', code => { cleanup(); failure ? reject(new Error(failure)) : resolve({ code: code ?? 1, stdout, stderr }); });
40
+ child.on('error', (cause) => {
41
+ cleanup();
42
+ reject(createProcessDiagnosticError({
43
+ code: cause?.code || 'process_spawn_failed',
44
+ message: cause?.message || 'Cannot start requested command',
45
+ command,
46
+ args: safeArgs,
47
+ cwd,
48
+ cause,
49
+ }));
50
+ });
51
+ child.on('close', (code, closeSignal) => {
52
+ cleanup();
53
+ if (failure) return reject(failure);
54
+ resolve({ code: code ?? 1, signal: closeSignal || null, stdout, stderr });
55
+ });
30
56
  });
31
57
  }
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
- import { buildMerkleTree } from '../lib/merkle/index.js';
3
- import { gitIgnoredPaths } from '../lib/merkle/git-ignore.js';
2
+ import { buildMerkleTree } from '../../packages/agentsam-repository/src/merkle/index.js';
3
+ import { gitIgnoredPaths } from '../../packages/agentsam-repository/src/merkle/git-ignore.js';
4
4
  import { analyzeExecutionBoundaries } from '../indexing/execution-boundary.js';
5
5
 
6
6
  export async function scanTrustBoundary(projectRoot = process.cwd(), options = {}) {
@@ -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,51 @@
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
+ 'approval.requested', 'approval.resolved',
9
+ 'plan.updated', 'task.updated',
10
+ 'timer.started', 'timer.updated', 'timer.completed',
11
+ 'runtime.waiting_input',
12
+ ]);
13
+
14
+ function integer(value, label) {
15
+ const number = Number(value ?? 0);
16
+ if (!Number.isFinite(number) || number < 0) throw new RangeError(`${label} must be a non-negative number`);
17
+ return Math.floor(number);
18
+ }
19
+
20
+ export function createAgentEvent(type, payload = {}, options = {}) {
21
+ if (!AGENT_EVENT_TYPES.includes(type)) throw new RangeError(`unsupported AgentEvent type: ${type}`);
22
+ const event = {
23
+ schema_version: 1,
24
+ type,
25
+ timestamp: options.timestamp || new Date().toISOString(),
26
+ ...(options.runId ? { run_id: String(options.runId) } : {}),
27
+ ...(Number.isInteger(options.sequence) && options.sequence >= 0 ? { sequence: options.sequence } : {}),
28
+ payload: payload && typeof payload === 'object' ? payload : { value: payload },
29
+ };
30
+ JSON.stringify(event);
31
+ return Object.freeze(event);
32
+ }
33
+
34
+ export function createUsageSnapshot(value = {}) {
35
+ const estimateKind = value.estimate_kind === 'provider' ? 'provider' : 'local';
36
+ return Object.freeze({
37
+ current_context: Object.freeze({
38
+ input_tokens: integer(value.current_context?.input_tokens ?? value.currentContextTokens, 'current_context.input_tokens'),
39
+ window_tokens: integer(value.current_context?.window_tokens ?? value.windowTokens, 'current_context.window_tokens'),
40
+ }),
41
+ cumulative: Object.freeze({
42
+ input_tokens: integer(value.cumulative?.input_tokens ?? value.inputTokens, 'cumulative.input_tokens'),
43
+ output_tokens: integer(value.cumulative?.output_tokens ?? value.outputTokens, 'cumulative.output_tokens'),
44
+ cached_input_tokens: integer(value.cumulative?.cached_input_tokens ?? value.cachedInputTokens, 'cumulative.cached_input_tokens'),
45
+ cache_write_tokens: integer(value.cumulative?.cache_write_tokens ?? value.cacheWriteTokens, 'cumulative.cache_write_tokens'),
46
+ reasoning_tokens: integer(value.cumulative?.reasoning_tokens ?? value.reasoningTokens, 'cumulative.reasoning_tokens'),
47
+ }),
48
+ estimate_kind: estimateKind,
49
+ provider_authoritative: estimateKind === 'provider',
50
+ });
51
+ }
@@ -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
+ }
@@ -1 +1,2 @@
1
1
  export { searchToolCards, toToolCard } from './search.js';
2
+ export { hydrateToolSchemas } from './hydrate.js';
package/src/ui/boot.js CHANGED
@@ -5,7 +5,6 @@ const FRAMES = ['◔', '◑', '◕', '●'];
5
5
  const CLEAR_LINE = '\x1b[2K';
6
6
  const HIDE_CURSOR = '\x1b[?25l';
7
7
  const SHOW_CURSOR = '\x1b[?25h';
8
-
9
8
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10
9
 
11
10
  function compactHome(value) {
@@ -14,43 +13,42 @@ function compactHome(value) {
14
13
  return value;
15
14
  }
16
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
+
17
23
  export function renderBootSummary({ identity, preferences }) {
18
24
  const branch = identity.branch ? ` · ${pc.cyan(identity.branch)}` : '';
19
- const model = preferences.modelPreference && preferences.modelPreference !== 'auto'
20
- ? preferences.modelPreference
21
- : 'auto';
22
25
  const runtime = preferences.runtime || 'local';
23
26
  const terminal = preferences.terminal || path.basename(process.env.SHELL || '') || 'shell';
24
27
  return [
25
28
  '',
26
29
  ` ${pc.bold('Agent Sam')} ${pc.green('●')}`,
27
- ` ${pc.cyan(identity.project)}${branch} · ${pc.white(model)}`,
30
+ ` ${pc.cyan(identity.project)}${branch}`,
31
+ ` ${pc.white(modelLine(preferences))}`,
28
32
  ` ${pc.dim(compactHome(identity.root))}`,
29
33
  ` ${pc.green('✓')} ${pc.dim(runtime)} · ${pc.dim(terminal)} · ready`,
34
+ ` ${pc.dim('Tip: /model changes model + reasoning + processing; / shows the command menu.')}`,
30
35
  '',
31
36
  ].join('\n');
32
37
  }
33
38
 
34
39
  export async function runBootScene({ identity, preferences, animate = true, write = process.stdout.write.bind(process.stdout) }) {
35
40
  const interactive = Boolean(animate && process.stdout.isTTY);
36
- if (!interactive) {
37
- write(renderBootSummary({ identity, preferences }));
38
- return;
39
- }
40
-
41
+ if (!interactive) { write(renderBootSummary({ identity, preferences })); return; }
41
42
  write(HIDE_CURSOR);
42
43
  try {
43
- write(`\n ${pc.bold('Agent Sam')}\n ${pc.cyan(identity.project)}${identity.branch ? ` · ${pc.cyan(identity.branch)}` : ''}\n\n`);
44
- const checks = ['project', 'runtime', 'model'];
45
- for (const label of checks) {
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
46
  for (let i = 0; i < FRAMES.length; i += 1) {
47
47
  write(`\r${CLEAR_LINE} ${pc.cyan(FRAMES[i])} ${pc.dim(`checking ${label}`)}`);
48
48
  await sleep(i === FRAMES.length - 1 ? 45 : 55);
49
49
  }
50
50
  write(`\r${CLEAR_LINE} ${pc.green('✓')} ${pc.dim(label)}\n`);
51
51
  }
52
- write(`\n ${pc.green('●')} ${pc.bold('ready')} ${pc.dim(compactHome(identity.root))}\n\n`);
53
- } finally {
54
- write(SHOW_CURSOR);
55
- }
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); }
56
54
  }
@@ -0,0 +1,76 @@
1
+ import pc from 'picocolors';
2
+
3
+ const CLEAR_LINE = '\x1b[2K';
4
+ const SPINNER = ['◐', '◓', '◑', '◒'];
5
+
6
+ function elapsed(ms) {
7
+ if (ms < 1000) return `${Math.max(0, Math.round(ms))}ms`;
8
+ return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)}s`;
9
+ }
10
+
11
+ export function createInlineActivity(options = {}) {
12
+ const write = options.write || process.stdout.write.bind(process.stdout);
13
+ const interactive = options.interactive ?? Boolean(process.stdout?.isTTY);
14
+ const now = options.now || (() => Date.now());
15
+ const setTimer = options.setInterval || globalThis.setInterval;
16
+ const clearTimer = options.clearInterval || globalThis.clearInterval;
17
+ const intervalMs = Math.max(80, Number(options.intervalMs) || 120);
18
+
19
+ let label = String(options.label || 'Thinking');
20
+ let startedAt = 0;
21
+ let tick = 0;
22
+ let timer = null;
23
+ let active = false;
24
+
25
+ function frame() {
26
+ if (!active || !interactive) return;
27
+ const icon = pc.cyan(SPINNER[tick % SPINNER.length]);
28
+ write(`\r${CLEAR_LINE} ${icon} ${label} ${pc.dim('· ' + elapsed(now() - startedAt))}`);
29
+ tick += 1;
30
+ }
31
+
32
+ function start(nextLabel = label) {
33
+ if (active) {
34
+ update(nextLabel);
35
+ return;
36
+ }
37
+ label = String(nextLabel || label);
38
+ startedAt = now();
39
+ active = true;
40
+ if (interactive) {
41
+ frame();
42
+ timer = setTimer(frame, intervalMs);
43
+ }
44
+ }
45
+
46
+ function update(nextLabel) {
47
+ if (nextLabel) label = String(nextLabel);
48
+ if (!active) start(label);
49
+ else frame();
50
+ }
51
+
52
+ function finish(status = 'success', finalLabel = label) {
53
+ if (!active) return;
54
+ if (timer) clearTimer(timer);
55
+ timer = null;
56
+ const duration = elapsed(now() - startedAt);
57
+ if (interactive) write(`\r${CLEAR_LINE}`);
58
+ const icon = status === 'error' ? pc.red('✗') : pc.green('✓');
59
+ write(` ${icon} ${finalLabel} ${pc.dim('· ' + duration)}\n`);
60
+ active = false;
61
+ }
62
+
63
+ return Object.freeze({
64
+ get active() { return active; },
65
+ start,
66
+ update,
67
+ succeed(labelText) { finish('success', labelText || label); },
68
+ fail(labelText) { finish('error', labelText || 'Failed'); },
69
+ clear() {
70
+ if (timer) clearTimer(timer);
71
+ timer = null;
72
+ if (interactive && active) write(`\r${CLEAR_LINE}`);
73
+ active = false;
74
+ },
75
+ });
76
+ }
@@ -0,0 +1,15 @@
1
+ import pc from 'picocolors';
2
+
3
+ function n(value) {
4
+ const number = Number(value);
5
+ return Number.isFinite(number) ? Math.max(0, Math.round(number)).toLocaleString('en-US') : null;
6
+ }
7
+
8
+ export function renderCompactionReceipt(payload = {}) {
9
+ const before = n(payload.tokens_before ?? payload.before_tokens);
10
+ const after = n(payload.tokens_after ?? payload.after_tokens);
11
+ const duration = Number(payload.duration_ms);
12
+ const detail = before && after ? ` ${before} → ${after}` : '';
13
+ const timing = Number.isFinite(duration) ? ` · ${duration < 1000 ? Math.round(duration) + 'ms' : (duration / 1000).toFixed(1) + 's'}` : '';
14
+ return ` ${pc.green('↻')} Context compacted${pc.dim(detail + timing)}`;
15
+ }
@@ -0,0 +1,39 @@
1
+ import pc from 'picocolors';
2
+
3
+ function count(value) {
4
+ const n = Number(value || 0);
5
+ if (!Number.isFinite(n)) return '0';
6
+ if (Math.abs(n) >= 1_000_000) {
7
+ const scaled = n / 1_000_000;
8
+ return (Number.isInteger(scaled) ? String(scaled) : scaled.toFixed(Math.abs(n) >= 10_000_000 ? 0 : 1)) + 'm';
9
+ }
10
+ if (Math.abs(n) >= 1_000) {
11
+ const scaled = n / 1_000;
12
+ return (Number.isInteger(scaled) ? String(scaled) : scaled.toFixed(Math.abs(n) >= 100_000 ? 0 : 1)) + 'k';
13
+ }
14
+ return String(Math.round(n));
15
+ }
16
+
17
+ function ctxLabel(usage) {
18
+ const active = Number(usage?.current_context?.input_tokens || 0);
19
+ const window = Number(usage?.current_context?.window_tokens || 0);
20
+ if (!(window > 0)) return active > 0 ? `ctx ${count(active)} / unknown` : 'ctx unknown';
21
+ return `ctx ${Math.min(999, Math.round((active / window) * 100))}%`;
22
+ }
23
+
24
+ export function renderCliFooter(value = {}) {
25
+ const usage = value.usage || value.usageSnapshot || {};
26
+ const cumulative = usage.cumulative || {};
27
+ const model = String(value.model || value.modelLabel || 'model');
28
+ const tier = String(value.tier || '').trim();
29
+ const elapsedMs = Number(value.elapsedMs);
30
+ const parts = [
31
+ model,
32
+ ctxLabel(usage),
33
+ `↑${count(cumulative.input_tokens)} ↓${count(cumulative.output_tokens)}`,
34
+ ];
35
+ if (Number(cumulative.cached_input_tokens) > 0) parts.push(`cache ${count(cumulative.cached_input_tokens)}`);
36
+ if (tier && tier !== 'default') parts.push(tier);
37
+ if (Number.isFinite(elapsedMs) && elapsedMs >= 0) parts.push(elapsedMs < 60_000 ? `${(elapsedMs / 1000).toFixed(1)}s` : `${Math.floor(elapsedMs / 60_000)}m${Math.floor((elapsedMs % 60_000) / 1000)}s`);
38
+ return pc.dim(' ' + parts.join(' · '));
39
+ }