svamp-cli 0.2.175 → 0.2.177

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 (33) hide show
  1. package/bin/skills/loop/SKILL.md +1 -1
  2. package/dist/{agentCommands-C4upUtMc.mjs → agentCommands-DWvXxX8p.mjs} +5 -5
  3. package/dist/{auth-BKN98KO4.mjs → auth-Dxka4cE6.mjs} +1 -1
  4. package/dist/cli.mjs +60 -64
  5. package/dist/{commands-D2lD-S4d.mjs → commands--dKRqMv8.mjs} +5 -5
  6. package/dist/{commands-BssfhM-2.mjs → commands-B4u6CUWH.mjs} +1 -1
  7. package/dist/{commands-CSK-jSnk.mjs → commands-Bf-TovMw.mjs} +1 -1
  8. package/dist/{commands-DICHik9Y.mjs → commands-BwBxmMxJ.mjs} +19 -13
  9. package/dist/{commands-BF_Wy2B4.mjs → commands-CwdYZQ-a.mjs} +1 -1
  10. package/dist/{commands-BbqjhR4z.mjs → commands-DVKFo1pt.mjs} +2 -2
  11. package/dist/{commands-D03sBXV4.mjs → commands-iXMwyExn.mjs} +2 -2
  12. package/dist/{fleet-qUmhLn41.mjs → fleet-plROHfB8.mjs} +1 -1
  13. package/dist/{frpc-BDZgTsi8.mjs → frpc-yc5-4EUe.mjs} +1 -1
  14. package/dist/{headlessCli-BG-PcYjM.mjs → headlessCli-D7vLrl7K.mjs} +2 -2
  15. package/dist/index.mjs +1 -1
  16. package/dist/{package-CMB-hfB0.mjs → package-Do-iBoUv.mjs} +2 -2
  17. package/dist/{rpc-CQTahguB.mjs → rpc-D1gr74eF.mjs} +11 -9
  18. package/dist/{rpc-gCEvLUNu.mjs → rpc-DUZqKPK4.mjs} +1 -1
  19. package/dist/{run-Buov6wz-.mjs → run-21-bMHCc.mjs} +37 -402
  20. package/dist/{run-DGs1hcvd.mjs → run-C__yViCA.mjs} +1 -1
  21. package/dist/scheduler-DweevMw_.mjs +93 -0
  22. package/dist/{serveCommands-C6l8yA9_.mjs → serveCommands-BZmiAjxi.mjs} +5 -5
  23. package/dist/{serveManager-B_Bj1AF4.mjs → serveManager-AbWv-Akt.mjs} +2 -2
  24. package/dist/{sideband-BYiyQgZR.mjs → sideband-CzzHtakf.mjs} +1 -1
  25. package/dist/store-BPL__e4D.mjs +125 -0
  26. package/package.json +2 -2
  27. package/bin/skills/loop/bin/routine-cli.mjs +0 -121
  28. package/bin/skills/loop/bin/routine-runner.mjs +0 -125
  29. package/bin/skills/loop/bin/routine-store.mjs +0 -49
  30. package/bin/skills/loop/routines.process.yaml +0 -20
  31. package/bin/skills/loop/test/test-routine-engine.mjs +0 -122
  32. package/dist/commands-CkygtYMZ.mjs +0 -223
  33. package/dist/store-D2HNe-Kv.mjs +0 -74
@@ -436,33 +436,8 @@ function buildTools(deps, skills) {
436
436
  return "(handed off to the deep agent \u2014 it works asynchronously and will report the result back; call get_context to poll progress)";
437
437
  }
438
438
  },
