@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
package/test/smoke.mjs
CHANGED
|
@@ -55,14 +55,17 @@ await assert.rejects(() => finalizeInboundOAuth({}, new Request('https://x'), {}
|
|
|
55
55
|
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/deploy'));
|
|
56
56
|
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/db'));
|
|
57
57
|
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/agent'));
|
|
58
|
+
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/model'));
|
|
59
|
+
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/reasoning'));
|
|
60
|
+
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/fast'));
|
|
58
61
|
assert.deepEqual(
|
|
59
62
|
listSlashCommands({ lane: 'deploy' }).map(({ cmd }) => cmd),
|
|
60
|
-
['/
|
|
63
|
+
['/settings', '/deploy', '/help', '/exit'],
|
|
61
64
|
'deploy lane keeps global shell controls and excludes commands from other lanes',
|
|
62
65
|
);
|
|
63
66
|
|
|
64
67
|
printContextSummary({
|
|
65
|
-
iam: { ready: true, source: 'sdk-
|
|
68
|
+
iam: { ready: true, source: 'sdk-key', detail: 'AGENTSAM_SDK_KEY' },
|
|
66
69
|
gcp: { source: 'vm-metadata', email: 'execos@project.iam.gserviceaccount.com' },
|
|
67
70
|
gcp_vm: true,
|
|
68
71
|
github: { source: 'gh-cli', account: 'user@example.com' },
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createAgentEvent, createUsageSnapshot } from '../src/telemetry/index.js';
|
|
4
|
+
|
|
5
|
+
test('AgentEvent is compact serializable provider-neutral telemetry', () => {
|
|
6
|
+
const event = createAgentEvent('context.compaction.completed', { before: 181_000, after: 72_000 }, {
|
|
7
|
+
runId: 'run_test', sequence: 4, timestamp: '2026-09-12T00:00:00.000Z',
|
|
8
|
+
});
|
|
9
|
+
assert.equal(event.type, 'context.compaction.completed');
|
|
10
|
+
assert.equal(event.run_id, 'run_test');
|
|
11
|
+
assert.doesNotThrow(() => JSON.stringify(event));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('current active context and cumulative run usage are different metrics', () => {
|
|
15
|
+
const usage = createUsageSnapshot({
|
|
16
|
+
current_context: { input_tokens: 48_000, window_tokens: 1_050_000 },
|
|
17
|
+
cumulative: { input_tokens: 310_000, output_tokens: 22_000, cached_input_tokens: 120_000, cache_write_tokens: 12_000, reasoning_tokens: 8_000 },
|
|
18
|
+
estimate_kind: 'provider',
|
|
19
|
+
});
|
|
20
|
+
assert.equal(usage.current_context.input_tokens, 48_000);
|
|
21
|
+
assert.equal(usage.cumulative.input_tokens, 310_000);
|
|
22
|
+
assert.equal(usage.provider_authoritative, true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
createRunReceipt,
|
|
27
|
+
createUsageReceipt,
|
|
28
|
+
createApprovalReceipt,
|
|
29
|
+
createTerminalJobReceipt,
|
|
30
|
+
} from '../src/telemetry/index.js';
|
|
31
|
+
|
|
32
|
+
test('runtime receipts are account-owned and emit no tenant/workspace/user ownership aliases', () => {
|
|
33
|
+
const run = createRunReceipt({
|
|
34
|
+
id: 'arun_1', account_id: 'au_1', mode: 'agent', status: 'running',
|
|
35
|
+
source_client: 'cli', surface: 'cli', reasoning_effort: 'high', requested_service_tier: 'fast',
|
|
36
|
+
});
|
|
37
|
+
assert.equal(run.account_id, 'au_1');
|
|
38
|
+
assert.equal(run.source_client, 'cli');
|
|
39
|
+
assert.equal(run.requested_service_tier, 'fast');
|
|
40
|
+
assert.equal('user_id' in run, false);
|
|
41
|
+
assert.equal('tenant_id' in run, false);
|
|
42
|
+
assert.equal('workspace_id' in run, false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('usage receipt models exactly one provider call and keeps cache/reasoning economics', () => {
|
|
46
|
+
const usage = createUsageReceipt({
|
|
47
|
+
id: 'ue_1', account_id: 'au_1', agent_run_id: 'arun_1', provider: 'openai', model_key: 'gpt-6-astra',
|
|
48
|
+
model_call_index: 2, provider_request_id: 'resp_1', input_tokens: 1000, cached_input_tokens: 600,
|
|
49
|
+
cache_write_tokens: 20, output_tokens: 100, reasoning_tokens: 40, cost_usd: 0.12, cost_basis: 'provider',
|
|
50
|
+
});
|
|
51
|
+
assert.equal(usage.total_tokens, 1100);
|
|
52
|
+
assert.equal(usage.cached_input_tokens, 600);
|
|
53
|
+
assert.equal(usage.reasoning_tokens, 40);
|
|
54
|
+
assert.equal(usage.model_call_index, 2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('approval and terminal receipts preserve lineage without turning D1 into transcript storage', () => {
|
|
58
|
+
const approval = createApprovalReceipt({
|
|
59
|
+
id: 'appr_1', account_id: 'au_1', agent_run_id: 'arun_1', tool_call_id: 'call_1', terminal_job_id: 'tjob_1',
|
|
60
|
+
action_summary: 'Deploy worker', sanitizedInput: { worker: 'api' }, risk_level: 'high',
|
|
61
|
+
});
|
|
62
|
+
assert.equal(approval.terminal_job_id, 'tjob_1');
|
|
63
|
+
assert.match(approval.sanitized_input_json, /worker/);
|
|
64
|
+
|
|
65
|
+
const job = createTerminalJobReceipt({
|
|
66
|
+
id: 'tjob_1', account_id: 'au_1', instance_id: 'tinst_1', connection_id: 'conn_1',
|
|
67
|
+
execos_run_id: 'xrun_1', status: 'running', idempotency_key: 'idem_1', max_attempts: 1,
|
|
68
|
+
});
|
|
69
|
+
assert.equal(job.execos_run_id, 'xrun_1');
|
|
70
|
+
assert.equal(job.max_attempts, 1);
|
|
71
|
+
assert.equal('stdout' in job, false);
|
|
72
|
+
assert.equal('stderr' in job, false);
|
|
73
|
+
assert.equal('command' in job, false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('runtime receipts refuse model-invented account identity', () => {
|
|
77
|
+
assert.throws(() => createRunReceipt({ id: 'arun_1' }), /account_id is required/);
|
|
78
|
+
assert.throws(() => createUsageReceipt({ id: 'ue_1', provider: 'openai', model_key: 'gpt-6-astra' }), /account_id is required/);
|
|
79
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { CLOUD, LOCAL, SANDBOX, getLaneThemeByName } from '../src/ui/theme.js';
|
|
5
|
+
|
|
6
|
+
test('SDK terminal lane metadata is provider and operator neutral', () => {
|
|
7
|
+
const serialized = JSON.stringify([LOCAL.meta, CLOUD.meta, SANDBOX.meta]);
|
|
8
|
+
assert.doesNotMatch(serialized, /sam(smac)?|inneranimalmedia|gcp|cloudflare|MY_CONTAINER/i);
|
|
9
|
+
assert.equal(LOCAL.meta.tunnelHint, 'current machine · real shell');
|
|
10
|
+
assert.equal(CLOUD.meta.label, 'Remote');
|
|
11
|
+
assert.equal(SANDBOX.meta.tunnelHint, 'isolated disposable runtime');
|
|
12
|
+
assert.equal(getLaneThemeByName('remote'), CLOUD);
|
|
13
|
+
assert.equal(getLaneThemeByName('cloud'), CLOUD);
|
|
14
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { getToolCatalog } from '../src/lib/tools.js';
|
|
4
|
+
import { hydrateToolSchemas, searchToolCards } from '../src/tools/index.js';
|
|
5
|
+
import { DEFAULT_RESULT_POLICY } from '../src/context/index.js';
|
|
6
|
+
|
|
7
|
+
test('every built-in SDK tool inherits a bounded result policy', () => {
|
|
8
|
+
for (const lane of ['fullstack', 'cms', 'data', 'crm', 'creative']) {
|
|
9
|
+
for (const tool of getToolCatalog(lane)) assert.deepEqual(tool.result_policy, DEFAULT_RESULT_POLICY);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('tools.search returns compact cards rather than full schemas', () => {
|
|
14
|
+
const catalog = Array.from({ length: 20 }, (_, index) => ({
|
|
15
|
+
name: index === 13 ? 'code.retrieve' : `tool.${index}`,
|
|
16
|
+
description: index === 13 ? 'Find symbols, callers, and semantic code matches.' : `Capability ${index}`,
|
|
17
|
+
category: index === 13 ? 'code' : 'misc',
|
|
18
|
+
risk: 'read',
|
|
19
|
+
input_schema: { type: 'object', required: ['query'], properties: { query: { type: 'string' }, giant: { description: 'x'.repeat(5000) } } },
|
|
20
|
+
}));
|
|
21
|
+
const result = searchToolCards(catalog, 'code symbols');
|
|
22
|
+
assert.ok(result.cards.length <= 8);
|
|
23
|
+
assert.equal(result.cards[0].tool, 'code.retrieve');
|
|
24
|
+
assert.deepEqual(result.cards[0].required, ['query']);
|
|
25
|
+
assert.equal('input_schema' in result.cards[0], false);
|
|
26
|
+
assert.ok(result.receipt.chars < 24_000);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('selected schemas hydrate without loading the full tool catalog', () => {
|
|
30
|
+
const catalog = Array.from({ length: 50 }, (_, index) => ({
|
|
31
|
+
name: `tool.${index}`,
|
|
32
|
+
description: `Capability ${index}`,
|
|
33
|
+
input_schema: { type: 'object', properties: { value: { type: 'string' }, padding: { description: 'x'.repeat(300) } } },
|
|
34
|
+
}));
|
|
35
|
+
const result = hydrateToolSchemas(catalog, ['tool.4', 'tool.22'], { maxTools: 4, maxChars: 5_000 });
|
|
36
|
+
assert.deepEqual(result.tools.map((tool) => tool.name), ['tool.4', 'tool.22']);
|
|
37
|
+
assert.equal(result.receipt.catalog_items, 50);
|
|
38
|
+
assert.equal(result.receipt.hydrated_tools, 2);
|
|
39
|
+
assert.ok(result.receipt.schema_chars < 5_000);
|
|
40
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { saveAccountSession } from '../src/lib/account-session.js';
|
|
7
|
+
import { createLocalSession } from '../src/lib/local-sessions.js';
|
|
8
|
+
import { collectWhoami } from '../src/commands/whoami.js';
|
|
9
|
+
import { runResume } from '../src/commands/resume.js';
|
|
10
|
+
|
|
11
|
+
function tempHome(t) {
|
|
12
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-ux-home-'));
|
|
13
|
+
t.after(() => fs.rmSync(home, { recursive: true, force: true }));
|
|
14
|
+
return home;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test('whoami validates persisted IAM identity while never returning the SDK or provider secret', async t => {
|
|
18
|
+
const home = tempHome(t);
|
|
19
|
+
saveAccountSession({ access_token: 'sdk_do_not_print', user_id: 'au_local' }, { home });
|
|
20
|
+
const envDir = path.join(home, '.agentsam', 'env.d');
|
|
21
|
+
fs.mkdirSync(envDir, { recursive: true });
|
|
22
|
+
const openaiFile = path.join(envDir, 'openai.env');
|
|
23
|
+
fs.writeFileSync(openaiFile, 'OPENAI_API_KEY=sk-never-print-this\n', { mode: 0o600 });
|
|
24
|
+
if (process.platform !== 'win32') fs.chmodSync(openaiFile, 0o600);
|
|
25
|
+
|
|
26
|
+
const status = await collectWhoami({
|
|
27
|
+
env: {}, home,
|
|
28
|
+
contextLoader: async token => {
|
|
29
|
+
assert.equal(token, 'sdk_do_not_print');
|
|
30
|
+
return { user_id: 'au_server', account_id: 'acct_server', email: 'dev@example.test', cloudflare: { ok: true }, byok: { openai: { configured: true, masked: 'secret' } } };
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
assert.equal(status.authenticated, true);
|
|
34
|
+
assert.equal(status.identity.account_id, 'acct_server');
|
|
35
|
+
assert.equal(status.sdk_credential.source, 'agentsam_account_session');
|
|
36
|
+
assert.equal(status.provider_credentials.find(row => row.provider === 'openai').configured, true);
|
|
37
|
+
const serialized = JSON.stringify(status);
|
|
38
|
+
assert.doesNotMatch(serialized, /sdk_do_not_print|sk-never-print-this|masked/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('resume restores saved cwd and session through the canonical shell runtime', async t => {
|
|
42
|
+
const home = tempHome(t);
|
|
43
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-resume-project-'));
|
|
44
|
+
t.after(() => fs.rmSync(cwd, { recursive: true, force: true }));
|
|
45
|
+
const session = createLocalSession({ cwd, title: 'Run wrangler whoami', model_key: 'openai:gpt-6-astra' }, { home });
|
|
46
|
+
let called = null;
|
|
47
|
+
const restored = await runResume([session.id], {
|
|
48
|
+
home,
|
|
49
|
+
write() {},
|
|
50
|
+
runShellImpl: async (argv, options) => { called = { argv, options }; },
|
|
51
|
+
});
|
|
52
|
+
assert.equal(restored.id, session.id);
|
|
53
|
+
assert.deepEqual(called.argv, []);
|
|
54
|
+
assert.equal(called.options.cwd, cwd);
|
|
55
|
+
assert.equal(called.options.session.id, session.id);
|
|
56
|
+
});
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Zero-dependency ANSI companion to the Agent Sam SDK TUI.
|
|
4
|
-
* Use this from Agent Sam PTY / Node scripts when you do not want Python+Rich.
|
|
5
|
-
*
|
|
6
|
-
* node scripts/demo_agentsam_ansi.mjs
|
|
7
|
-
* node scripts/demo_agentsam_ansi.mjs --scene sprite
|
|
8
|
-
* node scripts/demo_agentsam_ansi.mjs --check
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const HIDE = "\x1b[?25l";
|
|
12
|
-
const SHOW = "\x1b[?25h";
|
|
13
|
-
const CLEAR_LINE = "\x1b[2K";
|
|
14
|
-
const MAGENTA = "\x1b[38;2;211;54;130m";
|
|
15
|
-
const CYAN = "\x1b[38;2;45;212;191m";
|
|
16
|
-
const MUTED = "\x1b[38;2;122;154;170m";
|
|
17
|
-
const GREEN = "\x1b[38;2;163;184;0m";
|
|
18
|
-
const RESET = "\x1b[0m";
|
|
19
|
-
const BOLD = "\x1b[1m";
|
|
20
|
-
const DIM = "\x1b[2m";
|
|
21
|
-
|
|
22
|
-
const WALKER = [
|
|
23
|
-
[" · ", " ○ ", " │╲ ", " │ ○ ", " ○ ░ ", " ░ "],
|
|
24
|
-
[" · ", " ○ ", " │╲ ", " ○ ○ ", " ░ ", " ░ "],
|
|
25
|
-
[" · ", " ○ ", " │╲ ", " ○ │ ", " ○ ", " ░ "],
|
|
26
|
-
[" · ", " ○ ", " ╱│ ", " ○ │ ", " ○ ", " ░ "],
|
|
27
|
-
[" · ", " ○ ", " ╱│ ", " ○ │ ", " ○ ░ ", " ░ "],
|
|
28
|
-
[" · ", " ○ ", " ╱│ ", " ○ │ ", " ○ ░ ", " ░ "],
|
|
29
|
-
[" · ", " ○ ", " │╲ ", " ○ ○ ", " ○ ░ ", " ░ "],
|
|
30
|
-
[" · ", " ○ ", " │╲ ", " │ ○ ", " ○ ░ ", " ░ "],
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
34
|
-
|
|
35
|
-
function sleep(ms) {
|
|
36
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function paint(lines) {
|
|
40
|
-
const payload = lines.map((line) => `${CLEAR_LINE}${line}`).join("\n");
|
|
41
|
-
process.stdout.write(`${payload}\n`);
|
|
42
|
-
return lines.length;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function moveUp(n) {
|
|
46
|
-
if (n > 0) process.stdout.write(`\x1b[${n}A`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function box(title, bodyLines, width = 42) {
|
|
50
|
-
const inner = width - 2;
|
|
51
|
-
const top = `${MAGENTA}╭${"─".repeat(inner)}╮${RESET}`;
|
|
52
|
-
const label = ` ${title} `;
|
|
53
|
-
const titled = `${MAGENTA}╭${RESET}${BOLD}${CYAN}${label}${RESET}${MAGENTA}${"─".repeat(Math.max(0, inner - label.length))}╮${RESET}`;
|
|
54
|
-
const mid = bodyLines.map((line) => {
|
|
55
|
-
const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
56
|
-
const pad = Math.max(0, inner - 2 - visible.length);
|
|
57
|
-
return `${MAGENTA}│${RESET} ${line}${" ".repeat(pad)} ${MAGENTA}│${RESET}`;
|
|
58
|
-
});
|
|
59
|
-
const bot = `${MAGENTA}╰${"─".repeat(inner)}╯${RESET}`;
|
|
60
|
-
return [titled.length ? titled : top, ...mid, bot];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function sceneSprite({ ticks, delay }) {
|
|
64
|
-
let used = 0;
|
|
65
|
-
for (let i = 0; i < ticks; i += 1) {
|
|
66
|
-
const frame = WALKER[i % WALKER.length];
|
|
67
|
-
const caption = `${MUTED}Agent Sam is walking · frame ${String(i + 1).padStart(2, "0")}${RESET}`;
|
|
68
|
-
const lines = box("agent activity", [...frame.map((row) => `${CYAN}${BOLD}${row}${RESET}`), "", caption], 34);
|
|
69
|
-
if (used) moveUp(used);
|
|
70
|
-
used = paint(lines);
|
|
71
|
-
await sleep(delay);
|
|
72
|
-
}
|
|
73
|
-
return used;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function sceneDashboard({ ticks, delay }) {
|
|
77
|
-
const total = 2320;
|
|
78
|
-
let used = 0;
|
|
79
|
-
const started = Date.now();
|
|
80
|
-
for (let i = 0; i < ticks; i += 1) {
|
|
81
|
-
const done = Math.min(total, Math.round(((i + 1) / ticks) * total));
|
|
82
|
-
const symbols = Math.round(done * 2.76);
|
|
83
|
-
const elapsed = Math.round((Date.now() - started) / 1000);
|
|
84
|
-
const spin = BRAILLE[i % BRAILLE.length];
|
|
85
|
-
const walker = WALKER[i % WALKER.length];
|
|
86
|
-
const pct = Math.round((done / total) * 100);
|
|
87
|
-
const barFilled = Math.round(pct / 5);
|
|
88
|
-
const bar = `${CYAN}${"█".repeat(barFilled)}${MUTED}${"░".repeat(20 - barFilled)}${RESET}`;
|
|
89
|
-
const body = [
|
|
90
|
-
`${MUTED}stage${RESET} ${CYAN}${BOLD}parse_chunks${RESET} ${spin}`,
|
|
91
|
-
`${MUTED}files${RESET} ${done.toLocaleString()} / ${total.toLocaleString()}`,
|
|
92
|
-
`${MUTED}symbols${RESET} ${symbols.toLocaleString()}`,
|
|
93
|
-
`${MUTED}errors${RESET} ${GREEN}0${RESET}`,
|
|
94
|
-
`${MUTED}elapsed${RESET} ${elapsed}s`,
|
|
95
|
-
`${bar} ${pct}%`,
|
|
96
|
-
"",
|
|
97
|
-
...walker.map((row) => `${CYAN}${row}${RESET}`),
|
|
98
|
-
];
|
|
99
|
-
const lines = box("◉ Code Index", body, 46);
|
|
100
|
-
if (used) moveUp(used);
|
|
101
|
-
used = paint(lines);
|
|
102
|
-
await sleep(delay);
|
|
103
|
-
}
|
|
104
|
-
return used;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function sceneLogs({ delay }) {
|
|
108
|
-
const rows = [
|
|
109
|
-
["INFO", "Connected to Hyperdrive", "471 ms"],
|
|
110
|
-
["WORK", "Parsing batch", "24 files"],
|
|
111
|
-
["OK", "Checkpoint committed", "durable"],
|
|
112
|
-
];
|
|
113
|
-
const styles = { INFO: "\x1b[38;2;58;159;232m", WORK: "\x1b[38;2;230;172;0m", OK: GREEN };
|
|
114
|
-
for (const [level, message, detail] of rows) {
|
|
115
|
-
const now = new Date().toISOString().slice(11, 19);
|
|
116
|
-
process.stdout.write(
|
|
117
|
-
`${DIM}${now}${RESET} ${styles[level]}${level.padStart(4)}${RESET} ${message} ${DIM}· ${detail}${RESET}\n`,
|
|
118
|
-
);
|
|
119
|
-
await sleep(delay);
|
|
120
|
-
}
|
|
121
|
-
return 0;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function main() {
|
|
125
|
-
const args = new Set(process.argv.slice(2));
|
|
126
|
-
const check = args.has("--check");
|
|
127
|
-
const scene = [...args].find((a) => !a.startsWith("--")) || (args.has("--scene") ? null : "all");
|
|
128
|
-
const sceneFlagIdx = process.argv.indexOf("--scene");
|
|
129
|
-
const named = sceneFlagIdx >= 0 ? process.argv[sceneFlagIdx + 1] : scene;
|
|
130
|
-
const which = named && named !== "all" && !named.startsWith("--") ? named : "all";
|
|
131
|
-
const delay = check ? 0 : 90;
|
|
132
|
-
const ticks = check ? 4 : 32;
|
|
133
|
-
|
|
134
|
-
process.stdout.write(HIDE);
|
|
135
|
-
try {
|
|
136
|
-
if (which === "sprite" || which === "all") await sceneSprite({ ticks, delay });
|
|
137
|
-
if (which === "dashboard" || which === "all") await sceneDashboard({ ticks, delay });
|
|
138
|
-
if (which === "logs" || which === "all") await sceneLogs({ delay: check ? 0 : 120 });
|
|
139
|
-
process.stdout.write(`\n${GREEN}${BOLD}Done.${RESET} Use the Python agentsam tui command for Rich layouts.\n`);
|
|
140
|
-
} finally {
|
|
141
|
-
process.stdout.write(SHOW);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
main().catch((err) => {
|
|
146
|
-
process.stdout.write(SHOW);
|
|
147
|
-
console.error(err);
|
|
148
|
-
process.exit(1);
|
|
149
|
-
});
|
package/src/commands/tui.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { collectLocalStatus } from '../lib/local-status.js';
|
|
6
|
-
import { renderLocalStatus } from '../ui/ansi.js';
|
|
7
|
-
import { runMerkle } from './merkle.js';
|
|
8
|
-
|
|
9
|
-
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
10
|
-
const ANSI_DEMO = path.join(ROOT, 'examples', 'agentsam-tui-ansi.mjs');
|
|
11
|
-
const PYTHON_ROOT = path.join(ROOT, 'python');
|
|
12
|
-
|
|
13
|
-
function run(command, args, options = {}) {
|
|
14
|
-
return new Promise((resolve, reject) => {
|
|
15
|
-
const child = spawn(command, args, {
|
|
16
|
-
stdio: options.stdio || 'inherit',
|
|
17
|
-
cwd: options.cwd || process.cwd(),
|
|
18
|
-
env: options.env || process.env,
|
|
19
|
-
});
|
|
20
|
-
child.once('error', reject);
|
|
21
|
-
child.once('exit', (code, signal) => {
|
|
22
|
-
if (signal) reject(new Error(`${command} stopped by ${signal}`));
|
|
23
|
-
else if (code === 0) resolve(0);
|
|
24
|
-
else reject(new Error(`${command} exited ${code ?? 1}`));
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function hasScene(args) {
|
|
30
|
-
return args.includes('--scene') || args.some((arg) => arg.startsWith('--scene='));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function venvPython(venvRoot) {
|
|
34
|
-
return process.platform === 'win32'
|
|
35
|
-
? path.join(venvRoot, 'Scripts', 'python.exe')
|
|
36
|
-
: path.join(venvRoot, 'bin', 'python');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function findSystemPython() {
|
|
40
|
-
for (const candidate of process.platform === 'win32' ? ['python', 'py'] : ['python3', 'python']) {
|
|
41
|
-
try {
|
|
42
|
-
await run(candidate, ['--version'], { stdio: 'ignore' });
|
|
43
|
-
return candidate;
|
|
44
|
-
} catch {
|
|
45
|
-
// try next candidate
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function pythonHasRich(python) {
|
|
52
|
-
try {
|
|
53
|
-
await run(python, ['-c', 'import rich'], { stdio: 'ignore' });
|
|
54
|
-
return true;
|
|
55
|
-
} catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function runRich(args) {
|
|
61
|
-
const install = args.includes('--install');
|
|
62
|
-
const forwarded = args.filter((arg) => arg !== '--install');
|
|
63
|
-
const projectVenv = path.resolve(process.cwd(), '.agentsam', 'tui-venv');
|
|
64
|
-
let python = fs.existsSync(venvPython(projectVenv)) ? venvPython(projectVenv) : null;
|
|
65
|
-
|
|
66
|
-
if (!python && install) {
|
|
67
|
-
const systemPython = await findSystemPython();
|
|
68
|
-
if (!systemPython) throw new Error('Python 3 is required for the optional Rich TUI.');
|
|
69
|
-
fs.mkdirSync(path.dirname(projectVenv), { recursive: true });
|
|
70
|
-
console.log(`\n Agent Sam Rich TUI setup\n · creating ${projectVenv}`);
|
|
71
|
-
await run(systemPython, ['-m', 'venv', projectVenv]);
|
|
72
|
-
python = venvPython(projectVenv);
|
|
73
|
-
console.log(' · installing rich into isolated Agent Sam venv');
|
|
74
|
-
await run(python, ['-m', 'pip', 'install', 'rich>=13.7.0']);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!python) {
|
|
78
|
-
const systemPython = await findSystemPython();
|
|
79
|
-
if (systemPython && (await pythonHasRich(systemPython))) python = systemPython;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!python) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
'Rich TUI is optional and not installed. Run `agentsam tui rich --install` to create an isolated .agentsam/tui-venv.',
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const env = {
|
|
89
|
-
...process.env,
|
|
90
|
-
PYTHONPATH: [PYTHON_ROOT, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter),
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if (!hasScene(forwarded)) {
|
|
94
|
-
const status = await collectLocalStatus();
|
|
95
|
-
env.AGENTSAM_STATUS_JSON = JSON.stringify(status);
|
|
96
|
-
await run(python, ['-m', 'agentsam_sdk.tui.status'], { env });
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
await run(python, ['-m', 'agentsam_sdk.tui', ...forwarded], { env });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export async function runTui(argv = []) {
|
|
104
|
-
if (argv[0] === 'merkle') return runMerkle(['tui', ...argv.slice(1)]);
|
|
105
|
-
const args = [...argv];
|
|
106
|
-
const mode = args[0] === 'rich' || args[0] === 'ansi' ? args.shift() : 'ansi';
|
|
107
|
-
|
|
108
|
-
if (mode === 'rich') {
|
|
109
|
-
await runRich(args);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!hasScene(args)) {
|
|
114
|
-
const status = await collectLocalStatus();
|
|
115
|
-
process.stdout.write(`\n${renderLocalStatus(status)}\n\n`);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
await run(process.execPath, [ANSI_DEMO, ...args]);
|
|
120
|
-
}
|