@inneranimalmedia/agentsam-sdk 2.6.0 → 2.6.2
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/docs/AUTH_IDENTITY_CONTRACT.md +25 -27
- package/docs/PLATFORM_RUNTIME_EVENTS.md +48 -0
- package/docs/RELEASES.md +7 -7
- package/docs/SDK_WORKER.md +4 -3
- package/docs/SOURCE_ARCHITECTURE.md +58 -0
- package/docs/TEST_TIERS.md +26 -0
- package/migrations/runtime/0001_cli_runtime.sql +298 -0
- package/package.json +28 -7
- package/packages/agentsam-repository/README.md +15 -0
- package/packages/agentsam-repository/package.json +25 -0
- package/packages/agentsam-repository/src/contracts.js +113 -0
- package/packages/agentsam-repository/src/index.js +3 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/cloudflare-persistence.js +14 -24
- package/{src/lib → packages/agentsam-repository/src}/merkle/index.js +1 -0
- package/{src/lib → packages/agentsam-repository/src}/merkle/persistence.js +6 -4
- package/{src/lib → packages/agentsam-repository/src}/merkle/policy.js +1 -0
- package/packages/agentsam-repository/test/contracts.test.mjs +40 -0
- package/packages/agentsam-repository/test/git-context.test.mjs +24 -0
- package/{test/merkle.test.mjs → packages/agentsam-repository/test/merkle-core.test.mjs} +2 -32
- package/{test → packages/agentsam-repository/test}/merkle-persistence.test.mjs +11 -6
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/contracts/auth-config.js +23 -40
- package/packages/identity/tests/auth-config.test.mjs +14 -23
- package/protocol/COMPANY_REPOSITORY_GRAPH_V1.md +91 -0
- package/protocol/MERKLE_PERSISTENCE_V1.md +2 -0
- package/protocol/MERKLE_PERSISTENCE_V2.md +40 -0
- package/protocol/repository/repository-contract.schema.json +24 -0
- package/protocol/repository/repository-dependency.schema.json +24 -0
- package/protocol/repository/repository-identity.schema.json +17 -0
- package/protocol/rpc/v1/common.proto +16 -0
- package/protocol/rpc/v1/errors.proto +35 -0
- package/protocol/rpc/v1/knowledge.proto +77 -0
- package/services/knowledge/package-lock.json +333 -0
- package/services/knowledge/package.json +5 -1
- package/src/agent/responses-runner.js +63 -35
- package/src/capabilities/repository-snapshot.js +3 -3
- package/src/cli.js +36 -29
- package/src/commands/account-auth.js +1 -1
- package/src/commands/context-economics.js +17 -2
- package/src/commands/context.js +1 -1
- package/src/commands/db.js +20 -3
- package/src/commands/deploy.js +2 -2
- package/src/commands/env.js +90 -0
- package/src/commands/knowledge.js +12 -4
- package/src/commands/merkle-persist.js +30 -11
- package/src/commands/merkle.js +1 -1
- package/src/commands/models.js +123 -65
- package/src/commands/ollama.js +26 -0
- package/src/commands/preferences.js +53 -26
- package/src/commands/providers.js +308 -0
- package/src/commands/shell.js +240 -48
- package/src/commands/tunnel.js +8 -8
- package/src/commands/whoami.js +42 -21
- package/src/errors/contract.js +236 -0
- package/src/errors/diagnostic.js +1 -0
- package/src/errors/index.js +14 -0
- package/src/index.js +13 -1
- package/src/knowledge/service/auth.js +13 -0
- package/src/knowledge/service/grpc-client.js +115 -0
- package/src/knowledge/service/grpc-codec.js +237 -0
- package/src/knowledge/service/grpc-server.js +83 -0
- package/src/knowledge/service/job-engine.js +248 -0
- package/src/knowledge/service/server.js +87 -135
- package/src/knowledge/source.js +1 -1
- package/src/lib/account-session.js +94 -25
- package/src/lib/auth.js +314 -51
- package/src/lib/cli-preferences.js +31 -4
- package/src/lib/core-client.js +75 -30
- package/src/lib/deploy-receipt/index.js +2 -2
- package/src/lib/detect-context.js +14 -16
- package/src/lib/knowledge-docker.js +6 -3
- package/src/lib/local-sessions.js +23 -2
- package/src/lib/local-status.js +1 -1
- package/src/lib/project-config.js +1 -1
- package/src/lib/provider-credentials.js +230 -14
- package/src/lib/slash-commands.js +5 -3
- package/src/local/migrations.js +93 -0
- package/src/local/runtime-store.js +141 -0
- package/src/local/sqlite.js +2 -0
- package/src/local-pty/server.js +113 -51
- package/src/models/discovery.js +327 -0
- package/src/providers/anthropic-messages.js +192 -0
- package/src/providers/cloudflare-chat.js +183 -0
- package/src/providers/factory.js +69 -0
- package/src/providers/gemini-generate-content.js +208 -0
- package/src/providers/index.js +5 -0
- package/src/providers/ollama-chat.js +148 -0
- package/src/providers/openai-responses.js +226 -75
- package/src/repository/index.js +14 -2
- package/src/rpc/generated/common_grpc_pb.js +1 -0
- package/src/rpc/generated/common_pb.js +536 -0
- package/src/rpc/generated/errors_grpc_pb.js +1 -0
- package/src/rpc/generated/errors_pb.js +482 -0
- package/src/rpc/generated/knowledge_grpc_pb.js +135 -0
- package/src/rpc/generated/knowledge_pb.js +2168 -0
- package/src/rpc/generated/package.json +3 -0
- package/src/security/trust-boundary.js +2 -2
- package/src/telemetry/events.js +4 -1
- package/src/ui/cli/activity.js +76 -0
- package/src/ui/cli/compaction.js +15 -0
- package/src/ui/cli/footer.js +39 -0
- package/src/ui/cli/help.js +194 -0
- package/src/ui/cli/plan.js +20 -0
- package/src/ui/cli/runtime-events.js +110 -0
- package/src/ui/cli/waiting.js +16 -0
- package/src/ui/merkle/render.js +1 -1
- package/test/account-session.test.mjs +48 -11
- package/test/apps-scaffold-contract.test.mjs +43 -0
- package/test/cli/preferences-runtime.test.mjs +11 -0
- package/test/cli/runtime-ui.test.mjs +74 -0
- package/test/error-diagnostics.test.mjs +57 -1
- package/test/fixtures/knowledge-rpc-worker.mjs +16 -0
- package/test/integration/cli-help.test.mjs +37 -0
- package/test/integration/knowledge-rpc.test.mjs +112 -0
- package/test/integration/merkle-cli.test.mjs +61 -0
- package/test/integration/merkle-persistence-identity.test.mjs +48 -0
- package/test/integration/provider-env-cli.test.mjs +49 -0
- package/test/integration/provider-factory.test.mjs +197 -0
- package/test/integration/repository-company-graph.test.mjs +90 -0
- package/test/integration/runtime-migrations.test.mjs +82 -0
- package/test/knowledge-service.test.mjs +5 -0
- package/test/knowledge.test.mjs +16 -0
- package/test/live/terminal-transport.live.test.mjs +24 -0
- package/test/local-sessions.test.mjs +7 -1
- package/test/models.test.mjs +101 -4
- package/test/ollama.test.mjs +21 -0
- package/test/portable-context.test.mjs +1 -1
- package/test/provider-credentials.test.mjs +45 -1
- package/test/release-hygiene.test.mjs +13 -5
- package/test/responses-runner.test.mjs +3 -1
- package/test/sdk-worker-contract.test.mjs +5 -1
- package/test/shell.test.mjs +50 -8
- package/test/smoke.mjs +2 -1
- package/test/terminal/local-pty.mock.test.mjs +151 -0
- package/test/whoami-resume.test.mjs +6 -5
- package/src/lib/prompt-byok.js +0 -57
- package/src/lib/save-sdk-token.js +0 -19
- /package/{src/lib → packages/agentsam-repository/src}/git-context.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/diff.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/filemeta.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/git-ignore.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/hash.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/semantic.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/snapshot.js +0 -0
- /package/{src/lib → packages/agentsam-repository/src}/merkle/tree.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { buildMerkleTree } from '
|
|
3
|
-
import { gitIgnoredPaths } from '
|
|
2
|
+
import { buildMerkleTree } from '../../packages/agentsam-repository/src/merkle/index.js';
|
|
3
|
+
import { gitIgnoredPaths } from '../../packages/agentsam-repository/src/merkle/git-ignore.js';
|
|
4
4
|
import { analyzeExecutionBoundaries } from '../indexing/execution-boundary.js';
|
|
5
5
|
|
|
6
6
|
export async function scanTrustBoundary(projectRoot = process.cwd(), options = {}) {
|
package/src/telemetry/events.js
CHANGED
|
@@ -5,7 +5,10 @@ export const AGENT_EVENT_TYPES = Object.freeze([
|
|
|
5
5
|
'usage.snapshot', 'cost.snapshot',
|
|
6
6
|
'context.snapshot', 'context.compaction.started', 'context.compaction.completed',
|
|
7
7
|
'tool.search', 'tool.started', 'tool.completed', 'tool.failed',
|
|
8
|
-
'
|
|
8
|
+
'approval.requested', 'approval.resolved',
|
|
9
|
+
'plan.updated', 'task.updated',
|
|
10
|
+
'timer.started', 'timer.updated', 'timer.completed',
|
|
11
|
+
'runtime.waiting_input',
|
|
9
12
|
]);
|
|
10
13
|
|
|
11
14
|
function integer(value, label) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
const CLEAR_LINE = '\x1b[2K';
|
|
4
|
+
const SPINNER = ['◐', '◓', '◑', '◒'];
|
|
5
|
+
|
|
6
|
+
function elapsed(ms) {
|
|
7
|
+
if (ms < 1000) return `${Math.max(0, Math.round(ms))}ms`;
|
|
8
|
+
return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)}s`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createInlineActivity(options = {}) {
|
|
12
|
+
const write = options.write || process.stdout.write.bind(process.stdout);
|
|
13
|
+
const interactive = options.interactive ?? Boolean(process.stdout?.isTTY);
|
|
14
|
+
const now = options.now || (() => Date.now());
|
|
15
|
+
const setTimer = options.setInterval || globalThis.setInterval;
|
|
16
|
+
const clearTimer = options.clearInterval || globalThis.clearInterval;
|
|
17
|
+
const intervalMs = Math.max(80, Number(options.intervalMs) || 120);
|
|
18
|
+
|
|
19
|
+
let label = String(options.label || 'Thinking');
|
|
20
|
+
let startedAt = 0;
|
|
21
|
+
let tick = 0;
|
|
22
|
+
let timer = null;
|
|
23
|
+
let active = false;
|
|
24
|
+
|
|
25
|
+
function frame() {
|
|
26
|
+
if (!active || !interactive) return;
|
|
27
|
+
const icon = pc.cyan(SPINNER[tick % SPINNER.length]);
|
|
28
|
+
write(`\r${CLEAR_LINE} ${icon} ${label} ${pc.dim('· ' + elapsed(now() - startedAt))}`);
|
|
29
|
+
tick += 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function start(nextLabel = label) {
|
|
33
|
+
if (active) {
|
|
34
|
+
update(nextLabel);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
label = String(nextLabel || label);
|
|
38
|
+
startedAt = now();
|
|
39
|
+
active = true;
|
|
40
|
+
if (interactive) {
|
|
41
|
+
frame();
|
|
42
|
+
timer = setTimer(frame, intervalMs);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function update(nextLabel) {
|
|
47
|
+
if (nextLabel) label = String(nextLabel);
|
|
48
|
+
if (!active) start(label);
|
|
49
|
+
else frame();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function finish(status = 'success', finalLabel = label) {
|
|
53
|
+
if (!active) return;
|
|
54
|
+
if (timer) clearTimer(timer);
|
|
55
|
+
timer = null;
|
|
56
|
+
const duration = elapsed(now() - startedAt);
|
|
57
|
+
if (interactive) write(`\r${CLEAR_LINE}`);
|
|
58
|
+
const icon = status === 'error' ? pc.red('✗') : pc.green('✓');
|
|
59
|
+
write(` ${icon} ${finalLabel} ${pc.dim('· ' + duration)}\n`);
|
|
60
|
+
active = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Object.freeze({
|
|
64
|
+
get active() { return active; },
|
|
65
|
+
start,
|
|
66
|
+
update,
|
|
67
|
+
succeed(labelText) { finish('success', labelText || label); },
|
|
68
|
+
fail(labelText) { finish('error', labelText || 'Failed'); },
|
|
69
|
+
clear() {
|
|
70
|
+
if (timer) clearTimer(timer);
|
|
71
|
+
timer = null;
|
|
72
|
+
if (interactive && active) write(`\r${CLEAR_LINE}`);
|
|
73
|
+
active = false;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
function n(value) {
|
|
4
|
+
const number = Number(value);
|
|
5
|
+
return Number.isFinite(number) ? Math.max(0, Math.round(number)).toLocaleString('en-US') : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function renderCompactionReceipt(payload = {}) {
|
|
9
|
+
const before = n(payload.tokens_before ?? payload.before_tokens);
|
|
10
|
+
const after = n(payload.tokens_after ?? payload.after_tokens);
|
|
11
|
+
const duration = Number(payload.duration_ms);
|
|
12
|
+
const detail = before && after ? ` ${before} → ${after}` : '';
|
|
13
|
+
const timing = Number.isFinite(duration) ? ` · ${duration < 1000 ? Math.round(duration) + 'ms' : (duration / 1000).toFixed(1) + 's'}` : '';
|
|
14
|
+
return ` ${pc.green('↻')} Context compacted${pc.dim(detail + timing)}`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
function count(value) {
|
|
4
|
+
const n = Number(value || 0);
|
|
5
|
+
if (!Number.isFinite(n)) return '0';
|
|
6
|
+
if (Math.abs(n) >= 1_000_000) {
|
|
7
|
+
const scaled = n / 1_000_000;
|
|
8
|
+
return (Number.isInteger(scaled) ? String(scaled) : scaled.toFixed(Math.abs(n) >= 10_000_000 ? 0 : 1)) + 'm';
|
|
9
|
+
}
|
|
10
|
+
if (Math.abs(n) >= 1_000) {
|
|
11
|
+
const scaled = n / 1_000;
|
|
12
|
+
return (Number.isInteger(scaled) ? String(scaled) : scaled.toFixed(Math.abs(n) >= 100_000 ? 0 : 1)) + 'k';
|
|
13
|
+
}
|
|
14
|
+
return String(Math.round(n));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ctxLabel(usage) {
|
|
18
|
+
const active = Number(usage?.current_context?.input_tokens || 0);
|
|
19
|
+
const window = Number(usage?.current_context?.window_tokens || 0);
|
|
20
|
+
if (!(window > 0)) return active > 0 ? `ctx ${count(active)} / unknown` : 'ctx unknown';
|
|
21
|
+
return `ctx ${Math.min(999, Math.round((active / window) * 100))}%`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function renderCliFooter(value = {}) {
|
|
25
|
+
const usage = value.usage || value.usageSnapshot || {};
|
|
26
|
+
const cumulative = usage.cumulative || {};
|
|
27
|
+
const model = String(value.model || value.modelLabel || 'model');
|
|
28
|
+
const tier = String(value.tier || '').trim();
|
|
29
|
+
const elapsedMs = Number(value.elapsedMs);
|
|
30
|
+
const parts = [
|
|
31
|
+
model,
|
|
32
|
+
ctxLabel(usage),
|
|
33
|
+
`↑${count(cumulative.input_tokens)} ↓${count(cumulative.output_tokens)}`,
|
|
34
|
+
];
|
|
35
|
+
if (Number(cumulative.cached_input_tokens) > 0) parts.push(`cache ${count(cumulative.cached_input_tokens)}`);
|
|
36
|
+
if (tier && tier !== 'default') parts.push(tier);
|
|
37
|
+
if (Number.isFinite(elapsedMs) && elapsedMs >= 0) parts.push(elapsedMs < 60_000 ? `${(elapsedMs / 1000).toFixed(1)}s` : `${Math.floor(elapsedMs / 60_000)}m${Math.floor((elapsedMs % 60_000) / 1000)}s`);
|
|
38
|
+
return pc.dim(' ' + parts.join(' · '));
|
|
39
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { isCancel, select } from '@clack/prompts';
|
|
3
|
+
import pkg from '../../../package.json' with { type: 'json' };
|
|
4
|
+
|
|
5
|
+
const HELP_TOPICS = Object.freeze([
|
|
6
|
+
{
|
|
7
|
+
id: 'start',
|
|
8
|
+
label: 'Start / resume',
|
|
9
|
+
summary: 'Enter Agent Sam, resume work, inspect identity, and choose a model.',
|
|
10
|
+
rows: [
|
|
11
|
+
['agentsam', 'Enter the interactive Agent Sam experience'],
|
|
12
|
+
['agentsam resume [session]', 'Resume a saved Agent Sam session'],
|
|
13
|
+
['agentsam whoami', 'Show authenticated account and credential status'],
|
|
14
|
+
['agentsam models', 'Probe account-visible hosted/local models'],
|
|
15
|
+
['agentsam providers', 'Configure and verify machine provider credentials'],
|
|
16
|
+
['agentsam env init <provider>', 'Low-level provider profile compatibility command'],
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'work',
|
|
21
|
+
label: 'Build / inspect',
|
|
22
|
+
summary: 'Repository intelligence and normal project work.',
|
|
23
|
+
rows: [
|
|
24
|
+
['agentsam inspect', 'Bounded repository index / authority view'],
|
|
25
|
+
['agentsam index', 'Incremental AST and optional embeddings'],
|
|
26
|
+
['agentsam search "query"', 'Search indexed code/text'],
|
|
27
|
+
['agentsam repo snapshot', 'Git composition/churn snapshot'],
|
|
28
|
+
['agentsam security', 'Dependency and trust-boundary scan'],
|
|
29
|
+
['agentsam merkle', 'Integrity snapshots and comparisons'],
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'runtime',
|
|
34
|
+
label: 'Runtime / terminal',
|
|
35
|
+
summary: 'Local, remote, sandbox, and deployment controls.',
|
|
36
|
+
rows: [
|
|
37
|
+
['agentsam status', 'Project, Git, DB, API, and PTY status'],
|
|
38
|
+
['agentsam start-local', 'Start the local PTY service'],
|
|
39
|
+
['agentsam connections', 'Inspect available execution connections'],
|
|
40
|
+
['agentsam tunnel', 'Expose local PTY only when remote access is wanted'],
|
|
41
|
+
['agentsam deploy', 'Graduate intentionally to cloud infrastructure'],
|
|
42
|
+
['agentsam cloudflare', 'Wrangler and Cloudflare runtime diagnostics'],
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'inside',
|
|
47
|
+
label: 'Inside Agent Sam',
|
|
48
|
+
summary: 'Commands available while the interactive session is running.',
|
|
49
|
+
rows: [
|
|
50
|
+
['/', 'Open the keyboard command picker'],
|
|
51
|
+
['/model', 'Choose model, reasoning, and processing tier'],
|
|
52
|
+
['/providers', 'Configure and verify machine provider credentials'],
|
|
53
|
+
['/context', 'Show live context economics'],
|
|
54
|
+
['/usage', 'Show token/cost/session receipt'],
|
|
55
|
+
['/settings', 'Change runtime, terminal, and model policy'],
|
|
56
|
+
['/status', 'Show project/runtime health'],
|
|
57
|
+
['/help [topic]', 'Show in-session help'],
|
|
58
|
+
['/exit', 'Return to the host shell'],
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'create',
|
|
63
|
+
label: 'Create / extend',
|
|
64
|
+
summary: 'Scaffold and add reusable capabilities.',
|
|
65
|
+
rows: [
|
|
66
|
+
['agentsam create <name> --preset <preset>', 'Create a new AgentSam project'],
|
|
67
|
+
['agentsam add <capability>', 'Add a supported capability'],
|
|
68
|
+
['agentsam capabilities [id]', 'Inspect capability contracts'],
|
|
69
|
+
['agentsam skills [id]', 'Inspect packaged skills'],
|
|
70
|
+
['agentsam identity init', 'Add reusable identity surfaces'],
|
|
71
|
+
['agentsam dockerize', 'Build supported container targets'],
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
const TOPIC_ALIASES = new Map([
|
|
77
|
+
['models', 'start'], ['model', 'start'], ['provider', 'start'], ['providers', 'start'], ['resume', 'start'],
|
|
78
|
+
['repo', 'work'], ['repository', 'work'], ['inspect', 'work'], ['index', 'work'], ['security', 'work'],
|
|
79
|
+
['terminal', 'runtime'], ['connections', 'runtime'], ['remote', 'runtime'], ['sandbox', 'runtime'], ['deploy', 'runtime'],
|
|
80
|
+
['slash', 'inside'], ['commands', 'inside'], ['session', 'inside'], ['usage', 'inside'], ['context', 'inside'],
|
|
81
|
+
['create', 'create'], ['scaffold', 'create'], ['capabilities', 'create'], ['skills', 'create'],
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
function clean(value) {
|
|
85
|
+
return value == null ? '' : String(value).trim().toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function padRows(rows) {
|
|
89
|
+
const width = Math.min(42, Math.max(...rows.map(([command]) => command.length), 0));
|
|
90
|
+
return rows.map(([command, description]) => ' ' + pc.cyan(command.padEnd(width)) + ' ' + pc.dim(description));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function resolveHelpTopic(value) {
|
|
94
|
+
const query = clean(value);
|
|
95
|
+
if (!query) return null;
|
|
96
|
+
const direct = HELP_TOPICS.find((topic) => topic.id === query || clean(topic.label) === query);
|
|
97
|
+
if (direct) return direct;
|
|
98
|
+
const alias = TOPIC_ALIASES.get(query);
|
|
99
|
+
if (alias) return HELP_TOPICS.find((topic) => topic.id === alias) || null;
|
|
100
|
+
return HELP_TOPICS.find((topic) =>
|
|
101
|
+
topic.rows.some(([command, description]) => clean(command).includes(query) || clean(description).includes(query))) || null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function renderHelpOverview(version, options = {}) {
|
|
105
|
+
const lines = [
|
|
106
|
+
'',
|
|
107
|
+
' ' + pc.bold('Agent Sam') + ' ' + pc.dim('v' + version),
|
|
108
|
+
' ' + pc.dim('Type normally to work with Agent Sam. Use help only when you need the map.'),
|
|
109
|
+
'',
|
|
110
|
+
' ' + pc.bold('Start'),
|
|
111
|
+
' ' + pc.cyan('agentsam') + ' ' + pc.dim('enter the interactive experience'),
|
|
112
|
+
' ' + pc.cyan('agentsam resume') + ' ' + pc.dim('continue saved work'),
|
|
113
|
+
' ' + pc.cyan('agentsam help <topic>') + ' ' + pc.dim('focused help'),
|
|
114
|
+
'',
|
|
115
|
+
' ' + pc.bold('Common'),
|
|
116
|
+
' ' + pc.cyan('agentsam inspect') + ' ' + pc.dim('understand this repository'),
|
|
117
|
+
' ' + pc.cyan('agentsam models') + ' ' + pc.dim('see account-visible models'),
|
|
118
|
+
' ' + pc.cyan('agentsam status') + ' ' + pc.dim('check project/runtime health'),
|
|
119
|
+
' ' + pc.cyan('agentsam security') + ' ' + pc.dim('scan dependency + trust boundaries'),
|
|
120
|
+
' ' + pc.cyan('agentsam deploy') + ' ' + pc.dim('graduate intentionally'),
|
|
121
|
+
'',
|
|
122
|
+
' ' + pc.dim('Inside Agent Sam: press / for the command picker. Ask the selected model for natural-language help at any time.'),
|
|
123
|
+
];
|
|
124
|
+
if (options.showTopics !== false) {
|
|
125
|
+
lines.push('', ' ' + pc.dim('Topics: ' + HELP_TOPICS.map((topic) => topic.id).join(' · ') + ' · all'));
|
|
126
|
+
}
|
|
127
|
+
lines.push('');
|
|
128
|
+
return lines.join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function renderHelpTopic(topic, version) {
|
|
132
|
+
if (!topic) return renderHelpOverview(version);
|
|
133
|
+
return [
|
|
134
|
+
'',
|
|
135
|
+
' ' + pc.bold('Agent Sam') + ' ' + pc.dim('v' + version) + ' ' + pc.dim('·') + ' ' + pc.bold(topic.label),
|
|
136
|
+
' ' + pc.dim(topic.summary),
|
|
137
|
+
'',
|
|
138
|
+
...padRows(topic.rows),
|
|
139
|
+
'',
|
|
140
|
+
' ' + pc.dim('Tip: run agentsam help for the map, or agentsam to return to the interactive experience.'),
|
|
141
|
+
'',
|
|
142
|
+
].join('\n');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function renderAllHelp(version) {
|
|
146
|
+
const lines = [renderHelpOverview(version, { showTopics: false })];
|
|
147
|
+
for (const topic of HELP_TOPICS) lines.push(renderHelpTopic(topic, version));
|
|
148
|
+
return lines.join('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function runHelp(argv = [], options = {}) {
|
|
152
|
+
const version = String(options.version || pkg.version || 'unknown');
|
|
153
|
+
const write = options.write || ((value) => process.stdout.write(value));
|
|
154
|
+
const args = argv.filter((arg) => arg !== '--interactive');
|
|
155
|
+
|
|
156
|
+
if (args.includes('--all')) {
|
|
157
|
+
write(renderAllHelp(version));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const topicArg = args.find((arg) => !arg.startsWith('-'));
|
|
162
|
+
if (topicArg) {
|
|
163
|
+
const topic = resolveHelpTopic(topicArg);
|
|
164
|
+
if (!topic) {
|
|
165
|
+
write(renderHelpOverview(version));
|
|
166
|
+
write(' ' + pc.yellow('No exact help topic matched') + ' ' + topicArg + '\n\n');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
write(renderHelpTopic(topic, version));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const interactive = options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
174
|
+
if (!interactive) {
|
|
175
|
+
write(renderHelpOverview(version));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const choice = await select({
|
|
180
|
+
message: 'Agent Sam help',
|
|
181
|
+
options: [
|
|
182
|
+
...HELP_TOPICS.map((topic) => ({ value: topic.id, label: topic.label, hint: topic.summary })),
|
|
183
|
+
{ value: 'all', label: 'All commands', hint: 'Print the complete deterministic help map' },
|
|
184
|
+
{ value: 'exit', label: 'Back', hint: 'Return without printing more help' },
|
|
185
|
+
],
|
|
186
|
+
});
|
|
187
|
+
if (isCancel(choice) || choice === 'exit') return;
|
|
188
|
+
if (choice === 'all') write(renderAllHelp(version));
|
|
189
|
+
else write(renderHelpTopic(resolveHelpTopic(choice), version));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function listHelpTopics() {
|
|
193
|
+
return HELP_TOPICS.map((topic) => ({ id: topic.id, label: topic.label, summary: topic.summary }));
|
|
194
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
export function renderPlanUpdate(payload = {}) {
|
|
4
|
+
const items = Array.isArray(payload.items) ? payload.items : Array.isArray(payload.todos) ? payload.todos : [];
|
|
5
|
+
if (!items.length) return '';
|
|
6
|
+
const lines = ['', ' ' + pc.bold(payload.title || 'Plan')];
|
|
7
|
+
for (const item of items.slice(0, 20)) {
|
|
8
|
+
const status = String(item.status || 'open');
|
|
9
|
+
const icon = status === 'complete' || status === 'completed' || status === 'done'
|
|
10
|
+
? pc.green('✓')
|
|
11
|
+
: status === 'running' || status === 'active'
|
|
12
|
+
? pc.cyan('●')
|
|
13
|
+
: status === 'blocked'
|
|
14
|
+
? pc.yellow('◆')
|
|
15
|
+
: pc.dim('○');
|
|
16
|
+
lines.push(` ${icon} ${item.title || item.label || item.id || 'task'}`);
|
|
17
|
+
}
|
|
18
|
+
lines.push('');
|
|
19
|
+
return lines.join('\n');
|
|
20
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { renderPlanUpdate } from './plan.js';
|
|
2
|
+
import { renderWaitingInput } from './waiting.js';
|
|
3
|
+
import { renderCompactionReceipt } from './compaction.js';
|
|
4
|
+
|
|
5
|
+
export const RUNTIME_EVENT_ENVELOPE_SCHEMA = 'agentsam-runtime-event-v1';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transport-neutral event contract shared by standalone and future platform-connected runs.
|
|
9
|
+
* A platform SSE/WebSocket producer only needs to emit this normalized envelope; the SDK does
|
|
10
|
+
* not hardcode or invent the platform endpoint that will carry it.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeRuntimeEventEnvelope(value = {}) {
|
|
13
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
14
|
+
throw new TypeError('runtime event envelope must be an object');
|
|
15
|
+
}
|
|
16
|
+
const type = String(value.type || '').trim();
|
|
17
|
+
if (!type) throw new TypeError('runtime event envelope type is required');
|
|
18
|
+
const sequence = Number(value.sequence);
|
|
19
|
+
const envelope = {
|
|
20
|
+
schema: RUNTIME_EVENT_ENVELOPE_SCHEMA,
|
|
21
|
+
schema_version: Number.isInteger(Number(value.schema_version)) ? Number(value.schema_version) : 1,
|
|
22
|
+
type,
|
|
23
|
+
timestamp: String(value.timestamp || new Date().toISOString()),
|
|
24
|
+
...(value.run_id ? { run_id: String(value.run_id) } : {}),
|
|
25
|
+
...(Number.isInteger(sequence) && sequence >= 0 ? { sequence } : {}),
|
|
26
|
+
payload: value.payload && typeof value.payload === 'object' && !Array.isArray(value.payload)
|
|
27
|
+
? value.payload
|
|
28
|
+
: value.payload == null ? {} : { value: value.payload },
|
|
29
|
+
};
|
|
30
|
+
return Object.freeze(envelope);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function line(write, text) {
|
|
34
|
+
if (text) write(text.endsWith('\n') ? text : text + '\n');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createCliRuntimePresenter(options = {}) {
|
|
38
|
+
const activity = options.activity;
|
|
39
|
+
const write = options.write || process.stdout.write.bind(process.stdout);
|
|
40
|
+
const state = options.state || {};
|
|
41
|
+
|
|
42
|
+
function handle(event = {}) {
|
|
43
|
+
const envelope = normalizeRuntimeEventEnvelope(event);
|
|
44
|
+
const { type, payload } = envelope;
|
|
45
|
+
|
|
46
|
+
if (type === 'usage.snapshot') {
|
|
47
|
+
state.usageSnapshot = payload;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (type === 'cost.snapshot') {
|
|
51
|
+
state.costSnapshot = payload;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (type === 'model.started') {
|
|
55
|
+
activity?.update(`Thinking · ${payload.model || payload.provider || 'model'}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (type === 'tool.search') {
|
|
59
|
+
activity?.update('Finding relevant tools');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (type === 'tool.started') {
|
|
63
|
+
activity?.update(`Using ${payload.capability_id || payload.tool || 'tool'}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (type === 'tool.completed') {
|
|
67
|
+
activity?.update(`Tool complete · ${payload.capability_id || payload.tool || 'tool'}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (type === 'tool.failed') {
|
|
71
|
+
activity?.update(`Tool failed · ${payload.capability_id || payload.tool || 'tool'}`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (type === 'context.compaction.started') {
|
|
75
|
+
state.compactionStartedAt = Date.now();
|
|
76
|
+
activity?.update('Compacting context');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (type === 'context.compaction.completed') {
|
|
80
|
+
const enriched = {
|
|
81
|
+
...payload,
|
|
82
|
+
duration_ms: payload.duration_ms ?? (state.compactionStartedAt ? Date.now() - state.compactionStartedAt : undefined),
|
|
83
|
+
};
|
|
84
|
+
state.lastCompaction = enriched;
|
|
85
|
+
activity?.update('Context ready');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (type === 'plan.updated') {
|
|
89
|
+
activity?.clear();
|
|
90
|
+
line(write, renderPlanUpdate(payload));
|
|
91
|
+
activity?.start('Working');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (type === 'runtime.waiting_input') {
|
|
95
|
+
activity?.clear();
|
|
96
|
+
line(write, renderWaitingInput(payload));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (type === 'run.failed' || type === 'error.observed') {
|
|
100
|
+
activity?.update('Handling error');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function compactionReceipt() {
|
|
105
|
+
if (!state.lastCompaction) return '';
|
|
106
|
+
return renderCompactionReceipt(state.lastCompaction);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return Object.freeze({ handle, compactionReceipt, state });
|
|
110
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
|
|
3
|
+
export function renderWaitingInput(payload = {}) {
|
|
4
|
+
const reason = String(payload.reason || payload.awaiting_input_reason || 'input required');
|
|
5
|
+
const url = String(payload.url || payload.auth_url || '').trim();
|
|
6
|
+
const code = String(payload.code || payload.user_code || '').trim();
|
|
7
|
+
const lines = [
|
|
8
|
+
'',
|
|
9
|
+
` ${pc.yellow('◇')} ${pc.bold('Waiting for you')} ${pc.dim('· ' + reason)}`,
|
|
10
|
+
];
|
|
11
|
+
if (url) lines.push(` Open ${pc.cyan(url)}`);
|
|
12
|
+
if (code) lines.push(` Code ${pc.bold(code)}`);
|
|
13
|
+
lines.push(` ${pc.dim(payload.message || 'Complete the requested step; Agent Sam will continue when the runtime is ready.')}`);
|
|
14
|
+
lines.push('');
|
|
15
|
+
return lines.join('\n');
|
|
16
|
+
}
|
package/src/ui/merkle/render.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { comparePaths } from '
|
|
1
|
+
import { comparePaths } from '../../../packages/agentsam-repository/src/merkle/hash.js';
|
|
2
2
|
|
|
3
3
|
export const safeText = (value) => String(value ?? '').replace(/[\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/g,
|
|
4
4
|
(char) => '\\u' + char.charCodeAt(0).toString(16).padStart(4, '0'));
|
|
@@ -3,7 +3,15 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import test from 'node:test';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
accountSessionPath,
|
|
8
|
+
clearAccountSession,
|
|
9
|
+
readAccountSession,
|
|
10
|
+
resolveAccountApiKey,
|
|
11
|
+
resolveAccountAuth,
|
|
12
|
+
resolveBrowserSessionCredential,
|
|
13
|
+
saveAccountSession,
|
|
14
|
+
} from '../src/lib/account-session.js';
|
|
7
15
|
|
|
8
16
|
function tempHome(t) {
|
|
9
17
|
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agentsam-account-'));
|
|
@@ -11,26 +19,55 @@ function tempHome(t) {
|
|
|
11
19
|
return home;
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
test('browser
|
|
22
|
+
test('opaque browser login persists in machine-local storage and resolves independently from API keys', t => {
|
|
15
23
|
const home = tempHome(t);
|
|
16
|
-
const saved = saveAccountSession({
|
|
24
|
+
const saved = saveAccountSession({
|
|
25
|
+
access_token: 'browser_session_machine_test',
|
|
26
|
+
user_id: 'au_test',
|
|
27
|
+
account_id: 'acct_test',
|
|
28
|
+
email: 'dev@example.test',
|
|
29
|
+
}, { home });
|
|
17
30
|
assert.equal(saved.user_id, 'au_test');
|
|
31
|
+
|
|
18
32
|
const filename = accountSessionPath({ home });
|
|
19
33
|
assert.equal(fs.existsSync(filename), true);
|
|
20
34
|
if (process.platform !== 'win32') assert.equal(fs.statSync(filename).mode & 0o077, 0);
|
|
21
35
|
|
|
22
36
|
const loaded = readAccountSession({ home });
|
|
23
|
-
assert.equal(loaded.
|
|
24
|
-
const
|
|
25
|
-
assert.equal(
|
|
26
|
-
assert.equal(
|
|
37
|
+
assert.equal(loaded.access_token, 'browser_session_machine_test');
|
|
38
|
+
const browser = resolveBrowserSessionCredential({ env: {}, home });
|
|
39
|
+
assert.equal(browser.source, 'agentsam_browser_oauth');
|
|
40
|
+
assert.equal(browser.kind, 'browser_oauth');
|
|
41
|
+
assert.equal(browser.value, 'browser_session_machine_test');
|
|
42
|
+
assert.equal(resolveAccountApiKey({ env: {}, home }).value, '');
|
|
43
|
+
assert.equal(resolveAccountAuth({ env: {}, home }).value, 'browser_session_machine_test');
|
|
44
|
+
|
|
27
45
|
assert.equal(clearAccountSession({ home }), true);
|
|
28
46
|
assert.equal(readAccountSession({ home }), null);
|
|
29
47
|
});
|
|
30
48
|
|
|
31
|
-
test('explicit/environment
|
|
49
|
+
test('explicit/environment aak_ API key outranks browser login while legacy SDK env names are ignored', t => {
|
|
50
|
+
const home = tempHome(t);
|
|
51
|
+
saveAccountSession({ access_token: 'browser_session_disk' }, { home });
|
|
52
|
+
|
|
53
|
+
assert.equal(
|
|
54
|
+
resolveAccountAuth({ env: { AGENTSAM_API_KEY: 'aak_env_test' }, home }).value,
|
|
55
|
+
'aak_env_test',
|
|
56
|
+
);
|
|
57
|
+
assert.equal(
|
|
58
|
+
resolveAccountAuth({ env: { AGENTSAM_API_KEY: 'aak_env_test' }, explicit: 'aak_explicit_test', home }).value,
|
|
59
|
+
'aak_explicit_test',
|
|
60
|
+
);
|
|
61
|
+
assert.equal(
|
|
62
|
+
resolveAccountAuth({ env: { AGENTSAM_SDK_KEY: 'legacy_ignored' }, home }).value,
|
|
63
|
+
'browser_session_disk',
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('browser session storage refuses reusable aak_ credentials', t => {
|
|
32
68
|
const home = tempHome(t);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
assert.throws(
|
|
70
|
+
() => saveAccountSession({ access_token: 'aak_should_not_be_browser_session' }, { home }),
|
|
71
|
+
/account_browser_oauth_session_required/,
|
|
72
|
+
);
|
|
36
73
|
});
|
|
@@ -13,6 +13,10 @@ function packageJson(...parts) {
|
|
|
13
13
|
return JSON.parse(fs.readFileSync(at(...parts, 'package.json'), 'utf8'));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function read(...parts) {
|
|
17
|
+
return fs.readFileSync(at(...parts), 'utf8');
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
test('product apps are self-contained npm workspace roots with one lockfile', () => {
|
|
17
21
|
for (const app of productApps) {
|
|
18
22
|
const base = ['apps', app];
|
|
@@ -54,3 +58,42 @@ test('Local Studio retired donor-era duplicate deployment configs', () => {
|
|
|
54
58
|
assert.equal(exists('apps', 'local-studio', 'backend', file), false, `retire ${file}`);
|
|
55
59
|
}
|
|
56
60
|
});
|
|
61
|
+
|
|
62
|
+
test('CAD creator app package is curated for third-party installation', () => {
|
|
63
|
+
const pkg = packageJson('apps', 'cad-creator');
|
|
64
|
+
const manifest = JSON.parse(read('apps', 'cad-creator', 'agentsam.app.json'));
|
|
65
|
+
|
|
66
|
+
assert.equal(pkg.name, '@inneranimalmedia/agentsam-sdk-cad-creator');
|
|
67
|
+
assert.notEqual(pkg.private, true);
|
|
68
|
+
assert.equal(pkg.bin['agentsam-cad-creator'], 'bin/agentsam-cad-creator.mjs');
|
|
69
|
+
assert.equal(pkg.publishConfig.access, 'public');
|
|
70
|
+
assert.ok(pkg.files.includes('frontend/dist/'));
|
|
71
|
+
assert.ok(pkg.files.includes('backend/dist/'));
|
|
72
|
+
assert.ok(pkg.files.includes('backend/worker/'));
|
|
73
|
+
assert.equal(pkg.files.some((entry) => entry.startsWith('reference')), false);
|
|
74
|
+
assert.equal(pkg.files.some((entry) => entry.includes('.wrangler/state')), false);
|
|
75
|
+
|
|
76
|
+
assert.equal(manifest.schema, 'agentsam.app.v1');
|
|
77
|
+
assert.equal(manifest.id, 'cad-creator');
|
|
78
|
+
assert.equal(manifest.package, pkg.name);
|
|
79
|
+
assert.equal(manifest.runtime.local_preview, 'ready');
|
|
80
|
+
assert.equal(manifest.runtime.source_scaffold, 'ready');
|
|
81
|
+
assert.equal(manifest.runtime.cloudflare, 'scaffold');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('CAD creator keeps the robotics runtime lazy and the perception key server-side', () => {
|
|
85
|
+
const app = read('apps', 'cad-creator', 'frontend', 'src', 'App.tsx');
|
|
86
|
+
const lazy = read('apps', 'cad-creator', 'frontend', 'src', 'workspaces', 'robotics', 'lazy.tsx');
|
|
87
|
+
const httpProvider = read('apps', 'cad-creator', 'frontend', 'src', 'lib', 'robotics', 'perception', 'http-provider.ts');
|
|
88
|
+
const perception = read('apps', 'cad-creator', 'backend', 'src', 'robotics', 'perception.ts');
|
|
89
|
+
const worker = read('apps', 'cad-creator', 'backend', 'worker', 'index.js');
|
|
90
|
+
|
|
91
|
+
assert.doesNotMatch(app, /from ['\"]\.\/workspaces\/robotics\/RoboticsWorkspace['\"]/);
|
|
92
|
+
assert.doesNotMatch(app, /MujocoSimulationProvider/);
|
|
93
|
+
assert.match(app, /LazyRoboticsWorkspace/);
|
|
94
|
+
assert.match(lazy, /import\(['\"]\.\/RoboticsWorkspace['\"]\)/);
|
|
95
|
+
assert.match(httpProvider, /\/api\/robotics\/perception\/detect/);
|
|
96
|
+
assert.doesNotMatch(httpProvider, /GEMINI_API_KEY|GOOGLE_API_KEY/);
|
|
97
|
+
assert.match(perception, /env\.GEMINI_API_KEY/);
|
|
98
|
+
assert.match(worker, /\/api\/robotics\/perception\/detect/);
|
|
99
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { runtimeOptions } from '../../src/commands/preferences.js';
|
|
4
|
+
|
|
5
|
+
test('standalone users only see local execution', () => {
|
|
6
|
+
assert.deepEqual(runtimeOptions({ accountConnected: false }).map((row) => row.value), ['local']);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('IAM-connected users can choose enrolled remote and sandbox lanes', () => {
|
|
10
|
+
assert.deepEqual(runtimeOptions({ accountConnected: true }).map((row) => row.value), ['local', 'remote', 'sandbox']);
|
|
11
|
+
});
|