439
- // Create routine / loop / channel. ONLY call after the caller confirmed the
440
- // proposed config (see the propose-then-confirm instruction in context.ts).
441
- {
442
- name: "create_routine",
443
- readOnly: false,
444
- description: "Create a session-scoped routine (cron schedule or webhook \u2192 message or loop). ONLY after the caller confirmed the proposal.",
445
- parameters: { type: "object", properties: {
446
- name: { type: "string", description: "Short human name." },
447
- cron: { type: "string", description: 'Cron for a schedule trigger, e.g. "0 9 * * 1-5". Omit for a webhook.' },
448
- tz: { type: "string", description: "IANA timezone for the schedule." },
449
- action_kind: { type: "string", enum: ["message", "loop"], description: '"message" sends text to this session; "loop" starts a self-verifying loop.' },
450
- message: { type: "string", description: "For message action: the text/template to deliver." },
451
- task: { type: "string", description: "For loop action: the loop task." },
452
- oracle: { type: "string", description: "For loop action: optional pass/fail command." }
453
- }, required: ["name", "action_kind"], additionalProperties: false },
454
- run: async (a) => {
455
- const cron = a?.cron ? str$1(a.cron) : "";
456
- const routine = {
457
- name: str$1(a?.name),
458
- enabled: true,
459
- trigger: cron ? { type: "schedule", cron, ...a?.tz ? { tz: str$1(a.tz) } : {} } : { type: "webhook" },
460
- action: str$1(a?.action_kind) === "loop" ? { kind: "loop", task_template: str$1(a?.task), loop: { task: str$1(a?.task), ...a?.oracle ? { oracle: str$1(a.oracle) } : {} } } : { kind: "message", template: str$1(a?.message) }
461
- };
462
- const r = await deps.saveRoutine(routine);
463
- return r.success ? `Created routine "${str$1(a?.name)}" (${r.routine?.id || "saved"}).` : `Could not create routine: ${r.error || "unknown error"}.`;
464
- }
465
- },
439
+ // Create loop / channel. ONLY call after the caller confirmed the proposed
440
+ // config (see the propose-then-confirm instruction in context.ts).
466
441
  {
467
442
  name: "create_loop",
468
443
  readOnly: false,
@@ -664,13 +639,13 @@ You are WISE Agent, a fast, text-mode companion to the deep coding agent (Claude
664
639
  - use_skill \u2014 load a project skill's full steps by name, then carry them out with run_bash.
665
640
  - run_bash \u2014 run a shell command on the session's machine (when granted).
666
641
  - send_to_session \u2014 hand a clear, reformulated instruction to the deep coding agent (when granted); pass wait=true to block for its reply.
667
- - 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).
642
+ - create_loop / create_channel \u2014 start a self-verifying loop, or set up an inbound channel for this session (when granted). For SCHEDULED automation, hand the deep agent a "svamp workflow add \u2026 --on schedule --cron ..." instruction via send_to_session. ALWAYS propose first and confirm before calling these (see below).
668
643
  - set_checklist / stop_loop \u2014 turn the caller's goals into tracked ISSUES (the backlog = the loop's success criteria) and start the self-verifying loop, or stop a running loop (when granted). Each item becomes one concrete, checkable issue (optional per-item oracle command \u2192 its verify-cmd). ALWAYS propose the items first and confirm before calling.
669
644
 
670
645
  # Instructions
671
646
  - Answer general questions and questions about yourself directly. Use tools only to act on the machine/session.
672
647
  - Take the cheap path: read state directly; delegate anything LONG to summarize_session \u2014 keep your own context small.
673
- - 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.
648
+ - To create a 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_loop / create_channel / set_checklist / stop_loop after they confirm in a follow-up message. Never create or stop without confirmation.
674
649
  - For destructive actions (deleting, stopping, killing), require a verified caller and confirm intent; for safe reads, just do it.
675
650
  - If a tool fails or returns nothing useful, say so plainly \u2014 never fabricate a result.
