@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.
- package/AGENTSAM.md +6 -0
- package/README.md +16 -1
- package/bin/agentsam +16 -1
- package/docs/BRAND_INTELLIGENCE.md +1 -1
- package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
- package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
- package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
- package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
- package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
- package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
- package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
- package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
- package/docs/architecture/SAM_KERNEL.md +181 -0
- package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
- package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
- package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
- package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
- package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
- package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
- package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
- package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
- package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
- package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
- package/migrations/d1/0013_identity_oauth_states.sql +15 -0
- package/migrations/d1/0014_auth_event_log.sql +20 -0
- package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
- package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
- package/package.json +19 -4
- package/packages/agentsam-contracts/src/errors.ts +16 -0
- package/packages/agentsam-errors/src/envelope.js +53 -0
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/recovery.js +301 -0
- package/packages/agentsam-knowledge/src/providers/index.js +18 -6
- package/packages/connectors/cloudflare/src/routes.js +9 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
- package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
- package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
- package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
- package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
- package/packages/identity/src/adapters/sqlite/index.js +319 -0
- package/packages/identity/src/app/verify-app.js +95 -0
- package/packages/identity/src/contracts/identity-store.js +115 -0
- package/packages/identity/src/contracts/route-ids.js +30 -0
- package/packages/identity/src/contracts/route-projection.js +218 -0
- package/packages/identity/src/contracts/routes.js +11 -0
- package/packages/identity/src/core/browser-paths.js +4 -5
- package/packages/identity/src/core/constants.js +13 -8
- package/packages/identity/src/core/session-policy.js +32 -0
- package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
- package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
- package/packages/identity/src/index.js +25 -0
- package/packages/identity/src/oauth/README.md +10 -4
- package/packages/identity/src/oauth/credentials.js +20 -13
- package/packages/identity/src/oauth/finalize-inbound.js +1 -1
- package/packages/identity/src/oauth/iam-platform.js +8 -7
- package/packages/identity/src/oauth/redirect-paths.js +27 -36
- package/packages/identity/src/server/identity-service.js +30 -13
- package/packages/identity/src/server/mount-policy.js +30 -0
- package/packages/identity/src/server/post-auth.js +79 -0
- package/packages/identity/src/server/worker-router.js +104 -72
- package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
- package/packages/identity/tests/iam-provider.test.mjs +1 -1
- package/packages/identity/tests/identity-service.test.mjs +36 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
- package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
- package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
- package/packages/theme-church-site/package.json +2 -1
- package/packages/theme-church-site/src/index.js +1 -0
- package/packages/theme-companions-site/package.json +2 -1
- package/packages/theme-companions-site/src/index.js +1 -0
- package/packages/theme-floors-site/package.json +2 -1
- package/packages/theme-floors-site/src/index.js +1 -0
- package/packages/theme-fuelnfree-site/package.json +2 -1
- package/packages/theme-fuelnfree-site/src/index.js +1 -0
- package/packages/theme-handyman-site/package.json +2 -1
- package/packages/theme-handyman-site/src/index.js +1 -0
- package/packages/theme-insurance-site/package.json +2 -1
- package/packages/theme-insurance-site/src/index.js +1 -0
- package/packages/theme-shinshu-site/package.json +2 -1
- package/packages/theme-shinshu-site/src/index.js +1 -0
- package/protocol/apps/agentsam.app.v1.schema.json +51 -0
- package/protocol/brand/brandpack.v1.schema.json +43 -0
- package/protocol/credentials/issue.v1.schema.json +38 -0
- package/protocol/database/connection.v1.schema.json +41 -0
- package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
- package/protocol/errors/error-envelope.schema.json +135 -1
- package/protocol/errors/recovery.v1.schema.json +50 -0
- package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
- package/protocol/sam/activity.v1.schema.json +48 -0
- package/protocol/sam/answer.v1.schema.json +35 -0
- package/protocol/sam/calibration.v1.schema.json +21 -0
- package/protocol/sam/decision-receipt.v1.schema.json +29 -0
- package/protocol/sam/evaluation.v1.schema.json +19 -0
- package/protocol/sam/operation.schema.json +66 -0
- package/protocol/sam/outcome.v1.schema.json +36 -0
- package/protocol/sam/question.v1.schema.json +26 -0
- package/protocol/sam/registry.seed.json +153 -0
- package/protocol/sam/result.schema.json +53 -0
- package/protocol/sam/state.v1.schema.json +23 -0
- package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
- package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
- package/protocol/ui/icon-registry.mjs +226 -0
- package/protocol/ui/icon.v1.schema.json +51 -0
- package/skills/README.md +22 -9
- package/skills/agentsam-codebaseindex/SKILL.md +225 -0
- package/skills/catalog.json +14 -0
- package/src/cli/command-catalog.js +130 -0
- package/src/cli/dispatch.js +48 -0
- package/src/cli.js +43 -1
- package/src/commands/api-key.js +244 -0
- package/src/commands/app.js +60 -28
- package/src/commands/brand.js +17 -19
- package/src/commands/codebaseindex.js +688 -0
- package/src/commands/env.js +152 -25
- package/src/commands/go.js +366 -53
- package/src/commands/interaction-clack.js +115 -0
- package/src/commands/models.js +1 -1
- package/src/commands/providers.js +62 -14
- package/src/commands/shell.js +43 -2
- package/src/commands/skill.js +248 -0
- package/src/commands/skills.js +1 -1
- package/src/commands/start-local.js +4 -0
- package/src/commands/whoami.js +90 -18
- package/src/go/build.js +229 -39
- package/src/go/cloudflare.js +506 -105
- package/src/go/container.js +120 -0
- package/src/go/contract.js +9 -4
- package/src/go/discover.js +149 -33
- package/src/go/index.js +15 -3
- package/src/go/native-probe-runner.mjs +119 -0
- package/src/go/{registry.js → official-registry.js} +64 -7
- package/src/go/official-release.js +10 -0
- package/src/go/receipts.js +77 -10
- package/src/go/verify.js +9 -2
- package/src/index.js +27 -0
- package/src/indexing/ingest/discover-models.js +298 -0
- package/src/indexing/ingest/inventory.js +243 -0
- package/src/indexing/ingest/job-graph.js +181 -0
- package/src/indexing/ingest/materials.js +210 -0
- package/src/lib/provider-credentials.js +63 -21
- package/src/lib/slash-commands.js +1 -0
- package/src/local-fs/capability.js +121 -0
- package/src/local-fs/freshness.js +75 -0
- package/src/local-fs/index.js +385 -0
- package/src/local-fs/paths.js +100 -0
- package/src/local-pty/server.js +295 -19
- package/src/mcp/client.js +2 -2
- package/src/models/ai-access-onboarding.js +112 -0
- package/src/models/discovery.js +10 -2
- package/src/models/inventory-core.js +9 -1
- package/src/sam/activity/index.js +183 -0
- package/src/sam/client.js +252 -0
- package/src/sam/decision/calibration.js +109 -0
- package/src/sam/decision/confidence.js +126 -0
- package/src/sam/decision/evaluate.js +157 -0
- package/src/sam/decision/evaluators/deterministic.js +341 -0
- package/src/sam/decision/evaluators/heuristic.js +61 -0
- package/src/sam/decision/evaluators/select.js +50 -0
- package/src/sam/decision/evaluators/semantic.js +149 -0
- package/src/sam/decision/hierarchical.js +61 -0
- package/src/sam/decision/index.js +53 -0
- package/src/sam/decision/policy.js +86 -0
- package/src/sam/decision/questions.js +120 -0
- package/src/sam/decision/receipt.js +148 -0
- package/src/sam/decision/state.js +117 -0
- package/src/sam/decision/types.js +22 -0
- package/src/sam/decision/validate.js +200 -0
- package/src/sam/define.js +51 -0
- package/src/sam/index.js +65 -0
- package/src/sam/operations/brand-scan.js +62 -0
- package/src/sam/operations/cad-blender-inspect.js +36 -0
- package/src/sam/operations/codebaseindex-ingest.js +49 -0
- package/src/sam/operations/decision-evaluate.js +59 -0
- package/src/sam/operations/planning-astar.js +77 -0
- package/src/sam/operations/planning-goap.js +60 -0
- package/src/sam/operations/repository-inspect.js +72 -0
- package/src/sam/operations/security-scan.js +33 -0
- package/src/sam/operations/terminal-exec.js +29 -0
- package/src/sam/planning/astar.js +311 -0
- package/src/sam/planning/goap.js +177 -0
- package/src/sam/planning/index.js +21 -0
- package/src/sam/planning/state.js +84 -0
- package/src/sam/registry.js +48 -0
- package/src/sam/result.js +77 -0
- package/src/sam/seed.js +44 -0
- package/src/sam/types.js +91 -0
- package/src/skills/catalog.js +64 -0
- package/src/skills/content-resolver.js +124 -0
- package/src/skills/hosted-store.js +37 -0
- package/src/skills/index.js +29 -64
- package/src/skills/interaction.js +102 -0
- package/src/skills/local-store.js +228 -0
- package/src/skills/manifest.js +104 -0
- package/src/skills/metrics.js +31 -0
- package/src/skills/registry.js +184 -0
- package/src/skills/runtime.js +287 -0
- package/src/skills/slash.js +44 -0
- package/src/ui/cli/help.js +94 -101
- package/test/cli/api-key-env-whoami.test.mjs +129 -0
- package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
- package/test/cli/go.test.mjs +62 -5
- package/test/cli/skill-npm-and-env.test.mjs +52 -0
- package/test/cli/wireframes-go-registry.test.mjs +87 -2
- package/test/go/build-source-identity.test.mjs +31 -0
- package/test/go/cloudflare-probe.test.mjs +274 -10
- package/test/go/distribution.test.mjs +28 -0
- package/test/integration/ai-access-onboarding.test.mjs +47 -0
- package/test/integration/cli-help.test.mjs +1 -1
- package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
- package/test/integration/codebaseindex-ingest.test.mjs +166 -0
- package/test/integration/icon-registry.test.mjs +67 -0
- package/test/integration/ingest-discover-models.test.mjs +30 -0
- package/test/integration/install-script.test.mjs +12 -9
- package/test/integration/local-fs.test.mjs +113 -0
- package/test/integration/provider-env-cli.test.mjs +2 -1
- package/test/integration/sam-activity-recovery.test.mjs +147 -0
- package/test/integration/sam-decision.test.mjs +584 -0
- package/test/integration/sam-kernel.test.mjs +99 -0
- package/test/integration/sam-planning-astar.test.mjs +279 -0
- package/test/integration/skill-runtime.test.mjs +240 -0
- package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
- package/test/models.test.mjs +14 -7
- package/test/shell.test.mjs +4 -4
package/src/commands/whoami.js
CHANGED
|
@@ -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:
|
|
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(`
|
|
133
|
-
if (status.
|
|
134
|
-
|
|
135
|
-
|
|
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).
|
|
150
|
-
lines.push('
|
|
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`
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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, {
|
|
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
|
|
31
|
-
const
|
|
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
|
|
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({
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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:
|
|
98
|
-
},
|
|
99
|
-
target: {
|
|
100
|
-
provider: 'local',
|
|
101
|
-
kind: 'build',
|
|
289
|
+
runtime_probe: Boolean(probe.ok),
|
|
102
290
|
},
|
|
103
|
-
|
|
291
|
+
probe,
|
|
292
|
+
target: { provider: 'local', kind: 'build' },
|
|
293
|
+
built_at: builtAt,
|
|
104
294
|
};
|
|
105
295
|
|
|
106
|
-
const receiptPath = writeGoBuildReceipt(
|
|
107
|
-
return { receipt, receiptPath, tests, vet, build };
|
|
296
|
+
const receiptPath = writeGoBuildReceipt(stateRoot, receipt);
|
|
297
|
+
return { receipt, receiptPath, tests, vet, build, probe, source };
|
|
108
298
|
}
|