@inneranimalmedia/agentsam-sdk 2.6.3 → 2.6.4

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 +6 -0
  2. package/README.md +16 -1
  3. package/bin/agentsam +16 -1
  4. package/docs/BRAND_INTELLIGENCE.md +1 -1
  5. package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
  6. package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
  7. package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
  8. package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
  9. package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
  10. package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
  11. package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
  12. package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
  13. package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
  14. package/docs/architecture/SAM_KERNEL.md +181 -0
  15. package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
  16. package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
  17. package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
  18. package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
  19. package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
  20. package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
  21. package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
  22. package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
  23. package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
  24. package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
  25. package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
  26. package/migrations/d1/0013_identity_oauth_states.sql +15 -0
  27. package/migrations/d1/0014_auth_event_log.sql +20 -0
  28. package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
  29. package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
  30. package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
  31. package/package.json +19 -4
  32. package/packages/agentsam-contracts/src/errors.ts +16 -0
  33. package/packages/agentsam-errors/src/envelope.js +53 -0
  34. package/packages/agentsam-errors/src/index.js +1 -0
  35. package/packages/agentsam-errors/src/recovery.js +301 -0
  36. package/packages/agentsam-knowledge/src/providers/index.js +18 -6
  37. package/packages/connectors/cloudflare/src/routes.js +9 -0
  38. package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
  39. package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
  40. package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
  41. package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
  42. package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
  43. package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
  44. package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
  45. package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
  46. package/packages/identity/package.json +2 -2
  47. package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
  48. package/packages/identity/src/adapters/sqlite/index.js +319 -0
  49. package/packages/identity/src/app/verify-app.js +95 -0
  50. package/packages/identity/src/contracts/identity-store.js +115 -0
  51. package/packages/identity/src/contracts/route-ids.js +30 -0
  52. package/packages/identity/src/contracts/route-projection.js +218 -0
  53. package/packages/identity/src/contracts/routes.js +11 -0
  54. package/packages/identity/src/core/browser-paths.js +4 -5
  55. package/packages/identity/src/core/constants.js +13 -8
  56. package/packages/identity/src/core/session-policy.js +32 -0
  57. package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
  58. package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
  59. package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
  60. package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
  61. package/packages/identity/src/index.js +25 -0
  62. package/packages/identity/src/oauth/README.md +10 -4
  63. package/packages/identity/src/oauth/credentials.js +20 -13
  64. package/packages/identity/src/oauth/finalize-inbound.js +1 -1
  65. package/packages/identity/src/oauth/iam-platform.js +8 -7
  66. package/packages/identity/src/oauth/redirect-paths.js +27 -36
  67. package/packages/identity/src/server/identity-service.js +30 -13
  68. package/packages/identity/src/server/mount-policy.js +30 -0
  69. package/packages/identity/src/server/post-auth.js +79 -0
  70. package/packages/identity/src/server/worker-router.js +104 -72
  71. package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
  72. package/packages/identity/tests/iam-provider.test.mjs +1 -1
  73. package/packages/identity/tests/identity-service.test.mjs +36 -5
  74. package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
  75. package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
  76. package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
  77. package/packages/theme-church-site/package.json +2 -1
  78. package/packages/theme-church-site/src/index.js +1 -0
  79. package/packages/theme-companions-site/package.json +2 -1
  80. package/packages/theme-companions-site/src/index.js +1 -0
  81. package/packages/theme-floors-site/package.json +2 -1
  82. package/packages/theme-floors-site/src/index.js +1 -0
  83. package/packages/theme-fuelnfree-site/package.json +2 -1
  84. package/packages/theme-fuelnfree-site/src/index.js +1 -0
  85. package/packages/theme-handyman-site/package.json +2 -1
  86. package/packages/theme-handyman-site/src/index.js +1 -0
  87. package/packages/theme-insurance-site/package.json +2 -1
  88. package/packages/theme-insurance-site/src/index.js +1 -0
  89. package/packages/theme-shinshu-site/package.json +2 -1
  90. package/packages/theme-shinshu-site/src/index.js +1 -0
  91. package/protocol/apps/agentsam.app.v1.schema.json +51 -0
  92. package/protocol/brand/brandpack.v1.schema.json +43 -0
  93. package/protocol/credentials/issue.v1.schema.json +38 -0
  94. package/protocol/database/connection.v1.schema.json +41 -0
  95. package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
  96. package/protocol/errors/error-envelope.schema.json +135 -1
  97. package/protocol/errors/recovery.v1.schema.json +50 -0
  98. package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
  99. package/protocol/sam/activity.v1.schema.json +48 -0
  100. package/protocol/sam/answer.v1.schema.json +35 -0
  101. package/protocol/sam/calibration.v1.schema.json +21 -0
  102. package/protocol/sam/decision-receipt.v1.schema.json +29 -0
  103. package/protocol/sam/evaluation.v1.schema.json +19 -0
  104. package/protocol/sam/operation.schema.json +66 -0
  105. package/protocol/sam/outcome.v1.schema.json +36 -0
  106. package/protocol/sam/question.v1.schema.json +26 -0
  107. package/protocol/sam/registry.seed.json +153 -0
  108. package/protocol/sam/result.schema.json +53 -0
  109. package/protocol/sam/state.v1.schema.json +23 -0
  110. package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
  111. package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
  112. package/protocol/ui/icon-registry.mjs +226 -0
  113. package/protocol/ui/icon.v1.schema.json +51 -0
  114. package/skills/README.md +22 -9
  115. package/skills/agentsam-codebaseindex/SKILL.md +225 -0
  116. package/skills/catalog.json +14 -0
  117. package/src/cli/command-catalog.js +130 -0
  118. package/src/cli/dispatch.js +48 -0
  119. package/src/cli.js +43 -1
  120. package/src/commands/api-key.js +244 -0
  121. package/src/commands/app.js +60 -28
  122. package/src/commands/brand.js +17 -19
  123. package/src/commands/codebaseindex.js +688 -0
  124. package/src/commands/env.js +152 -25
  125. package/src/commands/go.js +366 -53
  126. package/src/commands/interaction-clack.js +115 -0
  127. package/src/commands/models.js +1 -1
  128. package/src/commands/providers.js +62 -14
  129. package/src/commands/shell.js +43 -2
  130. package/src/commands/skill.js +248 -0
  131. package/src/commands/skills.js +1 -1
  132. package/src/commands/start-local.js +4 -0
  133. package/src/commands/whoami.js +90 -18
  134. package/src/go/build.js +229 -39
  135. package/src/go/cloudflare.js +506 -105
  136. package/src/go/container.js +120 -0
  137. package/src/go/contract.js +9 -4
  138. package/src/go/discover.js +149 -33
  139. package/src/go/index.js +15 -3
  140. package/src/go/native-probe-runner.mjs +119 -0
  141. package/src/go/{registry.js → official-registry.js} +64 -7
  142. package/src/go/official-release.js +10 -0
  143. package/src/go/receipts.js +77 -10
  144. package/src/go/verify.js +9 -2
  145. package/src/index.js +27 -0
  146. package/src/indexing/ingest/discover-models.js +298 -0
  147. package/src/indexing/ingest/inventory.js +243 -0
  148. package/src/indexing/ingest/job-graph.js +181 -0
  149. package/src/indexing/ingest/materials.js +210 -0
  150. package/src/lib/provider-credentials.js +63 -21
  151. package/src/lib/slash-commands.js +1 -0
  152. package/src/local-fs/capability.js +121 -0
  153. package/src/local-fs/freshness.js +75 -0
  154. package/src/local-fs/index.js +385 -0
  155. package/src/local-fs/paths.js +100 -0
  156. package/src/local-pty/server.js +295 -19
  157. package/src/mcp/client.js +2 -2
  158. package/src/models/ai-access-onboarding.js +112 -0
  159. package/src/models/discovery.js +10 -2
  160. package/src/models/inventory-core.js +9 -1
  161. package/src/sam/activity/index.js +183 -0
  162. package/src/sam/client.js +252 -0
  163. package/src/sam/decision/calibration.js +109 -0
  164. package/src/sam/decision/confidence.js +126 -0
  165. package/src/sam/decision/evaluate.js +157 -0
  166. package/src/sam/decision/evaluators/deterministic.js +341 -0
  167. package/src/sam/decision/evaluators/heuristic.js +61 -0
  168. package/src/sam/decision/evaluators/select.js +50 -0
  169. package/src/sam/decision/evaluators/semantic.js +149 -0
  170. package/src/sam/decision/hierarchical.js +61 -0
  171. package/src/sam/decision/index.js +53 -0
  172. package/src/sam/decision/policy.js +86 -0
  173. package/src/sam/decision/questions.js +120 -0
  174. package/src/sam/decision/receipt.js +148 -0
  175. package/src/sam/decision/state.js +117 -0
  176. package/src/sam/decision/types.js +22 -0
  177. package/src/sam/decision/validate.js +200 -0
  178. package/src/sam/define.js +51 -0
  179. package/src/sam/index.js +65 -0
  180. package/src/sam/operations/brand-scan.js +62 -0
  181. package/src/sam/operations/cad-blender-inspect.js +36 -0
  182. package/src/sam/operations/codebaseindex-ingest.js +49 -0
  183. package/src/sam/operations/decision-evaluate.js +59 -0
  184. package/src/sam/operations/planning-astar.js +77 -0
  185. package/src/sam/operations/planning-goap.js +60 -0
  186. package/src/sam/operations/repository-inspect.js +72 -0
  187. package/src/sam/operations/security-scan.js +33 -0
  188. package/src/sam/operations/terminal-exec.js +29 -0
  189. package/src/sam/planning/astar.js +311 -0
  190. package/src/sam/planning/goap.js +177 -0
  191. package/src/sam/planning/index.js +21 -0
  192. package/src/sam/planning/state.js +84 -0
  193. package/src/sam/registry.js +48 -0
  194. package/src/sam/result.js +77 -0
  195. package/src/sam/seed.js +44 -0
  196. package/src/sam/types.js +91 -0
  197. package/src/skills/catalog.js +64 -0
  198. package/src/skills/content-resolver.js +124 -0
  199. package/src/skills/hosted-store.js +37 -0
  200. package/src/skills/index.js +29 -64
  201. package/src/skills/interaction.js +102 -0
  202. package/src/skills/local-store.js +228 -0
  203. package/src/skills/manifest.js +104 -0
  204. package/src/skills/metrics.js +31 -0
  205. package/src/skills/registry.js +184 -0
  206. package/src/skills/runtime.js +287 -0
  207. package/src/skills/slash.js +44 -0
  208. package/src/ui/cli/help.js +94 -101
  209. package/test/cli/api-key-env-whoami.test.mjs +129 -0
  210. package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
  211. package/test/cli/go.test.mjs +62 -5
  212. package/test/cli/skill-npm-and-env.test.mjs +52 -0
  213. package/test/cli/wireframes-go-registry.test.mjs +87 -2
  214. package/test/go/build-source-identity.test.mjs +31 -0
  215. package/test/go/cloudflare-probe.test.mjs +274 -10
  216. package/test/go/distribution.test.mjs +28 -0
  217. package/test/integration/ai-access-onboarding.test.mjs +47 -0
  218. package/test/integration/cli-help.test.mjs +1 -1
  219. package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
  220. package/test/integration/codebaseindex-ingest.test.mjs +166 -0
  221. package/test/integration/icon-registry.test.mjs +67 -0
  222. package/test/integration/ingest-discover-models.test.mjs +30 -0
  223. package/test/integration/install-script.test.mjs +12 -9
  224. package/test/integration/local-fs.test.mjs +113 -0
  225. package/test/integration/provider-env-cli.test.mjs +2 -1
  226. package/test/integration/sam-activity-recovery.test.mjs +147 -0
  227. package/test/integration/sam-decision.test.mjs +584 -0
  228. package/test/integration/sam-kernel.test.mjs +99 -0
  229. package/test/integration/sam-planning-astar.test.mjs +279 -0
  230. package/test/integration/skill-runtime.test.mjs +240 -0
  231. package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
  232. package/test/models.test.mjs +14 -7
  233. package/test/shell.test.mjs +4 -4
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Product-path filesystem ↔ PTY proof.
3
+ *
4
+ * Exercises the same RuntimeFilesystemAdapter contract Monaco uses, plus a
5
+ * real node-pty session on the SAME authorized root, with git diff evidence.
6
+ *
7
+ * This is not a raw /v1/fs unit test substitute for browser Monaco chrome —
8
+ * it is the Monaco adapter + real PTY identity path. Browser UI automation
9
+ * is layered on top when Studio is running (see receipt).
10
+ */
11
+
12
+ import assert from 'node:assert/strict';
13
+ import { spawnSync } from 'node:child_process';
14
+ import fs from 'node:fs';
15
+ import os from 'node:os';
16
+ import path from 'node:path';
17
+ import { after, before, describe, it } from 'node:test';
18
+ import { WebSocket } from 'ws';
19
+ import { startLocalPtyServer } from '../../src/local-pty/server.js';
20
+ import { getRepositoryFreshness } from '../../src/local-fs/freshness.js';
21
+
22
+ /**
23
+ * Minimal JS twin of RuntimeFilesystemAdapter (Studio Monaco path).
24
+ */
25
+ class RuntimeFilesystemAdapter {
26
+ constructor(baseUrl, capability) {
27
+ this.baseUrl = String(baseUrl).replace(/\/$/, '');
28
+ this.capability = capability;
29
+ }
30
+
31
+ headers(json = false) {
32
+ const h = {};
33
+ if (json) h['Content-Type'] = 'application/json';
34
+ if (this.capability) {
35
+ h.Authorization = `Bearer ${this.capability}`;
36
+ h['x-agentsam-workspace-capability'] = this.capability;
37
+ }
38
+ return h;
39
+ }
40
+
41
+ async read(filePath) {
42
+ const u = new URL(`${this.baseUrl}/v1/fs/read`);
43
+ u.searchParams.set('path', filePath);
44
+ const res = await fetch(u, { headers: this.headers() });
45
+ return res.json();
46
+ }
47
+
48
+ async write(filePath, content, expectedVersion, overwrite = false) {
49
+ const res = await fetch(`${this.baseUrl}/v1/fs/write`, {
50
+ method: 'POST',
51
+ headers: this.headers(true),
52
+ body: JSON.stringify({ path: filePath, content, expectedVersion, overwrite }),
53
+ });
54
+ return res.json();
55
+ }
56
+ }
57
+
58
+ function runGit(cwd, args) {
59
+ return spawnSync('git', args, { cwd, encoding: 'utf8' });
60
+ }
61
+
62
+ function wait(ms) {
63
+ return new Promise((r) => setTimeout(r, ms));
64
+ }
65
+
66
+ /**
67
+ * Drive a real PTY over the local WS wire: send a command, collect output.
68
+ */
69
+ async function ptyExec({ url, capability, cwd, command, timeoutMs = 8000 }) {
70
+ const wsUrl = `${url}/?cwd=${encodeURIComponent(cwd)}&cols=100&rows=30&capability=${encodeURIComponent(capability)}`;
71
+ const ws = new WebSocket(wsUrl);
72
+ let buf = '';
73
+ let sessionId = null;
74
+ let identity = null;
75
+
76
+ ws.on('message', (data) => {
77
+ const text = Buffer.isBuffer(data) ? data.toString('utf8') : String(data);
78
+ if (text.startsWith('{')) {
79
+ try {
80
+ const msg = JSON.parse(text);
81
+ if (msg.type === 'session_id') sessionId = msg.session_id;
82
+ if (msg.type === 'workspace_identity') {
83
+ identity = msg;
84
+ sessionId = msg.session_id || sessionId;
85
+ }
86
+ if (msg.type === 'error') buf += `\nPTY_ERROR:${msg.code}:${msg.message}\n`;
87
+ return;
88
+ } catch {
89
+ /* fall through */
90
+ }
91
+ }
92
+ buf += text;
93
+ });
94
+
95
+ await new Promise((resolve, reject) => {
96
+ const t = setTimeout(() => reject(new Error('pty_connect_timeout')), 5000);
97
+ ws.on('open', () => {
98
+ clearTimeout(t);
99
+ resolve();
100
+ });
101
+ ws.on('error', (err) => {
102
+ clearTimeout(t);
103
+ reject(err);
104
+ });
105
+ });
106
+
107
+ // Wait for bash prompt
108
+ const promptDeadline = Date.now() + 3000;
109
+ while (Date.now() < promptDeadline) {
110
+ if (/[$#]\s*$/.test(buf.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '').trimEnd()) || buf.includes('bash')) break;
111
+ await wait(50);
112
+ }
113
+ await wait(100);
114
+ ws.send(`${command}\r`);
115
+ const deadline = Date.now() + timeoutMs;
116
+ while (Date.now() < deadline) {
117
+ await wait(150);
118
+ const plain = buf.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '');
119
+ if (plain.includes('MONACO_MARK_DONE') || plain.includes('diff --git') || plain.includes('+##')) {
120
+ break;
121
+ }
122
+ }
123
+ await wait(400);
124
+ ws.close();
125
+ return { output: buf, sessionId, identity };
126
+ }
127
+
128
+ describe('Studio filesystem ↔ real PTY product proof', () => {
129
+ /** @type {string} */
130
+ let root;
131
+ /** @type {Awaited<ReturnType<typeof startLocalPtyServer>>} */
132
+ let server;
133
+ /** @type {RuntimeFilesystemAdapter} */
134
+ let adapter;
135
+ const monacoMarker = `MONACO_MARK_${Date.now()}`;
136
+ const ptyMarker = `PTY_MARK_${Date.now()}`;
137
+
138
+ before(async () => {
139
+ root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-fs-e2e-'));
140
+ runGit(root, ['init']);
141
+ runGit(root, ['config', 'user.email', 'agentsam-e2e@local']);
142
+ runGit(root, ['config', 'user.name', 'AgentSam E2E']);
143
+ fs.writeFileSync(path.join(root, 'README.md'), '# disposable\n');
144
+ fs.writeFileSync(path.join(root, 'NOTES.md'), 'stable\n');
145
+ runGit(root, ['add', '.']);
146
+ runGit(root, ['commit', '-m', 'init']);
147
+
148
+ server = await startLocalPtyServer({
149
+ cwd: root,
150
+ port: 0,
151
+ host: '127.0.0.1',
152
+ shell: '/bin/bash',
153
+ });
154
+ assert.ok(server.capability?.capability);
155
+ assert.equal(server.cwd, path.resolve(root));
156
+
157
+ // Bootstrap as Studio would
158
+ const boot = await fetch(server.bootstrapUrl);
159
+ const claim = await boot.json();
160
+ assert.equal(claim.ok, true);
161
+ assert.equal(claim.root, path.resolve(root));
162
+ assert.equal(claim.capability, server.capability.capability);
163
+
164
+ adapter = new RuntimeFilesystemAdapter(
165
+ `http://${server.host}:${server.port}`,
166
+ claim.capability,
167
+ );
168
+ });
169
+
170
+ after(async () => {
171
+ if (server) await server.close().catch(() => {});
172
+ fs.rmSync(root, { recursive: true, force: true });
173
+ });
174
+
175
+ it('rejects FS without capability', async () => {
176
+ const res = await fetch(`http://${server.host}:${server.port}/v1/fs/read?path=README.md`);
177
+ const body = await res.json();
178
+ assert.equal(body.ok, false);
179
+ assert.equal(body.code, 'WORKSPACE_CAPABILITY_REQUIRED');
180
+ });
181
+
182
+ it('rejects arbitrary root claim', async () => {
183
+ const outside = os.tmpdir();
184
+ const res = await fetch(
185
+ `${server.bootstrapUrl}?root=${encodeURIComponent(outside)}`,
186
+ );
187
+ const body = await res.json();
188
+ assert.equal(body.ok, false);
189
+ assert.equal(body.code, 'WORKSPACE_ROOT_MISMATCH');
190
+ });
191
+
192
+ it('Monaco adapter write → real PTY git diff sees the edit', async () => {
193
+ const before = await adapter.read('README.md');
194
+ assert.equal(before.ok, true);
195
+ const next = `${before.content}\n## ${monacoMarker}\n`;
196
+ const wrote = await adapter.write('README.md', next, before.version);
197
+ assert.equal(wrote.ok, true, JSON.stringify(wrote));
198
+
199
+ // Disk truth
200
+ const disk = fs.readFileSync(path.join(root, 'README.md'), 'utf8');
201
+ assert.ok(disk.includes(monacoMarker));
202
+
203
+ // Same-root PTY git diff
204
+ const pty = await ptyExec({
205
+ url: server.url,
206
+ capability: server.capability.capability,
207
+ cwd: root,
208
+ command: 'git diff -- README.md; echo MONACO_MARK_DONE',
209
+ });
210
+ assert.ok(pty.identity, 'workspace_identity required');
211
+ assert.equal(pty.identity.root, path.resolve(root));
212
+ assert.equal(pty.identity.workspace_id, server.workspace_id);
213
+ assert.ok(pty.sessionId || pty.identity.session_id);
214
+ assert.ok(
215
+ pty.output.includes(monacoMarker) || pty.output.includes('+##'),
216
+ `git diff missing Monaco edit.\nOUTPUT:\n${pty.output}`,
217
+ );
218
+
219
+ // Persist receipt fields on the test for the final report
220
+ globalThis.__AGENTSAM_FS_E2E__ = {
221
+ root,
222
+ workspace_id: server.workspace_id,
223
+ runtimeBaseUrl: `http://${server.host}:${server.port}`,
224
+ pty_session_id: pty.sessionId || pty.identity.session_id,
225
+ monaco_path: 'README.md',
226
+ monaco_marker: monacoMarker,
227
+ git_diff_snippet: pty.output.slice(0, 1200),
228
+ identity: pty.identity,
229
+ };
230
+ });
231
+
232
+ it('PTY disk edit → adapter detects version change → stale Monaco save conflicts', async () => {
233
+ const read1 = await adapter.read('NOTES.md');
234
+ assert.equal(read1.ok, true);
235
+ const staleVersion = read1.version;
236
+
237
+ // External change (simulates PTY echo >> NOTES.md)
238
+ fs.appendFileSync(path.join(root, 'NOTES.md'), `\n${ptyMarker}\n`);
239
+
240
+ const read2 = await adapter.read('NOTES.md');
241
+ assert.equal(read2.ok, true);
242
+ assert.notEqual(read2.version, staleVersion);
243
+ assert.ok(read2.content.includes(ptyMarker));
244
+
245
+ // Stale Monaco save must conflict
246
+ const conflict = await adapter.write('NOTES.md', 'should not land\n', staleVersion, false);
247
+ assert.equal(conflict.ok, false);
248
+ assert.equal(conflict.code, 'version_conflict');
249
+
250
+ // Intentional overwrite still works
251
+ const force = await adapter.write('NOTES.md', `${read2.content}\nforced\n`, null, true);
252
+ assert.equal(force.ok, true);
253
+
254
+ globalThis.__AGENTSAM_FS_E2E__ = globalThis.__AGENTSAM_FS_E2E__ || {};
255
+ globalThis.__AGENTSAM_FS_E2E__.pty_path = 'NOTES.md';
256
+ globalThis.__AGENTSAM_FS_E2E__.pty_marker = ptyMarker;
257
+ globalThis.__AGENTSAM_FS_E2E__.stale_conflict = conflict;
258
+ globalThis.__AGENTSAM_FS_E2E__.external_version_before = staleVersion;
259
+ globalThis.__AGENTSAM_FS_E2E__.external_version_after = read2.version;
260
+ });
261
+
262
+ it('filesystem write marks repository freshness stale (path-scoped)', () => {
263
+ const fresh = getRepositoryFreshness(path.resolve(root));
264
+ assert.equal(fresh.ok, true);
265
+ assert.ok(fresh.generation >= 1);
266
+ assert.ok(fresh.stale_paths.includes('README.md') || fresh.stale_paths.includes('NOTES.md'));
267
+ });
268
+
269
+ it('records final proof receipt JSON', () => {
270
+ const receipt = {
271
+ schema: 'agentsam.fs-e2e-receipt.v1',
272
+ complete: true,
273
+ ...globalThis.__AGENTSAM_FS_E2E__,
274
+ authorization: {
275
+ binding: '127.0.0.1',
276
+ capability_required: true,
277
+ cors: 'studio-origin-allowlist',
278
+ arbitrary_root_claim: 'rejected',
279
+ },
280
+ scratch_vs_filesystem: {
281
+ filesystem: 'real FS + real PTY',
282
+ scratch: 'virtual files + virtual shell',
283
+ silent_fallback: false,
284
+ },
285
+ };
286
+ const out = path.join(root, '.agentsam-fs-e2e-receipt.json');
287
+ // root may be deleted after — write to tmp as well
288
+ const durable = path.join(os.tmpdir(), `agentsam-fs-e2e-receipt-${Date.now()}.json`);
289
+ fs.writeFileSync(durable, `${JSON.stringify(receipt, null, 2)}\n`);
290
+ assert.ok(fs.existsSync(durable));
291
+ console.log(`\nFS_E2E_RECEIPT=${durable}\n`);
292
+ globalThis.__AGENTSAM_FS_E2E_RECEIPT_PATH__ = durable;
293
+ });
294
+ });
@@ -125,7 +125,7 @@ test('Gemini and xAI discovery keep per-key limits from provider metadata', asyn
125
125
  });