676
651
  - Report the outcome in one line.`;
@@ -1059,9 +1034,6 @@ function buildSessionDeps(rpc, opts = {}) {
1059
1034
  latestMessage: latestText
1060
1035
  };
1061
1036
  },
1062
- async saveRoutine(routine) {
1063
- return await rpc.saveRoutine(routine, ctx);
1064
- },
1065
1037
  async startLoop(cfg) {
1066
1038
  await rpc.updateConfig({
1067
1039
  loop: {
@@ -1234,37 +1206,6 @@ function cronMatches(expr, date) {
1234
1206
  if (c.domRestricted && c.dowRestricted) return domOk || dowOk;
1235
1207
  return domOk && dowOk;
1236
1208
  }
1237
- function inZone(date, tz) {
1238
- if (!tz) return date;
1239
- try {
1240
- const p = new Intl.DateTimeFormat("en-US", {
1241
- timeZone: tz,
1242
- hour12: false,
1243
- year: "numeric",
1244
- month: "2-digit",
1245
- day: "2-digit",
1246
- hour: "2-digit",
1247
- minute: "2-digit"
1248
- }).formatToParts(date).reduce((o, x) => {
1249
- o[x.type] = x.value;
1250
- return o;
1251
- }, {});
1252
- return new Date(+p.year, +p.month - 1, +p.day, +(p.hour === "24" ? 0 : p.hour), +p.minute);
1253
- } catch {
1254
- return date;
1255
- }
1256
- }
1257
- function nextFire(expr, from, tz) {
1258
- const c = parseCron(expr);
1259
- const d = new Date(from.getTime());
1260
- d.setSeconds(0, 0);
1261
- d.setMinutes(d.getMinutes() + 1);
1262
- for (let i = 0; i < 366 * 24 * 60; i++) {
1263
- if (cronMatches(c, tz ? inZone(d, tz) : d)) return new Date(d.getTime());
1264
- d.setMinutes(d.getMinutes() + 1);
1265
- }
1266
- return null;
1267
- }
1268
1209
  function resolvePath(ctx, path) {
1269
1210
  return path.split(".").reduce((o, k) => o == null ? void 0 : o[k], ctx);
1270
1211
  }
@@ -1274,42 +1215,9 @@ function renderTemplate(template, ctx) {
1274
1215
  return v == null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
1275
1216
  });
1276
1217
  }
1277
- const TRIGGER_TYPES = ["manual", "schedule", "webhook", "api"];
1278
- const ACTION_KINDS = ["message", "loop", "verify"];
1279
- const OVERLAP = ["queue", "skip", "replace"];
1280
- function validateRoutine(r) {
1281
- const errs = [];
1282
- if (!r || typeof r !== "object") return ["routine must be an object"];
1283
- if (!r.session_id) errs.push("session_id required");
1284
- if (!r.name) errs.push("name required");
1285
- const t = r.trigger;
1286
- if (!t || !TRIGGER_TYPES.includes(t.type)) errs.push(`trigger.type must be one of ${TRIGGER_TYPES.join("|")}`);
1287
- if (t?.type === "schedule") {
1288
- try {
1289
- parseCron(t.cron);
1290
- } catch (e) {
1291
- errs.push(`trigger.cron: ${e.message}`);
1292
- }
1293
- if (t.missed && !["catchup", "skip"].includes(t.missed)) errs.push("trigger.missed must be catchup|skip");
1294
- }
1295
- const a = r.action;
1296
- if (!a || !ACTION_KINDS.includes(a.kind)) errs.push(`action.kind must be one of ${ACTION_KINDS.join("|")}`);
1297
- if (a?.kind === "message" && !a.template) errs.push("action.template required for message action");
1298
- if (a?.kind === "loop" && !a.loop && !a.task_template) errs.push("action.loop or action.task_template required for loop action");
1299
- if (a?.kind === "verify" && !r.dir) errs.push("a verify (backlog watchdog) action requires a dir (the project root holding .svamp/issues)");
1300
- if (r.overlap && !OVERLAP.includes(r.overlap)) errs.push(`overlap must be one of ${OVERLAP.join("|")}`);
1301
- if (r.bind && r.bind !== "stateful" && r.bind !== "stateless") errs.push('bind must be "stateful" or "stateless"');
1302
- if (r.bind === "stateless") {
1303
- if (!r.dir) errs.push("a stateless routine requires a dir (the project folder to spawn the one-shot in)");
1304
- if (a?.kind === "loop") errs.push('a stateless routine cannot use a loop action (needs a persistent session); use a message action or bind "stateful"');
1305
- }
1306
- if ((t?.type === "webhook" || t?.type === "api") && t.public && a?.kind === "loop")
1307
- errs.push("a public webhook/api may not use a loop action (unauthenticated task injection) \u2014 use a message action or require a key");
1308
- return errs;
1309
- }
1310
1218
 
1311
- const genId$1 = () => "c_" + randomBytes(5).toString("hex");
1312
- const genKey$1 = () => "ck_" + randomBytes(18).toString("base64url");
1219
+ const genId = () => "c_" + randomBytes(5).toString("hex");
1220
+ const genKey = () => "ck_" + randomBytes(18).toString("base64url");
1313
1221
  const DEFAULT_TEMPLATE = `<inbound-message from="\${sender.name}" sender-type="\${sender.kind}" verified="\${sender.verified}" channel="\${channel.name}" call-id="\${call.id}" at="\${now}">
1314
1222
  \${body.message}
1315
1223
  </inbound-message>`;
@@ -1392,7 +1300,7 @@ class ChannelStore {
1392
1300
  }
1393
1301
  save(channel) {
1394
1302
  const c = { enabled: true, bind: "dynamic", template: DEFAULT_TEMPLATE, last_calls: [], ...channel };
1395
- if (!c.id) c.id = genId$1();
1303
+ if (!c.id) c.id = genId();
1396
1304
  const errs = validateChannel(c);
1397
1305
  if (errs.length) throw new Error("invalid channel: " + errs.join("; "));
1398
1306
  mkdirSync(this.dir, { recursive: true });
@@ -1427,7 +1335,7 @@ class ChannelStore {
1427
1335
  const c = this.get(id);
1428
1336
  if (!c) return null;
1429
1337
  c.identity.callers = c.identity.callers || [];
1430
- const caller = { name, kind, key: genKey$1() };
1338
+ const caller = { name, kind, key: genKey() };
1431
1339
  c.identity.callers.push(caller);
1432
1340
  this.save(c);
1433
1341
  return caller;
@@ -2791,7 +2699,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
2791
2699
  const tunnels = handlers.tunnels;
2792
2700
  if (!tunnels) throw new Error("Tunnel management not available");
2793
2701
  if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
2794
- const { FrpcTunnel } = await import('./frpc-BDZgTsi8.mjs');
2702
+ const { FrpcTunnel } = await import('./frpc-yc5-4EUe.mjs');
2795
2703
  const tunnel = new FrpcTunnel({
2796
2704
  name: params.name,
2797
2705
  ports: params.ports,
@@ -3238,7 +3146,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3238
3146
  }
3239
3147
  const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
3240
3148
  const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
3241
- const { toolsForRole } = await import('./sideband-BYiyQgZR.mjs');
3149
+ const { toolsForRole } = await import('./sideband-CzzHtakf.mjs');
3242
3150
  const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
3243
3151
  return fmt(r2);
3244
3152
  }
@@ -3337,7 +3245,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
3337
3245
  if (r.error || !r.sender) return { error: r.error || "unauthorized" };
3338
3246
  const callId = "call_" + Math.random().toString(16).slice(2, 12);
3339
3247
  const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
3340
- const { queryCore } = await import('./commands-BssfhM-2.mjs');
3248
+ const { queryCore } = await import('./commands-B4u6CUWH.mjs');
3341
3249
  const timeout = c.reply?.timeout_sec || 120;
3342
3250
  let result;
3343
3251
  try {
@@ -3481,209 +3389,6 @@ ${d?.error || "not found"}`;
3481
3389
  };
