@inneranimalmedia/agentsam-sdk 2.5.0 → 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 +12 -8
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/CLI_SHELL.md +163 -53
- package/docs/RELEASES.md +16 -7
- package/package.json +20 -8
- 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 +2 -2
- package/packages/identity/src/contracts/auth-config.js +18 -7
- package/packages/identity/tests/auth-config.test.mjs +9 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
- 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 +47 -0
- package/protocol/context/context-budget.schema.json +10 -15
- package/protocol/context/context-item.schema.json +4 -5
- package/protocol/context/resolved-context-pack.schema.json +19 -14
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -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/catalog.json +18 -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/cli.js +98 -28
- 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/deploy.js +39 -3
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +2 -5
- package/src/commands/models.js +85 -40
- package/src/commands/preferences.js +101 -59
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +5 -3
- package/src/commands/shell.js +370 -109
- package/src/commands/tunnel.js +2 -2
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +68 -6
- package/src/context/index.js +3 -1
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +44 -12
- 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 +55 -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/cli-preferences.js +28 -24
- 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/detect-context.js +2 -2
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/local-sessions.js +127 -0
- package/src/lib/provider-credentials.js +83 -0
- 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 -16
- 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/process.js +35 -9
- 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 +1 -0
- package/src/ui/boot.js +15 -17
- package/test/account-session.test.mjs +36 -0
- package/test/cli-preferences.test.mjs +26 -5
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +61 -12
- 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/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +30 -16
- package/test/npm10-lock.test.mjs +29 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +4 -4
- package/test/responses-runner.test.mjs +148 -0
- package/test/shell.test.mjs +47 -20
- package/test/smoke.mjs +4 -1
- package/test/telemetry.test.mjs +79 -0
- package/test/tools-search.test.mjs +14 -1
- package/test/whoami-resume.test.mjs +56 -0
package/src/ui/boot.js
CHANGED
|
@@ -5,7 +5,6 @@ const FRAMES = ['◔', '◑', '◕', '●'];
|
|
|
5
5
|
const CLEAR_LINE = '\x1b[2K';
|
|
6
6
|
const HIDE_CURSOR = '\x1b[?25l';
|
|
7
7
|
const SHOW_CURSOR = '\x1b[?25h';
|
|
8
|
-
|
|
9
8
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
9
|
|
|
11
10
|
function compactHome(value) {
|
|
@@ -14,43 +13,42 @@ function compactHome(value) {
|
|
|
14
13
|
return value;
|
|
15
14
|
}
|
|
16
15
|
|
|
16
|
+
function modelLine(preferences) {
|
|
17
|
+
const model = preferences.modelPreference && preferences.modelPreference !== 'auto' ? preferences.modelPreference : 'auto';
|
|
18
|
+
const reasoning = preferences.reasoningEffort && preferences.reasoningEffort !== 'auto' ? preferences.reasoningEffort : 'auto';
|
|
19
|
+
const tier = preferences.serviceTier || 'default';
|
|
20
|
+
return `${model} · ${reasoning} · ${tier}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
export function renderBootSummary({ identity, preferences }) {
|
|
18
24
|
const branch = identity.branch ? ` · ${pc.cyan(identity.branch)}` : '';
|
|
19
|
-
const model = preferences.modelPreference && preferences.modelPreference !== 'auto'
|
|
20
|
-
? preferences.modelPreference
|
|
21
|
-
: 'auto';
|
|
22
25
|
const runtime = preferences.runtime || 'local';
|
|
23
26
|
const terminal = preferences.terminal || path.basename(process.env.SHELL || '') || 'shell';
|
|
24
27
|
return [
|
|
25
28
|
'',
|
|
26
29
|
` ${pc.bold('Agent Sam')} ${pc.green('●')}`,
|
|
27
|
-
` ${pc.cyan(identity.project)}${branch}
|
|
30
|
+
` ${pc.cyan(identity.project)}${branch}`,
|
|
31
|
+
` ${pc.white(modelLine(preferences))}`,
|
|
28
32
|
` ${pc.dim(compactHome(identity.root))}`,
|
|
29
33
|
` ${pc.green('✓')} ${pc.dim(runtime)} · ${pc.dim(terminal)} · ready`,
|
|
34
|
+
` ${pc.dim('Tip: /model changes model + reasoning + processing; / shows the command menu.')}`,
|
|
30
35
|
'',
|
|
31
36
|
].join('\n');
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export async function runBootScene({ identity, preferences, animate = true, write = process.stdout.write.bind(process.stdout) }) {
|
|
35
40
|
const interactive = Boolean(animate && process.stdout.isTTY);
|
|
36
|
-
if (!interactive) {
|
|
37
|
-
write(renderBootSummary({ identity, preferences }));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
+
if (!interactive) { write(renderBootSummary({ identity, preferences })); return; }
|
|
41
42
|
write(HIDE_CURSOR);
|
|
42
43
|
try {
|
|
43
|
-
write(`\n ${pc.bold('Agent Sam')}\n ${pc.cyan(identity.project)}${identity.branch ? ` · ${pc.cyan(identity.branch)}` : ''}\n\n`);
|
|
44
|
-
const
|
|
45
|
-
for (const label of checks) {
|
|
44
|
+
write(`\n ${pc.bold('Agent Sam')}\n ${pc.cyan(identity.project)}${identity.branch ? ` · ${pc.cyan(identity.branch)}` : ''}\n ${pc.white(modelLine(preferences))}\n\n`);
|
|
45
|
+
for (const label of ['directory trust', 'runtime', 'model policy']) {
|
|
46
46
|
for (let i = 0; i < FRAMES.length; i += 1) {
|
|
47
47
|
write(`\r${CLEAR_LINE} ${pc.cyan(FRAMES[i])} ${pc.dim(`checking ${label}`)}`);
|
|
48
48
|
await sleep(i === FRAMES.length - 1 ? 45 : 55);
|
|
49
49
|
}
|
|
50
50
|
write(`\r${CLEAR_LINE} ${pc.green('✓')} ${pc.dim(label)}\n`);
|
|
51
51
|
}
|
|
52
|
-
write(`\n ${pc.green('●')} ${pc.bold('ready')} ${pc.dim(compactHome(identity.root))}\n\n`);
|
|
53
|
-
} finally {
|
|
54
|
-
write(SHOW_CURSOR);
|
|
55
|
-
}
|
|
52
|
+
write(`\n ${pc.green('●')} ${pc.bold('ready')} ${pc.dim(compactHome(identity.root))}\n ${pc.dim('Type / and press Enter for the scrollable command menu.')}\n\n`);
|
|
53
|
+
} finally { write(SHOW_CURSOR); }
|
|
56
54
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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 { accountSessionPath, clearAccountSession, readAccountSession, resolveAccountSdkKey, saveAccountSession } from '../src/lib/account-session.js';
|
|
7
|
+
|
|
8
|
+
function tempHome(t) {
|
|
9
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-account-'));
|
|
10
|
+
t.after(() => fs.rmSync(home, { recursive: true, force: true }));
|
|
11
|
+
return home;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test('browser-auth SDK bearer persists in machine-local storage, not project state', t => {
|
|
15
|
+
const home = tempHome(t);
|
|
16
|
+
const saved = saveAccountSession({ access_token: 'sdk_machine_session', user_id: 'au_test', account_id: 'acct_test', email: 'dev@example.test' }, { home });
|
|
17
|
+
assert.equal(saved.user_id, 'au_test');
|
|
18
|
+
const filename = accountSessionPath({ home });
|
|
19
|
+
assert.equal(fs.existsSync(filename), true);
|
|
20
|
+
if (process.platform !== 'win32') assert.equal(fs.statSync(filename).mode & 0o077, 0);
|
|
21
|
+
|
|
22
|
+
const loaded = readAccountSession({ home });
|
|
23
|
+
assert.equal(loaded.sdk_key, 'sdk_machine_session');
|
|
24
|
+
const resolved = resolveAccountSdkKey({ env: {}, home });
|
|
25
|
+
assert.equal(resolved.source, 'agentsam_account_session');
|
|
26
|
+
assert.equal(resolved.value, 'sdk_machine_session');
|
|
27
|
+
assert.equal(clearAccountSession({ home }), true);
|
|
28
|
+
assert.equal(readAccountSession({ home }), null);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('explicit/environment SDK bearer remains higher authority than local session fallback', t => {
|
|
32
|
+
const home = tempHome(t);
|
|
33
|
+
saveAccountSession({ access_token: 'sdk_disk' }, { home });
|
|
34
|
+
assert.equal(resolveAccountSdkKey({ env: { AGENTSAM_SDK_KEY: 'sdk_env' }, home }).value, 'sdk_env');
|
|
35
|
+
assert.equal(resolveAccountSdkKey({ env: { AGENTSAM_SDK_KEY: 'sdk_env' }, explicit: 'sdk_explicit', home }).value, 'sdk_explicit');
|
|
36
|
+
});
|
|
@@ -6,20 +6,41 @@ import test from 'node:test';
|
|
|
6
6
|
import { CLI_PREFERENCES_SCHEMA, detectCliProject, readCliPreferences, writeCliPreferences } from '../src/lib/cli-preferences.js';
|
|
7
7
|
import { renderBootSummary } from '../src/ui/boot.js';
|
|
8
8
|
|
|
9
|
-
test('CLI preferences
|
|
9
|
+
test('CLI preferences persist local model/runtime controls without becoming routing authority', () => {
|
|
10
10
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-cli-prefs-'));
|
|
11
11
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'demo-project' }));
|
|
12
|
-
const written = writeCliPreferences(root, {
|
|
12
|
+
const written = writeCliPreferences(root, {
|
|
13
|
+
trustedDirectory: true,
|
|
14
|
+
runtime: 'sandbox',
|
|
15
|
+
terminal: 'zsh',
|
|
16
|
+
modelPreference: 'openai:gpt-6-astra',
|
|
17
|
+
reasoningEffort: 'high',
|
|
18
|
+
serviceTier: 'fast',
|
|
19
|
+
});
|
|
13
20
|
assert.equal(written.schemaVersion, CLI_PREFERENCES_SCHEMA);
|
|
14
21
|
assert.equal(written.modelAuthority, 'preference-only');
|
|
22
|
+
assert.equal(written.trustedDirectory, true);
|
|
15
23
|
assert.deepEqual(readCliPreferences(root), written);
|
|
16
|
-
|
|
17
24
|
const identity = detectCliProject(root);
|
|
18
25
|
assert.equal(identity.project, 'demo-project');
|
|
19
26
|
assert.equal(identity.root, root);
|
|
20
|
-
|
|
21
27
|
const summary = renderBootSummary({ identity, preferences: written });
|
|
22
28
|
assert.match(summary, /Agent Sam/);
|
|
23
29
|
assert.match(summary, /demo-project/);
|
|
24
|
-
assert.match(summary, /
|
|
30
|
+
assert.match(summary, /openai:gpt-6-astra/);
|
|
31
|
+
assert.match(summary, /high/);
|
|
32
|
+
assert.match(summary, /fast/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('legacy v1 local preferences migrate in memory without inventing trust', () => {
|
|
36
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-cli-legacy-'));
|
|
37
|
+
fs.mkdirSync(path.join(root, '.agentsam'));
|
|
38
|
+
fs.writeFileSync(path.join(root, '.agentsam', 'cli.json'), JSON.stringify({
|
|
39
|
+
schemaVersion: 'agentsam-cli-preferences-v1', runtime: 'local', terminal: 'zsh', modelPreference: 'auto', modelAuthority: 'preference-only',
|
|
40
|
+
}));
|
|
41
|
+
const read = readCliPreferences(root);
|
|
42
|
+
assert.equal(read.schemaVersion, CLI_PREFERENCES_SCHEMA);
|
|
43
|
+
assert.equal(read.trustedDirectory, false);
|
|
44
|
+
assert.equal(read.reasoningEffort, 'auto');
|
|
45
|
+
assert.equal(read.serviceTier, 'default');
|
|
25
46
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { handleCloudflareConnectionRequest } from '../packages/connectors/cloudflare/src/routes.js';
|
|
4
|
+
import { rejectUntrustedOwnerHints } from '../packages/connectors/cloudflare/src/owner.js';
|
|
5
|
+
|
|
6
|
+
function req(url, { method = 'GET', headers = {}, body } = {}) {
|
|
7
|
+
return new Request(url, { method, headers, body });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('cloudflare connector routes', () => {
|
|
11
|
+
it('rejects unauthenticated status', async () => {
|
|
12
|
+
const res = await handleCloudflareConnectionRequest(
|
|
13
|
+
req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare'),
|
|
14
|
+
{},
|
|
15
|
+
);
|
|
16
|
+
assert.equal(res.status, 401);
|
|
17
|
+
const json = await res.json();
|
|
18
|
+
assert.equal(json.error, 'unauthenticated');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('rejects browser-submitted owner hints', () => {
|
|
22
|
+
const url = new URL('https://agentsam.inneranimalmedia.com/api/connections/cloudflare?account_id=evil');
|
|
23
|
+
assert.throws(
|
|
24
|
+
() => rejectUntrustedOwnerHints(req(url.toString()), url, {}),
|
|
25
|
+
/untrusted_owner_hint/,
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns 503 on start when only fixture credentials exist', async () => {
|
|
30
|
+
const env = {
|
|
31
|
+
CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
|
|
32
|
+
CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
|
|
33
|
+
sessions: new Map([['sess_1', 'user-sam']]),
|
|
34
|
+
};
|
|
35
|
+
const res = await handleCloudflareConnectionRequest(
|
|
36
|
+
req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare/start', {
|
|
37
|
+
headers: { cookie: 'agentsam_session=sess_1' },
|
|
38
|
+
}),
|
|
39
|
+
env,
|
|
40
|
+
);
|
|
41
|
+
assert.equal(res.status, 503);
|
|
42
|
+
const json = await res.json();
|
|
43
|
+
assert.equal(json.error, 'not_configured');
|
|
44
|
+
assert.equal(json.fixture, true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('status stays safe and does not treat fixture as production configured', async () => {
|
|
48
|
+
const env = {
|
|
49
|
+
CLOUDFLARE_OAUTH_CLIENT_ID: 'sillynotreal',
|
|
50
|
+
CLOUDFLARE_OAUTH_CLIENT_SECRET: 'sillynotreal-secret',
|
|
51
|
+
sessions: new Map([['sess_1', 'user-sam']]),
|
|
52
|
+
};
|
|
53
|
+
const res = await handleCloudflareConnectionRequest(
|
|
54
|
+
req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare', {
|
|
55
|
+
headers: { authorization: 'Bearer sess_1' },
|
|
56
|
+
}),
|
|
57
|
+
env,
|
|
58
|
+
);
|
|
59
|
+
assert.equal(res.status, 200);
|
|
60
|
+
const json = await res.json();
|
|
61
|
+
assert.equal(json.ok, true);
|
|
62
|
+
assert.equal(json.configured, false);
|
|
63
|
+
assert.equal(json.fixture, true);
|
|
64
|
+
assert.equal(JSON.stringify(json).includes('sillynotreal-secret'), false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('rejects owner_id in the JSON body', async () => {
|
|
68
|
+
const env = { sessions: new Map([['sess_1', 'user-sam']]) };
|
|
69
|
+
const res = await handleCloudflareConnectionRequest(
|
|
70
|
+
req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare/disconnect', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { cookie: 'agentsam_session=sess_1', 'content-type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({ owner_id: 'evil' }),
|
|
74
|
+
}),
|
|
75
|
+
env,
|
|
76
|
+
);
|
|
77
|
+
assert.equal(res.status, 400);
|
|
78
|
+
const json = await res.json();
|
|
79
|
+
assert.equal(json.error, 'untrusted_owner_hint');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('rejects callback without a stored oauth state', async () => {
|
|
83
|
+
const env = {
|
|
84
|
+
CLOUDFLARE_OAUTH_CLIENT_ID: 'real-client-id',
|
|
85
|
+
CLOUDFLARE_OAUTH_CLIENT_SECRET: 'real-client-secret-value',
|
|
86
|
+
oauthState: new Map(),
|
|
87
|
+
};
|
|
88
|
+
const res = await handleCloudflareConnectionRequest(
|
|
89
|
+
req('https://agentsam.inneranimalmedia.com/api/connections/cloudflare/callback?code=abc&state=missing'),
|
|
90
|
+
env,
|
|
91
|
+
);
|
|
92
|
+
assert.equal(res.status, 403);
|
|
93
|
+
const json = await res.json();
|
|
94
|
+
assert.equal(json.error, 'cloudflare_connection_forbidden');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
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 { buildWranglerInvocation, listWranglerNativeCommands, runWranglerNative, summarizeCloudflareCpuProfile, summarizeCloudflareCpuProfileFile } from '../src/cloudflare/index.js';
|
|
7
|
+
import { createCapabilityAdapter } from '../src/agent/capability-adapter.js';
|
|
8
|
+
|
|
9
|
+
function tempRoot() { return fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-cf-')); }
|
|
10
|
+
|
|
11
|
+
test('Cloudflare native command catalog exposes bounded read operations and never an auth-token secret read', () => {
|
|
12
|
+
const ids = listWranglerNativeCommands().map((row) => row.id);
|
|
13
|
+
assert.deepEqual(ids, ['whoami', 'deployments.list', 'versions.list', 'types.check', 'queues.list']);
|
|
14
|
+
assert.ok(!ids.some((id) => id.includes('token') || id.includes('secret') || id === 'deploy'));
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('Wrangler native invocation is argv-based and cwd/config scoped', () => {
|
|
18
|
+
const root = tempRoot();
|
|
19
|
+
fs.writeFileSync(path.join(root, 'wrangler.jsonc'), '{}');
|
|
20
|
+
const plan = buildWranglerInvocation('deployments.list', { cwd: root, name: 'demo' });
|
|
21
|
+
assert.equal(plan.cwd, root);
|
|
22
|
+
assert.deepEqual(plan.args.slice(0, 4), ['deployments', 'list', '--json', '--name']);
|
|
23
|
+
assert.equal(plan.args[4], 'demo');
|
|
24
|
+
assert.ok(plan.args.includes('--config'));
|
|
25
|
+
assert.throws(() => buildWranglerInvocation('whoami', { cwd: root, config: '../outside.toml' }), /outside_cwd/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('Wrangler native result preserves JSON and real exit code on failure', async () => {
|
|
29
|
+
const root = tempRoot();
|
|
30
|
+
const success = await runWranglerNative('whoami', { cwd: root }, { run: async () => ({ code: 0, stdout: JSON.stringify({ email: 'dev@example.com' }), stderr: '' }) });
|
|
31
|
+
assert.equal(success.data.email, 'dev@example.com');
|
|
32
|
+
await assert.rejects(
|
|
33
|
+
runWranglerNative('versions.list', { cwd: root }, { run: async () => ({ code: 7, stdout: '', stderr: 'upstream failed' }) }),
|
|
34
|
+
(error) => error.diagnostic?.exit_code === 7 && error.diagnostic?.code === 'wrangler_exit_nonzero',
|
|
35
|
+
);
|
|
36
|
+
await assert.rejects(
|
|
37
|
+
runWranglerNative('deployments.list', { cwd: root }, { run: async () => ({ code: 1, stdout: '', stderr: 'Authentication error [code: 10000]\nRequest ID: req_cf_1\nCF-Ray: ray-123' }) }),
|
|
38
|
+
(error) => error.diagnostic?.code === '10000' && error.diagnostic?.request_id === 'req_cf_1' && error.diagnostic?.ray_id === 'ray-123',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('CPU profile summary ranks self-time hotspots and explicitly rejects production timer inference', () => {
|
|
43
|
+
const profile = {
|
|
44
|
+
nodes: [
|
|
45
|
+
{ id: 1, callFrame: { functionName: 'fetch', url: 'worker.js', lineNumber: 0, columnNumber: 0 } },
|
|
46
|
+
{ id: 2, callFrame: { functionName: 'heavyLoop', url: 'worker.js', lineNumber: 10, columnNumber: 2 } },
|
|
47
|
+
{ id: 3, callFrame: { functionName: '(garbage collector)', url: '', lineNumber: -1, columnNumber: -1 } },
|
|
48
|
+
],
|
|
49
|
+
samples: [1, 2, 2, 3],
|
|
50
|
+
timeDeltas: [100, 1200, 800, 400],
|
|
51
|
+
};
|
|
52
|
+
const summary = summarizeCloudflareCpuProfile(profile);
|
|
53
|
+
assert.equal(summary.top_frames[0].function, 'heavyLoop');
|
|
54
|
+
assert.equal(summary.top_frames[0].self_us, 2000);
|
|
55
|
+
assert.equal(summary.garbage_collection_ms, 0.4);
|
|
56
|
+
assert.match(summary.timer_semantics, /do not advance during CPU-only execution/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('CPU profile file cannot escape runtime cwd', () => {
|
|
60
|
+
const root = tempRoot();
|
|
61
|
+
fs.writeFileSync(path.join(root, 'profile.cpuprofile'), JSON.stringify({ nodes: [], samples: [], timeDeltas: [] }));
|
|
62
|
+
assert.equal(summarizeCloudflareCpuProfileFile({ cwd: root, file: 'profile.cpuprofile' }).file, 'profile.cpuprofile');
|
|
63
|
+
assert.throws(() => summarizeCloudflareCpuProfileFile({ cwd: root, file: '../profile.cpuprofile' }), /outside_cwd/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('explicitly executable experimental Cloudflare capabilities enter cards-first discovery without exposing unavailable commands', () => {
|
|
67
|
+
const adapter = createCapabilityAdapter();
|
|
68
|
+
const rows = adapter.toolDescriptors();
|
|
69
|
+
const ids = rows.map((row) => row.name);
|
|
70
|
+
assert.ok(ids.includes('cloudflare.wrangler.native'));
|
|
71
|
+
assert.ok(ids.includes('cloudflare.cpu.profile'));
|
|
72
|
+
assert.ok(!ids.includes('cloudflare.cpu.audit'));
|
|
73
|
+
assert.ok(!ids.includes('blender.build'));
|
|
74
|
+
assert.equal(rows.find((row) => row.name === 'cloudflare.wrangler.native').risk, 'read');
|
|
75
|
+
});
|
package/test/context.test.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import path from 'node:path';
|
|
|
5
5
|
import test from 'node:test';
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_RESULT_POLICY,
|
|
8
|
+
assessContextUsage,
|
|
9
|
+
compileAgentInstructions,
|
|
8
10
|
createContextBudget,
|
|
9
11
|
normalizeResultPolicy,
|
|
10
12
|
resolveContext,
|
|
@@ -13,18 +15,14 @@ import {
|
|
|
13
15
|
|
|
14
16
|
test('default result policy is bounded and callers may request less', () => {
|
|
15
17
|
assert.deepEqual(DEFAULT_RESULT_POLICY, { max_items: 8, max_chars: 24_000, detail: 'excerpt' });
|
|
16
|
-
assert.deepEqual(normalizeResultPolicy({ max_items: 3, max_chars: 8_000, detail: 'card' }), {
|
|
17
|
-
max_items: 3,
|
|
18
|
-
max_chars: 8_000,
|
|
19
|
-
detail: 'card',
|
|
20
|
-
});
|
|
18
|
+
assert.deepEqual(normalizeResultPolicy({ max_items: 3, max_chars: 8_000, detail: 'card' }), { max_items: 3, max_chars: 8_000, detail: 'card' });
|
|
21
19
|
assert.throws(() => normalizeResultPolicy({ max_items: 9 }), /higher_detail_required:max_items/);
|
|
22
20
|
assert.throws(() => normalizeResultPolicy({ max_chars: 24_001 }), /higher_detail_required:max_chars/);
|
|
23
21
|
assert.throws(() => normalizeResultPolicy({ detail: 'full' }), /higher_detail_required:detail/);
|
|
24
22
|
assert.equal(normalizeResultPolicy({ detail: 'full', max_items: 20, max_chars: 100_000 }, { operation: 'higher-detail' }).detail, 'full');
|
|
25
23
|
});
|
|
26
24
|
|
|
27
|
-
test('context budget
|
|
25
|
+
test('legacy ratio context budget remains compatible for ordinary windows', () => {
|
|
28
26
|
const budget = createContextBudget({ windowTokens: 250_000 });
|
|
29
27
|
assert.equal(budget.targetInputTokens, 150_000);
|
|
30
28
|
assert.equal(budget.hardInputTokens, 212_500);
|
|
@@ -37,7 +35,33 @@ test('context budget reserves headroom and caps each ownership class', () => {
|
|
|
37
35
|
assert.equal(budget.maxToolResultChars, 24_000);
|
|
38
36
|
});
|
|
39
37
|
|
|
40
|
-
test('
|
|
38
|
+
test('large model capacity is independent from AgentSam working-set and pricing policy', () => {
|
|
39
|
+
const budget = createContextBudget({
|
|
40
|
+
windowTokens: 1_050_000,
|
|
41
|
+
targetInputTokens: 120_000,
|
|
42
|
+
compactAtTokens: 180_000,
|
|
43
|
+
interveneAtTokens: 220_000,
|
|
44
|
+
maxNormalInputTokens: 250_000,
|
|
45
|
+
pricingThresholdTokens: 272_000,
|
|
46
|
+
safetyMarginTokens: 22_000,
|
|
47
|
+
});
|
|
48
|
+
assert.equal(budget.windowTokens, 1_050_000);
|
|
49
|
+
assert.equal(budget.targetInputTokens, 120_000);
|
|
50
|
+
assert.equal(budget.compactAtTokens, 180_000);
|
|
51
|
+
assert.equal(budget.interveneAtTokens, 220_000);
|
|
52
|
+
assert.equal(budget.maxNormalInputTokens, 250_000);
|
|
53
|
+
assert.equal(budget.pricingThresholdTokens, 272_000);
|
|
54
|
+
assert.ok(budget.targetInputRatio < 0.12);
|
|
55
|
+
|
|
56
|
+
assert.equal(assessContextUsage(179_999, budget).shouldCompact, false);
|
|
57
|
+
assert.equal(assessContextUsage(180_000, budget).shouldCompact, true);
|
|
58
|
+
assert.equal(assessContextUsage(272_000, budget).pricingThresholdCrossed, false);
|
|
59
|
+
const crossed = assessContextUsage(272_001, budget);
|
|
60
|
+
assert.equal(crossed.pricingThresholdCrossed, true);
|
|
61
|
+
assert.equal(crossed.tokensUntilPricingThreshold, -1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('context.resolve separates composition classes and returns an economic receipt', () => {
|
|
41
65
|
const budget = createContextBudget({
|
|
42
66
|
windowTokens: 32_000,
|
|
43
67
|
maxEvidenceChars: 90,
|
|
@@ -49,6 +73,9 @@ test('context.resolve selects high-priority evidence and returns a receipt', ()
|
|
|
49
73
|
objective: 'fix terminal cwd persistence',
|
|
50
74
|
refs: ['repo:demo'],
|
|
51
75
|
budget,
|
|
76
|
+
instructionChars: 20,
|
|
77
|
+
toolSchemaChars: 12,
|
|
78
|
+
historyChars: 8,
|
|
52
79
|
items: [
|
|
53
80
|
{ ref: 'file:low', kind: 'file', priority: 1, content: 'L'.repeat(50) },
|
|
54
81
|
{ ref: 'tool:high', kind: 'tool_result', priority: 10, content: 'T'.repeat(50) },
|
|
@@ -61,17 +88,39 @@ test('context.resolve selects high-priority evidence and returns a receipt', ()
|
|
|
61
88
|
assert.equal(pack.items[0].chars, 30);
|
|
62
89
|
assert.equal(pack.items[1].chars, 50);
|
|
63
90
|
assert.equal(pack.items[2].chars, 10);
|
|
64
|
-
assert.equal(pack.receipt.
|
|
91
|
+
assert.equal(pack.receipt.tool_result_chars, 30);
|
|
92
|
+
assert.equal(pack.receipt.evidence_chars, 60);
|
|
93
|
+
assert.equal(pack.receipt.instruction_chars, 20);
|
|
94
|
+
assert.equal(pack.receipt.tool_schema_chars, 12);
|
|
95
|
+
assert.equal(pack.receipt.history_chars, 8);
|
|
96
|
+
assert.equal(pack.receipt.chars, 130);
|
|
97
|
+
assert.equal(pack.receipt.estimate_kind, 'local');
|
|
98
|
+
assert.equal(pack.receipt.window_tokens, 32_000);
|
|
65
99
|
assert.equal(pack.receipt.sources_considered, 4);
|
|
66
100
|
assert.equal(pack.receipt.sources_included, 3);
|
|
67
|
-
assert.ok(pack.receipt.sources_deferred >= 3);
|
|
101
|
+
assert.ok(pack.receipt.sources_deferred >= 3);
|
|
68
102
|
assert.ok(pack.receipt.deferred_refs.includes('memory:later'));
|
|
103
|
+
assert.deepEqual(pack.receipt.rehydratable_refs, ['tool:high', 'file:high', 'file:low']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('AgentSam instructions compile in stable then repository-specific precedence', t => {
|
|
107
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-instructions-'));
|
|
108
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
109
|
+
fs.mkdirSync(path.join(root, '.git'));
|
|
110
|
+
fs.writeFileSync(path.join(root, 'AGENTSAM.md'), '# Stable\nbase law\n');
|
|
111
|
+
fs.writeFileSync(path.join(root, '.agentsamrules'), '# Repo\nlocal law\n');
|
|
112
|
+
const compiled = compileAgentInstructions(root);
|
|
113
|
+
assert.deepEqual(compiled.precedence, ['AGENTSAM.md', '.agentsamrules']);
|
|
114
|
+
assert.equal(compiled.sources.length, 2);
|
|
115
|
+
assert.ok(compiled.content.indexOf('base law') < compiled.content.indexOf('local law'));
|
|
69
116
|
});
|
|
70
117
|
|
|
71
|
-
test('project context
|
|
118
|
+
test('project context loads bounded compiled AgentSam instructions', t => {
|
|
72
119
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-context-rules-'));
|
|
73
120
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
74
|
-
fs.
|
|
121
|
+
fs.mkdirSync(path.join(root, '.git'));
|
|
122
|
+
fs.writeFileSync(path.join(root, 'AGENTSAM.md'), '# stable\n' + 'a'.repeat(300));
|
|
123
|
+
fs.writeFileSync(path.join(root, '.agentsamrules'), '# rules\n' + 'x'.repeat(5000) + '\n');
|
|
75
124
|
const pack = resolveProjectContext({
|
|
76
125
|
cwd: root,
|
|
77
126
|
objective: 'inspect repository',
|
|
@@ -84,7 +133,7 @@ test('project context automatically loads bounded .agentsamrules as system conte
|
|
|
84
133
|
assert.equal(pack.receipt.system_chars, 1_000);
|
|
85
134
|
});
|
|
86
135
|
|
|
87
|
-
test('consumed tool results compact to 4k while preserving
|
|
136
|
+
test('consumed tool results compact to 4k while preserving rehydration identity', async () => {
|
|
88
137
|
const { compactConsumedToolResult } = await import('../src/context/index.js');
|
|
89
138
|
const item = compactConsumedToolResult('x'.repeat(10_000), { ref: 'tool:call_123', hash: 'sha256:abc' });
|
|
90
139
|
assert.equal(item.ref, 'tool:call_123');
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { scanTextForSecrets } from '../src/lib/deploy/secret-scan.js';
|
|
4
|
+
import { HEALTH_USER_AGENT, parseWranglerVersionId, probeDeployHealth, resolveHealthOrigin } from '../src/lib/deploy/health.js';
|
|
5
|
+
|
|
6
|
+
describe('deploy secret scan', () => {
|
|
7
|
+
it('allows fixture oauth values', () => {
|
|
8
|
+
const findings = scanTextForSecrets(
|
|
9
|
+
'CLOUDFLARE_OAUTH_CLIENT_ID=sillynotreal\nCLOUDFLARE_OAUTH_CLIENT_SECRET=sillynotreal-secret\n',
|
|
10
|
+
{ filename: 'apps/local-studio/.env.cloudflare.example' },
|
|
11
|
+
);
|
|
12
|
+
assert.equal(findings.length, 0);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('flags live openai-like keys', () => {
|
|
16
|
+
const findings = scanTextForSecrets('OPENAI_API_KEY=sk-abcdefghijklmnopqrstuvwxyz012345');
|
|
17
|
+
assert.equal(findings.some((f) => f.label === 'openai-like'), true);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('deploy health', () => {
|
|
22
|
+
it('parses wrangler version ids', () => {
|
|
23
|
+
assert.equal(
|
|
24
|
+
parseWranglerVersionId('Current Version ID: e9747d3a-4db6-4e02-b6fe-aa5a47f1588b'),
|
|
25
|
+
'e9747d3a-4db6-4e02-b6fe-aa5a47f1588b',
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('reads hostname from wrangler.jsonc', () => {
|
|
30
|
+
const origin = resolveHealthOrigin({
|
|
31
|
+
env: {},
|
|
32
|
+
wranglerConfigText: '{"routes":[{"pattern":"agentsam.inneranimalmedia.com","custom_domain":true}]}',
|
|
33
|
+
});
|
|
34
|
+
assert.equal(origin, 'https://agentsam.inneranimalmedia.com');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('treats /health ok:true with connector unconfigured as healthy', async () => {
|
|
38
|
+
const fetchImpl = async (url) => {
|
|
39
|
+
if (String(url).endsWith('/health')) {
|
|
40
|
+
return {
|
|
41
|
+
status: 200,
|
|
42
|
+
json: async () => ({ ok: true, connections: { cloudflare: { configured: false } } }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { status: 200, json: async () => ({}) };
|
|
46
|
+
};
|
|
47
|
+
const health = await probeDeployHealth('https://agentsam.inneranimalmedia.com', { fetchImpl });
|
|
48
|
+
assert.equal(health.ok, true);
|
|
49
|
+
assert.equal(health.results['/health'].appOk, true);
|
|
50
|
+
assert.equal(health.results['/health'].cloudflareConfigured, false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('sends a browser-safe User-Agent so WAF does not 403 the probe', async () => {
|
|
54
|
+
const seen = [];
|
|
55
|
+
const fetchImpl = async (url, init = {}) => {
|
|
56
|
+
seen.push({ url, init });
|
|
57
|
+
return {
|
|
58
|
+
status: 200,
|
|
59
|
+
json: async () => ({ ok: true, connections: { cloudflare: { configured: false } } }),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
await probeDeployHealth('https://agentsam.inneranimalmedia.com', { fetchImpl, paths: ['/health'] });
|
|
63
|
+
assert.equal(seen.length, 1);
|
|
64
|
+
assert.equal(seen[0].init.headers['User-Agent'], HEALTH_USER_AGENT);
|
|
65
|
+
assert.match(seen[0].init.headers.Accept, /application\/json/);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { classifyOpenAIError, createOpenAIHttpError, diagnosticFromError } from '../src/errors/index.js';
|
|
4
|
+
import { createOpenAIResponsesAdapter } from '../src/providers/openai-responses.js';
|
|
5
|
+
|
|
6
|
+
function response(status, body, headers = {}) { return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', ...headers } }); }
|
|
7
|
+
|
|
8
|
+
test('OpenAI diagnostics preserve machine code, request id, retry metadata and redact secrets', () => {
|
|
9
|
+
const error = createOpenAIHttpError({
|
|
10
|
+
status: 429,
|
|
11
|
+
body: { error: { type: 'rate_limit_error', code: 'slow_down', message: 'slow down Bearer abc.def.ghi', param: null }, api_key: 'sk-secret-value-123456789' },
|
|
12
|
+
headers: new Headers({ 'retry-after': '2', 'x-request-id': 'req_123' }),
|
|
13
|
+
requestedServiceTier: 'fast',
|
|
14
|
+
});
|
|
15
|
+
assert.equal(error.diagnostic.http_status, 429);
|
|
16
|
+
assert.equal(error.diagnostic.type, 'rate_limit_error');
|
|
17
|
+
assert.equal(error.diagnostic.code, 'slow_down');
|
|
18
|
+
assert.equal(error.diagnostic.request_id, 'req_123');
|
|
19
|
+
assert.equal(error.diagnostic.retry_after_ms, 2000);
|
|
20
|
+
assert.equal(error.diagnostic.retriable, true);
|
|
21
|
+
assert.equal(error.diagnostic.retry_strategy, 'retry_after_backoff');
|
|
22
|
+
assert.equal(error.diagnostic.requested_service_tier, 'fast');
|
|
23
|
+
assert.ok(!JSON.stringify(error.diagnostic).includes('sk-secret-value'));
|
|
24
|
+
assert.ok(!error.diagnostic.message.includes('abc.def.ghi'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('billing and spend errors are explicitly non-retriable while provider overload is retriable', () => {
|
|
28
|
+
for (const code of ['credit_balance_exhausted', 'organization_spend_limit_exceeded', 'project_spend_limit_exceeded', 'organization_usage_limit_exceeded']) {
|
|
29
|
+
assert.equal(classifyOpenAIError({ status: 429, code }).retriable, false);
|
|
30
|
+
}
|
|
31
|
+
assert.deepEqual(classifyOpenAIError({ status: 503, code: 'server_is_overloaded' }), { category: 'provider_overload', retriable: true, retry_strategy: 'retry_after_backoff' });
|
|
32
|
+
assert.equal(classifyOpenAIError({ status: 401 }).category, 'authentication');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('OpenAI retry classifier distinguishes configuration, continuation, rate, server, and operator-action failures', () => {
|
|
36
|
+
assert.deepEqual(classifyOpenAIError({ status: 400, message: 'Invalid service_tier argument' }), { category: 'service_tier', retriable: false, retry_strategy: 'change_configuration' });
|
|
37
|
+
assert.deepEqual(classifyOpenAIError({ status: 400, code: 'previous_response_not_found' }), { category: 'continuation', retriable: true, retry_strategy: 'retry_full_context' });
|
|
38
|
+
assert.deepEqual(classifyOpenAIError({ status: 400, code: 'websocket_connection_limit_reached' }), { category: 'connection_lifetime', retriable: true, retry_strategy: 'reconnect' });
|
|
39
|
+
assert.deepEqual(classifyOpenAIError({ status: 429, type: 'rate_limit_error', code: 'slow_down' }), { category: 'ramp_rate', retriable: true, retry_strategy: 'retry_after_backoff' });
|
|
40
|
+
assert.deepEqual(classifyOpenAIError({ status: 500 }), { category: 'provider_server', retriable: true, retry_strategy: 'retry_backoff' });
|
|
41
|
+
assert.deepEqual(classifyOpenAIError({ status: 403 }), { category: 'authorization_or_region', retriable: false, retry_strategy: 'operator_action' });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('Responses adapter throws a diagnostic error instead of flattening the provider response', async () => {
|
|
45
|
+
const adapter = createOpenAIResponsesAdapter({
|
|
46
|
+
apiKey: 'sk-test',
|
|
47
|
+
fetchImpl: async () => response(429, { error: { type: 'insufficient_quota', code: 'project_spend_limit_exceeded', message: 'budget reached' } }, { 'x-request-id': 'req_budget' }),
|
|
48
|
+
});
|
|
49
|
+
await assert.rejects(
|
|
50
|
+
adapter.create({ model: 'gpt-6-astra', reasoningEffort: 'low', serviceTier: 'default', input: 'hi' }),
|
|
51
|
+
(error) => {
|
|
52
|
+
const d = diagnosticFromError(error);
|
|
53
|
+
assert.equal(d.code, 'project_spend_limit_exceeded');
|
|
54
|
+
assert.equal(d.request_id, 'req_budget');
|
|
55
|
+
assert.equal(d.retriable, false);
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import { evaluateContextFixture, listContextEvalFixtures } from '../src/eval/index.js';
|
|
6
|
+
|
|
7
|
+
const repoRoot = path.resolve(new URL('..', import.meta.url).pathname);
|
|
8
|
+
|
|
9
|
+
test('context eval ships AgentSam-shaped deterministic fixtures and never calls a provider', async () => {
|
|
10
|
+
const fixtures = listContextEvalFixtures();
|
|
11
|
+
assert.ok(fixtures.length >= 6);
|
|
12
|
+
assert.ok(fixtures.includes('continuation-after-compaction'));
|
|
13
|
+
const report = await evaluateContextFixture({ fixture: 'exact-symbol-callers' });
|
|
14
|
+
assert.equal(report.live_provider_used, false);
|
|
15
|
+
assert.equal(report.strategies.length, 3);
|
|
16
|
+
assert.ok(report.strategies.every(row => row.result === 'PASS'));
|
|
17
|
+
assert.ok(report.winner);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('compaction fixture reaches a proactive pressure region and proves explicit rehydration', async () => {
|
|
21
|
+
const report = await evaluateContextFixture({ fixture: 'continuation-after-compaction', strategy: 'compact' });
|
|
22
|
+
const row = report.strategies[0];
|
|
23
|
+
assert.equal(row.result, 'PASS');
|
|
24
|
+
assert.ok(row.compacted_chars > 0);
|
|
25
|
+
assert.ok(row.rehydrated_refs.includes('tool:call_previous'));
|
|
26
|
+
assert.ok(row.active_context_tokens < row.pricing_threshold_tokens);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('CLI JSON context eval is stable and offline', () => {
|
|
30
|
+
const result = spawnSync(process.execPath, ['src/cli.js', 'eval', 'context', '--fixture', 'two-file-repair', '--strategy', 'discovery', '--json'], { cwd: repoRoot, encoding: 'utf8' });
|
|
31
|
+
assert.equal(result.status, 0, result.stderr);
|
|
32
|
+
const report = JSON.parse(result.stdout);
|
|
33
|
+
assert.equal(report.fixture, 'two-file-repair');
|
|
34
|
+
assert.equal(report.live_provider_used, false);
|
|
35
|
+
assert.equal(report.strategies[0].strategy, 'discovery');
|
|
36
|
+
assert.equal(report.strategies[0].result, 'PASS');
|
|
37
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
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 { grantExecutionApproval, isExecutionApproved, toolApprovalKey } from '../src/lib/execution-approvals.js';
|
|
7
|
+
|
|
8
|
+
function tempHome(t) {
|
|
9
|
+
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-approval-home-'));
|
|
10
|
+
t.after(() => fs.rmSync(home, { recursive: true, force: true }));
|
|
11
|
+
return home;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test('execution approvals are exact-command and project scoped', t => {
|
|
15
|
+
const home = tempHome(t);
|
|
16
|
+
const one = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-project-a-'));
|
|
17
|
+
const two = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-project-b-'));
|
|
18
|
+
t.after(() => fs.rmSync(one, { recursive: true, force: true }));
|
|
19
|
+
t.after(() => fs.rmSync(two, { recursive: true, force: true }));
|
|
20
|
+
const key = toolApprovalKey('cloudflare.wrangler.native', { command: 'whoami' });
|
|
21
|
+
assert.equal(key, 'cloudflare.wrangler.native:whoami');
|
|
22
|
+
assert.equal(isExecutionApproved({ cwd: one, key }, { home }), false);
|
|
23
|
+
grantExecutionApproval({ cwd: one, key }, { home });
|
|
24
|
+
assert.equal(isExecutionApproved({ cwd: one, key }, { home }), true);
|
|
25
|
+
assert.equal(isExecutionApproved({ cwd: two, key }, { home }), false);
|
|
26
|
+
assert.equal(isExecutionApproved({ cwd: one, key: 'cloudflare.wrangler.native:versions.list' }, { home }), false);
|
|
27
|
+
});
|