126
126
 
127
127
 
128
- test('Cloudflare discovery is scoped to the loaded account and surfaces text-generation models only', async t => {
128
+ test('Cloudflare discovery is scoped to the loaded account and surfaces text-generation + embeddings', async t => {
129
129
  const seen = [];
130
130
  const home = tempHome(t);
131
131
  const status = await collectModelsStatus({
@@ -144,13 +144,18 @@ test('Cloudflare discovery is scoped to the loaded account and surfaces text-gen
144
144
  assert.equal(seen.length, 1);
145
145
  assert.match(seen[0].url, /accounts\/44444444444444444444444444444444\/ai\/models\/search$/);
146
146
  assert.equal(status.discovery.cloudflare.ok, true);
147
- assert.equal(status.discovery.cloudflare.returnedModelCount, 1);
148
- assert.deepEqual(status.providerModels.cloudflare.map((row) => row.provider_model_id), ['@cf/qwen/code']);
149
- assert.equal(status.providerModels.cloudflare[0].availability_source, 'provider_api');
150
- assert.equal(status.providerModels.cloudflare[0].context_window_source, 'unknown');
147
+ assert.equal(status.discovery.cloudflare.returnedModelCount, 2);
148
+ assert.deepEqual(
149
+ status.providerModels.cloudflare.map((row) => row.provider_model_id).sort(),
150
+ ['@cf/baai/embed', '@cf/qwen/code'],
151
+ );
152
+ const embed = status.providerModels.cloudflare.find((row) => row.provider_model_id === '@cf/baai/embed');
153
+ const qwen = status.providerModels.cloudflare.find((row) => row.provider_model_id === '@cf/qwen/code');
154
+ assert.equal(qwen.availability_source, 'provider_api');
155
+ assert.equal(qwen.context_window_source, 'unknown');
151
156
  const rendered = renderModelsStatus(status);
152
157
  assert.match(rendered, /@cf\/qwen\/code/);
153
- assert.doesNotMatch(rendered, /@cf\/baai\/embed/);
158
+ assert.match(rendered, /@cf\/baai\/embed/);
154
159
  assert.doesNotMatch(rendered, /secret-cf/);
155
160
  });
156
161
 
@@ -255,12 +260,14 @@ test('Workers AI curated allowlist intersects live Cloudflare discovery', async
255
260
  { name: '@cf/qwen/qwen2.5-coder-32b-instruct', task: { name: 'Text Generation' } },
256
261
  { name: '@cf/meta/llama-3.2-1b-instruct', task: { name: 'Text Generation' } },
257
262
  { name: '@cf/openai/gpt-oss-120b', task: { name: 'Text Generation' } },
263
+ { name: '@cf/baai/bge-base-en-v1.5', task: { name: 'Text Embeddings' } },
258
264
  ],
259
265
  }),
260
266
  });
267
+ // Chat allowlist applies to Text Generation; Text Embeddings always pass (Vectorize lane).
261
268
  assert.deepEqual(
262
269
  status.providerModels.cloudflare.map((row) => row.provider_model_id).sort(),
263
- ['@cf/openai/gpt-oss-120b', '@cf/qwen/qwen2.5-coder-32b-instruct'],
270
+ ['@cf/baai/bge-base-en-v1.5', '@cf/openai/gpt-oss-120b', '@cf/qwen/qwen2.5-coder-32b-instruct'],
264
271
  );
265
272
  assert.equal(status.discovery.cloudflare.curation, 'agentsam_workers_ai_allowlist');
266
273
  });
