svamp-cli 0.2.247 → 0.2.248

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 (26) hide show
  1. package/dist/{agentCommands-DOzMhE_G.mjs → agentCommands-B_ozIVKT.mjs} +28 -11
  2. package/dist/{auth-B_ZnoKX0.mjs → auth-BLDmT7XN.mjs} +2 -1
  3. package/dist/cli.mjs +62 -61
  4. package/dist/{commands-vCeWXvYJ.mjs → commands-C3TQOYUo.mjs} +2 -1
  5. package/dist/{commands-Bm5q9G35.mjs → commands-CL8YI8k5.mjs} +2 -1
  6. package/dist/{commands-_rMwFmFH.mjs → commands-CRMQUQZG.mjs} +2 -1
  7. package/dist/{commands-F6j9TF4Q.mjs → commands-Cs2fNd-_.mjs} +7 -7
  8. package/dist/{commands-CxOPcoWa.mjs → commands-DS4i8ohA.mjs} +10 -6
  9. package/dist/{commands-BENx03Oz.mjs → commands-Djvi1cZ2.mjs} +3 -2
  10. package/dist/{commands-C2u9U3y6.mjs → commands-DwhA-iV3.mjs} +3 -2
  11. package/dist/{fleet-pMXCRilx.mjs → fleet-CJZOVjOY.mjs} +2 -1
  12. package/dist/{frpc-Di-y4cWK.mjs → frpc-CsjEXBuS.mjs} +2 -1
  13. package/dist/{headlessCli-D6k9Pnum.mjs → headlessCli-qBontdlf.mjs} +3 -2
  14. package/dist/index.mjs +2 -1
  15. package/dist/{package-BJYmQjS2.mjs → package-B7z6GsNX.mjs} +2 -2
  16. package/dist/{rpc-DbY7NUhj.mjs → rpc-BGFgegj5.mjs} +1 -3
  17. package/dist/{rpc-BncvIQMO.mjs → rpc-C1DI7HfZ.mjs} +2 -1
  18. package/dist/{run-CYx_Rhiu.mjs → run-C61rsl7r.mjs} +2 -1
  19. package/dist/{run-1BBi0_ol.mjs → run-CHIppB2r.mjs} +655 -35
  20. package/dist/{scheduler-BkXOKjHn.mjs → scheduler-Csp21zzg.mjs} +1 -3
  21. package/dist/{serveCommands-CESRU6-1.mjs → serveCommands-BYHk72ZO.mjs} +5 -5
  22. package/dist/{serveManager-BZtpClpC.mjs → serveManager-Di3xrvG2.mjs} +3 -2
  23. package/dist/{sideband-BqD4-WWR.mjs → sideband-Cs-kNLye.mjs} +2 -1
  24. package/package.json +2 -2
  25. package/dist/runStore-C5IyfbON.mjs +0 -239
  26. package/dist/store-BTs0H_y0.mjs +0 -148
@@ -1,6 +1,4 @@
1
- import { m as resolveProjectRoot, y as cronMatches } from './run-1BBi0_ol.mjs';
2
- import { l as listWorkflows, i as isWorkflowEnabled, w as workflowCrons } from './store-BTs0H_y0.mjs';
3
- import { r as runWorkflow } from './runStore-C5IyfbON.mjs';
1
+ import { n as resolveProjectRoot, H as listWorkflows, I as isWorkflowEnabled, J as workflowCrons, C as runWorkflow, K as cronMatches } from './run-CHIppB2r.mjs';
4
2
  import 'os';
5
3
  import 'fs/promises';
6
4
  import 'fs';
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-vCeWXvYJ.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-C3TQOYUo.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-vCeWXvYJ.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-C3TQOYUo.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-vCeWXvYJ.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-C3TQOYUo.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-vCeWXvYJ.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-C3TQOYUo.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-vCeWXvYJ.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-C3TQOYUo.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-1BBi0_ol.mjs';
7
+ import { l as getHyphaServerUrl, S as ServeAuth, m as hasCookieToken } from './run-CHIppB2r.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -22,6 +22,7 @@ import '@modelcontextprotocol/sdk/types.js';
22
22
  import 'zod';