3482
3390
  }
3483
3391
 
3484
- function defaultRoutinesDir() {
3485
- return process.env.SVAMP_ROUTINES_DIR || join(homedir(), ".svamp", "routines");
3486
- }
3487
- const genId = () => "rt_" + randomBytes(5).toString("hex");
3488
- const genKey = () => randomBytes(18).toString("base64url");
3489
- class RoutineStore {
3490
- dir;
3491
- constructor(dir = defaultRoutinesDir()) {
3492
- this.dir = dir;
3493
- mkdirSync(dir, { recursive: true });
3494
- }
3495
- _path(id) {
3496
- return join(this.dir, `${id}.json`);
3497
- }
3498
- list(sessionId) {
3499
- const all = readdirSync(this.dir).filter((f) => f.endsWith(".json")).map((f) => {
3500
- try {
3501
- return JSON.parse(readFileSync(join(this.dir, f), "utf8"));
3502
- } catch {
3503
- return null;
3504
- }
3505
- }).filter((r) => !!r);
3506
- return sessionId ? all.filter((r) => r.session_id === sessionId) : all;
3507
- }
3508
- get(id) {
3509
- try {
3510
- return JSON.parse(readFileSync(this._path(id), "utf8"));
3511
- } catch {
3512
- return null;
3513
- }
3514
- }
3515
- save(routine) {
3516
- const r = { overlap: "queue", enabled: true, last_runs: [], ...routine };
3517
- if (!r.id) r.id = genId();
3518
- if ((r.trigger?.type === "webhook" || r.trigger?.type === "api") && !r.trigger.key) r.trigger.key = genKey();
3519
- const errs = validateRoutine(r);
3520
- if (errs.length) throw new Error("invalid routine: " + errs.join("; "));
3521
- const tmp = this._path(r.id) + ".tmp";
3522
- writeFileSync(tmp, JSON.stringify(r, null, 2));
3523
- renameSync(tmp, this._path(r.id));
3524
- return r;
3525
- }
3526
- remove(id) {
3527
- const p = this._path(id);
3528
- if (existsSync(p)) {
3529
- rmSync(p);
3530
- return true;
3531
- }
3532
- return false;
3533
- }
3534
- setEnabled(id, enabled) {
3535
- const r = this.get(id);
3536
- if (!r) return null;
3537
- r.enabled = enabled;
3538
- return this.save(r);
3539
- }
3540
- recordRun(id, entry) {
3541
- const r = this.get(id);
3542
- if (!r) return null;
3543
- r.last_runs = [{ firedAt: (/* @__PURE__ */ new Date()).toISOString(), via: "manual", delivered: "message", outcome: "ok", ...entry }, ...r.last_runs || []].slice(0, 20);
3544
- return this.save(r);
3545
- }
3546
- }
3547
-
3548
- const minuteKey = (d) => `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}-${d.getHours()}-${d.getMinutes()}`;
3549
- class RoutineRunner {
3550
- store;
3551
- deliver;
3552
- now;
3553
- onReplace;
3554
- log;
3555
- active = /* @__PURE__ */ new Set();
3556
- _firedMinute = /* @__PURE__ */ new Map();
3557
- constructor(o) {
3558
- this.store = o.store;
3559
- this.deliver = o.deliver;
3560
- this.now = o.now || (() => /* @__PURE__ */ new Date());
3561
- this.onReplace = o.onReplace;
3562
- this.log = o.log || (() => {
3563
- });
3564
- }
3565
- resolveAction(routine, payload = {}) {
3566
- const ctx = { ...payload, now: this.now().toISOString(), routine: { id: routine.id, name: routine.name } };
3567
- const a = routine.action;
3568
- if (a.kind === "message") return { kind: "message", text: renderTemplate(a.template || "", ctx) };
3569
- if (a.kind === "verify") return { kind: "verify" };
3570
- const task = renderTemplate(a.task_template || a.loop?.task || "", ctx);
3571
- return { kind: "loop", task, loop: a.loop };
3572
- }
3573
- markDone(id) {
3574
- this.active.delete(id);
3575
- }
3576
- // Deliveries counted today via a dedicated counter (NOT derived from the
3577
- // capped last_runs audit, which would undercount past 20 runs/day).
3578
- _deliveredToday(routine) {
3579
- const today = this.now().toDateString();
3580
- return routine.daily && routine.daily.date === today ? routine.daily.n : 0;
3581
- }
3582
- // `active` guards genuinely CONCURRENT in-flight deliveries only — the runner
3583
- // cannot observe a spawned loop's full duration (fire-and-forget), so it is
3584
- // cleared once delivery returns. `replace` calls onReplace (best-effort) then proceeds.
3585
- async fire(routine, payload = {}, via = "manual") {
3586
- if (!routine.enabled) return { skipped: "disabled" };
3587
- if (routine.action?.kind === "loop" && (routine.trigger?.type === "webhook" || routine.trigger?.type === "api") && routine.trigger?.public)
3588
- return { skipped: "forbidden (public webhook + loop)" };
3589
- const today = this.now().toDateString();
3590
- const r0 = this.store.get(routine.id) || routine;
3591
- const usedToday = r0.daily && r0.daily.date === today ? r0.daily.n : 0;
3592
- if (routine.daily_cap && usedToday >= routine.daily_cap) {
3593
- this.store.recordRun(routine.id, { via, delivered: routine.action.kind, outcome: "skipped (daily cap)" });
3594
- return { skipped: "daily_cap" };
3595
- }
3596
- if (this.active.has(routine.id)) {
3597
- if (routine.overlap === "skip") {
3598
- this.store.recordRun(routine.id, { via, delivered: routine.action.kind, outcome: "skipped (busy)" });
3599
- return { skipped: "busy" };
3600
- }
3601
- if (routine.overlap === "replace") {
3602
- try {
3603
- this.onReplace?.(routine.id);
3604
- } catch {
3605
- }
3606
- }
3607
- }
3608
- this.active.add(routine.id);
3609
- if (routine.daily_cap) {
3610
- r0.daily = { date: today, n: usedToday + 1 };
3611
- this.store.save(r0);
3612
- }
3613
- const resolved = this.resolveAction(routine, payload);
3614
- let outcome = "delivered";
3615
- const refundDailyCap = () => {
3616
- if (!routine.daily_cap) return;
3617
- const rb = this.store.get(routine.id);
3618
- if (rb?.daily?.date === today && rb.daily.n > 0) {
3619
- rb.daily.n -= 1;
3620
- this.store.save(rb);
3621
- }
3622
- };
3623
- try {
3624
- const res = await this.deliver({ routine, action: routine.action, resolved, payload, via });
3625
- if (res && res.skipped) {
3626
- outcome = `skipped (${res.skipped})`;
3627
- this.log(`[routine] ${routine.id || routine.name}: ${outcome}`);
3628
- refundDailyCap();
3629
- }
3630
- } catch (e) {
3631
- outcome = "error: " + (e?.message || e);
3632
- refundDailyCap();
3633
- } finally {
3634
- this.active.delete(routine.id);
3635
- }
3636
- const r = this.store.get(routine.id) || routine;
3637
- r.last_fired_at = this.now().toISOString();
3638
- r.last_runs = [{ firedAt: r.last_fired_at, via, delivered: resolved.kind, outcome }, ...r.last_runs || []].slice(0, 20);
3639
- this.store.save(r);
3640
- return { fired: true, via, resolved, outcome };
3641
- }
3642
- async tick(date = this.now()) {
3643
- const results = [];
3644
- for (const r of this.store.list()) {
3645
- if (!r.enabled || r.trigger?.type !== "schedule") continue;
3646
- if (!cronMatches(r.trigger.cron, inZone(date, r.trigger.tz))) continue;
3647
- const mk = minuteKey(date);
3648
- if (this._firedMinute.get(r.id) === mk) continue;
3649
- this._firedMinute.set(r.id, mk);
3650
- results.push({ id: r.id, ...await this.fire(r, { tick: date.toISOString() }, "schedule") });
3651
- }
3652
- return results;
3653
- }
3654
- async webhook(id, opts = {}) {
3655
- const { key, method = "POST", body = {}, query = {} } = opts;
3656
- const r = this.store.get(id);
3657
- if (!r) return { status: 404, error: "routine not found" };
3658
- if (r.trigger?.type !== "webhook" && r.trigger?.type !== "api") return { status: 400, error: "not a webhook routine" };
3659
- if (!r.enabled) return { status: 409, error: "routine disabled" };
3660
- if (!r.trigger.public && r.trigger.key && key !== r.trigger.key) return { status: 401, error: "bad key" };
3661
- const methods = r.trigger.methods || ["GET", "POST"];
3662
- if (!methods.includes(method)) return { status: 405, error: "method not allowed" };
3663
- const res = await this.fire(r, { body, query }, r.trigger.type);
3664
- return { status: 200, ...res };
3665
- }
3666
- async runNow(id, payload = {}) {
3667
- const r = this.store.get(id);
3668
- if (!r) return { error: "not found" };
3669
- return this.fire(r, payload, "manual");
3670
- }
3671
- async catchUp(sinceDate, nowDate = this.now()) {
3672
- const results = [];
3673
- for (const r of this.store.list()) {
3674
- if (!r.enabled || r.trigger?.type !== "schedule" || r.trigger.missed !== "catchup") continue;
3675
- const deadlineMs = (r.trigger.deadline_sec || 3600) * 1e3;
3676
- const since = new Date(Math.max(sinceDate.getTime(), nowDate.getTime() - deadlineMs));
3677
- const due = nextFire(r.trigger.cron, since, r.trigger.tz);
3678
- if (due && due <= nowDate) {
3679
- const last = r.last_fired_at ? new Date(r.last_fired_at) : /* @__PURE__ */ new Date(0);
3680
- if (last < due) results.push({ id: r.id, ...await this.fire(r, { catchup: due.toISOString() }, "schedule") });
3681
- }
3682
- }
3683
- return results;
3684
- }
3685
- }
3686
-
3687
3392
  const PARTICIPANTS_CHANNEL_ID = "sys-participants";
