@inneranimalmedia/agentsam-sdk 2.5.0 → 2.6.1
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/PLATFORM_RUNTIME_EVENTS.md +48 -0
- package/docs/RELEASES.md +16 -7
- 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 +45 -12
- 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/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/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/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/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/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 +353 -0
- package/src/capabilities/repository-snapshot.js +3 -3
- package/src/cli.js +118 -31
- 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 +129 -0
- package/src/commands/context.js +1 -1
- package/src/commands/db.js +20 -3
- package/src/commands/deploy.js +39 -3
- package/src/commands/env.js +90 -0
- package/src/commands/eval.js +63 -0
- package/src/commands/interactive.js +2 -5
- 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 +149 -46
- package/src/commands/ollama.js +26 -0
- package/src/commands/preferences.js +130 -61
- package/src/commands/resume.js +67 -0
- package/src/commands/security.js +5 -3
- package/src/commands/shell.js +568 -119
- 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/contract.js +236 -0
- package/src/errors/diagnostic.js +160 -0
- package/src/errors/index.js +23 -0
- package/src/eval/context.js +191 -0
- package/src/eval/index.js +1 -0
- package/src/index.js +68 -2
- 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 +98 -0
- package/src/lib/agent-instructions.js +73 -0
- package/src/lib/auth.js +4 -0
- package/src/lib/cli-preferences.js +55 -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/deploy-receipt/index.js +2 -2
- package/src/lib/detect-context.js +2 -2
- package/src/lib/execution-approvals.js +59 -0
- package/src/lib/knowledge-docker.js +6 -3
- package/src/lib/local-sessions.js +148 -0
- package/src/lib/local-status.js +1 -1
- package/src/lib/project-config.js +1 -1
- package/src/lib/provider-credentials.js +183 -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 +23 -16
- 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/catalog.js +135 -0
- package/src/models/discovery.js +292 -0
- package/src/models/index.js +7 -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 +10 -0
- package/src/providers/ollama-chat.js +148 -0
- package/src/providers/openai-responses.js +426 -0
- 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/process.js +35 -9
- package/src/security/trust-boundary.js +2 -2
- package/src/telemetry/contracts.js +203 -0
- package/src/telemetry/events.js +51 -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/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 +192 -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 +36 -0
- package/test/cli/preferences-runtime.test.mjs +11 -0
- package/test/cli/runtime-ui.test.mjs +74 -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 +115 -0
- package/test/eval-context.test.mjs +37 -0
- package/test/execution-approvals.test.mjs +27 -0
- 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 +48 -0
- package/test/local-studio-deploy.test.mjs +83 -0
- package/test/model-catalog.test.mjs +43 -0
- package/test/models.test.mjs +127 -16
- package/test/npm10-lock.test.mjs +29 -0
- package/test/ollama.test.mjs +21 -0
- package/test/openai-responses.test.mjs +95 -0
- package/test/portable-context.test.mjs +1 -1
- package/test/provider-credentials.test.mjs +96 -0
- package/test/rehydrate.test.mjs +25 -0
- package/test/release-hygiene.test.mjs +13 -5
- package/test/responses-runner.test.mjs +150 -0
- package/test/shell.test.mjs +92 -23
- package/test/smoke.mjs +4 -1
- package/test/telemetry.test.mjs +79 -0
- package/test/terminal/local-pty.mock.test.mjs +151 -0
- package/test/tools-search.test.mjs +14 -1
- package/test/whoami-resume.test.mjs +56 -0
- /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
package/src/commands/shell.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { confirm, isCancel, select } from '@clack/prompts';
|
|
6
7
|
import { SLASH_COMMANDS, SHELL_PHASES } from '../lib/slash-commands.js';
|
|
7
8
|
import { runContext } from './context.js';
|
|
8
9
|
import { runDb } from './db.js';
|
|
@@ -10,11 +11,28 @@ import { runDeploy } from './deploy.js';
|
|
|
10
11
|
import { runStatus } from './status.js';
|
|
11
12
|
import { runModels } from './models.js';
|
|
12
13
|
import { configureCliPreferences } from './preferences.js';
|
|
13
|
-
import {
|
|
14
|
+
import { runCloudflare } from './cloudflare.js';
|
|
15
|
+
import { probeOllamaModel, resolveOllamaConfig } from './ollama.js';
|
|
16
|
+
import { createInlineActivity } from '../ui/cli/activity.js';
|
|
17
|
+
import { createCliRuntimePresenter } from '../ui/cli/runtime-events.js';
|
|
18
|
+
import { renderCliFooter } from '../ui/cli/footer.js';
|
|
19
|
+
import { diagnosticFromError, renderDiagnosticError } from '../errors/index.js';
|
|
20
|
+
import { getModelRecord } from '../models/index.js';
|
|
21
|
+
import { discoverProviderModels } from '../models/discovery.js';
|
|
22
|
+
import { readCliPreferences, updateCliPreferences } from '../lib/cli-preferences.js';
|
|
23
|
+
import { buildContextEconomicsReport, renderContextEconomics } from './context-economics.js';
|
|
24
|
+
import { createProviderAdapter } from '../providers/index.js';
|
|
25
|
+
import { createCapabilityAdapter, runResponsesAgent } from '../agent/index.js';
|
|
26
|
+
import { resolveProviderCredential } from '../lib/provider-credentials.js';
|
|
27
|
+
import { createLocalSession, saveLocalSession, sessionTitleFromInput, localSessionElapsedMs } from '../lib/local-sessions.js';
|
|
28
|
+
import { grantExecutionApproval, isExecutionApproved, toolApprovalKey } from '../lib/execution-approvals.js';
|
|
29
|
+
import { runWhoami } from './whoami.js';
|
|
30
|
+
import { runLogin, runLogout } from './account-auth.js';
|
|
31
|
+
import { runHelp } from '../ui/cli/help.js';
|
|
32
|
+
import { readAccountSession } from '../lib/account-session.js';
|
|
33
|
+
import { startRuntimeRun, finishRuntimeRun, recordRuntimeCompaction } from '../local/runtime-store.js';
|
|
14
34
|
|
|
15
|
-
function writeLine(write, value = '') {
|
|
16
|
-
write(`${value}\n`);
|
|
17
|
-
}
|
|
35
|
+
function writeLine(write, value = '') { write(`${value}\n`); }
|
|
18
36
|
|
|
19
37
|
export function compactCwd(value, home = process.env.HOME || process.env.USERPROFILE || '') {
|
|
20
38
|
const cwd = path.resolve(value);
|
|
@@ -41,34 +59,22 @@ export function tokenizeShellLine(input = '') {
|
|
|
41
59
|
let token = '';
|
|
42
60
|
let quote = '';
|
|
43
61
|
let started = false;
|
|
44
|
-
|
|
45
62
|
const flush = () => {
|
|
46
63
|
if (!started) return;
|
|
47
64
|
tokens.push(token);
|
|
48
65
|
token = '';
|
|
49
66
|
started = false;
|
|
50
67
|
};
|
|
51
|
-
|
|
52
68
|
for (let i = 0; i < source.length; i += 1) {
|
|
53
69
|
const ch = source[i];
|
|
54
70
|
if (quote) {
|
|
55
|
-
if (ch === quote)
|
|
56
|
-
|
|
57
|
-
} else {
|
|
58
|
-
token += ch;
|
|
59
|
-
}
|
|
60
|
-
started = true;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (ch === '"' || ch === "'") {
|
|
64
|
-
quote = ch;
|
|
71
|
+
if (ch === quote) quote = '';
|
|
72
|
+
else token += ch;
|
|
65
73
|
started = true;
|
|
66
74
|
continue;
|
|
67
75
|
}
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
76
|
+
if (ch === '"' || ch === "'") { quote = ch; started = true; continue; }
|
|
77
|
+
if (/\s/.test(ch)) { flush(); continue; }
|
|
72
78
|
token += ch;
|
|
73
79
|
started = true;
|
|
74
80
|
}
|
|
@@ -78,21 +84,20 @@ export function tokenizeShellLine(input = '') {
|
|
|
78
84
|
|
|
79
85
|
export function renderShellCatalog() {
|
|
80
86
|
const next = SHELL_PHASES.find((phase) => phase.status === 'next' || phase.status === 'current');
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
`;
|
|
87
|
+
return [
|
|
88
|
+
'',
|
|
89
|
+
' Agent Sam',
|
|
90
|
+
' Type normally to work with the selected model.',
|
|
91
|
+
'',
|
|
92
|
+
' / command picker',
|
|
93
|
+
' /model model · reasoning · processing',
|
|
94
|
+
' /usage tokens · cost · resume receipt',
|
|
95
|
+
' /help focused help',
|
|
96
|
+
' /exit return to host shell',
|
|
97
|
+
'',
|
|
98
|
+
` ${next?.label ?? 'interactive runtime'} · ${SLASH_COMMANDS.length} commands available through /`,
|
|
99
|
+
'',
|
|
100
|
+
].join('\n');
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
function parseDeployOptions(args, cwd) {
|
|
@@ -106,6 +111,105 @@ function parseDeployOptions(args, cwd) {
|
|
|
106
111
|
return opts;
|
|
107
112
|
}
|
|
108
113
|
|
|
114
|
+
function spawnGit(cwd, args) {
|
|
115
|
+
const result = spawnSync('git', args, { cwd, stdio: 'inherit', shell: false });
|
|
116
|
+
if (result.error) throw result.error;
|
|
117
|
+
if (result.status !== 0) throw new Error(`git exited ${result.status}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function selectedModel(cwd) {
|
|
121
|
+
const preferences = readCliPreferences(cwd) || {};
|
|
122
|
+
const snapshot = preferences.modelSnapshot?.model_key === preferences.modelPreference
|
|
123
|
+
? preferences.modelSnapshot
|
|
124
|
+
: null;
|
|
125
|
+
const model = snapshot || getModelRecord(preferences.modelPreference);
|
|
126
|
+
if (!model) throw new Error('Select an exact provider-verified model with /model first so Agent Sam can verify supported runtime controls.');
|
|
127
|
+
return { preferences, model };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function resolveModelForTurn(cwd, state) {
|
|
131
|
+
const selected = selectedModel(cwd);
|
|
132
|
+
const { preferences } = selected;
|
|
133
|
+
let model = selected.model;
|
|
134
|
+
|
|
135
|
+
if (model.provider === 'ollama') {
|
|
136
|
+
const config = resolveOllamaConfig({}, process.env);
|
|
137
|
+
const probe = await probeOllamaModel(model.provider_model_id, config, state.providerFetchImpl || fetch);
|
|
138
|
+
if (!probe.ok) throw new Error(`ollama_model_probe_failed:${model.provider_model_id}:${probe.error || 'unknown'}`);
|
|
139
|
+
model = {
|
|
140
|
+
...model,
|
|
141
|
+
context_window: probe.context_window,
|
|
142
|
+
context_window_source: probe.context_window_source || 'unknown',
|
|
143
|
+
capabilities: { ...(model.capabilities || {}), local_runtime: true, ...(Object.fromEntries((probe.capabilities || []).map((name) => [name, true]))) },
|
|
144
|
+
};
|
|
145
|
+
updateCliPreferences(cwd, { modelPreference: model.model_key, modelSnapshot: model });
|
|
146
|
+
return { preferences: readCliPreferences(cwd) || preferences, model, credential: null, verification: 'local_runtime' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const credential = resolveProviderCredential(model.provider, { home: state.home });
|
|
150
|
+
if (!credential.configured) {
|
|
151
|
+
throw new Error(`provider_credential_unavailable:${model.provider}:${credential.error || credential.env || 'not_configured'}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const discovery = await discoverProviderModels(model.provider, credential, { fetchImpl: state.providerFetchImpl || fetch });
|
|
155
|
+
if (discovery.ok) {
|
|
156
|
+
const live = discovery.models.find((row) => row.provider_model_id === model.provider_model_id || row.model_key === model.model_key);
|
|
157
|
+
if (!live) throw new Error(`selected_model_not_available_for_credential:${model.provider}:${model.provider_model_id}`);
|
|
158
|
+
model = live;
|
|
159
|
+
updateCliPreferences(cwd, { modelPreference: live.model_key, modelSnapshot: live });
|
|
160
|
+
return { preferences: readCliPreferences(cwd) || preferences, model, credential, verification: 'provider_api' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (preferences.modelSnapshot?.availability === 'available') {
|
|
164
|
+
return { preferences, model, credential, verification: 'cached_provider_snapshot', discoveryError: discovery.error || null };
|
|
165
|
+
}
|
|
166
|
+
throw new Error(`provider_model_discovery_failed:${model.provider}:${discovery.error || 'unknown'}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function chooseReasoning(cwd, args, state) {
|
|
170
|
+
const { preferences, model } = selectedModel(cwd);
|
|
171
|
+
let effort = String(args[0] || '').trim().toLowerCase();
|
|
172
|
+
if (!effort) {
|
|
173
|
+
if (!state.interactive) {
|
|
174
|
+
writeLine(state.write, ` ${model.provider_model_id} reasoning: ${model.reasoning_efforts.join(' | ')}`);
|
|
175
|
+
writeLine(state.write, ` current: ${preferences.reasoningEffort || 'auto'}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const choice = await select({
|
|
179
|
+
message: `Reasoning level · ${model.provider_model_id}`,
|
|
180
|
+
initialValue: model.reasoning_efforts.includes(preferences.reasoningEffort) ? preferences.reasoningEffort : model.reasoning_efforts[0],
|
|
181
|
+
options: model.reasoning_efforts.map((value) => ({ value, label: value === 'xhigh' ? 'Extra high' : value === 'max' ? 'Max' : value[0].toUpperCase() + value.slice(1) })),
|
|
182
|
+
});
|
|
183
|
+
if (isCancel(choice)) return;
|
|
184
|
+
effort = choice;
|
|
185
|
+
}
|
|
186
|
+
if (!model.reasoning_efforts.includes(effort)) throw new Error(`unsupported reasoning level for ${model.provider_model_id}: ${effort}`);
|
|
187
|
+
updateCliPreferences(cwd, { reasoningEffort: effort });
|
|
188
|
+
writeLine(state.write, ` reasoning → ${effort}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function setServiceTier(cwd, tier, write) {
|
|
192
|
+
const { model } = selectedModel(cwd);
|
|
193
|
+
if (!model.service_tiers.includes(tier)) throw new Error(`${model.provider_model_id} does not declare ${tier} processing support`);
|
|
194
|
+
updateCliPreferences(cwd, { serviceTier: tier });
|
|
195
|
+
const label = tier === 'default' ? 'standard' : tier;
|
|
196
|
+
writeLine(write, ` processing → ${label}`);
|
|
197
|
+
if (tier === 'fast') writeLine(write, ' Fast is a paid latency choice; Agent Sam will account for its model-specific pricing multiplier.');
|
|
198
|
+
if (tier === 'flex') writeLine(write, ' Flex trades latency/capacity availability for lower cost; it is not Batch.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function showCommandPicker(state) {
|
|
202
|
+
if (!state.interactive) {
|
|
203
|
+
state.write(renderShellCatalog());
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const choice = await select({
|
|
207
|
+
message: 'Agent Sam commands',
|
|
208
|
+
options: SLASH_COMMANDS.map((row) => ({ value: row.cmd, label: row.cmd, hint: row.description })),
|
|
209
|
+
});
|
|
210
|
+
if (isCancel(choice)) return;
|
|
211
|
+
await dispatchShellLine(choice, state);
|
|
212
|
+
}
|
|
109
213
|
|
|
110
214
|
export async function runLocalAgent(goal, write, options = {}) {
|
|
111
215
|
if (!goal) {
|
|
@@ -115,90 +219,404 @@ export async function runLocalAgent(goal, write, options = {}) {
|
|
|
115
219
|
}
|
|
116
220
|
const base = String(process.env.AGENTSAM_LOCAL_URL || 'http://127.0.0.1:8787').replace(/\/$/, '');
|
|
117
221
|
const fetchImpl = options.fetchImpl || fetch;
|
|
118
|
-
const activity = options.activity ||
|
|
119
|
-
write,
|
|
120
|
-
phase: 'thinking',
|
|
121
|
-
interactive: options.interactive,
|
|
122
|
-
});
|
|
222
|
+
const activity = options.activity || createInlineActivity({ write, label: 'Thinking', interactive: options.interactive });
|
|
123
223
|
let response;
|
|
124
224
|
activity.start('thinking');
|
|
125
225
|
try {
|
|
126
226
|
response = await fetchImpl(`${base}/api/agentsam/message`, {
|
|
127
|
-
method: 'POST',
|
|
128
|
-
headers: { 'content-type': 'application/json' },
|
|
129
|
-
body: JSON.stringify({ message: goal }),
|
|
227
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ message: goal }),
|
|
130
228
|
});
|
|
131
229
|
} catch (error) {
|
|
132
230
|
activity.fail('unavailable');
|
|
133
231
|
throw new Error(`local Agent Sam unavailable at ${base} — run \`npm run dev\` first (${error?.message || error})`);
|
|
134
232
|
}
|
|
135
233
|
let text;
|
|
136
|
-
try {
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
activity.fail('response error');
|
|
140
|
-
throw new Error(`local Agent Sam response could not be read: ${error?.message || error}`);
|
|
141
|
-
}
|
|
142
|
-
if (!response.ok) {
|
|
143
|
-
activity.fail(`HTTP ${response.status}`);
|
|
144
|
-
throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`);
|
|
145
|
-
}
|
|
234
|
+
try { text = await response.text(); }
|
|
235
|
+
catch (error) { activity.fail('response error'); throw new Error(`local Agent Sam response could not be read: ${error?.message || error}`); }
|
|
236
|
+
if (!response.ok) { activity.fail(`HTTP ${response.status}`); throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`); }
|
|
146
237
|
activity.succeed('done');
|
|
147
|
-
try {
|
|
148
|
-
|
|
149
|
-
} catch {
|
|
150
|
-
writeLine(write, text);
|
|
151
|
-
}
|
|
238
|
+
try { writeLine(write, JSON.stringify(JSON.parse(text), null, 2)); }
|
|
239
|
+
catch { writeLine(write, text); }
|
|
152
240
|
}
|
|
153
241
|
|
|
154
242
|
async function showLocalLogs(cwd, write) {
|
|
155
243
|
const dbPath = path.join(cwd, '.agentsam', 'data', 'agentsam.sqlite');
|
|
156
|
-
if (!fs.existsSync(dbPath)) {
|
|
157
|
-
writeLine(write, ' No local Agent Sam DB found. Run `agentsam init . --yes` first.');
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
244
|
+
if (!fs.existsSync(dbPath)) { writeLine(write, ' No local Agent Sam DB found. Run `agentsam init . --yes` first.'); return; }
|
|
160
245
|
const { createLocalSqliteDatabase } = await import('../local/sqlite.js');
|
|
161
246
|
const db = await createLocalSqliteDatabase(dbPath);
|
|
162
247
|
try {
|
|
163
|
-
const calls = await db
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
writeLine(write, ' No local Agent Sam tool-call events yet.');
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
248
|
+
const calls = await db.prepare('SELECT id, session_id, tool_name, status, created_at, completed_at FROM agent_tool_calls ORDER BY created_at DESC LIMIT 20').all();
|
|
249
|
+
if (!calls.results.length) { writeLine(write, ' No local Agent Sam tool-call events yet.'); return; }
|
|
250
|
+
writeLine(write, '\n Recent Agent Sam tool calls');
|
|
251
|
+
for (const row of calls.results) writeLine(write, ` ${String(row.created_at || '').padEnd(20)} ${String(row.status || '').padEnd(10)} ${row.tool_name}`);
|
|
170
252
|
writeLine(write, '');
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
253
|
+
} finally { db.close(); }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function formatCount(value) {
|
|
257
|
+
return Math.max(0, Number(value || 0)).toLocaleString('en-US');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function formatUsd(value) {
|
|
261
|
+
const amount = Number(value || 0);
|
|
262
|
+
if (!Number.isFinite(amount)) return 'unavailable';
|
|
263
|
+
const currency = String.fromCharCode(36);
|
|
264
|
+
if (amount === 0) return currency + '0.000000';
|
|
265
|
+
return currency + (amount < 0.01 ? amount.toFixed(6) : amount.toFixed(4));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function formatElapsed(ms) {
|
|
269
|
+
const totalSeconds = Math.max(0, Math.round(Number(ms || 0) / 1000));
|
|
270
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
271
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
272
|
+
const seconds = totalSeconds % 60;
|
|
273
|
+
if (hours) return `${hours}h ${minutes}m ${seconds}s`;
|
|
274
|
+
if (minutes) return `${minutes}m ${seconds}s`;
|
|
275
|
+
return `${seconds}s`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function renderSessionReceipt(session) {
|
|
279
|
+
if (!session) return '';
|
|
280
|
+
const usage = session.cumulative_usage || {};
|
|
281
|
+
const input = Number(usage.input_tokens || 0);
|
|
282
|
+
const output = Number(usage.output_tokens || 0);
|
|
283
|
+
const cached = Number(usage.cached_input_tokens || 0);
|
|
284
|
+
const cacheWrite = Number(usage.cache_write_tokens || 0);
|
|
285
|
+
const reasoning = Number(usage.reasoning_tokens || 0);
|
|
286
|
+
const total = input + output;
|
|
287
|
+
const active = Number(session.usage_snapshot?.current_context?.input_tokens || 0);
|
|
288
|
+
const model = session.provider_model_id || session.model_key || 'model unavailable';
|
|
289
|
+
const breakdown = session.cost_breakdown_usd || {};
|
|
290
|
+
const componentTotal = ['input', 'cached_input', 'cache_write', 'output'].reduce((sum, key) => sum + Number(breakdown[key] || 0), 0);
|
|
291
|
+
const elapsed = localSessionElapsedMs(session);
|
|
292
|
+
const lines = [
|
|
293
|
+
'',
|
|
294
|
+
'Session summary',
|
|
295
|
+
`Token usage: total=${formatCount(total)} input=${formatCount(input)}${cached ? ` (+ ${formatCount(cached)} cached)` : ''}${cacheWrite ? ` (+ ${formatCount(cacheWrite)} cache write)` : ''} output=${formatCount(output)}${reasoning ? ` reasoning=${formatCount(reasoning)}` : ''}`,
|
|
296
|
+
`Spent: ${formatUsd(session.total_cost_usd)} · ${model}${session.actual_service_tier ? ` · ${session.actual_service_tier}` : ''}`,
|
|
297
|
+
`Elapsed: ${formatElapsed(elapsed)}`,
|
|
298
|
+
];
|
|
299
|
+
if (componentTotal > 0) {
|
|
300
|
+
lines.push(`Cost breakdown: input ${formatUsd(breakdown.input)} · cached ${formatUsd(breakdown.cached_input)} · cache write ${formatUsd(breakdown.cache_write)} · output ${formatUsd(breakdown.output)}`);
|
|
301
|
+
}
|
|
302
|
+
if (active) lines.push(`Active context: ${formatCount(active)} tokens`);
|
|
303
|
+
lines.push('', 'To continue this session, run:', ` agentsam resume ${session.id}`, '', 'Or run:', ' agentsam resume', '', 'and select:', ` ${session.title || 'this session'}`, '');
|
|
304
|
+
return lines.join('\n');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function persistSession(state, patch = {}) {
|
|
308
|
+
if (!state.session) return null;
|
|
309
|
+
state.session = saveLocalSession({ ...state.session, ...patch }, { home: state.home });
|
|
310
|
+
return state.session;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function recordSessionInput(state, input) {
|
|
314
|
+
const value = String(input || '').trim();
|
|
315
|
+
if (!state.session || !value) return;
|
|
316
|
+
const housekeeping = new Set(['/exit', '/quit', '/session', '/help', '/', '/menu', '/clear']);
|
|
317
|
+
if (housekeeping.has(value.toLowerCase())) return;
|
|
318
|
+
persistSession(state, {
|
|
319
|
+
status: 'active',
|
|
320
|
+
last_input: value,
|
|
321
|
+
title: sessionTitleFromInput(value),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function safeToolInput(value, depth = 0) {
|
|
326
|
+
if (depth > 3) return '[nested]';
|
|
327
|
+
if (Array.isArray(value)) return value.slice(0, 12).map((row) => safeToolInput(row, depth + 1));
|
|
328
|
+
if (!value || typeof value !== 'object') return value;
|
|
329
|
+
const output = {};
|
|
330
|
+
for (const [key, item] of Object.entries(value)) {
|
|
331
|
+
if (/token|secret|password|api[_-]?key|authorization/i.test(key)) output[key] = '[REDACTED]';
|
|
332
|
+
else output[key] = safeToolInput(item, depth + 1);
|
|
333
|
+
}
|
|
334
|
+
return output;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function approveModelRequest(preflight, state) {
|
|
338
|
+
const projected = Number(preflight.projected_max_call_cost_usd);
|
|
339
|
+
const threshold = Number(process.env.AGENTSAM_CONFIRM_CALL_COST_USD || 0);
|
|
340
|
+
if (!(Number.isFinite(projected) && projected >= 0)) return true;
|
|
341
|
+
if (!(Number.isFinite(threshold) && threshold > 0) || projected <= threshold) return true;
|
|
342
|
+
|
|
343
|
+
const approvedCeiling = Number(state.session?.approved_projected_call_cost_usd || 0);
|
|
344
|
+
if (approvedCeiling > 0 && projected <= approvedCeiling) return true;
|
|
345
|
+
if (!state.interactive) return false;
|
|
346
|
+
|
|
347
|
+
state.activity?.clear?.();
|
|
348
|
+
writeLine(state.write, '');
|
|
349
|
+
writeLine(state.write, ' ◆ Model cost approval');
|
|
350
|
+
writeLine(state.write, ` model ${preflight.model}`);
|
|
351
|
+
writeLine(state.write, ` reasoning ${preflight.reasoning_effort}`);
|
|
352
|
+
writeLine(state.write, ` processing ${preflight.service_tier}`);
|
|
353
|
+
writeLine(state.write, ` context ~${formatCount(preflight.estimated_input_tokens)} input tokens`);
|
|
354
|
+
writeLine(state.write, ` max call ${formatUsd(projected)} conservative ceiling`);
|
|
355
|
+
if (Number.isFinite(preflight.tokens_until_pricing_threshold)) writeLine(state.write, ` headroom ${formatCount(preflight.tokens_until_pricing_threshold)} tokens`);
|
|
356
|
+
const approved = await confirm({ message: 'Allow this call?', initialValue: false });
|
|
357
|
+
if (isCancel(approved) || approved !== true) return false;
|
|
358
|
+
persistSession(state, { approved_projected_call_cost_usd: Math.max(approvedCeiling, projected) });
|
|
359
|
+
state.activity?.start?.('Working');
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function approveToolExecution(request, state) {
|
|
364
|
+
const sideEffects = request?.descriptor?.side_effects || 'none';
|
|
365
|
+
if (sideEffects === 'none') return true;
|
|
366
|
+
const key = toolApprovalKey(request.capability_id, request.input);
|
|
367
|
+
if (isExecutionApproved({ cwd: state.cwd, key }, { home: state.home })) return true;
|
|
368
|
+
if (!state.interactive) return false;
|
|
369
|
+
|
|
370
|
+
const summary = JSON.stringify(safeToolInput(request.input || {}));
|
|
371
|
+
state.activity?.clear?.();
|
|
372
|
+
writeLine(state.write, '');
|
|
373
|
+
writeLine(state.write, ' Agent Sam needs execution permission');
|
|
374
|
+
writeLine(state.write, ` action ${key}`);
|
|
375
|
+
writeLine(state.write, ` target local runtime · ${compactCwd(state.cwd)}`);
|
|
376
|
+
writeLine(state.write, ` effect ${sideEffects}`);
|
|
377
|
+
if (summary && summary !== '{}') writeLine(state.write, ` input ${summary.length > 500 ? `${summary.slice(0, 497)}...` : summary}`);
|
|
378
|
+
writeLine(state.write, ' secrets remain runtime-owned and are not included in the model-visible result.');
|
|
379
|
+
|
|
380
|
+
const choice = await select({
|
|
381
|
+
message: `Allow ${key}?`,
|
|
382
|
+
options: [
|
|
383
|
+
{ value: 'once', label: 'Allow once' },
|
|
384
|
+
{ value: 'always', label: `Always allow ${key} in this project` },
|
|
385
|
+
{ value: 'deny', label: 'Deny' },
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
if (isCancel(choice) || choice === 'deny') return false;
|
|
389
|
+
if (choice === 'always') grantExecutionApproval({ cwd: state.cwd, key, label: key }, { home: state.home });
|
|
390
|
+
state.activity?.start?.(`Working · ${request.capability_id}`);
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function runInteractiveModelTurn(prompt, state) {
|
|
395
|
+
const resolved = await resolveModelForTurn(state.cwd, state);
|
|
396
|
+
const { preferences, model, credential } = resolved;
|
|
397
|
+
const provider = createProviderAdapter({
|
|
398
|
+
modelRecord: model,
|
|
399
|
+
credential,
|
|
400
|
+
endpoint: model.provider === 'ollama' ? process.env.OLLAMA_BASE_URL : undefined,
|
|
401
|
+
fetchImpl: state.providerFetchImpl,
|
|
402
|
+
});
|
|
403
|
+
const capabilityAdapter = createCapabilityAdapter();
|
|
404
|
+
|
|
405
|
+
const samePolicy = state.session
|
|
406
|
+
&& state.session.model_key === model.model_key
|
|
407
|
+
&& state.session.reasoning_effort === preferences.reasoningEffort
|
|
408
|
+
&& state.session.requested_service_tier === preferences.serviceTier;
|
|
409
|
+
const previousProviderState = samePolicy ? state.session?.provider_state : null;
|
|
410
|
+
const previousUsageSnapshot = samePolicy ? state.session?.usage_snapshot : null;
|
|
411
|
+
const accountId = readAccountSession({ home: state.home })?.account_id || null;
|
|
412
|
+
let runtimeRunId = null;
|
|
413
|
+
try {
|
|
414
|
+
runtimeRunId = await startRuntimeRun({
|
|
415
|
+
cwd: state.cwd,
|
|
416
|
+
account_id: accountId,
|
|
417
|
+
mode: 'agent',
|
|
418
|
+
model_key: model.model_key,
|
|
419
|
+
reasoning_effort: preferences.reasoningEffort,
|
|
420
|
+
service_tier: preferences.serviceTier,
|
|
421
|
+
});
|
|
422
|
+
} catch {
|
|
423
|
+
runtimeRunId = null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const activity = createInlineActivity({
|
|
427
|
+
write: state.write,
|
|
428
|
+
interactive: state.interactive,
|
|
429
|
+
label: `Thinking · ${model.provider_model_id}`,
|
|
430
|
+
});
|
|
431
|
+
state.activity = activity;
|
|
432
|
+
const presenter = createCliRuntimePresenter({ activity, write: state.write, state });
|
|
433
|
+
const startedAt = Date.now();
|
|
434
|
+
activity.start(`Thinking · ${model.provider_model_id}`);
|
|
435
|
+
|
|
436
|
+
let result;
|
|
437
|
+
try {
|
|
438
|
+
result = await runResponsesAgent({
|
|
439
|
+
provider,
|
|
440
|
+
capabilityAdapter,
|
|
441
|
+
cwd: state.cwd,
|
|
442
|
+
prompt,
|
|
443
|
+
model: model.model_key,
|
|
444
|
+
modelRecord: model,
|
|
445
|
+
reasoningEffort: preferences.reasoningEffort,
|
|
446
|
+
serviceTier: preferences.serviceTier,
|
|
447
|
+
previousProviderState,
|
|
448
|
+
previousUsageSnapshot,
|
|
449
|
+
cumulativeUsage: state.session?.cumulative_usage || null,
|
|
450
|
+
promptCacheKey: state.session?.id || undefined,
|
|
451
|
+
runId: runtimeRunId || state.session?.id || undefined,
|
|
452
|
+
beforeRequest: (preflight) => approveModelRequest(preflight, state),
|
|
453
|
+
beforeTool: (request) => approveToolExecution(request, state),
|
|
454
|
+
emit(event) {
|
|
455
|
+
presenter.handle(event);
|
|
456
|
+
if (event?.type === 'usage.snapshot') state.usageSnapshot = event.payload;
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
} catch (error) {
|
|
460
|
+
activity.fail('Failed');
|
|
461
|
+
state.activity = null;
|
|
462
|
+
if (runtimeRunId) {
|
|
463
|
+
try {
|
|
464
|
+
await finishRuntimeRun({
|
|
465
|
+
cwd: state.cwd,
|
|
466
|
+
id: runtimeRunId,
|
|
467
|
+
status: 'failed',
|
|
468
|
+
error_code: error?.code || 'interactive_error',
|
|
469
|
+
error_message: error?.message || String(error),
|
|
470
|
+
latency_ms: Date.now() - startedAt,
|
|
471
|
+
});
|
|
472
|
+
} catch { /* execution result remains primary */ }
|
|
174
473
|
}
|
|
175
|
-
|
|
176
|
-
} finally {
|
|
177
|
-
db.close();
|
|
474
|
+
throw error;
|
|
178
475
|
}
|
|
476
|
+
|
|
477
|
+
activity.succeed(result.tool_receipts?.length ? `Done · ${result.tool_receipts.length} tool call${result.tool_receipts.length === 1 ? '' : 's'}` : 'Done');
|
|
478
|
+
state.activity = null;
|
|
479
|
+
|
|
480
|
+
const compacted = presenter.compactionReceipt();
|
|
481
|
+
if (compacted) writeLine(state.write, compacted);
|
|
482
|
+
|
|
483
|
+
if (result.output_text) {
|
|
484
|
+
writeLine(state.write, '');
|
|
485
|
+
writeLine(state.write, result.output_text);
|
|
486
|
+
writeLine(state.write, '');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
state.usageSnapshot = result.usage_snapshot;
|
|
490
|
+
if (runtimeRunId) {
|
|
491
|
+
try {
|
|
492
|
+
await finishRuntimeRun({
|
|
493
|
+
cwd: state.cwd,
|
|
494
|
+
id: runtimeRunId,
|
|
495
|
+
status: 'completed',
|
|
496
|
+
actual_service_tier: result.actual_service_tier,
|
|
497
|
+
usage: result.cumulative_usage || result.usage_snapshot?.cumulative || {},
|
|
498
|
+
cost_usd: result.total_cost_usd || 0,
|
|
499
|
+
latency_ms: Date.now() - startedAt,
|
|
500
|
+
});
|
|
501
|
+
if (presenter.state.lastCompaction) {
|
|
502
|
+
await recordRuntimeCompaction({
|
|
503
|
+
cwd: state.cwd,
|
|
504
|
+
account_id: accountId,
|
|
505
|
+
agent_run_id: runtimeRunId,
|
|
506
|
+
session_id: state.session?.id,
|
|
507
|
+
provider: model.provider,
|
|
508
|
+
model_key: model.model_key,
|
|
509
|
+
tokens_before: presenter.state.lastCompaction.tokens_before,
|
|
510
|
+
tokens_after: presenter.state.lastCompaction.tokens_after,
|
|
511
|
+
summary_text: presenter.state.lastCompaction.summary_text || '',
|
|
512
|
+
source_kind: model.provider === 'ollama' ? 'filesystem' : 'api',
|
|
513
|
+
metadata: { verification: resolved.verification, compaction_id: presenter.state.lastCompaction.compaction_id || null },
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
} catch { /* local telemetry persistence is best-effort */ }
|
|
517
|
+
}
|
|
518
|
+
if (state.session) {
|
|
519
|
+
persistSession(state, {
|
|
520
|
+
status: 'active',
|
|
521
|
+
model_key: model.model_key,
|
|
522
|
+
provider_model_id: result.model,
|
|
523
|
+
reasoning_effort: result.reasoning_effort,
|
|
524
|
+
requested_service_tier: result.requested_service_tier,
|
|
525
|
+
actual_service_tier: result.actual_service_tier,
|
|
526
|
+
provider_state: { provider: model.provider, ...(result.provider_state || {}) },
|
|
527
|
+
usage_snapshot: result.usage_snapshot,
|
|
528
|
+
cumulative_usage: result.cumulative_usage,
|
|
529
|
+
total_cost_usd: Number(state.session.total_cost_usd || 0) + Number(result.total_cost_usd || 0),
|
|
530
|
+
cost_breakdown_usd: Object.fromEntries(['input', 'cached_input', 'cache_write', 'output'].map((key) => [
|
|
531
|
+
key, Number(state.session.cost_breakdown_usd?.[key] || 0) + Number(result.cost_breakdown_usd?.[key] || 0),
|
|
532
|
+
])),
|
|
533
|
+
last_error: null,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
writeLine(state.write, renderCliFooter({
|
|
538
|
+
model: result.model || model.provider_model_id,
|
|
539
|
+
usageSnapshot: result.usage_snapshot,
|
|
540
|
+
tier: result.actual_service_tier,
|
|
541
|
+
elapsedMs: Date.now() - startedAt,
|
|
542
|
+
}));
|
|
543
|
+
writeLine(state.write, '');
|
|
544
|
+
return result;
|
|
179
545
|
}
|
|
180
546
|
|
|
181
547
|
export async function dispatchShellLine(line, state = {}) {
|
|
182
548
|
const tokens = tokenizeShellLine(line);
|
|
183
549
|
const write = state.write || ((text) => process.stdout.write(text));
|
|
550
|
+
state.write = write;
|
|
184
551
|
state.cwd = path.resolve(state.cwd || process.cwd());
|
|
185
552
|
if (!tokens.length) return { handled: true, exit: false, cwd: state.cwd };
|
|
186
|
-
|
|
187
553
|
const [command, ...args] = tokens;
|
|
188
554
|
try {
|
|
189
555
|
switch (command.toLowerCase()) {
|
|
556
|
+
case '/':
|
|
557
|
+
case '/menu':
|
|
558
|
+
await showCommandPicker(state);
|
|
559
|
+
break;
|
|
190
560
|
case '/help':
|
|
191
|
-
write
|
|
192
|
-
|
|
561
|
+
await runHelp(args, { write, interactive: state.interactive });
|
|
562
|
+
break;
|
|
193
563
|
case '/exit':
|
|
194
564
|
case '/quit':
|
|
195
565
|
return { handled: true, exit: true, cwd: state.cwd };
|
|
566
|
+
case '/model': {
|
|
567
|
+
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false, section: 'model', home: state.home });
|
|
568
|
+
state.cwd = configured.identity.root;
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
case '/reasoning':
|
|
572
|
+
await chooseReasoning(state.cwd, args, state);
|
|
573
|
+
break;
|
|
574
|
+
case '/fast':
|
|
575
|
+
setServiceTier(state.cwd, 'fast', write);
|
|
576
|
+
break;
|
|
577
|
+
case '/flex':
|
|
578
|
+
setServiceTier(state.cwd, 'flex', write);
|
|
579
|
+
break;
|
|
580
|
+
case '/standard':
|
|
581
|
+
setServiceTier(state.cwd, 'default', write);
|
|
582
|
+
break;
|
|
583
|
+
case '/context':
|
|
584
|
+
if (args[0] === 'repo' || args[0] === 'git') await runContext(['--cwd', state.cwd, ...args.slice(1)]);
|
|
585
|
+
else write(renderContextEconomics(buildContextEconomicsReport(state.cwd, {
|
|
586
|
+
activeInputTokens: state.usageSnapshot?.current_context?.input_tokens,
|
|
587
|
+
estimateKind: state.usageSnapshot?.estimate_kind,
|
|
588
|
+
})));
|
|
589
|
+
break;
|
|
196
590
|
case '/status':
|
|
197
591
|
await runStatus(args, { cwd: state.cwd });
|
|
198
592
|
break;
|
|
199
|
-
case '/
|
|
200
|
-
await
|
|
593
|
+
case '/models':
|
|
594
|
+
await runModels(args, { cwd: state.cwd, write, home: state.home });
|
|
201
595
|
break;
|
|
596
|
+
case '/login':
|
|
597
|
+
await runLogin(args, { write, home: state.home });
|
|
598
|
+
break;
|
|
599
|
+
case '/logout':
|
|
600
|
+
runLogout(args, { write, home: state.home });
|
|
601
|
+
if (state.session) write(renderSessionReceipt(state.session));
|
|
602
|
+
break;
|
|
603
|
+
case '/whoami':
|
|
604
|
+
await runWhoami(args, { write, home: state.home });
|
|
605
|
+
break;
|
|
606
|
+
case '/session':
|
|
607
|
+
case '/usage':
|
|
608
|
+
if (state.session) write(renderSessionReceipt(state.session));
|
|
609
|
+
else writeLine(write, ' No persistent session is active in this shell invocation.');
|
|
610
|
+
break;
|
|
611
|
+
case '/cf':
|
|
612
|
+
case '/cloudflare':
|
|
613
|
+
await runCloudflare(args.length ? args : ['commands'], { cwd: state.cwd, write });
|
|
614
|
+
break;
|
|
615
|
+
case '/settings': {
|
|
616
|
+
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false, home: state.home });
|
|
617
|
+
state.cwd = configured.identity.root;
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
202
620
|
case '/pwd':
|
|
203
621
|
writeLine(write, state.cwd);
|
|
204
622
|
break;
|
|
@@ -210,54 +628,60 @@ export async function dispatchShellLine(line, state = {}) {
|
|
|
210
628
|
writeLine(write, state.cwd);
|
|
211
629
|
break;
|
|
212
630
|
}
|
|
213
|
-
case '/git':
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
631
|
+
case '/git':
|
|
632
|
+
spawnGit(state.cwd, args.length ? args : ['status', '--short', '--branch']);
|
|
633
|
+
break;
|
|
634
|
+
case '/diff':
|
|
635
|
+
spawnGit(state.cwd, ['diff', ...args]);
|
|
218
636
|
break;
|
|
219
|
-
}
|
|
220
637
|
case '/db':
|
|
221
638
|
await runDb(args.length ? args : ['status'], { cwd: state.cwd });
|
|
222
639
|
break;
|
|
223
640
|
case '/agent':
|
|
224
641
|
await runLocalAgent(args.join(' '), write, { interactive: state.interactive });
|
|
225
642
|
break;
|
|
226
|
-
case '/models':
|
|
227
|
-
await runModels(args, { cwd: state.cwd, write });
|
|
228
|
-
break;
|
|
229
|
-
case '/settings': {
|
|
230
|
-
const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false });
|
|
231
|
-
state.cwd = configured.identity.root;
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
643
|
case '/logs':
|
|
235
644
|
await showLocalLogs(state.cwd, write);
|
|
236
645
|
break;
|
|
237
646
|
case '/deploy':
|
|
238
647
|
await runDeploy(parseDeployOptions(args, state.cwd));
|
|
239
648
|
break;
|
|
649
|
+
case '/clear':
|
|
650
|
+
write('\x1b[2J\x1b[H');
|
|
651
|
+
break;
|
|
240
652
|
default:
|
|
653
|
+
if (!command.startsWith('/')) {
|
|
654
|
+
await runInteractiveModelTurn(line, state);
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
241
657
|
writeLine(write, ` Unknown Agent Sam command: ${command}`);
|
|
242
|
-
writeLine(write, ' Type /help for available commands.');
|
|
658
|
+
writeLine(write, ' Type / or /help for available commands.');
|
|
243
659
|
return { handled: false, exit: false, cwd: state.cwd };
|
|
244
660
|
}
|
|
245
661
|
} catch (error) {
|
|
246
|
-
|
|
662
|
+
if (state.session) {
|
|
663
|
+
persistSession(state, { last_error: diagnosticFromError(error, { source: 'shell', kind: 'interactive_error' }) });
|
|
664
|
+
}
|
|
665
|
+
if (!error?.reported) {
|
|
666
|
+
for (const line of renderDiagnosticError(error).split('\n')) writeLine(write, ` ${line}`);
|
|
667
|
+
}
|
|
247
668
|
}
|
|
248
|
-
|
|
249
669
|
return { handled: true, exit: false, cwd: state.cwd };
|
|
250
670
|
}
|
|
251
671
|
|
|
252
672
|
export async function runShell(argv = [], options = {}) {
|
|
253
673
|
const write = options.write || ((text) => process.stdout.write(text));
|
|
254
|
-
const state = {
|
|
674
|
+
const state = {
|
|
675
|
+
cwd: path.resolve(options.cwd || process.cwd()),
|
|
676
|
+
write,
|
|
677
|
+
interactive: options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
678
|
+
usageSnapshot: options.usageSnapshot || options.session?.usage_snapshot || null,
|
|
679
|
+
session: options.session || null,
|
|
680
|
+
home: options.home,
|
|
681
|
+
providerFetchImpl: options.providerFetchImpl,
|
|
682
|
+
};
|
|
255
683
|
const sub = argv[0] || '';
|
|
256
|
-
|
|
257
|
-
if (sub === 'list' || sub === 'status') {
|
|
258
|
-
write(renderShellCatalog());
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
684
|
+
if (sub === 'list' || sub === 'status') { write(renderShellCatalog()); return; }
|
|
261
685
|
if (sub === '--command' || sub === '--once') {
|
|
262
686
|
const line = argv.slice(1).join(' ');
|
|
263
687
|
if (!line) throw new Error(`${sub} requires a slash command`);
|
|
@@ -266,32 +690,57 @@ export async function runShell(argv = [], options = {}) {
|
|
|
266
690
|
}
|
|
267
691
|
if (sub) throw new Error(`unknown shell option: ${sub}`);
|
|
268
692
|
|
|
693
|
+
if (state.session) {
|
|
694
|
+
state.cwd = path.resolve(state.session.cwd || state.cwd);
|
|
695
|
+
state.session = saveLocalSession({
|
|
696
|
+
...state.session,
|
|
697
|
+
status: 'active',
|
|
698
|
+
cwd: state.cwd,
|
|
699
|
+
active_started_at: new Date().toISOString(),
|
|
700
|
+
}, { home: state.home });
|
|
701
|
+
state.usageSnapshot = state.session.usage_snapshot || state.usageSnapshot;
|
|
702
|
+
} else {
|
|
703
|
+
const preferences = readCliPreferences(state.cwd) || {};
|
|
704
|
+
state.session = createLocalSession({
|
|
705
|
+
cwd: state.cwd,
|
|
706
|
+
status: 'active',
|
|
707
|
+
model_key: preferences.modelPreference !== 'auto' ? preferences.modelPreference : null,
|
|
708
|
+
reasoning_effort: preferences.reasoningEffort !== 'auto' ? preferences.reasoningEffort : null,
|
|
709
|
+
requested_service_tier: preferences.serviceTier || 'default',
|
|
710
|
+
}, { home: state.home });
|
|
711
|
+
}
|
|
712
|
+
|
|
269
713
|
if (options.intro !== 'quiet') {
|
|
270
714
|
write(renderShellCatalog());
|
|
271
|
-
writeLine(write, ' Interactive shell ready. Type /
|
|
272
|
-
writeLine(write, '');
|
|
715
|
+
writeLine(write, ' Interactive shell ready. Type / for the command picker; /exit to return to your host shell.\n');
|
|
273
716
|
}
|
|
274
|
-
|
|
275
717
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY) });
|
|
718
|
+
let interrupted = false;
|
|
719
|
+
rl.on('SIGINT', () => {
|
|
720
|
+
interrupted = true;
|
|
721
|
+
rl.close();
|
|
722
|
+
});
|
|
276
723
|
const promptText = () => {
|
|
277
724
|
if (typeof options.prompt === 'function') return options.prompt(state);
|
|
278
725
|
if (typeof options.prompt === 'string' && options.prompt) return options.prompt;
|
|
279
726
|
return renderShellPrompt(state.cwd);
|
|
280
727
|
};
|
|
281
|
-
if (rl.terminal) {
|
|
282
|
-
rl.setPrompt(promptText());
|
|
283
|
-
rl.prompt();
|
|
284
|
-
}
|
|
285
|
-
|
|
728
|
+
if (rl.terminal) { rl.setPrompt(promptText()); rl.prompt(); }
|
|
286
729
|
for await (const line of rl) {
|
|
730
|
+
recordSessionInput(state, line);
|
|
287
731
|
const result = await dispatchShellLine(line, state);
|
|
288
|
-
if (result.exit) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
732
|
+
if (result.exit) { rl.close(); break; }
|
|
733
|
+
if (rl.terminal) { rl.setPrompt(promptText()); rl.prompt(); }
|
|
734
|
+
}
|
|
735
|
+
if (state.session) {
|
|
736
|
+
const activeElapsedMs = localSessionElapsedMs(state.session);
|
|
737
|
+
persistSession(state, {
|
|
738
|
+
status: interrupted ? 'interrupted' : 'paused',
|
|
739
|
+
cwd: state.cwd,
|
|
740
|
+
active_elapsed_ms: activeElapsedMs,
|
|
741
|
+
active_started_at: null,
|
|
742
|
+
});
|
|
743
|
+
if (options.receipt !== false) write(renderSessionReceipt(state.session));
|
|
296
744
|
}
|
|
745
|
+
return state.session;
|
|
297
746
|
}
|