@inneranimalmedia/agentsam-sdk 2.4.1 → 2.5.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/README.md +7 -6
- 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 +57 -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 +26 -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 +17 -4
- package/packages/identity/package.json +11 -2
- package/packages/identity/src/contracts/auth-config.js +98 -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 +57 -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/capabilities/manifest.json +32 -3
- package/protocol/capabilities/repository-snapshot.schema.json +1 -0
- package/protocol/context/context-budget.schema.json +41 -0
- package/protocol/context/context-item.schema.json +20 -0
- package/protocol/context/resolved-context-pack.schema.json +42 -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/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-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 +53 -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 +108 -56
- package/src/commands/context.js +14 -2
- package/src/commands/db.js +4 -7
- package/src/commands/deploy.js +17 -31
- package/src/commands/interactive.js +21 -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 +107 -0
- package/src/commands/ollama.js +259 -0
- package/src/commands/preferences.js +102 -0
- package/src/commands/product.js +86 -16
- package/src/commands/security.js +3 -3
- package/src/commands/shell.js +71 -27
- package/src/commands/skills.js +66 -0
- package/src/commands/start-local.js +1 -1
- package/src/commands/tunnel.js +5 -4
- package/src/context/budget.js +58 -0
- package/src/context/compact.js +28 -0
- package/src/context/index.js +5 -0
- package/src/context/resolve.js +84 -0
- package/src/context/result-policy.js +66 -0
- package/src/index.js +16 -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/bridge-client.js +7 -5
- package/src/lib/cli-preferences.js +74 -0
- package/src/lib/core-client.js +8 -8
- package/src/lib/deploy-receipt/index.js +5 -2
- package/src/lib/detect-context.js +6 -5
- package/src/lib/identity-scaffold.js +1 -1
- package/src/lib/local-scaffold.js +35 -33
- 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/save-sdk-token.js +1 -1
- package/src/lib/slash-commands.js +2 -1
- package/src/lib/tools.js +11 -5
- package/src/security/index.js +1 -0
- package/src/security/inventory.js +4 -1
- 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/tools/index.js +1 -0
- package/src/tools/search.js +70 -0
- package/src/ui/ansi.js +1 -1
- package/src/ui/boot.js +56 -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/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 +25 -0
- package/test/context.test.mjs +95 -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/merkle-persistence.test.mjs +91 -0
- package/test/merkle.test.mjs +77 -3
- package/test/models.test.mjs +37 -0
- package/test/ollama.test.mjs +94 -0
- package/test/project-config.test.mjs +81 -0
- package/test/project-rules.test.mjs +44 -0
- package/test/release-hygiene.test.mjs +34 -0
- package/test/repository-snapshot-view.test.mjs +112 -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 +8 -1
- package/test/skills.test.mjs +22 -0
- package/test/smoke.mjs +2 -2
- package/test/theme-portability.test.mjs +14 -0
- package/test/tools-search.test.mjs +27 -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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(import.meta.dirname, '..');
|
|
7
|
+
const read = (rel) => fs.readFileSync(path.join(root, rel), 'utf8');
|
|
8
|
+
|
|
9
|
+
test('2.5 release metadata and public terminal vocabulary are aligned', () => {
|
|
10
|
+
const pkg = JSON.parse(read('package.json'));
|
|
11
|
+
const manifest = read('agentsam.yaml');
|
|
12
|
+
const readme = read('README.md');
|
|
13
|
+
|
|
14
|
+
assert.equal(pkg.version, '2.5.0');
|
|
15
|
+
assert.match(manifest, /target_version: "2\.5\.0"/);
|
|
16
|
+
assert.match(manifest, /state: release_candidate/);
|
|
17
|
+
assert.match(manifest, /current_latest: "2\.4\.1"/);
|
|
18
|
+
assert.doesNotMatch(manifest, /^\s*- tui\s*$/m);
|
|
19
|
+
assert.doesNotMatch(readme, /agentsam tui|CLI\/TUI/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('dead branded splash paths are gone and UI preview stays dev-only', () => {
|
|
23
|
+
for (const rel of ['src/ui/splash.js', 'src/ui/splash-xterm.js', 'src/commands/tui.js']) {
|
|
24
|
+
assert.equal(fs.existsSync(path.join(root, rel)), false, `${rel} should not ship`);
|
|
25
|
+
}
|
|
26
|
+
for (const rel of ['scripts/internal/ui-preview-runner.mjs', 'scripts/internal/ansi-ui-preview.mjs']) {
|
|
27
|
+
assert.equal(fs.existsSync(path.join(root, rel)), true, `${rel} should remain available to contributors`);
|
|
28
|
+
}
|
|
29
|
+
assert.equal(fs.existsSync(path.join(root, 'src/ui/runtime-activity.js')), true, 'automatic runtime activity must ship with the product CLI');
|
|
30
|
+
|
|
31
|
+
const pkg = JSON.parse(read('package.json'));
|
|
32
|
+
assert.ok(pkg.files.includes('skills'));
|
|
33
|
+
assert.equal(pkg.files.includes('scripts'), false, 'internal preview scripts must not be published');
|
|
34
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {
|
|
4
|
+
buildRepositorySnapshotFacets,
|
|
5
|
+
projectRepositorySnapshot,
|
|
6
|
+
repositoryFileMatches,
|
|
7
|
+
} from '../src/capabilities/repository-snapshot-view.js';
|
|
8
|
+
|
|
9
|
+
function snapshot() {
|
|
10
|
+
return {
|
|
11
|
+
schema_version: 1,
|
|
12
|
+
capability: 'repository.snapshot',
|
|
13
|
+
snapshot_id: 'rsnap_0123456789abcdef01234567',
|
|
14
|
+
created_at: '2026-09-11T00:00:00.000Z',
|
|
15
|
+
content_hash: `sha256:${'1'.repeat(64)}`,
|
|
16
|
+
repository: { full_name: 'SamPrimeaux/agentsam-sdk', branch: 'main', revision_sha: 'abc123', dirty: false },
|
|
17
|
+
tree: {
|
|
18
|
+
merkle_root: `sha256:${'2'.repeat(64)}`,
|
|
19
|
+
metadata_root: `sha256:${'3'.repeat(64)}`,
|
|
20
|
+
manifest: { format: 'agentsam-merkle', version: 1, hash_algorithm: 'sha256', policy_hash: `sha256:${'4'.repeat(64)}` },
|
|
21
|
+
classifier: { format: 'agentsam-filemeta', version: 1, source: 'path+package+ast' },
|
|
22
|
+
stats: { files: 4, directories: 3, symlinks: 0, bytes: 1000 },
|
|
23
|
+
semantic_stats: { entries: 4, files: 4, packages: 2, ast_indexed: 4 },
|
|
24
|
+
paths: ['packages/identity/src/core/accounts.js', 'packages/identity/src/oauth/login.js', 'apps/cms/src/editor.tsx', 'apps/cms/src/assets.ts'],
|
|
25
|
+
files: [
|
|
26
|
+
{
|
|
27
|
+
path: 'packages/identity/src/core/accounts.js', type: 'file', size: 213, mode: 420, hash: `sha256:${'a'.repeat(64)}`,
|
|
28
|
+
package: '@inneranimalmedia/agentsam-sdk-identity', package_root: 'packages/identity', system: 'identity',
|
|
29
|
+
category: 'accounts', layer: 'core', kind: 'source', language: 'javascript', role: 'business-logic', execution_domain: 'unknown',
|
|
30
|
+
tags: ['account-scoped', 'authentication'], symbols: ['accountLinkingNotConfigured'], imports: [],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
path: 'packages/identity/src/oauth/login.js', type: 'file', size: 300, mode: 420, hash: `sha256:${'b'.repeat(64)}`,
|
|
34
|
+
package: '@inneranimalmedia/agentsam-sdk-identity', package_root: 'packages/identity', system: 'identity',
|
|
35
|
+
category: 'login', layer: 'oauth', kind: 'source', language: 'javascript', role: 'business-logic', execution_domain: 'unknown',
|
|
36
|
+
tags: ['authentication'], symbols: ['login'], imports: ['./accounts.js'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
path: 'apps/cms/src/editor.tsx', type: 'file', size: 250, mode: 420, hash: `sha256:${'c'.repeat(64)}`,
|
|
40
|
+
package: '@inneranimalmedia/cms', package_root: 'apps/cms', system: 'cms-frontend', category: 'editor', layer: 'ui',
|
|
41
|
+
kind: 'source', language: 'typescript', role: 'ui', execution_domain: 'browser', tags: ['cms'], symbols: ['CmsEditor'], imports: ['react'],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
path: 'apps/cms/src/assets.ts', type: 'file', size: 237, mode: 420, hash: `sha256:${'d'.repeat(64)}`,
|
|
45
|
+
package: '@inneranimalmedia/cms', package_root: 'apps/cms', system: 'cms-frontend', category: 'assets', layer: 'data',
|
|
46
|
+
kind: 'source', language: 'typescript', role: 'business-logic', execution_domain: 'browser', tags: ['cms', 'assets'], symbols: ['loadAsset'], imports: [],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
intelligence: { summary: { file_count: 4 } },
|
|
51
|
+
packages: [], knowledge: { configured: false },
|
|
52
|
+
analysis: {
|
|
53
|
+
trust_boundary: {
|
|
54
|
+
schema_version: 1, analyzer: 'agentsam-execution-boundary', metadata_root: `sha256:${'3'.repeat(64)}`,
|
|
55
|
+
complete: true, status: 'action-required', ok: false, browser_roots: 2, browser_reachable_files: 2, ast_files: 4,
|
|
56
|
+
findings: [{ kind: 'browser-server-import', severity: 'high', source: 'apps/cms/src/editor.tsx', target: 'server/secrets.ts', repair: { action: 'move-server-call-behind-api-boundary' } }],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
deploy: null,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test('repository snapshot index is bounded and preserves the three canonical identities', () => {
|
|
64
|
+
const full = snapshot();
|
|
65
|
+
const view = projectRepositorySnapshot(full, { view: 'index' });
|
|
66
|
+
assert.equal(view.snapshot_id, full.snapshot_id);
|
|
67
|
+
assert.equal(view.content_hash, full.content_hash);
|
|
68
|
+
assert.equal(view.tree.merkle_root, full.tree.merkle_root);
|
|
69
|
+
assert.equal(view.tree.metadata_root, full.tree.metadata_root);
|
|
70
|
+
assert.equal(view.tree.manifest.policy_hash, full.tree.manifest.policy_hash);
|
|
71
|
+
assert.equal(view.files, undefined);
|
|
72
|
+
assert.equal(view.projection.matched, 4);
|
|
73
|
+
assert.match(view.projection.projection_hash, /^sha256:[a-f0-9]{64}$/);
|
|
74
|
+
assert.equal(view.tree.paths, undefined);
|
|
75
|
+
assert.equal(view.tree.files, undefined);
|
|
76
|
+
assert.equal(view.analysis.trust_boundary.status, 'action-required');
|
|
77
|
+
assert.equal(view.analysis.trust_boundary.finding_count, 1);
|
|
78
|
+
assert.equal(view.analysis.trust_boundary.findings[0].repair_action, 'move-server-call-behind-api-boundary');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('repository snapshot files view filters semantics before bounding output', () => {
|
|
82
|
+
const full = snapshot();
|
|
83
|
+
const view = projectRepositorySnapshot(full, {
|
|
84
|
+
view: 'files',
|
|
85
|
+
filters: { system: ['identity'], tag: ['authentication'], category: ['accounts'], execution_domain: ['unknown'] },
|
|
86
|
+
limit: 1,
|
|
87
|
+
});
|
|
88
|
+
assert.equal(view.projection.matched, 1);
|
|
89
|
+
assert.equal(view.projection.returned, 1);
|
|
90
|
+
assert.equal(view.projection.truncated, false);
|
|
91
|
+
assert.deepEqual(view.files.map((entry) => entry.path), ['packages/identity/src/core/accounts.js']);
|
|
92
|
+
assert.equal(view.files[0].hash, undefined);
|
|
93
|
+
assert.equal(view.files[0].mode, undefined);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('path globs, AST selectors, and facet counts are deterministic', () => {
|
|
97
|
+
const full = snapshot();
|
|
98
|
+
const files = full.tree.files;
|
|
99
|
+
assert.equal(repositoryFileMatches(files[2], { path: ['apps/cms/**'], symbol: ['CmsEditor'] }), true);
|
|
100
|
+
assert.equal(repositoryFileMatches(files[2], { import: ['react'], language: ['javascript'] }), false);
|
|
101
|
+
const facets = buildRepositorySnapshotFacets(files);
|
|
102
|
+
assert.deepEqual(facets.systems.slice(0, 2), [
|
|
103
|
+
{ value: 'cms-frontend', count: 2 },
|
|
104
|
+
{ value: 'identity', count: 2 },
|
|
105
|
+
]);
|
|
106
|
+
assert.deepEqual(facets.tags.find((row) => row.value === 'authentication'), { value: 'authentication', count: 2 });
|
|
107
|
+
assert.deepEqual(facets.execution_domains, [
|
|
108
|
+
{ value: 'browser', count: 2 },
|
|
109
|
+
{ value: 'unknown', count: 2 },
|
|
110
|
+
]);
|
|
111
|
+
assert.equal(repositoryFileMatches(files[2], { execution_domain: ['browser'] }), true);
|
|
112
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import {
|
|
4
|
+
canAnimateTerminal,
|
|
5
|
+
createRuntimeActivity,
|
|
6
|
+
phaseForRuntimeEvent,
|
|
7
|
+
renderRuntimeActivityFrame,
|
|
8
|
+
renderRuntimeActivityPanel,
|
|
9
|
+
renderRuntimeActivityResult,
|
|
10
|
+
} from '../src/ui/runtime-activity.js';
|
|
11
|
+
import { runLocalAgent } from '../src/commands/shell.js';
|
|
12
|
+
|
|
13
|
+
test('runtime activity is automatic only in a real interactive terminal', () => {
|
|
14
|
+
assert.equal(canAnimateTerminal({ stdout: { isTTY: true }, env: { TERM: 'xterm-256color' } }), true);
|
|
15
|
+
assert.equal(canAnimateTerminal({ stdout: { isTTY: false }, env: { TERM: 'xterm-256color' } }), false);
|
|
16
|
+
assert.equal(canAnimateTerminal({ stdout: { isTTY: true }, env: { TERM: 'dumb' } }), false);
|
|
17
|
+
assert.equal(canAnimateTerminal({ stdout: { isTTY: true }, env: { TERM: 'xterm-256color', CI: '1' } }), false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('runtime renderer exposes thinking, tool, and context phases without product renderer vocabulary', () => {
|
|
21
|
+
assert.match(renderRuntimeActivityFrame({ phase: 'thinking', tick: 1, elapsedMs: 1250 }), /Agent Sam/);
|
|
22
|
+
assert.match(renderRuntimeActivityFrame({ phase: 'thinking', tick: 1, elapsedMs: 1250 }), /thinking/);
|
|
23
|
+
const panel = renderRuntimeActivityPanel({ phase: 'thinking', tick: 2, elapsedMs: 1250 });
|
|
24
|
+
assert.match(panel, /Agent Sam/);
|
|
25
|
+
assert.match(panel, /thinking · working locally/);
|
|
26
|
+
assert.match(panel, /\(●\)/);
|
|
27
|
+
assert.equal(panel.split('\n').length, 5);
|
|
28
|
+
assert.match(renderRuntimeActivityResult({ phase: 'done', elapsedMs: 1250 }), /done/);
|
|
29
|
+
assert.equal(phaseForRuntimeEvent({ type: 'model.started' }), 'thinking');
|
|
30
|
+
assert.equal(phaseForRuntimeEvent({ type: 'tool.started', tool: 'git.status' }), 'using git.status');
|
|
31
|
+
assert.equal(phaseForRuntimeEvent({ type: 'context.compaction.started' }), 'tidying context');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('runtime activity redraws automatically and restores the cursor', () => {
|
|
35
|
+
let output = '';
|
|
36
|
+
let clock = 0;
|
|
37
|
+
let intervalFn = null;
|
|
38
|
+
const activity = createRuntimeActivity({
|
|
39
|
+
write: (text) => { output += text; },
|
|
40
|
+
interactive: true,
|
|
41
|
+
now: () => clock,
|
|
42
|
+
setInterval: (fn) => { intervalFn = fn; return 7; },
|
|
43
|
+
clearInterval: () => {},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
activity.start('thinking');
|
|
47
|
+
clock = 320;
|
|
48
|
+
intervalFn();
|
|
49
|
+
activity.update('using repository.snapshot');
|
|
50
|
+
clock = 840;
|
|
51
|
+
activity.succeed('done');
|
|
52
|
+
|
|
53
|
+
assert.match(output, /thinking/);
|
|
54
|
+
assert.match(output, /using repository\.snapshot/);
|
|
55
|
+
assert.match(output, /done/);
|
|
56
|
+
assert.match(output, /\x1b\[\?25l/);
|
|
57
|
+
assert.match(output, /\x1b\[\?25h/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('/agent starts and completes product activity around the real request', async () => {
|
|
61
|
+
const phases = [];
|
|
62
|
+
let output = '';
|
|
63
|
+
const activity = {
|
|
64
|
+
start: (phase) => phases.push(`start:${phase}`),
|
|
65
|
+
succeed: (phase) => phases.push(`success:${phase}`),
|
|
66
|
+
fail: (phase) => phases.push(`fail:${phase}`),
|
|
67
|
+
};
|
|
68
|
+
const fetchImpl = async () => ({
|
|
69
|
+
ok: true,
|
|
70
|
+
status: 200,
|
|
71
|
+
async text() { return JSON.stringify({ ok: true, answer: 'ready' }); },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await runLocalAgent('inspect this repo', (text) => { output += text; }, { fetchImpl, activity, interactive: true });
|
|
75
|
+
assert.deepEqual(phases, ['start:thinking', 'success:done']);
|
|
76
|
+
assert.match(output, /"answer": "ready"/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
test('/agent restores product activity when the response body fails', async () => {
|
|
81
|
+
const phases = [];
|
|
82
|
+
const activity = {
|
|
83
|
+
start: (phase) => phases.push(`start:${phase}`),
|
|
84
|
+
succeed: (phase) => phases.push(`success:${phase}`),
|
|
85
|
+
fail: (phase) => phases.push(`fail:${phase}`),
|
|
86
|
+
};
|
|
87
|
+
const fetchImpl = async () => ({
|
|
88
|
+
ok: true,
|
|
89
|
+
status: 200,
|
|
90
|
+
async text() { throw new Error('body interrupted'); },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await assert.rejects(
|
|
94
|
+
runLocalAgent('inspect this repo', () => {}, { fetchImpl, activity, interactive: true }),
|
|
95
|
+
/response could not be read: body interrupted/,
|
|
96
|
+
);
|
|
97
|
+
assert.deepEqual(phases, ['start:thinking', 'fail:response error']);
|
|
98
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
|
|
5
|
+
const read = (relative) => fs.readFileSync(new URL(`../${relative}`, import.meta.url), 'utf8');
|
|
6
|
+
const exists = (relative) => fs.existsSync(new URL(`../${relative}`, import.meta.url));
|
|
7
|
+
|
|
8
|
+
const wrangler = read('apps/local-studio/backend/wrangler.jsonc');
|
|
9
|
+
const providers = read('apps/local-studio/shared/agentsam/src/model-providers.ts');
|
|
10
|
+
const runtime = read('apps/local-studio/backend/server/lib/cloudflare-runtime.ts');
|
|
11
|
+
const worker = read('apps/local-studio/backend/worker/index.js');
|
|
12
|
+
|
|
13
|
+
test('Local Studio backend is the only agentsam-sdk Cloudflare application owner', () => {
|
|
14
|
+
assert.equal(exists('wrangler.jsonc'), false);
|
|
15
|
+
assert.equal(exists('worker/index.js'), false);
|
|
16
|
+
assert.equal(exists('scripts/with-cloudflare-env.sh'), false);
|
|
17
|
+
assert.ok(exists('apps/local-studio/backend/worker/index.js'));
|
|
18
|
+
assert.equal(exists('apps/local-studio/backend/wrangler.ui.toml'), false);
|
|
19
|
+
assert.equal(exists('apps/local-studio/backend/wrangler.workmode.toml'), false);
|
|
20
|
+
assert.ok(exists('apps/local-studio/backend/wrangler.jsonc'));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('agentsam-sdk Worker uses the backend Worker entry, custom domain only, and canonical bindings', () => {
|
|
24
|
+
assert.match(wrangler, /"name"\s*:\s*"agentsam-sdk"/);
|
|
25
|
+
assert.match(wrangler, /"main"\s*:\s*"worker\/index\.js"/);
|
|
26
|
+
assert.match(wrangler, /"directory"\s*:\s*"\.\.\/\.output\/public"/);
|
|
27
|
+
assert.match(wrangler, /"workers_dev"\s*:\s*false/);
|
|
28
|
+
assert.match(wrangler, /"pattern"\s*:\s*"agentsam\.inneranimalmedia\.com"/);
|
|
29
|
+
assert.match(wrangler, /"binding"\s*:\s*"DB"/);
|
|
30
|
+
assert.match(wrangler, /"database_name"\s*:\s*"inneranimalmedia-business"/);
|
|
31
|
+
assert.match(wrangler, /"binding"\s*:\s*"WEBSITE_ASSETS"/);
|
|
32
|
+
assert.match(wrangler, /"bucket_name"\s*:\s*"agentsam-os-blueprint-content"/);
|
|
33
|
+
assert.match(wrangler, /"binding"\s*:\s*"AGENTSAM_WAI"/);
|
|
34
|
+
assert.match(wrangler, /"binding"\s*:\s*"EXECOS"/);
|
|
35
|
+
assert.match(wrangler, /"service"\s*:\s*"execos"/);
|
|
36
|
+
assert.match(wrangler, /"binding"\s*:\s*"PTY_SERVICE"/);
|
|
37
|
+
assert.match(wrangler, /"service_id"\s*:\s*"019db639-7c70-7071-8ef3-32ec0392a9ff"/);
|
|
38
|
+
assert.match(wrangler, /"IAM_ORIGIN"\s*:\s*"https:\/\/inneranimalmedia\.com"/);
|
|
39
|
+
assert.doesNotMatch(wrangler, /AGENTSAM_WORKER_ROLE/);
|
|
40
|
+
assert.doesNotMatch(wrangler, /"OLLAMA_BASE_URL"\s*:/);
|
|
41
|
+
assert.doesNotMatch(wrangler, /workers\.dev/);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('canonical Worker preserves vault routes and delegates app traffic to Nitro', () => {
|
|
45
|
+
assert.match(worker, /url\.pathname\.startsWith\("\/api\/vault\/"\)/);
|
|
46
|
+
assert.match(worker, /\/api\/vault\/secrets/);
|
|
47
|
+
assert.match(worker, /\/api\/vault\/unwrap/);
|
|
48
|
+
assert.match(worker, /return nitroWorker\.fetch\(request, env, context\)/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('model providers include Grok independently from Grok gate authentication', () => {
|
|
52
|
+
for (const id of ['grok', 'openai', 'gemini', 'workers-ai', 'ollama']) {
|
|
53
|
+
assert.match(providers, new RegExp(`id: ["']${id}["']`));
|
|
54
|
+
}
|
|
55
|
+
assert.match(providers, /credential: "XAI_API_KEY"/);
|
|
56
|
+
assert.match(providers, /binding: "AGENTSAM_WAI"/);
|
|
57
|
+
assert.match(providers, /binding: "EXECOS"/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('Ollama uses local loopback behind ExecOS with a portable explicit cwd', () => {
|
|
61
|
+
assert.match(runtime, /baseUrl: "http:\/\/127\.0\.0\.1:11434"/);
|
|
62
|
+
assert.match(runtime, /target: "local"/);
|
|
63
|
+
assert.match(runtime, /x-bridge-key/);
|
|
64
|
+
assert.match(runtime, /return explicit \|\| "\/"/);
|
|
65
|
+
assert.match(runtime, /JSON\.stringify\(\{ command, target: "local", cwd \}\)/);
|
|
66
|
+
assert.match(runtime, /public_base_url: false/);
|
|
67
|
+
assert.doesNotMatch(runtime, /ollama\.inneranimalmedia\.com/);
|
|
68
|
+
});
|
package/test/security.test.mjs
CHANGED
|
@@ -181,3 +181,49 @@ test('a missing transitive dependency cannot pass as complete coverage', async t
|
|
|
181
181
|
assert.equal(report.complete,false);
|
|
182
182
|
assert.ok(report.issues.some(issue=>issue.includes('Missing locked dependency edge')));
|
|
183
183
|
});
|
|
184
|
+
|
|
185
|
+
test('security scan includes AST/Merkle trust-boundary contradictions and mechanical repair evidence', async t => {
|
|
186
|
+
const f = fixture(t);
|
|
187
|
+
fs.mkdirSync(path.join(f.root, 'frontend'), { recursive: true });
|
|
188
|
+
fs.mkdirSync(path.join(f.root, 'backend'), { recursive: true });
|
|
189
|
+
fs.writeFileSync(path.join(f.root, 'frontend', 'client.ts'), [
|
|
190
|
+
"import { secretValue } from '../backend/secrets.ts';",
|
|
191
|
+
"export const value = secretValue;",
|
|
192
|
+
].join('\n'));
|
|
193
|
+
fs.writeFileSync(path.join(f.root, 'backend', 'secrets.ts'), [
|
|
194
|
+
"export const secretValue = process.env.API_KEY;",
|
|
195
|
+
].join('\n'));
|
|
196
|
+
|
|
197
|
+
const report = await scanProjectSecurity({ projectRoot: f.root, fetch: empty });
|
|
198
|
+
assert.equal(report.complete, true);
|
|
199
|
+
assert.equal(report.ok, false);
|
|
200
|
+
assert.equal(report.status, 'action-required');
|
|
201
|
+
assert.match(report.trust_boundary.merkle_root, /^sha256:/);
|
|
202
|
+
assert.match(report.trust_boundary.metadata_root, /^sha256:/);
|
|
203
|
+
const finding = report.trust_boundary.findings.find((item) => item.kind === 'browser-server-import');
|
|
204
|
+
assert.ok(finding);
|
|
205
|
+
assert.equal(finding.source, 'frontend/client.ts');
|
|
206
|
+
assert.equal(finding.target, 'backend/secrets.ts');
|
|
207
|
+
assert.equal(finding.repair.action, 'move-server-call-behind-api-boundary');
|
|
208
|
+
assert.equal(reportExitCode(report), 1);
|
|
209
|
+
const text = formatSecurityReport(report);
|
|
210
|
+
assert.match(text, /Trust boundary/);
|
|
211
|
+
assert.match(text, /frontend\/client\.ts/);
|
|
212
|
+
assert.match(text, /move-server-call-behind-api-boundary/);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('browser env and server-runtime imports are derived from syntax rather than folder names alone', async t => {
|
|
216
|
+
const f = fixture(t);
|
|
217
|
+
fs.mkdirSync(path.join(f.root, 'frontend', 'src'), { recursive: true });
|
|
218
|
+
fs.writeFileSync(path.join(f.root, 'frontend', 'src', 'client.ts'), [
|
|
219
|
+
"import fs from 'node:fs';",
|
|
220
|
+
"export const leaked = import.meta.env.VITE_API_KEY;",
|
|
221
|
+
"export const privateValue = process.env.INTERNAL_TOKEN;",
|
|
222
|
+
"export const exists = Boolean(fs);",
|
|
223
|
+
].join('\n'));
|
|
224
|
+
const report = await scanProjectSecurity({ projectRoot: f.root, fetch: empty });
|
|
225
|
+
const kinds = new Set(report.trust_boundary.findings.map((item) => item.kind));
|
|
226
|
+
assert.ok(kinds.has('browser-server-runtime-import'));
|
|
227
|
+
assert.ok(kinds.has('browser-public-secret-name'));
|
|
228
|
+
assert.ok(kinds.has('browser-private-env-access'));
|
|
229
|
+
});
|
package/test/shell.test.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import test from 'node:test';
|
|
7
|
-
import { dispatchShellLine, renderShellCatalog, tokenizeShellLine } from '../src/commands/shell.js';
|
|
7
|
+
import { dispatchShellLine, renderShellCatalog, renderShellPrompt, tokenizeShellLine } from '../src/commands/shell.js';
|
|
8
8
|
|
|
9
9
|
const repoRoot = path.resolve(new URL('..', import.meta.url).pathname);
|
|
10
10
|
|
|
@@ -14,6 +14,13 @@ test('shell tokenizer preserves Windows paths and quoted arguments', () => {
|
|
|
14
14
|
assert.deepEqual(tokenizeShellLine('/agent "inspect this repo"'), ['/agent', 'inspect this repo']);
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
test('interactive prompt derives username and cwd instead of hardcoding Agent Sam identity', () => {
|
|
18
|
+
const env = { USER: 'alice', HOME: '/Users/alice' };
|
|
19
|
+
assert.equal(renderShellPrompt('/Users/alice/code/demo', env), 'alice ~/code/demo > ');
|
|
20
|
+
assert.equal(renderShellPrompt('/Users/alice', env), 'alice ~ > ');
|
|
21
|
+
assert.equal(renderShellPrompt('/tmp/demo', env), 'alice /tmp/demo > ');
|
|
22
|
+
});
|
|
23
|
+
|
|
17
24
|
test('shell catalog advertises commands that the REPL owns', () => {
|
|
18
25
|
const catalog = renderShellCatalog();
|
|
19
26
|
assert.match(catalog, /\/help\s+Show Agent Sam commands/);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { getSkill, listSkills, loadSkill } from '../src/skills/index.js';
|
|
4
|
+
|
|
5
|
+
test('portable skill registry exposes AgentSam Jr Dev by id and alias', () => {
|
|
6
|
+
const listed = listSkills();
|
|
7
|
+
assert.ok(listed.some((skill) => skill.id === 'agentsam-jr-dev'));
|
|
8
|
+
|
|
9
|
+
assert.equal(getSkill('agentsam_jr_dev')?.id, 'agentsam-jr-dev');
|
|
10
|
+
assert.equal(getSkill('jr-dev')?.id, 'agentsam-jr-dev');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('AgentSam Jr Dev loads compact instructions and references on demand', () => {
|
|
14
|
+
const compact = loadSkill('agentsam-jr-dev');
|
|
15
|
+
assert.match(compact.instructions, /# AgentSam Jr Dev/);
|
|
16
|
+
assert.equal('references' in compact, false);
|
|
17
|
+
|
|
18
|
+
const full = loadSkill('agentsam_jr_dev', { references: true });
|
|
19
|
+
assert.equal(full.references.length, 2);
|
|
20
|
+
assert.match(full.references[0].content, /## HTTP/);
|
|
21
|
+
assert.match(full.references[1].content, /# Real Application Logic/);
|
|
22
|
+
});
|
package/test/smoke.mjs
CHANGED
|
@@ -57,12 +57,12 @@ assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/db'));
|
|
|
57
57
|
assert.ok(SLASH_COMMANDS.some((c) => c.cmd === '/agent'));
|
|
58
58
|
assert.deepEqual(
|
|
59
59
|
listSlashCommands({ lane: 'deploy' }).map(({ cmd }) => cmd),
|
|
60
|
-
['/help', '/deploy', '/exit'],
|
|
60
|
+
['/help', '/models', '/settings', '/deploy', '/exit'],
|
|
61
61
|
'deploy lane keeps global shell controls and excludes commands from other lanes',
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
printContextSummary({
|
|
65
|
-
iam: { ready: true, source: 'sdk-
|
|
65
|
+
iam: { ready: true, source: 'sdk-key', detail: 'AGENTSAM_SDK_KEY' },
|
|
66
66
|
gcp: { source: 'vm-metadata', email: 'execos@project.iam.gserviceaccount.com' },
|
|
67
67
|
gcp_vm: true,
|
|
68
68
|
github: { source: 'gh-cli', account: 'user@example.com' },
|
|
@@ -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,27 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { getToolCatalog } from '../src/lib/tools.js';
|
|
4
|
+
import { 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
|
+
});
|
|
@@ -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
|
-
});
|