@inneranimalmedia/agentsam-sdk 2.4.1 → 2.6.0
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 +55 -0
- package/README.md +17 -12
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/AGENTSAM_WORKBENCH.md +30 -0
- package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
- package/docs/CAPABILITIES.md +5 -3
- package/docs/CLI_SHELL.md +167 -80
- package/docs/CMS_STUDIO.md +101 -0
- package/docs/CONTEXT.md +170 -0
- package/docs/LOCAL_OLLAMA.md +58 -0
- package/docs/MERKLE.md +19 -19
- package/docs/PORTABLE_CONTEXT.md +4 -3
- package/docs/PROJECT_CONFIG.md +72 -0
- package/docs/RELEASES.md +35 -2
- package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
- package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
- package/docs/SDK_WORKER.md +86 -0
- package/docs/SECURITY.md +59 -22
- package/docs/client-cms-editor.md +15 -0
- package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
- package/package.json +35 -10
- package/packages/connectors/cloudflare/package.json +10 -0
- package/packages/connectors/cloudflare/src/index.js +127 -0
- package/packages/connectors/cloudflare/src/owner.js +76 -0
- package/packages/connectors/cloudflare/src/routes.js +223 -0
- package/packages/connectors/cloudflare/src/vault.js +80 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
- package/packages/identity/package.json +10 -1
- package/packages/identity/src/contracts/auth-config.js +109 -0
- package/packages/identity/src/index.js +1 -0
- package/packages/identity/src/oauth/README.md +2 -2
- package/packages/identity/src/oauth/credentials.js +11 -4
- package/packages/identity/src/oauth/iam-platform.js +3 -3
- package/packages/identity/src/providers/iam/index.js +7 -7
- package/packages/identity/src/providers/iam/oauth.js +6 -4
- package/packages/identity/src/providers/iam/profile.js +5 -5
- package/packages/identity/tests/auth-config.test.mjs +61 -0
- package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
- package/protocol/FILEMETA_V1.md +95 -0
- package/protocol/INSPECT_VIEWS_V1.md +40 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
- package/protocol/MERKLE_V1.md +3 -1
- package/protocol/README.md +1 -0
- package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
- package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
- package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
- package/protocol/capabilities/manifest.json +79 -3
- package/protocol/capabilities/repository-snapshot.schema.json +1 -0
- package/protocol/context/context-budget.schema.json +36 -0
- package/protocol/context/context-item.schema.json +19 -0
- package/protocol/context/resolved-context-pack.schema.json +47 -0
- package/protocol/context/result-policy.schema.json +17 -0
- package/protocol/knowledge/context-pack.schema.json +33 -0
- package/protocol/knowledge/retrieval-query.schema.json +68 -13
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/python/README.md +15 -7
- package/python/agentsam_sdk/cli.py +0 -21
- package/python/agentsam_sdk/tui/README.md +17 -12
- package/python/agentsam_sdk/tui/bootstrap.py +2 -2
- package/python/agentsam_sdk/tui/demo.py +25 -10
- package/python/agentsam_sdk/tui/onboarding.py +208 -0
- package/python/tests/test_tui_cli.py +7 -6
- package/skills/README.md +21 -0
- package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
- package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
- package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
- package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
- package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
- package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
- package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
- package/skills/agentsam-jr-dev/SKILL.md +232 -0
- package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
- package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
- package/skills/agentsam-progression-guard/SKILL.md +197 -0
- package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
- package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
- package/skills/catalog.json +71 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +325 -0
- package/src/capabilities/index.js +7 -0
- package/src/capabilities/repository-snapshot-view.js +238 -0
- package/src/capabilities/repository-snapshot.js +28 -14
- package/src/cli.js +198 -76
- package/src/cloudflare/cpu-profile.js +115 -0
- package/src/cloudflare/index.js +14 -0
- package/src/cloudflare/wrangler.js +132 -0
- package/src/commands/account-auth.js +47 -0
- package/src/commands/cloudflare.js +58 -0
- package/src/commands/connections.js +93 -0
- package/src/commands/context-economics.js +114 -0
- package/src/commands/context.js +14 -2
- package/src/commands/db.js +4 -7
- package/src/commands/deploy.js +54 -32
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +18 -0
- package/src/commands/knowledge.js +27 -4
- package/src/commands/merkle-persist.js +118 -0
- package/src/commands/merkle.js +32 -17
- package/src/commands/models.js +152 -0
- package/src/commands/ollama.js +259 -0
- package/src/commands/preferences.js +144 -0
- package/src/commands/product.js +86 -16
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +8 -6
- package/src/commands/shell.js +405 -100
- package/src/commands/skills.js +66 -0
- package/src/commands/start-local.js +1 -1
- package/src/commands/tunnel.js +5 -4
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +120 -0
- package/src/context/compact.js +28 -0
- package/src/context/index.js +7 -0
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +116 -0
- package/src/context/result-policy.js +66 -0
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +9 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +70 -0
- package/src/indexing/execution-boundary.js +144 -0
- package/src/indexing/index.js +8 -0
- package/src/indexing/provider.js +41 -0
- package/src/knowledge/config.js +2 -2
- package/src/knowledge/context-pack.js +12 -1
- package/src/knowledge/contracts.js +9 -4
- package/src/knowledge/engine.js +7 -3
- package/src/knowledge/service/server.js +1 -1
- package/src/lib/account-session.js +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/bridge-client.js +7 -5
- package/src/lib/cli-preferences.js +78 -0
- package/src/lib/core-client.js +8 -8
- package/src/lib/deploy/git-guard.js +69 -0
- package/src/lib/deploy/health.js +57 -0
- package/src/lib/deploy/local-studio.js +283 -0
- package/src/lib/deploy/secret-scan.js +65 -0
- package/src/lib/deploy-receipt/index.js +5 -2
- package/src/lib/detect-context.js +6 -5
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/identity-scaffold.js +1 -1
- package/src/lib/local-scaffold.js +35 -33
- package/src/lib/local-sessions.js +127 -0
- package/src/lib/local-status.js +9 -17
- package/src/lib/merkle/cloudflare-persistence.js +321 -0
- package/src/lib/merkle/filemeta.js +43 -0
- package/src/lib/merkle/git-ignore.js +24 -0
- package/src/lib/merkle/hash.js +1 -0
- package/src/lib/merkle/index.js +22 -0
- package/src/lib/merkle/persistence.js +72 -0
- package/src/lib/merkle/semantic.js +359 -0
- package/src/lib/merkle/snapshot.js +9 -3
- package/src/lib/merkle/tree.js +11 -6
- package/src/lib/project-config.js +227 -0
- package/src/lib/project-rules.js +68 -0
- package/src/lib/provider-credentials.js +83 -0
- package/src/lib/save-sdk-token.js +1 -1
- package/src/lib/scaffold/templates/worker-api/index.js +101 -20
- package/src/lib/scaffold/wizards/worker-api.js +27 -11
- package/src/lib/slash-commands.js +22 -15
- package/src/lib/tools.js +11 -5
- package/src/models/catalog.js +135 -0
- package/src/models/index.js +7 -0
- package/src/providers/index.js +5 -0
- package/src/providers/openai-responses.js +275 -0
- package/src/security/index.js +1 -0
- package/src/security/inventory.js +4 -1
- package/src/security/process.js +35 -9
- package/src/security/render.js +27 -5
- package/src/security/scan.js +24 -9
- package/src/security/trust-boundary.js +24 -0
- package/src/skills/index.js +64 -0
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +48 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +2 -0
- package/src/tools/search.js +70 -0
- package/src/ui/ansi.js +1 -1
- package/src/ui/boot.js +54 -0
- package/src/ui/merkle/render.js +1 -0
- package/src/ui/runtime-activity.js +192 -0
- package/src/ui/theme.js +19 -18
- package/test/account-session.test.mjs +36 -0
- package/test/app-building-skills.test.mjs +61 -0
- package/test/apps-scaffold-contract.test.mjs +56 -0
- package/test/capabilities.test.mjs +53 -4
- package/test/cli-preferences.test.mjs +46 -0
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +144 -0
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +59 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- package/test/indexing-provider.test.mjs +29 -0
- package/test/jr-dev-skill.test.mjs +26 -0
- package/test/knowledge-context-pack.test.mjs +19 -0
- package/test/knowledge.test.mjs +1 -1
- package/test/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/merkle-persistence.test.mjs +91 -0
- package/test/merkle.test.mjs +77 -3
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +51 -0
- package/test/npm10-lock.test.mjs +29 -0
- package/test/ollama.test.mjs +94 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/project-config.test.mjs +81 -0
- package/test/project-rules.test.mjs +44 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +34 -0
- package/test/repository-snapshot-view.test.mjs +112 -0
- package/test/responses-runner.test.mjs +148 -0
- package/test/runtime-activity.test.mjs +98 -0
- package/test/sdk-worker-contract.test.mjs +68 -0
- package/test/security.test.mjs +46 -0
- package/test/shell.test.mjs +55 -21
- package/test/skills.test.mjs +22 -0
- package/test/smoke.mjs +5 -2
- package/test/telemetry.test.mjs +79 -0
- package/test/theme-portability.test.mjs +14 -0
- package/test/tools-search.test.mjs +40 -0
- package/test/whoami-resume.test.mjs +56 -0
- package/examples/agentsam-tui-ansi.mjs +0 -149
- package/src/commands/tui.js +0 -120
- package/src/ui/splash-xterm.js +0 -290
- package/src/ui/splash.js +0 -426
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
export function createContextPack({ queryId, query, hits = [], diagnostics = {}, confidence = "unknown" }) {
|
|
2
2
|
const normalizedHits = hits.map((hit) => Object.freeze({ stale: false, reasons: [], metadata: {}, ...hit }));
|
|
3
|
+
const estimatedTokens = estimateTokens(normalizedHits);
|
|
4
|
+
const chars = normalizedHits.reduce((sum, hit) => sum + String(hit.content || "").length, 0);
|
|
5
|
+
const considered = Number.isInteger(diagnostics.sources_considered) ? diagnostics.sources_considered : normalizedHits.length;
|
|
6
|
+
const deferred = Number.isInteger(diagnostics.sources_deferred) ? diagnostics.sources_deferred : Math.max(0, considered - normalizedHits.length);
|
|
3
7
|
return Object.freeze({
|
|
4
8
|
query_id: queryId,
|
|
5
9
|
query,
|
|
6
10
|
hits: normalizedHits,
|
|
7
|
-
estimated_tokens:
|
|
11
|
+
estimated_tokens: estimatedTokens,
|
|
8
12
|
confidence,
|
|
9
13
|
diagnostics: Object.freeze({ ...diagnostics }),
|
|
14
|
+
receipt: Object.freeze({
|
|
15
|
+
chars,
|
|
16
|
+
estimated_tokens: estimatedTokens,
|
|
17
|
+
sources_considered: considered,
|
|
18
|
+
sources_included: normalizedHits.length,
|
|
19
|
+
sources_deferred: deferred,
|
|
20
|
+
}),
|
|
10
21
|
});
|
|
11
22
|
}
|
|
12
23
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Provider-neutral AgentSam Knowledge SDK contracts and validators. */
|
|
2
2
|
|
|
3
|
+
import { normalizeResultPolicy } from '../context/result-policy.js';
|
|
4
|
+
|
|
3
5
|
export const KNOWLEDGE_OPERATIONS = Object.freeze({
|
|
4
6
|
RETRIEVE: "knowledge.retrieve",
|
|
5
7
|
INDEX: "knowledge.index",
|
|
@@ -9,10 +11,13 @@ export function assertRetrievalQuery(value) {
|
|
|
9
11
|
if (!value || typeof value !== "object") throw new TypeError("RetrievalQuery must be an object");
|
|
10
12
|
if (!String(value.text || "").trim()) throw new TypeError("RetrievalQuery.text is required");
|
|
11
13
|
if (value.workspace_id != null && !String(value.workspace_id).trim()) throw new TypeError("RetrievalQuery.workspace_id must be non-empty when present");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
if (!Number.isInteger(
|
|
15
|
-
|
|
14
|
+
const resultPolicy = normalizeResultPolicy(value.result_policy || {});
|
|
15
|
+
const topK = value.top_k ?? resultPolicy.max_items;
|
|
16
|
+
if (!Number.isInteger(topK) || topK < 1 || topK > resultPolicy.max_items) throw new RangeError(`top_k must be 1..${resultPolicy.max_items} under the active result policy`);
|
|
17
|
+
const defaultTokenBudget = Math.max(256, Math.floor(resultPolicy.max_chars / 4));
|
|
18
|
+
const tokenBudget = value.token_budget ?? defaultTokenBudget;
|
|
19
|
+
if (!Number.isInteger(tokenBudget) || tokenBudget < 256 || tokenBudget > defaultTokenBudget) throw new RangeError(`token_budget must be 256..${defaultTokenBudget} under the active result policy`);
|
|
20
|
+
return { ...value, result_policy: resultPolicy, top_k: topK, token_budget: tokenBudget };
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export function assertContextPack(value) {
|
package/src/knowledge/engine.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { validateConfig, fingerprint, scopeKey, cacheNamespace, gitEvidence } from './config.js';
|
|
3
3
|
import { PARSER, inventory, readSource, parseSource } from './source.js';
|
|
4
4
|
import { createContextPack } from './context-pack.js';
|
|
5
|
+
import { normalizeResultPolicy } from '../context/result-policy.js';
|
|
5
6
|
|
|
6
7
|
export const embeddingProfileId = profile => fingerprint({ ...profile, input_format: 'agentsam-code-content:1' });
|
|
7
8
|
export function validateVector(vector, dimensions) {
|
|
@@ -72,10 +73,13 @@ export async function runIndex({ root, config, store, embedder, embed = false, m
|
|
|
72
73
|
return { ...receipt, generation_id: generation.id, published: true };
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
export async function retrieve({ store, config: input, text, semantic = false, embedder, topK = 8, tokenBudget =
|
|
76
|
+
export async function retrieve({ store, config: input, text, semantic = false, embedder, topK = 8, tokenBudget = 6000, generationId, resultPolicy: requestedResultPolicy }) {
|
|
76
77
|
const config = validateConfig(input);
|
|
77
78
|
if (typeof text !== 'string' || !text.trim()) throw new Error('A nonempty query is required.');
|
|
78
|
-
|
|
79
|
+
const resultPolicy = normalizeResultPolicy(requestedResultPolicy || {});
|
|
80
|
+
if (!Number.isInteger(topK) || topK < 1 || topK > resultPolicy.max_items) throw new Error(`topK must be 1..${resultPolicy.max_items} under the active result policy.`);
|
|
81
|
+
const maxTokenBudget = Math.max(256, Math.floor(resultPolicy.max_chars / 4));
|
|
82
|
+
if (!Number.isInteger(tokenBudget) || tokenBudget < 256 || tokenBudget > maxTokenBudget) throw new Error(`tokenBudget must be 256..${maxTokenBudget} under the active result policy.`);
|
|
79
83
|
const generation = generationId ? await store?.getGeneration(scopeKey(config), generationId) : await store?.active(scopeKey(config));
|
|
80
84
|
if (!generation) throw new Error('No generation found for this repository/scope. Run agentsam index run.');
|
|
81
85
|
let queryVector;
|
|
@@ -113,6 +117,6 @@ export async function retrieve({ store, config: input, text, semantic = false, e
|
|
|
113
117
|
metadata: { generation_id: generation.id, symbol: hit.symbol, content_hash: hit.content_hash, freshness: 'working tree not checked' } }); estimatedTokens += tokens;
|
|
114
118
|
if (hits.length === topK) break;
|
|
115
119
|
}
|
|
116
|
-
return createContextPack({ queryId: randomUUID(), query: { text, top_k: topK, token_budget: tokenBudget }, hits,
|
|
120
|
+
return createContextPack({ queryId: randomUUID(), query: { text, top_k: topK, token_budget: tokenBudget, result_policy: resultPolicy }, hits,
|
|
117
121
|
diagnostics: { generation_id: generation.id, source_hash: generation.source_hash, scope: generation.config.scope, mode: semantic ? 'semantic-exact' : 'lexical', freshness: 'snapshot; working tree not checked' } });
|
|
118
122
|
}
|
|
@@ -30,7 +30,7 @@ function normalizeRequest(body, repositories, allowEmbeddings) {
|
|
|
30
30
|
};
|
|
31
31
|
const request = { ...body, embed: body.embed ?? false, semantic: body.semantic ?? false,
|
|
32
32
|
max_inputs: bounded('max_inputs', 100, 0, 1000), max_characters: bounded('max_characters', 200000, 0, 2000000),
|
|
33
|
-
top_k: bounded('top_k', 8, 1,
|
|
33
|
+
top_k: bounded('top_k', 8, 1, 8), token_budget: bounded('token_budget', 6000, 256, 6000) };
|
|
34
34
|
const config = structuredClone(repo.config);
|
|
35
35
|
if (body.scope !== undefined) config.scope.name = body.scope;
|
|
36
36
|
if (body.include !== undefined) config.scope.include = body.include;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolveSdkKey } from '../../packages/identity/src/contracts/auth-config.js';
|
|
5
|
+
|
|
6
|
+
export const ACCOUNT_SESSION_SCHEMA = 'agentsam-account-session-v1';
|
|
7
|
+
|
|
8
|
+
function clean(value) { return value == null ? '' : String(value).trim(); }
|
|
9
|
+
function homeDirectory(options = {}) {
|
|
10
|
+
return path.resolve(clean(options.home) || clean(options.env?.HOME) || clean(options.env?.USERPROFILE) || os.homedir());
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function accountSessionPath(options = {}) {
|
|
14
|
+
return path.join(homeDirectory(options), '.agentsam', 'auth', 'session.json');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readAccountSession(options = {}) {
|
|
18
|
+
const filename = accountSessionPath(options);
|
|
19
|
+
if (!fs.existsSync(filename)) return null;
|
|
20
|
+
try {
|
|
21
|
+
const stat = fs.statSync(filename);
|
|
22
|
+
if (!stat.isFile()) return null;
|
|
23
|
+
if (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) return null;
|
|
24
|
+
const parsed = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
|
25
|
+
if (parsed?.schema_version !== ACCOUNT_SESSION_SCHEMA) return null;
|
|
26
|
+
const token = clean(parsed.sdk_key);
|
|
27
|
+
if (!token.startsWith('sdk_')) return null;
|
|
28
|
+
return {
|
|
29
|
+
schema_version: ACCOUNT_SESSION_SCHEMA,
|
|
30
|
+
sdk_key: token,
|
|
31
|
+
user_id: clean(parsed.user_id) || null,
|
|
32
|
+
account_id: clean(parsed.account_id) || null,
|
|
33
|
+
email: clean(parsed.email) || null,
|
|
34
|
+
created_at: clean(parsed.created_at) || null,
|
|
35
|
+
updated_at: clean(parsed.updated_at) || null,
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function saveAccountSession(session = {}, options = {}) {
|
|
43
|
+
const token = clean(session.sdk_key || session.access_token);
|
|
44
|
+
if (!token.startsWith('sdk_')) throw new Error('account_session_sdk_key_required');
|
|
45
|
+
const filename = accountSessionPath(options);
|
|
46
|
+
const dir = path.dirname(filename);
|
|
47
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
48
|
+
if (process.platform !== 'win32') {
|
|
49
|
+
try { fs.chmodSync(dir, 0o700); } catch { /* best effort */ }
|
|
50
|
+
}
|
|
51
|
+
const previous = readAccountSession(options);
|
|
52
|
+
const now = new Date().toISOString();
|
|
53
|
+
const value = {
|
|
54
|
+
schema_version: ACCOUNT_SESSION_SCHEMA,
|
|
55
|
+
sdk_key: token,
|
|
56
|
+
user_id: clean(session.user_id) || previous?.user_id || null,
|
|
57
|
+
account_id: clean(session.account_id) || previous?.account_id || null,
|
|
58
|
+
email: clean(session.email) || previous?.email || null,
|
|
59
|
+
created_at: previous?.created_at || now,
|
|
60
|
+
updated_at: now,
|
|
61
|
+
};
|
|
62
|
+
const temp = `${filename}.${process.pid}.tmp`;
|
|
63
|
+
fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
64
|
+
if (process.platform !== 'win32') {
|
|
65
|
+
try { fs.chmodSync(temp, 0o600); } catch { /* best effort */ }
|
|
66
|
+
}
|
|
67
|
+
fs.renameSync(temp, filename);
|
|
68
|
+
return { ...value };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function clearAccountSession(options = {}) {
|
|
72
|
+
const filename = accountSessionPath(options);
|
|
73
|
+
if (!fs.existsSync(filename)) return false;
|
|
74
|
+
fs.rmSync(filename, { force: true });
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolveAccountSdkKey(options = {}) {
|
|
79
|
+
const env = options.env || process.env;
|
|
80
|
+
const explicit = clean(options.explicit);
|
|
81
|
+
const fromEnv = resolveSdkKey(env, explicit);
|
|
82
|
+
if (fromEnv) return { value: fromEnv, source: explicit ? 'explicit' : 'environment' };
|
|
83
|
+
const session = readAccountSession({ ...options, env });
|
|
84
|
+
return session?.sdk_key
|
|
85
|
+
? { value: session.sdk_key, source: 'agentsam_account_session', session }
|
|
86
|
+
: { value: '', source: null, session: null };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function describeAccountSession(options = {}) {
|
|
90
|
+
const resolved = resolveAccountSdkKey(options);
|
|
91
|
+
return {
|
|
92
|
+
configured: Boolean(resolved.value),
|
|
93
|
+
source: resolved.source,
|
|
94
|
+
user_id: resolved.session?.user_id || null,
|
|
95
|
+
account_id: resolved.session?.account_id || null,
|
|
96
|
+
email: resolved.session?.email || null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { findProjectRules } from './project-rules.js';
|
|
5
|
+
|
|
6
|
+
export const AGENT_RUNTIME_FILENAME = 'AGENTSAM.md';
|
|
7
|
+
export const AGENT_INSTRUCTION_PRECEDENCE = Object.freeze(['AGENTSAM.md', '.agentsamrules']);
|
|
8
|
+
|
|
9
|
+
function sha256(value) {
|
|
10
|
+
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function findUp(startDir, filename) {
|
|
14
|
+
let dir = path.resolve(startDir);
|
|
15
|
+
for (let i = 0; i < 16; i += 1) {
|
|
16
|
+
const candidate = path.join(dir, filename);
|
|
17
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
18
|
+
const boundary = fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, '.agentsam', 'config.json'));
|
|
19
|
+
if (boundary) break;
|
|
20
|
+
const parent = path.dirname(dir);
|
|
21
|
+
if (parent === dir) break;
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function findAgentRuntimeContract(startDir = process.cwd()) {
|
|
28
|
+
return findUp(startDir, AGENT_RUNTIME_FILENAME);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function compileAgentInstructions(startDir = process.cwd(), options = {}) {
|
|
32
|
+
const maxChars = Number(options.maxChars ?? 24_000);
|
|
33
|
+
if (!Number.isInteger(maxChars) || maxChars < 1) throw new RangeError('agent instructions maxChars must be a positive integer');
|
|
34
|
+
|
|
35
|
+
const runtimePath = options.runtimeFilename ? path.resolve(options.runtimeFilename) : findAgentRuntimeContract(startDir);
|
|
36
|
+
const projectPath = options.projectFilename ? path.resolve(options.projectFilename) : findProjectRules(startDir);
|
|
37
|
+
const descriptors = [
|
|
38
|
+
{ id: 'runtime', filename: AGENT_RUNTIME_FILENAME, path: runtimePath },
|
|
39
|
+
{ id: 'project', filename: '.agentsamrules', path: projectPath },
|
|
40
|
+
];
|
|
41
|
+
const sections = [];
|
|
42
|
+
const sources = [];
|
|
43
|
+
let sourceChars = 0;
|
|
44
|
+
|
|
45
|
+
for (const descriptor of descriptors) {
|
|
46
|
+
if (!descriptor.path || !fs.existsSync(descriptor.path)) continue;
|
|
47
|
+
const source = fs.readFileSync(descriptor.path, 'utf8');
|
|
48
|
+
sourceChars += source.length;
|
|
49
|
+
sources.push(Object.freeze({
|
|
50
|
+
id: descriptor.id,
|
|
51
|
+
filename: descriptor.filename,
|
|
52
|
+
path: descriptor.path,
|
|
53
|
+
chars: source.length,
|
|
54
|
+
hash: sha256(source),
|
|
55
|
+
}));
|
|
56
|
+
sections.push(`<!-- agentsam:${descriptor.id}:${descriptor.filename} -->\n${source.trim()}\n`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const combined = sections.join('\n');
|
|
60
|
+
const truncated = combined.length > maxChars;
|
|
61
|
+
const content = truncated ? `${combined.slice(0, Math.max(0, maxChars - 1))}…` : combined;
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
found: sources.length > 0,
|
|
64
|
+
path: projectPath || runtimePath || null,
|
|
65
|
+
content,
|
|
66
|
+
chars: content.length,
|
|
67
|
+
source_chars: sourceChars,
|
|
68
|
+
truncated,
|
|
69
|
+
hash: combined ? sha256(combined) : null,
|
|
70
|
+
precedence: AGENT_INSTRUCTION_PRECEDENCE,
|
|
71
|
+
sources: Object.freeze(sources),
|
|
72
|
+
});
|
|
73
|
+
}
|
package/src/lib/auth.js
CHANGED
|
@@ -5,6 +5,7 @@ import http from 'node:http';
|
|
|
5
5
|
import { randomBytes } from 'node:crypto';
|
|
6
6
|
import { postJson } from './core-client.js';
|
|
7
7
|
import { promptToOpenUrl } from './open-url.js';
|
|
8
|
+
import { saveAccountSession } from './account-session.js';
|
|
8
9
|
|
|
9
10
|
function randomState() {
|
|
10
11
|
return randomBytes(16).toString('hex');
|
|
@@ -63,5 +64,8 @@ export async function authenticateViaBrowser() {
|
|
|
63
64
|
|
|
64
65
|
const code = await codePromise;
|
|
65
66
|
const session = await postJson('/api/sdk/auth/exchange', { code, state });
|
|
67
|
+
if (String(session?.access_token || '').trim().startsWith('sdk_')) {
|
|
68
|
+
saveAccountSession(session);
|
|
69
|
+
}
|
|
66
70
|
return session;
|
|
67
71
|
}
|
package/src/lib/bridge-client.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
resolveBridgeKey as resolveContractBridgeKey,
|
|
3
|
+
resolveIamOrigin,
|
|
4
|
+
} from '../../packages/identity/src/contracts/auth-config.js';
|
|
2
5
|
|
|
3
6
|
function clean(value) {
|
|
4
7
|
return value == null ? '' : String(value).trim();
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
export function resolveAgentSamBaseUrl(env = process.env, explicit = '') {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
).replace(/\/$/, '');
|
|
11
|
+
const compatibilityBase = env?.AGENTSAM_BASE_URL || env?.AGENTSAM_CORE_URL || env?.IAM_CORE_URL || '';
|
|
12
|
+
return resolveIamOrigin(env, explicit || env?.IAM_ORIGIN || compatibilityBase);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export function resolveBridgeKey(env = process.env, explicit = '') {
|
|
14
|
-
return
|
|
16
|
+
return resolveContractBridgeKey(env, explicit);
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { getProjectName, tryReadProjectConfig } from './project-config.js';
|
|
5
|
+
|
|
6
|
+
export const CLI_PREFERENCES_SCHEMA = 'agentsam-cli-preferences-v2';
|
|
7
|
+
export const LEGACY_CLI_PREFERENCES_SCHEMA = 'agentsam-cli-preferences-v1';
|
|
8
|
+
|
|
9
|
+
function readJson(filename) {
|
|
10
|
+
try { return JSON.parse(fs.readFileSync(filename, 'utf8')); }
|
|
11
|
+
catch { return null; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function gitValue(cwd, args) {
|
|
15
|
+
const result = spawnSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
16
|
+
return result.status === 0 ? String(result.stdout || '').trim() : '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function findCliProjectRoot(startDir = process.cwd()) {
|
|
20
|
+
const cwd = path.resolve(startDir);
|
|
21
|
+
const gitRoot = gitValue(cwd, ['rev-parse', '--show-toplevel']);
|
|
22
|
+
if (gitRoot) return path.resolve(gitRoot);
|
|
23
|
+
let dir = cwd;
|
|
24
|
+
for (let i = 0; i < 16; i += 1) {
|
|
25
|
+
if (fs.existsSync(path.join(dir, '.agentsam', 'cli.json')) || fs.existsSync(path.join(dir, '.agentsam', 'config.json')) || fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
26
|
+
const parent = path.dirname(dir);
|
|
27
|
+
if (parent === dir) break;
|
|
28
|
+
dir = parent;
|
|
29
|
+
}
|
|
30
|
+
return cwd;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function detectCliProject(startDir = process.cwd()) {
|
|
34
|
+
const root = findCliProjectRoot(startDir);
|
|
35
|
+
const config = tryReadProjectConfig(root) || {};
|
|
36
|
+
const pkg = readJson(path.join(root, 'package.json')) || {};
|
|
37
|
+
const branch = gitValue(root, ['branch', '--show-current']);
|
|
38
|
+
const remote = gitValue(root, ['config', '--get', 'remote.origin.url']);
|
|
39
|
+
const project = String(getProjectName(config, pkg.name || path.basename(root)));
|
|
40
|
+
const website = String(pkg.homepage || '').trim();
|
|
41
|
+
return { root, project, branch, remote, website, configured: Boolean(getProjectName(config)) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function cliPreferencesPath(root) { return path.join(path.resolve(root), '.agentsam', 'cli.json'); }
|
|
45
|
+
|
|
46
|
+
function normalizePreferences(value = {}) {
|
|
47
|
+
return {
|
|
48
|
+
schemaVersion: CLI_PREFERENCES_SCHEMA,
|
|
49
|
+
trustedDirectory: value.trustedDirectory === true,
|
|
50
|
+
runtime: value.runtime || 'local',
|
|
51
|
+
terminal: value.terminal || '',
|
|
52
|
+
modelPreference: value.modelPreference || 'auto',
|
|
53
|
+
reasoningEffort: value.reasoningEffort || 'auto',
|
|
54
|
+
serviceTier: value.serviceTier || 'default',
|
|
55
|
+
modelAuthority: 'preference-only',
|
|
56
|
+
updatedAt: value.updatedAt || null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function readCliPreferences(root) {
|
|
61
|
+
const value = readJson(cliPreferencesPath(root));
|
|
62
|
+
if (!value) return null;
|
|
63
|
+
if (value.schemaVersion !== CLI_PREFERENCES_SCHEMA && value.schemaVersion !== LEGACY_CLI_PREFERENCES_SCHEMA) return null;
|
|
64
|
+
return normalizePreferences(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function writeCliPreferences(root, value = {}) {
|
|
68
|
+
const filename = cliPreferencesPath(root);
|
|
69
|
+
fs.mkdirSync(path.dirname(filename), { recursive: true });
|
|
70
|
+
const previous = readCliPreferences(root) || {};
|
|
71
|
+
const next = normalizePreferences({ ...previous, ...value, updatedAt: new Date().toISOString() });
|
|
72
|
+
fs.writeFileSync(filename, `${JSON.stringify(next, null, 2)}\n`);
|
|
73
|
+
return next;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function updateCliPreferences(root, patch = {}) {
|
|
77
|
+
return writeCliPreferences(root, { ...(readCliPreferences(root) || {}), ...patch });
|
|
78
|
+
}
|
package/src/lib/core-client.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
+
import { resolveIamOrigin } from '../../packages/identity/src/contracts/auth-config.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* IAM CORE client — SDK is a delivery mechanism; intelligence lives server-side.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* scaffold/local CLI only.
|
|
6
|
+
* IAM_ORIGIN is the canonical platform authority/API origin. During the
|
|
7
|
+
* migration window IAM_CORE_URL / AGENTSAM_CORE_URL remain compatibility-only
|
|
8
|
+
* fallbacks. Without a CORE backend, consumers get scaffold/local CLI only.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return (process.env.IAM_CORE_URL || process.env.AGENTSAM_CORE_URL || DEFAULT_CORE).replace(/\/$/, '');
|
|
11
|
+
export function coreBaseUrl(env = process.env) {
|
|
12
|
+
const explicit = env?.IAM_ORIGIN || env?.IAM_CORE_URL || env?.AGENTSAM_CORE_URL || '';
|
|
13
|
+
return resolveIamOrigin(env, explicit);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export async function postJson(path, body, token) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Wrangler deploys are allowed only from a clean main that matches origin/main.
|
|
3
|
+
* Dry-run / plan may run from a feature branch. Never from /private/tmp.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const BLOCKED_BRANCH_PREFIXES = ['feat/', 'fix/', 'chore/', 'release/'];
|
|
9
|
+
|
|
10
|
+
function git(cwd, args) {
|
|
11
|
+
const r = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
12
|
+
return {
|
|
13
|
+
status: r.status,
|
|
14
|
+
stdout: String(r.stdout || '').trim(),
|
|
15
|
+
stderr: String(r.stderr || '').trim(),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function inspectDeployGit(cwd = process.cwd()) {
|
|
20
|
+
const root = git(cwd, ['rev-parse', '--show-toplevel']);
|
|
21
|
+
const branch = git(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
22
|
+
const head = git(cwd, ['rev-parse', 'HEAD']);
|
|
23
|
+
const origin = git(cwd, ['rev-parse', 'origin/main']);
|
|
24
|
+
const porcelain = git(cwd, ['status', '--porcelain']);
|
|
25
|
+
const detached = branch.stdout === 'HEAD';
|
|
26
|
+
const repoRoot = root.stdout || path.resolve(cwd);
|
|
27
|
+
return {
|
|
28
|
+
ok: root.status === 0,
|
|
29
|
+
repoRoot,
|
|
30
|
+
branch: branch.stdout,
|
|
31
|
+
detached,
|
|
32
|
+
head: head.stdout,
|
|
33
|
+
originMain: origin.stdout,
|
|
34
|
+
dirty: Boolean(porcelain.stdout),
|
|
35
|
+
tmpCheckout: repoRoot.includes('/private/tmp/') || repoRoot.includes('/tmp/'),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function productionDeployBlockedReason(info) {
|
|
40
|
+
if (!info?.ok) return 'not a git checkout';
|
|
41
|
+
if (info.tmpCheckout) return 'refusing production deploy from a temporary checkout';
|
|
42
|
+
if (info.detached) return 'refusing production deploy from detached HEAD';
|
|
43
|
+
if (info.branch !== 'main') {
|
|
44
|
+
return `refusing production deploy from ${info.branch}; origin/main only`;
|
|
45
|
+
}
|
|
46
|
+
for (const prefix of BLOCKED_BRANCH_PREFIXES) {
|
|
47
|
+
if (info.branch.startsWith(prefix)) {
|
|
48
|
+
return `refusing production deploy from ${info.branch}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!info.originMain) return 'origin/main is missing; fetch before deploying';
|
|
52
|
+
if (info.head !== info.originMain) {
|
|
53
|
+
return `HEAD (${info.head.slice(0, 12)}) != origin/main (${info.originMain.slice(0, 12)})`;
|
|
54
|
+
}
|
|
55
|
+
if (info.dirty) return 'working tree is not clean';
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function assertProductionDeployAllowed(cwd = process.cwd()) {
|
|
60
|
+
const info = inspectDeployGit(cwd);
|
|
61
|
+
const reason = productionDeployBlockedReason(info);
|
|
62
|
+
if (reason) {
|
|
63
|
+
const err = new Error(reason);
|
|
64
|
+
err.code = 'production_deploy_refused';
|
|
65
|
+
err.git = info;
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
return info;
|
|
69
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const DEFAULT_HEALTH_ORIGIN = 'https://agentsam.inneranimalmedia.com';
|
|
2
|
+
export const HEALTH_USER_AGENT = 'AgentSam-deploy-health/1';
|
|
3
|
+
|
|
4
|
+
export function parseWranglerVersionId(output = '') {
|
|
5
|
+
const text = String(output || '');
|
|
6
|
+
const m = text.match(/Current Version ID:\s*([0-9a-f-]{36})/i)
|
|
7
|
+
|| text.match(/Version ID:\s*([0-9a-f-]{36})/i);
|
|
8
|
+
return m ? m[1] : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveHealthOrigin({ env = process.env, wranglerConfigText = '' } = {}) {
|
|
12
|
+
const fromEnv = String(env.PRODUCT_HOST || env.AGENTSAM_HEALTH_ORIGIN || '').trim();
|
|
13
|
+
if (fromEnv) return fromEnv.replace(/\/+$/, '');
|
|
14
|
+
const m = String(wranglerConfigText || '').match(/"pattern"\s*:\s*"([^"]+)"/);
|
|
15
|
+
if (m) {
|
|
16
|
+
const host = m[1].trim();
|
|
17
|
+
if (host.startsWith('http://') || host.startsWith('https://')) return host.replace(/\/+$/, '');
|
|
18
|
+
return `https://${host.replace(/\/+$/, '')}`;
|
|
19
|
+
}
|
|
20
|
+
return DEFAULT_HEALTH_ORIGIN;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function probeDeployHealth(origin, { fetchImpl = globalThis.fetch, paths = ['/health', '/'] } = {}) {
|
|
24
|
+
const results = {};
|
|
25
|
+
for (const p of paths) {
|
|
26
|
+
const url = `${String(origin).replace(/\/+$/, '')}${p}`;
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetchImpl(url, {
|
|
29
|
+
redirect: 'manual',
|
|
30
|
+
headers: {
|
|
31
|
+
'User-Agent': HEALTH_USER_AGENT,
|
|
32
|
+
Accept: 'application/json,text/html,*/*',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
let body = null;
|
|
36
|
+
if (p === '/health') {
|
|
37
|
+
try { body = await res.json(); } catch { body = null; }
|
|
38
|
+
}
|
|
39
|
+
const ok = res.status >= 200 && res.status < 400;
|
|
40
|
+
results[p] = {
|
|
41
|
+
status: res.status,
|
|
42
|
+
ok,
|
|
43
|
+
...(body && typeof body === 'object' ? {
|
|
44
|
+
appOk: body.ok === true,
|
|
45
|
+
cloudflareConfigured: Boolean(body?.connections?.cloudflare?.configured),
|
|
46
|
+
} : {}),
|
|
47
|
+
};
|
|
48
|
+
} catch (err) {
|
|
49
|
+
results[p] = { status: 0, ok: false, error: err.message };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
origin,
|
|
54
|
+
results,
|
|
55
|
+
ok: Object.values(results).every((r) => r.ok),
|
|
56
|
+
};
|
|
57
|
+
}
|