@inneranimalmedia/agentsam-sdk 2.4.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTSAM.md +55 -0
- package/README.md +17 -12
- package/bin/agentsam +2 -0
- package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
- package/docs/AGENTSAM_WORKBENCH.md +30 -0
- package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
- package/docs/CAPABILITIES.md +5 -3
- package/docs/CLI_SHELL.md +167 -80
- package/docs/CMS_STUDIO.md +101 -0
- package/docs/CONTEXT.md +170 -0
- package/docs/LOCAL_OLLAMA.md +58 -0
- package/docs/MERKLE.md +19 -19
- package/docs/PORTABLE_CONTEXT.md +4 -3
- package/docs/PROJECT_CONFIG.md +72 -0
- package/docs/RELEASES.md +35 -2
- package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
- package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
- package/docs/SDK_WORKER.md +86 -0
- package/docs/SECURITY.md +59 -22
- package/docs/client-cms-editor.md +15 -0
- package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
- package/package.json +35 -10
- package/packages/connectors/cloudflare/package.json +10 -0
- package/packages/connectors/cloudflare/src/index.js +127 -0
- package/packages/connectors/cloudflare/src/owner.js +76 -0
- package/packages/connectors/cloudflare/src/routes.js +223 -0
- package/packages/connectors/cloudflare/src/vault.js +80 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
- package/packages/identity/package.json +10 -1
- package/packages/identity/src/contracts/auth-config.js +109 -0
- package/packages/identity/src/index.js +1 -0
- package/packages/identity/src/oauth/README.md +2 -2
- package/packages/identity/src/oauth/credentials.js +11 -4
- package/packages/identity/src/oauth/iam-platform.js +3 -3
- package/packages/identity/src/providers/iam/index.js +7 -7
- package/packages/identity/src/providers/iam/oauth.js +6 -4
- package/packages/identity/src/providers/iam/profile.js +5 -5
- package/packages/identity/tests/auth-config.test.mjs +61 -0
- package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
- package/protocol/FILEMETA_V1.md +95 -0
- package/protocol/INSPECT_VIEWS_V1.md +40 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
- package/protocol/MERKLE_V1.md +3 -1
- package/protocol/README.md +1 -0
- package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
- package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
- package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
- package/protocol/capabilities/manifest.json +79 -3
- package/protocol/capabilities/repository-snapshot.schema.json +1 -0
- package/protocol/context/context-budget.schema.json +36 -0
- package/protocol/context/context-item.schema.json +19 -0
- package/protocol/context/resolved-context-pack.schema.json +47 -0
- package/protocol/context/result-policy.schema.json +17 -0
- package/protocol/knowledge/context-pack.schema.json +33 -0
- package/protocol/knowledge/retrieval-query.schema.json +68 -13
- package/protocol/models/README.md +373 -0
- package/protocol/models/model-inventory-v2.schema.json +212 -0
- package/python/README.md +15 -7
- package/python/agentsam_sdk/cli.py +0 -21
- package/python/agentsam_sdk/tui/README.md +17 -12
- package/python/agentsam_sdk/tui/bootstrap.py +2 -2
- package/python/agentsam_sdk/tui/demo.py +25 -10
- package/python/agentsam_sdk/tui/onboarding.py +208 -0
- package/python/tests/test_tui_cli.py +7 -6
- package/skills/README.md +21 -0
- package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
- package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
- package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
- package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
- package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
- package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
- package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
- package/skills/agentsam-jr-dev/SKILL.md +232 -0
- package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
- package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
- package/skills/agentsam-progression-guard/SKILL.md +197 -0
- package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
- package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
- package/skills/catalog.json +71 -0
- package/src/agent/capability-adapter.js +25 -13
- package/src/agent/index.js +1 -0
- package/src/agent/responses-runner.js +325 -0
- package/src/capabilities/index.js +7 -0
- package/src/capabilities/repository-snapshot-view.js +238 -0
- package/src/capabilities/repository-snapshot.js +28 -14
- package/src/cli.js +198 -76
- package/src/cloudflare/cpu-profile.js +115 -0
- package/src/cloudflare/index.js +14 -0
- package/src/cloudflare/wrangler.js +132 -0
- package/src/commands/account-auth.js +47 -0
- package/src/commands/cloudflare.js +58 -0
- package/src/commands/connections.js +93 -0
- package/src/commands/context-economics.js +114 -0
- package/src/commands/context.js +14 -2
- package/src/commands/db.js +4 -7
- package/src/commands/deploy.js +54 -32
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +18 -0
- package/src/commands/knowledge.js +27 -4
- package/src/commands/merkle-persist.js +118 -0
- package/src/commands/merkle.js +32 -17
- package/src/commands/models.js +152 -0
- package/src/commands/ollama.js +259 -0
- package/src/commands/preferences.js +144 -0
- package/src/commands/product.js +86 -16
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +8 -6
- package/src/commands/shell.js +405 -100
- package/src/commands/skills.js +66 -0
- package/src/commands/start-local.js +1 -1
- package/src/commands/tunnel.js +5 -4
- package/src/commands/whoami.js +86 -0
- package/src/context/budget.js +120 -0
- package/src/context/compact.js +28 -0
- package/src/context/index.js +7 -0
- package/src/context/rehydrate.js +35 -0
- package/src/context/resolve.js +116 -0
- package/src/context/result-policy.js +66 -0
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +9 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +70 -0
- package/src/indexing/execution-boundary.js +144 -0
- package/src/indexing/index.js +8 -0
- package/src/indexing/provider.js +41 -0
- package/src/knowledge/config.js +2 -2
- package/src/knowledge/context-pack.js +12 -1
- package/src/knowledge/contracts.js +9 -4
- package/src/knowledge/engine.js +7 -3
- package/src/knowledge/service/server.js +1 -1
- package/src/lib/account-session.js +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/bridge-client.js +7 -5
- package/src/lib/cli-preferences.js +78 -0
- package/src/lib/core-client.js +8 -8
- package/src/lib/deploy/git-guard.js +69 -0
- package/src/lib/deploy/health.js +57 -0
- package/src/lib/deploy/local-studio.js +283 -0
- package/src/lib/deploy/secret-scan.js +65 -0
- package/src/lib/deploy-receipt/index.js +5 -2
- package/src/lib/detect-context.js +6 -5
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/identity-scaffold.js +1 -1
- package/src/lib/local-scaffold.js +35 -33
- package/src/lib/local-sessions.js +127 -0
- package/src/lib/local-status.js +9 -17
- package/src/lib/merkle/cloudflare-persistence.js +321 -0
- package/src/lib/merkle/filemeta.js +43 -0
- package/src/lib/merkle/git-ignore.js +24 -0
- package/src/lib/merkle/hash.js +1 -0
- package/src/lib/merkle/index.js +22 -0
- package/src/lib/merkle/persistence.js +72 -0
- package/src/lib/merkle/semantic.js +359 -0
- package/src/lib/merkle/snapshot.js +9 -3
- package/src/lib/merkle/tree.js +11 -6
- package/src/lib/project-config.js +227 -0
- package/src/lib/project-rules.js +68 -0
- package/src/lib/provider-credentials.js +83 -0
- package/src/lib/save-sdk-token.js +1 -1
- package/src/lib/scaffold/templates/worker-api/index.js +101 -20
- package/src/lib/scaffold/wizards/worker-api.js +27 -11
- package/src/lib/slash-commands.js +22 -15
- package/src/lib/tools.js +11 -5
- package/src/models/catalog.js +135 -0
- package/src/models/index.js +7 -0
- package/src/providers/index.js +5 -0
- package/src/providers/openai-responses.js +275 -0
- package/src/security/index.js +1 -0
- package/src/security/inventory.js +4 -1
- package/src/security/process.js +35 -9
- package/src/security/render.js +27 -5
- package/src/security/scan.js +24 -9
- package/src/security/trust-boundary.js +24 -0
- package/src/skills/index.js +64 -0
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +48 -0
- package/src/telemetry/index.js +8 -0
- package/src/tools/hydrate.js +35 -0
- package/src/tools/index.js +2 -0
- package/src/tools/search.js +70 -0
- package/src/ui/ansi.js +1 -1
- package/src/ui/boot.js +54 -0
- package/src/ui/merkle/render.js +1 -0
- package/src/ui/runtime-activity.js +192 -0
- package/src/ui/theme.js +19 -18
- package/test/account-session.test.mjs +36 -0
- package/test/app-building-skills.test.mjs +61 -0
- package/test/apps-scaffold-contract.test.mjs +56 -0
- package/test/capabilities.test.mjs +53 -4
- package/test/cli-preferences.test.mjs +46 -0
- package/test/cloudflare-connector.test.mjs +96 -0
- package/test/cloudflare-runtime.test.mjs +75 -0
- package/test/context.test.mjs +144 -0
- package/test/deploy-health-scan.test.mjs +67 -0
- package/test/error-diagnostics.test.mjs +59 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- package/test/indexing-provider.test.mjs +29 -0
- package/test/jr-dev-skill.test.mjs +26 -0
- package/test/knowledge-context-pack.test.mjs +19 -0
- package/test/knowledge.test.mjs +1 -1
- package/test/local-sessions.test.mjs +42 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/merkle-persistence.test.mjs +91 -0
- package/test/merkle.test.mjs +77 -3
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +51 -0
- package/test/npm10-lock.test.mjs +29 -0
- package/test/ollama.test.mjs +94 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/project-config.test.mjs +81 -0
- package/test/project-rules.test.mjs +44 -0
- package/test/provider-credentials.test.mjs +52 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +34 -0
- package/test/repository-snapshot-view.test.mjs +112 -0
- package/test/responses-runner.test.mjs +148 -0
- package/test/runtime-activity.test.mjs +98 -0
- package/test/sdk-worker-contract.test.mjs +68 -0
- package/test/security.test.mjs +46 -0
- package/test/shell.test.mjs +55 -21
- package/test/skills.test.mjs +22 -0
- package/test/smoke.mjs +5 -2
- package/test/telemetry.test.mjs +79 -0
- package/test/theme-portability.test.mjs +14 -0
- package/test/tools-search.test.mjs +40 -0
- package/test/whoami-resume.test.mjs +56 -0
- package/examples/agentsam-tui-ansi.mjs +0 -149
- package/src/commands/tui.js +0 -120
- package/src/ui/splash-xterm.js +0 -290
- package/src/ui/splash.js +0 -426
package/src/ui/theme.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file src/ui/theme.js
|
|
3
|
-
* @description
|
|
4
|
-
*
|
|
5
|
-
* - xterm.js panels in XTermShell.tsx (pass theme object to Terminal constructor)
|
|
6
|
-
* - CLI ANSI output in agentsam-sdk (import ANSI constants)
|
|
7
|
-
* - Splash/HUD renders in both contexts
|
|
3
|
+
* @description Agent Sam terminal theme system — portable color and semantic
|
|
4
|
+
* status tokens for native ANSI output and browser/xterm consumers.
|
|
8
5
|
*
|
|
9
|
-
* Lane identity:
|
|
10
|
-
* LOCAL → amber (
|
|
11
|
-
*
|
|
12
|
-
* SANDBOX → purple (
|
|
6
|
+
* Lane identity is intentionally provider-neutral:
|
|
7
|
+
* LOCAL → amber (the user's current machine / real shell)
|
|
8
|
+
* REMOTE → teal (a connected remote runtime, provider chosen by the host)
|
|
9
|
+
* SANDBOX → purple (an isolated/disposable runtime, provider chosen by the host)
|
|
10
|
+
*
|
|
11
|
+
* Infrastructure names, domains, machine names, and account-specific routing
|
|
12
|
+
* never belong in this SDK theme. Hosts may supply their own runtime labels.
|
|
13
13
|
*
|
|
14
14
|
* No dependencies. ESM + CJS compatible (conditional exports in package.json).
|
|
15
15
|
*/
|
|
@@ -31,7 +31,7 @@ export const PALETTE = {
|
|
|
31
31
|
ghost: '#aaaaaa',
|
|
32
32
|
white: '#e8e8e8',
|
|
33
33
|
|
|
34
|
-
// LOCAL lane — amber/gold (
|
|
34
|
+
// LOCAL lane — amber/gold (current machine / real shell)
|
|
35
35
|
amber900: '#0d0900',
|
|
36
36
|
amber800: '#1a1200',
|
|
37
37
|
amber700: '#2d1f00',
|
|
@@ -43,7 +43,7 @@ export const PALETTE = {
|
|
|
43
43
|
amber100: '#fde68a',
|
|
44
44
|
amber050: '#fef3c7',
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// REMOTE lane — teal/cyan (provider-neutral connected runtime)
|
|
47
47
|
teal900: '#000d0b',
|
|
48
48
|
teal800: '#001a17',
|
|
49
49
|
teal700: '#002e28',
|
|
@@ -55,7 +55,7 @@ export const PALETTE = {
|
|
|
55
55
|
teal100: '#99f6e4',
|
|
56
56
|
teal050: '#ccfbf1',
|
|
57
57
|
|
|
58
|
-
// SANDBOX lane — purple (
|
|
58
|
+
// SANDBOX lane — purple (provider-neutral isolated runtime)
|
|
59
59
|
purple900: '#06020d',
|
|
60
60
|
purple800: '#0d0520',
|
|
61
61
|
purple700: '#1e0a3d',
|
|
@@ -130,26 +130,26 @@ const makeLane = ({ bg, bgSubtle, primary, secondary, dim, accent, name, label,
|
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
133
|
-
//
|
|
133
|
+
// PORTABLE EXECUTION LANES
|
|
134
134
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
135
135
|
|
|
136
136
|
export const LOCAL = makeLane({
|
|
137
137
|
name: 'local', label: 'Local', icon: '◈',
|
|
138
|
-
tunnelHint: '
|
|
138
|
+
tunnelHint: 'current machine · real shell',
|
|
139
139
|
bg: '#0d0900', bgSubtle: '#1a1200',
|
|
140
140
|
primary: '#f59e0b', secondary: '#fbbf24', dim: '#78450a', accent: '#22d3c8',
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
export const CLOUD = makeLane({
|
|
144
|
-
name: '
|
|
145
|
-
tunnelHint: '
|
|
144
|
+
name: 'remote', label: 'Remote', icon: '●',
|
|
145
|
+
tunnelHint: 'connected remote runtime',
|
|
146
146
|
bg: '#000d0b', bgSubtle: '#001a17',
|
|
147
147
|
primary: '#22d3c8', secondary: '#5eead4', dim: '#065f52', accent: '#f59e0b',
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
export const SANDBOX = makeLane({
|
|
151
151
|
name: 'sandbox', label: 'Sandbox', icon: '◌',
|
|
152
|
-
tunnelHint: '
|
|
152
|
+
tunnelHint: 'isolated disposable runtime',
|
|
153
153
|
bg: '#06020d', bgSubtle: '#0d0520',
|
|
154
154
|
primary: '#a78bfa', secondary: '#c4b5fd', dim: '#4c1d95', accent: '#22d3c8',
|
|
155
155
|
});
|
|
@@ -173,11 +173,12 @@ export function getLaneTheme(targetType) {
|
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
175
|
* Get lane theme by name string (for SDK CLI use).
|
|
176
|
-
* @param {'local'|'cloud'|'sandbox'|string} name
|
|
176
|
+
* @param {'local'|'remote'|'cloud'|'sandbox'|string} name
|
|
177
177
|
*/
|
|
178
178
|
export function getLaneThemeByName(name) {
|
|
179
179
|
switch (name?.toLowerCase()) {
|
|
180
180
|
case 'local': return LOCAL;
|
|
181
|
+
case 'remote':
|
|
181
182
|
case 'cloud': return CLOUD;
|
|
182
183
|
case 'sandbox': return SANDBOX;
|
|
183
184
|
default: return CLOUD;
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { getSkill, listSkills, loadSkill } from '../src/skills/index.js';
|
|
7
|
+
|
|
8
|
+
const root = path.resolve(import.meta.dirname, '..');
|
|
9
|
+
const cli = path.join(root, 'src', 'cli.js');
|
|
10
|
+
|
|
11
|
+
test('application fundamentals and progression guard are portable registered skills', () => {
|
|
12
|
+
const ids = new Set(listSkills().map((skill) => skill.id));
|
|
13
|
+
assert.ok(ids.has('agentsam-app-fundamentals'));
|
|
14
|
+
assert.ok(ids.has('agentsam-progression-guard'));
|
|
15
|
+
assert.equal(getSkill('quick-bytes')?.id, 'agentsam-app-fundamentals');
|
|
16
|
+
assert.equal(getSkill('no-regress')?.id, 'agentsam-progression-guard');
|
|
17
|
+
|
|
18
|
+
const fundamentals = loadSkill('quick-bytes', { references: true });
|
|
19
|
+
assert.equal(fundamentals.references.length, 2);
|
|
20
|
+
assert.match(fundamentals.instructions, /Credentialed-destination card/);
|
|
21
|
+
assert.match(fundamentals.instructions, /AST/);
|
|
22
|
+
assert.match(fundamentals.instructions, /Merkle/);
|
|
23
|
+
assert.match(fundamentals.instructions, /Firewall\/WAF/);
|
|
24
|
+
assert.match(fundamentals.references[0].content, /OAuth redirect mental model/);
|
|
25
|
+
assert.match(fundamentals.references[1].content, /The codebase is a graph/);
|
|
26
|
+
|
|
27
|
+
const guard = loadSkill('no-regress', { references: true });
|
|
28
|
+
assert.equal(guard.references.length, 2);
|
|
29
|
+
assert.match(guard.instructions, /failed candidate must not advance the last-known-good baseline/);
|
|
30
|
+
assert.match(guard.instructions, /agentsam deploy-receipt capture/);
|
|
31
|
+
assert.match(guard.references[0].content, /Gate 6 — postdeploy/);
|
|
32
|
+
assert.match(guard.references[1].content, /Ingress versus egress hooks/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('skills CLI lists compact cards and loads quick reminders by alias', () => {
|
|
36
|
+
const listed = spawnSync(process.execPath, [cli, 'skills', '--json'], { cwd: root, encoding: 'utf8' });
|
|
37
|
+
assert.equal(listed.status, 0, listed.stderr);
|
|
38
|
+
const catalog = JSON.parse(listed.stdout);
|
|
39
|
+
assert.ok(catalog.some((skill) => skill.id === 'agentsam-app-fundamentals'));
|
|
40
|
+
assert.ok(catalog.some((skill) => skill.id === 'agentsam-progression-guard'));
|
|
41
|
+
|
|
42
|
+
const loaded = spawnSync(process.execPath, [cli, 'skills', 'quick-bytes'], { cwd: root, encoding: 'utf8' });
|
|
43
|
+
assert.equal(loaded.status, 0, loaded.stderr);
|
|
44
|
+
assert.match(loaded.stdout, /# AgentSam Application Fundamentals/);
|
|
45
|
+
assert.doesNotMatch(loaded.stdout, /# Trust, Credentials, and Credentialed Destinations/);
|
|
46
|
+
|
|
47
|
+
const full = spawnSync(process.execPath, [cli, 'skills', 'no-regress', '--references'], { cwd: root, encoding: 'utf8' });
|
|
48
|
+
assert.equal(full.status, 0, full.stderr);
|
|
49
|
+
assert.match(full.stdout, /# AgentSam Progression Guard/);
|
|
50
|
+
assert.match(full.stdout, /# Checkpoint Chain/);
|
|
51
|
+
assert.match(full.stdout, /# Hooks and Operational I\/O/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('portable npm package still owns all skill files', () => {
|
|
55
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
56
|
+
assert.ok(pkg.files.includes('skills'));
|
|
57
|
+
for (const rel of [
|
|
58
|
+
'skills/agentsam-app-fundamentals/SKILL.md',
|
|
59
|
+
'skills/agentsam-progression-guard/SKILL.md',
|
|
60
|
+
]) assert.equal(fs.existsSync(path.join(root, rel)), true, `${rel} must exist`);
|
|
61
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const productApps = ['local-studio', 'cad-creator', 'client-cms-editor'];
|
|
9
|
+
const at = (...parts) => path.join(root, ...parts);
|
|
10
|
+
const exists = (...parts) => fs.existsSync(at(...parts));
|
|
11
|
+
|
|
12
|
+
function packageJson(...parts) {
|
|
13
|
+
return JSON.parse(fs.readFileSync(at(...parts, 'package.json'), 'utf8'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('product apps are self-contained npm workspace roots with one lockfile', () => {
|
|
17
|
+
for (const app of productApps) {
|
|
18
|
+
const base = ['apps', app];
|
|
19
|
+
assert.ok(exists(...base, 'package.json'), `${app}: root package.json`);
|
|
20
|
+
assert.ok(exists(...base, 'package-lock.json'), `${app}: root package-lock.json`);
|
|
21
|
+
assert.ok(exists(...base, 'frontend', 'package.json'), `${app}: frontend package`);
|
|
22
|
+
assert.ok(exists(...base, 'backend', 'package.json'), `${app}: backend package`);
|
|
23
|
+
assert.equal(exists(...base, 'frontend', 'package-lock.json'), false, `${app}: no frontend lockfile`);
|
|
24
|
+
assert.equal(exists(...base, 'backend', 'package-lock.json'), false, `${app}: no backend lockfile`);
|
|
25
|
+
|
|
26
|
+
const pkg = packageJson(...base);
|
|
27
|
+
assert.deepEqual(pkg.workspaces, ['frontend', 'backend', 'shared/*'], `${app}: canonical workspace list`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('every product app owns its Cloudflare boundary under backend/', () => {
|
|
32
|
+
for (const app of productApps) {
|
|
33
|
+
const base = ['apps', app, 'backend'];
|
|
34
|
+
assert.ok(exists(...base, 'worker', 'index.js'), `${app}: backend/worker/index.js`);
|
|
35
|
+
assert.ok(exists(...base, 'wrangler.jsonc'), `${app}: backend/wrangler.jsonc`);
|
|
36
|
+
const wrangler = fs.readFileSync(at(...base, 'wrangler.jsonc'), 'utf8');
|
|
37
|
+
assert.match(wrangler, /"main"\s*:\s*"worker\/index\.js"/, `${app}: wrangler main`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('SDK root is package tooling only and does not own an app Worker', () => {
|
|
42
|
+
assert.equal(exists('wrangler.jsonc'), false);
|
|
43
|
+
assert.equal(exists('worker', 'index.js'), false);
|
|
44
|
+
assert.equal(exists('scripts', 'with-cloudflare-env.sh'), false);
|
|
45
|
+
|
|
46
|
+
const rootPkg = packageJson();
|
|
47
|
+
for (const workspace of rootPkg.workspaces || []) {
|
|
48
|
+
assert.equal(workspace.startsWith('apps/'), false, `root workspace must not absorb app workspace: ${workspace}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('Local Studio retired donor-era duplicate deployment configs', () => {
|
|
53
|
+
for (const file of ['wrangler.ui.toml', 'wrangler.workmode.toml', 'wrangler.toml.example']) {
|
|
54
|
+
assert.equal(exists('apps', 'local-studio', 'backend', file), false, `retire ${file}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
@@ -6,6 +6,7 @@ import path from 'node:path';
|
|
|
6
6
|
import { execFileSync } from 'node:child_process';
|
|
7
7
|
import { getCapability, getCapabilityManifest, repositorySnapshot } from '../src/capabilities/index.js';
|
|
8
8
|
import { getPreset, resolvePreset } from '../src/presets/index.js';
|
|
9
|
+
import { scanTrustBoundary } from '../src/security/trust-boundary.js';
|
|
9
10
|
|
|
10
11
|
const CLI = path.resolve('src/cli.js');
|
|
11
12
|
|
|
@@ -51,11 +52,51 @@ test('repository.snapshot composes deterministic evidence and content addressing
|
|
|
51
52
|
assert.equal(one.snapshot_id, two.snapshot_id);
|
|
52
53
|
assert.equal(one.repository.revision_sha, git(root, ['rev-parse', 'HEAD']));
|
|
53
54
|
assert.ok(one.tree.merkle_root);
|
|
55
|
+
assert.match(one.tree.metadata_root, /^sha256:[a-f0-9]{64}$/);
|
|
56
|
+
assert.equal(one.tree.manifest.format, 'agentsam-merkle');
|
|
57
|
+
assert.equal(one.tree.manifest.version, 1);
|
|
58
|
+
assert.equal(one.tree.manifest.hash_algorithm, 'sha256');
|
|
59
|
+
assert.match(one.tree.manifest.policy_hash, /^sha256:[a-f0-9]{64}$/);
|
|
60
|
+
assert.equal(one.tree.classifier.format, 'agentsam-filemeta');
|
|
61
|
+
assert.equal(one.tree.classifier.version, 1);
|
|
62
|
+
const source = one.tree.files.find((entry) => entry.path === 'src/index.js');
|
|
63
|
+
assert.equal(source.mode, 420);
|
|
64
|
+
assert.equal(source.system, 'snapshot-fixture');
|
|
65
|
+
assert.equal(source.language, 'javascript');
|
|
66
|
+
assert.deepEqual(source.symbols, ['value']);
|
|
67
|
+
assert.equal(one.analysis.trust_boundary.analyzer, 'agentsam-execution-boundary');
|
|
68
|
+
assert.equal(one.analysis.trust_boundary.metadata_root, one.tree.metadata_root);
|
|
69
|
+
assert.equal(one.analysis.trust_boundary.complete, true);
|
|
70
|
+
assert.equal(one.analysis.trust_boundary.status, 'clean');
|
|
54
71
|
assert.ok(one.intelligence.summary.file_count >= 2);
|
|
55
72
|
assert.equal(one.packages[0].name, 'snapshot-fixture');
|
|
56
73
|
assert.equal(one.knowledge.configured, false);
|
|
57
74
|
});
|
|
58
75
|
|
|
76
|
+
test('repository inspection and security scan share one Git-ignore/Merkle trust authority', async t => {
|
|
77
|
+
const root = fixture();
|
|
78
|
+
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
79
|
+
fs.mkdirSync(path.join(root, 'frontend'));
|
|
80
|
+
fs.mkdirSync(path.join(root, 'backend'));
|
|
81
|
+
fs.mkdirSync(path.join(root, 'ignored'));
|
|
82
|
+
fs.writeFileSync(path.join(root, 'frontend', 'client.ts'), "import { secretValue } from '../backend/secrets.ts';\nexport const value = secretValue;\n");
|
|
83
|
+
fs.writeFileSync(path.join(root, 'backend', 'secrets.ts'), "export const secretValue = process.env.API_KEY;\n");
|
|
84
|
+
fs.writeFileSync(path.join(root, 'ignored', 'noise.ts'), "import fs from 'node:fs'; export const x = process.env.PRIVATE_TOKEN + String(fs);\n");
|
|
85
|
+
fs.writeFileSync(path.join(root, '.gitignore'), 'ignored/\n');
|
|
86
|
+
|
|
87
|
+
const snapshot = await repositorySnapshot({ cwd: root, churnDays: 30 });
|
|
88
|
+
const boundary = await scanTrustBoundary(root);
|
|
89
|
+
assert.equal(snapshot.tree.metadata_root, boundary.metadata_root);
|
|
90
|
+
assert.equal(snapshot.tree.merkle_root, boundary.merkle_root);
|
|
91
|
+
assert.equal(snapshot.analysis.trust_boundary.status, 'action-required');
|
|
92
|
+
assert.deepEqual(
|
|
93
|
+
snapshot.analysis.trust_boundary.findings.map((item) => [item.kind, item.source, item.target]),
|
|
94
|
+
boundary.findings.map((item) => [item.kind, item.source, item.target]),
|
|
95
|
+
);
|
|
96
|
+
assert.equal(snapshot.tree.paths.some((value) => value.startsWith('ignored/')), false);
|
|
97
|
+
assert.equal(boundary.findings.some((item) => item.source.startsWith('ignored/')), false);
|
|
98
|
+
});
|
|
99
|
+
|
|
59
100
|
test('product UX creates a preset project, adds a feature, and inspects before first commit', t => {
|
|
60
101
|
const parent = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-product-'));
|
|
61
102
|
t.after(() => fs.rmSync(parent, { recursive: true, force: true }));
|
|
@@ -65,10 +106,15 @@ test('product UX creates a preset project, adds a feature, and inspects before f
|
|
|
65
106
|
});
|
|
66
107
|
const project = path.join(parent, 'demo');
|
|
67
108
|
const config = JSON.parse(fs.readFileSync(path.join(project, '.agentsam', 'config.json'), 'utf8'));
|
|
68
|
-
assert.equal(config.
|
|
69
|
-
assert.equal(config.
|
|
70
|
-
assert.
|
|
71
|
-
assert.
|
|
109
|
+
assert.equal(config.schema_version, 2);
|
|
110
|
+
assert.equal(config.project.name, 'demo');
|
|
111
|
+
assert.equal(config.product.preset, 'cms');
|
|
112
|
+
assert.deepEqual(config.product.features, ['cms', 'knowledge']);
|
|
113
|
+
assert.ok(config.product.capabilities.includes('repository.snapshot'));
|
|
114
|
+
assert.match(config.repository.id, /^local:[0-9a-f-]{36}$/);
|
|
115
|
+
assert.equal(config.defaults.runtime, 'local');
|
|
116
|
+
assert.equal(config.defaults.model, 'auto');
|
|
117
|
+
assert.equal(fs.existsSync(path.join(project, 'agentsam.config.js')), false);
|
|
72
118
|
|
|
73
119
|
execFileSync(process.execPath, [CLI, 'add', 'knowledge', '--cwd', project, '--json'], { stdio: 'pipe' });
|
|
74
120
|
const features = JSON.parse(fs.readFileSync(path.join(project, '.agentsam', 'features.json'), 'utf8'));
|
|
@@ -79,6 +125,9 @@ test('product UX creates a preset project, adds a feature, and inspects before f
|
|
|
79
125
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
126
|
}));
|
|
81
127
|
assert.equal(snapshot.capability, 'repository.snapshot');
|
|
128
|
+
assert.equal(snapshot.repository.repository_id, config.repository.id);
|
|
129
|
+
assert.equal(snapshot.repository.identity_source, 'project-config');
|
|
82
130
|
assert.equal(snapshot.repository.revision_sha, null);
|
|
83
131
|
assert.ok(snapshot.tree.stats.files > 0);
|
|
132
|
+
assert.ok(snapshot.analysis?.trust_boundary);
|
|
84
133
|
});
|
|
@@ -0,0 +1,46 @@
|
|
|
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 { CLI_PREFERENCES_SCHEMA, detectCliProject, readCliPreferences, writeCliPreferences } from '../src/lib/cli-preferences.js';
|
|
7
|
+
import { renderBootSummary } from '../src/ui/boot.js';
|
|
8
|
+
|
|
9
|
+
test('CLI preferences persist local model/runtime controls without becoming routing authority', () => {
|
|
10
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-cli-prefs-'));
|
|
11
|
+
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'demo-project' }));
|
|
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
|
+
});
|
|
20
|
+
assert.equal(written.schemaVersion, CLI_PREFERENCES_SCHEMA);
|
|
21
|
+
assert.equal(written.modelAuthority, 'preference-only');
|
|
22
|
+
assert.equal(written.trustedDirectory, true);
|
|
23
|
+
assert.deepEqual(readCliPreferences(root), written);
|
|
24
|
+
const identity = detectCliProject(root);
|
|
25
|
+
assert.equal(identity.project, 'demo-project');
|
|
26
|
+
assert.equal(identity.root, root);
|
|
27
|
+
const summary = renderBootSummary({ identity, preferences: written });
|
|
28
|
+
assert.match(summary, /Agent Sam/);
|
|
29
|
+
assert.match(summary, /demo-project/);
|
|
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');
|
|
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
|
+
});
|