@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
@@ -2,11 +2,18 @@ import { spawnSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { parseWranglerVersionId } from '../lib/deploy/health.js';
5
- import { writeDeploymentReceipt, writeProductRegistryLocal, buildProductRow } from './receipts.js';
6
- import { applyGoProductRegistry } from './registry.js';
7
- import { gitEvidence } from '../knowledge/config.js';
5
+ import {
6
+ writeDeploymentReceipt,
7
+ writeDeploymentValidationReceipt,
8
+ writeProductRegistryLocal,
9
+ writeProductValidationLocal,
10
+ buildProductRow,
11
+ } from './receipts.js';
12
+ import { applyInnerAnimalMediaOfficialGoProductRegistry } from './official-registry.js';
8
13
  import { SDK_ROOT } from './discover.js';
9
14
 
15
+ const EXPECTED_HASH = '2e60bba13dc2bc37d75dd2ce5deb25466f19cb2994e20889388948879875eae9';
16
+
10
17
  function resolveWranglerInvocation(productRoot) {
11
18
  const js = path.join(productRoot, 'node_modules', 'wrangler', 'bin', 'wrangler.js');
12
19
  if (fs.existsSync(js)) return { command: process.execPath, args: [js] };
@@ -15,106 +22,461 @@ function resolveWranglerInvocation(productRoot) {
15
22
  return { command: 'npx', args: ['--yes', 'wrangler'] };
16
23
  }
17
24
 
25
+ function runWrangler(wrangler, args, {
26
+ productRoot,
27
+ spawn = spawnSync,
28
+ accountId = null,
29
+ } = {}) {
30
+ const env = { ...process.env };
31
+ if (accountId) env.CLOUDFLARE_ACCOUNT_ID = accountId;
32
+ const res = spawn(wrangler.command, wrangler.args.concat(args), {
33
+ cwd: productRoot,
34
+ encoding: 'utf8',
35
+ env,
36
+ maxBuffer: 16 * 1024 * 1024,
37
+ });
38
+ return {
39
+ status: res.status,
40
+ stdout: res.stdout || '',
41
+ stderr: res.stderr || '',
42
+ output: ((res.stdout || '') + '\n' + (res.stderr || '')).trim(),
43
+ };
44
+ }
45
+
46
+ function parseJSON(text) {
47
+ try { return JSON.parse(String(text || '').trim()); } catch { return null; }
48
+ }
49
+
50
+ function publicAccount(account) {
51
+ if (!account) return null;
52
+ return {
53
+ id: account.id || null,
54
+ name: account.name || null,
55
+ type: account.type || null,
56
+ };
57
+ }
58
+
59
+ export function resolveConfiguredDeploymentUrl(productRoot) {
60
+ const configPath = path.join(productRoot, 'wrangler.jsonc');
61
+ if (!fs.existsSync(configPath)) return null;
62
+
63
+ try {
64
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
65
+ const routes = Array.isArray(config.routes) ? config.routes : [];
66
+
67
+ const customDomain = routes.find((row) =>
68
+ row
69
+ && typeof row === 'object'
70
+ && row.custom_domain === true
71
+ && typeof row.pattern === 'string'
72
+ && row.pattern.trim()
73
+ && !row.pattern.includes('*')
74
+ );
75
+
76
+ if (!customDomain) return null;
77
+
78
+ const host = customDomain.pattern
79
+ .trim()
80
+ .replace(/^https?:\/\//i, '')
81
+ .replace(/\/.*$/, '');
82
+
83
+ return host ? `https://${host}` : null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ export function resolveWranglerIdentity(productRoot, {
90
+ requestedAccountId = null,
91
+ spawn = spawnSync,
92
+ } = {}) {
93
+ const wrangler = resolveWranglerInvocation(productRoot);
94
+ const res = runWrangler(wrangler, ['whoami', '--json'], { productRoot, spawn });
95
+ const body = parseJSON(res.stdout);
96
+ if (res.status !== 0 || body?.loggedIn !== true) {
97
+ return {
98
+ ok: false,
99
+ authenticated: false,
100
+ error: 'cloudflare_not_authenticated',
101
+ detail: res.output.slice(0, 1200),
102
+ accounts: [],
103
+ account: null,
104
+ };
105
+ }
106
+
107
+ const accounts = Array.isArray(body.accounts)
108
+ ? body.accounts.map(publicAccount).filter((row) => row.id)
109
+ : [];
110
+ if (!accounts.length) {
111
+ return {
112
+ ok: false,
113
+ authenticated: true,
114
+ auth_type: body.authType || null,
115
+ error: 'cloudflare_account_missing',
116
+ accounts: [],
117
+ account: null,
118
+ };
119
+ }
120
+
121
+ let account = null;
122
+ if (requestedAccountId) {
123
+ account = accounts.find((row) => row.id === requestedAccountId) || null;
124
+ if (!account) {
125
+ return {
126
+ ok: false,
127
+ authenticated: true,
128
+ auth_type: body.authType || null,
129
+ error: 'cloudflare_account_not_available',
130
+ requested_account_id: requestedAccountId,
131
+ accounts,
132
+ account: null,
133
+ };
134
+ }
135
+ } else if (accounts.length === 1) {
136
+ account = accounts[0];
137
+ } else {
138
+ return {
139
+ ok: false,
140
+ authenticated: true,
141
+ auth_type: body.authType || null,
142
+ error: 'cloudflare_account_ambiguous',
143
+ accounts,
144
+ account: null,
145
+ };
146
+ }
147
+
148
+ return {
149
+ ok: true,
150
+ authenticated: true,
151
+ auth_type: body.authType || null,
152
+ accounts,
153
+ account,
154
+ };
155
+ }
156
+
157
+ export function readLatestWranglerDeployment(productRoot, product, {
158
+ spawn = spawnSync,
159
+ accountId = null,
160
+ } = {}) {
161
+ const wrangler = resolveWranglerInvocation(productRoot);
162
+ const res = runWrangler(
163
+ wrangler,
164
+ ['deployments', 'list', '--name', product, '--json'],
165
+ { productRoot, spawn, accountId },
166
+ );
167
+ if (res.status !== 0) {
168
+ return { ok: false, error: 'wrangler_deployments_list_failed', detail: res.output.slice(0, 2000) };
169
+ }
170
+ const rows = parseJSON(res.stdout);
171
+ if (!Array.isArray(rows) || !rows.length) {
172
+ return { ok: false, error: 'wrangler_deployment_identity_missing', detail: res.stdout.slice(0, 2000) };
173
+ }
174
+ rows.sort((a, b) => String(b.created_on || '').localeCompare(String(a.created_on || '')));
175
+ const latest = rows[0];
176
+ const version = Array.isArray(latest.versions)
177
+ ? [...latest.versions].sort((a, b) => Number(b.percentage || 0) - Number(a.percentage || 0))[0]
178
+ : null;
179
+ return {
180
+ ok: true,
181
+ deployment_id: latest.id || null,
182
+ version_id: version?.version_id || null,
183
+ created_on: latest.created_on || null,
184
+ source: latest.source || null,
185
+ };
186
+ }
187
+
188
+ function deploymentArgs({
189
+ dryRun = false,
190
+ source = null,
191
+ builtAt = null,
192
+ } = {}) {
193
+ const args = ['deploy', '-c', 'wrangler.jsonc'];
194
+ if (source?.identity) args.push('--var', 'AGENTSAM_BUILD_SOURCE:' + source.identity);
195
+ if (source?.commit) args.push('--var', 'AGENTSAM_BUILD_COMMIT:' + source.commit);
196
+ if (builtAt) args.push('--var', 'AGENTSAM_BUILT_AT:' + builtAt);
197
+ if (dryRun) args.push('--dry-run');
198
+ return args;
199
+ }
200
+
18
201
  /**
19
- * Cloudflare adapter for Go products.
20
- * Owns Wrangler invocation + live probes. Does not own the Go build itself.
202
+ * Deploy to the explicitly resolved Wrangler/Cloudflare account.
203
+ *
204
+ * Default mode is third-party self-host:
205
+ * - no InnerAnimalMedia D1 mutation
206
+ * - state/receipts stay in the caller's AgentSam state root
207
+ *
208
+ * InnerAnimalMedia product registration requires BOTH officialRelease=true and
209
+ * AGENTSAM_INNERANIMALMEDIA_OFFICIAL_RELEASE=1.
21
210
  */
22
211
  export async function deployGoCloudflare({
23
212
  productRoot,
213
+ stateRoot = productRoot,
24
214
  product = 'agentsam-go-worker',
25
215
  dryRun = false,
26
216
  skipDeploy = false,
27
217
  skipRegistry = false,
218
+ officialRelease = false,
219
+ accountId = null,
220
+ cloudflareIdentity = null,
221
+ source = null,
222
+ builtAt = null,
223
+ artifactDigest = null,
224
+ containerDigest = null,
28
225
  spawn = spawnSync,
29
226
  fetchImpl = globalThis.fetch,
30
227
  } = {}) {
31
228
  const wranglerConfig = path.join(productRoot, 'wrangler.jsonc');
32
- if (!fs.existsSync(wranglerConfig)) throw new Error(`wrangler_config_missing: ${wranglerConfig}`);
229
+ if (!fs.existsSync(wranglerConfig)) throw new Error('wrangler_config_missing: ' + wranglerConfig);
230
+
231
+ let cfIdentity = cloudflareIdentity;
232
+ if (!skipDeploy) {
233
+ cfIdentity = cfIdentity || resolveWranglerIdentity(productRoot, {
234
+ requestedAccountId: accountId,
235
+ spawn,
236
+ });
237
+ if (!cfIdentity?.ok || !cfIdentity?.account?.id) {
238
+ const err = new Error(cfIdentity?.error || 'cloudflare_identity_unavailable');
239
+ err.code = cfIdentity?.error || 'cloudflare_identity_unavailable';
240
+ err.detail = cfIdentity || null;
241
+ err.hint = 'Authenticate Wrangler and select an explicit Cloudflare account before deploying.';
242
+ throw err;
243
+ }
244
+ if (accountId && cfIdentity.account.id !== accountId) {
245
+ throw new Error('cloudflare_account_resolution_mismatch');
246
+ }
247
+ accountId = cfIdentity.account.id;
248
+ }
33
249
 
34
250
  const docker = spawn('docker', ['info'], { encoding: 'utf8' });
35
251
  const dockerOk = docker.status === 0;
36
- if (!dockerOk && !dryRun && !skipDeploy) {
252
+ if (!dockerOk && !skipDeploy) {
37
253
  const err = new Error('docker_unavailable');
38
254
  err.detail = (docker.stderr || docker.stdout || '').trim().slice(0, 400);
39
- err.hint = 'Cloudflare Containers require a local container engine for image build. Start Docker Desktop or pass --skip-deploy / --dry-run.';
255
+ err.hint = 'Cloudflare Containers require a local container engine for image build.';
40
256
  throw err;
41
257
  }
42
258
 
43
259
  const wrangler = resolveWranglerInvocation(productRoot);
44
260
  let deployOutput = '';
45
- let versionId = null;
46
261
  let deployed = false;
262
+ let dryRunValidated = false;
263
+ let identity = { ok: false, deployment_id: null, version_id: null, created_on: null };
47
264
 
48
- if (dryRun || skipDeploy) {
49
- deployOutput = dryRun ? 'dry_run' : 'skip_deploy';
265
+ if (skipDeploy) {
266
+ deployOutput = 'skip_deploy';
267
+ } else if (dryRun) {
268
+ const res = runWrangler(
269
+ wrangler,
270
+ deploymentArgs({ dryRun: true, source, builtAt }),
271
+ { productRoot, spawn, accountId },
272
+ );
273
+ deployOutput = res.output;
274
+ if (res.status !== 0) {
275
+ const err = new Error('wrangler_dry_run_failed');
276
+ err.detail = deployOutput.slice(0, 3000);
277
+ throw err;
278
+ }
279
+ dryRunValidated = true;
50
280
  } else {
51
- const res = spawn(wrangler.command, wrangler.args.concat(['deploy', '-c', 'wrangler.jsonc']), {
52
- cwd: productRoot,
53
- encoding: 'utf8',
54
- env: { ...process.env },
55
- });
56
- deployOutput = `${res.stdout || ''}\n${res.stderr || ''}`.trim();
281
+ const res = runWrangler(
282
+ wrangler,
283
+ deploymentArgs({ source, builtAt }),
284
+ { productRoot, spawn, accountId },
285
+ );
286
+ deployOutput = res.output;
57
287
  if (res.status !== 0) {
58
288
  const err = new Error('wrangler_deploy_failed');
59
- err.detail = deployOutput.slice(0, 2000);
289
+ err.detail = deployOutput.slice(0, 3000);
60
290
  throw err;
61
291
  }
62
- versionId = parseWranglerVersionId(deployOutput);
63
292
  deployed = true;
293
+ identity = readLatestWranglerDeployment(productRoot, product, { spawn, accountId });
294
+ if (!identity.ok) {
295
+ const err = new Error(identity.error || 'wrangler_deployment_identity_missing');
296
+ err.detail = identity.detail;
297
+ throw err;
298
+ }
299
+ }
300
+
301
+ const outputVersionId = parseWranglerVersionId(deployOutput);
302
+ const versionId = identity.version_id || outputVersionId || null;
303
+ const deploymentId = identity.deployment_id || null;
304
+ const account = cfIdentity?.account || null;
305
+
306
+ // A successful remote deployment is durable evidence even if URL resolution
307
+ // or health verification fails afterward. Persist Cloudflare identity first.
308
+ if (deployed) {
309
+ writeDeploymentReceipt(stateRoot, {
310
+ product,
311
+ provider: 'cloudflare',
312
+ kind: 'worker-container',
313
+ url: null,
314
+ health: 'pending',
315
+ artifact_digest: artifactDigest,
316
+ container_image_digest: containerDigest,
317
+ worker_deployment_id: deploymentId,
318
+ worker_version_id: versionId,
319
+ deployed_at: identity.created_on || new Date().toISOString(),
320
+ source_identity: source?.identity || null,
321
+ source_commit: source?.commit || null,
322
+ source_package: source?.package_name || null,
323
+ source_package_version: source?.package_version || null,
324
+ cloudflare: account
325
+ ? {
326
+ account_id: account.id,
327
+ account_name: account.name,
328
+ auth_type: cfIdentity?.auth_type || null,
329
+ }
330
+ : null,
331
+ probes: {
332
+ skipped: true,
333
+ ok: true,
334
+ results: {},
335
+ checks: {},
336
+ probed_at: null,
337
+ },
338
+ dry_run: false,
339
+ dry_run_validated: false,
340
+ skipped_deploy: false,
341
+ official_release: Boolean(officialRelease),
342
+ registry_mode: officialRelease
343
+ ? 'inneranimalmedia_official'
344
+ : 'self_host_local',
345
+ });
64
346
  }
65
347
 
66
- const url = extractWorkersDevUrl(deployOutput) || guessWorkersDevUrl(product);
67
- const probes = url && !dryRun && !skipDeploy
68
- ? await probeGoDeployment(url, { fetchImpl })
69
- : { skipped: true, ok: Boolean(dryRun || skipDeploy), results: {} };
348
+ const url = deployed
349
+ ? (
350
+ resolveConfiguredDeploymentUrl(productRoot)
351
+ || extractWorkersDevUrl(deployOutput)
352
+ || guessWorkersDevUrl(product)
353
+ )
354
+ : null;
355
+ if (deployed && !url) {
356
+ const err = new Error('cloudflare_deployment_url_missing');
357
+ err.detail = deployOutput.slice(0, 3000);
358
+ throw err;
359
+ }
360
+
361
+ const probes = deployed
362
+ ? await probeGoDeploymentWithRetry(url, {
363
+ fetchImpl,
364
+ expectedSource: source?.identity || null,
365
+ expectedSourceCommit: source?.commit || null,
366
+ expectedTarget: 'cloudflare',
367
+ edge: true,
368
+ })
369
+ : {
370
+ skipped: true,
371
+ ok: Boolean(skipDeploy || dryRunValidated),
372
+ results: {},
373
+ checks: {},
374
+ probed_at: null,
375
+ };
70
376
 
71
- const git = gitEvidence(path.resolve(productRoot, '../..'));
72
- const health = probes.skipped
73
- ? 'pending'
74
- : (probes.ok ? 'healthy' : (deployed ? 'degraded' : 'pending'));
377
+ const health = probes.skipped ? 'pending' : (probes.ok ? 'healthy' : 'degraded');
75
378
  const receipt = {
76
379
  product,
77
380
  provider: 'cloudflare',
78
381
  kind: 'worker-container',
79
382
  url,
80
383
  health,
81
- artifact_digest: null,
384
+ artifact_digest: artifactDigest,
385
+ container_image_digest: containerDigest,
386
+ worker_deployment_id: deploymentId,
82
387
  worker_version_id: versionId,
83
- deployed_at: new Date().toISOString(),
84
- source_commit: git.commit,
388
+ deployed_at: deployed ? (identity.created_on || new Date().toISOString()) : null,
389
+ source_identity: source?.identity || null,
390
+ source_commit: source?.commit || null,
391
+ source_package: source?.package_name || null,
392
+ source_package_version: source?.package_version || null,
393
+ cloudflare: account
394
+ ? {
395
+ account_id: account.id,
396
+ account_name: account.name,
397
+ auth_type: cfIdentity?.auth_type || null,
398
+ }
399
+ : null,
85
400
  probes,
86
401
  dry_run: Boolean(dryRun),
402
+ dry_run_validated: Boolean(dryRunValidated),
87
403
  skipped_deploy: Boolean(skipDeploy),
404
+ official_release: Boolean(officialRelease),
405
+ registry_mode: officialRelease ? 'inneranimalmedia_official' : 'self_host_local',
88
406
  };
89
407
 
90
- const receiptPath = writeDeploymentReceipt(productRoot, receipt);
408
+ const receiptPath = deployed
409
+ ? writeDeploymentReceipt(stateRoot, receipt)
410
+ : writeDeploymentValidationReceipt(stateRoot, receipt);
411
+ const repositoryId = officialRelease ? 'github:samprimeaux/agentsam-sdk' : null;
91
412
  const productRow = buildProductRow({
92
413
  product,
93
- repositoryId: 'github:samprimeaux/agentsam-sdk',
94
- commit: git.commit,
414
+ repositoryId,
415
+ commit: source?.commit || null,
416
+ sourceIdentity: source?.identity || null,
417
+ packageName: source?.package_name || '@inneranimalmedia/agentsam-go-worker',
418
+ packageVersion: source?.package_version || '0.1.0',
419
+ cloudflareAccount: account
420
+ ? { id: account.id, name: account.name }
421
+ : null,
95
422
  url,
96
423
  health,
424
+ workerDeploymentId: deploymentId,
425
+ workerVersionId: versionId,
426
+ artifactDigest,
427
+ containerDigest,
97
428
  });
98
- const productPath = writeProductRegistryLocal(productRoot, productRow);
429
+ const productPath = deployed
430
+ ? writeProductRegistryLocal(stateRoot, productRow)
431
+ : writeProductValidationLocal(stateRoot, productRow);
99
432
 
100
- const registryStatus = health === 'healthy' ? 'wired' : (deployed ? 'scaffolded' : 'prototype');
101
- const registry = applyGoProductRegistry({
102
- productRoot,
103
- product,
104
- cwd: SDK_ROOT,
105
- status: registryStatus,
106
- url,
107
- commit: git.commit,
108
- health,
109
- dryRun,
110
- skipRemote: skipRegistry || skipDeploy,
111
- spawn,
112
- });
433
+ let registry = {
434
+ ok: true,
435
+ remote: false,
436
+ skipped: true,
437
+ reason: officialRelease ? 'official_release_not_eligible' : 'self_host_registry_isolated',
438
+ };
439
+
440
+ const officialEligible = officialRelease
441
+ && !skipRegistry
442
+ && !skipDeploy
443
+ && !dryRun
444
+ && health === 'healthy';
445
+
446
+ if (officialEligible) {
447
+ registry = applyInnerAnimalMediaOfficialGoProductRegistry({
448
+ productRoot: stateRoot,
449
+ product,
450
+ officialRelease: true,
451
+ cwd: SDK_ROOT,
452
+ status: 'deployed',
453
+ url,
454
+ commit: source?.commit || null,
455
+ health,
456
+ workerDeploymentId: deploymentId,
457
+ workerVersionId: versionId,
458
+ artifactDigest,
459
+ containerDigest,
460
+ dryRun: false,
461
+ skipRemote: false,
462
+ spawn,
463
+ });
464
+ } else if (officialRelease && skipRegistry) {
465
+ registry.reason = 'official_registry_explicitly_skipped';
466
+ } else if (officialRelease && dryRun) {
467
+ registry.reason = 'dry_run';
468
+ } else if (officialRelease && skipDeploy) {
469
+ registry.reason = 'skip_deploy';
470
+ } else if (officialRelease && health !== 'healthy') {
471
+ registry.reason = 'deployment_not_healthy';
472
+ }
113
473
 
114
474
  return {
115
475
  deployed,
476
+ dryRunValidated,
116
477
  url,
117
478
  versionId,
479
+ deploymentId,
118
480
  probes,
119
481
  receipt,
120
482
  receiptPath,
@@ -122,86 +484,125 @@ export async function deployGoCloudflare({
122
484
  productRow,
123
485
  registry,
124
486
  dockerOk,
487
+ cloudflare: cfIdentity,
488
+ deployOutput,
125
489
  };
126
490
  }
127
491
 
128
492
  export function extractWorkersDevUrl(output = '') {
129
- const m = String(output).match(/https:\/\/[a-z0-9.-]+\.workers\.dev[^\s]*/i);
130
- return m ? m[0].replace(/[).,]+$/, '') : null;
493
+ const match = String(output).match(/https:\/\/[a-z0-9.-]+\.workers\.dev[^\s]*/i);
494
+ return match ? match[0].replace(/[).,]+$/, '') : null;
131
495
  }
132
496
 
133
497
  export function guessWorkersDevUrl(product) {
134
498
  const account = process.env.CLOUDFLARE_ACCOUNT_SUBDOMAIN || process.env.CF_SUBDOMAIN || '';
135
- if (!account) return null;
136
- return `https://${product}.${account}.workers.dev`;
499
+ return account ? 'https://' + product + '.' + account + '.workers.dev' : null;
137
500
  }
138
501
 
139
- export async function probeGoDeployment(origin, { fetchImpl = globalThis.fetch } = {}) {
502
+ export async function probeGoDeployment(origin, {
503
+ fetchImpl = globalThis.fetch,
504
+ expectedSource = null,
505
+ expectedSourceCommit = null,
506
+ expectedTarget = null,
507
+ edge = true,
508
+ } = {}) {
140
509
  const base = String(origin).replace(/\/+$/, '');
141
510
  const results = {};
511
+ const checks = {};
512
+ const probedAt = new Date().toISOString();
142
513
 
143
- async function get(pathname) {
144
- const url = `${base}${pathname}`;
514
+ async function request(key, pathname, init = {}) {
145
515
  try {
146
- const res = await fetchImpl(url, { headers: { Accept: 'application/json' } });
516
+ const res = await fetchImpl(base + pathname, init);
147
517
  let body = null;
148
- try { body = await res.json(); } catch { body = null; }
149
- results[pathname] = { status: res.status, ok: res.status >= 200 && res.status < 300, body };
150
- return { res, body };
518
+ try { body = await res.json(); } catch {}
519
+ results[key] = {
520
+ path: pathname,
521
+ status: res.status,
522
+ ok: res.status >= 200 && res.status < 300,
523
+ body,
524
+ };
525
+ return results[key];
151
526
  } catch (error) {
152
- results[pathname] = { status: 0, ok: false, error: error.message };
153
- return { res: null, body: null };
527
+ results[key] = { path: pathname, status: 0, ok: false, error: error.message };
528
+ return results[key];
154
529
  }
155
530
  }
156
531
 
157
- async function post(pathname, payload) {
158
- const url = `${base}${pathname}`;
159
- try {
160
- const res = await fetchImpl(url, {
161
- method: 'POST',
162
- headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
163
- body: JSON.stringify(payload),
164
- });
165
- let body = null;
166
- try { body = await res.json(); } catch { body = null; }
167
- results[pathname] = { status: res.status, ok: res.status >= 200 && res.status < 300, body };
168
- return { res, body };
169
- } catch (error) {
170
- results[pathname] = { status: 0, ok: false, error: error.message };
171
- return { res: null, body: null };
172
- }
532
+ if (edge) {
533
+ await request('edge_root', '/', { headers: { Accept: 'application/json' } });
534
+ await request('edge_health', '/edge/health', { headers: { Accept: 'application/json' } });
173
535
  }
174
-
175
- await get('/health');
176
- await get('/v1/runtime');
177
- const hash = await post('/v1/hash', { input: 'agentsam', algorithm: 'sha256' });
178
- const inspect = await post('/v1/inspect', {
179
- files: [{ path: 'demo.css', content: '.button { color: #2563eb; }' }],
536
+ await request('health', '/health', { headers: { Accept: 'application/json' } });
537
+ await request('runtime', '/v1/runtime', { headers: { Accept: 'application/json' } });
538
+ await request('capabilities', '/v1/capabilities', { headers: { Accept: 'application/json' } });
539
+ await request('hash', '/v1/hash', {
540
+ method: 'POST',
541
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
542
+ body: JSON.stringify({ input: 'agentsam', algorithm: 'sha256' }),
180
543
  });
181
- // Probe malformed separately so it does not overwrite the successful /v1/inspect result.
182
- let malformed;
183
- try {
184
- const res = await fetchImpl(`${base}/v1/inspect`, {
185
- method: 'POST',
186
- headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
187
- body: JSON.stringify({ not_files: true }),
188
- });
189
- malformed = { res, body: null };
190
- try { malformed.body = await res.json(); } catch { /* ignore */ }
191
- } catch (error) {
192
- malformed = { res: null, body: null, error: error.message };
193
- }
544
+ await request('inspect', '/v1/inspect', {
545
+ method: 'POST',
546
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
547
+ body: JSON.stringify({ files: [{ path: 'demo.css', content: '.button { color: #2563eb; }' }] }),
548
+ });
549
+ await request('malformed', '/v1/inspect', {
550
+ method: 'POST',
551
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
552
+ body: JSON.stringify({ not_files: true }),
553
+ });
554
+
555
+ const health = results.health?.body;
556
+ const runtime = results.runtime?.body;
557
+ const caps = results.capabilities?.body;
558
+ const malformed = results.malformed?.body;
194
559
 
195
- const hashOk = Boolean(hash.body?.hash && String(hash.body.hash).length === 64);
196
- const inspectOk = Array.isArray(inspect.body?.findings)
197
- && inspect.body.findings.some((f) => f.kind === 'hardcoded_color' && f.value === '#2563eb');
198
- const malformedOk = Boolean(malformed.res && malformed.res.status >= 400);
560
+ if (edge) {
561
+ checks.edge_root = results.edge_root?.status === 200 && results.edge_root?.body?.edge === 'worker';
562
+ checks.edge_health = results.edge_health?.status === 200 && results.edge_health?.body?.edge === 'worker';
563
+ checks.edge_source = expectedSource
564
+ ? results.edge_root?.body?.source === expectedSource
565
+ : true;
566
+ }
567
+ checks.health = results.health?.status === 200
568
+ && health?.ok === true
569
+ && health?.runtime === 'go'
570
+ && (!expectedTarget || health?.target === expectedTarget);
571
+ checks.source_identity = expectedSource ? health?.build?.source === expectedSource : true;
572
+ checks.source_commit = expectedSourceCommit ? health?.build?.commit === expectedSourceCommit : true;
573
+ checks.runtime = results.runtime?.status === 200
574
+ && runtime?.schema === 'agentsam.go-runtime.v1'
575
+ && runtime?.os === 'linux';
576
+ checks.capabilities = results.capabilities?.status === 200
577
+ && caps?.schema === 'agentsam.go-capabilities.v1'
578
+ && ['hash', 'inspect', 'runtime', 'capabilities'].every((name) => caps?.capabilities?.includes(name));
579
+ checks.hash = results.hash?.status === 200 && results.hash?.body?.hash === EXPECTED_HASH;
580
+ checks.inspect = results.inspect?.status === 200
581
+ && results.inspect?.body?.findings?.some((finding) => finding.kind === 'hardcoded_color' && finding.value === '#2563eb');
582
+ checks.error_envelope = results.malformed?.status === 400
583
+ && malformed?.ok === false
584
+ && malformed?.schema_version === 1
585
+ && malformed?.reason === 'input_invalid'
586
+ && malformed?.code === 'INVALID_ARGUMENT';
199
587
 
200
- results['deterministic_hash'] = { ok: hashOk };
201
- results['deterministic_inspect'] = { ok: inspectOk };
202
- results['malformed_rejected'] = { ok: malformedOk };
588
+ results.deterministic_hash = { ok: checks.hash };
589
+ results.deterministic_inspect = { ok: checks.inspect };
590
+ results.malformed_rejected = { ok: checks.error_envelope };
591
+ const ok = Object.values(checks).every(Boolean);
592
+ return { origin: base, ok, checks, results, probed_at: probedAt };
593
+ }
203
594
 
204
- const required = ['/health', '/v1/runtime', 'deterministic_hash', 'deterministic_inspect', 'malformed_rejected'];
205
- const ok = required.every((key) => results[key]?.ok);
206
- return { origin: base, ok, results };
595
+ export async function probeGoDeploymentWithRetry(origin, {
596
+ attempts = 12,
597
+ delayMs = 2500,
598
+ ...options
599
+ } = {}) {
600
+ let latest = null;
601
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
602
+ latest = await probeGoDeployment(origin, options);
603
+ latest.attempt = attempt;
604
+ if (latest.ok) return latest;
605
+ if (attempt < attempts) await new Promise((resolve) => setTimeout(resolve, delayMs));
606
+ }
607
+ return latest || { origin, ok: false, checks: {}, results: {}, probed_at: new Date().toISOString() };
207
608
  }