@agentic-surfaces/cli 0.1.28 → 0.1.30
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/index.js +246 -52
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
2
|
-
import { join, dirname, resolve } from "node:path";
|
|
1
|
+
import { readFileSync, readdirSync, existsSync, watch as watchFs, } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve, relative, sep } from "node:path";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { loadWorkflow, runWorkflowOnce, Scheduler, loadProjectConfig, buildWorkflowRunner, defaultRegistry, parseAgentFile, FilePauseState } from "@agentic-surfaces/core";
|
|
4
|
+
import { loadWorkflow, runWorkflowOnce, Scheduler, loadProjectConfig, buildWorkflowRunner, defaultRegistry, parseAgentFile, FilePauseState, } from "@agentic-surfaces/core";
|
|
5
5
|
import { serve, StreamingObserver } from "@agentic-surfaces/server";
|
|
6
6
|
import { buildServices } from "./services.js";
|
|
7
7
|
const CONFIG = "agentic-surfaces.config.yaml";
|
|
@@ -19,7 +19,8 @@ function loadDotenv(dir = process.cwd()) {
|
|
|
19
19
|
if (!m)
|
|
20
20
|
continue; // skips blanks + `# comments`
|
|
21
21
|
let val = m[2];
|
|
22
|
-
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
22
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
23
|
+
(val.startsWith("'") && val.endsWith("'")))
|
|
23
24
|
val = val.slice(1, -1);
|
|
24
25
|
if (process.env[m[1]] === undefined)
|
|
25
26
|
process.env[m[1]] = val;
|
|
@@ -41,15 +42,63 @@ function discoverProjectDir(start) {
|
|
|
41
42
|
dir = parent;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
//
|
|
45
|
+
// Recursively collect every workflow yaml under `dir` as absolute file paths.
|
|
46
|
+
// Subdirectories are walked so workflows can be organised into folders (the
|
|
47
|
+
// folder path becomes the sidebar group when a workflow omits an explicit
|
|
48
|
+
// `group:` field — see loadWorkflows).
|
|
49
|
+
function collectWorkflowFiles(dir) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
52
|
+
const full = join(dir, entry.name);
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
out.push(...collectWorkflowFiles(full));
|
|
55
|
+
}
|
|
56
|
+
else if (entry.isFile() &&
|
|
57
|
+
entry.name.endsWith(".yaml") &&
|
|
58
|
+
entry.name !== CONFIG) {
|
|
59
|
+
out.push(full);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
// Derive the fallback group for a workflow file from its subdirectory path
|
|
65
|
+
// relative to the workflows dir. `workflows/sentry/poll.yaml` → "sentry";
|
|
66
|
+
// `workflows/sentry/bugs/x.yaml` → "sentry/bugs"; a file at the root → undefined.
|
|
67
|
+
function groupFromPath(rootDir, file) {
|
|
68
|
+
const rel = relative(rootDir, dirname(file));
|
|
69
|
+
if (!rel || rel === "." || rel.startsWith(".."))
|
|
70
|
+
return undefined;
|
|
71
|
+
return rel.split(sep).join("/");
|
|
72
|
+
}
|
|
73
|
+
// Load every workflow yaml under a folder (recursively) into a name→Workflow map.
|
|
74
|
+
// Grouping precedence: an explicit top-level `group:` field on the workflow wins;
|
|
75
|
+
// otherwise the file's subdirectory path becomes the group; a workflow at the root
|
|
76
|
+
// with no `group:` stays ungrouped. The map is keyed by workflow `name`, which must
|
|
77
|
+
// stay globally unique across subdirs (sub-workflow `foreach`/run-workflow refs
|
|
78
|
+
// resolve by name) — a collision is a hard error rather than a silent overwrite.
|
|
45
79
|
function loadWorkflows(dir) {
|
|
46
80
|
const map = new Map();
|
|
47
|
-
|
|
81
|
+
const seenAt = new Map(); // name → file that defined it
|
|
82
|
+
for (const file of collectWorkflowFiles(dir)) {
|
|
83
|
+
let w;
|
|
48
84
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
85
|
+
w = loadWorkflow(readFileSync(file, "utf8"));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
continue; /* skip unparseable */
|
|
51
89
|
}
|
|
52
|
-
|
|
90
|
+
if (seenAt.has(w.name)) {
|
|
91
|
+
throw new Error(`duplicate workflow name "${w.name}" — defined in both ${relative(dir, seenAt.get(w.name))} and ${relative(dir, file)}. ` +
|
|
92
|
+
`Workflow names must be globally unique (they are how sub-workflows are referenced).`);
|
|
93
|
+
}
|
|
94
|
+
seenAt.set(w.name, file);
|
|
95
|
+
// Explicit `group:` field wins; otherwise fall back to the subdirectory path.
|
|
96
|
+
if (w.group === undefined) {
|
|
97
|
+
const g = groupFromPath(dir, file);
|
|
98
|
+
if (g)
|
|
99
|
+
w = { ...w, group: g };
|
|
100
|
+
}
|
|
101
|
+
map.set(w.name, w);
|
|
53
102
|
}
|
|
54
103
|
return map;
|
|
55
104
|
}
|
|
@@ -71,6 +120,74 @@ function loadAgents(projectDir) {
|
|
|
71
120
|
}
|
|
72
121
|
return map;
|
|
73
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Build the in-process hot-reload function for `start`/`ui`. Re-scans the workflows dir (recursively,
|
|
125
|
+
* same loader as startup) + the agents dir, rebuilds the shared maps IN PLACE (so the workflow runner's
|
|
126
|
+
* closure over `workflows` and `services.agents` sees the new set), and re-registers the scheduler's cron
|
|
127
|
+
* timers from the fresh workflow set. Returns the new workflow list for the server to serve.
|
|
128
|
+
*
|
|
129
|
+
* Mutating the existing Map instances (rather than replacing them) is deliberate: the runner built by
|
|
130
|
+
* `buildWorkflowRunner` and the services object both close over those Map references, so `foreach` /
|
|
131
|
+
* run-workflow sub-workflow resolution and agent lookups pick up edits without rebuilding the runner.
|
|
132
|
+
*
|
|
133
|
+
* A reload that fails to parse the dir (e.g. mid-write) is reported and skipped — the previous good set
|
|
134
|
+
* stays live rather than blanking the dashboard.
|
|
135
|
+
*/
|
|
136
|
+
function makeReloader(opts) {
|
|
137
|
+
return () => {
|
|
138
|
+
const next = loadWorkflows(opts.workflowsDir); // throws on duplicate names; caught by /api/reload
|
|
139
|
+
// Rebuild the shared workflow map in place.
|
|
140
|
+
opts.workflows.clear();
|
|
141
|
+
for (const [name, wf] of next)
|
|
142
|
+
opts.workflows.set(name, wf);
|
|
143
|
+
// Rebuild the shared agents map in place.
|
|
144
|
+
const nextAgents = loadAgents(opts.projectDir);
|
|
145
|
+
opts.agents.clear();
|
|
146
|
+
for (const [name, def] of nextAgents)
|
|
147
|
+
opts.agents.set(name, def);
|
|
148
|
+
// Re-register cron timers from the fresh set (cancels the old ones — no leaks, no double-fire).
|
|
149
|
+
opts.scheduler.reload([...next.values()]);
|
|
150
|
+
return [...next.values()];
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Watch the project dir (recursively) and hot-reload on workflow/agent edits. Editors fire several
|
|
155
|
+
* fs events per save, so the reload is debounced (~400ms). After a successful reload it emits a
|
|
156
|
+
* `workflows:changed` SSE event so the dashboard re-fetches /api/workflows without a refresh.
|
|
157
|
+
*
|
|
158
|
+
* Loop guard: a reload only reads files (the scheduler + maps are rebuilt from them) and never writes
|
|
159
|
+
* back into the watched dir, so a reload cannot retrigger itself; the debounce additionally collapses
|
|
160
|
+
* any burst into a single reload.
|
|
161
|
+
*
|
|
162
|
+
* This is the in-process successor to the old `--watch` (which exited for a supervisor restart). The
|
|
163
|
+
* launchd path that relied on `start --watch` still works — see the `start` command for the flag's
|
|
164
|
+
* retained behavior.
|
|
165
|
+
*/
|
|
166
|
+
function watchAndReload(dir, observer, reload) {
|
|
167
|
+
let pending;
|
|
168
|
+
try {
|
|
169
|
+
watchFs(dir, { recursive: true }, (_event, filename) => {
|
|
170
|
+
if (!filename || !/\.(yaml|md)$/.test(filename.toString()))
|
|
171
|
+
return;
|
|
172
|
+
clearTimeout(pending);
|
|
173
|
+
pending = setTimeout(() => {
|
|
174
|
+
try {
|
|
175
|
+
const next = reload();
|
|
176
|
+
observer.notifyWorkflowsChanged();
|
|
177
|
+
console.log(`hot-reload: ${next.length} workflow(s) (changed: ${filename})`);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
// Keep the previous good set live; just report (e.g. mid-write parse error or a name clash).
|
|
181
|
+
console.error("hot-reload skipped:", err instanceof Error ? err.message : String(err));
|
|
182
|
+
}
|
|
183
|
+
}, 400); // debounce editor multi-writes
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
// recursive watch isn't supported everywhere; degrade to manual reload (the Reload button).
|
|
188
|
+
console.error("hot-reload watch unavailable (use the Reload button):", err instanceof Error ? err.message : String(err));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
74
191
|
// Walk up to the directory that contains agentic-surfaces.config.yaml.
|
|
75
192
|
function findProjectDir(start) {
|
|
76
193
|
let dir = resolve(start);
|
|
@@ -106,7 +223,14 @@ function launchUi(opts) {
|
|
|
106
223
|
onRun: opts.onRun,
|
|
107
224
|
cache: opts.cache,
|
|
108
225
|
pause: opts.pause,
|
|
109
|
-
|
|
226
|
+
planUsage: opts.planUsage,
|
|
227
|
+
onReload: opts.onReload,
|
|
228
|
+
config: {
|
|
229
|
+
dryRun: opts.dryRun,
|
|
230
|
+
agent: opts.agentDefaults,
|
|
231
|
+
version: VERSION,
|
|
232
|
+
atlassianSite: opts.atlassianSite,
|
|
233
|
+
},
|
|
110
234
|
});
|
|
111
235
|
const url = `http://127.0.0.1:${p}`;
|
|
112
236
|
openBrowser(url);
|
|
@@ -115,8 +239,10 @@ function launchUi(opts) {
|
|
|
115
239
|
function openBrowser(url) {
|
|
116
240
|
if (process.env["FLOW_NO_OPEN"])
|
|
117
241
|
return; // headless / CI / don't hijack a browser
|
|
118
|
-
const cmd = process.platform === "darwin"
|
|
119
|
-
|
|
242
|
+
const cmd = process.platform === "darwin"
|
|
243
|
+
? "open"
|
|
244
|
+
: process.platform === "win32"
|
|
245
|
+
? "start"
|
|
120
246
|
: "xdg-open";
|
|
121
247
|
try {
|
|
122
248
|
spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
@@ -163,8 +289,17 @@ export async function run(argv) {
|
|
|
163
289
|
const registry = defaultRegistry();
|
|
164
290
|
const observer = new StreamingObserver();
|
|
165
291
|
const services = buildServices({ projectConfig: pc, projectDir: dir });
|
|
166
|
-
const runWorkflowFn = buildWorkflowRunner({
|
|
167
|
-
|
|
292
|
+
const runWorkflowFn = buildWorkflowRunner({
|
|
293
|
+
workflows: allWorkflows,
|
|
294
|
+
registry,
|
|
295
|
+
services: { ...services, agents },
|
|
296
|
+
observer,
|
|
297
|
+
});
|
|
298
|
+
const servicesWithRunner = {
|
|
299
|
+
...services,
|
|
300
|
+
agents,
|
|
301
|
+
runWorkflow: runWorkflowFn,
|
|
302
|
+
};
|
|
168
303
|
// Platform-wide play/pause (file-backed so it survives a --watch restart).
|
|
169
304
|
const pause = new FilePauseState();
|
|
170
305
|
// Cron-triggered workflows fire on schedule too.
|
|
@@ -172,17 +307,31 @@ export async function run(argv) {
|
|
|
172
307
|
for (const [, w] of allWorkflows)
|
|
173
308
|
sched.add(w);
|
|
174
309
|
sched.start();
|
|
310
|
+
const reload = makeReloader({
|
|
311
|
+
workflowsDir,
|
|
312
|
+
projectDir: dir,
|
|
313
|
+
workflows: allWorkflows,
|
|
314
|
+
agents,
|
|
315
|
+
scheduler: sched,
|
|
316
|
+
});
|
|
175
317
|
const url = launchUi({
|
|
176
|
-
observer,
|
|
318
|
+
observer,
|
|
319
|
+
workflows: allWorkflows,
|
|
320
|
+
agents,
|
|
177
321
|
onRun: (name, payload) => runWorkflowFn(name, payload),
|
|
178
322
|
cache: services.cache,
|
|
179
323
|
pause,
|
|
324
|
+
planUsage: () => services.agent.getPlanUsage?.() ?? Promise.resolve(null),
|
|
180
325
|
dryRun: pc?.dryRun,
|
|
181
326
|
agentDefaults: { model: pc?.agent?.model, effort: pc?.agent?.effort },
|
|
182
327
|
atlassianSite: pc?.atlassianSite,
|
|
328
|
+
onReload: reload,
|
|
183
329
|
});
|
|
330
|
+
// Automatic hot reload: a debounced recursive watch on the project dir re-scans + re-registers
|
|
331
|
+
// the scheduler in place and pushes a `workflows:changed` SSE event so the dashboard re-fetches.
|
|
332
|
+
watchAndReload(dir, observer, reload);
|
|
184
333
|
console.log(`\n▶ agentic-surfaces ${VERSION} — ${url}`);
|
|
185
|
-
console.log(` project: ${dir} · ${allWorkflows.size} workflow(s) · Ctrl-C to exit`);
|
|
334
|
+
console.log(` project: ${dir} · ${allWorkflows.size} workflow(s) · hot-reload on · Ctrl-C to exit`);
|
|
186
335
|
await new Promise(() => { }); // serve until interrupted
|
|
187
336
|
return 0;
|
|
188
337
|
}
|
|
@@ -192,7 +341,7 @@ export async function run(argv) {
|
|
|
192
341
|
return 0;
|
|
193
342
|
}
|
|
194
343
|
if (cmd === "run-once" || cmd === "run") {
|
|
195
|
-
const file = rest.find(a => !a.startsWith("--"));
|
|
344
|
+
const file = rest.find((a) => !a.startsWith("--"));
|
|
196
345
|
if (!file) {
|
|
197
346
|
console.error("run: missing workflow file");
|
|
198
347
|
return 1;
|
|
@@ -201,36 +350,60 @@ export async function run(argv) {
|
|
|
201
350
|
const pc = findProjectConfig(dirname(file));
|
|
202
351
|
const wf = loadWorkflow(readFileSync(file, "utf8"));
|
|
203
352
|
const projectDir = findProjectDir(dirname(file)) ?? dirname(file);
|
|
204
|
-
const services = buildServices(fake
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const w = loadWorkflow(readFileSync(join(dir, f), "utf8"));
|
|
210
|
-
allWorkflows.set(w.name, w);
|
|
353
|
+
const services = buildServices(fake
|
|
354
|
+
? {
|
|
355
|
+
fakeAgent: [{ text: "fake reply" }],
|
|
356
|
+
projectConfig: pc,
|
|
357
|
+
projectDir,
|
|
211
358
|
}
|
|
212
|
-
|
|
213
|
-
|
|
359
|
+
: { projectConfig: pc, projectDir });
|
|
360
|
+
// Scan recursively from the workflows root (so sub-workflow refs resolve
|
|
361
|
+
// across subdir folders + groups come through), falling back to the file's
|
|
362
|
+
// own dir when no project config is present.
|
|
363
|
+
const scanDir = pc?.workflows
|
|
364
|
+
? join(projectDir, pc.workflows)
|
|
365
|
+
: dirname(file);
|
|
366
|
+
const allWorkflows = loadWorkflows(scanDir);
|
|
214
367
|
const agents = loadAgents(projectDir);
|
|
215
368
|
const registry = defaultRegistry();
|
|
216
369
|
const ui = rest.includes("--ui");
|
|
217
370
|
const observer = ui ? new StreamingObserver() : undefined;
|
|
218
|
-
const runWorkflowFn = buildWorkflowRunner({
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
371
|
+
const runWorkflowFn = buildWorkflowRunner({
|
|
372
|
+
workflows: allWorkflows,
|
|
373
|
+
registry,
|
|
374
|
+
services: { ...services, agents },
|
|
375
|
+
observer,
|
|
376
|
+
});
|
|
377
|
+
const servicesWithRunner = {
|
|
378
|
+
...services,
|
|
379
|
+
agents,
|
|
380
|
+
runWorkflow: runWorkflowFn,
|
|
381
|
+
};
|
|
382
|
+
const url = ui
|
|
383
|
+
? launchUi({
|
|
384
|
+
observer: observer,
|
|
385
|
+
workflows: allWorkflows,
|
|
386
|
+
agents,
|
|
387
|
+
onRun: (name, payload) => runWorkflowFn(name, payload),
|
|
388
|
+
cache: services.cache,
|
|
389
|
+
dryRun: pc?.dryRun,
|
|
390
|
+
agentDefaults: {
|
|
391
|
+
model: pc?.agent?.model,
|
|
392
|
+
effort: pc?.agent?.effort,
|
|
393
|
+
},
|
|
394
|
+
atlassianSite: pc?.atlassianSite,
|
|
395
|
+
})
|
|
396
|
+
: undefined;
|
|
228
397
|
// A run error must NOT tear down the --ui server: the failure is already
|
|
229
398
|
// streamed to the dashboard (the failed node + run), so keep serving so it
|
|
230
399
|
// stays inspectable. Without --ui, an error still exits non-zero.
|
|
231
400
|
let failed = false;
|
|
232
401
|
try {
|
|
233
|
-
const outputs = await runWorkflowOnce(wf, {
|
|
402
|
+
const outputs = await runWorkflowOnce(wf, {
|
|
403
|
+
registry,
|
|
404
|
+
services: servicesWithRunner,
|
|
405
|
+
observer,
|
|
406
|
+
});
|
|
234
407
|
console.log("ran", wf.name, "->", [...outputs.keys()].join(", "));
|
|
235
408
|
}
|
|
236
409
|
catch (err) {
|
|
@@ -244,47 +417,68 @@ export async function run(argv) {
|
|
|
244
417
|
return failed ? 1 : 0;
|
|
245
418
|
}
|
|
246
419
|
if (cmd === "start") {
|
|
247
|
-
const dir = rest.find(a => !a.startsWith("--")) ?? ".";
|
|
420
|
+
const dir = rest.find((a) => !a.startsWith("--")) ?? ".";
|
|
248
421
|
const pc = loadProjectConfig(dir);
|
|
249
422
|
const workflowsDir = pc?.workflows ? join(dir, pc.workflows) : dir;
|
|
250
423
|
const services = buildServices({ projectConfig: pc, projectDir: dir });
|
|
251
|
-
const allWorkflows =
|
|
252
|
-
const yamlFiles = readdirSync(workflowsDir).filter(f => f.endsWith(".yaml") && f !== "agentic-surfaces.config.yaml");
|
|
253
|
-
for (const f of yamlFiles) {
|
|
254
|
-
try {
|
|
255
|
-
const w = loadWorkflow(readFileSync(join(workflowsDir, f), "utf8"));
|
|
256
|
-
allWorkflows.set(w.name, w);
|
|
257
|
-
}
|
|
258
|
-
catch { /* skip unparseable */ }
|
|
259
|
-
}
|
|
424
|
+
const allWorkflows = loadWorkflows(workflowsDir);
|
|
260
425
|
const agents = loadAgents(dir);
|
|
261
426
|
const registry = defaultRegistry();
|
|
262
427
|
const ui = rest.includes("--ui");
|
|
263
428
|
const observer = ui ? new StreamingObserver() : undefined;
|
|
264
|
-
const runWorkflowFn = buildWorkflowRunner({
|
|
265
|
-
|
|
429
|
+
const runWorkflowFn = buildWorkflowRunner({
|
|
430
|
+
workflows: allWorkflows,
|
|
431
|
+
registry,
|
|
432
|
+
services: { ...services, agents },
|
|
433
|
+
observer,
|
|
434
|
+
});
|
|
435
|
+
const servicesWithRunner = {
|
|
436
|
+
...services,
|
|
437
|
+
agents,
|
|
438
|
+
runWorkflow: runWorkflowFn,
|
|
439
|
+
};
|
|
266
440
|
const pause = new FilePauseState();
|
|
267
441
|
const sched = new Scheduler(servicesWithRunner, registry, observer, pause);
|
|
268
442
|
for (const [, w] of allWorkflows)
|
|
269
443
|
sched.add(w);
|
|
270
444
|
sched.start();
|
|
445
|
+
const reload = makeReloader({
|
|
446
|
+
workflowsDir,
|
|
447
|
+
projectDir: dir,
|
|
448
|
+
workflows: allWorkflows,
|
|
449
|
+
agents,
|
|
450
|
+
scheduler: sched,
|
|
451
|
+
});
|
|
271
452
|
console.log(`scheduler started; watching ${workflowsDir}${pause.isPaused() ? " (PAUSED)" : ""}`);
|
|
453
|
+
const watchFlag = rest.includes("--watch");
|
|
272
454
|
if (ui) {
|
|
273
455
|
const url = launchUi({
|
|
274
|
-
observer: observer,
|
|
456
|
+
observer: observer,
|
|
457
|
+
workflows: allWorkflows,
|
|
458
|
+
agents,
|
|
275
459
|
onRun: (name, payload) => runWorkflowFn(name, payload),
|
|
276
460
|
cache: services.cache,
|
|
277
461
|
pause,
|
|
462
|
+
planUsage: () => services.agent.getPlanUsage?.() ?? Promise.resolve(null),
|
|
278
463
|
dryRun: pc?.dryRun,
|
|
279
464
|
agentDefaults: { model: pc?.agent?.model, effort: pc?.agent?.effort },
|
|
280
465
|
atlassianSite: pc?.atlassianSite,
|
|
466
|
+
onReload: reload,
|
|
281
467
|
});
|
|
282
468
|
console.log(`▶ agentic-surfaces ${VERSION} — ${url}`);
|
|
469
|
+
// With the dashboard up, prefer in-process hot reload (button + automatic) over the legacy
|
|
470
|
+
// process-exit watcher — unless --watch is explicitly passed (the supervisor-restart contract).
|
|
471
|
+
if (!watchFlag) {
|
|
472
|
+
watchAndReload(dir, observer, reload);
|
|
473
|
+
console.log("hot-reload on (Reload button + automatic file watch)");
|
|
474
|
+
}
|
|
283
475
|
}
|
|
284
476
|
// --watch: exit when a config file changes so a supervisor (launchd/pm2/systemd) restarts us
|
|
285
477
|
// with the fresh config. The seen-cache (.flow-cache.sqlite) persists across the restart, so
|
|
286
|
-
// dedup is preserved. Without a supervisor this just exits on the first edit.
|
|
287
|
-
|
|
478
|
+
// dedup is preserved. Without a supervisor this just exits on the first edit. RETAINED for the
|
|
479
|
+
// launchd path; the dashboard's in-process hot reload above is the lighter-weight default when
|
|
480
|
+
// --watch is omitted. When --watch is set it wins (a supervisor expects the process to exit).
|
|
481
|
+
if (watchFlag) {
|
|
288
482
|
const { watch } = await import("node:fs");
|
|
289
483
|
let pending;
|
|
290
484
|
watch(dir, { recursive: true }, (_event, filename) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentic-surfaces/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"README.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@agentic-surfaces/
|
|
26
|
-
"@agentic-surfaces/
|
|
27
|
-
"@agentic-surfaces/server": "0.1.
|
|
25
|
+
"@agentic-surfaces/agent": "0.1.30",
|
|
26
|
+
"@agentic-surfaces/core": "0.1.30",
|
|
27
|
+
"@agentic-surfaces/server": "0.1.30"
|
|
28
28
|
},
|
|
29
29
|
"repository": {
|
|
30
30
|
"type": "git",
|