@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.
Files changed (233) hide show
  1. package/AGENTSAM.md +55 -0
  2. package/README.md +17 -12
  3. package/bin/agentsam +2 -0
  4. package/docs/AGENTSAM_ASTRA_OPENAI_INTEGRATION.md +1363 -0
  5. package/docs/AGENTSAM_WORKBENCH.md +30 -0
  6. package/docs/AUTH_IDENTITY_CONTRACT.md +52 -0
  7. package/docs/CAPABILITIES.md +5 -3
  8. package/docs/CLI_SHELL.md +167 -80
  9. package/docs/CMS_STUDIO.md +101 -0
  10. package/docs/CONTEXT.md +170 -0
  11. package/docs/LOCAL_OLLAMA.md +58 -0
  12. package/docs/MERKLE.md +19 -19
  13. package/docs/PORTABLE_CONTEXT.md +4 -3
  14. package/docs/PROJECT_CONFIG.md +72 -0
  15. package/docs/RELEASES.md +35 -2
  16. package/docs/REPOSITORY_INTELLIGENCE.md +1 -1
  17. package/docs/REPOSITORY_KNOWLEDGE.md +114 -0
  18. package/docs/SDK_WORKER.md +86 -0
  19. package/docs/SECURITY.md +59 -22
  20. package/docs/client-cms-editor.md +15 -0
  21. package/docs/local-studio/WORKMODE_DONOR_NOTES.md +485 -0
  22. package/package.json +35 -10
  23. package/packages/connectors/cloudflare/package.json +10 -0
  24. package/packages/connectors/cloudflare/src/index.js +127 -0
  25. package/packages/connectors/cloudflare/src/owner.js +76 -0
  26. package/packages/connectors/cloudflare/src/routes.js +223 -0
  27. package/packages/connectors/cloudflare/src/vault.js +80 -0
  28. package/packages/connectors/cloudflare/tests/connector.test.mjs +44 -0
  29. package/packages/identity/package.json +10 -1
  30. package/packages/identity/src/contracts/auth-config.js +109 -0
  31. package/packages/identity/src/index.js +1 -0
  32. package/packages/identity/src/oauth/README.md +2 -2
  33. package/packages/identity/src/oauth/credentials.js +11 -4
  34. package/packages/identity/src/oauth/iam-platform.js +3 -3
  35. package/packages/identity/src/providers/iam/index.js +7 -7
  36. package/packages/identity/src/providers/iam/oauth.js +6 -4
  37. package/packages/identity/src/providers/iam/profile.js +5 -5
  38. package/packages/identity/tests/auth-config.test.mjs +61 -0
  39. package/packages/identity/tests/oauth-credentials.test.mjs +13 -2
  40. package/protocol/FILEMETA_V1.md +95 -0
  41. package/protocol/INSPECT_VIEWS_V1.md +40 -0
  42. package/protocol/MERKLE_PERSISTENCE_V1.md +50 -0
  43. package/protocol/MERKLE_V1.md +3 -1
  44. package/protocol/README.md +1 -0
  45. package/protocol/capabilities/cloudflare-cpu-audit-input.schema.json +19 -0
  46. package/protocol/capabilities/cloudflare-cpu-profile-input.schema.json +13 -0
  47. package/protocol/capabilities/cloudflare-wrangler-native-input.schema.json +19 -0
  48. package/protocol/capabilities/manifest.json +79 -3
  49. package/protocol/capabilities/repository-snapshot.schema.json +1 -0
  50. package/protocol/context/context-budget.schema.json +36 -0
  51. package/protocol/context/context-item.schema.json +19 -0
  52. package/protocol/context/resolved-context-pack.schema.json +47 -0
  53. package/protocol/context/result-policy.schema.json +17 -0
  54. package/protocol/knowledge/context-pack.schema.json +33 -0
  55. package/protocol/knowledge/retrieval-query.schema.json +68 -13
  56. package/protocol/models/README.md +373 -0
  57. package/protocol/models/model-inventory-v2.schema.json +212 -0
  58. package/python/README.md +15 -7
  59. package/python/agentsam_sdk/cli.py +0 -21
  60. package/python/agentsam_sdk/tui/README.md +17 -12
  61. package/python/agentsam_sdk/tui/bootstrap.py +2 -2
  62. package/python/agentsam_sdk/tui/demo.py +25 -10
  63. package/python/agentsam_sdk/tui/onboarding.py +208 -0
  64. package/python/tests/test_tui_cli.py +7 -6
  65. package/skills/README.md +21 -0
  66. package/skills/agentsam-app-fundamentals/SKILL.md +165 -0
  67. package/skills/agentsam-app-fundamentals/references/graphs-contracts-ast-merkle.md +89 -0
  68. package/skills/agentsam-app-fundamentals/references/trust-credentials-and-destinations.md +99 -0
  69. package/skills/agentsam-cloudflare-workers/SKILL.md +53 -0
  70. package/skills/agentsam-cloudflare-workers/references/cpu-profiling.md +16 -0
  71. package/skills/agentsam-cloudflare-workers/references/errors-and-observability.md +29 -0
  72. package/skills/agentsam-cloudflare-workers/references/wrangler-native-map.md +28 -0
  73. package/skills/agentsam-jr-dev/SKILL.md +232 -0
  74. package/skills/agentsam-jr-dev/references/real-application-logic.md +156 -0
  75. package/skills/agentsam-jr-dev/references/web-application-fundamentals.md +240 -0
  76. package/skills/agentsam-progression-guard/SKILL.md +197 -0
  77. package/skills/agentsam-progression-guard/references/checkpoint-chain.md +111 -0
  78. package/skills/agentsam-progression-guard/references/hooks-operational-io.md +96 -0
  79. package/skills/catalog.json +71 -0
  80. package/src/agent/capability-adapter.js +25 -13
  81. package/src/agent/index.js +1 -0
  82. package/src/agent/responses-runner.js +325 -0
  83. package/src/capabilities/index.js +7 -0
  84. package/src/capabilities/repository-snapshot-view.js +238 -0
  85. package/src/capabilities/repository-snapshot.js +28 -14
  86. package/src/cli.js +198 -76
  87. package/src/cloudflare/cpu-profile.js +115 -0
  88. package/src/cloudflare/index.js +14 -0
  89. package/src/cloudflare/wrangler.js +132 -0
  90. package/src/commands/account-auth.js +47 -0
  91. package/src/commands/cloudflare.js +58 -0
  92. package/src/commands/connections.js +93 -0
  93. package/src/commands/context-economics.js +114 -0
  94. package/src/commands/context.js +14 -2
  95. package/src/commands/db.js +4 -7
  96. package/src/commands/deploy.js +54 -32
  97. package/src/commands/eval.js +63 -0
  98. package/src/commands/interactive.js +18 -0
  99. package/src/commands/knowledge.js +27 -4
  100. package/src/commands/merkle-persist.js +118 -0
  101. package/src/commands/merkle.js +32 -17
  102. package/src/commands/models.js +152 -0
  103. package/src/commands/ollama.js +259 -0
  104. package/src/commands/preferences.js +144 -0
  105. package/src/commands/product.js +86 -16
  106. package/src/commands/resume.js +67 -0
  107. package/src/commands/security.js +8 -6
  108. package/src/commands/shell.js +405 -100
  109. package/src/commands/skills.js +66 -0
  110. package/src/commands/start-local.js +1 -1
  111. package/src/commands/tunnel.js +5 -4
  112. package/src/commands/whoami.js +86 -0
  113. package/src/context/budget.js +120 -0
  114. package/src/context/compact.js +28 -0
  115. package/src/context/index.js +7 -0
  116. package/src/context/rehydrate.js +35 -0
  117. package/src/context/resolve.js +116 -0
  118. package/src/context/result-policy.js +66 -0
  119. package/src/errors/diagnostic.js +160 -0
  120. package/src/errors/index.js +9 -0
  121. package/src/eval/context.js +191 -0
  122. package/src/eval/index.js +1 -0
  123. package/src/index.js +70 -0
  124. package/src/indexing/execution-boundary.js +144 -0
  125. package/src/indexing/index.js +8 -0
  126. package/src/indexing/provider.js +41 -0
  127. package/src/knowledge/config.js +2 -2
  128. package/src/knowledge/context-pack.js +12 -1
  129. package/src/knowledge/contracts.js +9 -4
  130. package/src/knowledge/engine.js +7 -3
  131. package/src/knowledge/service/server.js +1 -1
  132. package/src/lib/account-session.js +98 -0
  133. package/src/lib/agent-instructions.js +73 -0
  134. package/src/lib/auth.js +4 -0
  135. package/src/lib/bridge-client.js +7 -5
  136. package/src/lib/cli-preferences.js +78 -0
  137. package/src/lib/core-client.js +8 -8
  138. package/src/lib/deploy/git-guard.js +69 -0
  139. package/src/lib/deploy/health.js +57 -0
  140. package/src/lib/deploy/local-studio.js +283 -0
  141. package/src/lib/deploy/secret-scan.js +65 -0
  142. package/src/lib/deploy-receipt/index.js +5 -2
  143. package/src/lib/detect-context.js +6 -5
  144. package/src/lib/execution-approvals.js +59 -0
  145. package/src/lib/identity-scaffold.js +1 -1
  146. package/src/lib/local-scaffold.js +35 -33
  147. package/src/lib/local-sessions.js +127 -0
  148. package/src/lib/local-status.js +9 -17
  149. package/src/lib/merkle/cloudflare-persistence.js +321 -0
  150. package/src/lib/merkle/filemeta.js +43 -0
  151. package/src/lib/merkle/git-ignore.js +24 -0
  152. package/src/lib/merkle/hash.js +1 -0
  153. package/src/lib/merkle/index.js +22 -0
  154. package/src/lib/merkle/persistence.js +72 -0
  155. package/src/lib/merkle/semantic.js +359 -0
  156. package/src/lib/merkle/snapshot.js +9 -3
  157. package/src/lib/merkle/tree.js +11 -6
  158. package/src/lib/project-config.js +227 -0
  159. package/src/lib/project-rules.js +68 -0
  160. package/src/lib/provider-credentials.js +83 -0
  161. package/src/lib/save-sdk-token.js +1 -1
  162. package/src/lib/scaffold/templates/worker-api/index.js +101 -20
  163. package/src/lib/scaffold/wizards/worker-api.js +27 -11
  164. package/src/lib/slash-commands.js +22 -15
  165. package/src/lib/tools.js +11 -5
  166. package/src/models/catalog.js +135 -0
  167. package/src/models/index.js +7 -0
  168. package/src/providers/index.js +5 -0
  169. package/src/providers/openai-responses.js +275 -0
  170. package/src/security/index.js +1 -0
  171. package/src/security/inventory.js +4 -1
  172. package/src/security/process.js +35 -9
  173. package/src/security/render.js +27 -5
  174. package/src/security/scan.js +24 -9
  175. package/src/security/trust-boundary.js +24 -0
  176. package/src/skills/index.js +64 -0
  177. package/src/telemetry/contracts.js +203 -0
  178. package/src/telemetry/events.js +48 -0
  179. package/src/telemetry/index.js +8 -0
  180. package/src/tools/hydrate.js +35 -0
  181. package/src/tools/index.js +2 -0
  182. package/src/tools/search.js +70 -0
  183. package/src/ui/ansi.js +1 -1
  184. package/src/ui/boot.js +54 -0
  185. package/src/ui/merkle/render.js +1 -0
  186. package/src/ui/runtime-activity.js +192 -0
  187. package/src/ui/theme.js +19 -18
  188. package/test/account-session.test.mjs +36 -0
  189. package/test/app-building-skills.test.mjs +61 -0
  190. package/test/apps-scaffold-contract.test.mjs +56 -0
  191. package/test/capabilities.test.mjs +53 -4
  192. package/test/cli-preferences.test.mjs +46 -0
  193. package/test/cloudflare-connector.test.mjs +96 -0
  194. package/test/cloudflare-runtime.test.mjs +75 -0
  195. package/test/context.test.mjs +144 -0
  196. package/test/deploy-health-scan.test.mjs +67 -0
  197. package/test/error-diagnostics.test.mjs +59 -0
  198. package/test/eval-context.test.mjs +37 -0
  199. package/test/execution-approvals.test.mjs +27 -0
  200. package/test/indexing-provider.test.mjs +29 -0
  201. package/test/jr-dev-skill.test.mjs +26 -0
  202. package/test/knowledge-context-pack.test.mjs +19 -0
  203. package/test/knowledge.test.mjs +1 -1
  204. package/test/local-sessions.test.mjs +42 -0
  205. package/test/local-studio-deploy.test.mjs +83 -0
  206. package/test/merkle-persistence.test.mjs +91 -0
  207. package/test/merkle.test.mjs +77 -3
  208. package/test/model-catalog.test.mjs +43 -0
  209. package/test/models.test.mjs +51 -0
  210. package/test/npm10-lock.test.mjs +29 -0
  211. package/test/ollama.test.mjs +94 -0
  212. package/test/openai-responses.test.mjs +95 -0
  213. package/test/project-config.test.mjs +81 -0
  214. package/test/project-rules.test.mjs +44 -0
  215. package/test/provider-credentials.test.mjs +52 -0
  216. package/test/rehydrate.test.mjs +25 -0
  217. package/test/release-hygiene.test.mjs +34 -0
  218. package/test/repository-snapshot-view.test.mjs +112 -0
  219. package/test/responses-runner.test.mjs +148 -0
  220. package/test/runtime-activity.test.mjs +98 -0
  221. package/test/sdk-worker-contract.test.mjs +68 -0
  222. package/test/security.test.mjs +46 -0
  223. package/test/shell.test.mjs +55 -21
  224. package/test/skills.test.mjs +22 -0
  225. package/test/smoke.mjs +5 -2
  226. package/test/telemetry.test.mjs +79 -0
  227. package/test/theme-portability.test.mjs +14 -0
  228. package/test/tools-search.test.mjs +40 -0
  229. package/test/whoami-resume.test.mjs +56 -0
  230. package/examples/agentsam-tui-ansi.mjs +0 -149
  231. package/src/commands/tui.js +0 -120
  232. package/src/ui/splash-xterm.js +0 -290
  233. package/src/ui/splash.js +0 -426