23
23
  import 'node:fs/promises';
24
24
  import 'node:util';
25
+ import 'yaml';
25
26
 
26
27
  function generateLinkToken() {
27
28
  return crypto.randomBytes(11).toString("hex");
@@ -750,7 +751,7 @@ class ServeManager {
750
751
  const mount = this.mounts.get(mountName);
751
752
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
752
753
  try {
753
- const { FrpcTunnel } = await import('./frpc-Di-y4cWK.mjs');
754
+ const { FrpcTunnel } = await import('./frpc-CsjEXBuS.mjs');
754
755
  let tunnel;
755
756
  tunnel = new FrpcTunnel({
756
757
  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-1BBi0_ol.mjs';
1
+ import { R as READ_ONLY_TOOLS, N as loadMachineContext, O as buildMachineInstructions, P as machineToolsForRole, Q as buildMachineTools } from './run-CHIppB2r.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
@@ -20,6 +20,7 @@ import '@modelcontextprotocol/sdk/types.js';
20
20
  import 'zod';
21
21
  import 'node:fs/promises';
22
22
  import 'node:util';
23
+ import 'yaml';
23
24
 
24
25
  function toolsForRole(role) {
25
26
  if (role === "admin") return [...READ_ONLY_TOOLS, "run_bash", "send_to_session"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.247",
3
+ "version": "0.2.248",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist bin/skills 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-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-loop-verify.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-cron.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 && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-graceful-restart.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-loop-verify.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-cron.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-workflow-idle.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 && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-graceful-restart.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,239 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
3
- import { randomBytes } from 'node:crypto';
4
- import { join } from 'node:path';
5
- import { c as workflowSteps, d as workflowsDir } from './store-BTs0H_y0.mjs';
6
- import { v as listIssues, t as addIssue } from './run-1BBi0_ol.mjs';
7
-
8
- const MAX_RUNS_PER_WORKFLOW = 50;
9
- function maxRunsPerWorkflow() {
10
- const raw = Number(process.env.SVAMP_MAX_RUNS_PER_WORKFLOW);
11
- if (Number.isFinite(raw) && raw > 0) return Math.min(Math.floor(raw), 5e3);
12
- return MAX_RUNS_PER_WORKFLOW;
13
- }
14
- const MAX_STREAM_CHARS = 8e3;
15
- const STEP_TIMEOUT_MS = 12e4;
16
- function capStream(s, max = MAX_STREAM_CHARS) {
17
- if (s.length <= max) return s;
18
- const head = Math.floor(max * 0.6);
19
- const tail = max - head;
20
- const omitted = s.length - max;
21
- return `${s.slice(0, head)}
22
- \u2026 [${omitted} chars omitted] \u2026
23
- ${s.slice(s.length - tail)}`;
24
- }
25
- function runsDir(projectRoot) {
26
- return join(workflowsDir(projectRoot), ".runs");
27
- }
28
- function runsFile(projectRoot, workflow) {
29
- const safe = workflow.replace(/[^\w.-]+/g, "_");
30
- return join(runsDir(projectRoot), `${safe}.jsonl`);
31
- }
32
- function shortRunId(seed) {
33
- let h = 2166136261;
34
- for (let i = 0; i < seed.length; i++) {
35
- h ^= seed.charCodeAt(i);
36
- h = Math.imul(h, 16777619);
37
- }
38
- return (h >>> 0).toString(36).padStart(7, "0").slice(0, 7);
39
- }
40
- function recordRun(projectRoot, run) {
41
- try {
42
- const dir = runsDir(projectRoot);
43
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
44
- const file = runsFile(projectRoot, run.workflow);
45
- const existing = loadRuns(projectRoot, run.workflow);
46
- const idx = existing.findIndex((r) => r.id === run.id);
47
- if (idx >= 0) existing[idx] = run;
48
- else existing.push(run);
49
- const kept = existing.slice(-maxRunsPerWorkflow());
50
- const tmp = `${file}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
51
- writeFileSync(tmp, kept.map((r) => JSON.stringify(r)).join("\n") + "\n");
52
- renameSync(tmp, file);
53
- } catch {
54
- }
55
- }
56
- function loadRuns(projectRoot, workflow) {
57
- try {
58
- const file = runsFile(projectRoot, workflow);
59
- if (!existsSync(file)) return [];
60
- return readFileSync(file, "utf-8").split("\n").filter(Boolean).map((l) => {
61
- try {
62
- return JSON.parse(l);
63
- } catch {
64
- return null;
65
- }
66
- }).filter((r) => !!r);
67
- } catch {
68
- return [];
69
- }
70
- }
71
- function listRuns(projectRoot, workflow, opts = {}) {
72
- const o = typeof opts === "number" ? { limit: opts } : opts;
73
- const all = loadRuns(projectRoot, workflow);
74
- const total = all.length;
75
- const newestFirst = all.slice().reverse();
76
- const offset = Math.max(0, Math.floor(o.offset ?? 0));
77
- const limit = o.limit != null && o.limit > 0 ? Math.floor(o.limit) : total;
78
- const page = newestFirst.slice(offset, offset + limit).map((r) => {
79
- const failedStep = r.steps.findIndex((s) => s.exitCode !== 0 && s.exitCode !== null || s.timedOut);
80
- const { steps, ...rest } = r;
81
- return { ...rest, stepCount: steps.length, failedStep: failedStep >= 0 ? failedStep : void 0 };
82
- });
83
- return { runs: page, total, offset, hasMore: offset + page.length < total };
84
- }
85
- function getRun(projectRoot, workflow, runId) {
86
- return loadRuns(projectRoot, workflow).find((r) => r.id === runId) || null;
87
- }
88
- function defaultExecStep(root, cmd, env) {
89
- return new Promise((resolve) => {
90
- let stdout = "", stderr = "", timedOut = false, settled = false;
91
- const finish = (exitCode) => {
92
- if (settled) return;
93
- settled = true;
94
- resolve({ exitCode, stdout, stderr, timedOut });
95
- };
96
- try {
97
- const child = spawn("sh", ["-c", cmd], { cwd: root, env, stdio: ["ignore", "pipe", "pipe"] });
98
- const timer = setTimeout(() => {
99
- timedOut = true;
100
- try {
101
- child.kill("SIGKILL");
102
- } catch {
103
- }
104
- }, STEP_TIMEOUT_MS);
105
- child.stdout?.on("data", (d) => {
106
- stdout += d.toString();
107
- if (stdout.length > MAX_STREAM_CHARS * 4) stdout = capStream(stdout);
108
- });
109
- child.stderr?.on("data", (d) => {
110
- stderr += d.toString();
111
- if (stderr.length > MAX_STREAM_CHARS * 4) stderr = capStream(stderr);
112
- });
113
- child.on("error", (e) => {
114
- clearTimeout(timer);
115
- stderr += `
116
- [spawn error] ${e.message}`;
117
- finish(null);
118
- });
119
- child.on("close", (code) => {
120
- clearTimeout(timer);
121
- finish(code);
122
- });
123
- } catch (e) {
124
- stderr += `[spawn threw] ${e?.message || e}`;
125
- finish(null);
126
- }
127
- });
128
- }
129
- function escalateWorkflowFailure(projectRoot, wf, run) {
130
- try {
131
- if (process.env.SVAMP_WORKFLOW_ESCALATE === "0") return void 0;
132
- const failLabel = `wf-fail:${wf.name}`;
133
- const open = listIssues(projectRoot, { label: failLabel }).filter((i) => i.status !== "archived");
134
- if (open.length > 0) return void 0;
135
- const failed = run.steps.find((s) => s.timedOut || s.exitCode !== 0 && s.exitCode !== null) || run.steps[run.steps.length - 1];
136
- const failCause = run.error ? `runner error: ${run.error}` : failed?.timedOut ? `step timed out after ${STEP_TIMEOUT_MS / 1e3}s` : `step exited with code ${failed?.exitCode ?? "null (killed/signal)"}`;
137
- const defLines = workflowSteps(wf).map((s, i) => ` ${i}. ${s.run}`).join("\n") || " (no steps)";
138
- const stderrTail = failed?.stderr ? capStream(failed.stderr, 3e3) : "(none)";
139
- const stdoutTail = failed?.stdout ? capStream(failed.stdout, 1500) : "(none)";
140
- const body = [
141
- `Workflow **${wf.name}** failed on its last run (${run.trigger} trigger${run.triggerContext ? ` \xB7 ${run.triggerContext}` : ""}). Diagnose the failure and **correct the workflow definition** so future runs succeed \u2014 edit it with \`svamp workflow\` (see \`svamp workflow --help\`), or pause/remove it if it's obsolete.`,
142
- "",
143
- "## Failure",
144
- `- Cause: ${failCause}`,
145
- `- Failed step: ${failed ? `#${failed.index} \u2014 \`${failed.cmd}\`` : "(unknown)"}`,
146
- `- Run id: ${run.id} \xB7 finished: ${run.finishedAt || "?"}`,
147
- "",
148
- "## Workflow definition",
149
- "```",
150
- `name: ${wf.name}`,
151
- `steps:`,
152
- defLines,
153
- "```",
154
- "",
155
- "## Failed step stderr (tail)",
156
- "```",
157
- stderrTail,
158
- "```",
159
- "",
160
- "## Failed step stdout (tail)",
161
- "```",
162
- stdoutTail,
163
- "```"
164
- ].join("\n");
165
- const issue = addIssue(projectRoot, {
166
- title: `Fix failing workflow "${wf.name}" (${failCause})`,
167
- labels: ["workflow", "automation", "auto-escalated", failLabel],
168
- verify: { type: "agent", text: `The workflow "${wf.name}" is corrected (or removed/paused) so it no longer fails with: ${failCause}` },
169
- scope: wf.session ? "session" : "project",
170
- session: wf.session ?? null,
171
- triaged: true,
172
- body
173
- });
174
- return issue.id;
175
- } catch {
176
- return void 0;
177
- }
178
- }
179
- async function runWorkflow(projectRoot, wf, opts) {
180
- const now = opts.now || (() => {
181
- const d = /* @__PURE__ */ new Date();
182
- return { iso: d.toISOString(), ms: d.getTime() };
183
- });
184
- const exec = opts.execStep || defaultExecStep;
185
- const start = now();
186
- const id = shortRunId(`${wf.name}:${start.iso}:${start.ms}`);
187
- const env = wf.session ? { ...process.env, SVAMP_SESSION_ID: wf.session } : process.env;
188
- const run = {
189
- id,
190
- workflow: wf.name,
191
- trigger: opts.trigger,
192
- triggerContext: opts.triggerContext,
193
- actor: opts.actor ?? wf.session ?? null,
194
- startedAt: start.iso,
195
- status: "running",
196
- steps: []
197
- };
198
- recordRun(projectRoot, run);
199
- const steps = workflowSteps(wf);
200
- try {
201
- for (let i = 0; i < steps.length; i++) {
202
- const s0 = now();
203
- const r = await exec(projectRoot, steps[i].run, env);
204
- const s1 = now();
205
- run.steps.push({
206
- index: i,
207
- cmd: steps[i].run,
208
- exitCode: r.exitCode,
209
- stdout: capStream(r.stdout),
210
- stderr: capStream(r.stderr),
211
- durationMs: s1.ms - s0.ms,
212
- timedOut: r.timedOut || void 0
213
- });
214
- if (r.timedOut || r.exitCode !== 0) {
215
- run.status = "failed";
216
- break;
217
- }
218
- }
219
- if (run.status === "running") run.status = "success";
220
- } catch (e) {
221
- run.status = "failed";
222
- run.error = e?.message || String(e);
223
- }
224
- const end = now();
225
- run.finishedAt = end.iso;
226
- run.durationMs = end.ms - start.ms;
227
- if (run.status === "failed") {
228
- const escalated = escalateWorkflowFailure(projectRoot, wf, run);
229
- if (escalated) {
230
- run.escalatedIssue = escalated;
231
- opts.log?.(`[workflow] run ${id} "${wf.name}" failed \u2192 escalated to issue #${escalated}`);
232
- }
233
- }
234
- recordRun(projectRoot, run);
235
- opts.log?.(`[workflow] run ${id} "${wf.name}" \u2192 ${run.status} (${run.steps.length} step${run.steps.length === 1 ? "" : "s"})`);
236
- return run;
237
- }
238
-
239
- export { getRun as g, listRuns as l, runWorkflow as r };
@@ -1,148 +0,0 @@
1
- import { existsSync, unlinkSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { parse, stringify } from 'yaml';
4
-
5
- function isWorkflowEnabled(wf) {
6
- return wf.enabled !== false;
7
- }
8
- function workflowSteps(wf) {
9
- return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
10
- }
11
- function workflowCrons(wf) {
12
- return (wf.on?.schedule || []).map((s) => s?.cron).filter((c) => !!c);
13
- }
14
- function workflowsDir(projectRoot) {
15
- return join(projectRoot, ".svamp", "workflows");
16
- }
17
- function workflowPath(projectRoot, name) {
18
- return join(workflowsDir(projectRoot), `${name}.yaml`);
19
- }
20
- function normalizeOn(on) {
21
- if (!on || typeof on !== "object") {
22
- return void 0;
23
- }
24
- const out = {};
25
- if (Array.isArray(on.schedule)) {
26
- const sched = on.schedule.map((s) => ({ cron: String(s?.cron ?? s ?? "") })).filter((s) => s.cron);
27
- if (sched.length) out.schedule = sched;
28
- } else if (typeof on.schedule === "string" && on.schedule.trim()) {
29
- out.schedule = [{ cron: on.schedule.trim() }];
30
- }
31
- if ("workflow_dispatch" in on || on.dispatch === true) out.workflow_dispatch = true;
32
- if (typeof on.channel === "string" && on.channel) out.channel = on.channel;
33
- if (Array.isArray(on.issue) && on.issue.length) out.issue = on.issue.map(String);
34
- return Object.keys(out).length ? out : void 0;
35
- }
36
- function normalizeJobs(jobs) {
37
- if (Array.isArray(jobs)) {
38
- const steps = jobs.map((j) => ({ run: String(j?.run ?? "") })).filter((s) => s.run);
39
- return steps.length ? { run: { steps } } : {};
40
- }
41
- if (jobs && typeof jobs === "object") {
42
- const out = {};
43
- for (const [id, job] of Object.entries(jobs)) {
44
- const rawSteps = Array.isArray(job?.steps) ? job.steps : job?.run ? [{ run: job.run }] : [];
45
- const steps = rawSteps.map((s) => ({ run: String(s?.run ?? ""), ...s?.name ? { name: String(s.name) } : {} })).filter((s) => s.run);
46
- if (steps.length) out[String(id)] = { steps };
47
- }
48
- return out;
49
- }
50
- return {};
51
- }
52
- function parseWorkflow(content) {
53
- try {
54
- const o = parse(content);
55
- if (!o || typeof o !== "object" || !o.name) return null;
56
- return {
57
- name: String(o.name),
58
- on: normalizeOn(o.on),
59
- jobs: normalizeJobs(o.jobs),
60
- session: o.session ?? null,
61
- // Only `enabled: false` disables; anything else (absent/true) is active.
62
- ...o.enabled === false ? { enabled: false } : {}
63
- };
64
- } catch {
65
- return null;
66
- }
67
- }
68
- function serializeWorkflow(wf) {
69
- const clean = { name: wf.name };
70
- if (wf.session) clean.session = wf.session;
71
- if (wf.enabled === false) clean.enabled = false;
72
- const on = {};
73
- if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => ({ cron: s.cron }));
74
- if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
75
- if (wf.on?.channel) on.channel = wf.on.channel;
76
- if (wf.on?.issue?.length) on.issue = wf.on.issue;
77
- if (Object.keys(on).length) clean.on = on;
78
- const jobs = {};
79
- for (const [id, job] of Object.entries(wf.jobs || {})) {
80
- jobs[id] = { steps: (job.steps || []).map((s) => s.name ? { name: s.name, run: s.run } : { run: s.run }) };
81
- }
82
- clean.jobs = Object.keys(jobs).length ? jobs : { run: { steps: [] } };
83
- return stringify(clean);
84
- }
85
- function listWorkflows(projectRoot) {
86
- const dir = workflowsDir(projectRoot);
87
- if (!existsSync(dir)) return [];
88
- const out = [];
89
- for (const name of readdirSync(dir)) {
90
- if (!name.endsWith(".yaml") && !name.endsWith(".yml")) continue;
91
- const p = join(dir, name);
92
- try {
93
- const wf = parseWorkflow(readFileSync(p, "utf-8"));
94
- if (wf) {
95
- try {
96
- const st = statSync(p);
97
- wf.created = new Date(st.birthtimeMs || st.mtimeMs).toISOString();
98
- } catch {
99
- }
100
- out.push(wf);
101
- }
102
- } catch {
103
- }
104
- }
105
- return out.sort((a, b) => a.name.localeCompare(b.name));
106
- }
107
- function getWorkflow(projectRoot, name) {
108
- const p = workflowPath(projectRoot, name);
109
- if (!existsSync(p)) return null;
110
- try {
111
- return parseWorkflow(readFileSync(p, "utf-8"));
112
- } catch {
113
- return null;
114
- }
115
- }
116
- function rawWorkflow(projectRoot, name) {
117
- const p = workflowPath(projectRoot, name);
118
- return existsSync(p) ? readFileSync(p, "utf-8") : null;
119
- }
120
- function saveWorkflow(projectRoot, wf) {
121
- const dir = workflowsDir(projectRoot);
122
- mkdirSync(dir, { recursive: true });
123
- const path = workflowPath(projectRoot, wf.name);
124
- const tmp = `${path}.tmp-${process.pid}`;
125
- writeFileSync(tmp, serializeWorkflow(wf));
126
- renameSync(tmp, path);
127
- }
128
- function setWorkflowEnabled(projectRoot, name, enabled) {
129
- const wf = getWorkflow(projectRoot, name);
130
- if (!wf) return null;
131
- const next = { ...wf };
132
- if (enabled) delete next.enabled;
133
- else next.enabled = false;
134
- saveWorkflow(projectRoot, next);
135
- return next;
136
- }
137
- function removeWorkflow(projectRoot, name) {
138
- const p = workflowPath(projectRoot, name);
139
- if (!existsSync(p)) return false;
140
- try {
141
- unlinkSync(p);
142
- return true;
143
- } catch {
144
- return false;
145
- }
146
- }
147
-
148
- export { saveWorkflow as a, rawWorkflow as b, workflowSteps as c, workflowsDir as d, getWorkflow as g, isWorkflowEnabled as i, listWorkflows as l, removeWorkflow as r, setWorkflowEnabled as s, workflowCrons as w };