svamp-cli 0.2.144 → 0.2.145

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.
@@ -0,0 +1,396 @@
1
+ import { existsSync, readFileSync, unlinkSync, mkdirSync, writeFileSync, renameSync, readdirSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+
5
+ const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "branch", "session", "created", "closed"];
6
+ function resolveProjectRoot(start = process.cwd()) {
7
+ let dir = start;
8
+ for (let i = 0; i < 40; i++) {
9
+ if (existsSync(join(dir, ".git")) || existsSync(join(dir, ".svamp"))) return dir;
10
+ const parent = dirname(dir);
11
+ if (parent === dir) break;
12
+ dir = parent;
13
+ }
14
+ return start;
15
+ }
16
+ function issuesDir(projectRoot) {
17
+ return join(projectRoot, ".svamp", "issues");
18
+ }
19
+ function archiveDir(projectRoot) {
20
+ return join(issuesDir(projectRoot), "archive");
21
+ }
22
+ function issuePath(projectRoot, id, archived = false) {
23
+ return join(archived ? archiveDir(projectRoot) : issuesDir(projectRoot), `${id}.md`);
24
+ }
25
+ function serializeIssue(issue) {
26
+ const lines = ["---"];
27
+ for (const k of FIELD_ORDER) {
28
+ const v = issue[k];
29
+ if (v === void 0) continue;
30
+ lines.push(`${k}: ${JSON.stringify(v)}`);
31
+ }
32
+ lines.push("---", "");
33
+ return lines.join("\n") + (issue.body ? issue.body.replace(/\s+$/, "") + "\n" : "");
34
+ }
35
+ function parseIssue(content) {
36
+ const m = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
37
+ if (!m) return null;
38
+ const fm = {};
39
+ for (const line of m[1].split("\n")) {
40
+ const idx = line.indexOf(": ");
41
+ if (idx === -1) continue;
42
+ const key = line.slice(0, idx).trim();
43
+ const raw = line.slice(idx + 2).trim();
44
+ try {
45
+ fm[key] = JSON.parse(raw);
46
+ } catch {
47
+ fm[key] = raw;
48
+ }
49
+ }
50
+ if (!fm.id) return null;
51
+ return {
52
+ id: String(fm.id),
53
+ title: String(fm.title ?? ""),
54
+ status: fm.status ?? "backlog",
55
+ scope: fm.scope ?? "session",
56
+ labels: Array.isArray(fm.labels) ? fm.labels.map(String) : [],
57
+ verify: fm.verify ?? null,
58
+ branch: fm.branch ?? null,
59
+ session: fm.session ?? null,
60
+ created: String(fm.created ?? (/* @__PURE__ */ new Date()).toISOString()),
61
+ closed: fm.closed ?? null,
62
+ body: (m[2] || "").trim() || void 0
63
+ };
64
+ }
65
+ function readDir(dir) {
66
+ if (!existsSync(dir)) return [];
67
+ const out = [];
68
+ for (const name of readdirSync(dir)) {
69
+ if (!name.endsWith(".md")) continue;
70
+ try {
71
+ const issue = parseIssue(readFileSync(join(dir, name), "utf-8"));
72
+ if (issue) out.push(issue);
73
+ } catch {
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ function listIssues(projectRoot, opts = {}) {
79
+ let items = readDir(issuesDir(projectRoot));
80
+ if (opts.includeArchived) items = items.concat(readDir(archiveDir(projectRoot)));
81
+ if (opts.status) items = items.filter((i) => i.status === opts.status);
82
+ if (opts.label) items = items.filter((i) => i.labels.includes(opts.label));
83
+ if (opts.scope) items = items.filter((i) => i.scope === opts.scope);
84
+ return items.sort((a, b) => Number(a.id) - Number(b.id));
85
+ }
86
+ function getIssue(projectRoot, id) {
87
+ const padded = /^\d+$/.test(id) ? id.padStart(4, "0") : id;
88
+ for (const p of [issuePath(projectRoot, padded), issuePath(projectRoot, padded, true)]) {
89
+ if (existsSync(p)) {
90
+ try {
91
+ return parseIssue(readFileSync(p, "utf-8"));
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ function atomicWrite(path, content) {
100
+ mkdirSync(dirname(path), { recursive: true });
101
+ const tmp = `${path}.tmp-${process.pid}`;
102
+ writeFileSync(tmp, content);
103
+ renameSync(tmp, path);
104
+ }
105
+ function nextId(projectRoot) {
106
+ const all = listIssues(projectRoot, { includeArchived: true });
107
+ const max = all.reduce((acc, i) => Math.max(acc, Number(i.id) || 0), 0);
108
+ return String(max + 1).padStart(4, "0");
109
+ }
110
+ function addIssue(projectRoot, fields) {
111
+ const issue = {
112
+ id: nextId(projectRoot),
113
+ title: fields.title.trim(),
114
+ status: fields.status ?? "backlog",
115
+ scope: fields.scope ?? "session",
116
+ labels: fields.labels ?? [],
117
+ verify: fields.verify ?? null,
118
+ branch: null,
119
+ session: fields.session ?? null,
120
+ created: (/* @__PURE__ */ new Date()).toISOString(),
121
+ closed: null,
122
+ body: fields.body
123
+ };
124
+ atomicWrite(issuePath(projectRoot, issue.id), serializeIssue(issue));
125
+ return issue;
126
+ }
127
+ function updateIssue(projectRoot, id, patch) {
128
+ const cur = getIssue(projectRoot, id);
129
+ if (!cur) return null;
130
+ const wasArchived = cur.status === "archived";
131
+ const next = { ...cur, ...patch, id: cur.id };
132
+ const nowArchived = next.status === "archived";
133
+ if ((next.status === "done" || nowArchived) && !next.closed) next.closed = (/* @__PURE__ */ new Date()).toISOString();
134
+ if (next.status !== "done" && !nowArchived) next.closed = null;
135
+ atomicWrite(issuePath(projectRoot, next.id, nowArchived), serializeIssue(next));
136
+ if (wasArchived !== nowArchived) {
137
+ const oldPath = issuePath(projectRoot, cur.id, wasArchived);
138
+ try {
139
+ if (existsSync(oldPath)) unlinkSync(oldPath);
140
+ } catch {
141
+ }
142
+ }
143
+ return next;
144
+ }
145
+ function addComment(projectRoot, id, text) {
146
+ const cur = getIssue(projectRoot, id);
147
+ if (!cur || !text.trim()) return cur;
148
+ const entry = `
149
+
150
+ ---
151
+ **Follow-up \xB7 ${(/* @__PURE__ */ new Date()).toISOString()}**
152
+
153
+ ${text.trim()}`;
154
+ const body = (cur.body ? cur.body.replace(/\s+$/, "") : "") + entry;
155
+ return updateIssue(projectRoot, id, { body });
156
+ }
157
+ function searchIssues(projectRoot, query) {
158
+ const q = query.trim();
159
+ if (!q) return [];
160
+ try {
161
+ const out = execFileSync("rg", ["-l", "-i", "--no-messages", q, issuesDir(projectRoot)], { encoding: "utf-8" });
162
+ const files = out.split("\n").filter(Boolean);
163
+ const issues = [];
164
+ for (const f of files) {
165
+ try {
166
+ const i = parseIssue(readFileSync(f, "utf-8"));
167
+ if (i) issues.push(i);
168
+ } catch {
169
+ }
170
+ }
171
+ return issues.sort((a, b) => Number(a.id) - Number(b.id));
172
+ } catch {
173
+ const ql = q.toLowerCase();
174
+ return listIssues(projectRoot, { includeArchived: true }).filter((i) => i.title.toLowerCase().includes(ql) || (i.body || "").toLowerCase().includes(ql) || i.labels.some((l) => l.toLowerCase().includes(ql)));
175
+ }
176
+ }
177
+ function summarize(issues) {
178
+ const by = (s) => issues.filter((i) => i.status === s).length;
179
+ return { total: issues.length, backlog: by("backlog"), ready: by("ready"), in_progress: by("in_progress"), done: by("done"), archived: by("archived") };
180
+ }
181
+
182
+ const STATUS_GLYPH = {
183
+ backlog: "\u25CB",
184
+ ready: "\u25C9",
185
+ in_progress: "\u25D0",
186
+ done: "\u2713",
187
+ archived: "\u25A3"
188
+ };
189
+ function flag(args, name) {
190
+ const i = args.indexOf(name);
191
+ return i !== -1 && i + 1 < args.length ? args[i + 1] : void 0;
192
+ }
193
+ function has(args, name) {
194
+ return args.includes(name);
195
+ }
196
+ function positional(args) {
197
+ const out = [];
198
+ for (let i = 0; i < args.length; i++) {
199
+ const a = args[i];
200
+ if (a.startsWith("--")) {
201
+ if (a !== "--json" && a !== "--ready") i++;
202
+ continue;
203
+ }
204
+ out.push(a);
205
+ }
206
+ return out;
207
+ }
208
+ function fmtIssue(i) {
209
+ const label = i.labels.length ? ` [${i.labels.join(", ")}]` : "";
210
+ const v = i.verify ? ` (verify:${i.verify.type})` : "";
211
+ const br = i.branch ? ` {branch:${i.branch}}` : "";
212
+ return `#${i.id} ${STATUS_GLYPH[i.status]} ${i.title}${label}${v}${br}`;
213
+ }
214
+ function buildVerify(args) {
215
+ const cmd = flag(args, "--verify-cmd");
216
+ if (cmd) return { type: "command", text: cmd };
217
+ const agent = flag(args, "--verify");
218
+ if (agent) return { type: "agent", text: agent };
219
+ if (has(args, "--manual")) return { type: "manual" };
220
+ return null;
221
+ }
222
+ async function issueCommand(args) {
223
+ const sub = args[0];
224
+ const rest = args.slice(1);
225
+ const root = resolveProjectRoot();
226
+ const json = has(args, "--json");
227
+ const sessionId = process.env.SVAMP_SESSION_ID || null;
228
+ const out = (text) => console.log(text);
229
+ const ok = (obj, text) => {
230
+ if (json) console.log(JSON.stringify(obj));
231
+ else out(text);
232
+ };
233
+ switch (sub) {
234
+ case "add": {
235
+ const title = positional(rest)[0];
236
+ if (!title) {
237
+ console.error('usage: svamp issue add "<title>" [--ready] [--label x] [--verify-cmd "npm test"] [--scope project|session] [--body "\u2026"]');
238
+ process.exit(1);
239
+ }
240
+ const labels = rest.filter((a, idx) => rest[idx - 1] === "--label");
241
+ const scope = flag(rest, "--scope") || (sessionId ? "session" : "project");
242
+ const issue = addIssue(root, {
243
+ title,
244
+ status: has(rest, "--ready") ? "ready" : "backlog",
245
+ scope,
246
+ labels,
247
+ verify: buildVerify(rest),
248
+ session: scope === "session" ? sessionId : null,
249
+ body: flag(rest, "--body")
250
+ });
251
+ ok(issue, `Created #${issue.id} (${issue.status}): ${issue.title}`);
252
+ break;
253
+ }
254
+ case "list":
255
+ case "ls": {
256
+ const status = flag(rest, "--status");
257
+ const opts = { includeArchived: status === "all" || status === "archived" || has(rest, "--all") };
258
+ if (status && status !== "all") opts.status = status;
259
+ if (flag(rest, "--label")) opts.label = flag(rest, "--label");
260
+ if (flag(rest, "--scope")) opts.scope = flag(rest, "--scope");
261
+ const items = listIssues(root, opts);
262
+ if (json) {
263
+ console.log(JSON.stringify(items));
264
+ break;
265
+ }
266
+ if (!items.length) {
267
+ out("No issues.");
268
+ break;
269
+ }
270
+ for (const i of items) out(fmtIssue(i));
271
+ const s = summarize(listIssues(root, { includeArchived: true }));
272
+ out(`
273
+ ${s.ready} ready \xB7 ${s.in_progress} in-progress \xB7 ${s.backlog} backlog \xB7 ${s.done} done \xB7 ${s.archived} archived`);
274
+ break;
275
+ }
276
+ case "show": {
277
+ const id = positional(rest)[0];
278
+ const issue = id ? getIssue(root, id) : null;
279
+ if (!issue) {
280
+ console.error(`Issue not found: ${id}`);
281
+ process.exit(1);
282
+ }
283
+ if (json) {
284
+ console.log(JSON.stringify(issue));
285
+ break;
286
+ }
287
+ out(fmtIssue(issue));
288
+ out(`status: ${issue.status} \xB7 scope: ${issue.scope} \xB7 created: ${issue.created}${issue.closed ? ` \xB7 closed: ${issue.closed}` : ""}`);
289
+ if (issue.body) out(`
290
+ ${issue.body}`);
291
+ break;
292
+ }
293
+ case "ready":
294
+ case "start":
295
+ case "close":
296
+ case "done":
297
+ case "reopen":
298
+ case "archive":
299
+ case "backlog": {
300
+ const id = positional(rest)[0];
301
+ if (!id) {
302
+ console.error(`usage: svamp issue ${sub} <id>`);
303
+ process.exit(1);
304
+ }
305
+ const map = {
306
+ ready: "ready",
307
+ start: "in_progress",
308
+ close: "done",
309
+ done: "done",
310
+ reopen: "ready",
311
+ archive: "archived",
312
+ backlog: "backlog"
313
+ };
314
+ const updated = updateIssue(root, id, { status: map[sub] });
315
+ if (!updated) {
316
+ console.error(`Issue not found: ${id}`);
317
+ process.exit(1);
318
+ }
319
+ ok(updated, `#${updated.id} \u2192 ${updated.status}: ${updated.title}`);
320
+ break;
321
+ }
322
+ case "edit": {
323
+ const id = positional(rest)[0];
324
+ if (!id) {
325
+ console.error("usage: svamp issue edit <id> [--title \u2026] [--label x] [--verify-cmd \u2026] [--scope \u2026] [--body \u2026]");
326
+ process.exit(1);
327
+ }
328
+ const patch = {};
329
+ if (flag(rest, "--title")) patch.title = flag(rest, "--title");
330
+ const labels = rest.filter((a, idx) => rest[idx - 1] === "--label");
331
+ if (labels.length) patch.labels = labels;
332
+ const v = buildVerify(rest);
333
+ if (v || has(rest, "--no-verify")) patch.verify = has(rest, "--no-verify") ? null : v;
334
+ if (flag(rest, "--scope")) patch.scope = flag(rest, "--scope");
335
+ if (flag(rest, "--body")) patch.body = flag(rest, "--body");
336
+ if (flag(rest, "--status")) patch.status = flag(rest, "--status");
337
+ const updated = updateIssue(root, id, patch);
338
+ if (!updated) {
339
+ console.error(`Issue not found: ${id}`);
340
+ process.exit(1);
341
+ }
342
+ ok(updated, `Updated #${updated.id}: ${updated.title}`);
343
+ break;
344
+ }
345
+ case "comment": {
346
+ const id = positional(rest)[0];
347
+ const text = positional(rest)[1];
348
+ if (!id || !text) {
349
+ console.error('usage: svamp issue comment <id> "<text>"');
350
+ process.exit(1);
351
+ }
352
+ const updated = addComment(root, id, text);
353
+ if (!updated) {
354
+ console.error(`Issue not found: ${id}`);
355
+ process.exit(1);
356
+ }
357
+ ok(updated, `Commented on #${updated.id}`);
358
+ break;
359
+ }
360
+ case "search": {
361
+ const q = positional(rest)[0] || "";
362
+ const items = searchIssues(root, q);
363
+ if (json) {
364
+ console.log(JSON.stringify(items));
365
+ break;
366
+ }
367
+ if (!items.length) {
368
+ out(`No issues match "${q}".`);
369
+ break;
370
+ }
371
+ for (const i of items) out(fmtIssue(i));
372
+ break;
373
+ }
374
+ case "help":
375
+ case void 0:
376
+ case "--help":
377
+ case "-h":
378
+ out([
379
+ "svamp issue \u2014 the session backlog (folder of .svamp/issues/<id>.md)",
380
+ "",
381
+ ' add "<title>" [--ready] [--label x] [--verify-cmd "cmd"] [--scope project|session] [--body "\u2026"]',
382
+ " list [--status ready|backlog|in_progress|done|archived|all] [--label x] [--scope \u2026] [--json]",
383
+ " show <id> [--json]",
384
+ " ready <id> | start <id> | close <id> | reopen <id> | backlog <id> | archive <id>",
385
+ " edit <id> [--title \u2026] [--label x] [--verify-cmd \u2026] [--no-verify] [--status \u2026] [--body \u2026]",
386
+ ' comment <id> "<text>" # append a follow-up to the issue',
387
+ ' search "<query>" [--json]'
388
+ ].join("\n"));
389
+ break;
390
+ default:
391
+ console.error(`Unknown: svamp issue ${sub}. Try: svamp issue help`);
392
+ process.exit(1);
393
+ }
394
+ }
395
+
396
+ export { issueCommand };
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-Fpw-qO_m.mjs';
2
+ import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-Ch1QWOw5.mjs';
3
3
  import { execSync } from 'node:child_process';
4
- import { m as shortId } from './run-4FVCBRiz.mjs';
4
+ import { m as shortId } from './run-C_xwiN7C.mjs';
5
5
  import 'node:path';
6
6
  import 'node:os';
7
7
  import 'os';
@@ -1,11 +1,11 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
  import { resolve } from 'path';
3
- import { connectAndGetMachine } from './commands-Fpw-qO_m.mjs';
3
+ import { connectAndGetMachine } from './commands-Ch1QWOw5.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:child_process';
6
6
  import 'node:path';
7
7
  import 'node:os';
8
- import './run-4FVCBRiz.mjs';
8
+ import './run-C_xwiN7C.mjs';
9
9
  import 'os';
10
10
  import 'fs/promises';
11
11
  import 'url';
@@ -1,7 +1,7 @@
1
1
  import os from 'os';
2
2
  import fs__default from 'fs';
3
3
  import { resolve, join, relative } from 'path';
4
- import { p as parseFrontmatter, n as getSkillsServer, o as getSkillsWorkspaceName, q as getSkillsCollectionName, t as fetchWithTimeout, u as searchSkills, v as SKILLS_DIR, w as getSkillInfo, x as downloadSkillFile, y as listSkillFiles } from './run-4FVCBRiz.mjs';
4
+ import { p as parseFrontmatter, n as getSkillsServer, o as getSkillsWorkspaceName, q as getSkillsCollectionName, t as fetchWithTimeout, u as searchSkills, v as SKILLS_DIR, w as getSkillInfo, x as downloadSkillFile, y as listSkillFiles } from './run-C_xwiN7C.mjs';
5
5
  import 'fs/promises';
6
6
  import 'url';
7
7
  import 'child_process';
@@ -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-4FVCBRiz.mjs';
4
+ import { c as connectToHypha } from './run-C_xwiN7C.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-HydRNEt7.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync, unlinkSync, existsSync, chmodSync, readFileSy
4
4
  import { join } from 'path';
5
5
  import { homedir, platform, arch } from 'os';
6
6
  import { randomUUID, createHash } from 'crypto';
7
- import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-4FVCBRiz.mjs';
7
+ import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-C_xwiN7C.mjs';
8
8
  import 'fs/promises';
9
9
  import 'url';
10
10
  import 'node:crypto';
@@ -1,5 +1,5 @@
1
- import { D as resolveModel, Y as describeMisconfiguration, Z as buildMachineDeps } from './run-4FVCBRiz.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-C9zoh2QP.mjs';
1
+ import { D as resolveModel, Y as describeMisconfiguration, Z as buildMachineDeps } from './run-C_xwiN7C.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-xxUkkrOq.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-4FVCBRiz.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-C_xwiN7C.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.144";
2
+ var version = "0.2.145";
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-ratelimit-retry.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-inbox-cross-machine.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.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 && npx tsx test/test-frpc-status.mjs && 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-shared-session-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-ratelimit-retry.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-inbox-cross-machine.mjs && npx tsx test/test-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.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 && npx tsx test/test-frpc-status.mjs && 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-shared-session-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",
@@ -482,6 +482,39 @@ function buildTools(deps, skills) {
482
482
  return `Started a loop: "${str$1(a?.task).slice(0, 80)}".`;
483
483
  }
484
484
  },
485
+ {
486
+ name: "set_checklist",
487
+ readOnly: false,
488
+ description: "Create or replace the bound session's checklist (the loop's success criteria) and start the self-verifying loop. Each item is a concrete, checkable requirement. ONLY after the caller confirmed the proposal.",
489
+ parameters: { type: "object", properties: {
490
+ items: { type: "array", description: "The checklist items (success criteria).", items: { type: "object", properties: {
491
+ text: { type: "string", description: "A concrete, checkable requirement." },
492
+ oracle: { type: "string", description: "Optional pass/fail command verifying this item." }
493
+ }, required: ["text"], additionalProperties: false } },
494
+ prompt: { type: "string", description: "Optional overall goal/instruction for the loop." },
495
+ max_iterations: { type: "number", description: "Iteration ceiling (default 20)." }
496
+ }, required: ["items"], additionalProperties: false },
497
+ run: async (a) => {
498
+ const rawItems = Array.isArray(a?.items) ? a.items : [];
499
+ const items = rawItems.map((i) => ({ text: str$1(i?.text), oracle: i?.oracle ? str$1(i.oracle) : void 0 })).filter((i) => i.text.trim());
500
+ if (!items.length) return "No checklist items were provided.";
501
+ const r = await deps.applyChecklist(items, {
502
+ prompt: a?.prompt ? str$1(a.prompt) : void 0,
503
+ maxIterations: typeof a?.max_iterations === "number" ? a.max_iterations : void 0
504
+ });
505
+ return r.ok ? `Set a ${items.length}-item checklist and started the loop.` : `Could not set the checklist: ${r.error || "unknown error"}.`;
506
+ }
507
+ },
508
+ {
509
+ name: "stop_loop",
510
+ readOnly: false,
511
+ description: "Stop/cancel the running loop in the bound session. ONLY after the caller asked to stop.",
512
+ parameters: { type: "object", properties: {}, additionalProperties: false },
513
+ run: async () => {
514
+ await deps.stopLoop();
515
+ return "Stopped the loop.";
516
+ }
517
+ },
485
518
  {
486
519
  name: "create_channel",
487
520
  readOnly: false,
@@ -631,11 +664,12 @@ You are WISE Agent, a fast, text-mode companion to the deep coding agent (Claude
631
664
  - run_bash \u2014 run a shell command on the session's machine (when granted).
632
665
  - send_to_session \u2014 hand a clear, reformulated instruction to the deep coding agent (when granted); pass wait=true to block for its reply.
633
666
  - create_routine / create_loop / create_channel \u2014 set up a scheduled/triggered routine, a self-verifying loop, or an inbound channel for this session (when granted). ALWAYS propose first and confirm before calling these (see below).
667
+ - set_checklist / stop_loop \u2014 turn the caller's goals into a tracked checklist (the loop's success criteria) and start the self-verifying loop, or stop a running loop (when granted). Each item is one concrete, checkable requirement (optional per-item oracle command). ALWAYS propose the items first and confirm before calling.
634
668
 
635
669
  # Instructions
636
670
  - Answer general questions and questions about yourself directly. Use tools only to act on the machine/session.
637
671
  - Take the cheap path: read state directly; delegate anything LONG to summarize_session \u2014 keep your own context small.
638
- - To create a routine, loop, or channel: first restate the resolved config in one line and ask the caller to reply "confirm" to proceed. Only call create_routine / create_loop / create_channel after they confirm in a follow-up message. Never create without confirmation.
672
+ - To create a routine, loop, channel, or checklist: first restate the resolved config (for set_checklist, list the items) in one line and ask the caller to reply "confirm" to proceed. Only call create_routine / create_loop / create_channel / set_checklist / stop_loop after they confirm in a follow-up message. Never create or stop without confirmation.
639
673
  - For destructive actions (deleting, stopping, killing), require a verified caller and confirm intent; for safe reads, just do it.
640
674
  - If a tool fails or returns nothing useful, say so plainly \u2014 never fabricate a result.
641
675
  - Report the outcome in one line.`;
@@ -1023,6 +1057,28 @@ function buildSessionDeps(rpc, opts = {}) {
1023
1057
  }
1024
1058
  }, ctx);
1025
1059
  },
1060
+ async applyChecklist(items, opts2) {
1061
+ const built = (items || []).filter((i) => i?.text && i.text.trim()).map((i, idx) => ({
1062
+ id: `wise-${Date.now().toString(36)}-${idx}`,
1063
+ text: i.text.trim(),
1064
+ status: "todo",
1065
+ disposition: "inline",
1066
+ scope: "session",
1067
+ ...i.oracle && i.oracle.trim() ? { verify: { type: "command", text: i.oracle.trim() }, oracle: i.oracle.trim() } : {}
1068
+ }));
1069
+ await rpc.updateConfig({
1070
+ checklist: built,
1071
+ checklistConfig: {
1072
+ ...opts2?.prompt ? { prompt: opts2.prompt } : {},
1073
+ maxIterations: opts2?.maxIterations ?? 20,
1074
+ evaluator: true
1075
+ }
1076
+ }, ctx);
1077
+ return { ok: true };
1078
+ },
1079
+ async stopLoop() {
1080
+ await rpc.updateConfig({ loop: null }, ctx);
1081
+ },
1026
1082
  async saveChannel(channel) {
1027
1083
  return await rpc.saveChannel(channel, ctx);
1028
1084
  }
@@ -2715,7 +2771,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2715
2771
  const tunnels = handlers.tunnels;
2716
2772
  if (!tunnels) throw new Error("Tunnel management not available");
2717
2773
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2718
- const { FrpcTunnel } = await import('./frpc-DQZjSc7h.mjs');
2774
+ const { FrpcTunnel } = await import('./frpc-MevmUvAf.mjs');
2719
2775
  const tunnel = new FrpcTunnel({
2720
2776
  name: params.name,
2721
2777
  ports: params.ports,
@@ -3065,7 +3121,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3065
3121
  }
3066
3122
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3067
3123
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3068
- const { toolsForRole } = await import('./sideband-C9zoh2QP.mjs');
3124
+ const { toolsForRole } = await import('./sideband-xxUkkrOq.mjs');
3069
3125
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3070
3126
  return fmt(r2);
3071
3127
  }
@@ -3164,7 +3220,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3164
3220
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3165
3221
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3166
3222
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3167
- const { queryCore } = await import('./commands-Fpw-qO_m.mjs');
3223
+ const { queryCore } = await import('./commands-Ch1QWOw5.mjs');
3168
3224
  const timeout = c.reply?.timeout_sec || 120;
3169
3225
  let result;
3170
3226
  try {
@@ -11625,7 +11681,7 @@ async function startDaemon(options) {
11625
11681
  saveExposedTunnels(list);
11626
11682
  }
11627
11683
  async function createExposedTunnel(spec) {
11628
- const { FrpcTunnel } = await import('./frpc-DQZjSc7h.mjs');
11684
+ const { FrpcTunnel } = await import('./frpc-MevmUvAf.mjs');
11629
11685
  const tunnel = new FrpcTunnel({
11630
11686
  name: spec.name,
11631
11687
  ports: spec.ports,
@@ -11645,7 +11701,7 @@ async function startDaemon(options) {
11645
11701
  return tunnel;
11646
11702
  }
11647
11703
  const tunnelRecreateState = /* @__PURE__ */ new Map();
11648
- const { ServeManager } = await import('./serveManager-DHTgwal8.mjs');
11704
+ const { ServeManager } = await import('./serveManager-CK5APPYi.mjs');
11649
11705
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
11650
11706
  ensureAutoInstalledSkills(logger).catch(() => {
11651
11707
  });
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { _ as composeSessionId, $ as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a0 as generateHookSettings } from './run-4FVCBRiz.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { _ as composeSessionId, $ as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a0 as generateHookSettings } from './run-C_xwiN7C.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';