@@ -1,16 +1,48 @@
1
1
  import readline from 'node:readline';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import os from 'node:os';
4
5
  import { spawnSync } from 'node:child_process';
6
+ import { confirm, isCancel, select } from '@clack/prompts';
5
7
  import { SLASH_COMMANDS, SHELL_PHASES } from '../lib/slash-commands.js';
6
8
  import { runContext } from './context.js';
7
9
  import { runDb } from './db.js';
8
10
  import { runDeploy } from './deploy.js';
9
11
  import { runStatus } from './status.js';
10
- import { runTui } from './tui.js';
12
+ import { runModels } from './models.js';
13
+ import { configureCliPreferences } from './preferences.js';
14
+ import { runCloudflare } from './cloudflare.js';
15
+ import { createRuntimeActivity } from '../ui/runtime-activity.js';
16
+ import { diagnosticFromError, renderDiagnosticError } from '../errors/index.js';
17
+ import { getModelRecord } from '../models/index.js';
18
+ import { readCliPreferences, updateCliPreferences } from '../lib/cli-preferences.js';
19
+ import { buildContextEconomicsReport, renderContextEconomics } from './context-economics.js';
20
+ import { createOpenAIResponsesAdapter } from '../providers/index.js';
21
+ import { createCapabilityAdapter, runResponsesAgent } from '../agent/index.js';
22
+ import { resolveProviderCredential } from '../lib/provider-credentials.js';
23
+ import { createLocalSession, saveLocalSession, sessionTitleFromInput } from '../lib/local-sessions.js';
24
+ import { grantExecutionApproval, isExecutionApproved, toolApprovalKey } from '../lib/execution-approvals.js';
25
+ import { runWhoami } from './whoami.js';
11
26
 
