@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,192 @@
1
+ import pc from 'picocolors';
2
+ import { isCancel, select } from '@clack/prompts';
3
+ import pkg from '../../../package.json' with { type: 'json' };
4
+
5
+ const HELP_TOPICS = Object.freeze([
6
+ {
7
+ id: 'start',
8
+ label: 'Start / resume',
9
+ summary: 'Enter Agent Sam, resume work, inspect identity, and choose a model.',
10
+ rows: [
11
+ ['agentsam', 'Enter the interactive Agent Sam experience'],
12
+ ['agentsam resume [session]', 'Resume a saved Agent Sam session'],
13
+ ['agentsam whoami', 'Show authenticated account and credential status'],
14
+ ['agentsam models', 'Probe account-visible hosted/local models'],
15
+ ['agentsam env init <provider>', 'Create a secure local provider profile'],
16
+ ],
17
+ },
18
+ {
19
+ id: 'work',
20
+ label: 'Build / inspect',
21
+ summary: 'Repository intelligence and normal project work.',
22
+ rows: [
23
+ ['agentsam inspect', 'Bounded repository index / authority view'],
24
+ ['agentsam index', 'Incremental AST and optional embeddings'],
25
+ ['agentsam search "query"', 'Search indexed code/text'],
26
+ ['agentsam repo snapshot', 'Git composition/churn snapshot'],
27
+ ['agentsam security', 'Dependency and trust-boundary scan'],
28
+ ['agentsam merkle', 'Integrity snapshots and comparisons'],
29
+ ],
30
+ },
31
+ {
32
+ id: 'runtime',
33
+ label: 'Runtime / terminal',
34
+ summary: 'Local, remote, sandbox, and deployment controls.',
35
+ rows: [
36
+ ['agentsam status', 'Project, Git, DB, API, and PTY status'],
37
+ ['agentsam start-local', 'Start the local PTY service'],
38
+ ['agentsam connections', 'Inspect available execution connections'],
39
+ ['agentsam tunnel', 'Expose local PTY only when remote access is wanted'],
40
+ ['agentsam deploy', 'Graduate intentionally to cloud infrastructure'],
41
+ ['agentsam cloudflare', 'Wrangler and Cloudflare runtime diagnostics'],
42
+ ],
43
+ },
44
+ {
45
+ id: 'inside',
46
+ label: 'Inside Agent Sam',
47
+ summary: 'Commands available while the interactive session is running.',
48
+ rows: [
49
+ ['/', 'Open the keyboard command picker'],
50
+ ['/model', 'Choose model, reasoning, and processing tier'],
51
+ ['/context', 'Show live context economics'],
52
+ ['/usage', 'Show token/cost/session receipt'],
53
+ ['/settings', 'Change runtime, terminal, and model policy'],
54
+ ['/status', 'Show project/runtime health'],
55
+ ['/help [topic]', 'Show in-session help'],
56
+ ['/exit', 'Return to the host shell'],
57
+ ],
58
+ },
59
+ {
60
+ id: 'create',
61
+ label: 'Create / extend',
62
+ summary: 'Scaffold and add reusable capabilities.',
63
+ rows: [
64
+ ['agentsam create <name> --preset <preset>', 'Create a new AgentSam project'],
65
+ ['agentsam add <capability>', 'Add a supported capability'],
66
+ ['agentsam capabilities [id]', 'Inspect capability contracts'],
67
+ ['agentsam skills [id]', 'Inspect packaged skills'],
68
+ ['agentsam identity init', 'Add reusable identity surfaces'],
69
+ ['agentsam dockerize', 'Build supported container targets'],
70
+ ],
71
+ },
72
+ ]);
73
+
74
+ const TOPIC_ALIASES = new Map([
75
+ ['models', 'start'], ['model', 'start'], ['provider', 'start'], ['providers', 'start'], ['resume', 'start'],
76
+ ['repo', 'work'], ['repository', 'work'], ['inspect', 'work'], ['index', 'work'], ['security', 'work'],
77
+ ['terminal', 'runtime'], ['connections', 'runtime'], ['remote', 'runtime'], ['sandbox', 'runtime'], ['deploy', 'runtime'],
78
+ ['slash', 'inside'], ['commands', 'inside'], ['session', 'inside'], ['usage', 'inside'], ['context', 'inside'],
79
+ ['create', 'create'], ['scaffold', 'create'], ['capabilities', 'create'], ['skills', 'create'],
80
+ ]);
81
+
82
+ function clean(value) {
83
+ return value == null ? '' : String(value).trim().toLowerCase();
84
+ }
85
+
86
+ function padRows(rows) {
87
+ const width = Math.min(42, Math.max(...rows.map(([command]) => command.length), 0));
88
+ return rows.map(([command, description]) => ' ' + pc.cyan(command.padEnd(width)) + ' ' + pc.dim(description));
89
+ }
90
+
91
+ export function resolveHelpTopic(value) {
92
+ const query = clean(value);
93
+ if (!query) return null;
94
+ const direct = HELP_TOPICS.find((topic) => topic.id === query || clean(topic.label) === query);
95
+ if (direct) return direct;
96
+ const alias = TOPIC_ALIASES.get(query);
97
+ if (alias) return HELP_TOPICS.find((topic) => topic.id === alias) || null;
98
+ return HELP_TOPICS.find((topic) =>
99
+ topic.rows.some(([command, description]) => clean(command).includes(query) || clean(description).includes(query))) || null;
100
+ }
101
+
102
+ export function renderHelpOverview(version, options = {}) {
103
+ const lines = [
104
+ '',
105
+ ' ' + pc.bold('Agent Sam') + ' ' + pc.dim('v' + version),
106
+ ' ' + pc.dim('Type normally to work with Agent Sam. Use help only when you need the map.'),
107
+ '',
108
+ ' ' + pc.bold('Start'),
109
+ ' ' + pc.cyan('agentsam') + ' ' + pc.dim('enter the interactive experience'),
110
+ ' ' + pc.cyan('agentsam resume') + ' ' + pc.dim('continue saved work'),
111
+ ' ' + pc.cyan('agentsam help <topic>') + ' ' + pc.dim('focused help'),
112
+ '',
113
+ ' ' + pc.bold('Common'),
114
+ ' ' + pc.cyan('agentsam inspect') + ' ' + pc.dim('understand this repository'),
115
+ ' ' + pc.cyan('agentsam models') + ' ' + pc.dim('see account-visible models'),
116
+ ' ' + pc.cyan('agentsam status') + ' ' + pc.dim('check project/runtime health'),
117
+ ' ' + pc.cyan('agentsam security') + ' ' + pc.dim('scan dependency + trust boundaries'),
118
+ ' ' + pc.cyan('agentsam deploy') + ' ' + pc.dim('graduate intentionally'),
119
+ '',
120
+ ' ' + pc.dim('Inside Agent Sam: press / for the command picker. Ask the selected model for natural-language help at any time.'),
121
+ ];
122
+ if (options.showTopics !== false) {
123
+ lines.push('', ' ' + pc.dim('Topics: ' + HELP_TOPICS.map((topic) => topic.id).join(' · ') + ' · all'));
124
+ }
125
+ lines.push('');
126
+ return lines.join('\n');
127
+ }
128
+
129
+ export function renderHelpTopic(topic, version) {
130
+ if (!topic) return renderHelpOverview(version);
131
+ return [
132
+ '',
133
+ ' ' + pc.bold('Agent Sam') + ' ' + pc.dim('v' + version) + ' ' + pc.dim('·') + ' ' + pc.bold(topic.label),
134
+ ' ' + pc.dim(topic.summary),
135
+ '',
136
+ ...padRows(topic.rows),
137
+ '',
138
+ ' ' + pc.dim('Tip: run agentsam help for the map, or agentsam to return to the interactive experience.'),
139
+ '',
140
+ ].join('\n');
141
+ }
142
+
143
+ export function renderAllHelp(version) {
144
+ const lines = [renderHelpOverview(version, { showTopics: false })];
145
+ for (const topic of HELP_TOPICS) lines.push(renderHelpTopic(topic, version));
146
+ return lines.join('');
147
+ }
148
+
149
+ export async function runHelp(argv = [], options = {}) {
150
+ const version = String(options.version || pkg.version || 'unknown');
151
+ const write = options.write || ((value) => process.stdout.write(value));
152
+ const args = argv.filter((arg) => arg !== '--interactive');
153
+
154
+ if (args.includes('--all')) {
155
+ write(renderAllHelp(version));
156
+ return;
157
+ }
158
+
159
+ const topicArg = args.find((arg) => !arg.startsWith('-'));
160
+ if (topicArg) {
161
+ const topic = resolveHelpTopic(topicArg);
162
+ if (!topic) {
163
+ write(renderHelpOverview(version));
164
+ write(' ' + pc.yellow('No exact help topic matched') + ' ' + topicArg + '\n\n');
165
+ return;
166
+ }
167
+ write(renderHelpTopic(topic, version));
168
+ return;
169
+ }
170
+
171
+ const interactive = options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
172
+ if (!interactive) {
173
+ write(renderHelpOverview(version));
174
+ return;
175
+ }
176
+
177
+ const choice = await select({
178
+ message: 'Agent Sam help',
179
+ options: [
180
+ ...HELP_TOPICS.map((topic) => ({ value: topic.id, label: topic.label, hint: topic.summary })),
181
+ { value: 'all', label: 'All commands', hint: 'Print the complete deterministic help map' },
182
+ { value: 'exit', label: 'Back', hint: 'Return without printing more help' },
183
+ ],
184
+ });
185
+ if (isCancel(choice) || choice === 'exit') return;
186
+ if (choice === 'all') write(renderAllHelp(version));
187
+ else write(renderHelpTopic(resolveHelpTopic(choice), version));
188
+ }
189
+
190
+ export function listHelpTopics() {
191
+ return HELP_TOPICS.map((topic) => ({ id: topic.id, label: topic.label, summary: topic.summary }));
192
+ }
@@ -0,0 +1,20 @@
1
+ import pc from 'picocolors';
2
+
3
+ export function renderPlanUpdate(payload = {}) {
4
+ const items = Array.isArray(payload.items) ? payload.items : Array.isArray(payload.todos) ? payload.todos : [];
5
+ if (!items.length) return '';
6
+ const lines = ['', ' ' + pc.bold(payload.title || 'Plan')];
7
+ for (const item of items.slice(0, 20)) {
8
+ const status = String(item.status || 'open');
9
+ const icon = status === 'complete' || status === 'completed' || status === 'done'
10
+ ? pc.green('✓')
11
+ : status === 'running' || status === 'active'
12
+ ? pc.cyan('●')
13
+ : status === 'blocked'
14
+ ? pc.yellow('◆')
15
+ : pc.dim('○');
16
+ lines.push(` ${icon} ${item.title || item.label || item.id || 'task'}`);
17
+ }
18
+ lines.push('');
19
+ return lines.join('\n');
20
+ }
@@ -0,0 +1,110 @@
1
+ import { renderPlanUpdate } from './plan.js';
2
+ import { renderWaitingInput } from './waiting.js';
3
+ import { renderCompactionReceipt } from './compaction.js';
4
+
5
+ export const RUNTIME_EVENT_ENVELOPE_SCHEMA = 'agentsam-runtime-event-v1';
6
+
7
+ /**
8
+ * Transport-neutral event contract shared by standalone and future platform-connected runs.
9
+ * A platform SSE/WebSocket producer only needs to emit this normalized envelope; the SDK does
10
+ * not hardcode or invent the platform endpoint that will carry it.
11
+ */
12
+ export function normalizeRuntimeEventEnvelope(value = {}) {
13
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
14
+ throw new TypeError('runtime event envelope must be an object');
15
+ }
16
+ const type = String(value.type || '').trim();
17
+ if (!type) throw new TypeError('runtime event envelope type is required');
18
+ const sequence = Number(value.sequence);
19
+ const envelope = {
20
+ schema: RUNTIME_EVENT_ENVELOPE_SCHEMA,
21
+ schema_version: Number.isInteger(Number(value.schema_version)) ? Number(value.schema_version) : 1,
22
+ type,
23
+ timestamp: String(value.timestamp || new Date().toISOString()),
24
+ ...(value.run_id ? { run_id: String(value.run_id) } : {}),
25
+ ...(Number.isInteger(sequence) && sequence >= 0 ? { sequence } : {}),
26
+ payload: value.payload && typeof value.payload === 'object' && !Array.isArray(value.payload)
27
+ ? value.payload
28
+ : value.payload == null ? {} : { value: value.payload },
29
+ };
30
+ return Object.freeze(envelope);
31
+ }
32
+
33
+ function line(write, text) {
34
+ if (text) write(text.endsWith('\n') ? text : text + '\n');
35
+ }
36
+
37
+ export function createCliRuntimePresenter(options = {}) {
38
+ const activity = options.activity;
39
+ const write = options.write || process.stdout.write.bind(process.stdout);
40
+ const state = options.state || {};
41
+
42
+ function handle(event = {}) {
43
+ const envelope = normalizeRuntimeEventEnvelope(event);
44
+ const { type, payload } = envelope;
45
+
46
+ if (type === 'usage.snapshot') {
47
+ state.usageSnapshot = payload;
48
+ return;
49
+ }
50
+ if (type === 'cost.snapshot') {
51
+ state.costSnapshot = payload;
52
+ return;
53
+ }
54
+ if (type === 'model.started') {
55
+ activity?.update(`Thinking · ${payload.model || payload.provider || 'model'}`);
56
+ return;
57
+ }
58
+ if (type === 'tool.search') {
59
+ activity?.update('Finding relevant tools');
60
+ return;
61
+ }
62
+ if (type === 'tool.started') {
63
+ activity?.update(`Using ${payload.capability_id || payload.tool || 'tool'}`);
64
+ return;
65
+ }
66
+ if (type === 'tool.completed') {
67
+ activity?.update(`Tool complete · ${payload.capability_id || payload.tool || 'tool'}`);
68
+ return;
69
+ }
70
+ if (type === 'tool.failed') {
71
+ activity?.update(`Tool failed · ${payload.capability_id || payload.tool || 'tool'}`);
72
+ return;
73
+ }
74
+ if (type === 'context.compaction.started') {
75
+ state.compactionStartedAt = Date.now();
76
+ activity?.update('Compacting context');
77
+ return;
78
+ }
79
+ if (type === 'context.compaction.completed') {
80
+ const enriched = {
81
+ ...payload,
82
+ duration_ms: payload.duration_ms ?? (state.compactionStartedAt ? Date.now() - state.compactionStartedAt : undefined),
83
+ };
84
+ state.lastCompaction = enriched;
85
+ activity?.update('Context ready');
86
+ return;
87
+ }
88
+ if (type === 'plan.updated') {
89
+ activity?.clear();
90
+ line(write, renderPlanUpdate(payload));
91
+ activity?.start('Working');
92
+ return;
93
+ }
94
+ if (type === 'runtime.waiting_input') {
95
+ activity?.clear();
96
+ line(write, renderWaitingInput(payload));
97
+ return;
98
+ }
99
+ if (type === 'run.failed' || type === 'error.observed') {
100
+ activity?.update('Handling error');
101
+ }
102
+ }
103
+
104
+ function compactionReceipt() {
105
+ if (!state.lastCompaction) return '';
106
+ return renderCompactionReceipt(state.lastCompaction);
107
+ }
108
+
109
+ return Object.freeze({ handle, compactionReceipt, state });
110
+ }
@@ -0,0 +1,16 @@
1
+ import pc from 'picocolors';
2
+
3
+ export function renderWaitingInput(payload = {}) {
4
+ const reason = String(payload.reason || payload.awaiting_input_reason || 'input required');
5
+ const url = String(payload.url || payload.auth_url || '').trim();
6
+ const code = String(payload.code || payload.user_code || '').trim();
7
+ const lines = [
8
+ '',
9
+ ` ${pc.yellow('◇')} ${pc.bold('Waiting for you')} ${pc.dim('· ' + reason)}`,
10
+ ];
11
+ if (url) lines.push(` Open ${pc.cyan(url)}`);
12
+ if (code) lines.push(` Code ${pc.bold(code)}`);
13
+ lines.push(` ${pc.dim(payload.message || 'Complete the requested step; Agent Sam will continue when the runtime is ready.')}`);
14
+ lines.push('');
15
+ return lines.join('\n');
16
+ }
@@ -1,4 +1,4 @@
1
- import { comparePaths } from '../../lib/merkle/hash.js';
1
+ import { comparePaths } from '../../../packages/agentsam-repository/src/merkle/hash.js';
2
2
 