3688
3393
  function channelPublicView(c) {
3689
3394
  return { id: c.id, name: c.name, description: c.description, identity: { mode: c.identity?.mode }, action: c.action?.kind, bind: c.bind };
@@ -3961,13 +3666,6 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
3961
3666
  });
3962
3667
  return msg;
3963
3668
  };
3964
- const routineStore = new RoutineStore();
3965
- const syncRoutinesToMetadata = () => {
3966
- metadata.routines = routineStore.list(sessionId);
3967
- metadataVersion++;
3968
- notifyListeners({ type: "update-session", sessionId, metadata: { value: metadata, version: metadataVersion } });
3969
- callbacks.onMetadataUpdate?.(metadata);
3970
- };
3971
3669
  const channelStore = new ChannelStore(initialMetadata.path);
3972
3670
  const meetingActivity = [];
3973
3671
  const channelOutbox = new ChannelOutbox(initialMetadata.path);
@@ -4242,70 +3940,6 @@ function createSessionStore(server, sessionId, initialMetadata, initialAgentStat
4242
3940
  callbacks.onUpdateConfig?.(patch);
4243
3941
  return { success: true };
4244
3942
  },
4245
- // ── Routines (session-scoped triggers -> message/loop). CRUD + metadata
4246
- // sync; actual schedule/webhook firing is handled by the supervised
4247
- // routine server (`svamp routine serve`) reading the shared store. ──
4248
- listRoutines: async (context) => {
4249
- authorizeRequest(context, metadata.sharing, "view");
4250
- return { routines: routineStore.list(sessionId) };
4251
- },
4252
- saveRoutine: async (routine, context) => {
4253
- authorizeRequest(context, metadata.sharing, "admin");
4254
- try {
4255
- const saved = routineStore.save({ ...routine, session_id: sessionId, dir: routine.dir || metadata.path });
4256
- syncRoutinesToMetadata();
4257
- return { success: true, routine: saved };
4258
- } catch (e) {
4259
- return { success: false, error: e?.message || String(e) };
4260
- }
4261
- },
4262
- removeRoutine: async (id, context) => {
4263
- authorizeRequest(context, metadata.sharing, "admin");
4264
- const r = routineStore.get(id);
4265
- if (r && r.session_id !== sessionId) return { success: false };
4266
- const ok = routineStore.remove(id);
4267
- syncRoutinesToMetadata();
4268
- return { success: ok };
4269
- },
4270
- setRoutineEnabled: async (id, enabled, context) => {
4271
- authorizeRequest(context, metadata.sharing, "admin");
4272
- const r = routineStore.get(id);
4273
- if (r && r.session_id !== sessionId) return { success: false };
4274
- routineStore.setEnabled(id, enabled);
4275
- syncRoutinesToMetadata();
4276
- return { success: true };
4277
- },
4278
- runRoutineNow: async (id, context) => {
4279
- authorizeRequest(context, metadata.sharing, "interact");
4280
- const r = routineStore.get(id);
4281
- if (!r || r.session_id !== sessionId) return { error: "not found" };
4282
- const resolved = new RoutineRunner({ store: routineStore, deliver: async () => {
4283
- } }).resolveAction(r, {});
4284
- let outcome = "delivered";
4285
- try {
4286
- if (r.bind === "stateless") {
4287
- if (!r.dir || !callbacks.onRunStatelessRoutine) {
4288
- outcome = "stateless run-now unavailable (no dir/handler)";
4289
- } else {
4290
- callbacks.onRunStatelessRoutine(r.dir, resolved.text || resolved.task || "run");
4291
- outcome = "spawned stateless one-shot";
4292
- }
4293
- } else {
4294
- const text = resolved.kind === "message" ? resolved.text || "" : `Begin the loop (LOOP MODE). Task: ${resolved.task}. Iterate until the gate lets you stop.`;
4295
- await rpcHandlers.sendMessage(
4296
- JSON.stringify({ role: "user", content: { type: "text", text }, meta: { sentFrom: "routine-run-now" } }),
4297
- void 0,
4298
- { sentFrom: "routine-run-now" },
4299
- context
4300
- );
4301
- }
4302
- } catch (e) {
4303
- outcome = "error: " + (e?.message || e);
4304
- }
4305
- routineStore.recordRun(id, { via: "manual", delivered: resolved.kind, outcome });
4306
- syncRoutinesToMetadata();
4307
- return { success: !/error|unavailable/.test(outcome), resolved, outcome };
4308
- },
4309
3943
  // ── Channels (project-folder config; served by the channel server) ──
