@superjackfruitlabs/salaam 0.0.3 → 0.0.5

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 (172) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +12 -11
  3. package/dist/src/acp-desk.js +140 -0
  4. package/dist/src/acp-outcome.js +182 -18
  5. package/dist/src/acp.js +296 -15
  6. package/dist/src/agent-profiles.js +45 -0
  7. package/dist/src/agentpod-authority.js +169 -11
  8. package/dist/src/agentpod-default-config.js +12 -0
  9. package/dist/src/agentpod-work-decision-protocol.js +40 -6
  10. package/dist/src/agentpod-work-decisions.js +108 -4
  11. package/dist/src/agentpod-work.js +1214 -127
  12. package/dist/src/approval-rules.js +287 -0
  13. package/dist/src/attach.js +3 -1
  14. package/dist/src/autonomy.js +80 -9
  15. package/dist/src/board-reads.js +106 -0
  16. package/dist/src/children.js +83 -0
  17. package/dist/src/cli-help.js +61 -6
  18. package/dist/src/cli.js +140 -4
  19. package/dist/src/command-danger.js +797 -0
  20. package/dist/src/command-network.js +200 -0
  21. package/dist/src/command-sandbox.js +327 -0
  22. package/dist/src/custom-commands.js +159 -0
  23. package/dist/src/danger-classifier.js +286 -0
  24. package/dist/src/execution-evidence.js +33 -1
  25. package/dist/src/git-helpers.js +160 -0
  26. package/dist/src/hooks.js +351 -0
  27. package/dist/src/host-bind.js +111 -0
  28. package/dist/src/host-breaker.js +88 -0
  29. package/dist/src/host-start.js +41 -1
  30. package/dist/src/host.js +284 -6
  31. package/dist/src/house-extension.js +292 -61
  32. package/dist/src/local-coding.js +726 -35
  33. package/dist/src/local-edit-helper.js +25 -0
  34. package/dist/src/local-edits.js +168 -6
  35. package/dist/src/local-undo.js +25 -16
  36. package/dist/src/managed.js +1 -1
  37. package/dist/src/mentions.js +277 -0
  38. package/dist/src/operator-mcp-cli.js +107 -0
  39. package/dist/src/operator-mcp.js +342 -0
  40. package/dist/src/persona.js +31 -0
  41. package/dist/src/pi-sessions.js +5 -1
  42. package/dist/src/pi.js +22 -1
  43. package/dist/src/project-instructions.js +195 -0
  44. package/dist/src/protocol.js +9 -3
  45. package/dist/src/rate-limit-retries.js +62 -0
  46. package/dist/src/recurring-work.js +1 -0
  47. package/dist/src/repository-snapshots.js +5 -2
  48. package/dist/src/run-grants.js +62 -0
  49. package/dist/src/service.js +482 -10
  50. package/dist/src/session-pick.js +77 -0
  51. package/dist/src/shell-passthrough.js +39 -0
  52. package/dist/src/station-checkpoints.js +139 -0
  53. package/dist/src/station-config.js +8 -1
  54. package/dist/src/station-files.js +20 -1
  55. package/dist/src/station-task-verification.js +6 -3
  56. package/dist/src/station-work.js +315 -22
  57. package/dist/src/store.js +70 -4
  58. package/dist/src/supervisor.js +408 -25
  59. package/dist/src/task-budget.js +39 -3
  60. package/dist/src/task-context.js +8 -2
  61. package/dist/src/task-critic.js +115 -0
  62. package/dist/src/task-driver.js +46 -2
  63. package/dist/src/task-policy.js +44 -12
  64. package/dist/src/task-verification.js +42 -9
  65. package/dist/src/task-work.js +47 -0
  66. package/dist/src/terminal-status.js +88 -0
  67. package/dist/src/tool-identity.js +69 -0
  68. package/dist/src/tui-activity.js +99 -0
  69. package/dist/src/tui-classifier.js +25 -0
  70. package/dist/src/tui-client.js +186 -5
  71. package/dist/src/tui-commands.js +23 -5
  72. package/dist/src/tui-composer.js +113 -0
  73. package/dist/src/tui-keymap.js +116 -0
  74. package/dist/src/tui-mentions.js +38 -0
  75. package/dist/src/tui-modals.js +157 -34
  76. package/dist/src/tui-notify.js +62 -0
  77. package/dist/src/tui-review.js +55 -0
  78. package/dist/src/tui-turn-keys.js +52 -0
  79. package/dist/src/tui-view.js +243 -69
  80. package/dist/src/tui.js +875 -118
  81. package/dist/src/turn-checkpoints.js +221 -0
  82. package/dist/src/turn-rewind.js +168 -0
  83. package/dist/src/usage.js +1 -1
  84. package/dist/src/web-fetch.js +114 -0
  85. package/dist/src/web-guard.js +163 -0
  86. package/dist/src/web-search.js +302 -0
  87. package/npm-shrinkwrap.json +2 -2
  88. package/package.json +1 -1
  89. package/src/acp-desk.ts +155 -0
  90. package/src/acp-outcome.ts +174 -20
  91. package/src/acp.ts +386 -30
  92. package/src/agent-profiles.ts +48 -0
  93. package/src/agentpod-authority.ts +234 -15
  94. package/src/agentpod-default-config.ts +16 -0
  95. package/src/agentpod-work-decision-protocol.ts +69 -8
  96. package/src/agentpod-work-decisions.ts +123 -5
  97. package/src/agentpod-work.ts +1455 -152
  98. package/src/approval-rules.ts +360 -0
  99. package/src/attach.ts +4 -1
  100. package/src/autonomy.ts +111 -10
  101. package/src/board-reads.ts +148 -0
  102. package/src/children.ts +112 -0
  103. package/src/cli-help.ts +60 -6
  104. package/src/cli.ts +174 -4
  105. package/src/command-danger.ts +886 -0
  106. package/src/command-network.ts +215 -0
  107. package/src/command-sandbox.ts +393 -0
  108. package/src/custom-commands.ts +175 -0
  109. package/src/danger-classifier.ts +321 -0
  110. package/src/execution-evidence.ts +31 -1
  111. package/src/git-helpers.ts +177 -0
  112. package/src/hooks.ts +425 -0
  113. package/src/host-bind.ts +156 -0
  114. package/src/host-breaker.ts +89 -0
  115. package/src/host-start.ts +51 -1
  116. package/src/host.ts +365 -6
  117. package/src/house-extension.ts +313 -60
  118. package/src/local-coding.ts +894 -43
  119. package/src/local-edit-helper.ts +26 -0
  120. package/src/local-edits.ts +191 -8
  121. package/src/local-undo.ts +29 -14
  122. package/src/managed.ts +2 -2
  123. package/src/mentions.ts +304 -0
  124. package/src/operator-mcp-cli.ts +108 -0
  125. package/src/operator-mcp.ts +420 -0
  126. package/src/persona.ts +37 -0
  127. package/src/pi-sessions.ts +9 -1
  128. package/src/pi.ts +23 -1
  129. package/src/project-instructions.ts +229 -0
  130. package/src/protocol.ts +25 -3
  131. package/src/rate-limit-retries.ts +69 -0
  132. package/src/recurring-work.ts +1 -0
  133. package/src/repository-snapshots.ts +5 -2
  134. package/src/run-grants.ts +107 -0
  135. package/src/service.ts +709 -10
  136. package/src/session-pick.ts +102 -0
  137. package/src/shell-passthrough.ts +38 -0
  138. package/src/station-checkpoints.ts +183 -0
  139. package/src/station-config.ts +10 -1
  140. package/src/station-files.ts +20 -1
  141. package/src/station-task-verification.ts +6 -3
  142. package/src/station-work.ts +369 -20
  143. package/src/store.ts +70 -3
  144. package/src/supervisor.ts +463 -26
  145. package/src/task-budget.ts +48 -3
  146. package/src/task-context.ts +9 -2
  147. package/src/task-critic.ts +171 -0
  148. package/src/task-driver.ts +49 -2
  149. package/src/task-policy.ts +65 -13
  150. package/src/task-verification.ts +53 -9
  151. package/src/task-work.ts +75 -0
  152. package/src/terminal-status.ts +103 -0
  153. package/src/tool-identity.ts +110 -0
  154. package/src/tui-activity.ts +133 -0
  155. package/src/tui-classifier.ts +38 -0
  156. package/src/tui-client.ts +239 -8
  157. package/src/tui-commands.ts +49 -10
  158. package/src/tui-composer.ts +124 -0
  159. package/src/tui-keymap.ts +152 -0
  160. package/src/tui-mentions.ts +43 -0
  161. package/src/tui-modals.ts +173 -43
  162. package/src/tui-notify.ts +77 -0
  163. package/src/tui-review.ts +76 -0
  164. package/src/tui-turn-keys.ts +77 -0
  165. package/src/tui-view.ts +341 -88
  166. package/src/tui.ts +1047 -137
  167. package/src/turn-checkpoints.ts +253 -0
  168. package/src/turn-rewind.ts +258 -0
  169. package/src/usage.ts +1 -1
  170. package/src/web-fetch.ts +140 -0
  171. package/src/web-guard.ts +195 -0
  172. package/src/web-search.ts +402 -0
