svamp-cli 0.2.223-canary.2 → 0.2.224

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 (39) hide show
  1. package/dist/{agentCommands-MGRUIvWB.mjs → agentCommands-BignK4hn.mjs} +5 -5
  2. package/dist/{auth-FvyDIAch.mjs → auth-tP-DHfsI.mjs} +1 -1
  3. package/dist/cli.mjs +61 -61
  4. package/dist/{commands-Cmjb9Je1.mjs → commands-1IupY9qh.mjs} +6 -6
  5. package/dist/{commands-Dm3EQICq.mjs → commands-Bpq8_9OU.mjs} +2 -2
  6. package/dist/{commands-BhFr0kUL.mjs → commands-C1VIwqOH.mjs} +1 -1
  7. package/dist/{commands-_RXnsA1v.mjs → commands-CA8AMvq-.mjs} +1 -1
  8. package/dist/{commands-BiIkCdGB.mjs → commands-DMoXgLUh.mjs} +1 -1
  9. package/dist/{commands-BuecUAk8.mjs → commands-DqvzIDMK.mjs} +13 -1
  10. package/dist/{commands-nlra_-D0.mjs → commands-ez1xdcH5.mjs} +2 -2
  11. package/dist/{fleet-Bi3KIev7.mjs → fleet-B6Rh4mje.mjs} +1 -1
  12. package/dist/{frpc-BpQyeWvI.mjs → frpc-vpwKk0kV.mjs} +1 -1
  13. package/dist/{headlessCli-Be0bYOUB.mjs → headlessCli-C51w8i7T.mjs} +2 -2
  14. package/dist/index.mjs +1 -1
  15. package/dist/package-CzzAMi8h.mjs +63 -0
  16. package/dist/{rpc-CRZWx5YY.mjs → rpc-CcQQrbBO.mjs} +1 -1
  17. package/dist/{rpc-WS0yDVqB.mjs → rpc-D7QFTk_g.mjs} +1 -1
  18. package/dist/{run-8_mnLg6R.mjs → run-BZrPFG_w.mjs} +1 -1
  19. package/dist/{run-DVdqRhtp.mjs → run-C-9ZtEi4.mjs} +77 -33
  20. package/dist/{scheduler-Dx-QrQfP.mjs → scheduler-jlX6Mkxf.mjs} +1 -1
  21. package/dist/{serveCommands-CI2P6buK.mjs → serveCommands-Dhrv-HAD.mjs} +5 -5
  22. package/dist/{serveManager-B-keYGE9.mjs → serveManager-DtBN8E9D.mjs} +2 -2
  23. package/dist/{sideband-BdYb3ezu.mjs → sideband-DxH9bMhv.mjs} +1 -1
  24. package/package.json +3 -3
  25. package/bin/skills/loop/IMPLEMENTATION_PROGRESS.md +0 -49
  26. package/bin/skills/loop/SKILL.md +0 -100
  27. package/bin/skills/loop/bin/channel-core.mjs +0 -161
  28. package/bin/skills/loop/bin/channel-server.mjs +0 -151
  29. package/bin/skills/loop/bin/inject-loop.mjs +0 -90
  30. package/bin/skills/loop/bin/loop-init.mjs +0 -194
  31. package/bin/skills/loop/bin/loop-status.mjs +0 -51
  32. package/bin/skills/loop/bin/precompact.mjs +0 -30
  33. package/bin/skills/loop/bin/routine-core.mjs +0 -126
  34. package/bin/skills/loop/bin/state-fp.mjs +0 -111
  35. package/bin/skills/loop/bin/stop-gate.mjs +0 -349
  36. package/bin/skills/loop/test/test-channel-core.mjs +0 -86
  37. package/bin/skills/loop/test/test-loop-gate.mjs +0 -489
  38. package/bin/skills/loop/test/test-routine-core.mjs +0 -54
  39. package/dist/package-CwGmpH_-.mjs +0 -63
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { execSync } from 'node:child_process';
3
3
  import { basename, resolve, join, isAbsolute } from 'node:path';
4
4
  import os from 'node:os';