4310
3944
  listChannels: async (context) => {
4311
3945
  authorizeRequest(context, metadata.sharing, "view");
@@ -11907,7 +11541,7 @@ async function startDaemon(options) {
11907
11541
  saveExposedTunnels(list);
11908
11542
  }
11909
11543
  async function createExposedTunnel(spec) {
11910
- const { FrpcTunnel } = await import('./frpc-BDZgTsi8.mjs');
11544
+ const { FrpcTunnel } = await import('./frpc-yc5-4EUe.mjs');
11911
11545
  const tunnel = new FrpcTunnel({
11912
11546
  name: spec.name,
11913
11547
  ports: spec.ports,
@@ -11927,7 +11561,7 @@ async function startDaemon(options) {
11927
11561
  return tunnel;
11928
11562
  }
11929
11563
  const tunnelRecreateState = /* @__PURE__ */ new Map();
11930
- const { ServeManager } = await import('./serveManager-B_Bj1AF4.mjs');
11564
+ const { ServeManager } = await import('./serveManager-AbWv-Akt.mjs');
11931
11565
  const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
11932
11566
  ensureAutoInstalledSkills(logger).catch(() => {
11933
11567
  });
@@ -13662,15 +13296,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13662
13296
  }
13663
13297
  }
13664
13298
  },
13665
- onRunStatelessRoutine: (dir, prompt) => {
13666
- try {
13667
- const child = spawn$1("svamp", ["session", "query", dir, prompt], { detached: true, stdio: "ignore" });
13668
- child.unref();
13669
- logger.log(`[routine] run-now: spawned stateless one-shot in ${dir}`);
13670
- } catch (e) {
13671
- logger.log(`[routine] run-now stateless spawn failed: ${e?.message || e}`);
13672
- }
13673
- },
13674
13299
  onMetadataUpdate: (newMeta) => {
13675
13300
  const oldTitle = sessionMetadata.summary?.text;
13676
13301
  const newTitle = newMeta.summary?.text;
@@ -13727,11 +13352,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
13727
13352
  });
