svamp-cli 0.2.85 → 0.2.86
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.
- package/dist/{agentCommands-C10NivHS.mjs → agentCommands-BiIVrk52.mjs} +2 -2
- package/dist/cli.mjs +50 -46
- package/dist/{commands-BeZMEZhQ.mjs → commands-BKYz39Hl.mjs} +2 -2
- package/dist/{commands-CtYB14Y-.mjs → commands-BkUoyPy-.mjs} +5 -5
- package/dist/commands-DQaKE9SC.mjs +154 -0
- package/dist/{commands-biNfOPRI.mjs → commands-Db9HLZR5.mjs} +2 -2
- package/dist/{commands-B75aXb6C.mjs → commands-Dj7Be8dw.mjs} +1 -1
- package/dist/{fleet-D_sem31C.mjs → fleet-DHV-NyW1.mjs} +1 -1
- package/dist/{frpc-D3LiH7GX.mjs → frpc-BfBqHN33.mjs} +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{package-BDGD9W2l.mjs → package-C93hgi-7.mjs} +2 -2
- package/dist/{run-Xk8il2Yz.mjs → run-CEs0etJC.mjs} +1 -1
- package/dist/{run-MQKWgYFS.mjs → run-rWXfNd7e.mjs} +335 -19
- package/dist/{serveCommands-9D4WCoJR.mjs → serveCommands-Cw5hnh0B.mjs} +5 -5
- package/dist/{serveManager-j2Z-hrHL.mjs → serveManager-BgnooFJZ.mjs} +3 -3
- package/package.json +45 -45
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-
|
|
1
|
+
export { c as connectToHypha, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, a as registerSessionService, s as startDaemon, b as stopDaemon } from './run-rWXfNd7e.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -10,9 +10,9 @@ import 'node:fs';
|
|
|
10
10
|
import 'util';
|
|
11
11
|
import 'node:crypto';
|
|
12
12
|
import 'node:path';
|
|
13
|
+
import 'node:os';
|
|
13
14
|
import 'node:child_process';
|
|
14
15
|
import '@agentclientprotocol/sdk';
|
|
15
|
-
import 'node:os';
|
|
16
16
|
import '@modelcontextprotocol/sdk/client/index.js';
|
|
17
17
|
import '@modelcontextprotocol/sdk/client/stdio.js';
|
|
18
18
|
import '@modelcontextprotocol/sdk/types.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "svamp-cli";
|
|
2
|
-
var version = "0.2.
|
|
2
|
+
var version = "0.2.86";
|
|
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 && 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-ralph-loop.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-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-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.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 && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.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",
|
|
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-ralph-loop.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-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-ralph-loop-integration.mjs && npx tsx test/test-ralph-loop-modes.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 && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.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",
|
|
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",
|
|
@@ -2,7 +2,7 @@ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(im
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { existsSync, readFileSync, watch } from 'node:fs';
|
|
5
|
-
import { c as connectToHypha, a as registerSessionService,
|
|
5
|
+
import { c as connectToHypha, a as registerSessionService, E as generateHookSettings } from './run-rWXfNd7e.mjs';
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { createInterface } from 'node:readline';
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import os, { homedir as homedir$1 } from 'os';
|
|
2
2
|
import fs, { mkdir as mkdir$1, readdir as readdir$1, readFile, writeFile as writeFile$1, rename, unlink } from 'fs/promises';
|
|
3
|
-
import { readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync, existsSync as existsSync$1, rmSync, copyFileSync, unlinkSync as unlinkSync$1, watch, rmdirSync, readdirSync } from 'fs';
|
|
3
|
+
import { readFileSync as readFileSync$1, mkdirSync, writeFileSync, renameSync, existsSync as existsSync$1, rmSync as rmSync$1, copyFileSync, unlinkSync as unlinkSync$1, watch, rmdirSync, readdirSync as readdirSync$1 } from 'fs';
|
|
4
4
|
import path__default, { join, dirname, resolve, basename } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execFile, spawn as spawn$1, execSync as execSync$1 } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
|
-
import { existsSync, readFileSync, writeFileSync as writeFileSync$1,
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync as mkdirSync$1, readdirSync, writeFileSync as writeFileSync$1, renameSync as renameSync$1, rmSync, appendFileSync, unlinkSync } from 'node:fs';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
|
-
import { randomUUID, createHash } from 'node:crypto';
|
|
10
|
+
import { randomBytes, randomUUID, createHash } from 'node:crypto';
|
|
11
11
|
import { join as join$1 } from 'node:path';
|
|
12
|
+
import os$1, { homedir, platform } from 'node:os';
|
|
12
13
|
import { spawn, execSync, execFile as execFile$1, execFileSync } from 'node:child_process';
|
|
13
14
|
import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
|
|
14
|
-
import os$1, { homedir, platform } from 'node:os';
|
|
15
15
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
16
16
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
17
17
|
import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -1393,7 +1393,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
1393
1393
|
const tunnels = handlers.tunnels;
|
|
1394
1394
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
1395
1395
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
1396
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
1396
|
+
const { FrpcTunnel } = await import('./frpc-BfBqHN33.mjs');
|
|
1397
1397
|
const tunnel = new FrpcTunnel({
|
|
1398
1398
|
name: params.name,
|
|
1399
1399
|
ports: params.ports,
|
|
@@ -1609,6 +1609,262 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
1609
1609
|
};
|
|
1610
1610
|
}
|
|
1611
1611
|
|
|
1612
|
+
const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
1613
|
+
function parseField(token, [min, max]) {
|
|
1614
|
+
const set = /* @__PURE__ */ new Set();
|
|
1615
|
+
for (const part of token.split(",")) {
|
|
1616
|
+
let m;
|
|
1617
|
+
if (part === "*") {
|
|
1618
|
+
for (let i = min; i <= max; i++) set.add(i);
|
|
1619
|
+
} else if (m = part.match(/^\*\/(\d+)$/)) {
|
|
1620
|
+
const s = +m[1];
|
|
1621
|
+
for (let i = min; i <= max; i += s) set.add(i);
|
|
1622
|
+
} else if (m = part.match(/^(\d+)-(\d+)\/(\d+)$/)) {
|
|
1623
|
+
for (let i = +m[1]; i <= +m[2]; i += +m[3]) set.add(i);
|
|
1624
|
+
} else if (m = part.match(/^(\d+)-(\d+)$/)) {
|
|
1625
|
+
for (let i = +m[1]; i <= +m[2]; i++) set.add(i);
|
|
1626
|
+
} else if (m = part.match(/^(\d+)$/)) {
|
|
1627
|
+
set.add(+m[1]);
|
|
1628
|
+
} else throw new Error(`invalid cron field: "${token}"`);
|
|
1629
|
+
}
|
|
1630
|
+
for (const v of set) if (v < min || v > max) throw new Error(`cron value ${v} out of range [${min},${max}]`);
|
|
1631
|
+
return set;
|
|
1632
|
+
}
|
|
1633
|
+
function parseCron(expr) {
|
|
1634
|
+
const fields = String(expr).trim().split(/\s+/);
|
|
1635
|
+
if (fields.length !== 5) throw new Error(`cron must have 5 fields, got ${fields.length}: "${expr}"`);
|
|
1636
|
+
const [minute, hour, dom, month, dow] = fields.map((f, i) => parseField(f, FIELD_RANGES[i]));
|
|
1637
|
+
return { minute, hour, dom, month, dow, domRestricted: fields[2] !== "*", dowRestricted: fields[4] !== "*" };
|
|
1638
|
+
}
|
|
1639
|
+
function cronMatches(expr, date) {
|
|
1640
|
+
const c = typeof expr === "string" ? parseCron(expr) : expr;
|
|
1641
|
+
if (!c.minute.has(date.getMinutes())) return false;
|
|
1642
|
+
if (!c.hour.has(date.getHours())) return false;
|
|
1643
|
+
if (!c.month.has(date.getMonth() + 1)) return false;
|
|
1644
|
+
const domOk = c.dom.has(date.getDate());
|
|
1645
|
+
const dowOk = c.dow.has(date.getDay());
|
|
1646
|
+
if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
|
|
1647
|
+
return domOk && dowOk;
|
|
1648
|
+
}
|
|
1649
|
+
function nextFire(expr, from) {
|
|
1650
|
+
const c = parseCron(expr);
|
|
1651
|
+
const d = new Date(from.getTime());
|
|
1652
|
+
d.setSeconds(0, 0);
|
|
1653
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
1654
|
+
for (let i = 0; i < 366 * 24 * 60; i++) {
|
|
1655
|
+
if (cronMatches(c, d)) return new Date(d.getTime());
|
|
1656
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
1657
|
+
}
|
|
1658
|
+
return null;
|
|
1659
|
+
}
|
|
1660
|
+
function resolvePath(ctx, path) {
|
|
1661
|
+
return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
|
|
1662
|
+
}
|
|
1663
|
+
function renderTemplate(template, ctx) {
|
|
1664
|
+
return String(template).replace(/\$\{([\w.$]+)\}/g, (_m, p) => {
|
|
1665
|
+
const v = resolvePath(ctx, p);
|
|
1666
|
+
return v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
const TRIGGER_TYPES = ["manual", "schedule", "webhook", "api"];
|
|
1670
|
+
const ACTION_KINDS = ["message", "loop"];
|
|
1671
|
+
const OVERLAP = ["queue", "skip", "replace"];
|
|
1672
|
+
function validateRoutine(r) {
|
|
1673
|
+
const errs = [];
|
|
1674
|
+
if (!r || typeof r !== "object") return ["routine must be an object"];
|
|
1675
|
+
if (!r.session_id) errs.push("session_id required");
|
|
1676
|
+
if (!r.name) errs.push("name required");
|
|
1677
|
+
const t = r.trigger;
|
|
1678
|
+
if (!t || !TRIGGER_TYPES.includes(t.type)) errs.push(`trigger.type must be one of ${TRIGGER_TYPES.join("|")}`);
|
|
1679
|
+
if (t?.type === "schedule") {
|
|
1680
|
+
try {
|
|
1681
|
+
parseCron(t.cron);
|
|
1682
|
+
} catch (e) {
|
|
1683
|
+
errs.push(`trigger.cron: ${e.message}`);
|
|
1684
|
+
}
|
|
1685
|
+
if (t.missed && !["catchup", "skip"].includes(t.missed)) errs.push("trigger.missed must be catchup|skip");
|
|
1686
|
+
}
|
|
1687
|
+
const a = r.action;
|
|
1688
|
+
if (!a || !ACTION_KINDS.includes(a.kind)) errs.push(`action.kind must be one of ${ACTION_KINDS.join("|")}`);
|
|
1689
|
+
if (a?.kind === "message" && !a.template) errs.push("action.template required for message action");
|
|
1690
|
+
if (a?.kind === "loop" && !a.loop && !a.task_template) errs.push("action.loop or action.task_template required for loop action");
|
|
1691
|
+
if (r.overlap && !OVERLAP.includes(r.overlap)) errs.push(`overlap must be one of ${OVERLAP.join("|")}`);
|
|
1692
|
+
return errs;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
function defaultRoutinesDir() {
|
|
1696
|
+
return process.env.SVAMP_ROUTINES_DIR || join$1(homedir(), ".svamp", "routines");
|
|
1697
|
+
}
|
|
1698
|
+
const genId = () => "rt_" + randomBytes(5).toString("hex");
|
|
1699
|
+
const genKey = () => randomBytes(18).toString("base64url");
|
|
1700
|
+
class RoutineStore {
|
|
1701
|
+
dir;
|
|
1702
|
+
constructor(dir = defaultRoutinesDir()) {
|
|
1703
|
+
this.dir = dir;
|
|
1704
|
+
mkdirSync$1(dir, { recursive: true });
|
|
1705
|
+
}
|
|
1706
|
+
_path(id) {
|
|
1707
|
+
return join$1(this.dir, `${id}.json`);
|
|
1708
|
+
}
|
|
1709
|
+
list(sessionId) {
|
|
1710
|
+
const all = readdirSync(this.dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
1711
|
+
try {
|
|
1712
|
+
return JSON.parse(readFileSync(join$1(this.dir, f), "utf8"));
|
|
1713
|
+
} catch {
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
}).filter((r) => !!r);
|
|
1717
|
+
return sessionId ? all.filter((r) => r.session_id === sessionId) : all;
|
|
1718
|
+
}
|
|
1719
|
+
get(id) {
|
|
1720
|
+
try {
|
|
1721
|
+
return JSON.parse(readFileSync(this._path(id), "utf8"));
|
|
1722
|
+
} catch {
|
|
1723
|
+
return null;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
save(routine) {
|
|
1727
|
+
const r = { overlap: "queue", enabled: true, last_runs: [], ...routine };
|
|
1728
|
+
if (!r.id) r.id = genId();
|
|
1729
|
+
if ((r.trigger?.type === "webhook" || r.trigger?.type === "api") && !r.trigger.key) r.trigger.key = genKey();
|
|
1730
|
+
const errs = validateRoutine(r);
|
|
1731
|
+
if (errs.length) throw new Error("invalid routine: " + errs.join("; "));
|
|
1732
|
+
const tmp = this._path(r.id) + ".tmp";
|
|
1733
|
+
writeFileSync$1(tmp, JSON.stringify(r, null, 2));
|
|
1734
|
+
renameSync$1(tmp, this._path(r.id));
|
|
1735
|
+
return r;
|
|
1736
|
+
}
|
|
1737
|
+
remove(id) {
|
|
1738
|
+
const p = this._path(id);
|
|
1739
|
+
if (existsSync(p)) {
|
|
1740
|
+
rmSync(p);
|
|
1741
|
+
return true;
|
|
1742
|
+
}
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
setEnabled(id, enabled) {
|
|
1746
|
+
const r = this.get(id);
|
|
1747
|
+
if (!r) return null;
|
|
1748
|
+
r.enabled = enabled;
|
|
1749
|
+
return this.save(r);
|
|
1750
|
+
}
|
|
1751
|
+
recordRun(id, entry) {
|
|
1752
|
+
const r = this.get(id);
|
|
1753
|
+
if (!r) return null;
|
|
1754
|
+
r.last_runs = [{ firedAt: (/* @__PURE__ */ new Date()).toISOString(), via: "manual", delivered: "message", outcome: "ok", ...entry }, ...r.last_runs || []].slice(0, 20);
|
|
1755
|
+
return this.save(r);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
const minuteKey = (d) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}-${d.getHours()}-${d.getMinutes()}`;
|
|
1760
|
+
class RoutineRunner {
|
|
1761
|
+
store;
|
|
1762
|
+
deliver;
|
|
1763
|
+
now;
|
|
1764
|
+
onReplace;
|
|
1765
|
+
log;
|
|
1766
|
+
active = /* @__PURE__ */ new Set();
|
|
1767
|
+
_firedMinute = /* @__PURE__ */ new Map();
|
|
1768
|
+
constructor(o) {
|
|
1769
|
+
this.store = o.store;
|
|
1770
|
+
this.deliver = o.deliver;
|
|
1771
|
+
this.now = o.now || (() => /* @__PURE__ */ new Date());
|
|
1772
|
+
this.onReplace = o.onReplace;
|
|
1773
|
+
this.log = o.log || (() => {
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
resolveAction(routine, payload = {}) {
|
|
1777
|
+
const ctx = { ...payload, now: this.now().toISOString(), routine: { id: routine.id, name: routine.name } };
|
|
1778
|
+
const a = routine.action;
|
|
1779
|
+
if (a.kind === "message") return { kind: "message", text: renderTemplate(a.template || "", ctx) };
|
|
1780
|
+
const task = renderTemplate(a.task_template || a.loop?.task || "", ctx);
|
|
1781
|
+
return { kind: "loop", task, loop: a.loop };
|
|
1782
|
+
}
|
|
1783
|
+
markDone(id) {
|
|
1784
|
+
this.active.delete(id);
|
|
1785
|
+
}
|
|
1786
|
+
_deliveredToday(routine) {
|
|
1787
|
+
const today = this.now().toDateString();
|
|
1788
|
+
return (routine.last_runs || []).filter((r) => r.outcome === "delivered" && new Date(r.firedAt).toDateString() === today).length;
|
|
1789
|
+
}
|
|
1790
|
+
async fire(routine, payload = {}, via = "manual") {
|
|
1791
|
+
if (!routine.enabled) return { skipped: "disabled" };
|
|
1792
|
+
if (routine.daily_cap && this._deliveredToday(this.store.get(routine.id) || routine) >= routine.daily_cap) {
|
|
1793
|
+
this.store.recordRun(routine.id, { via, delivered: routine.action.kind, outcome: "skipped (daily cap)" });
|
|
1794
|
+
return { skipped: "daily_cap" };
|
|
1795
|
+
}
|
|
1796
|
+
if (this.active.has(routine.id)) {
|
|
1797
|
+
if (routine.overlap === "skip") {
|
|
1798
|
+
this.store.recordRun(routine.id, { via, delivered: routine.action.kind, outcome: "skipped (busy)" });
|
|
1799
|
+
return { skipped: "busy" };
|
|
1800
|
+
}
|
|
1801
|
+
if (routine.overlap === "replace") {
|
|
1802
|
+
try {
|
|
1803
|
+
this.onReplace?.(routine.id);
|
|
1804
|
+
} catch {
|
|
1805
|
+
}
|
|
1806
|
+
this.active.delete(routine.id);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
const resolved = this.resolveAction(routine, payload);
|
|
1810
|
+
let outcome = "delivered";
|
|
1811
|
+
try {
|
|
1812
|
+
await this.deliver({ routine, action: routine.action, resolved, payload, via });
|
|
1813
|
+
if (resolved.kind === "loop") this.active.add(routine.id);
|
|
1814
|
+
} catch (e) {
|
|
1815
|
+
outcome = "error: " + (e?.message || e);
|
|
1816
|
+
}
|
|
1817
|
+
const r = this.store.get(routine.id) || routine;
|
|
1818
|
+
r.last_fired_at = this.now().toISOString();
|
|
1819
|
+
r.last_runs = [{ firedAt: r.last_fired_at, via, delivered: resolved.kind, outcome }, ...r.last_runs || []].slice(0, 20);
|
|
1820
|
+
this.store.save(r);
|
|
1821
|
+
return { fired: true, via, resolved, outcome };
|
|
1822
|
+
}
|
|
1823
|
+
async tick(date = this.now()) {
|
|
1824
|
+
const results = [];
|
|
1825
|
+
for (const r of this.store.list()) {
|
|
1826
|
+
if (!r.enabled || r.trigger?.type !== "schedule") continue;
|
|
1827
|
+
if (!cronMatches(r.trigger.cron, date)) continue;
|
|
1828
|
+
const mk = minuteKey(date);
|
|
1829
|
+
if (this._firedMinute.get(r.id) === mk) continue;
|
|
1830
|
+
this._firedMinute.set(r.id, mk);
|
|
1831
|
+
results.push({ id: r.id, ...await this.fire(r, { tick: date.toISOString() }, "schedule") });
|
|
1832
|
+
}
|
|
1833
|
+
return results;
|
|
1834
|
+
}
|
|
1835
|
+
async webhook(id, opts = {}) {
|
|
1836
|
+
const { key, method = "POST", body = {}, query = {} } = opts;
|
|
1837
|
+
const r = this.store.get(id);
|
|
1838
|
+
if (!r) return { status: 404, error: "routine not found" };
|
|
1839
|
+
if (r.trigger?.type !== "webhook" && r.trigger?.type !== "api") return { status: 400, error: "not a webhook routine" };
|
|
1840
|
+
if (!r.enabled) return { status: 409, error: "routine disabled" };
|
|
1841
|
+
if (!r.trigger.public && r.trigger.key && key !== r.trigger.key) return { status: 401, error: "bad key" };
|
|
1842
|
+
const methods = r.trigger.methods || ["GET", "POST"];
|
|
1843
|
+
if (!methods.includes(method)) return { status: 405, error: "method not allowed" };
|
|
1844
|
+
const res = await this.fire(r, { body, query }, r.trigger.type);
|
|
1845
|
+
return { status: 200, ...res };
|
|
1846
|
+
}
|
|
1847
|
+
async runNow(id, payload = {}) {
|
|
1848
|
+
const r = this.store.get(id);
|
|
1849
|
+
if (!r) return { error: "not found" };
|
|
1850
|
+
return this.fire(r, payload, "manual");
|
|
1851
|
+
}
|
|
1852
|
+
async catchUp(sinceDate, nowDate = this.now()) {
|
|
1853
|
+
const results = [];
|
|
1854
|
+
for (const r of this.store.list()) {
|
|
1855
|
+
if (!r.enabled || r.trigger?.type !== "schedule" || r.trigger.missed !== "catchup") continue;
|
|
1856
|
+
const deadlineMs = (r.trigger.deadline_sec || 3600) * 1e3;
|
|
1857
|
+
const since = new Date(Math.max(sinceDate.getTime(), nowDate.getTime() - deadlineMs));
|
|
1858
|
+
const due = nextFire(r.trigger.cron, since);
|
|
1859
|
+
if (due && due <= nowDate) {
|
|
1860
|
+
const last = r.last_fired_at ? new Date(r.last_fired_at) : /* @__PURE__ */ new Date(0);
|
|
1861
|
+
if (last < due) results.push({ id: r.id, ...await this.fire(r, { catchup: due.toISOString() }, "schedule") });
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return results;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1612
1868
|
function isStructuredMessage(msg) {
|
|
1613
1869
|
return !!(msg.from || msg.fromSession || msg.subject || msg.replyTo || msg.threadId);
|
|
1614
1870
|
}
|
|
@@ -1821,6 +2077,13 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
1821
2077
|
});
|
|
1822
2078
|
return msg;
|
|
1823
2079
|
};
|
|
2080
|
+
const routineStore = new RoutineStore();
|
|
2081
|
+
const syncRoutinesToMetadata = () => {
|
|
2082
|
+
metadata.routines = routineStore.list(sessionId);
|
|
2083
|
+
metadataVersion++;
|
|
2084
|
+
notifyListeners({ type: "update-session", sessionId, metadata: { value: metadata, version: metadataVersion } });
|
|
2085
|
+
callbacks.onMetadataUpdate?.(metadata);
|
|
2086
|
+
};
|
|
1824
2087
|
const rpcHandlers = {
|
|
1825
2088
|
// ── Messages ──
|
|
1826
2089
|
getMessages: async (afterSeq, limit, context) => {
|
|
@@ -1948,6 +2211,49 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
|
|
|
1948
2211
|
callbacks.onUpdateConfig?.(patch);
|
|
1949
2212
|
return { success: true };
|
|
1950
2213
|
},
|
|
2214
|
+
// ── Routines (session-scoped triggers -> message/loop). CRUD + metadata
|
|
2215
|
+
// sync; actual schedule/webhook firing is handled by the supervised
|
|
2216
|
+
// routine server (`svamp routine serve`) reading the shared store. ──
|
|
2217
|
+
listRoutines: async (context) => {
|
|
2218
|
+
authorizeRequest(context, metadata.sharing, "view");
|
|
2219
|
+
return { routines: routineStore.list(sessionId) };
|
|
2220
|
+
},
|
|
2221
|
+
saveRoutine: async (routine, context) => {
|
|
2222
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
2223
|
+
try {
|
|
2224
|
+
const saved = routineStore.save({ ...routine, session_id: sessionId });
|
|
2225
|
+
syncRoutinesToMetadata();
|
|
2226
|
+
return { success: true, routine: saved };
|
|
2227
|
+
} catch (e) {
|
|
2228
|
+
return { success: false, error: e?.message || String(e) };
|
|
2229
|
+
}
|
|
2230
|
+
},
|
|
2231
|
+
removeRoutine: async (id, context) => {
|
|
2232
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
2233
|
+
const r = routineStore.get(id);
|
|
2234
|
+
if (r && r.session_id !== sessionId) return { success: false };
|
|
2235
|
+
const ok = routineStore.remove(id);
|
|
2236
|
+
syncRoutinesToMetadata();
|
|
2237
|
+
return { success: ok };
|
|
2238
|
+
},
|
|
2239
|
+
setRoutineEnabled: async (id, enabled, context) => {
|
|
2240
|
+
authorizeRequest(context, metadata.sharing, "admin");
|
|
2241
|
+
const r = routineStore.get(id);
|
|
2242
|
+
if (r && r.session_id !== sessionId) return { success: false };
|
|
2243
|
+
routineStore.setEnabled(id, enabled);
|
|
2244
|
+
syncRoutinesToMetadata();
|
|
2245
|
+
return { success: true };
|
|
2246
|
+
},
|
|
2247
|
+
runRoutineNow: async (id, context) => {
|
|
2248
|
+
authorizeRequest(context, metadata.sharing, "interact");
|
|
2249
|
+
const r = routineStore.get(id);
|
|
2250
|
+
if (!r || r.session_id !== sessionId) return { error: "not found" };
|
|
2251
|
+
const resolved = new RoutineRunner({ store: routineStore, deliver: async () => {
|
|
2252
|
+
} }).resolveAction(r, {});
|
|
2253
|
+
routineStore.recordRun(id, { via: "manual", delivered: resolved.kind, outcome: "resolved" });
|
|
2254
|
+
syncRoutinesToMetadata();
|
|
2255
|
+
return { success: true, resolved };
|
|
2256
|
+
},
|
|
1951
2257
|
// ── Agent State ──
|
|
1952
2258
|
getAgentState: async (context) => {
|
|
1953
2259
|
authorizeRequest(context, metadata.sharing, "view");
|
|
@@ -5556,11 +5862,16 @@ var claudeAuth = /*#__PURE__*/Object.freeze({
|
|
|
5556
5862
|
updateEnvFile: updateEnvFile
|
|
5557
5863
|
});
|
|
5558
5864
|
|
|
5559
|
-
|
|
5560
|
-
|
|
5865
|
+
function svampHome() {
|
|
5866
|
+
return process.env.SVAMP_HOME || join(homedir$1(), ".svamp");
|
|
5867
|
+
}
|
|
5868
|
+
function cacheFile() {
|
|
5869
|
+
return join(svampHome(), "instance-config.json");
|
|
5870
|
+
}
|
|
5561
5871
|
const CONFIG_FILENAME = "svamp.json";
|
|
5562
5872
|
let _config = null;
|
|
5563
5873
|
let _loaded = false;
|
|
5874
|
+
let _cacheChecked = false;
|
|
5564
5875
|
function stripSlash$1(u) {
|
|
5565
5876
|
return u.replace(/\/+$/, "");
|
|
5566
5877
|
}
|
|
@@ -5588,8 +5899,8 @@ async function fetchConfig(anchor) {
|
|
|
5588
5899
|
}
|
|
5589
5900
|
function readCache() {
|
|
5590
5901
|
try {
|
|
5591
|
-
if (existsSync$1(
|
|
5592
|
-
const data = JSON.parse(readFileSync$1(
|
|
5902
|
+
if (existsSync$1(cacheFile())) {
|
|
5903
|
+
const data = JSON.parse(readFileSync$1(cacheFile(), "utf-8"));
|
|
5593
5904
|
if (data && typeof data === "object" && !Array.isArray(data)) return data;
|
|
5594
5905
|
}
|
|
5595
5906
|
} catch {
|
|
@@ -5598,8 +5909,8 @@ function readCache() {
|
|
|
5598
5909
|
}
|
|
5599
5910
|
function writeCache(cfg) {
|
|
5600
5911
|
try {
|
|
5601
|
-
mkdirSync(
|
|
5602
|
-
writeFileSync(
|
|
5912
|
+
mkdirSync(svampHome(), { recursive: true });
|
|
5913
|
+
writeFileSync(cacheFile(), JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
5603
5914
|
} catch {
|
|
5604
5915
|
}
|
|
5605
5916
|
}
|
|
@@ -5618,38 +5929,43 @@ async function loadInstanceConfig(opts = {}) {
|
|
|
5618
5929
|
applyConfig(fetched);
|
|
5619
5930
|
writeCache(fetched);
|
|
5620
5931
|
_loaded = true;
|
|
5932
|
+
_cacheChecked = true;
|
|
5621
5933
|
return _config;
|
|
5622
5934
|
}
|
|
5623
5935
|
}
|
|
5624
5936
|
const cached = readCache();
|
|
5625
5937
|
if (cached) applyConfig(cached);
|
|
5626
5938
|
_loaded = true;
|
|
5939
|
+
_cacheChecked = true;
|
|
5627
5940
|
return _config;
|
|
5628
5941
|
}
|
|
5629
5942
|
function getLoadedConfig() {
|
|
5630
|
-
if (!_loaded) {
|
|
5943
|
+
if (!_loaded && !_cacheChecked) {
|
|
5631
5944
|
const cached = readCache();
|
|
5632
5945
|
if (cached) applyConfig(cached);
|
|
5633
|
-
|
|
5946
|
+
_cacheChecked = true;
|
|
5634
5947
|
}
|
|
5635
5948
|
return _config;
|
|
5636
5949
|
}
|
|
5637
5950
|
function clearInstanceConfigCache() {
|
|
5638
5951
|
_config = null;
|
|
5639
5952
|
_loaded = false;
|
|
5953
|
+
_cacheChecked = false;
|
|
5640
5954
|
try {
|
|
5641
|
-
if (existsSync$1(
|
|
5955
|
+
if (existsSync$1(cacheFile())) rmSync$1(cacheFile());
|
|
5642
5956
|
} catch {
|
|
5643
5957
|
}
|
|
5644
5958
|
}
|
|
5645
5959
|
function __setConfigForTest(cfg) {
|
|
5646
5960
|
_config = cfg;
|
|
5647
5961
|
_loaded = true;
|
|
5962
|
+
_cacheChecked = true;
|
|
5648
5963
|
if (cfg) applyConfig(cfg);
|
|
5649
5964
|
}
|
|
5650
5965
|
function __resetForTest() {
|
|
5651
5966
|
_config = null;
|
|
5652
5967
|
_loaded = false;
|
|
5968
|
+
_cacheChecked = false;
|
|
5653
5969
|
}
|
|
5654
5970
|
|
|
5655
5971
|
var instanceConfig = /*#__PURE__*/Object.freeze({
|
|
@@ -7337,7 +7653,7 @@ function installBundledSkill(name) {
|
|
|
7337
7653
|
mkdirSync(dst, { recursive: true });
|
|
7338
7654
|
function copyDir(s, d) {
|
|
7339
7655
|
mkdirSync(d, { recursive: true });
|
|
7340
|
-
for (const entry of readdirSync(s, { withFileTypes: true })) {
|
|
7656
|
+
for (const entry of readdirSync$1(s, { withFileTypes: true })) {
|
|
7341
7657
|
const sp = join(s, entry.name);
|
|
7342
7658
|
const dp = join(d, entry.name);
|
|
7343
7659
|
if (entry.isDirectory()) copyDir(sp, dp);
|
|
@@ -8301,7 +8617,7 @@ async function startDaemon(options) {
|
|
|
8301
8617
|
}
|
|
8302
8618
|
}
|
|
8303
8619
|
try {
|
|
8304
|
-
await loadInstanceConfig();
|
|
8620
|
+
await loadInstanceConfig({ force: true });
|
|
8305
8621
|
} catch (err) {
|
|
8306
8622
|
logger.log(`[config] Instance config load failed (continuing with env): ${err?.message || err}`);
|
|
8307
8623
|
}
|
|
@@ -8369,7 +8685,7 @@ async function startDaemon(options) {
|
|
|
8369
8685
|
const list = loadExposedTunnels().filter((t) => t.name !== name);
|
|
8370
8686
|
saveExposedTunnels(list);
|
|
8371
8687
|
}
|
|
8372
|
-
const { ServeManager } = await import('./serveManager-
|
|
8688
|
+
const { ServeManager } = await import('./serveManager-BgnooFJZ.mjs');
|
|
8373
8689
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
8374
8690
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
8375
8691
|
});
|
|
@@ -10942,7 +11258,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
10942
11258
|
const specs = loadExposedTunnels();
|
|
10943
11259
|
if (specs.length === 0) return;
|
|
10944
11260
|
logger.log(`[exposed-tunnels] Restoring ${specs.length} tunnel(s) from ${EXPOSED_TUNNELS_FILE}`);
|
|
10945
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
11261
|
+
const { FrpcTunnel } = await import('./frpc-BfBqHN33.mjs');
|
|
10946
11262
|
for (const spec of specs) {
|
|
10947
11263
|
if (tunnels.has(spec.name)) continue;
|
|
10948
11264
|
try {
|
|
@@ -11689,4 +12005,4 @@ var run = /*#__PURE__*/Object.freeze({
|
|
|
11689
12005
|
stopDaemon: stopDaemon
|
|
11690
12006
|
});
|
|
11691
12007
|
|
|
11692
|
-
export {
|
|
12008
|
+
export { buildSecurityContextFromFlags as A, mergeSecurityContexts as B, buildSessionShareUrl as C, buildMachineShareUrl as D, generateHookSettings as E, DefaultTransport$1 as F, acpBackend as G, acpAgentConfig as H, codexMcpBackend as I, GeminiTransport$1 as J, claudeAuth as K, instanceConfig as L, api as M, run as N, RoutineStore as R, ServeAuth as S, registerSessionService as a, stopDaemon as b, connectToHypha as c, daemonStatus as d, getFrpsSubdomainHost as e, getFrpsServerPort as f, getHyphaServerUrl$1 as g, getFrpsServerAddr as h, getHyphaServerUrl as i, hasCookieToken as j, RoutineRunner as k, getSkillsServer as l, getSkillsWorkspaceName as m, getSkillsCollectionName as n, fetchWithTimeout as o, parseFrontmatter as p, searchSkills as q, registerMachineService as r, startDaemon as s, SKILLS_DIR as t, getSkillInfo as u, downloadSkillFile as v, listSkillFiles as w, normalizeAllowedUser as x, loadSecurityContextConfig as y, resolveSecurityContext as z };
|
|
@@ -54,7 +54,7 @@ async function handleServeCommand() {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
async function serveAdd(args, machineId) {
|
|
57
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
57
|
+
const { connectAndGetMachine } = await import('./commands-Dj7Be8dw.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-
|
|
96
|
+
const { connectAndGetMachine } = await import('./commands-Dj7Be8dw.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-
|
|
185
|
+
const { connectAndGetMachine } = await import('./commands-Dj7Be8dw.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-
|
|
205
|
+
const { connectAndGetMachine } = await import('./commands-Dj7Be8dw.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-
|
|
238
|
+
const { connectAndGetMachine } = await import('./commands-Dj7Be8dw.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 { i as getHyphaServerUrl, S as ServeAuth, j as hasCookieToken } from './run-
|
|
7
|
+
import { i as getHyphaServerUrl, S as ServeAuth, j as hasCookieToken } from './run-rWXfNd7e.mjs';
|
|
8
8
|
import 'os';
|
|
9
9
|
import 'fs/promises';
|
|
10
10
|
import 'url';
|
|
@@ -12,9 +12,9 @@ import 'node:fs';
|
|
|
12
12
|
import 'util';
|
|
13
13
|
import 'node:crypto';
|
|
14
14
|
import 'node:path';
|
|
15
|
+
import 'node:os';
|
|
15
16
|
import 'node:child_process';
|
|
16
17
|
import '@agentclientprotocol/sdk';
|
|
17
|
-
import 'node:os';
|
|
18
18
|
import '@modelcontextprotocol/sdk/client/index.js';
|
|
19
19
|
import '@modelcontextprotocol/sdk/client/stdio.js';
|
|
20
20
|
import '@modelcontextprotocol/sdk/types.js';
|
|
@@ -712,7 +712,7 @@ class ServeManager {
|
|
|
712
712
|
const mount = this.mounts.get(mountName);
|
|
713
713
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, `static-${subdomainSafe}-${mount.linkToken}`]]) : void 0;
|
|
714
714
|
try {
|
|
715
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
715
|
+
const { FrpcTunnel } = await import('./frpc-BfBqHN33.mjs');
|
|
716
716
|
let tunnel;
|
|
717
717
|
tunnel = new FrpcTunnel({
|
|
718
718
|
name: tunnelName,
|