5
- import { Q as formatHandle, T as normalizeAllowedUser, U as loadSecurityContextConfig, V as resolveSecurityContext, W as buildSecurityContextFromFlags, X as mergeSecurityContexts, c as connectToHypha, Y as buildSessionShareUrl, Z as computeOutboundHop, A as shortId, _ as buildMachineShareUrl, $ as parseHandle, a0 as handleMatchesMetadata } from './run-DVdqRhtp.mjs';
5
+ import { Q as formatHandle, T as normalizeAllowedUser, U as loadSecurityContextConfig, V as resolveSecurityContext, W as buildSecurityContextFromFlags, X as mergeSecurityContexts, c as connectToHypha, Y as buildSessionShareUrl, Z as computeOutboundHop, A as shortId, _ as registerAwaitingReply, $ as buildMachineShareUrl, a0 as parseHandle, a1 as handleMatchesMetadata } from './run-C-9ZtEi4.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -1834,6 +1834,12 @@ async function sendCore(machine, fullId, message, opts) {
1834
1834
  ...hop.threadId ? { threadId: hop.threadId } : {}
1835
1835
  };
1836
1836
  const result = await machine.sessionRPC(fullId, "sendInboxMessage", { message: inboxMessage });
1837
+ if (callerSessionId) {
1838
+ try {
1839
+ registerAwaitingReply(callerSessionId, hop.threadId || inboxMessage.messageId);
1840
+ } catch {
1841
+ }
1842
+ }
1837
1843
  let waitResult;
1838
1844
  if (shouldWait) {
1839
1845
  const timeoutMs = (opts?.timeout || 300) * 1e3;
@@ -2614,6 +2620,12 @@ async function sessionInboxSend(sessionIdPartial, body, machineId, opts) {
2614
2620
  threadId: opts?.threadId || hop.threadId
2615
2621
  };
2616
2622
  const result = await machine.sessionRPC(fullId, "sendInboxMessage", { message });
2623
+ if (callerSessionId) {
2624
+ try {
2625
+ registerAwaitingReply(callerSessionId, opts?.threadId || hop.threadId || message.messageId);
2626
+ } catch {
2627
+ }
2628
+ }
2617
2629
  if (opts?.json) {
2618
2630
  console.log(formatJson({ sessionId: fullId, messageId: result.messageId, sent: true }));
2619
2631
  } else {
@@ -1,8 +1,8 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
- import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-BuecUAk8.mjs';
3
+ import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-DqvzIDMK.mjs';
4
4
  import { execSync } from 'node:child_process';
5
- import { u as updateIssue, q as addComment, t as addIssue, A as shortId } from './run-DVdqRhtp.mjs';
5
+ import { u as updateIssue, q as addComment, t as addIssue, A as shortId } from './run-C-9ZtEi4.mjs';
6
6
  import 'node:os';
7
7
  import 'os';
8
8
  import 'fs/promises';
@@ -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-DVdqRhtp.mjs';
4
+ import { c as connectToHypha } from './run-C-9ZtEi4.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-VIupR1NK.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-DVdqRhtp.mjs';
7
+ import { h as getFrpsSubdomainHost, i as getFrpsServerPort, j as getFrpsServerAddr } from './run-C-9ZtEi4.mjs';
8
8
  import 'fs/promises';
9
9
  import 'url';
10
10
  import 'node:crypto';
@@ -1,5 +1,5 @@
1
- import { P as resolveModel, a1 as describeMisconfiguration, a2 as buildMachineDeps } from './run-DVdqRhtp.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-BdYb3ezu.mjs';
1
+ import { P as resolveModel, a2 as describeMisconfiguration, a3 as buildMachineDeps } from './run-C-9ZtEi4.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-DxH9bMhv.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-DVdqRhtp.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-9ZtEi4.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -0,0 +1,63 @@
1
+ var name = "svamp-cli";
2
+ var version = "0.2.224";
3
+ var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
+ var author = "Amun AI AB";
5
+ var license = "SEE LICENSE IN LICENSE";
6
+ var type = "module";
7
+ var bin = {
8
+ svamp: "./bin/svamp.mjs"
9
+ };
10
+ var files = [
11
+ "dist",
12
+ "bin"
13
+ ];
14
+ var main = "./dist/index.mjs";
15
+ var exports$1 = {
16
+ ".": "./dist/index.mjs",
17
+ "./cli": "./dist/cli.mjs"
18
+ };
19
+ var scripts = {
20
+ build: "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
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-sharing-notify-sync.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-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-btw-proxy-env.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-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-workflow-runs.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-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-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.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-channel-upload.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 && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs",
23
+ "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
+ dev: "tsx src/cli.ts",
25
+ "dev:daemon": "tsx src/cli.ts daemon start-sync",
26
+ "test:e2e": "node --no-warnings test/e2e-session-tests.mjs",
27
+ "test:frpc": "npx tsx test/test-frpc-e2e.mjs"
28
+ };
29
+ var dependencies = {
30
+ "@agentclientprotocol/sdk": "^0.14.1",
31
+ "@modelcontextprotocol/sdk": "^1.25.3",
32
+ "hypha-rpc": "0.21.42",
33
+ "node-pty": "1.2.0-beta.11",
34
+ ws: "^8.18.0",
35
+ yaml: "^2.8.2",
36
+ zod: "^3.24.4"
37
+ };
38
+ var devDependencies = {
39
+ "@types/node": ">=20",
40
+ "@types/ws": "^8.5.14",
41
+ pkgroll: "^2.14.2",
42
+ tsx: "^4.20.6",
43
+ typescript: "5.9.3"
44
+ };
45
+ var packageManager = "yarn@1.22.22";
46
+ var _package = {
47
+ name: name,
48
+ version: version,
49
+ description: description,
50
+ author: author,
51
+ license: license,
52
+ type: type,
53
+ bin: bin,
54
+ files: files,
55
+ main: main,
56
+ exports: exports$1,
57
+ scripts: scripts,
58
+ dependencies: dependencies,
59
+ devDependencies: devDependencies,
60
+ packageManager: packageManager
61
+ };
62
+
63
+ export { author, bin, _package as default, dependencies, description, devDependencies, exports$1 as exports, files, license, main, name, packageManager, scripts, type, version };
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot } from './run-DVdqRhtp.mjs';
1
+ import { m as resolveProjectRoot } from './run-C-9ZtEi4.mjs';
2
2
  import { g as getWorkflow, s as setWorkflowEnabled, r as removeWorkflow, a as saveWorkflow, b as rawWorkflow, l as listWorkflows } from './store-BTs0H_y0.mjs';
3
3
  import { g as getRun, l as listRuns, r as runWorkflow } from './runStore-CtptN7US.mjs';
4
4
  import 'os';
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-DVdqRhtp.mjs';
1
+ import { m as resolveProjectRoot, u as updateIssue, n as getIssue, o as resumeIssue, p as pauseIssue, q as addComment, t as addIssue, v as listIssues, w as searchIssues, x as isVisibleTo } from './run-C-9ZtEi4.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a3 as applyClaudeProxyEnv, a4 as composeSessionId, a5 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a6 as generateHookSettings } from './run-DVdqRhtp.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a4 as applyClaudeProxyEnv, a5 as composeSessionId, a6 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a7 as generateHookSettings } from './run-C-9ZtEi4.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';
@@ -2912,7 +2912,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2912
2912
  const tunnels = handlers.tunnels;