13728
13353
  },
13729
13354
  onIssue: async (params) => {
13730
- const { issueRpc } = await import('./rpc-gCEvLUNu.mjs');
13355
+ const { issueRpc } = await import('./rpc-DUZqKPK4.mjs');
13731
13356
  return issueRpc(params?.cwd || directory, params || {});
13732
13357
  },
13733
13358
  onWorkflow: async (params) => {
13734
- const { workflowRpc } = await import('./rpc-CQTahguB.mjs');
13359
+ const { workflowRpc } = await import('./rpc-D1gr74eF.mjs');
13735
13360
  return workflowRpc(params?.cwd || directory, params || {});
13736
13361
  },
13737
13362
  onRipgrep: async (args, cwd) => {
@@ -14172,15 +13797,6 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14172
13797
  }
14173
13798
  }
14174
13799
  },
14175
- onRunStatelessRoutine: (dir, prompt) => {
14176
- try {
14177
- const child = spawn$1("svamp", ["session", "query", dir, prompt], { detached: true, stdio: "ignore" });
14178
- child.unref();
14179
- logger.log(`[routine] run-now: spawned stateless one-shot in ${dir}`);
14180
- } catch (e) {
14181
- logger.log(`[routine] run-now stateless spawn failed: ${e?.message || e}`);
14182
- }
14183
- },
14184
13800
  onMetadataUpdate: (newMeta) => {
14185
13801
  const oldTitleAcp = sessionMetadata.summary?.text;
14186
13802
  const newTitleAcp = newMeta.summary?.text;
@@ -14246,11 +13862,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
14246
13862
  });
14247
13863
  },
14248
13864
  onIssue: async (params) => {
14249
- const { issueRpc } = await import('./rpc-gCEvLUNu.mjs');
13865
+ const { issueRpc } = await import('./rpc-DUZqKPK4.mjs');
14250
13866
  return issueRpc(params?.cwd || directory, params || {});
14251
13867
  },
14252
13868
  onWorkflow: async (params) => {
14253
- const { workflowRpc } = await import('./rpc-CQTahguB.mjs');
13869
+ const { workflowRpc } = await import('./rpc-D1gr74eF.mjs');
14254
13870
  return workflowRpc(params?.cwd || directory, params || {});
14255
13871
  },