12
- function writeLine(write, value = '') {
13
- write(`${value}\n`);
27
+ function writeLine(write, value = '') { write(`${value}\n`); }
28
+
29
+ export function compactCwd(value, home = process.env.HOME || process.env.USERPROFILE || '') {
30
+ const cwd = path.resolve(value);
31
+ const resolvedHome = home ? path.resolve(home) : '';
32
+ if (!resolvedHome) return cwd;
33
+ if (cwd === resolvedHome) return '~';
34
+ return cwd.startsWith(`${resolvedHome}${path.sep}`) ? `~${cwd.slice(resolvedHome.length)}` : cwd;
35
+ }
36
+
37
+ export function shellUsername(env = process.env) {
38
+ const fromEnv = String(env.USER || env.USERNAME || '').trim();
39
+ if (fromEnv) return fromEnv;
40
+ try { return String(os.userInfo().username || '').trim() || 'user'; }
41
+ catch { return 'user'; }
42
+ }
43
+
44
+ export function renderShellPrompt(cwd, env = process.env) {
45
+ return `${shellUsername(env)} ${compactCwd(cwd, env.HOME || env.USERPROFILE || '')} > `;
14
46
  }
15
47
 
16
48
  export function tokenizeShellLine(input = '') {
@@ -19,34 +51,22 @@ export function tokenizeShellLine(input = '') {
19
51
  let token = '';
20
52
  let quote = '';
21
53
  let started = false;
22
-
23
54
  const flush = () => {
24
55
  if (!started) return;
25
56
  tokens.push(token);
26
57
  token = '';
27
58
  started = false;
28
59
  };
29
-
30
60
  for (let i = 0; i < source.length; i += 1) {
31
61
  const ch = source[i];
32
62
  if (quote) {
33
- if (ch === quote) {
34
- quote = '';
35
- } else {
36
- token += ch;
37
- }
38
- started = true;
39
- continue;
40
- }
41
- if (ch === '"' || ch === "'") {
42
- quote = ch;
63
+ if (ch === quote) quote = '';
64
+ else token += ch;
43
65
  started = true;
44
66
  continue;
45
67
  }
46
- if (/\s/.test(ch)) {
47
- flush();
48
- continue;
49
- }
68
+ if (ch === '"' || ch === "'") { quote = ch; started = true; continue; }
69
+ if (/\s/.test(ch)) { flush(); continue; }
50
70
  token += ch;
51
71
  started = true;
52
72
  }
@@ -62,15 +82,10 @@ export function renderShellCatalog() {
62
82
  ║ Agent Sam Terminal ║
63
83
  ╚════════════════════════════════╝
64
84
 
65
- Local PTY agentsam start-local ws://127.0.0.1:3099
66
- ANSI TUI agentsam tui zero-dependency Node UI
67
- Rich TUI agentsam tui rich optional richer Python UI
68
- agentsam tui rich --install
69
- DB agentsam db status local SQLite
85
+ Current milestone: ${next?.label ?? 'interactive runtime'}
86
+ Type / and press Enter for the scrollable command picker.
70
87
 
71
- Current milestone: ${next?.label ?? 'local terminal experience'}
72
-
73
- Slash commands (${SLASH_COMMANDS.length} registered):
88
+ Slash commands (${SLASH_COMMANDS.length} implemented):
74
89
  ${rows}
75
90
  `;
76
91
  }
@@ -86,90 +101,346 @@ function parseDeployOptions(args, cwd) {
86
101
  return opts;
87
102
  }
88
103
 
89
- async function withProcessCwd(cwd, fn) {
90
- const previous = process.cwd();
91
- process.chdir(cwd);
92
- try {
93
- return await fn();
94
- } finally {
95
- process.chdir(previous);
104
+ function spawnGit(cwd, args) {
105
+ const result = spawnSync('git', args, { cwd, stdio: 'inherit', shell: false });
106
+ if (result.error) throw result.error;
107
+ if (result.status !== 0) throw new Error(`git exited ${result.status}`);
108
+ }
109
+
110
+ function selectedModel(cwd) {
111
+ const preferences = readCliPreferences(cwd) || {};
112
+ const model = getModelRecord(preferences.modelPreference);
113
+ if (!model) throw new Error('Select an exact provider-verified model with /model first so Agent Sam can verify supported runtime controls.');
114
+ return { preferences, model };
115
+ }
116
+
117
+ async function chooseReasoning(cwd, args, state) {
118
+ const { preferences, model } = selectedModel(cwd);
119
+ let effort = String(args[0] || '').trim().toLowerCase();
120
+ if (!effort) {
121
+ if (!state.interactive) {
122
+ writeLine(state.write, ` ${model.provider_model_id} reasoning: ${model.reasoning_efforts.join(' | ')}`);
123
+ writeLine(state.write, ` current: ${preferences.reasoningEffort || 'auto'}`);
124
+ return;
125
+ }
126
+ const choice = await select({
127
+ message: `Reasoning level · ${model.provider_model_id}`,
128
+ initialValue: model.reasoning_efforts.includes(preferences.reasoningEffort) ? preferences.reasoningEffort : model.reasoning_efforts[0],
129
+ options: model.reasoning_efforts.map((value) => ({ value, label: value === 'xhigh' ? 'Extra high' : value === 'max' ? 'Max' : value[0].toUpperCase() + value.slice(1) })),
130
+ });
131
+ if (isCancel(choice)) return;
132
+ effort = choice;
96
133
  }
134
+ if (!model.reasoning_efforts.includes(effort)) throw new Error(`unsupported reasoning level for ${model.provider_model_id}: ${effort}`);
135
+ updateCliPreferences(cwd, { reasoningEffort: effort });
136
+ writeLine(state.write, ` reasoning → ${effort}`);
137
+ }
138
+
139
+ function setServiceTier(cwd, tier, write) {
140
+ const { model } = selectedModel(cwd);
141
+ if (!model.service_tiers.includes(tier)) throw new Error(`${model.provider_model_id} does not declare ${tier} processing support`);
142
+ updateCliPreferences(cwd, { serviceTier: tier });
143
+ const label = tier === 'default' ? 'standard' : tier;
144
+ writeLine(write, ` processing → ${label}`);
145
+ if (tier === 'fast') writeLine(write, ' Fast is a paid latency choice; Agent Sam will account for its model-specific pricing multiplier.');
146
+ if (tier === 'flex') writeLine(write, ' Flex trades latency/capacity availability for lower cost; it is not Batch.');
97
147
  }
98
148
 
99
- async function runLocalAgent(goal, write) {
149
+ async function showCommandPicker(state) {
150
+ if (!state.interactive) {
151
+ state.write(renderShellCatalog());
152
+ return;
153
+ }
154
+ const choice = await select({
155
+ message: 'Agent Sam commands',
156
+ options: SLASH_COMMANDS.map((row) => ({ value: row.cmd, label: row.cmd, hint: row.description })),
157
+ });
158
+ if (isCancel(choice)) return;
159
+ await dispatchShellLine(choice, state);
160
+ }
161
+
162
+ export async function runLocalAgent(goal, write, options = {}) {
100
163
  if (!goal) {
101
164
  writeLine(write, ' Usage: /agent <goal>');
102
165
  writeLine(write, ' Requires the local Agent Sam dev server (default http://127.0.0.1:8787).');
103
166
  return;
104
167
  }
105
168
  const base = String(process.env.AGENTSAM_LOCAL_URL || 'http://127.0.0.1:8787').replace(/\/$/, '');
169
+ const fetchImpl = options.fetchImpl || fetch;
170
+ const activity = options.activity || createRuntimeActivity({ write, phase: 'thinking', interactive: options.interactive });
106
171
  let response;
172
+ activity.start('thinking');
107
173
  try {
108
- response = await fetch(`${base}/api/agentsam/message`, {
109
- method: 'POST',
110
- headers: { 'content-type': 'application/json' },
111
- body: JSON.stringify({ message: goal }),
174
+ response = await fetchImpl(`${base}/api/agentsam/message`, {
175
+ method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ message: goal }),
112
176
  });
113
177
  } catch (error) {
178
+ activity.fail('unavailable');
114
179
  throw new Error(`local Agent Sam unavailable at ${base} — run \`npm run dev\` first (${error?.message || error})`);
115
180
  }
116
- const text = await response.text();
117
- if (!response.ok) throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`);
118
- try {
119
- writeLine(write, JSON.stringify(JSON.parse(text), null, 2));
120
- } catch {
121
- writeLine(write, text);
122
- }
181
+ let text;
182
+ try { text = await response.text(); }
183
+ catch (error) { activity.fail('response error'); throw new Error(`local Agent Sam response could not be read: ${error?.message || error}`); }
184
+ if (!response.ok) { activity.fail(`HTTP ${response.status}`); throw new Error(`local Agent Sam returned HTTP ${response.status}: ${text.slice(0, 400)}`); }
185
+ activity.succeed('done');
186
+ try { writeLine(write, JSON.stringify(JSON.parse(text), null, 2)); }
187
+ catch { writeLine(write, text); }
123
188
  }
124
189
 
125
190
  async function showLocalLogs(cwd, write) {
126
191
  const dbPath = path.join(cwd, '.agentsam', 'data', 'agentsam.sqlite');
127
- if (!fs.existsSync(dbPath)) {
128
- writeLine(write, ' No local Agent Sam DB found. Run `agentsam init . --yes` first.');
129
- return;
130
- }
192
+ if (!fs.existsSync(dbPath)) { writeLine(write, ' No local Agent Sam DB found. Run `agentsam init . --yes` first.'); return; }
131
193
  const { createLocalSqliteDatabase } = await import('../local/sqlite.js');
132
194
  const db = await createLocalSqliteDatabase(dbPath);
133
195
  try {
134
- const calls = await db
135
- .prepare('SELECT id, session_id, tool_name, status, created_at, completed_at FROM agent_tool_calls ORDER BY created_at DESC LIMIT 20')
136
- .all();
137
- if (!calls.results.length) {
138
- writeLine(write, ' No local Agent Sam tool-call events yet.');
139
- return;
140
- }
141
- writeLine(write, '');
142
- writeLine(write, ' Recent Agent Sam tool calls');
143
- for (const row of calls.results) {
144
- writeLine(write, ` ${String(row.created_at || '').padEnd(20)} ${String(row.status || '').padEnd(10)} ${row.tool_name}`);
145
- }
196
+ 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();
197
+ if (!calls.results.length) { writeLine(write, ' No local Agent Sam tool-call events yet.'); return; }
198
+ writeLine(write, '\n Recent Agent Sam tool calls');
199
+ for (const row of calls.results) writeLine(write, ` ${String(row.created_at || '').padEnd(20)} ${String(row.status || '').padEnd(10)} ${row.tool_name}`);
146
200
  writeLine(write, '');
147
- } finally {
148
- db.close();
201
+ } finally { db.close(); }
202
+ }
203
+
204
+ function formatCount(value) {
205
+ return Math.max(0, Number(value || 0)).toLocaleString('en-US');
206
+ }
207
+
208
+ function formatUsd(value) {
209
+ const amount = Number(value || 0);
210
+ if (!Number.isFinite(amount)) return 'unavailable';
211
+ const currency = String.fromCharCode(36);
212
+ if (amount === 0) return currency + '0.000000';
213
+ return currency + (amount < 0.01 ? amount.toFixed(6) : amount.toFixed(4));
214
+ }
215
+
216
+ export function renderSessionReceipt(session) {
217
+ if (!session) return '';
218
+ const usage = session.cumulative_usage || {};
219
+ const input = Number(usage.input_tokens || 0);
220
+ const output = Number(usage.output_tokens || 0);
221
+ const cached = Number(usage.cached_input_tokens || 0);
222
+ const reasoning = Number(usage.reasoning_tokens || 0);
223
+ const total = input + output;
224
+ const active = Number(session.usage_snapshot?.current_context?.input_tokens || 0);
225
+ const lines = [
226
+ '',
227
+ `Token usage: total=${formatCount(total)} input=${formatCount(input)}${cached ? ` (+ ${formatCount(cached)} cached)` : ''} output=${formatCount(output)}${reasoning ? ` reasoning=${formatCount(reasoning)}` : ''}`,
228
+ `Cost: ${formatUsd(session.total_cost_usd)} · ${session.model_key || 'model unavailable'}${session.actual_service_tier ? ` · ${session.actual_service_tier}` : ''}`,
229
+ ];
230
+ if (active) lines.push(`Active context: ${formatCount(active)} tokens`);
231
+ lines.push('', 'To continue this session, run:', ` agentsam resume ${session.id}`, '', 'Or run:', ' agentsam resume', '', 'and select:', ` ${session.title || 'this session'}`, '');
232
+ return lines.join('\n');
233
+ }
234
+
235
+ function persistSession(state, patch = {}) {
236
+ if (!state.session) return null;
237
+ state.session = saveLocalSession({ ...state.session, ...patch }, { home: state.home });
238
+ return state.session;
239
+ }
240
+
241
+ function recordSessionInput(state, input) {
242
+ const value = String(input || '').trim();
243
+ if (!state.session || !value) return;
244
+ const housekeeping = new Set(['/exit', '/quit', '/session', '/help', '/', '/menu', '/clear']);
245
+ if (housekeeping.has(value.toLowerCase())) return;
246
+ persistSession(state, {
247
+ status: 'active',
248
+ last_input: value,
249
+ title: sessionTitleFromInput(value),
250
+ });
251
+ }
252
+
253
+ function safeToolInput(value, depth = 0) {
254
+ if (depth > 3) return '[nested]';
255
+ if (Array.isArray(value)) return value.slice(0, 12).map((row) => safeToolInput(row, depth + 1));
256
+ if (!value || typeof value !== 'object') return value;
257
+ const output = {};
258
+ for (const [key, item] of Object.entries(value)) {
259
+ if (/token|secret|password|api[_-]?key|authorization/i.test(key)) output[key] = '[REDACTED]';
260
+ else output[key] = safeToolInput(item, depth + 1);
261
+ }
262
+ return output;
263
+ }
264
+
265
+ async function approveModelRequest(preflight, state) {
266
+ const approvedCeiling = Number(state.session?.approved_projected_call_cost_usd || 0);
267
+ if (approvedCeiling > 0 && preflight.projected_max_call_cost_usd <= approvedCeiling) return true;
268
+ if (!state.interactive) return false;
269
+ writeLine(state.write, '');
270
+ writeLine(state.write, ' Model request');
271
+ writeLine(state.write, ` model ${preflight.model}`);
272
+ writeLine(state.write, ` reasoning ${preflight.reasoning_effort}`);
273
+ writeLine(state.write, ` processing ${preflight.service_tier}`);
274
+ writeLine(state.write, ` context ~${formatCount(preflight.estimated_input_tokens)} input tokens`);
275
+ writeLine(state.write, ` max call ${formatUsd(preflight.projected_max_call_cost_usd)} conservative ceiling`);
276
+ if (Number.isFinite(preflight.tokens_until_pricing_threshold)) writeLine(state.write, ` price cliff ${formatCount(preflight.tokens_until_pricing_threshold)} tokens headroom`);
277
+ const approved = await confirm({ message: 'Send this request?', initialValue: true });
278
+ if (isCancel(approved) || approved !== true) return false;
279
+ persistSession(state, { approved_projected_call_cost_usd: Math.max(approvedCeiling, preflight.projected_max_call_cost_usd) });
280
+ return true;
281
+ }
282
+
283
+ async function approveToolExecution(request, state) {
284
+ const sideEffects = request?.descriptor?.side_effects || 'none';
285
+ if (sideEffects === 'none') return true;
286
+ const key = toolApprovalKey(request.capability_id, request.input);
287
+ if (isExecutionApproved({ cwd: state.cwd, key }, { home: state.home })) return true;
288
+ if (!state.interactive) return false;
289
+
290
+ const summary = JSON.stringify(safeToolInput(request.input || {}));
291
+ writeLine(state.write, '');
292
+ writeLine(state.write, ' Agent Sam needs execution permission');
293
+ writeLine(state.write, ` action ${key}`);
294
+ writeLine(state.write, ` target local runtime · ${compactCwd(state.cwd)}`);
295
+ writeLine(state.write, ` effect ${sideEffects}`);
296
+ if (summary && summary !== '{}') writeLine(state.write, ` input ${summary.length > 500 ? `${summary.slice(0, 497)}...` : summary}`);
297
+ writeLine(state.write, ' secrets remain runtime-owned and are not included in the model-visible result.');
298
+
299
+ const choice = await select({
300
+ message: `Allow ${key}?`,
301
+ options: [
302
+ { value: 'once', label: 'Allow once' },
303
+ { value: 'always', label: `Always allow ${key} in this project` },
304
+ { value: 'deny', label: 'Deny' },
305
+ ],
306
+ });
307
+ if (isCancel(choice) || choice === 'deny') return false;
308
+ if (choice === 'always') grantExecutionApproval({ cwd: state.cwd, key, label: key }, { home: state.home });
309
+ return true;
310
+ }
311
+
312
+ async function runInteractiveModelTurn(prompt, state) {
313
+ const { preferences, model } = selectedModel(state.cwd);
314
+ if (model.provider !== 'openai') throw new Error(`interactive_provider_not_implemented:${model.provider}`);
315
+ const credential = resolveProviderCredential(model.provider, { home: state.home });
316
+ if (!credential.configured) throw new Error(`provider_credential_unavailable:${model.provider}:${credential.error || credential.env || 'not_configured'}`);
317
+
318
+ const activity = createRuntimeActivity({ write: state.write, phase: 'thinking', interactive: state.interactive });
319
+ const provider = createOpenAIResponsesAdapter({ apiKey: credential.value });
320
+ const capabilityAdapter = createCapabilityAdapter();
321
+ const samePolicy = state.session
322
+ && state.session.model_key === model.model_key
323
+ && state.session.reasoning_effort === preferences.reasoningEffort
324
+ && state.session.requested_service_tier === preferences.serviceTier;
325
+ const previousResponseId = samePolicy ? state.session?.provider_state?.previous_response_id : null;
326
+ const previousUsageSnapshot = samePolicy ? state.session?.usage_snapshot : null;
327
+
328
+ activity.start('thinking');
329
+ let result;
330
+ try {
331
+ result = await runResponsesAgent({
332
+ provider,
333
+ capabilityAdapter,
334
+ cwd: state.cwd,
335
+ prompt,
336
+ model: model.model_key,
337
+ reasoningEffort: preferences.reasoningEffort,
338
+ serviceTier: preferences.serviceTier,
339
+ previousResponseId,
340
+ previousUsageSnapshot,
341
+ cumulativeUsage: state.session?.cumulative_usage || null,
342
+ promptCacheKey: state.session?.id || undefined,
343
+ runId: state.session?.id || undefined,
344
+ beforeRequest: (preflight) => approveModelRequest(preflight, state),
345
+ beforeTool: (request) => approveToolExecution(request, state),
346
+ emit(event) {
347
+ if (event?.type === 'usage.snapshot') state.usageSnapshot = event.payload;
348
+ },
349
+ });
350
+ } catch (error) {
351
+ activity.fail('failed');
352
+ throw error;
353
+ }
354
+ activity.succeed('done');
355
+ if (result.output_text) {
356
+ writeLine(state.write, '');
357
+ writeLine(state.write, result.output_text);
358
+ writeLine(state.write, '');
359
+ }
360
+ state.usageSnapshot = result.usage_snapshot;
361
+ if (state.session) {
362
+ persistSession(state, {
363
+ status: 'active',
364
+ model_key: model.model_key,
365
+ provider_model_id: result.model,
366
+ reasoning_effort: result.reasoning_effort,
367
+ requested_service_tier: result.requested_service_tier,
368
+ actual_service_tier: result.actual_service_tier,
369
+ provider_state: { provider: model.provider, previous_response_id: result.response_id },
370
+ usage_snapshot: result.usage_snapshot,
371
+ cumulative_usage: result.cumulative_usage,
372
+ total_cost_usd: Number(state.session.total_cost_usd || 0) + Number(result.total_cost_usd || 0),
373
+ last_error: null,
374
+ });
149
375
  }
376
+ return result;
150
377
  }
151
378
 
152
379
  export async function dispatchShellLine(line, state = {}) {
153
380
  const tokens = tokenizeShellLine(line);
154
381
  const write = state.write || ((text) => process.stdout.write(text));
382
+ state.write = write;
155
383
  state.cwd = path.resolve(state.cwd || process.cwd());
156
384
  if (!tokens.length) return { handled: true, exit: false, cwd: state.cwd };
157
-
158
385
  const [command, ...args] = tokens;
159
386
  try {
160
387
  switch (command.toLowerCase()) {
388
+ case '/':
389
+ case '/menu':
390
+ await showCommandPicker(state);
391
+ break;
161
392
  case '/help':
162
393
  write(renderShellCatalog());
163
- return { handled: true, exit: false, cwd: state.cwd };
394
+ break;
164
395
  case '/exit':
165
396
  case '/quit':
166
397
  return { handled: true, exit: true, cwd: state.cwd };
398
+ case '/model': {
399
+ const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false, section: 'model' });
400
+ state.cwd = configured.identity.root;
401
+ break;
402
+ }
403
+ case '/reasoning':
404
+ await chooseReasoning(state.cwd, args, state);
405
+ break;
406
+ case '/fast':
407
+ setServiceTier(state.cwd, 'fast', write);
408
+ break;
409
+ case '/flex':
410
+ setServiceTier(state.cwd, 'flex', write);
411
+ break;
412
+ case '/standard':
413
+ setServiceTier(state.cwd, 'default', write);
414
+ break;
415
+ case '/context':
416
+ if (args[0] === 'repo' || args[0] === 'git') await runContext(['--cwd', state.cwd, ...args.slice(1)]);
417
+ else write(renderContextEconomics(buildContextEconomicsReport(state.cwd, {
418
+ activeInputTokens: state.usageSnapshot?.current_context?.input_tokens,
419
+ estimateKind: state.usageSnapshot?.estimate_kind,
420
+ })));
421
+ break;
167
422
  case '/status':
168
423
  await runStatus(args, { cwd: state.cwd });
169
424
  break;
170
- case '/context':
171
- await runContext(['--cwd', state.cwd, ...args]);
425
+ case '/models':
426
+ await runModels(args, { cwd: state.cwd, write, home: state.home });
172
427
  break;
428
+ case '/whoami':
429
+ await runWhoami(args, { write, home: state.home });
430
+ break;
431
+ case '/session':
432
+ if (state.session) write(renderSessionReceipt(state.session));
433
+ else writeLine(write, ' No persistent session is active in this shell invocation.');
434
+ break;
435
+ case '/cf':
436
+ case '/cloudflare':
437
+ await runCloudflare(args.length ? args : ['commands'], { cwd: state.cwd, write });
438
+ break;
439
+ case '/settings': {
440
+ const configured = await configureCliPreferences({ cwd: state.cwd, firstRun: false });
441
+ state.cwd = configured.identity.root;
442
+ break;
443
+ }
173
444
  case '/pwd':
174
445
  writeLine(write, state.cwd);
175
446
  break;
@@ -181,49 +452,59 @@ export async function dispatchShellLine(line, state = {}) {
181
452
  writeLine(write, state.cwd);
182
453
  break;
183
454
  }
184
- case '/git': {
185
- const gitArgs = args.length ? args : ['status', '--short', '--branch'];
186
- const result = spawnSync('git', gitArgs, { cwd: state.cwd, stdio: 'inherit', shell: false });
187
- if (result.error) throw result.error;
188
- if (result.status !== 0) throw new Error(`git exited ${result.status}`);
455
+ case '/git':
456
+ spawnGit(state.cwd, args.length ? args : ['status', '--short', '--branch']);
457
+ break;
458
+ case '/diff':
459
+ spawnGit(state.cwd, ['diff', ...args]);
189
460
  break;
190
- }
191
461
  case '/db':
192
462
  await runDb(args.length ? args : ['status'], { cwd: state.cwd });
193
463
  break;
194
464
  case '/agent':
195
- await runLocalAgent(args.join(' '), write);
465
+ await runLocalAgent(args.join(' '), write, { interactive: state.interactive });
196
466
  break;
197
467
  case '/logs':
198
468
  await showLocalLogs(state.cwd, write);
199
469
  break;
200
- case '/tui':
201
- await withProcessCwd(state.cwd, () => runTui(args));
202
- break;
203
470
  case '/deploy':
204
471
  await runDeploy(parseDeployOptions(args, state.cwd));
205
472
  break;
473
+ case '/clear':
474
+ write('\x1b[2J\x1b[H');
475
+ break;
206
476
  default:
477
+ if (!command.startsWith('/')) {
478
+ await runInteractiveModelTurn(line, state);
479
+ break;
480
+ }
207
481
  writeLine(write, ` Unknown Agent Sam command: ${command}`);
208
- writeLine(write, ' Type /help for available commands.');
482
+ writeLine(write, ' Type / or /help for available commands.');
209
483
  return { handled: false, exit: false, cwd: state.cwd };
210
484
  }
211
485
  } catch (error) {
212
- writeLine(write, ` ✗ ${error?.message || error}`);
486
+ if (state.session) {
487
+ persistSession(state, { last_error: diagnosticFromError(error, { source: 'shell', kind: 'interactive_error' }) });
488
+ }
489
+ if (!error?.reported) {
490
+ for (const line of renderDiagnosticError(error).split('\n')) writeLine(write, ` ${line}`);
491
+ }
213
492
  }
214
-
215
493
  return { handled: true, exit: false, cwd: state.cwd };
216
494
  }
217
495
 
218
496
  export async function runShell(argv = [], options = {}) {
219
497
  const write = options.write || ((text) => process.stdout.write(text));
220
- const state = { cwd: path.resolve(options.cwd || process.cwd()), write };
498
+ const state = {
499
+ cwd: path.resolve(options.cwd || process.cwd()),
500
+ write,
501
+ interactive: options.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY),
502
+ usageSnapshot: options.usageSnapshot || options.session?.usage_snapshot || null,
503
+ session: options.session || null,
504
+ home: options.home,
505
+ };
221
506
  const sub = argv[0] || '';
222
-
223
- if (sub === 'list' || sub === 'status') {
224
- write(renderShellCatalog());
225
- return;
226
- }
507
+ if (sub === 'list' || sub === 'status') { write(renderShellCatalog()); return; }
227
508
  if (sub === '--command' || sub === '--once') {
228
509
  const line = argv.slice(1).join(' ');
229
510
  if (!line) throw new Error(`${sub} requires a slash command`);
@@ -232,22 +513,46 @@ export async function runShell(argv = [], options = {}) {
232
513
  }
233
514
  if (sub) throw new Error(`unknown shell option: ${sub}`);
234
515
 
235
- write(renderShellCatalog());
236
- writeLine(write, ' Interactive shell ready. Type /help for commands; /exit to return to your host shell.');
237
- writeLine(write, '');
238
-
239
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY) });
240
- if (rl.terminal) {
241
- rl.setPrompt('agentsam> ');
242
- rl.prompt();
516
+ if (state.session) {
517
+ state.cwd = path.resolve(state.session.cwd || state.cwd);
518
+ state.session = saveLocalSession({ ...state.session, status: 'active', cwd: state.cwd }, { home: state.home });
519
+ state.usageSnapshot = state.session.usage_snapshot || state.usageSnapshot;
520
+ } else {
521
+ const preferences = readCliPreferences(state.cwd) || {};
522
+ state.session = createLocalSession({
523
+ cwd: state.cwd,
524
+ status: 'active',
525
+ model_key: preferences.modelPreference !== 'auto' ? preferences.modelPreference : null,
526
+ reasoning_effort: preferences.reasoningEffort !== 'auto' ? preferences.reasoningEffort : null,
527
+ requested_service_tier: preferences.serviceTier || 'default',
528
+ }, { home: state.home });
243
529
  }
244
530
 
531
+ if (options.intro !== 'quiet') {
532
+ write(renderShellCatalog());
533
+ writeLine(write, ' Interactive shell ready. Type / for the command picker; /exit to return to your host shell.\n');
534
+ }
535
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY && process.stdout.isTTY) });
536
+ let interrupted = false;
537
+ rl.on('SIGINT', () => {
538
+ interrupted = true;
539
+ rl.close();
540
+ });
541
+ const promptText = () => {
542
+ if (typeof options.prompt === 'function') return options.prompt(state);
543
+ if (typeof options.prompt === 'string' && options.prompt) return options.prompt;
544
+ return renderShellPrompt(state.cwd);
545
+ };
546
+ if (rl.terminal) { rl.setPrompt(promptText()); rl.prompt(); }
245
547
  for await (const line of rl) {
548
+ recordSessionInput(state, line);
246
549
  const result = await dispatchShellLine(line, state);
247
- if (result.exit) {
248
- rl.close();
249
- break;
250
- }
251
- if (rl.terminal) rl.prompt();
550
+ if (result.exit) { rl.close(); break; }
551
+ if (rl.terminal) { rl.setPrompt(promptText()); rl.prompt(); }
552
+ }
553
+ if (state.session) {
554
+ persistSession(state, { status: interrupted ? 'interrupted' : 'paused', cwd: state.cwd });
555
+ if (options.receipt !== false) write(renderSessionReceipt(state.session));
252
556
  }
557
+ return state.session;
253
558
  }