2913
2913
  if (!tunnels) throw new Error("Tunnel management not available");
2914
2914
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2915
- const { FrpcTunnel } = await import('./frpc-BpQyeWvI.mjs');
2915
+ const { FrpcTunnel } = await import('./frpc-vpwKk0kV.mjs');
2916
2916
  const tunnel = new FrpcTunnel({
2917
2917
  name: params.name,
2918
2918
  ports: params.ports,
@@ -3359,7 +3359,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3359
3359
  }
3360
3360
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3361
3361
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3362
- const { toolsForRole } = await import('./sideband-BdYb3ezu.mjs');
3362
+ const { toolsForRole } = await import('./sideband-DxH9bMhv.mjs');
3363
3363
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3364
3364
  return fmt(r2);
3365
3365
  }
@@ -3458,7 +3458,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3458
3458
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3459
3459
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3460
3460
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3461
- const { queryCore } = await import('./commands-BuecUAk8.mjs');
3461
+ const { queryCore } = await import('./commands-DqvzIDMK.mjs');
3462
3462
  const timeout = c.reply?.timeout_sec || 120;
3463
3463
  let result;
3464
3464
  try {
@@ -3694,6 +3694,48 @@ function readInboundContext(sessionId) {
3694
3694
  return null;
3695
3695
  }
3696
3696
  }