@@ -53,12 +53,22 @@ salaam agents revoke GRANT_ID --session SESSION_ID
53
53
  Profiles are data in .salaam/agents/*.md with JSON front matter and a prompt body.
54
54
  Inspect the effective tools and digest before admission. Project definitions cannot
55
55
  grant themselves authority. Mutations accept --request-id and --takeover.`,
56
+ code: `salaam code [--continue | --new | --resume] [--cwd DIR] [--takeover]
57
+ salaam here (the same)
58
+ salaam -c (short for salaam code --continue)
59
+
60
+ Work in this folder with one command: start the service if it is not running, then continue
61
+ the folder's latest session (the default), start a new local coding session (--new), or pick
62
+ any session from a list (--resume), this folder's sessions first. No session id needed.
63
+ SALAAM_TERMINAL_STATUS=off stops the window-title and Herdr status reports.`,
56
64
  tui: `salaam tui [--session SESSION_ID] [--observe|--takeover] [--theme night|paper]
57
65
  [--cwd DIR] [--coding-root PRIVATE_DIR] [--coding-socket DOCKER_SOCKET]
58
66
 
59
67
  Bare sal opens the same Salaam terminal interface in a TTY.
60
- Ctrl+K actions · Ctrl+P sessions · Ctrl+N new · Ctrl+O approvals
61
- Ctrl+G changes · Ctrl+T commands · Ctrl+E external editor · Ctrl+C detach.
68
+ Ctrl+K (empty composer) or F2 actions · Ctrl+P sessions · Ctrl+N new · Ctrl+O approvals
69
+ Alt+G changes · Ctrl+T commands · Ctrl+G external editor · Ctrl+R history search
70
+ F5 refresh · !command shell · Ctrl+C stops/clears, twice to detach.
71
+ ? on an empty composer (or F1) shows the keys for where you are.
62
72
  Work continues in the service when the terminal closes.`,
63
73
  remote: `salaam remote SSH_HOST [--remote-bin ABSOLUTE_PATH] [--state REMOTE_DIR]
64
74
  [--session SESSION_ID] [--observe|--takeover] [--theme night|paper]
@@ -88,9 +98,11 @@ Run the M9.1 boundary proof on a pre-provisioned local Linux Docker engine.
88
98
  Only disposable fixtures are mounted. The pinned image must already be present.
89
99
  No coding tools are enabled. A failed/unavailable backend exits nonzero.
90
100
  Cleanup reconciles the saved proof run without replaying commands.`,
91
- acp: `salaam acp [--state DIR] [--coding]
101
+ acp: `salaam acp [--state DIR] [--local | --coding]
92
102
 
93
103
  Run the ACP v1 stdio client adapter against an already running Salaam service.
104
+ --local creates your desk session in the editor's project folder, the same as
105
+ salaam in a terminal there: modes, approvals with diffs, rules and /rewind.
94
106
  --coding creates owned coding sessions; the service must have --coding-root.
95
107
  Loading an existing session preserves its original tool authority.`,
96
108
  artifacts: `salaam artifacts put SESSION_ID --text TEXT [--request-id ID] [--takeover]
@@ -103,6 +115,10 @@ salaam host inspect --manifest host-manifest.json
103
115
  salaam host unit --manifest host-manifest.json --platform systemd|launchd
104
116
  salaam host run --manifest host-manifest.json
105
117
  salaam host start --manifest host-manifest.json
118
+ salaam host unit-start --unit salaam-host@STATION.service --state DIR
119
+ salaam host bind --organization ORG --node NODE --station-key KEY --workspace DIR \
120
+ --tools-user USER --files HELPER --runtime-root DIR --runtime-image-digest sha256:… \
121
+ [--operator-group GROUP] [--provider P --model M] [--mcp-config FILE] [--persona FILE] [--managed-mcp-config FILE] [--dir DIR] [--state DIR]
106
122
 
107
123
  Config requires absolute state, coding_root and coding_socket paths; optional
108
124
  provider, model, max_output_tokens and workspace_id. Planning probes the existing
@@ -116,7 +132,18 @@ For a bound AgentPod host, use mode="agentpod", state and agentpod_config instea
116
132
  of coding_root/coding_socket. This profile enables no station tools. The private
117
133
  manifest pins Node, CLI, source/build, Pi, provider and authority pins. Invoke the
118
134
  canonical Node executable explicitly. host start waits for matching live health
119
- and safely coalesces concurrent callers; it never installs or upgrades packages.`,
135
+ and safely coalesces concurrent callers; it never installs or upgrades packages.
136
+
137
+ Launcher profile (Linux): root plans a strict work host with operator_group set.
138
+ A root-owned unit runs host run from that root-held manifest. The daemon lets that
139
+ one group traverse its state and use operator.sock. An unprivileged member then
140
+ runs host unit-start, which asks systemd to start the named salaam-host@ unit and
141
+ prints the receipt the running daemon reports. It cannot choose what root runs.
142
+
143
+ host bind (root, Linux) writes everything a launcher-profile station needs into
144
+ --dir (default /etc/salaam/stations/<key>): agentpod-pins.json, station-config.json
145
+ (with tools_user), host-config.json, and host.json from host plan. It is
146
+ idempotent: the same inputs leave the files and the manifest digest unchanged.`,
120
147
  schedules: `salaam schedules create TASK_ID --name NAME --revision N --max-steps N [--schedule FILE] [--compact]
121
148
  salaam schedules list [--offset N]
122
149
  salaam schedules inspect JOB_ID
@@ -208,10 +235,32 @@ Status prints metadata only. Existing Anthropic keys remain compatible.`,
208
235
  [--coding-root PRIVATE_DIR] [--coding-socket DOCKER_SOCKET]
209
236
  [--harness-config OWNER_ONLY_FILE] [--a2a-config OWNER_ONLY_FILE]
210
237
  [--a2a-listen-config OWNER_ONLY_FILE]
211
- [--agentpod-config OWNER_ONLY_FILE]
238
+ [--agentpod-config OWNER_ONLY_FILE] [--mcp-config FILE]
239
+ [--tools-user USER]
240
+ [--local-sandbox read-only|workspace-write|full-access]
241
+ [--local-sandbox-read PATH,…|toolchain] [--unsandboxed-commands allow]
212
242
  salaam service status
213
243
  salaam service stop
214
244
 
245
+ --tools-user USER (or SALAAM_TOOLS_USER) runs local agent commands and edits as
246
+ USER through sudo -n -u USER on Linux. Without it, tools share the provider
247
+ credential's OS user and start prints a warning.
248
+
249
+ Local commands (session create --local-coding) run under the host OS sandbox:
250
+ bubblewrap on Linux, Seatbelt on macOS. --local-sandbox (or SALAAM_LOCAL_SANDBOX)
251
+ picks the level: workspace-write (default) writes only inside the workspace,
252
+ read-only writes nothing, full-access runs commands unsandboxed with network. A
253
+ sandboxed command has no network and cannot read outside the workspace and the
254
+ system; --local-sandbox-read (or SALAAM_LOCAL_SANDBOX_READ) lists read-only paths
255
+ it may also read, comma-separated, and "toolchain" adds this host's node prefix,
256
+ ~/.npm, ~/.nvm, ~/.cargo, ~/.rustup, ~/go and ~/.gitconfig where present. Home
257
+ itself and credential directories (~/.ssh, ~/.aws, keychains …) are refused. On
258
+ macOS commands get the real git from the active developer dir. A host with no
259
+ usable sandbox refuses commands unless --unsandboxed-commands allow.
260
+
261
+ Without --agentpod-config, start binds to ~/.config/salaam/agentpod-pins.json
262
+ ($XDG_CONFIG_HOME/salaam) when that file exists.
263
+
215
264
  The default provider is offline. Anthropic defaults to claude-sonnet-5-5.
216
265
  Other providers require --model; use salaam providers list and salaam models list PROVIDER.
217
266
  Start with --foreground for process-supervisor use; otherwise the service detaches.
@@ -219,7 +268,8 @@ An AgentPod daemon requires node-bound admissions and a fresh authority lease.
219
268
  Keep its private state outside the pinned workspace. Coding, external runtimes,
220
269
  effect connectors and managed MCP are unavailable in this profile.`,
221
270
  session: `salaam session create [--cwd DIR] [--request-id ID] [--coding | --local | --local-coding]
222
- --local-coding allows reviewed edits and native commands (no OS sandbox)
271
+ --local-coding allows reviewed edits and native commands, each under the host OS sandbox
272
+ (see salaam help service for levels and read paths)
223
273
  --local allows bounded local file inspection; optionally select --provider ID --model ID
224
274
  salaam session list
225
275
  salaam session inspect SESSION_ID
@@ -272,6 +322,8 @@ const globalOptions = `Global options:
272
322
  -h, --help Show help without starting a service or model request.
273
323
  -V, --version Show the installed version.`;
274
324
  export function cliHelp(group) {
325
+ if (group === 'here')
326
+ group = 'code';
275
327
  if (group)
276
328
  return commands[group] ? `${commands[group]}\n\n${globalOptions}` : undefined;
277
329
  return `Salaam — persistent coding sessions
@@ -280,6 +332,7 @@ Usage: salaam <command> [options]
280
332
  sal <command> [options]
281
333
 
282
334
  Commands:
335
+ code Work in this folder: continue its session or start one (also: here, -c)
283
336
  tui Open the Salaam terminal interface (also bare sal)
284
337
  remote Open the same session over SSH
285
338
  providers List pinned Pi providers and capabilities
@@ -287,6 +340,7 @@ Commands:
287
340
  auth Configure provider credentials or OAuth
288
341
  service Start, inspect or stop the session service
289
342
  session Create, inspect, submit, resume or cancel work
343
+ mcp Declare operator MCP servers (add, list, rm)
290
344
  attach Open an interactive terminal on a session
291
345
  acp Connect an editor over ACP v1 stdio
292
346
  approvals Inspect, approve or deny managed operations
@@ -357,6 +411,7 @@ const valueOptions = new Set([
357
411
  '--a2a-config',
358
412
  '--agentpod-config',
359
413
  '--a2a-listen-config',
414
+ '--tools-user',
360
415
  '--assignment',
361
416
  '--peer',
362
417
  '--peer-client',
package/dist/src/cli.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import { loadDangerClassifierOptions } from './danger-classifier.js';
3
+ import { loadWebSearchConfig } from './web-search.js';
4
+ import { SANDBOX_LEVELS } from './command-sandbox.js';
2
5
  import { acp } from './acp.js';
3
6
  import { homedir } from 'node:os';
4
7
  import { join, resolve, dirname } from 'node:path';
@@ -6,9 +9,12 @@ import { spawn } from 'node:child_process';
6
9
  import { randomUUID, randomBytes } from 'node:crypto';
7
10
  import { digest, object } from './protocol.js';
8
11
  import { fileURLToPath } from 'node:url';
9
- import { mkdirSync, openSync, closeSync, readFileSync, realpathSync, writeFileSync, fsyncSync, } from 'node:fs';
12
+ import { mkdirSync, openSync, closeSync, readFileSync, realpathSync, writeFileSync, fsyncSync, existsSync, } from 'node:fs';
10
13
  import { VERSION } from './config.js';
11
14
  import { cliHelp, hasHelpFlag } from './cli-help.js';
15
+ import { mcpCli, MCP_HELP } from './operator-mcp-cli.js';
16
+ import { operatorMcpPath, readOperatorMcpFile } from './operator-mcp.js';
17
+ import { defaultAgentPodConfig } from './agentpod-default-config.js';
12
18
  import { SalaamService } from './service.js';
13
19
  import { call } from './transport.js';
14
20
  import { attach, acquireControl, Outbox } from './attach.js';
@@ -29,6 +35,21 @@ async function main() {
29
35
  console.log('salaam ' + VERSION);
30
36
  return;
31
37
  }
38
+ if (args[0] === '-c' || args[0] === '--continue') {
39
+ const { continueArgs } = await import('./session-pick.js');
40
+ args.splice(0, args.length, ...continueArgs(args));
41
+ }
42
+ if (args[0] === 'mcp') {
43
+ // Parsed before global help: a declared command's own flags follow "--".
44
+ const rest = args.slice(1);
45
+ const dash = rest.indexOf('--');
46
+ const flags = dash < 0 ? rest : rest.slice(0, dash);
47
+ if (!rest.length || flags.includes('--help') || flags.includes('-h'))
48
+ console.log(MCP_HELP);
49
+ else
50
+ console.log(mcpCli(rest));
51
+ return;
52
+ }
32
53
  if ((!args.length && !(process.stdin.isTTY && process.stdout.isTTY)) ||
33
54
  args[0] === 'help' ||
34
55
  hasHelpFlag(args)) {
@@ -388,7 +409,7 @@ async function main() {
388
409
  return;
389
410
  }
390
411
  if (group === 'host') {
391
- const { planHost, readHost, hostUnit, runHost } = await import('./host.js');
412
+ const { planHost, readHost, hostUnit, runHost, hostReceipt, hostToolsUser, hostApprovalTtlMs, hostAllowsUnsandboxed, hostAllowsFullAccessSessions, hostAllowsYoloSessions, hostDangerClassifier, hostCommandNetwork, hostReadPaths, } = await import('./host.js');
392
413
  if (verb?.startsWith('config-')) {
393
414
  const file = option('--manifest');
394
415
  if (!file)
@@ -413,6 +434,42 @@ async function main() {
413
434
  console.log(JSON.stringify(await planHost(JSON.parse(readFileSync(resolve(file), 'utf8'))), null, 2));
414
435
  return;
415
436
  }
437
+ if (verb === 'bind') {
438
+ const { bindHost } = await import('./host-bind.js');
439
+ const required = (name) => {
440
+ const v = option(name);
441
+ if (!v)
442
+ throw new Error('Supply ' + name);
443
+ return v;
444
+ };
445
+ console.log(JSON.stringify(await bindHost({
446
+ organization_id: required('--organization'),
447
+ node_id: required('--node'),
448
+ station_key: required('--station-key'),
449
+ workspace: required('--workspace'),
450
+ tools_user: required('--tools-user'),
451
+ files: required('--files'),
452
+ runtime_root: required('--runtime-root'),
453
+ runtime_image_digest: required('--runtime-image-digest'),
454
+ operator_group: option('--operator-group'),
455
+ dir: option('--dir'),
456
+ state: explicitState ? state : undefined,
457
+ provider: option('--provider'),
458
+ model: option('--model'),
459
+ mcp_config: option('--mcp-config'),
460
+ persona: option('--persona'),
461
+ managed_mcp_config: option('--managed-mcp-config'),
462
+ })));
463
+ return;
464
+ }
465
+ if (verb === 'unit-start') {
466
+ const { startHostUnit } = await import('./host-start.js');
467
+ const unit = option('--unit');
468
+ if (!unit || !explicitState)
469
+ throw new Error('Supply --unit UNIT and --state DIR');
470
+ console.log(JSON.stringify(await startHostUnit(unit, state)));
471
+ return;
472
+ }
416
473
  const file = option('--manifest');
417
474
  if (!file)
418
475
  throw new Error('Supply --manifest FILE');
@@ -462,9 +519,32 @@ async function main() {
462
519
  : {}),
463
520
  ...(m.schema === 5 || m.schema === 6 ? { stationConfig: m.station_execution } : {}),
464
521
  ...(m.schema === 6 ? { agentpodWorkProgression: true } : {}),
522
+ ...(m.schema === 6 && m.operator_access
523
+ ? { operatorAccess: m.operator_access }
524
+ : {}),
525
+ ...(m.schema === 6 && m.operator_mcp
526
+ ? { operatorMcpConfig: m.operator_mcp.config }
527
+ : {}),
528
+ ...(m.schema === 6 && m.persona ? { personaConfig: m.persona } : {}),
465
529
  }),
466
530
  hostManifestDigest: digest(m),
531
+ hostReceipt: hostReceipt(m),
532
+ toolsUser: hostToolsUser(m),
467
533
  providerRoute: m.provider,
534
+ approvalTtlMs: hostApprovalTtlMs(m),
535
+ ...(hostAllowsUnsandboxed(m) ? { unsandboxedCommands: 'allow' } : {}),
536
+ // A host session may choose full access only when the reviewed manifest says so.
537
+ allowFullAccessSessions: hostAllowsFullAccessSessions(m),
538
+ allowYoloSessions: hostAllowsYoloSessions(m),
539
+ // The command reviewer only as the reviewed manifest names it (forbidden when absent).
540
+ ...(hostDangerClassifier(m).config
541
+ ? { dangerClassifierConfig: hostDangerClassifier(m).config }
542
+ : {}),
543
+ ...(hostDangerClassifier(m).pin
544
+ ? { dangerClassifierPin: hostDangerClassifier(m).pin }
545
+ : {}),
546
+ ...(hostCommandNetwork(m) ? { commandNetwork: hostCommandNetwork(m) } : {}),
547
+ ...(hostReadPaths(m).length ? { localSandboxReadPaths: hostReadPaths(m) } : {}),
468
548
  });
469
549
  if (process.connected)
470
550
  process.once('disconnect', () => void service.close());
@@ -493,6 +573,20 @@ async function main() {
493
573
  });
494
574
  return;
495
575
  }
576
+ if (group === 'code' || group === 'here') {
577
+ const { parseCodeStart } = await import('./session-pick.js');
578
+ const start = parseCodeStart(args.slice(1));
579
+ const { tui } = await import('./tui.js');
580
+ await tui({
581
+ state,
582
+ start,
583
+ cwd: option('--cwd'),
584
+ takeover: args.includes('--takeover'),
585
+ codingRoot: option('--coding-root'),
586
+ codingSocket: option('--coding-socket'),
587
+ });
588
+ return;
589
+ }
496
590
  if (!group ||
497
591
  group === 'tui' ||
498
592
  [
@@ -626,7 +720,7 @@ async function main() {
626
720
  return;
627
721
  }
628
722
  if (group === 'acp') {
629
- await acp(state, { coding: args.includes('--coding') });
723
+ await acp(state, { coding: args.includes('--coding'), local: args.includes('--local') });
630
724
  return;
631
725
  }
632
726
  if (group === 'providers' && verb === 'list') {
@@ -1245,17 +1339,41 @@ async function main() {
1245
1339
  }
1246
1340
  const effectSocket = option('--effect-socket');
1247
1341
  const opaque = args.includes('--effect-opaque');
1248
- const codingRoot = option('--coding-root'), codingSocket = option('--coding-socket'), harnessConfig = option('--harness-config'), a2aConfig = option('--a2a-config'), agentpodConfig = option('--agentpod-config'), a2aListenConfig = option('--a2a-listen-config');
1342
+ const codingRoot = option('--coding-root'), codingSocket = option('--coding-socket'), harnessConfig = option('--harness-config'), a2aConfig = option('--a2a-config'), agentpodConfig = option('--agentpod-config') ?? defaultAgentPodConfig(process.env, existsSync), a2aListenConfig = option('--a2a-listen-config'), mcpConfig = resolve(option('--mcp-config') ?? operatorMcpPath(process.env)), toolsUser = option('--tools-user') ?? (process.env.SALAAM_TOOLS_USER || undefined), localSandbox = option('--local-sandbox') ?? (process.env.SALAAM_LOCAL_SANDBOX || undefined), unsandboxedCommands = option('--unsandboxed-commands') ?? (process.env.SALAAM_UNSANDBOXED_COMMANDS || undefined), localSandboxRead = (option('--local-sandbox-read') ??
1343
+ (process.env.SALAAM_LOCAL_SANDBOX_READ || ''))
1344
+ .split(',')
1345
+ .map((p) => p.trim())
1346
+ .filter(Boolean), webSearchConfig = option('--web-search-config') ?? (process.env.SALAAM_WEB_SEARCH_CONFIG || undefined), dangerClassifierConfig = option('--danger-classifier-config') ??
1347
+ (process.env.SALAAM_DANGER_CLASSIFIER_CONFIG || undefined);
1348
+ // Fail early on a bad command-reviewer config; its key is read from the named variable, never printed.
1349
+ if (dangerClassifierConfig)
1350
+ loadDangerClassifierOptions(JSON.parse(readFileSync(resolve(dangerClassifierConfig), 'utf8')));
1351
+ // Fail early on a bad web search config; its keys are read, never printed.
1352
+ if (webSearchConfig)
1353
+ loadWebSearchConfig(resolve(webSearchConfig));
1354
+ if (localSandbox !== undefined && !SANDBOX_LEVELS.includes(localSandbox))
1355
+ throw Error('--local-sandbox is read-only, workspace-write or full-access');
1356
+ if (unsandboxedCommands !== undefined && unsandboxedCommands !== 'allow')
1357
+ throw Error('--unsandboxed-commands is allow or absent');
1249
1358
  if (foreground) {
1250
1359
  const service = await SalaamService.open({
1251
1360
  stateDir: state,
1252
1361
  providerRoute: route,
1253
1362
  agentpodConfig: agentpodConfig ? resolve(agentpodConfig) : undefined,
1363
+ operatorMcpConfig: mcpConfig,
1254
1364
  codingRoot: codingRoot ? resolve(codingRoot) : undefined,
1255
1365
  codingSocket: codingSocket ? resolve(codingSocket) : undefined,
1256
1366
  harnessConfig: harnessConfig ? resolve(harnessConfig) : undefined,
1257
1367
  a2aConfig: a2aConfig ? resolve(a2aConfig) : undefined,
1258
1368
  a2aListenConfig: a2aListenConfig ? resolve(a2aListenConfig) : undefined,
1369
+ toolsUser,
1370
+ ...(localSandbox ? { localSandbox: localSandbox } : {}),
1371
+ ...(unsandboxedCommands ? { unsandboxedCommands: 'allow' } : {}),
1372
+ ...(localSandboxRead.length ? { localSandboxReadPaths: localSandboxRead } : {}),
1373
+ ...(webSearchConfig ? { webSearchConfig: resolve(webSearchConfig) } : {}),
1374
+ ...(dangerClassifierConfig
1375
+ ? { dangerClassifierConfig: resolve(dangerClassifierConfig) }
1376
+ : {}),
1259
1377
  effectConnector: effectSocket
1260
1378
  ? new SocketEffectConnector(resolve(effectSocket), { opaque })
1261
1379
  : undefined,
@@ -1263,6 +1381,9 @@ async function main() {
1263
1381
  const warning = service.supervisor.providerCapabilities().warning;
1264
1382
  if (warning)
1265
1383
  console.error(warning);
1384
+ // Only a live provider has a credential to share; the offline proof route stays quiet.
1385
+ if (service.toolIdentity.warning && route.provider !== 'salaam-proof')
1386
+ console.error('warning: ' + service.toolIdentity.warning);
1266
1387
  process.stdout.write(JSON.stringify({ socket, service: 'started' }) + '\n');
1267
1388
  for (const signal of ['SIGINT', 'SIGTERM'])
1268
1389
  process.once(signal, () => {
@@ -1290,6 +1411,11 @@ async function main() {
1290
1411
  if ((health.coding_root ?? null) !== (codingRoot ? resolve(codingRoot) : null) ||
1291
1412
  (health.coding_socket ?? null) !== (codingSocket ? resolve(codingSocket) : null))
1292
1413
  throw new Error('Service is already running with another coding configuration; stop it first');
1414
+ if ((health.operator_mcp?.config ?? null) !== mcpConfig)
1415
+ throw new Error('Service is already running with another MCP config; stop it first');
1416
+ if (health.operator_mcp &&
1417
+ health.operator_mcp.digest !== digest(readOperatorMcpFile(mcpConfig)))
1418
+ console.error('Declared MCP servers changed since the service started; run salaam service stop, then start, to apply them');
1293
1419
  console.log('Salaam service already running');
1294
1420
  return;
1295
1421
  }
@@ -1315,6 +1441,16 @@ async function main() {
1315
1441
  ...(a2aConfig ? ['--a2a-config', resolve(a2aConfig)] : []),
1316
1442
  ...(agentpodConfig ? ['--agentpod-config', resolve(agentpodConfig)] : []),
1317
1443
  ...(a2aListenConfig ? ['--a2a-listen-config', resolve(a2aListenConfig)] : []),
1444
+ '--mcp-config',
1445
+ mcpConfig,
1446
+ ...(toolsUser ? ['--tools-user', toolsUser] : []),
1447
+ ...(localSandbox ? ['--local-sandbox', localSandbox] : []),
1448
+ ...(unsandboxedCommands ? ['--unsandboxed-commands', 'allow'] : []),
1449
+ ...(localSandboxRead.length ? ['--local-sandbox-read', localSandboxRead.join(',')] : []),
1450
+ ...(webSearchConfig ? ['--web-search-config', resolve(webSearchConfig)] : []),
1451
+ ...(dangerClassifierConfig
1452
+ ? ['--danger-classifier-config', resolve(dangerClassifierConfig)]
1453
+ : []),
1318
1454
  ], { detached: true, stdio: ['ignore', fd, fd] });
1319
1455
  closeSync(fd);
1320
1456
  child.unref();