svamp-cli 0.2.120 → 0.2.122

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.
@@ -58,7 +58,7 @@ async function serviceExpose(args) {
58
58
  process.exit(1);
59
59
  }
60
60
  if (foreground) {
61
- const { runFrpcTunnel } = await import('./frpc-DlsBjcRf.mjs');
61
+ const { runFrpcTunnel } = await import('./frpc-1YwDchv1.mjs');
62
62
  await runFrpcTunnel(name, ports, void 0, {
63
63
  group,
64
64
  groupKey,
@@ -68,7 +68,7 @@ async function serviceExpose(args) {
68
68
  });
69
69
  return;
70
70
  }
71
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
71
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
72
72
  const { server, machine } = await connectAndGetMachine();
73
73
  try {
74
74
  const status = await machine.tunnelStart({
@@ -123,7 +123,7 @@ async function serviceServe(args) {
123
123
  };
124
124
  process.on("SIGINT", cleanup);
125
125
  process.on("SIGTERM", cleanup);
126
- const { runFrpcTunnel } = await import('./frpc-DlsBjcRf.mjs');
126
+ const { runFrpcTunnel } = await import('./frpc-1YwDchv1.mjs');
127
127
  await runFrpcTunnel(name, [caddyPort]);
128
128
  } catch (err) {
129
129
  console.error(`Error serving directory: ${err.message}`);
@@ -132,7 +132,7 @@ async function serviceServe(args) {
132
132
  }
133
133
  async function serviceList(_args) {
134
134
  try {
135
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
135
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
136
136
  const { server, machine } = await connectAndGetMachine();
137
137
  try {
138
138
  const tunnels = await machine.tunnelList({});
@@ -161,7 +161,7 @@ async function serviceDelete(args) {
161
161
  process.exit(1);
162
162
  }
163
163
  try {
164
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
164
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
165
165
  const { server, machine } = await connectAndGetMachine();
166
166
  try {
167
167
  await machine.tunnelStop({ name });
@@ -1,6 +1,7 @@
1
- import { execFileSync } from 'node:child_process';
1
+ import { execSync, execFileSync } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { createServer } from 'node:http';
3
- import { R as RoutineStore, m as RoutineRunner } from './run-C4BsPJ_p.mjs';
4
+ import { E as readChecklist, F as compileChecklist, G as RoutineStore, H as RoutineRunner } from './run-PlINWUZp.mjs';
4
5
  import 'os';
5
6
  import 'fs/promises';
6
7
  import 'fs';
@@ -8,7 +9,6 @@ import 'path';
8
9
  import 'url';
9
10
  import 'child_process';
10
11
  import 'crypto';
11
- import 'node:crypto';
12
12
  import 'node:fs';
13
13
  import 'util';
14
14
  import 'node:path';
@@ -22,6 +22,41 @@ import 'zod';
22
22
  import 'node:fs/promises';
23
23
  import 'node:util';
24
24
 
25
+ function runOracle(cmd, cwd, timeoutSec = 600) {
26
+ try {
27
+ execSync(cmd, { cwd, stdio: "pipe", maxBuffer: 16 * 1024 * 1024, timeout: timeoutSec * 1e3 });
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+ function itemOracle(item) {
34
+ if (item.eval?.type === "oracle" && item.eval.cmd?.trim()) return item.eval.cmd;
35
+ const legacy = item.oracle;
36
+ return typeof legacy === "string" && legacy.trim() ? legacy : void 0;
37
+ }
38
+ function verifyChecklistOracles(projectRoot, sessionId, timeoutSec = 600) {
39
+ const items = readChecklist(projectRoot, sessionId).filter((i) => i.disposition !== "delegated" && i.status !== "done");
40
+ const criteria = compileChecklist(items).criteria;
41
+ const failing = [];
42
+ let oracleTotal = 0, oraclePass = 0, agentSkipped = 0;
43
+ for (const it of items) {
44
+ const cmd = itemOracle(it);
45
+ if (cmd) {
46
+ oracleTotal++;
47
+ if (runOracle(cmd, projectRoot, timeoutSec)) oraclePass++;
48
+ else failing.push({ id: it.id, text: it.text });
49
+ } else if (it.eval?.type === "agent") {
50
+ agentSkipped++;
51
+ }
52
+ }
53
+ const verdict = failing.length === 0 ? "approved" : "rework";
54
+ const parts = [`Watchdog: ${oraclePass}/${oracleTotal} oracle check${oracleTotal === 1 ? "" : "s"} pass.`];
55
+ if (failing.length) parts.push(`Failing: ${failing.map((f) => f.text).join("; ")}.`);
56
+ if (agentSkipped) parts.push(`${agentSkipped} agent-judged item(s) need a live session (not checked).`);
57
+ return { verdict, criteria, guidance: parts.join(" "), oracleTotal, oraclePass, failing, agentSkipped, itemCount: items.length };
58
+ }
59
+
25
60
  function parseArgs(argv) {
26
61
  const a = { _: [] };
27
62
  for (let i = 0; i < argv.length; i++) {
@@ -41,6 +76,43 @@ function isSessionLive(sid) {
41
76
  }
42
77
  }
43
78
  async function cliDeliver({ routine, resolved }) {
79
+ if (resolved.kind === "verify") {
80
+ const dir = routine.dir;
81
+ if (!dir) {
82
+ console.error(`[trigger] skip ${routine.id || routine.name}: verify action has no dir`);
83
+ return { skipped: "verify: no dir" };
84
+ }
85
+ const target = routine.action.verify?.target_session || routine.session_id;
86
+ const reportTo = routine.action.verify?.report_to || routine.session_id;
87
+ if (!isSessionLive(reportTo)) {
88
+ console.error(`[trigger] skip ${routine.id || routine.name}: report session ${reportTo} not live (inbox is in-memory)`);
89
+ return { skipped: "verify: report session not live" };
90
+ }
91
+ const res = verifyChecklistOracles(dir, target);
92
+ const message = {
93
+ messageId: randomUUID(),
94
+ from: `watchdog:${routine.name || routine.id}`,
95
+ fromSession: target,
96
+ to: reportTo,
97
+ subject: `watchdog: ${res.verdict} \u2014 ${routine.name || routine.id}`,
98
+ body: `<supervision-verdict source="watchdog" session="${target}" verdict="${res.verdict}" judge="oracle" round="0">
99
+ ${res.guidance}
100
+ Criteria: ${res.criteria || "(none)"}
101
+ </supervision-verdict>`,
102
+ timestamp: Date.now(),
103
+ read: false,
104
+ urgency: "normal",
105
+ hopCount: 1
106
+ };
107
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
108
+ const { server, machine } = await connectAndGetMachine();
109
+ try {
110
+ await machine.sessionRPC(reportTo, "sendInboxMessage", { message });
111
+ } finally {
112
+ await server.disconnect();
113
+ }
114
+ return;
115
+ }
44
116
  if (routine.bind === "stateless") {
45
117
  const dir = routine.dir;
46
118
  if (!dir) {
@@ -71,7 +143,8 @@ function buildRoutineFromArgs(a) {
71
143
  if (a.schedule) r.trigger = { type: "schedule", cron: a.schedule, missed: a.missed || "skip", tz: a.tz };
72
144
  else if (a.webhook || a.api) r.trigger = { type: a.api ? "api" : "webhook", methods: a.get ? ["GET"] : ["GET", "POST"], public: !!a.public };
73
145
  else r.trigger = { type: "manual" };
74
- if (a.loop) r.action = { kind: "loop", task_template: a.task || "", loop: { dir: a.dir, oracle: a.oracle, evaluator: a.evaluator || "off" } };
146
+ if (a.verify) r.action = { kind: "verify", verify: { target_session: a["target-session"], report_to: a["report-to"] } };
147
+ else if (a.loop) r.action = { kind: "loop", task_template: a.task || "", loop: { dir: a.dir, oracle: a.oracle, evaluator: a.evaluator || "off" } };
75
148
  else r.action = { kind: "message", template: a.message || a.task || "run" };
76
149
  if (a.dir) r.dir = a.dir;
77
150
  if (a.stateless) r.bind = "stateless";
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import os from 'node:os';
4
- import { c as connectToHypha } from './run-C4BsPJ_p.mjs';
4
+ import { c as connectToHypha } from './run-PlINWUZp.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-HydRNEt7.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -3,7 +3,7 @@ import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSy
3
3
  import { join } from 'path';
4
4
  import { homedir, platform, arch } from 'os';
5
5
  import { createHash, randomUUID } from 'crypto';
6
- import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-C4BsPJ_p.mjs';
6
+ import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-PlINWUZp.mjs';
7
7
  import 'fs/promises';
8
8
  import 'url';
9
9
  import 'node:crypto';
@@ -1,5 +1,5 @@
1
- import { F as resolveModel, T as describeMisconfiguration, U as buildMachineDeps } from './run-C4BsPJ_p.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-Wfli3n7U.mjs';
1
+ import { D as resolveModel, V as describeMisconfiguration, W as buildMachineDeps } from './run-PlINWUZp.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-DF2mZOOx.mjs';
3
3
  import { WebSocket } from 'ws';
4
4
  import { execSync, spawn } from 'child_process';
5
5
  import 'os';
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-C4BsPJ_p.mjs';
1
+ export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-PlINWUZp.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,5 +1,5 @@
1
1
  var name = "svamp-cli";
2
- var version = "0.2.120";
2
+ var version = "0.2.121";
3
3
  var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
4
  var author = "Amun AI AB";
5
5
  var license = "SEE LICENSE IN LICENSE";
@@ -19,7 +19,7 @@ var exports$1 = {
19
19
  var scripts = {
20
20
  build: "rm -rf dist bin/skills && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/loop bin/skills/loop && cp -r ../../skills/crew bin/skills/crew && tsc --noEmit && pkgroll",
21
21
  typecheck: "tsc --noEmit",
22
- test: "npx tsx test/test-context-window.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-loop-activation.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
22
+ test: "npx tsx test/test-context-window.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-loop-activation.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
23
23
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
24
  dev: "tsx src/cli.ts",
25
25
  "dev:daemon": "tsx src/cli.ts daemon start-sync",
@@ -1199,7 +1199,7 @@ function renderTemplate(template, ctx) {
1199
1199
  });
1200
1200
  }
1201
1201
  const TRIGGER_TYPES = ["manual", "schedule", "webhook", "api"];
1202
- const ACTION_KINDS = ["message", "loop"];
1202
+ const ACTION_KINDS = ["message", "loop", "verify"];
1203
1203
  const OVERLAP = ["queue", "skip", "replace"];
1204
1204
  function validateRoutine(r) {
1205
1205
  const errs = [];
@@ -1220,6 +1220,7 @@ function validateRoutine(r) {
1220
1220
  if (!a || !ACTION_KINDS.includes(a.kind)) errs.push(`action.kind must be one of ${ACTION_KINDS.join("|")}`);
1221
1221
  if (a?.kind === "message" && !a.template) errs.push("action.template required for message action");
1222
1222
  if (a?.kind === "loop" && !a.loop && !a.task_template) errs.push("action.loop or action.task_template required for loop action");
1223
+ if (a?.kind === "verify" && !r.dir) errs.push("a verify (checklist watchdog) action requires a dir (the project root holding .svamp/<sid>/loop/checklist.json)");
1223
1224
  if (r.overlap && !OVERLAP.includes(r.overlap)) errs.push(`overlap must be one of ${OVERLAP.join("|")}`);
1224
1225
  if (r.bind && r.bind !== "stateful" && r.bind !== "stateless") errs.push('bind must be "stateful" or "stateless"');
1225
1226
  if (r.bind === "stateless") {
@@ -2676,7 +2677,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2676
2677
  const tunnels = handlers.tunnels;
2677
2678
  if (!tunnels) throw new Error("Tunnel management not available");
2678
2679
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2679
- const { FrpcTunnel } = await import('./frpc-DlsBjcRf.mjs');
2680
+ const { FrpcTunnel } = await import('./frpc-1YwDchv1.mjs');
2680
2681
  const tunnel = new FrpcTunnel({
2681
2682
  name: params.name,
2682
2683
  ports: params.ports,
@@ -2937,7 +2938,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
2937
2938
  }
2938
2939
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
2939
2940
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
2940
- const { toolsForRole } = await import('./sideband-Wfli3n7U.mjs');
2941
+ const { toolsForRole } = await import('./sideband-DF2mZOOx.mjs');
2941
2942
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
2942
2943
  return fmt(r2);
2943
2944
  }
@@ -3036,7 +3037,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3036
3037
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3037
3038
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3038
3039
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3039
- const { queryCore } = await import('./commands-CuY9G_88.mjs');
3040
+ const { queryCore } = await import('./commands-BKrGv74d.mjs');
3040
3041
  const timeout = c.reply?.timeout_sec || 120;
3041
3042
  let result;
3042
3043
  try {
@@ -3113,9 +3114,6 @@ ${d?.error || "not found"}`;
3113
3114
  trackInbound();
3114
3115
  const res = resolveChannel(kwargs.channel, kwargs.session);
3115
3116
  if ("error" in res) return { error: res.error };
3116
- if (res.tier === "session" && !("stopped" in res)) {
3117
- return res.rpc.channelReceive({ channel: kwargs.channel, key: kwargs.key, from: kwargs.from, cursor: kwargs.cursor, correlationId: kwargs.correlationId, wait: kwargs.wait }, context);
3118
- }
3119
3117
  const { c, dir } = res;
3120
3118
  if (c.reply?.mode !== "queue") {
3121
3119
  return { error: "this channel has no async replies (not a queue-mode channel)" };
@@ -3130,9 +3128,16 @@ ${d?.error || "not found"}`;
3130
3128
  });
3131
3129
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3132
3130
  const cursor = Math.max(0, Number(kwargs.cursor) || 0);
3133
- const outbox = new ChannelOutbox(dir);
3134
- const replies = outbox.since(c.id, cursor, r.sender.name, kwargs.correlationId);
3135
- return { ok: true, replies, cursor: outbox.cursor(c.id) };
3131
+ const waitMs = Math.min(Math.max(0, Number(kwargs.wait ?? 25) * 1e3), 6e4);
3132
+ const deadline = Date.now() + waitMs;
3133
+ for (; ; ) {
3134
+ const outbox = new ChannelOutbox(dir);
3135
+ const replies = outbox.since(c.id, cursor, r.sender.name, kwargs.correlationId);
3136
+ if (replies.length || Date.now() >= deadline) {
3137
+ return { ok: true, replies, cursor: outbox.cursor(c.id) };
3138
+ }
3139
+ await new Promise((resolve) => setTimeout(resolve, 500));
3140
+ }
3136
3141
  }
3137
3142
  },
3138
3143
  { overwrite: true }
@@ -3257,6 +3262,7 @@ class RoutineRunner {
3257
3262
  const ctx = { ...payload, now: this.now().toISOString(), routine: { id: routine.id, name: routine.name } };
3258
3263
  const a = routine.action;
3259
3264
  if (a.kind === "message") return { kind: "message", text: renderTemplate(a.template || "", ctx) };
3265
+ if (a.kind === "verify") return { kind: "verify" };
3260
3266
  const task = renderTemplate(a.task_template || a.loop?.task || "", ctx);
3261
3267
  return { kind: "loop", task, loop: a.loop };
3262
3268
  }
@@ -9686,6 +9692,118 @@ class ProcessSupervisor {
9686
9692
  }
9687
9693
  }
9688
9694
 
9695
+ const STATUS_TO_MARKER = {
9696
+ todo: " ",
9697
+ active: "~",
9698
+ verifying: "*",
9699
+ awaiting_review: "?",
9700
+ rework: "r",
9701
+ blocked: "!",
9702
+ done: "x"
9703
+ };
9704
+ function renderCriteria(items) {
9705
+ if (!items.length) return "";
9706
+ const lines = items.map((it) => {
9707
+ const tag = it.disposition === "delegated" ? " (delegated)" : "";
9708
+ return `- [${STATUS_TO_MARKER[it.status] ?? " "}] ${it.text}${tag}`;
9709
+ });
9710
+ const { done, total } = summarize(items);
9711
+ return `Success criteria \u2014 drive this checklist to all-done (${done}/${total}):
9712
+ ${lines.join("\n")}`;
9713
+ }
9714
+ function compileChecklist(items) {
9715
+ const criteria = renderCriteria(items);
9716
+ const oracleItem = items.find((i) => i.disposition === "inline" && i.eval?.type === "oracle");
9717
+ const oracle = oracleItem && oracleItem.eval?.type === "oracle" ? oracleItem.eval.cmd : void 0;
9718
+ return { criteria, ...oracle ? { oracle } : {} };
9719
+ }
9720
+ function routeVerdictToChecklist(items, v) {
9721
+ let matched = false;
9722
+ const next = items.map((it) => {
9723
+ if (it.child?.sessionId && it.child.sessionId === v.sessionId) {
9724
+ matched = true;
9725
+ return applyVerdict(it, v);
9726
+ }
9727
+ return it;
9728
+ });
9729
+ return { items: next, matched };
9730
+ }
9731
+ function applyVerdict(item, v) {
9732
+ const lastVerdict = { verdict: v.verdict, ...v.guidance ? { guidance: v.guidance } : {}, round: v.round, ts: v.ts };
9733
+ if (v.verdict === "approved") return { ...item, status: "done", doneAt: Date.parse(v.ts) || item.createdAt, lastVerdict };
9734
+ return { ...item, status: "rework", lastVerdict };
9735
+ }
9736
+ const VALID_STATUS = /* @__PURE__ */ new Set(["todo", "active", "verifying", "awaiting_review", "rework", "blocked", "done"]);
9737
+ function validateChecklist(items) {
9738
+ const errs = [];
9739
+ const ids = /* @__PURE__ */ new Set();
9740
+ for (const it of items) {
9741
+ if (!it.id) errs.push("item missing id");
9742
+ else if (ids.has(it.id)) errs.push(`duplicate item id "${it.id}"`);
9743
+ else ids.add(it.id);
9744
+ if (!it.text || !it.text.trim()) errs.push(`item "${it.id}" has empty text`);
9745
+ if (it.disposition !== "inline" && it.disposition !== "delegated") errs.push(`item "${it.id}" bad disposition`);
9746
+ if (!VALID_STATUS.has(it.status)) errs.push(`item "${it.id}" bad status "${it.status}"`);
9747
+ if (it.eval?.type === "oracle" && !it.eval.cmd.trim()) errs.push(`item "${it.id}" oracle eval needs a cmd`);
9748
+ }
9749
+ return errs;
9750
+ }
9751
+ function summarize(items) {
9752
+ const by = (s) => items.filter((i) => i.status === s).length;
9753
+ const done = by("done");
9754
+ return {
9755
+ total: items.length,
9756
+ done,
9757
+ todo: by("todo"),
9758
+ active: by("active"),
9759
+ blocked: by("blocked"),
9760
+ awaiting_review: by("awaiting_review"),
9761
+ delegated: items.filter((i) => i.disposition === "delegated").length,
9762
+ allDone: items.length > 0 && done === items.length
9763
+ };
9764
+ }
9765
+ function checklistPath(projectRoot, sessionId) {
9766
+ return join(projectRoot, ".svamp", sessionId, "loop", "checklist.json");
9767
+ }
9768
+ function legacyChecklistPath(projectRoot, sessionId) {
9769
+ return join(projectRoot, ".svamp", sessionId, "checklist.json");
9770
+ }
9771
+
9772
+ function readChecklist(projectRoot, sessionId) {
9773
+ let p = checklistPath(projectRoot, sessionId);
9774
+ if (!existsSync(p)) {
9775
+ const legacy = legacyChecklistPath(projectRoot, sessionId);
9776
+ if (!existsSync(legacy)) return [];
9777
+ p = legacy;
9778
+ }
9779
+ try {
9780
+ const parsed = JSON.parse(readFileSync(p, "utf8"));
9781
+ return Array.isArray(parsed?.items) ? parsed.items : [];
9782
+ } catch {
9783
+ return [];
9784
+ }
9785
+ }
9786
+ function writeChecklist(projectRoot, sessionId, items) {
9787
+ const p = checklistPath(projectRoot, sessionId);
9788
+ mkdirSync(dirname(p), { recursive: true });
9789
+ const file = { version: 1, items, updatedAt: Date.now() };
9790
+ const tmp = p + ".tmp";
9791
+ writeFileSync(tmp, JSON.stringify(file, null, 2));
9792
+ renameSync(tmp, p);
9793
+ }
9794
+ function clearChecklist(projectRoot, sessionId) {
9795
+ const p = checklistPath(projectRoot, sessionId);
9796
+ try {
9797
+ if (existsSync(p)) rmSync(p);
9798
+ } catch {
9799
+ }
9800
+ }
9801
+ function mutateChecklist(projectRoot, sessionId, fn) {
9802
+ const next = fn(readChecklist(projectRoot, sessionId));
9803
+ writeChecklist(projectRoot, sessionId, next);
9804
+ return next;
9805
+ }
9806
+
9689
9807
  const OAUTH_TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
9690
9808
  const OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
9691
9809
  const OAUTH_SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
@@ -10778,6 +10896,38 @@ Continue working until they are met, or verify and finish \u2014 an independent
10778
10896
  const { supervisor: _s, ...restPatch } = patch;
10779
10897
  patch = restPatch;
10780
10898
  }
10899
+ if ("checklist" in patch) {
10900
+ const raw = patch.checklist;
10901
+ const items = Array.isArray(raw) ? raw : [];
10902
+ if (items.length) {
10903
+ const errs = validateChecklist(items);
10904
+ if (errs.length) {
10905
+ sessionService.pushMessage({ type: "message", message: `Checklist rejected: ${errs.join("; ")}`, level: "error" }, "event");
10906
+ } else {
10907
+ writeChecklist(directory, sessionId, items);
10908
+ const { criteria, oracle } = compileChecklist(items);
10909
+ const ok = initLoop(directory, { task: criteria, criteria, oracle, maxIterations: 20, evaluator: true, sessionId });
10910
+ const s = summarize(items);
10911
+ if (ok) {
10912
+ const idle = getMetadata().lifecycleState === "idle";
10913
+ if (idle) {
10914
+ const q = getMetadata().messageQueue || [];
10915
+ const nudge = `Your goal checklist was updated (${s.done}/${s.total} done). Work the open items in order; an independent Stop gate re-checks the criteria before you can stop.`;
10916
+ setMetadata((m) => ({ ...m, messageQueue: [...q, { id: randomUUID$1(), text: nudge, displayText: `\u{1F4CB} Checklist updated`, createdAt: Date.now() }] }));
10917
+ onLoopActivated?.();
10918
+ }
10919
+ sessionService.pushMessage({ type: "message", message: `\u{1F4CB} Checklist set \u2014 ${s.total} item${s.total === 1 ? "" : "s"} (${s.delegated} delegated), ${s.done} done.` }, "event");
10920
+ logger.log(`[svampConfig] Checklist set (${s.total} items, ${s.delegated} delegated)`);
10921
+ }
10922
+ }
10923
+ } else {
10924
+ clearChecklist(directory, sessionId);
10925
+ deactivateLoop(directory, sessionId);
10926
+ sessionService.pushMessage({ type: "message", message: "Checklist cleared." }, "event");
10927
+ }
10928
+ const { checklist: _c, ...restPatch } = patch;
10929
+ patch = restPatch;
10930
+ }
10781
10931
  if (Object.keys(patch).length > 0) {
10782
10932
  const config = readSvampConfig(configPath);
10783
10933
  for (const [key, value] of Object.entries(patch)) {
@@ -11205,7 +11355,7 @@ async function startDaemon(options) {
11205
11355
  const list = loadExposedTunnels().filter((t) => t.name !== name);
11206
11356
  saveExposedTunnels(list);
11207
11357
  }
11208
- const { ServeManager } = await import('./serveManager-QZooKtI4.mjs');
11358
+ const { ServeManager } = await import('./serveManager-DymHllAS.mjs');
11209
11359
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
11210
11360
  ensureAutoInstalledSkills(logger).catch(() => {
11211
11361
  });
@@ -11266,6 +11416,13 @@ ${v.guidance ? v.guidance + "\n" : ""}Criteria: ${v.criteria || "(none)"}
11266
11416
  hopCount: 1
11267
11417
  })).catch(() => {
11268
11418
  });
11419
+ try {
11420
+ if (parent?.directory) {
11421
+ const res = mutateChecklist(parent.directory, parentId, (items) => routeVerdictToChecklist(items, { sessionId: v.sessionId, verdict: v.verdict, guidance: v.guidance, round: v.round, ts: v.ts }).items);
11422
+ if (res.some((i) => i.child?.sessionId === v.sessionId)) logger.log(`[supervision] reflected '${v.verdict}' onto parent ${parentId.slice(0, 8)} checklist`);
11423
+ }
11424
+ } catch {
11425
+ }
11269
11426
  logger.log(`[supervision] verdict '${v.verdict}' for ${v.sessionId.slice(0, 8)} \u2192 parent ${parentId.slice(0, 8)}`);
11270
11427
  } catch {
11271
11428
  }
@@ -13874,7 +14031,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13874
14031
  const specs = loadExposedTunnels();
13875
14032
  if (specs.length === 0) return;
13876
14033
  logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
13877
- const { FrpcTunnel } = await import('./frpc-DlsBjcRf.mjs');
14034
+ const { FrpcTunnel } = await import('./frpc-1YwDchv1.mjs');
13878
14035
  for (const spec of specs) {
13879
14036
  if (tunnels.has(spec.name)) continue;
13880
14037
  try {
@@ -14650,4 +14807,4 @@ var run = /*#__PURE__*/Object.freeze({
14650
14807
  writeStopMarker: writeStopMarker
14651
14808
  });
14652
14809
 
14653
- export { GeminiTransport$1 as $, READ_ONLY_TOOLS as A, loadMachineContext as B, buildMachineInstructions as C, machineToolsForRole as D, buildMachineTools as E, resolveModel as F, formatHandle as G, normalizeAllowedUser as H, loadSecurityContextConfig as I, resolveSecurityContext as J, buildSecurityContextFromFlags as K, mergeSecurityContexts as L, buildSessionShareUrl as M, computeOutboundHop as N, buildMachineShareUrl as O, parseHandle as P, handleMatchesMetadata as Q, RoutineStore as R, ServeAuth as S, describeMisconfiguration as T, buildMachineDeps as U, generateHookSettings as V, projectInfo as W, DefaultTransport$1 as X, acpBackend as Y, acpAgentConfig as Z, codexMcpBackend as _, createSessionStore as a, claudeAuth as a0, instanceConfig as a1, api as a2, run as a3, stopDaemon as b, connectToHypha as c, daemonStatus as d, clearStopMarker as e, stopMarkerExists as f, getHyphaServerUrl$1 as g, getFrpsSubdomainHost as h, getFrpsServerPort as i, getFrpsServerAddr as j, getHyphaServerUrl as k, hasCookieToken as l, RoutineRunner as m, shortId as n, getSkillsServer as o, parseFrontmatter as p, getSkillsWorkspaceName as q, registerMachineService as r, startDaemon as s, getSkillsCollectionName as t, fetchWithTimeout as u, searchSkills as v, SKILLS_DIR as w, getSkillInfo as x, downloadSkillFile as y, listSkillFiles as z };
14810
+ export { acpAgentConfig as $, buildMachineInstructions as A, machineToolsForRole as B, buildMachineTools as C, resolveModel as D, readChecklist as E, compileChecklist as F, RoutineStore as G, RoutineRunner as H, formatHandle as I, normalizeAllowedUser as J, loadSecurityContextConfig as K, resolveSecurityContext as L, buildSecurityContextFromFlags as M, mergeSecurityContexts as N, buildSessionShareUrl as O, computeOutboundHop as P, buildMachineShareUrl as Q, READ_ONLY_TOOLS as R, ServeAuth as S, parseHandle as T, handleMatchesMetadata as U, describeMisconfiguration as V, buildMachineDeps as W, generateHookSettings as X, projectInfo as Y, DefaultTransport$1 as Z, acpBackend as _, createSessionStore as a, codexMcpBackend as a0, GeminiTransport$1 as a1, claudeAuth as a2, instanceConfig as a3, api as a4, run as a5, stopDaemon as b, connectToHypha as c, daemonStatus as d, clearStopMarker as e, stopMarkerExists as f, getHyphaServerUrl$1 as g, getFrpsSubdomainHost as h, getFrpsServerPort as i, getFrpsServerAddr as j, getHyphaServerUrl as k, hasCookieToken as l, shortId as m, getSkillsServer as n, getSkillsWorkspaceName as o, parseFrontmatter as p, getSkillsCollectionName as q, registerMachineService as r, startDaemon as s, fetchWithTimeout as t, searchSkills as u, SKILLS_DIR as v, getSkillInfo as w, downloadSkillFile as x, listSkillFiles as y, loadMachineContext as z };
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { n as shortId, c as connectToHypha, a as createSessionStore, r as registerMachineService, V as generateHookSettings } from './run-C4BsPJ_p.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { m as shortId, c as connectToHypha, a as createSessionStore, r as registerMachineService, X as generateHookSettings } from './run-PlINWUZp.mjs';
2
2
  import os from 'node:os';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-CuY9G_88.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-BKrGv74d.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();
@@ -4,7 +4,7 @@ import * as fs from 'fs';
4
4
  import * as http from 'http';
5
5
  import * as net from 'net';
6
6
  import * as path from 'path';
7
- import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-C4BsPJ_p.mjs';
7
+ import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-PlINWUZp.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -713,7 +713,7 @@ class ServeManager {
713
713
  const mount = this.mounts.get(mountName);
714
714
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, `static-${subdomainSafe}-${mount.linkToken}`]]) : void 0;
715
715
  try {
716
- const { FrpcTunnel } = await import('./frpc-DlsBjcRf.mjs');
716
+ const { FrpcTunnel } = await import('./frpc-1YwDchv1.mjs');
717
717
  let tunnel;
718
718
  tunnel = new FrpcTunnel({
719
719
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { A as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-C4BsPJ_p.mjs';
1
+ import { R as READ_ONLY_TOOLS, z as loadMachineContext, A as buildMachineInstructions, B as machineToolsForRole, C as buildMachineTools } from './run-PlINWUZp.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.120",
3
+ "version": "0.2.122",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist bin/skills && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/loop bin/skills/loop && cp -r ../../skills/crew bin/skills/crew && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-loop-activation.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
23
+ "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-loop-activation.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-checklist-watchdog.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",