3697
+ const AWAIT_TTL_MS = num("SVAMP_INBOX_AWAIT_TTL_MS", 60 * 6e4);
3698
+ const AWAIT_MAX = num("SVAMP_INBOX_AWAIT_MAX", 200);
3699
+ function awaitPath(sessionId) {
3700
+ return join(SVAMP_HOME$2, "awaiting", `${sessionId}.json`);
3701
+ }
3702
+ function readAwaiting(sessionId) {
3703
+ try {
3704
+ const p = awaitPath(sessionId);
3705
+ if (!existsSync(p)) return {};
3706
+ const m = JSON.parse(readFileSync(p, "utf-8"));
3707
+ return m && typeof m === "object" ? m : {};
3708
+ } catch {
3709
+ return {};
3710
+ }
3711
+ }
3712
+ function writeAwaiting(sessionId, map) {
3713
+ try {
3714
+ mkdirSync(join(SVAMP_HOME$2, "awaiting"), { recursive: true });
3715
+ writeFileSync(awaitPath(sessionId), JSON.stringify(map));
3716
+ } catch {
3717
+ }
3718
+ }
3719
+ function registerAwaitingReply(sessionId, threadId, now = Date.now()) {
3720
+ if (!sessionId || !threadId) return;
3721
+ const map = readAwaiting(sessionId);
3722
+ for (const k of Object.keys(map)) if (now - map[k] > AWAIT_TTL_MS) delete map[k];
3723
+ map[threadId] = now;
3724
+ const keys = Object.keys(map);
3725
+ if (keys.length > AWAIT_MAX) {
3726
+ keys.sort((a, b) => map[a] - map[b]).slice(0, keys.length - AWAIT_MAX).forEach((k) => delete map[k]);
3727
+ }
3728
+ writeAwaiting(sessionId, map);
3729
+ }
3730
+ function takeAwaitingReply(sessionId, threadId, now = Date.now()) {
3731
+ if (!sessionId || !threadId) return false;
3732
+ const map = readAwaiting(sessionId);
3733
+ const at = map[threadId];
3734
+ if (typeof at !== "number") return false;
3735
+ delete map[threadId];
3736
+ writeAwaiting(sessionId, map);
3737
+ return now - at <= AWAIT_TTL_MS;
3738
+ }
3697
3739
  function computeOutboundHop(sessionId) {
3698
3740
  if (!sessionId) return { hopCount: 0, fromInboxTurn: false };
3699
3741
  const ctx = readInboundContext(sessionId);
@@ -3739,18 +3781,27 @@ function senderIsHuman(message) {
3739
3781
  function senderKey(message) {
3740
3782
  return message.fromSession || message.from || "unknown";
3741
3783
  }
3742
- function classifyInbound(message, now = Date.now()) {
3784
+ function classifyInbound(message, now = Date.now(), opts) {
3743
3785
  const hop = message.hopCount ?? 0;
3744
3786
  const human = senderIsHuman(message);
3745
- if (!human && !message.channel && hop > MAX_AGENT_HOPS) {
3787
+ const self = !!opts?.self;
3788
+ const openThreadReply = !!opts?.openThreadReply;
3789
+ if (!human && !self && !message.channel && hop > MAX_AGENT_HOPS) {
3746
3790
  return { action: "silent", reason: `loop-guard: hop ${hop} > ${MAX_AGENT_HOPS}` };
3747
3791
  }
3748
- if (message.urgency !== "urgent") {
3749
- return { action: "queue", reason: "non-urgent" };
3792
+ if (human || self) {
3793
+ const tripped2 = recordWake(now);
3794
+ return { action: "wake", reason: self ? "always-wake (self)" : "always-wake (human)", breakerTripped: tripped2 };
3750
3795
  }
3751
- if (human) {
3796
+ if (openThreadReply) {
3797
+ if (isBreakerOpenAt(now)) {
3798
+ return { action: "queue", reason: "open-thread reply (breaker open)" };
3799
+ }
3752
3800
  const tripped2 = recordWake(now);
3753
- return { action: "wake", reason: "urgent (human)", breakerTripped: tripped2 };
3801
+ return { action: "wake", reason: "open-thread reply", breakerTripped: tripped2 };
3802
+ }
3803
+ if (message.urgency !== "urgent") {
3804
+ return { action: "queue", reason: "non-urgent" };
3754
3805
  }
3755
3806
  if (isBreakerOpenAt(now)) {
3756
3807
  return { action: "queue", reason: "circuit-breaker-open" };
@@ -11295,20 +11346,6 @@ async function ensureAutoInstalledSkills(logger) {
11295
11346
  },
11296
11347
  marketplaceVersion: async () => readBundledSkillVersion("artifact")
11297
11348
  },
11298
- {
11299
- // The self-verifying `loop` skill drives loop mode (Stop-hook gate +
11300
- // LOOP.md injection). Bundled in the npm package (bin/skills/loop/) so the
11301
- // daemon can run loop-init.mjs even offline / before marketplace publish.
11302
- name: "loop",
11303
- install: async () => {
11304
- try {
11305
- installBundledSkill("loop");
11306
- } catch {
11307
- await installSkillFromMarketplace("loop");
11308
- }
11309
- },
11310
- marketplaceVersion: async () => readBundledSkillVersion("loop")
11311
- },
11312
11349
  {
11313
11350
  // The `crew` skill: a lead session delegates features to managed worktree
11314
11351
  // children (svamp feature start/list/done/merge) and merges them back.
@@ -11496,6 +11533,13 @@ function parseLoopBudget(raw) {
11496
11533
  if (Number.isFinite(tph) && tph > 0) out.max_tokens_per_hour = tph;
11497
11534
  return out.max_runtime_sec || out.max_tokens || out.max_tokens_per_hour ? out : void 0;
11498
11535
  }
11536
+ function classifyOptsForMessage(sessionId, message, ownerEmail) {
11537
+ const fromVal = message.from || "";
11538
+ const senderEmail = message.verified && fromVal.includes("@") ? fromVal.replace(/^user:/, "").toLowerCase() : void 0;
11539
+ const self = !!(ownerEmail && senderEmail && senderEmail === String(ownerEmail).toLowerCase());
11540
+ const openThreadReply = takeAwaitingReply(sessionId, message.threadId);
11541
+ return { self, openThreadReply };
11542
+ }
11499
11543
  function buildGoalCondition(o) {
11500
11544
  const parts = [];
11501
11545
  if (o.task) parts.push(o.task.trim());
@@ -12188,7 +12232,7 @@ async function startDaemon(options) {
12188
12232
  saveExposedTunnels(list);
12189
12233
  }
12190
12234
  async function createExposedTunnel(spec) {
12191
- const { FrpcTunnel } = await import('./frpc-BpQyeWvI.mjs');
12235
+ const { FrpcTunnel } = await import('./frpc-vpwKk0kV.mjs');
12192
12236
  const tunnel = new FrpcTunnel({
12193
12237
  name: spec.name,
12194
12238
  ports: spec.ports,
@@ -12208,7 +12252,7 @@ async function startDaemon(options) {
12208
12252
  return tunnel;
12209
12253
  }
12210
12254
  const tunnelRecreateState = /* @__PURE__ */ new Map();
12211
- const { ServeManager } = await import('./serveManager-B-keYGE9.mjs');
12255
+ const { ServeManager } = await import('./serveManager-DtBN8E9D.mjs');
12212
12256
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
12213
12257
  ensureAutoInstalledSkills(logger).catch(() => {
12214
12258
  });
@@ -14013,7 +14057,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14013
14057
  },
14014
14058
  onInboxMessage: (message) => {
14015
14059
  if (trackedSession?.stopped) return;
14016
- const decision = classifyInbound(message);
14060
+ const decision = classifyInbound(message, Date.now(), classifyOptsForMessage(sessionId, message, sessionMetadata?.sharing?.owner));
14017
14061
  logger.log(`[Session ${sessionId}] Inbox message (urgency: ${message.urgency || "normal"}, hop: ${message.hopCount ?? 0}, from: ${message.from || "unknown"}) \u2192 ${decision.action} (${decision.reason})`);
14018
14062
  if (decision.breakerTripped) {
14019
14063
  logger.error(`[Session ${sessionId}] Inbox circuit breaker TRIPPED \u2014 cross-agent wake rate too high; auto-waking paused.`);
@@ -14109,11 +14153,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14109
14153
  });
14110
14154
  },
14111
14155
  onIssue: async (params) => {
14112
- const { issueRpc } = await import('./rpc-WS0yDVqB.mjs');
14156
+ const { issueRpc } = await import('./rpc-D7QFTk_g.mjs');
14113
14157
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
14114
14158
  },
14115
14159
  onWorkflow: async (params) => {
14116
- const { workflowRpc } = await import('./rpc-CRZWx5YY.mjs');
14160
+ const { workflowRpc } = await import('./rpc-CcQQrbBO.mjs');
14117
14161
  return workflowRpc(params?.cwd || directory, params || {});
14118
14162
  },
14119
14163
  onRipgrep: async (args, cwd) => {
@@ -14519,7 +14563,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14519
14563
  },
14520
14564
  onInboxMessage: (message) => {
14521
14565
  if (acpStopped) return;
14522
- const decision = classifyInbound(message);
14566
+ const decision = classifyInbound(message, Date.now(), classifyOptsForMessage(sessionId, message, sessionMetadata?.sharing?.owner));
14523
14567
  logger.log(`[${agentName} Session ${sessionId}] Inbox message (urgency: ${message.urgency || "normal"}, hop: ${message.hopCount ?? 0}, from: ${message.from || "unknown"}) \u2192 ${decision.action} (${decision.reason})`);
14524
14568
  if (decision.breakerTripped) {
14525
14569
  logger.error(`[${agentName} Session ${sessionId}] Inbox circuit breaker TRIPPED \u2014 auto-waking paused.`);
@@ -14646,11 +14690,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14646
14690
  });
14647
14691
  },
14648
14692
  onIssue: async (params) => {
14649
- const { issueRpc } = await import('./rpc-WS0yDVqB.mjs');
14693
+ const { issueRpc } = await import('./rpc-D7QFTk_g.mjs');
14650
14694
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
14651
14695
  },
14652
14696
  onWorkflow: async (params) => {
14653
- const { workflowRpc } = await import('./rpc-CRZWx5YY.mjs');
14697
+ const { workflowRpc } = await import('./rpc-CcQQrbBO.mjs');
14654
14698
  return workflowRpc(params?.cwd || directory, params || {});
14655
14699
  },
14656
14700
  onRipgrep: async (args, cwd) => {
@@ -15424,7 +15468,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15424
15468
  const PING_TIMEOUT_MS = 15e3;
15425
15469
  const POST_RECONNECT_GRACE_MS = 2e4;
15426
15470
  const RECONNECT_JITTER_MS = 2500;
15427
- const { WorkflowScheduler } = await import('./scheduler-Dx-QrQfP.mjs');
15471
+ const { WorkflowScheduler } = await import('./scheduler-jlX6Mkxf.mjs');
15428
15472
  const workflowScheduler = new WorkflowScheduler({
15429
15473
  projectRoots: () => {
15430
15474
  const dirs = /* @__PURE__ */ new Set();
@@ -16003,4 +16047,4 @@ var run = /*#__PURE__*/Object.freeze({
16003
16047
  writeStopMarker: writeStopMarker
16004
16048
  });
16005
16049
 
16006
- export { parseHandle as $, shortId as A, loadMachineContext as B, buildMachineInstructions as C, machineToolsForRole as D, buildMachineTools as E, parseFrontmatter as F, getSkillsServer as G, getSkillsWorkspaceName as H, getSkillsCollectionName as I, fetchWithTimeout as J, searchSkills as K, SKILLS_DIR as L, getSkillInfo as M, downloadSkillFile as N, listSkillFiles as O, resolveModel as P, formatHandle as Q, READ_ONLY_TOOLS as R, ServeAuth as S, normalizeAllowedUser as T, loadSecurityContextConfig as U, resolveSecurityContext as V, buildSecurityContextFromFlags as W, mergeSecurityContexts as X, buildSessionShareUrl as Y, computeOutboundHop as Z, buildMachineShareUrl as _, createSessionStore as a, handleMatchesMetadata as a0, describeMisconfiguration as a1, buildMachineDeps as a2, applyClaudeProxyEnv as a3, composeSessionId as a4, generateFriendlyName as a5, generateHookSettings as a6, claudeAuth as a7, projectInfo as a8, DefaultTransport$1 as a9, acpBackend as aa, acpAgentConfig as ab, codexMcpBackend as ac, GeminiTransport$1 as ad, instanceConfig as ae, api as af, run as ag, 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, resolveProjectRoot as m, getIssue as n, resumeIssue as o, pauseIssue as p, addComment as q, registerMachineService as r, startDaemon as s, addIssue as t, updateIssue as u, listIssues as v, searchIssues as w, isVisibleTo as x, cronMatches as y, summarize as z };
16050
+ export { buildMachineShareUrl as $, shortId as A, loadMachineContext as B, buildMachineInstructions as C, machineToolsForRole as D, buildMachineTools as E, parseFrontmatter as F, getSkillsServer as G, getSkillsWorkspaceName as H, getSkillsCollectionName as I, fetchWithTimeout as J, searchSkills as K, SKILLS_DIR as L, getSkillInfo as M, downloadSkillFile as N, listSkillFiles as O, resolveModel as P, formatHandle as Q, READ_ONLY_TOOLS as R, ServeAuth as S, normalizeAllowedUser as T, loadSecurityContextConfig as U, resolveSecurityContext as V, buildSecurityContextFromFlags as W, mergeSecurityContexts as X, buildSessionShareUrl as Y, computeOutboundHop as Z, registerAwaitingReply as _, createSessionStore as a, parseHandle as a0, handleMatchesMetadata as a1, describeMisconfiguration as a2, buildMachineDeps as a3, applyClaudeProxyEnv as a4, composeSessionId as a5, generateFriendlyName as a6, generateHookSettings as a7, claudeAuth as a8, projectInfo as a9, DefaultTransport$1 as aa, acpBackend as ab, acpAgentConfig as ac, codexMcpBackend as ad, GeminiTransport$1 as ae, instanceConfig as af, api as ag, run as ah, 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, resolveProjectRoot as m, getIssue as n, resumeIssue as o, pauseIssue as p, addComment as q, registerMachineService as r, startDaemon as s, addIssue as t, updateIssue as u, listIssues as v, searchIssues as w, isVisibleTo as x, cronMatches as y, summarize as z };
@@ -1,4 +1,4 @@
1
- import { m as resolveProjectRoot, y as cronMatches } from './run-DVdqRhtp.mjs';
1
+ import { m as resolveProjectRoot, y as cronMatches } from './run-C-9ZtEi4.mjs';
2
2
  import { l as listWorkflows, i as isWorkflowEnabled, w as workflowCrons } from './store-BTs0H_y0.mjs';
3
3
  import { r as runWorkflow } from './runStore-CtptN7US.mjs';
4
4
  import 'os';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-BuecUAk8.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-DqvzIDMK.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-BuecUAk8.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-DqvzIDMK.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-BuecUAk8.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-DqvzIDMK.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-BuecUAk8.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-DqvzIDMK.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-BuecUAk8.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-DqvzIDMK.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-DVdqRhtp.mjs';
7
+ import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-C-9ZtEi4.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -733,7 +733,7 @@ class ServeManager {
733
733
  const mount = this.mounts.get(mountName);
734
734
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
735
735
  try {
736
- const { FrpcTunnel } = await import('./frpc-BpQyeWvI.mjs');
736
+ const { FrpcTunnel } = await import('./frpc-vpwKk0kV.mjs');
737
737
  let tunnel;
738
738
  tunnel = new FrpcTunnel({
739
739
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { R as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-DVdqRhtp.mjs';
1
+ import { R as READ_ONLY_TOOLS, B as loadMachineContext, C as buildMachineInstructions, D as machineToolsForRole, E as buildMachineTools } from './run-C-9ZtEi4.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.223-canary.2",
3
+ "version": "0.2.224",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -18,9 +18,9 @@
18
18
  "./cli": "./dist/cli.mjs"
19
19
  },
20
20
  "scripts": {
21
- "build": "rm -rf dist bin/skills bin/commands && 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 && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
21
+ "build": "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "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-sharing-notify-sync.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-btw-proxy-env.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-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-workflow-runs.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-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-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.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-channel-upload.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 && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs",
23
+ "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-sharing-notify-sync.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-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-btw-proxy-env.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-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-workflow-runs.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-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-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.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-channel-upload.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 && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.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",
@@ -1,49 +0,0 @@
1
- # Loops, Routines & Channels — implementation status
2
-
3
- Branch: `loop-engineering-phase1`. Design docs: `docs/loops-and-routines-design.md`, `docs/channels-design.md`.
4
-
5
- This consolidates the work into one accurate picture: what's built, how it was verified, and the single remaining step (deploy + UI QA).
6
-
7
- ---
8
-
9
- ## 1. Loop engine — DONE (tested + live-E2E + 2 adversarial reviews)
10
- Skill-first loop with a harness-enforced completion gate. Files in `skills/loop/`.
11
- - **`SKILL.md`** — loop-engineering protocol (actor ≠ judge; oracle + isolated evaluator).
12
- - **`bin/stop-gate.mjs`** — Claude Code `Stop` hook: blocks the turn ending unless the **oracle exits 0 AND a fingerprint-fresh evaluator verdict says "done."** Bounded by clamped `max_iterations` (hard ceiling 200), runtime budget, and token budget (summed from the transcript). Appends a per-iteration audit trail.
13
- - **`bin/state-fp.mjs`** — working-tree fingerprint (git diff, or filesystem walk incl. symlink targets for non-git dirs) tying a verdict to the exact code it judged.
14
- - **`bin/inject-loop.mjs`** (SessionStart/UserPromptSubmit) · **`bin/precompact.mjs`** (PreCompact handoff) · **`bin/loop-init.mjs`** (generates LOOP.md + `.claude/settings.json` hooks + optional `loop-evaluator` agent) · **`bin/loop-status.mjs`** (timeline reader).
15
- - **Tests:** `test/test-loop-gate.mjs` (29) — all pass. **Live-verified** in real svamp sessions: happy path, **early-quit blocked**, isolated evaluator subagent, full regression.
16
- - Skill↔gate↔inject **contract verified consistent** (verdict path/fields/`state_fp` command all agree).
17
-
18
- ## 2. Routines — DONE (tested + live-E2E)
19
- Session-scoped triggers → message/loop actions. `skills/loop/bin/routine-*.mjs` (JS engine + supervised server) and `packages/svamp-cli/src/routine/*.ts` (daemon).
20
- - Triggers: schedule (cron, tz-aware tick **and** catch-up), webhook (capability URL, GET/POST), manual. Actions: message | loop. Overlap (queue/skip/replace concurrent-guard), daily_cap (reserve-before-deliver), missed-run catch-up.
21
- - `svamp routine add/list/remove/enable/disable/run-now/serve`; daemon RPC (`hyphaSessionService`, 10 runtime tests); frontend **Routines tab**.
22
- - **Tests:** routine-core, routine-engine (26), cli routine (18), cli routine-rpc (10) — all pass. **Live-verified**: webhook → templated message delivered into a real session.
23
-
24
- ## 3. Channels — DONE (tested + live-E2E both transports + 2 security reviews)
25
- External / agent-to-agent inbound endpoints. `skills/loop/bin/channel-*.mjs` + `packages/svamp-cli/src/channel/store.ts`.
26
- - Identity: per-key (verified) / caller-supplied (unverified) / fixed / Hypha `context.user` (opt-in `hypha_allow`, deny-by-default, anonymous rejected). Default **XML provenance template** (all values XML-escaped + control-chars stripped → no forgery/tag-breakout). Auto-generated **skill** per channel.
27
- - Transports: **Hypha `svamp-channels` service** (verified identity, no key) **and** HTTP `/channel/<id>` (+ `/skill.md`, bearer key). Delivers as a **plain single-tag message**. Actions: **message and loop** (loop seeds `loop-init` + kickoff).
28
- - `hypha channel list/describe/send` CLI client; daemon RPC (10 runtime tests); frontend **Channels tab**.
29
- - **Tests:** channel-core (32), cli channel-rpc (10) — all pass. **Live-verified**: HTTP per-key (`from=alice verified`), Hypha (`from=<hypha-user> verified`, no key), loop-action (loop seeded + work completed).
30
-
31
- ## Verification summary
32
- All feature suites green; **svamp-cli + svamp-app + hypha-cli typecheck 0 errors**. Standalone/skill parts live-E2E-verified. Daemon RPC runtime-verified via `createSessionStore`. Two adversarial review rounds each on the loop gate and channels (caught + fixed 3 CRITICAL block-forever bugs and a CRITICAL provenance-forgery hole); daemon-code review confirmed no destabilization. Review has **converged** (latest pass found nothing).
33
-
34
- ---
35
-
36
- ## The ONE remaining step — deploy + UI QA (must be done from a SEPARATE session)
37
- The daemon RPC + the three frontend tabs (Routines / Channels / Loop-monitor) are typecheck-clean and production-bundle clean, but their **live rendering + RPC round-trip have not been visually confirmed** — and this repo has no component-render test infra (UI is verified by deploy/manual). Activating them restarts the daemon hosting this loop session, so it is **deliberately not done here**. To finish:
38
- ```
39
- yarn workspace svamp-cli build && svamp daemon restart # activates routine/channel RPC
40
- ./deploy/quick-deploy.sh # ships the web UI
41
- ```
42
- Then open a session → input → settings and confirm the **Routines / Channels / Loop-monitor** tabs render and round-trip. The standalone engines need no deploy — `node skills/loop/bin/loop-init.mjs …`, `svamp routine …`, and the supervised channel/routine servers already work today.
43
-
44
- ## Deferred (by design or low-risk-to-leave)
45
- - **await-reply (channels v1.1)** — request/response; v1 is async (two channels), per the design decision.
46
- - **Loop gate on an already-running session** — hooks load at Claude startup, so looping a live session is best-effort (self-contained kickoff); full enforcement needs a fresh/restarted session.
47
- - **#4/#6/#11 concurrency edges** — cross-process lost-update / git↔walk fp flip / store last-writer-wins. Low-likelihood for the single-session loop; a forced fix needs locking that could itself cause block-forever, so documented rather than fixed.
48
- - **Token/cost budget in USD** — token budget (from transcript) is implemented; USD cost would need daemon usage accounting.
49
- </content>