3
3
  export const safeText = (value) => String(value ?? '').replace(/[\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/g,
4
4
  (char) => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0'));
@@ -0,0 +1,36 @@
1
+ import assert from 'node:assert/strict';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import test from 'node:test';
6
+ import { accountSessionPath, clearAccountSession, readAccountSession, resolveAccountSdkKey, saveAccountSession } from '../src/lib/account-session.js';
7
+
8
+ function tempHome(t) {
9
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-account-'));
10
+ t.after(() => fs.rmSync(home, { recursive: true, force: true }));
11
+ return home;
12
+ }
13
+
14
+ test('browser-auth SDK bearer persists in machine-local storage, not project state', t => {
15
+ const home = tempHome(t);
16
+ const saved = saveAccountSession({ access_token: 'sdk_machine_session', user_id: 'au_test', account_id: 'acct_test', email: 'dev@example.test' }, { home });
17
+ assert.equal(saved.user_id, 'au_test');
18
+ const filename = accountSessionPath({ home });
19
+ assert.equal(fs.existsSync(filename), true);
20
+ if (process.platform !== 'win32') assert.equal(fs.statSync(filename).mode & 0o077, 0);
21
+
22
+ const loaded = readAccountSession({ home });
23
+ assert.equal(loaded.sdk_key, 'sdk_machine_session');
24
+ const resolved = resolveAccountSdkKey({ env: {}, home });
25
+ assert.equal(resolved.source, 'agentsam_account_session');
26
+ assert.equal(resolved.value, 'sdk_machine_session');
27
+ assert.equal(clearAccountSession({ home }), true);
28
+ assert.equal(readAccountSession({ home }), null);
29
+ });
30
+
31
+ test('explicit/environment SDK bearer remains higher authority than local session fallback', t => {
32
+ const home = tempHome(t);
33
+ saveAccountSession({ access_token: 'sdk_disk' }, { home });
34
+ assert.equal(resolveAccountSdkKey({ env: { AGENTSAM_SDK_KEY: 'sdk_env' }, home }).value, 'sdk_env');
35
+ assert.equal(resolveAccountSdkKey({ env: { AGENTSAM_SDK_KEY: 'sdk_env' }, explicit: 'sdk_explicit', home }).value, 'sdk_explicit');
36
+ });
@@ -0,0 +1,11 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { runtimeOptions } from '../../src/commands/preferences.js';
4
+
5
+ test('standalone users only see local execution', () => {
6
+ assert.deepEqual(runtimeOptions({ accountConnected: false }).map((row) => row.value), ['local']);
7
+ });
8
+
9
+ test('IAM-connected users can choose enrolled remote and sandbox lanes', () => {
10
+ assert.deepEqual(runtimeOptions({ accountConnected: true }).map((row) => row.value), ['local', 'remote', 'sandbox']);
11
+ });
@@ -0,0 +1,74 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { createInlineActivity } from '../../src/ui/cli/activity.js';
4
+ import { renderCliFooter } from '../../src/ui/cli/footer.js';
5
+ import { createCliRuntimePresenter, normalizeRuntimeEventEnvelope, RUNTIME_EVENT_ENVELOPE_SCHEMA } from '../../src/ui/cli/runtime-events.js';
6
+ import { renderWaitingInput } from '../../src/ui/cli/waiting.js';
7
+
8
+ test('CLI footer reports unknown context honestly', () => {
9
+ const text = renderCliFooter({
10
+ model: 'claude-test',
11
+ usageSnapshot: {
12
+ current_context: { input_tokens: 1234, window_tokens: 0 },
13
+ cumulative: { input_tokens: 5000, output_tokens: 600, cached_input_tokens: 2000 },
14
+ },
15
+ });
16
+ assert.match(text, /claude-test/);
17
+ assert.match(text, /ctx 1\.2k \/ unknown/);
18
+ assert.match(text, /↑5k ↓600/);
19
+ assert.match(text, /cache 2k/);
20
+ });
21
+
22
+ test('runtime event envelope is the single standalone/platform producer contract', () => {
23
+ const envelope = normalizeRuntimeEventEnvelope({
24
+ schema_version: 1,
25
+ type: 'tool.started',
26
+ timestamp: '2026-09-18T00:00:00.000Z',
27
+ run_id: 'run_1',
28
+ sequence: 3,
29
+ payload: { capability_id: 'repo.inspect' },
30
+ });
31
+ assert.equal(envelope.schema, RUNTIME_EVENT_ENVELOPE_SCHEMA);
32
+ assert.equal(envelope.schema_version, 1);
33
+ assert.equal(envelope.type, 'tool.started');
34
+ assert.equal(envelope.run_id, 'run_1');
35
+ assert.equal(envelope.sequence, 3);
36
+ assert.deepEqual(envelope.payload, { capability_id: 'repo.inspect' });
37
+ assert.throws(() => normalizeRuntimeEventEnvelope({ payload: {} }), /type is required/);
38
+ });
39
+
40
+ test('runtime presenter turns events into one-line activity and waiting handoff', () => {
41
+ let output = '';
42
+ let now = 1_000;
43
+ const timers = [];
44
+ const activity = createInlineActivity({
45
+ write: (value) => { output += value; },
46
+ interactive: false,
47
+ now: () => now,
48
+ setInterval: (fn) => { timers.push(fn); return timers.length; },
49
+ clearInterval() {},
50
+ });
51
+ const state = {};
52
+ const presenter = createCliRuntimePresenter({ activity, write: (value) => { output += value; }, state });
53
+ activity.start('Thinking');
54
+ presenter.handle({ type: 'tool.started', payload: { capability_id: 'repo.inspect' } });
55
+ presenter.handle({ type: 'usage.snapshot', payload: { current_context: { input_tokens: 10, window_tokens: 100 }, cumulative: {} } });
56
+ presenter.handle({ type: 'context.compaction.started', payload: { provider: 'test' } });
57
+ now += 500;
58
+ presenter.handle({ type: 'context.compaction.completed', payload: { provider: 'test', tokens_before: 80, tokens_after: 20, summary_text: 'kept state' } });
59
+ presenter.handle({ type: 'runtime.waiting_input', payload: { reason: 'authentication', auth_url: 'https://example.test/auth', user_code: 'ABCD' } });
60
+ assert.equal(state.usageSnapshot.current_context.input_tokens, 10);
61
+ assert.equal(state.lastCompaction.tokens_before, 80);
62
+ assert.match(output, /Waiting for you/);
63
+ assert.match(output, /https:\/\/example\.test\/auth/);
64
+ assert.match(output, /ABCD/);
65
+ assert.match(presenter.compactionReceipt(), /80 → 20/);
66
+ });
67
+
68
+ test('waiting renderer gives CLI-native auth instructions', () => {
69
+ const text = renderWaitingInput({ awaiting_input_reason: 'otp', auth_url: 'https://example.test/login', user_code: '123456' });
70
+ assert.match(text, /Waiting for you/);
71
+ assert.match(text, /otp/);
72
+ assert.match(text, /Open/);
73
+ assert.match(text, /123456/);
74
+ });
@@ -6,20 +6,41 @@ import test from 'node:test';
6
6
  import { CLI_PREFERENCES_SCHEMA, detectCliProject, readCliPreferences, writeCliPreferences } from '../src/lib/cli-preferences.js';
7
7
  import { renderBootSummary } from '../src/ui/boot.js';
8
8
 
9
- test('CLI preferences are project-local and explicitly non-authoritative for routing', () => {
9
+ test('CLI preferences persist local model/runtime controls without becoming routing authority', () => {
10
10
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-cli-prefs-'));
11
11
  fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'demo-project' }));
12
- const written = writeCliPreferences(root, { runtime: 'sandbox', terminal: 'zsh', modelPreference: 'ollama:qwen2.5-coder' });
12
+ const written = writeCliPreferences(root, {
13
+ trustedDirectory: true,
14
+ runtime: 'sandbox',
15
+ terminal: 'zsh',
16
+ modelPreference: 'openai:gpt-6-astra',
17
+ reasoningEffort: 'high',
18
+ serviceTier: 'fast',
19
+ });
13
20
  assert.equal(written.schemaVersion, CLI_PREFERENCES_SCHEMA);
14
21
  assert.equal(written.modelAuthority, 'preference-only');
22
+ assert.equal(written.trustedDirectory, true);
15
23
  assert.deepEqual(readCliPreferences(root), written);
16
-
17
24
  const identity = detectCliProject(root);
18
25
  assert.equal(identity.project, 'demo-project');
19
26
  assert.equal(identity.root, root);
20
-
21
27
  const summary = renderBootSummary({ identity, preferences: written });
22
28
  assert.match(summary, /Agent Sam/);
23
29
  assert.match(summary, /demo-project/);
24
- assert.match(summary, /qwen2\.5-coder/);
30
+ assert.match(summary, /openai:gpt-6-astra/);
31
+ assert.match(summary, /high/);
32
+ assert.match(summary, /fast/);
33
+ });
34
+
35
+ test('legacy v1 local preferences migrate in memory without inventing trust', () => {
36
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-cli-legacy-'));
37
+ fs.mkdirSync(path.join(root, '.agentsam'));
38
+ fs.writeFileSync(path.join(root, '.agentsam', 'cli.json'), JSON.stringify({
39
+ schemaVersion: 'agentsam-cli-preferences-v1', runtime: 'local', terminal: 'zsh', modelPreference: 'auto', modelAuthority: 'preference-only',
40
+ }));
41
+ const read = readCliPreferences(root);
42
+ assert.equal(read.schemaVersion, CLI_PREFERENCES_SCHEMA);
43
+ assert.equal(read.trustedDirectory, false);
44
+ assert.equal(read.reasoningEffort, 'auto');
45
+ assert.equal(read.serviceTier, 'default');
25
46
  });
@@ -0,0 +1,96 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import { handleCloudflareConnectionRequest } from '../packages/connectors/cloudflare/src/routes.js';
4
+ import { rejectUntrustedOwnerHints } from '../packages/connectors/cloudflare/src/owner.js';
5
+
6
+ function req(url, { method = 'GET', headers = {}, body } = {}) {
7
+ return new Request(url, { method, headers, body });
8
+ }
9
+
10
+ describe('cloudflare connector routes', () => {
11
+ it('rejects unauthenticated status', async () => {
12
+ const res = await handleCloudflareConnectionRequest(
13
+ req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare'),
14
+ {},
15
+ );
16
+ assert.equal(res.status, 401);
17
+ const json = await res.json();
18
+ assert.equal(json.error, 'unauthenticated');
19
+ });
20
+
21
+ it('rejects browser-submitted owner hints', () => {
22
+ const url = new URL('https://agentsam.inneranimalmedia.com/api/connections/cloudflare?account_id=evil');
23
+ assert.throws(
24
+ () => rejectUntrustedOwnerHints(req(url.toString()), url, {}),
25
+ /untrusted_owner_hint/,
26
+ );
27
+ });
28
+
29
+ it('returns 503 on start when only fixture credentials exist', async () => {
30
+ const env = {
31
+ CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
32
+ CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
33
+ sessions: new Map([['sess_1', 'user-sam']]),
34
+ };
35
+ const res = await handleCloudflareConnectionRequest(
36
+ req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare/start', {
37
+ headers: { cookie: 'agentsam_session=sess_1' },
38
+ }),
39
+ env,
40
+ );
41
+ assert.equal(res.status, 503);
42
+ const json = await res.json();
43
+ assert.equal(json.error, 'not_configured');
44
+ assert.equal(json.fixture, true);
45
+ });
46
+
47
+ it('status stays safe and does not treat fixture as production configured', async () => {
48
+ const env = {
49
+ CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
50
+ CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
51
+ sessions: new Map([['sess_1', 'user-sam']]),
52
+ };
53
+ const res = await handleCloudflareConnectionRequest(
54
+ req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare', {
55
+ headers: { authorization: 'Bearer sess_1' },
56
+ }),
57
+ env,
58
+ );
59
+ assert.equal(res.status, 200);
60
+ const json = await res.json();
61
+ assert.equal(json.ok, true);
62
+ assert.equal(json.configured, false);
63
+ assert.equal(json.fixture, true);
64
+ assert.equal(JSON.stringify(json).includes('sillynotreal-secret'), false);
65
+ });
66
+
67
+ it('rejects owner_id in the JSON body', async () => {
68
+ const env = { sessions: new Map([['sess_1', 'user-sam']]) };
69
+ const res = await handleCloudflareConnectionRequest(
70
+ req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare/disconnect', {
71
+ method: 'POST',
72
+ headers: { cookie: 'agentsam_session=sess_1', 'content-type': 'application/json' },
73
+ body: JSON.stringify({ owner_id: 'evil' }),
74
+ }),
75
+ env,
76
+ );
77
+ assert.equal(res.status, 400);
78
+ const json = await res.json();
79
+ assert.equal(json.error, 'untrusted_owner_hint');
80
+ });
81
+
82
+ it('rejects callback without a stored oauth state', async () => {
83
+ const env = {
84
+ CLOUDFLARE_OAUTH_CLIENT_ID: 'real-client-id',
85
+ CLOUDFLARE_OAUTH_CLIENT_SECRET: 'real-client-secret-value',
86
+ oauthState: new Map(),
87
+ };
88
+ const res = await handleCloudflareConnectionRequest(
89
+ req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare/callback?code=abc&state=missing'),
90
+ env,
91
+ );
92
+ assert.equal(res.status, 403);
93
+ const json = await res.json();
94
+ assert.equal(json.error, 'cloudflare_connection_forbidden');
95
+ });
96
+ });