14256
13872
  onRipgrep: async (args, cwd) => {
@@ -15003,6 +14619,24 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15003
14619
  const PING_TIMEOUT_MS = 15e3;
15004
14620
  const POST_RECONNECT_GRACE_MS = 2e4;
15005
14621
  const RECONNECT_JITTER_MS = 2500;
14622
+ const { WorkflowScheduler } = await import('./scheduler-DweevMw_.mjs');
14623
+ const workflowScheduler = new WorkflowScheduler({
14624
+ projectRoots: () => {
14625
+ const dirs = /* @__PURE__ */ new Set();
14626
+ for (const s of pidToTrackedSession.values()) {
14627
+ if (!s.stopped && s.directory) dirs.add(s.directory);
14628
+ }
14629
+ return [...dirs];
14630
+ },
14631
+ log: (m) => logger.log(m)
14632
+ });
14633
+ const workflowSchedulerInterval = setInterval(() => {
14634
+ try {
14635
+ workflowScheduler.tick(/* @__PURE__ */ new Date());
14636
+ } catch (e) {
14637
+ logger.log(`[workflow] scheduler tick error: ${e?.message || e}`);
14638
+ }
14639
+ }, 2e4);
15006
14640
  let heartbeatRunning = false;
15007
14641
  let lastReconnectAt = 0;
15008
14642
  let heartbeatCycle = 0;
@@ -15225,6 +14859,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
15225
14859
  const cleanup = async (source) => {
15226
14860
  logger.log(`Cleaning up (source: ${source})...`);
15227
14861
  clearInterval(heartbeatInterval);
14862
+ clearInterval(workflowSchedulerInterval);
15228
14863
  if (proxyTokenRefreshInterval) clearInterval(proxyTokenRefreshInterval);
15229
14864
  if (oauthRefreshInterval) clearInterval(oauthRefreshInterval);
15230
14865
  if (unhandledRejectionResetTimer) clearTimeout(unhandledRejectionResetTimer);
@@ -15563,4 +15198,4 @@ var run = /*#__PURE__*/Object.freeze({
15563
15198
  writeStopMarker: writeStopMarker
15564
15199
  });
15565
15200
 
15566
- export { describeMisconfiguration as $, buildMachineInstructions as A, machineToolsForRole as B, buildMachineTools as C, parseFrontmatter as D, getSkillsServer as E, getSkillsWorkspaceName as F, getSkillsCollectionName as G, fetchWithTimeout as H, searchSkills as I, SKILLS_DIR as J, getSkillInfo as K, downloadSkillFile as L, listSkillFiles as M, resolveModel as N, formatHandle as O, normalizeAllowedUser as P, loadSecurityContextConfig as Q, RoutineStore as R, ServeAuth as S, resolveSecurityContext as T, buildSecurityContextFromFlags as U, mergeSecurityContexts as V, buildSessionShareUrl as W, computeOutboundHop as X, buildMachineShareUrl as Y, parseHandle as Z, handleMatchesMetadata as _, createSessionStore as a, buildMachineDeps as a0, composeSessionId as a1, generateFriendlyName as a2, generateHookSettings as a3, projectInfo as a4, DefaultTransport$1 as a5, acpBackend as a6, acpAgentConfig as a7, codexMcpBackend as a8, GeminiTransport$1 as a9, claudeAuth as aa, instanceConfig as ab, api as ac, run as ad, 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, addComment as o, addIssue as p, listIssues as q, registerMachineService as r, startDaemon as s, searchIssues as t, updateIssue as u, summarize as v, RoutineRunner as w, shortId as x, READ_ONLY_TOOLS as y, loadMachineContext as z };
15201
+ export { buildMachineDeps as $, machineToolsForRole as A, buildMachineTools as B, parseFrontmatter as C, getSkillsServer as D, getSkillsWorkspaceName as E, getSkillsCollectionName as F, fetchWithTimeout as G, searchSkills as H, SKILLS_DIR as I, getSkillInfo as J, downloadSkillFile as K, listSkillFiles as L, resolveModel as M, formatHandle as N, normalizeAllowedUser as O, loadSecurityContextConfig as P, resolveSecurityContext as Q, READ_ONLY_TOOLS as R, ServeAuth as S, buildSecurityContextFromFlags as T, mergeSecurityContexts as U, buildSessionShareUrl as V, computeOutboundHop as W, buildMachineShareUrl as X, parseHandle as Y, handleMatchesMetadata as Z, describeMisconfiguration as _, createSessionStore as a, composeSessionId as a0, generateFriendlyName as a1, generateHookSettings as a2, projectInfo as a3, DefaultTransport$1 as a4, acpBackend as a5, acpAgentConfig as a6, codexMcpBackend as a7, GeminiTransport$1 as a8, claudeAuth as a9, instanceConfig as aa, api as ab, run as ac, 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, addComment as o, addIssue as p, listIssues as q, registerMachineService as r, startDaemon as s, searchIssues as t, updateIssue as u, cronMatches as v, summarize as w, shortId as x, loadMachineContext as y, buildMachineInstructions as z };
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a1 as composeSessionId, a2 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a3 as generateHookSettings } from './run-Buov6wz-.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { a0 as composeSessionId, a1 as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, a2 as generateHookSettings } from './run-21-bMHCc.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';