@inneranimalmedia/agentsam-sdk 2.4.0 → 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 -5
- 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/cad.js +56 -6
- 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/auth.js +10 -19
- 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/open-url.js +66 -0
- 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/open-url.test.mjs +64 -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,102 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { cancel, intro, isCancel, outro, select, text } from '@clack/prompts';
|
|
5
|
+
import { collectModelsStatus } from './models.js';
|
|
6
|
+
import { detectCliProject, readCliPreferences, writeCliPreferences } from '../lib/cli-preferences.js';
|
|
7
|
+
|
|
8
|
+
function stopIfCancelled(value) {
|
|
9
|
+
if (!isCancel(value)) return value;
|
|
10
|
+
cancel('Agent Sam setup cancelled.');
|
|
11
|
+
const error = new Error('setup_cancelled');
|
|
12
|
+
error.code = 'AGENTSAM_SETUP_CANCELLED';
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function commandExists(command) {
|
|
17
|
+
const probe = process.platform === 'win32' ? ['where', [command]] : ['sh', ['-lc', `command -v ${command}`]];
|
|
18
|
+
return spawnSync(probe[0], probe[1], { stdio: 'ignore' }).status === 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function availableShells(env = process.env) {
|
|
22
|
+
const values = [];
|
|
23
|
+
const detected = path.basename(env.SHELL || env.ComSpec || '').trim();
|
|
24
|
+
if (detected) values.push(detected);
|
|
25
|
+
for (const shell of ['zsh', 'bash', 'fish', 'pwsh']) {
|
|
26
|
+
if (!values.includes(shell) && commandExists(shell)) values.push(shell);
|
|
27
|
+
}
|
|
28
|
+
return values.length ? values : ['shell'];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function modelOptions(status) {
|
|
32
|
+
const options = [{ value: 'auto', label: 'Automatic', hint: 'connected host/runtime decides' }];
|
|
33
|
+
if (status.local?.online) {
|
|
34
|
+
for (const row of status.local.models || []) {
|
|
35
|
+
if (row?.name) options.push({ value: `ollama:${row.name}`, label: `Ollama · ${row.name}`, hint: 'local' });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const provider of status.providers || []) {
|
|
39
|
+
if (provider.configured) options.push({ value: `${provider.id}:auto`, label: provider.label, hint: 'provider configured' });
|
|
40
|
+
}
|
|
41
|
+
return options;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function configureCliPreferences({ cwd = process.cwd(), firstRun = false } = {}) {
|
|
45
|
+
let identity = detectCliProject(cwd);
|
|
46
|
+
const existing = readCliPreferences(identity.root) || {};
|
|
47
|
+
intro(firstRun ? 'Welcome to Agent Sam' : 'Agent Sam settings');
|
|
48
|
+
|
|
49
|
+
const projectChoice = stopIfCancelled(await select({
|
|
50
|
+
message: 'Where should Agent Sam work?',
|
|
51
|
+
initialValue: 'current',
|
|
52
|
+
options: [
|
|
53
|
+
{ value: 'current', label: `This project · ${identity.project}`, hint: identity.root },
|
|
54
|
+
{ value: 'other', label: 'Choose another folder' },
|
|
55
|
+
],
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
if (projectChoice === 'other') {
|
|
59
|
+
const folder = stopIfCancelled(await text({
|
|
60
|
+
message: 'Project folder',
|
|
61
|
+
placeholder: identity.root,
|
|
62
|
+
defaultValue: identity.root,
|
|
63
|
+
validate(value) {
|
|
64
|
+
const resolved = path.resolve(String(value || ''));
|
|
65
|
+
if (!fs.existsSync(resolved)) return 'Folder does not exist';
|
|
66
|
+
if (!fs.statSync(resolved).isDirectory()) return 'Path is not a directory';
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
identity = detectCliProject(path.resolve(String(folder)));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const runtime = stopIfCancelled(await select({
|
|
73
|
+
message: 'Runtime',
|
|
74
|
+
initialValue: existing.runtime || 'local',
|
|
75
|
+
options: [
|
|
76
|
+
{ value: 'local', label: 'Local machine', hint: 'real shell + filesystem' },
|
|
77
|
+
{ value: 'remote', label: 'Remote', hint: 'connected remote runtime' },
|
|
78
|
+
{ value: 'sandbox', label: 'Sandbox', hint: 'isolated disposable runtime' },
|
|
79
|
+
],
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
const shells = availableShells();
|
|
83
|
+
const terminal = stopIfCancelled(await select({
|
|
84
|
+
message: 'Terminal',
|
|
85
|
+
initialValue: existing.terminal && shells.includes(existing.terminal) ? existing.terminal : shells[0],
|
|
86
|
+
options: shells.map((shell) => ({ value: shell, label: shell, hint: shell === shells[0] ? 'detected/default' : undefined })),
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
let status = { providers: [], local: { online: false, models: [] } };
|
|
90
|
+
try { status = await collectModelsStatus(); } catch { /* inventory is best-effort */ }
|
|
91
|
+
const models = modelOptions(status);
|
|
92
|
+
const initialModel = models.some((row) => row.value === existing.modelPreference) ? existing.modelPreference : 'auto';
|
|
93
|
+
const modelPreference = stopIfCancelled(await select({
|
|
94
|
+
message: 'Model preference',
|
|
95
|
+
initialValue: initialModel,
|
|
96
|
+
options: models,
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
const preferences = writeCliPreferences(identity.root, { runtime, terminal, modelPreference });
|
|
100
|
+
outro(`Ready · ${identity.project}`);
|
|
101
|
+
return { identity, preferences };
|
|
102
|
+
}
|
package/src/commands/product.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
|
-
import { getCapability, getCapabilityManifest, listCapabilities, repositorySnapshot } from '../capabilities/index.js';
|
|
4
|
+
import { getCapability, getCapabilityManifest, listCapabilities, projectRepositorySnapshot, repositorySnapshot } from '../capabilities/index.js';
|
|
5
5
|
import { getAddon, listAddons, listPresets } from '../presets/index.js';
|
|
6
|
+
import { projectConfigPath, readProjectConfig, setDeployTarget, setProductPreset, writeProjectConfig } from '../lib/project-config.js';
|
|
6
7
|
|
|
7
8
|
function parseCommon(argv = []) {
|
|
8
9
|
const out = { json: false, cwd: process.cwd(), positionals: [] };
|
|
@@ -18,24 +19,94 @@ function parseCommon(argv = []) {
|
|
|
18
19
|
export async function runInspect(argv = []) {
|
|
19
20
|
const opts = parseCommon(argv);
|
|
20
21
|
let churnDays = 30;
|
|
22
|
+
let view = 'index';
|
|
23
|
+
let viewExplicit = false;
|
|
24
|
+
let limit = 50;
|
|
25
|
+
let facetLimit = 48;
|
|
26
|
+
let pretty = false;
|
|
27
|
+
let snapshotFile = '';
|
|
28
|
+
let saveSnapshot = '';
|
|
29
|
+
const filters = {};
|
|
30
|
+
const addFilter = (field, value) => {
|
|
31
|
+
const clean = String(value || '').trim();
|
|
32
|
+
if (!clean) throw new Error(`inspect_${field}_value_required`);
|
|
33
|
+
(filters[field] ||= []).push(clean);
|
|
34
|
+
};
|
|
35
|
+
const filterFlags = new Map([
|
|
36
|
+
['--system', 'system'], ['--package', 'package'], ['--category', 'category'], ['--layer', 'layer'],
|
|
37
|
+
['--kind', 'kind'], ['--language', 'language'], ['--role', 'role'], ['--execution-domain', 'execution_domain'], ['--tag', 'tag'],
|
|
38
|
+
['--path', 'path'], ['--symbol', 'symbol'], ['--import', 'import'], ['--match', 'match'],
|
|
39
|
+
]);
|
|
21
40
|
for (let i = 0; i < opts.positionals.length; i += 1) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
else
|
|
41
|
+
const arg = opts.positionals[i];
|
|
42
|
+
if (arg === '--churn-days') churnDays = Number(opts.positionals[++i] || 30);
|
|
43
|
+
else if (arg === '--view') { view = String(opts.positionals[++i] || '').trim().toLowerCase(); viewExplicit = true; }
|
|
44
|
+
else if (arg === '--limit') limit = Number(opts.positionals[++i] || 50);
|
|
45
|
+
else if (arg === '--facet-limit') facetLimit = Number(opts.positionals[++i] || 48);
|
|
46
|
+
else if (arg === '--pretty') pretty = true;
|
|
47
|
+
else if (arg === '--full') { view = 'full'; viewExplicit = true; }
|
|
48
|
+
else if (arg === '--index') { view = 'index'; viewExplicit = true; }
|
|
49
|
+
else if (arg === '--snapshot-file') snapshotFile = String(opts.positionals[++i] || '').trim();
|
|
50
|
+
else if (arg === '--save-snapshot') saveSnapshot = String(opts.positionals[++i] || '').trim();
|
|
51
|
+
else if (filterFlags.has(arg)) addFilter(filterFlags.get(arg), opts.positionals[++i]);
|
|
52
|
+
else if (arg === 'repository') continue;
|
|
53
|
+
else throw new Error(`unknown inspect option: ${arg}`);
|
|
25
54
|
}
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
55
|
+
if (!['full', 'index', 'files'].includes(view)) throw new Error(`invalid inspect view: ${view}`);
|
|
56
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new Error('inspect limit must be an integer from 1..500');
|
|
57
|
+
if (!Number.isInteger(facetLimit) || facetLimit < 1 || facetLimit > 200) throw new Error('inspect facet limit must be an integer from 1..200');
|
|
58
|
+
if (Object.keys(filters).length && !viewExplicit) view = 'files';
|
|
59
|
+
else if (Object.keys(filters).length && view === 'full') view = 'files';
|
|
60
|
+
if (argv.includes('--snapshot-file') && !snapshotFile) throw new Error('inspect_snapshot_file_value_required');
|
|
61
|
+
if (argv.includes('--save-snapshot') && !saveSnapshot) throw new Error('inspect_save_snapshot_value_required');
|
|
62
|
+
|
|
63
|
+
const result = snapshotFile
|
|
64
|
+
? JSON.parse(fs.readFileSync(path.resolve(opts.cwd, snapshotFile), 'utf8'))
|
|
65
|
+
: await repositorySnapshot({ cwd: opts.cwd, churnDays });
|
|
66
|
+
if (!result || result.capability !== 'repository.snapshot' || !result.snapshot_id || !result.tree) {
|
|
67
|
+
throw new Error('inspect_snapshot_file_invalid');
|
|
68
|
+
}
|
|
69
|
+
if (saveSnapshot) {
|
|
70
|
+
const filename = path.resolve(opts.cwd, saveSnapshot);
|
|
71
|
+
fs.mkdirSync(path.dirname(filename), { recursive: true });
|
|
72
|
+
fs.writeFileSync(filename, `${JSON.stringify(result)}\n`, 'utf8');
|
|
73
|
+
}
|
|
74
|
+
const output = view === 'full' ? result : projectRepositorySnapshot(result, { view, filters, limit, facetLimit });
|
|
75
|
+
if (opts.json) process.stdout.write(`${JSON.stringify(output, null, pretty ? 2 : 0)}\n`);
|
|
76
|
+
else if (view !== 'full') {
|
|
77
|
+
console.log(`\nRepository snapshot ${result.snapshot_id} · ${view}`);
|
|
78
|
+
console.log(` repo ${result.repository.full_name || result.repository.repository_id || '(local)'}`);
|
|
79
|
+
console.log(` revision ${result.repository.revision_sha}`);
|
|
80
|
+
console.log(` merkle ${result.tree.merkle_root}`);
|
|
81
|
+
console.log(` metadata ${result.tree.metadata_root}`);
|
|
82
|
+
console.log(` matched ${output.projection.matched}${output.projection.truncated ? ` (showing ${output.projection.returned})` : ''}`);
|
|
83
|
+
const systems = output.facets.systems.slice(0, 12).map((row) => `${row.value}:${row.count}`).join(', ');
|
|
84
|
+
if (systems) console.log(` systems ${systems}`);
|
|
85
|
+
const trust = output.analysis?.trust_boundary;
|
|
86
|
+
if (trust) {
|
|
87
|
+
console.log(` trust ${trust.status}${trust.finding_count ? ` · ${trust.finding_count} contradiction${trust.finding_count === 1 ? '' : 's'}` : ''}`);
|
|
88
|
+
for (const finding of trust.findings || []) {
|
|
89
|
+
console.log(` ! ${finding.kind}: ${finding.source}${finding.target ? ` → ${finding.target}` : finding.specifier ? ` · ${finding.specifier}` : finding.env ? ` · ${finding.env}` : ''}`);
|
|
90
|
+
if (finding.repair_action) console.log(` repair: ${finding.repair_action}`);
|
|
91
|
+
}
|
|
92
|
+
if (trust.findings_truncated) console.log(` … ${trust.finding_count - trust.findings_returned} more contradiction(s); use --view full --json for full evidence`);
|
|
93
|
+
}
|
|
94
|
+
if (view === 'files') for (const file of output.files) console.log(` ${file.path} [${file.system || '-'} / ${file.category || '-'} / ${file.execution_domain || 'unknown'}]`);
|
|
95
|
+
console.log('');
|
|
96
|
+
} else {
|
|
29
97
|
console.log(`\nRepository snapshot ${result.snapshot_id}`);
|
|
30
98
|
console.log(` repo ${result.repository.full_name || result.repository.repository_id || '(local)'}`);
|
|
31
99
|
console.log(` revision ${result.repository.revision_sha}`);
|
|
32
100
|
console.log(` merkle ${result.tree.merkle_root}`);
|
|
101
|
+
console.log(` metadata ${result.tree.metadata_root}`);
|
|
33
102
|
console.log(` files ${result.intelligence.summary?.file_count ?? result.tree.stats?.files ?? 'unknown'}`);
|
|
34
103
|
console.log(` knowledge ${result.knowledge?.indexed ? result.knowledge.generation_id : result.knowledge?.configured ? 'configured / not indexed' : 'not configured'}`);
|
|
104
|
+
const trust = result.analysis?.trust_boundary;
|
|
105
|
+
if (trust) console.log(` trust ${trust.status}${trust.findings?.length ? ` · ${trust.findings.length} contradiction${trust.findings.length === 1 ? '' : 's'}` : ''}`);
|
|
35
106
|
console.log(` deploy ${result.deploy?.status || 'no trusted receipt'}`);
|
|
36
107
|
console.log(` content ${result.content_hash}\n`);
|
|
37
108
|
}
|
|
38
|
-
return
|
|
109
|
+
return output;
|
|
39
110
|
}
|
|
40
111
|
|
|
41
112
|
export async function runCapabilities(argv = []) {
|
|
@@ -53,16 +124,15 @@ export async function runCapabilities(argv = []) {
|
|
|
53
124
|
return result;
|
|
54
125
|
}
|
|
55
126
|
|
|
56
|
-
function projectConfigPath(cwd) { return path.join(cwd, '.agentsam', 'config.json'); }
|
|
57
127
|
function featureStatePath(cwd) { return path.join(cwd, '.agentsam', 'features.json'); }
|
|
58
128
|
|
|
59
129
|
export function applyPresetSelection(cwd, preset) {
|
|
60
130
|
const filename = projectConfigPath(cwd);
|
|
61
131
|
if (!fs.existsSync(filename)) throw new Error('not_agentsam_project');
|
|
62
|
-
const config =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return
|
|
132
|
+
const config = readProjectConfig(cwd);
|
|
133
|
+
setProductPreset(config, preset);
|
|
134
|
+
writeProjectConfig(cwd, config);
|
|
135
|
+
return config;
|
|
66
136
|
}
|
|
67
137
|
|
|
68
138
|
export async function runAdd(argv = []) {
|
|
@@ -84,9 +154,9 @@ export async function runAdd(argv = []) {
|
|
|
84
154
|
state.features[addon.id] = { selected: true, capabilities: addon.capabilities, selected_at: new Date().toISOString() };
|
|
85
155
|
fs.writeFileSync(featureStatePath(cwd), `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
86
156
|
if (addon.id === 'deploy-cloudflare') {
|
|
87
|
-
const config =
|
|
88
|
-
config
|
|
89
|
-
|
|
157
|
+
const config = readProjectConfig(cwd);
|
|
158
|
+
setDeployTarget(config, 'cloudflare');
|
|
159
|
+
writeProjectConfig(cwd, config);
|
|
90
160
|
}
|
|
91
161
|
const result = { ok: true, feature: addon.id, capabilities: addon.capabilities, description: addon.description, state_file: '.agentsam/features.json' };
|
|
92
162
|
if (opts.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
package/src/commands/security.js
CHANGED
|
@@ -3,16 +3,16 @@ import { scanProjectSecurity, reportExitCode, repairProject, formatSecurityRepor
|
|
|
3
3
|
import { runProcess } from '../security/process.js';
|
|
4
4
|
|
|
5
5
|
const help = [
|
|
6
|
-
'Agent Sam ·
|
|
6
|
+
'Agent Sam · security',
|
|
7
7
|
' agentsam security scan [--path DIR] [--offline] [--json]',
|
|
8
8
|
' agentsam security check --log FILE [--path DIR] [--json]',
|
|
9
9
|
' agentsam security run [--path DIR] [--json] -- COMMAND ARGS...',
|
|
10
10
|
' agentsam security repair [--path DIR] [--log FILE] [--apply] [--verify SCRIPT] [--json]',
|
|
11
11
|
' agentsam sca ... alias',
|
|
12
12
|
'',
|
|
13
|
-
'scan/check:
|
|
13
|
+
'scan/check: npm/OSV evidence plus AST+Merkle client/server trust-boundary contradictions.',
|
|
14
14
|
'run: execute an explicitly supplied install/build command, then check its logs and dependencies.',
|
|
15
|
-
'repair:
|
|
15
|
+
'repair: plans dependency + trust-boundary repairs; --apply automates dependency-only repairs in an isolated worktree.',
|
|
16
16
|
'Online lookup sends package names/versions to OSV, never source code or deployment logs.',
|
|
17
17
|
'Offline is inventory only and exits 2. pnpm/Yarn/Bun graphs are explicitly unsupported.',
|
|
18
18
|
'Exit: 0 clean/verified, 1 unresolved findings or command failure, 2 incomplete/error.',
|
package/src/commands/shell.js
CHANGED
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
4
5
|
import { spawnSync } from 'node:child_process';
|
|
5
6
|
import { SLASH_COMMANDS, SHELL_PHASES } from '../lib/slash-commands.js';
|
|
6
7
|
import { runContext } from './context.js';
|
|
7
8
|
import { runDb } from './db.js';
|
|
8
9
|
import { runDeploy } from './deploy.js';
|
|
9
10
|
import { runStatus } from './status.js';
|
|
10
|
-
import {
|
|
11
|
+
import { runModels } from './models.js';
|
|
12
|
+
import { configureCliPreferences } from './preferences.js';
|
|
13
|
+
import { createRuntimeActivity } from '../ui/runtime-activity.js';
|
|
11
14
|
|
|
12
15
|
function writeLine(write, value = '') {
|
|
13
16
|
write(`${value}\n`);
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
export function compactCwd(value, home = process.env.HOME || process.env.USERPROFILE || '') {
|
|
20
|
+
const cwd = path.resolve(value);
|
|
21
|
+
const resolvedHome = home ? path.resolve(home) : '';
|
|
22
|
+
if (!resolvedHome) return cwd;
|
|
23
|
+
if (cwd === resolvedHome) return '~';
|
|
24
|
+
return cwd.startsWith(`${resolvedHome}${path.sep}`) ? `~${cwd.slice(resolvedHome.length)}` : cwd;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function shellUsername(env = process.env) {
|
|
28
|
+
const fromEnv = String(env.USER || env.USERNAME || '').trim();
|
|
29
|
+
if (fromEnv) return fromEnv;
|
|
30
|
+
try { return String(os.userInfo().username || '').trim() || 'user'; }
|
|
31
|
+
catch { return 'user'; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function renderShellPrompt(cwd, env = process.env) {
|
|
35
|
+
return `${shellUsername(env)} ${compactCwd(cwd, env.HOME || env.USERPROFILE || '')} > `;
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
export function tokenizeShellLine(input = '') {
|
|
17
39
|
const source = String(input);
|
|
18
40
|
const tokens = [];
|
|
@@ -63,9 +85,7 @@ export function renderShellCatalog() {
|
|
|
63
85
|
╚════════════════════════════════╝
|
|
64
86
|
|
|
65
87
|
Local PTY agentsam start-local ws://127.0.0.1:3099
|
|
66
|
-
|
|
67
|
-
Rich TUI agentsam tui rich optional richer Python UI
|
|
68
|
-
agentsam tui rich --install
|
|
88
|
+
Models agentsam models providers + local model inventory
|
|
69
89
|
DB agentsam db status local SQLite
|
|
70
90
|
|
|
71
91
|
Current milestone: ${next?.label ?? 'local terminal experience'}
|
|
@@ -86,35 +106,44 @@ function parseDeployOptions(args, cwd) {
|
|
|
86
106
|
return opts;
|
|
87
107
|
}
|
|
88
108
|
|
|
89
|
-
async function withProcessCwd(cwd, fn) {
|
|
90
|
-
const previous = process.cwd();
|
|
91
|
-
process.chdir(cwd);
|
|
92
|
-
try {
|
|
93
|
-
return await fn();
|
|
94
|
-
} finally {
|
|
95
|
-
process.chdir(previous);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
109
|
|
|
99
|
-
async function runLocalAgent(goal, write) {
|
|
110
|
+
export async function runLocalAgent(goal, write, options = {}) {
|
|
100
111
|
if (!goal) {
|
|
101
112
|
writeLine(write, ' Usage: /agent <goal>');
|
|
102
113
|
writeLine(write, ' Requires the local Agent Sam dev server (default http://127.0.0.1:8787).');
|
|
103
114
|
return;
|
|
104
115
|
}
|
|
105
116
|
const base = String(process.env.AGENTSAM_LOCAL_URL || 'http://127.0.0.1:8787').replace(/\/$/, '');
|
|
117
|
+
const fetchImpl = options.fetchImpl || fetch;
|
|
118
|
+
const activity = options.activity || createRuntimeActivity({
|
|
119
|
+
write,
|
|
120
|
+
phase: 'thinking',
|
|
121
|
+
interactive: options.interactive,
|
|
122
|
+
});
|
|
106
123
|
let response;
|
|
124
|
+
activity.start('thinking');
|
|
107
125
|
try {
|
|
108
|
-
response = await
|
|
126
|
+
response = await fetchImpl(`${base}/api/agentsam/message`, {
|
|
109
127
|
method: 'POST',
|
|
110
128
|
headers: { 'content-type': 'application/json' },
|
|
111
129
|
body: JSON.stringify({ message: goal }),
|
|
112
130
|
});
|
|
113
131
|
} catch (error) {
|
|
132
|
+
activity.fail('unavailable');
|
|
114
133
|
throw new Error(`local Agent Sam unavailable at ${base} — run \`npm run dev\` first (${error?.message || error})`);
|
|
115
134
|
}
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
let text;
|
|
136
|
+
try {
|
|
137
|
+
text = await response.text();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
activity.fail('response error');
|
|
140
|
+
throw new Error(`local Agent Sam response could not be read: ${error?.message || error}`);
|
|
141
|
+
}
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
activity.fail(`HTTP ${response.status}`);
|
|
144
|
+
throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`);
|
|
145
|
+
}
|
|
146
|
+
activity.succeed('done');
|
|
118
147
|
try {
|
|
119
148
|
writeLine(write, JSON.stringify(JSON.parse(text), null, 2));
|
|
120
149
|
} catch {
|
|
@@ -192,14 +221,19 @@ export async function dispatchShellLine(line, state = {}) {
|
|
|
192
221
|
await runDb(args.length ? args : ['status'], { cwd: state.cwd });
|
|
193
222
|
break;
|
|
194
223
|
case '/agent':
|
|
195
|
-
await runLocalAgent(args.join(' '), write);
|
|
224
|
+
await runLocalAgent(args.join(' '), write, { interactive: state.interactive });
|
|
225
|
+
break;
|
|
226
|
+
case '/models':
|
|
227
|
+
await runModels(args, { cwd: state.cwd, write });
|
|
196
228
|
break;
|
|
229
|
+
case '/settings': {
|
|
230
|
+
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false });
|
|
231
|
+
state.cwd = configured.identity.root;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
197
234
|
case '/logs':
|
|
198
235
|
await showLocalLogs(state.cwd, write);
|
|
199
236
|
break;
|
|
200
|
-
case '/tui':
|
|
201
|
-
await withProcessCwd(state.cwd, () => runTui(args));
|
|
202
|
-
break;
|
|
203
237
|
case '/deploy':
|
|
204
238
|
await runDeploy(parseDeployOptions(args, state.cwd));
|
|
205
239
|
break;
|
|
@@ -217,7 +251,7 @@ export async function dispatchShellLine(line, state = {}) {
|
|
|
217
251
|
|
|
218
252
|
export async function runShell(argv = [], options = {}) {
|
|
219
253
|
const write = options.write || ((text) => process.stdout.write(text));
|
|
220
|
-
const state = { cwd: path.resolve(options.cwd || process.cwd()), write };
|
|
254
|
+
const state = { cwd: path.resolve(options.cwd || process.cwd()), write, interactive: options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY) };
|
|
221
255
|
const sub = argv[0] || '';
|
|
222
256
|
|
|
223
257
|
if (sub === 'list' || sub === 'status') {
|
|
@@ -232,13 +266,20 @@ export async function runShell(argv = [], options = {}) {
|
|
|
232
266
|
}
|
|
233
267
|
if (sub) throw new Error(`unknown shell option: ${sub}`);
|
|
234
268
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
269
|
+
if (options.intro !== 'quiet') {
|
|
270
|
+
write(renderShellCatalog());
|
|
271
|
+
writeLine(write, ' Interactive shell ready. Type /help for commands; /exit to return to your host shell.');
|
|
272
|
+
writeLine(write, '');
|
|
273
|
+
}
|
|
238
274
|
|
|
239
275
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY) });
|
|
276
|
+
const promptText = () => {
|
|
277
|
+
if (typeof options.prompt === 'function') return options.prompt(state);
|
|
278
|
+
if (typeof options.prompt === 'string' && options.prompt) return options.prompt;
|
|
279
|
+
return renderShellPrompt(state.cwd);
|
|
280
|
+
};
|
|
240
281
|
if (rl.terminal) {
|
|
241
|
-
rl.setPrompt(
|
|
282
|
+
rl.setPrompt(promptText());
|
|
242
283
|
rl.prompt();
|
|
243
284
|
}
|
|
244
285
|
|
|
@@ -248,6 +289,9 @@ export async function runShell(argv = [], options = {}) {
|
|
|
248
289
|
rl.close();
|
|
249
290
|
break;
|
|
250
291
|
}
|
|
251
|
-
if (rl.terminal)
|
|
292
|
+
if (rl.terminal) {
|
|
293
|
+
rl.setPrompt(promptText());
|
|
294
|
+
rl.prompt();
|
|
295
|
+
}
|
|
252
296
|
}
|
|
253
297
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getSkill, listSkills, loadSkill } from '../skills/index.js';
|
|
2
|
+
|
|
3
|
+
function parse(argv = []) {
|
|
4
|
+
const out = { id: '', json: false, references: false };
|
|
5
|
+
for (const arg of argv) {
|
|
6
|
+
if (arg === '--json') out.json = true;
|
|
7
|
+
else if (arg === '--references' || arg === '--full') out.references = true;
|
|
8
|
+
else if (arg === '--help' || arg === '-h') out.help = true;
|
|
9
|
+
else if (arg.startsWith('-')) throw new Error(`Unknown skills option: ${arg}`);
|
|
10
|
+
else if (!out.id) out.id = arg;
|
|
11
|
+
else throw new Error('Only one skill id or alias may be supplied.');
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function help() {
|
|
17
|
+
return [
|
|
18
|
+
'Agent Sam · skills',
|
|
19
|
+
' agentsam skills list portable skills',
|
|
20
|
+
' agentsam skills <id-or-alias> print compact skill instructions',
|
|
21
|
+
' agentsam skills <id-or-alias> --references include on-demand references',
|
|
22
|
+
' agentsam skills <id-or-alias> --json machine-readable skill payload',
|
|
23
|
+
'',
|
|
24
|
+
'Examples:',
|
|
25
|
+
' agentsam skills quick-bytes',
|
|
26
|
+
' agentsam skills no-regress --references',
|
|
27
|
+
'',
|
|
28
|
+
].join('\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function runSkills(argv = []) {
|
|
32
|
+
const opts = parse(argv);
|
|
33
|
+
if (opts.help) {
|
|
34
|
+
process.stdout.write(help());
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!opts.id) {
|
|
39
|
+
const skills = listSkills();
|
|
40
|
+
if (opts.json) process.stdout.write(`${JSON.stringify(skills, null, 2)}\n`);
|
|
41
|
+
else {
|
|
42
|
+
process.stdout.write('Agent Sam · portable skills\n\n');
|
|
43
|
+
for (const skill of skills) {
|
|
44
|
+
const aliases = (skill.aliases || []).length ? ` (${skill.aliases.join(', ')})` : '';
|
|
45
|
+
process.stdout.write(` ${skill.id}${aliases}\n ${skill.description}\n`);
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write('\n');
|
|
48
|
+
}
|
|
49
|
+
return skills;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!getSkill(opts.id)) throw new Error(`Unknown skill: ${opts.id}`);
|
|
53
|
+
const loaded = loadSkill(opts.id, { references: opts.references });
|
|
54
|
+
if (opts.json) {
|
|
55
|
+
process.stdout.write(`${JSON.stringify(loaded, null, 2)}\n`);
|
|
56
|
+
return loaded;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
process.stdout.write(`${loaded.instructions.trimEnd()}\n`);
|
|
60
|
+
if (loaded.references?.length) {
|
|
61
|
+
for (const reference of loaded.references) {
|
|
62
|
+
process.stdout.write(`\n---\nReference: ${reference.path}\n---\n\n${reference.content.trimEnd()}\n`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return loaded;
|
|
66
|
+
}
|
|
@@ -50,7 +50,7 @@ export async function runStartLocal(opts = {}) {
|
|
|
50
50
|
Project services:
|
|
51
51
|
npm run dev → local Agent Sam API http://127.0.0.1:${config?.dev_port ?? 8787}
|
|
52
52
|
npm run db:status → inspect local SQLite
|
|
53
|
-
|
|
53
|
+
npx agentsam → enter Agent Sam
|
|
54
54
|
|
|
55
55
|
Press Ctrl+C to stop.
|
|
56
56
|
`);
|
package/src/commands/tunnel.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { authenticateViaBrowser } from '../lib/auth.js';
|
|
9
9
|
import { postJson } from '../lib/core-client.js';
|
|
10
|
+
import { resolveSdkKey } from '../../packages/identity/src/contracts/auth-config.js';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_PORT = 3099;
|
|
12
13
|
|
|
@@ -32,7 +33,7 @@ function parseArgs(argv) {
|
|
|
32
33
|
else if (a === '--platform') opts.platform = argv[++i] || opts.platform;
|
|
33
34
|
else if (a === '--shell') opts.shell = argv[++i] || opts.shell;
|
|
34
35
|
else if (a === '--token' && argv[i + 1]) {
|
|
35
|
-
process.env.
|
|
36
|
+
process.env.AGENTSAM_SDK_KEY = argv[++i];
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
return opts;
|
|
@@ -50,12 +51,12 @@ function ensureCloudflared() {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
async function resolveToken() {
|
|
53
|
-
const existing =
|
|
54
|
+
const existing = resolveSdkKey(process.env);
|
|
54
55
|
if (existing.startsWith('sdk_')) return existing;
|
|
55
56
|
const session = await authenticateViaBrowser();
|
|
56
57
|
const tok = String(session?.access_token || '').trim();
|
|
57
|
-
if (!tok.startsWith('sdk_')) throw new Error('IAM auth did not return an sdk_ bearer
|
|
58
|
-
process.env.
|
|
58
|
+
if (!tok.startsWith('sdk_')) throw new Error('IAM auth did not return an sdk_ bearer key');
|
|
59
|
+
process.env.AGENTSAM_SDK_KEY = tok;
|
|
59
60
|
return tok;
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const DEFAULT_CONTEXT_RATIOS = Object.freeze({
|
|
2
|
+
targetInputRatio: 0.60,
|
|
3
|
+
hardInputRatio: 0.85,
|
|
4
|
+
charsPerToken: 4,
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
function ratio(value, label) {
|
|
8
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0 || value > 1) throw new RangeError(`${label} must be > 0 and <= 1`);
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function positiveNumber(value, label) {
|
|
13
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) throw new RangeError(`${label} must be positive`);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function positiveInteger(value, label) {
|
|
18
|
+
if (!Number.isInteger(value) || value < 1) throw new RangeError(`${label} must be a positive integer`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createContextBudget(options = {}) {
|
|
23
|
+
const windowTokens = positiveInteger(options.windowTokens, 'windowTokens');
|
|
24
|
+
const charsPerToken = positiveNumber(options.charsPerToken ?? DEFAULT_CONTEXT_RATIOS.charsPerToken, 'charsPerToken');
|
|
25
|
+
const targetInputRatio = ratio(options.targetInputRatio ?? DEFAULT_CONTEXT_RATIOS.targetInputRatio, 'targetInputRatio');
|
|
26
|
+
const hardInputRatio = ratio(options.hardInputRatio ?? DEFAULT_CONTEXT_RATIOS.hardInputRatio, 'hardInputRatio');
|
|
27
|
+
if (targetInputRatio >= hardInputRatio) throw new RangeError('targetInputRatio must be lower than hardInputRatio');
|
|
28
|
+
|
|
29
|
+
const windowChars = Math.floor(windowTokens * charsPerToken);
|
|
30
|
+
const targetInputTokens = Math.floor(windowTokens * targetInputRatio);
|
|
31
|
+
const hardInputTokens = Math.floor(windowTokens * hardInputRatio);
|
|
32
|
+
const targetInputChars = Math.floor(windowChars * targetInputRatio);
|
|
33
|
+
const hardInputChars = Math.floor(windowChars * hardInputRatio);
|
|
34
|
+
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
windowTokens,
|
|
37
|
+
windowChars,
|
|
38
|
+
charsPerToken,
|
|
39
|
+
targetInputRatio,
|
|
40
|
+
hardInputRatio,
|
|
41
|
+
targetInputTokens,
|
|
42
|
+
hardInputTokens,
|
|
43
|
+
targetInputChars,
|
|
44
|
+
hardInputChars,
|
|
45
|
+
maxSystemChars: positiveInteger(options.maxSystemChars ?? Math.max(4_000, Math.min(60_000, Math.floor(windowChars * 0.05))), 'maxSystemChars'),
|
|
46
|
+
maxToolSchemaChars: positiveInteger(options.maxToolSchemaChars ?? Math.max(4_000, Math.min(40_000, Math.floor(windowChars * 0.04))), 'maxToolSchemaChars'),
|
|
47
|
+
maxEvidenceChars: positiveInteger(options.maxEvidenceChars ?? Math.max(8_000, Math.min(100_000, Math.floor(targetInputChars * 0.25))), 'maxEvidenceChars'),
|
|
48
|
+
maxFileCharsPerRead: positiveInteger(options.maxFileCharsPerRead ?? 32_768, 'maxFileCharsPerRead'),
|
|
49
|
+
maxFileCharsPerTurn: positiveInteger(options.maxFileCharsPerTurn ?? Math.max(32_768, Math.min(131_072, Math.floor(targetInputChars * 0.25))), 'maxFileCharsPerTurn'),
|
|
50
|
+
maxToolResultChars: positiveInteger(options.maxToolResultChars ?? 24_000, 'maxToolResultChars'),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function estimateContextTokens(chars, charsPerToken = DEFAULT_CONTEXT_RATIOS.charsPerToken) {
|
|
55
|
+
if (!Number.isFinite(chars) || chars < 0) throw new RangeError('chars must be a non-negative number');
|
|
56
|
+
if (!Number.isFinite(charsPerToken) || charsPerToken <= 0) throw new RangeError('charsPerToken must be positive');
|
|
57
|
+
return Math.ceil(chars / charsPerToken);
|
|
58
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { normalizeContextItem } from './resolve.js';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_CONSUMED_CONTEXT_CHARS = 4_000;
|
|
4
|
+
|
|
5
|
+
export function compactContextItem(value, options = {}) {
|
|
6
|
+
const item = normalizeContextItem(value);
|
|
7
|
+
const maxChars = Number(options.maxChars ?? DEFAULT_CONSUMED_CONTEXT_CHARS);
|
|
8
|
+
if (!Number.isInteger(maxChars) || maxChars < 1) throw new RangeError('compact maxChars must be a positive integer');
|
|
9
|
+
if (item.content.length <= maxChars) return Object.freeze({ ...item, compacted: false });
|
|
10
|
+
const content = `${item.content.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
...item,
|
|
13
|
+
content,
|
|
14
|
+
chars: content.length,
|
|
15
|
+
compacted: true,
|
|
16
|
+
source_chars: item.content.length,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function compactConsumedToolResult(value, options = {}) {
|
|
21
|
+
const ref = String(options.ref || value?.ref || 'tool_result:consumed').trim();
|
|
22
|
+
const hash = options.hash || value?.hash;
|
|
23
|
+
const priority = Number.isFinite(options.priority) ? options.priority : Number(value?.priority || 0);
|
|
24
|
+
const content = typeof value === 'string'
|
|
25
|
+
? value
|
|
26
|
+
: String(value?.content ?? JSON.stringify(value?.content ?? value ?? null));
|
|
27
|
+
return compactContextItem({ ref, kind: 'tool_result', hash, priority, content }, options);
|
|
28
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { DEFAULT_CONTEXT_RATIOS, createContextBudget, estimateContextTokens } from './budget.js';
|
|
2
|
+
export { DEFAULT_RESULT_POLICY, RESULT_DETAIL_ORDER, normalizeResultPolicy, truncateResultText, boundResultItems } from './result-policy.js';
|
|
3
|
+
export { CONTEXT_ITEM_KINDS, normalizeContextItem, resolveContext, resolveProjectContext } from './resolve.js';
|
|
4
|
+
export { PROJECT_RULES_FILENAME, DEFAULT_PROJECT_RULES_MAX_CHARS, defaultProjectRules, ensureProjectRules, findProjectRules, loadProjectRules } from '../lib/project-rules.js';
|
|
5
|
+
export { DEFAULT_CONSUMED_CONTEXT_CHARS, compactContextItem, compactConsumedToolResult } from './compact.js';
|