@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
@@ -39,6 +39,20 @@ function safeTerminalContext(value = {}) {
39
39
  };
40
40
  }
41
41
 
42
+ function capabilityProbe(credentials = []) {
43
+ const has = (provider) => credentials.some((row) => row.provider === provider && row.configured);
44
+ return {
45
+ repository: { available: true },
46
+ database: { available: true, drivers: ['sqlite'] },
47
+ vectors: { available: true, drivers: ['local_exact'] },
48
+ models: {
49
+ available: has('openai') || has('anthropic') || has('gemini') || has('xai') || has('cursor') || has('cloudflare'),
50
+ configuredProviders: ['openai', 'anthropic', 'gemini', 'xai', 'cursor', 'cloudflare'].filter((p) => has(p)).length,
51
+ },
52
+ deploy: { available: has('cloudflare') },
53
+ };
54
+ }
55
+
42
56
  export async function collectWhoami(options = {}) {
43
57
  const env = options.env || process.env;
44
58
  const apiKey = resolveAccountApiKey({ env, explicit: options.token || '', home: options.home });
@@ -57,10 +71,22 @@ export async function collectWhoami(options = {}) {
57
71
  const credentials = listProviderCredentialStatus({ env, home: options.home });
58
72
 
59
73
  const base = {
60
- schema_version: 2,
74
+ schema_version: 3,
75
+ ok: false,
76
+ command_id: 'whoami',
77
+ risk: 'read',
78
+ exit_code: 1,
79
+ format: 'json',
80
+ loggedIn: false,
61
81
  authenticated: false,
82
+ authType: null,
83
+ authLabel: null,
62
84
  authority: 'inneranimalmedia',
63
85
  identity: null,
86
+ account: null,
87
+ credential: null,
88
+ tokenPermissions: [],
89
+ capabilities: capabilityProbe(credentials),
64
90
  active_auth: {
65
91
  configured: Boolean(active.value || active.error),
66
92
  kind: active.kind || null,
@@ -92,14 +118,52 @@ export async function collectWhoami(options = {}) {
92
118
  signal: options.signal,
93
119
  }));
94
120
  const context = await loader(active.value);
121
+ const authType = context?.auth_type || (active.kind === 'api_key' ? 'api_key' : 'oauth_session');
122
+ const tokenPermissions = Array.isArray(context?.tokenPermissions)
123
+ ? context.tokenPermissions
124
+ : Array.isArray(context?.credential?.scopes)
125
+ ? context.credential.scopes
126
+ : [];
127
+ const authLabel =
128
+ authType === 'api_key'
129
+ ? 'AgentSam API Key'
130
+ : authType === 'oauth_session'
131
+ ? 'OAuth Session'
132
+ : String(authType);
133
+
95
134
  return {
96
135
  ...base,
136
+ ok: true,
137
+ exit_code: 0,
138
+ loggedIn: true,
97
139
  authenticated: true,
140
+ authType,
141
+ authLabel,
98
142
  identity: {
99
143
  user_id: context?.user_id || null,
100
144
  account_id: context?.account_id || null,
101
145
  email: context?.email || context?.user?.email || null,
102
146
  },
147
+ account: {
148
+ id: context?.account_id || context?.user_id || null,
149
+ display_name: context?.email || context?.user?.email || context?.account_id || null,
150
+ },
151
+ credential: context?.credential
152
+ ? {
153
+ id: context.credential.id || null,
154
+ name: context.credential.name || null,
155
+ prefix: context.credential.prefix || null,
156
+ environment: context.credential.environment || null,
157
+ status: context.credential.status || 'active',
158
+ created_at: context.credential.created_at || null,
159
+ expires_at: context.credential.expires_at || null,
160
+ last_used_at: context.credential.last_used_at || null,
161
+ }
162
+ : active.kind === 'api_key'
163
+ ? { id: null, name: null, prefix: null, status: 'active' }
164
+ : null,
165
+ tokenPermissions,
166
+ capabilities: capabilityProbe(credentials),
103
167
  active_auth: { ...base.active_auth, valid: true, error: null },
104
168
  api_key: active.kind === 'api_key' ? { ...base.api_key, valid: true, error: null } : base.api_key,
105
169
  cloudflare_connected: context?.cloudflare?.ok === true,
@@ -110,10 +174,6 @@ export async function collectWhoami(options = {}) {
110
174
  };
111
175
  } catch (error) {
112
176
  const rawMessage = error?.message || String(error);
113
- // error.status/endpoint come from getJson (src/lib/core-client.js) when
114
- // the failure was an HTTP response -- previously discarded, now kept so
115
- // "Unauthorized" tells you WHERE it came from instead of just that it
116
- // happened.
117
177
  const message = error?.status
118
178
  ? `${rawMessage} (HTTP ${error.status} from ${error.endpoint || 'server'})`
119
179
  : rawMessage;
@@ -127,12 +187,21 @@ export async function collectWhoami(options = {}) {
127
187
 
128
188
  export function renderWhoami(status) {
129
189
  const lines = ['', ' Agent Sam · whoami', ''];
130
- if (status.authenticated) {
190
+ if (status.authenticated || status.loggedIn) {
131
191
  lines.push(' authenticated yes');
132
- lines.push(` authority ${status.authority}`);
133
- if (status.identity?.email) lines.push(` email ${status.identity.email}`);
134
- if (status.identity?.account_id) lines.push(` account ${status.identity.account_id}`);
135
- if (status.identity?.user_id) lines.push(` user ${status.identity.user_id}`);
192
+ lines.push(` authType ${status.authLabel || status.authType || status.active_auth?.kind || 'unknown'}`);
193
+ if (status.account?.display_name || status.identity?.email) {
194
+ lines.push(` account ${status.account?.display_name || status.identity?.email}`);
195
+ }
196
+ if (status.account?.id || status.identity?.account_id) {
197
+ lines.push(` account id ${status.account?.id || status.identity?.account_id}`);
198
+ }
199
+ if (status.credential?.name || status.credential?.prefix) {
200
+ lines.push(` credential ${[status.credential.name, status.credential.prefix].filter(Boolean).join(' · ')}`);
201
+ }
202
+ if (status.tokenPermissions?.length) {
203
+ lines.push(` permissions ${status.tokenPermissions.join(', ')}`);
204
+ }
136
205
  lines.push(` active auth ${status.active_auth?.kind || 'unknown'} · ${status.active_auth?.source || 'runtime'}`);
137
206
  } else {
138
207
  lines.push(' authenticated no');
@@ -141,19 +210,22 @@ export function renderWhoami(status) {
141
210
  if (status.active_auth?.error) lines.push(` error ${status.active_auth.error}`);
142
211
  lines.push('');
143
212
  if (status.browser_session?.configured && status.active_auth?.error) {
144
- // A session file exists locally but the server rejected it on this
145
- // call -- that's not "not logged in", it's a rejected token. Say so
146
- // plainly instead of leaving the person to guess why "stored" didn't
147
- // mean "working".
148
213
  lines.push(' tip A login session is saved locally, but the server rejected it on this');
149
- lines.push(' request (see error above). This is not the same as never having logged');
150
- lines.push(' in -- run `agentsam login` again for a fresh token, or `agentsam whoami --json`');
151
- lines.push(' for the full response.');
214
+ lines.push(' request (see error above). Run `agentsam login` again, or');
215
+ lines.push(' `agentsam whoami --json` for the full response.');
152
216
  } else if (!status.api_key?.configured) {
153
- lines.push(' tip Run `agentsam login` to authenticate interactively.');
217
+ lines.push(' tip Run `agentsam login` then `agentsam api-key create --store keychain --activate`.');
154
218
  }
155
219
  }
156
220
  lines.push('');
221
+ lines.push(' Capabilities');
222
+ const caps = status.capabilities || {};
223
+ for (const [key, value] of Object.entries(caps)) {
224
+ const available = value?.available === true ? 'yes' : 'no';
225
+ const extra = value?.configuredProviders != null ? ` · providers ${value.configuredProviders}` : '';
226
+ lines.push(` ${key.padEnd(14)} ${available}${extra}`);
227
+ }
228
+ lines.push('');
157
229
  lines.push(' Provider credentials');
158
230
  for (const row of status.provider_credentials || []) {
159
231
  const state = row.configured ? 'available' : row.error ? `blocked (${row.error})` : 'not configured';
package/src/go/build.js CHANGED
@@ -1,34 +1,164 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
1
2
  import { spawnSync } from 'node:child_process';
2
3
  import fs from 'node:fs';
3
4
  import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
4
6
  import { gitEvidence } from '../knowledge/config.js';
7
+ import { tryResolveGitContext } from '../repository/index.js';
5
8
  import { writeGoBuildReceipt } from './receipts.js';
6
9
 
7
- export function runGoTests(runtimeRoot, { spawn = spawnSync } = {}) {
8
- const res = spawn('go', ['test', './...'], { cwd: runtimeRoot, encoding: 'utf8' });
10
+ const NATIVE_PROBE_RUNNER = fileURLToPath(new URL('./native-probe-runner.mjs', import.meta.url));
11
+
12
+ function ldflags(commit, source, builtAt) {
13
+ return [
14
+ '-s',
15
+ '-w',
16
+ '-X github.com/inneranimalmedia/agentsam-go-worker/internal/health.BuildCommit=' + (commit || ''),
17
+ '-X github.com/inneranimalmedia/agentsam-go-worker/internal/health.BuildSource=' + (source || ''),
18
+ '-X github.com/inneranimalmedia/agentsam-go-worker/internal/health.BuildTime=' + (builtAt || ''),
19
+ ].join(' ');
20
+ }
21
+
22
+ function readPackage(productRoot) {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(path.join(productRoot, 'package.json'), 'utf8'));
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ export function sourceTreeDigest(runtimeRoot) {
31
+ const files = [];
32
+ const walk = (dir) => {
33
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
34
+ if (entry.name.startsWith('.agentsam-build') || entry.name === '.agentsam-built-at') continue;
35
+ const full = path.join(dir, entry.name);
36
+ if (entry.isDirectory()) walk(full);
37
+ else if (entry.name === 'go.mod' || entry.name.endsWith('.go')) files.push(full);
38
+ }
39
+ };
40
+ walk(runtimeRoot);
41
+ files.sort();
42
+ const hash = createHash('sha256');
43
+ for (const file of files) {
44
+ hash.update(path.relative(runtimeRoot, file));
45
+ hash.update('\0');
46
+ hash.update(fs.readFileSync(file));
47
+ hash.update('\0');
48
+ }
49
+ return 'sha256:' + hash.digest('hex');
50
+ }
51
+
52
+ export function repositoryIdFromGitContext(context = null) {
53
+ if (!context?.repoFullName) return null;
54
+
55
+ const host = String(context.remoteHost || '').trim().toLowerCase();
56
+ const repoFullName = String(context.repoFullName).trim();
57
+
58
+ if (!repoFullName) return null;
59
+
60
+ // GitHub repository identity is canonical and case-insensitive.
61
+ if (host === 'github.com') {
62
+ return `github:${repoFullName.toLowerCase()}`;
63
+ }
64
+
65
+ // Preserve provider-qualified identity for non-GitHub remotes.
66
+ return host ? `${host}:${repoFullName}` : `git:${repoFullName}`;
67
+ }
68
+
69
+ export function resolveBuildSource({ productRoot, runtimeRoot, repositoryRoot } = {}) {
70
+ const git = gitEvidence(repositoryRoot || productRoot);
71
+ const pkg = readPackage(productRoot);
72
+ const treeDigest = sourceTreeDigest(runtimeRoot);
73
+ const repositoryContext = tryResolveGitContext({
74
+ cwd: repositoryRoot || productRoot,
75
+ });
76
+ let identity = null;
77
+ if (git.commit) identity = 'git:' + git.commit;
78
+ else if (pkg?.name && pkg?.version) identity = 'npm:' + pkg.name + '@' + pkg.version;
79
+ else identity = 'tree:' + treeDigest;
80
+
9
81
  return {
10
- ok: res.status === 0,
11
- status: res.status,
12
- stdout: res.stdout || '',
13
- stderr: res.stderr || '',
82
+ identity,
83
+ commit: git.commit || null,
84
+ branch: git.branch || null,
85
+ dirty: Boolean(git.dirty),
86
+ package_name: pkg?.name || null,
87
+ package_version: pkg?.version || null,
88
+ tree_digest: treeDigest,
89
+ repository_id: git.commit ? repositoryIdFromGitContext(repositoryContext) : null,
90
+ };
91
+ }
92
+
93
+ export function portableReceiptPath(value, {
94
+ repositoryRoot = null,
95
+ stateRoot = null,
96
+ productRoot = null,
97
+ packageName = null,
98
+ } = {}) {
99
+ if (!value) return null;
100
+
101
+ const absolute = path.resolve(value);
102
+
103
+ const candidate = (root) => {
104
+ if (!root) return null;
105
+ const rel = path.relative(path.resolve(root), absolute);
106
+ if (!rel || rel === '.') return '.';
107
+ if (rel.startsWith('..' + path.sep) || rel === '..' || path.isAbsolute(rel)) {
108
+ return null;
109
+ }
110
+ return rel.split(path.sep).join('/');
14
111
  };
112
+
113
+ const repoRelative = candidate(repositoryRoot);
114
+ if (repoRelative) return repoRelative;
115
+
116
+ const stateRelative = candidate(stateRoot);
117
+ if (stateRelative) return stateRelative;
118
+
119
+ const productRelative = candidate(productRoot);
120
+ if (productRelative) {
121
+ const prefix = packageName
122
+ ? `package:${packageName}`
123
+ : 'package';
124
+ return `${prefix}/${productRelative}`;
125
+ }
126
+
127
+ // Never put an arbitrary absolute user path into a portable receipt.
128
+ return path.basename(absolute);
129
+ }
130
+
131
+ export function runGoTests(runtimeRoot, { spawn = spawnSync } = {}) {
132
+ const res = spawn('go', ['test', './...'], { cwd: runtimeRoot, encoding: 'utf8' });
133
+ return { ok: res.status === 0, status: res.status, stdout: res.stdout || '', stderr: res.stderr || '' };
15
134
  }
16
135
 
17
136
  export function runGoVet(runtimeRoot, { spawn = spawnSync } = {}) {
18
137
  const res = spawn('go', ['vet', './...'], { cwd: runtimeRoot, encoding: 'utf8' });
19
- return {
20
- ok: res.status === 0,
21
- status: res.status,
22
- stdout: res.stdout || '',
23
- stderr: res.stderr || '',
24
- };
138
+ return { ok: res.status === 0, status: res.status, stdout: res.stdout || '', stderr: res.stderr || '' };
25
139
  }
26
140
 
27
- export function runGoBuild(runtimeRoot, { outDir = null, spawn = spawnSync } = {}) {
141
+ export function runGoBuild(runtimeRoot, {
142
+ outDir = null,
143
+ commit = '',
144
+ source = '',
145
+ builtAt = '',
146
+ spawn = spawnSync,
147
+ } = {}) {
28
148
  const binDir = outDir || path.join(runtimeRoot, '..', '.agentsam', 'go-build');
29
149
  fs.mkdirSync(binDir, { recursive: true });
30
- const out = path.join(binDir, 'agentsam-go-worker');
31
- const res = spawn('go', ['build', '-o', out, './cmd/server'], { cwd: runtimeRoot, encoding: 'utf8' });
150
+ const identityHash = createHash('sha256').update(source || commit || 'dev').digest('hex').slice(0, 12);
151
+ const suffix = identityHash + '-' + process.pid + '-' + randomBytes(3).toString('hex');
152
+ const out = path.join(binDir, 'agentsam-go-worker-' + suffix);
153
+ const res = spawn('go', [
154
+ 'build',
155
+ '-trimpath',
156
+ '-ldflags',
157
+ ldflags(commit, source, builtAt),
158
+ '-o',
159
+ out,
160
+ './cmd/server',
161
+ ], { cwd: runtimeRoot, encoding: 'utf8' });
32
162
  return {
33
163
  ok: res.status === 0,
34
164
  status: res.status,
@@ -38,39 +168,96 @@ export function runGoBuild(runtimeRoot, { outDir = null, spawn = spawnSync } = {
38
168
  };
39
169
  }
40
170
 
171
+ export function sha256File(file) {
172
+ const hash = createHash('sha256');
173
+ hash.update(fs.readFileSync(file));
174
+ return 'sha256:' + hash.digest('hex');
175
+ }
176
+
177
+ export function runNativeRuntimeProbe(binary, {
178
+ expectedSource = '',
179
+ expectedCommit = '',
180
+ spawn = spawnSync,
181
+ } = {}) {
182
+ if (!binary || !fs.existsSync(binary)) return { ok: false, error: 'binary_missing', results: {} };
183
+ const res = spawn(process.execPath, [NATIVE_PROBE_RUNNER, binary, expectedSource || '', expectedCommit || ''], {
184
+ encoding: 'utf8',
185
+ maxBuffer: 4 * 1024 * 1024,
186
+ });
187
+ let parsed = null;
188
+ try { parsed = JSON.parse(res.stdout || '{}'); } catch {}
189
+ return {
190
+ ok: res.status === 0 && parsed?.ok === true,
191
+ status: res.status,
192
+ ...(parsed || {}),
193
+ stdout: parsed ? '' : (res.stdout || ''),
194
+ stderr: ((parsed?.stderr || '') + (res.stderr || '')).trim(),
195
+ };
196
+ }
197
+
41
198
  /**
42
- * Build + test the discovered Go runtime and emit a build receipt.
199
+ * Build + test + boot the discovered Go runtime and emit deployment-grade
200
+ * source/artifact evidence. Git is authoritative in a maintainer checkout;
201
+ * an immutable npm package name/version is authoritative in distribution mode.
43
202
  */
44
- export function buildGoProduct({ productRoot, runtimeRoot, repositoryRoot, dryRun = false } = {}) {
203
+ export function buildGoProduct({
204
+ productRoot,
205
+ runtimeRoot,
206
+ repositoryRoot,
207
+ stateRoot = productRoot,
208
+ dryRun = false,
209
+ } = {}) {
45
210
  if (!runtimeRoot || !fs.existsSync(path.join(runtimeRoot, 'go.mod'))) {
46
211
  throw new Error('go_runtime_missing');
47
212
  }
213
+
214
+ const source = resolveBuildSource({ productRoot, runtimeRoot, repositoryRoot });
215
+ const builtAt = new Date().toISOString();
216
+
48
217
  const tests = dryRun ? { ok: true, skipped: true } : runGoTests(runtimeRoot);
49
218
  if (!tests.ok) {
50
219
  const err = new Error('go_test_failed');
51
220
  err.detail = tests;
52
221
  throw err;
53
222
  }
223
+
54
224
  const vet = dryRun ? { ok: true, skipped: true } : runGoVet(runtimeRoot);
55
225
  if (!vet.ok) {
56
226
  const err = new Error('go_vet_failed');
57
227
  err.detail = vet;
58
228
  throw err;
59
229
  }
60
- const build = dryRun ? { ok: true, skipped: true, binary: null } : runGoBuild(runtimeRoot);
230
+
231
+ const build = dryRun
232
+ ? { ok: true, skipped: true, binary: null }
233
+ : runGoBuild(runtimeRoot, {
234
+ outDir: path.join(stateRoot, '.agentsam', 'go-build'),
235
+ commit: source.commit,
236
+ source: source.identity,
237
+ builtAt,
238
+ });
61
239
  if (!build.ok) {
62
240
  const err = new Error('go_build_failed');
63
241
  err.detail = build;
64
242
  throw err;
65
243
  }
66
244
 
67
- const git = gitEvidence(repositoryRoot || productRoot);
245
+ const probe = dryRun
246
+ ? { ok: true, skipped: true, results: {} }
247
+ : runNativeRuntimeProbe(build.binary, {
248
+ expectedSource: source.identity,
249
+ expectedCommit: source.commit,
250
+ });
251
+ if (!probe.ok) {
252
+ const err = new Error('go_runtime_probe_failed');
253
+ err.detail = probe;
254
+ throw err;
255
+ }
256
+
68
257
  let goVersion = null;
69
258
  try {
70
259
  goVersion = spawnSync('go', ['env', 'GOVERSION'], { encoding: 'utf8' }).stdout?.trim() || null;
71
- } catch {
72
- goVersion = null;
73
- }
260
+ } catch {}
74
261
 
75
262
  const receipt = {
76
263
  schema: 'agentsam.go-build-receipt.v1',
@@ -78,31 +265,34 @@ export function buildGoProduct({ productRoot, runtimeRoot, repositoryRoot, dryRu
78
265
  runtime: {
79
266
  language: 'go',
80
267
  version: goVersion,
81
- module_root: runtimeRoot,
82
- },
83
- source: {
84
- repository_id: path.basename(repositoryRoot || productRoot),
85
- commit: git.commit,
86
- branch: git.branch,
87
- dirty: git.dirty,
268
+ module_root: portableReceiptPath(runtimeRoot, {
269
+ repositoryRoot,
270
+ stateRoot,
271
+ productRoot,
272
+ packageName: source.package_name,
273
+ }),
88
274
  },
275
+ source,
89
276
  artifact: {
90
277
  type: build.binary ? 'binary' : 'none',
91
- path: build.binary,
92
- digest: null,
278
+ path: portableReceiptPath(build.binary, {
279
+ repositoryRoot,
280
+ stateRoot,
281
+ productRoot,
282
+ packageName: source.package_name,
283
+ }),
284
+ digest: build.binary ? sha256File(build.binary) : null,
93
285
  },
94
286
  tests: {
95
287
  go_test: Boolean(tests.ok),
96
288
  go_vet: Boolean(vet.ok),
97
- runtime_probe: false,
98
- },
99
- target: {
100
- provider: 'local',
101
- kind: 'build',
289
+ runtime_probe: Boolean(probe.ok),
102
290
  },
103
- built_at: new Date().toISOString(),
291
+ probe,
292
+ target: { provider: 'local', kind: 'build' },
293
+ built_at: builtAt,
104
294
  };
105
295
 
106
- const receiptPath = writeGoBuildReceipt(productRoot, receipt);
107
- return { receipt, receiptPath, tests, vet, build };
296
+ const receiptPath = writeGoBuildReceipt(stateRoot, receipt);
297
+ return { receipt, receiptPath, tests, vet, build, probe, source };
108
298
  }