@@ -41,7 +41,7 @@ test('dispatch handles help, menu fallback, pwd, cd, and exit without falling th
41
41
  const state = { cwd: root, write: (text) => { output += text; }, interactive: false };
42
42
  let result = await dispatchShellLine('/help', state);
43
43
  assert.equal(result.handled, true);
44
- assert.match(output, /Type normally to work with Agent Sam/);
44
+ assert.match(output, /SAM = Systematic Autonomous Machinery/);
45
45
  assert.match(output, /agentsam help <topic>/);
46
46
  output = '';
47
47
  await dispatchShellLine('/', state);
@@ -177,7 +177,7 @@ test('bare /context shows truthful economics without inventing active token usag
177
177
  test('CLI supports a deterministic one-shot slash command for regression tests', () => {
178
178
  const result = spawnSync(process.execPath, ['src/cli.js', 'shell', '--command', '/help'], { cwd: repoRoot, encoding: 'utf8' });
179
179
  assert.equal(result.status, 0, result.stderr);
180
- assert.match(result.stdout, /Type normally to work with Agent Sam/);
180
+ assert.match(result.stdout, /SAM = Systematic Autonomous Machinery/);
181
181
  assert.match(result.stdout, /agentsam help <topic>/);
182
182
  assert.match(result.stdout, /command picker/);
183
183
  });
@@ -212,12 +212,12 @@ test('dispatchShellLine accepts "agentsam <cmd>" and bare common verbs without s
212
212
 
213
213
  const r1 = await dispatchShellLine('agentsam help', state);
214
214
  assert.equal(r1.handled, true);
215
- assert.match(output, /Type normally to work with Agent Sam/);
215
+ assert.match(output, /SAM = Systematic Autonomous Machinery/);
216
216
 
217
217
  output = '';
218
218
  const r2 = await dispatchShellLine('help', state);
219
219
  assert.equal(r2.handled, true);
220
- assert.match(output, /Type normally to work with Agent Sam/);
220
+ assert.match(output, /SAM = Systematic Autonomous Machinery/);
221
221
 
222
222
  const r3 = await dispatchShellLine('exit', state);
223
223
  assert.equal(r3.exit, true);