@netnodeag/kraftwerk 0.61.0 → 0.63.0

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 (75) hide show
  1. package/dist/cli/routines.js +2 -3
  2. package/dist/client/index.d.ts +59 -0
  3. package/dist/client/index.js +63 -0
  4. package/dist/client/live.d.ts +34 -0
  5. package/dist/client/live.js +169 -0
  6. package/dist/client/routes.d.ts +125 -0
  7. package/dist/client/routes.js +126 -0
  8. package/dist/inspector/api/agents.d.ts +31 -0
  9. package/dist/inspector/api/agents.js +50 -0
  10. package/dist/inspector/api/attention.d.ts +10 -0
  11. package/dist/inspector/api/attention.js +23 -0
  12. package/dist/inspector/api/channels.d.ts +67 -0
  13. package/dist/inspector/api/channels.js +58 -0
  14. package/dist/inspector/api/chats.d.ts +55 -0
  15. package/dist/inspector/api/chats.js +154 -0
  16. package/dist/inspector/api/files.d.ts +15 -0
  17. package/dist/inspector/api/files.js +43 -0
  18. package/dist/inspector/api/git.d.ts +25 -0
  19. package/dist/inspector/api/git.js +56 -0
  20. package/dist/inspector/api/index.d.ts +334 -0
  21. package/dist/inspector/api/index.js +43 -0
  22. package/dist/inspector/api/knowledge.d.ts +13 -0
  23. package/dist/inspector/api/knowledge.js +56 -0
  24. package/dist/inspector/api/mime.d.ts +2 -0
  25. package/dist/inspector/api/mime.js +18 -0
  26. package/dist/inspector/api/projects.d.ts +11 -0
  27. package/dist/inspector/api/projects.js +32 -0
  28. package/dist/inspector/api/router.d.ts +75 -0
  29. package/dist/inspector/api/router.js +186 -0
  30. package/dist/inspector/api/runs.d.ts +33 -0
  31. package/dist/inspector/api/runs.js +105 -0
  32. package/dist/inspector/api/socket.d.ts +3 -0
  33. package/dist/inspector/api/socket.js +115 -0
  34. package/dist/inspector/api/vibeables.d.ts +4 -0
  35. package/dist/inspector/api/vibeables.js +31 -0
  36. package/dist/inspector/api/workspace.d.ts +55 -0
  37. package/dist/inspector/api/workspace.js +117 -0
  38. package/dist/inspector/chat/sessions.js +31 -19
  39. package/dist/inspector/context.d.ts +5 -7
  40. package/dist/inspector/context.js +6 -20
  41. package/dist/inspector/git.js +52 -43
  42. package/dist/inspector/live.d.ts +42 -0
  43. package/dist/inspector/live.js +91 -0
  44. package/dist/inspector/notifications.js +19 -13
  45. package/dist/inspector/routines.d.ts +22 -1
  46. package/dist/inspector/routines.js +32 -5
  47. package/dist/inspector/runner.js +5 -4
  48. package/dist/inspector/server.d.ts +2 -2
  49. package/dist/inspector/server.js +59 -1389
  50. package/dist/inspector/version.d.ts +9 -0
  51. package/dist/inspector/version.js +27 -0
  52. package/dist/inspector/vibeables.js +25 -23
  53. package/dist/inspector/workspace.d.ts +42 -0
  54. package/dist/inspector/workspace.js +63 -0
  55. package/dist/inspector/ws.d.ts +11 -0
  56. package/dist/inspector/ws.js +143 -0
  57. package/inspector/dist/assets/{dist-B3L4wDm3.js → dist-9RESajfo.js} +1 -1
  58. package/inspector/dist/assets/{dist-Cwukoo61.js → dist-Bm5S1BoZ.js} +1 -1
  59. package/inspector/dist/assets/dist-C8BofvHx.js +1 -0
  60. package/inspector/dist/assets/{dist-wYE0FpHk.js → dist-CIs4wnTu.js} +1 -1
  61. package/inspector/dist/assets/{dist-De7-pK5i.js → dist-CjuETd-_.js} +1 -1
  62. package/inspector/dist/assets/{dist-B58fOC3R.js → dist-CmVK6rgN.js} +1 -1
  63. package/inspector/dist/assets/{dist-zk56dqRQ.js → dist-CryXvMV_.js} +1 -1
  64. package/inspector/dist/assets/{dist-BiWJ3XY1.js → dist-D1hk0iC5.js} +1 -1
  65. package/inspector/dist/assets/{dist-CH9pz3rR.js → dist-DvW9QE1d.js} +1 -1
  66. package/inspector/dist/assets/{dist-Dno2CmWo.js → dist-VD9jMxBv.js} +1 -1
  67. package/inspector/dist/assets/{dist-BKBuUSVQ.js → dist-VlAqWD8T.js} +1 -1
  68. package/inspector/dist/assets/{editor-CMQMyCOv.js → editor-DkWqwl89.js} +3 -3
  69. package/inspector/dist/assets/index-Bra74Nxe.css +2 -0
  70. package/inspector/dist/assets/index-DRJS2pGj.js +229 -0
  71. package/inspector/dist/index.html +2 -2
  72. package/package.json +7 -2
  73. package/inspector/dist/assets/dist-Bj93ipvc.js +0 -1
  74. package/inspector/dist/assets/index-1wfYRgHE.css +0 -2
  75. package/inspector/dist/assets/index-CtygNkZJ.js +0 -229
@@ -2,32 +2,38 @@ import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { getOutputDir } from "./context.js";
5
+ import { perWorkspace } from "./workspace.js";
6
+ import { touch } from "./live.js";
5
7
  const MAX_ITEMS = 200;
6
8
  const BODY_MAX = 280;
7
- let cache = null;
8
- let writeChain = Promise.resolve();
9
+ /** The in-memory copy and the write queue, one per workspace. */
10
+ const store = perWorkspace(() => ({ cache: null, writeChain: Promise.resolve() }));
9
11
  const file = () => path.join(getOutputDir(), "notifications.json");
10
12
  async function load() {
11
- if (cache)
12
- return cache;
13
+ const st = store();
14
+ if (st.cache)
15
+ return st.cache;
13
16
  try {
14
17
  const parsed = JSON.parse(await fs.readFile(file(), "utf8"));
15
- cache = Array.isArray(parsed) ? parsed : [];
18
+ st.cache = Array.isArray(parsed) ? parsed : [];
16
19
  }
17
20
  catch {
18
- cache = [];
21
+ st.cache = [];
19
22
  }
20
- return cache;
23
+ return st.cache;
21
24
  }
22
25
  function persist(items) {
23
- cache = items;
24
- writeChain = writeChain
26
+ const st = store();
27
+ const [dir, target] = [getOutputDir(), file()];
28
+ st.cache = items;
29
+ touch();
30
+ st.writeChain = st.writeChain
25
31
  .then(async () => {
26
- await fs.mkdir(getOutputDir(), { recursive: true });
32
+ await fs.mkdir(dir, { recursive: true });
27
33
  // Write-then-rename: a reader (the UI, a test) never sees a half-written file.
28
- const tmp = `${file()}.${process.pid}.tmp`;
34
+ const tmp = `${target}.${process.pid}.tmp`;
29
35
  await fs.writeFile(tmp, JSON.stringify(items, null, 2));
30
- await fs.rename(tmp, file());
36
+ await fs.rename(tmp, target);
31
37
  })
32
38
  .catch(() => { });
33
39
  }
@@ -92,5 +98,5 @@ export async function clearNotifications() {
92
98
  }
93
99
  /** Tests / project switches: forget the in-memory copy so the next read hits disk. */
94
100
  export function resetNotificationsCache() {
95
- cache = null;
101
+ store().cache = null;
96
102
  }
@@ -49,10 +49,31 @@ export interface SaveRoutineInput {
49
49
  }
50
50
  export declare function saveRoutine(slug: string, input: SaveRoutineInput): Promise<Routine>;
51
51
  export declare function deleteRoutine(slug: string, id: string): Promise<void>;
52
+ /**
53
+ * Whether a routine's prompt names a workflow: its slug or its name as a whole word
54
+ * ("Run the daily-ceo-report workflow", "Run helpdesk workflow"). Routines are an
55
+ * agent's prompts, so this is how a workflow learns it runs on a schedule.
56
+ */
57
+ export declare function mentionsWorkflow(prompt: string, w: {
58
+ slug: string;
59
+ name?: string;
60
+ }): boolean;
61
+ /** Every agent's routines with their run state, each with the workflows its prompt names (GET /api/routines). */
62
+ export declare function allRoutines(workflows: {
63
+ slug: string;
64
+ name?: string;
65
+ }[]): Promise<(RoutineStatus & {
66
+ agent: {
67
+ slug: string;
68
+ name: string;
69
+ emoji: string;
70
+ };
71
+ workflows: string[];
72
+ })[]>;
52
73
  export declare function routineStatuses(slug: string): Promise<RoutineStatus[]>;
53
74
  export declare function runRoutineNow(slug: string, id: string): Promise<{
54
75
  chatId: string;
55
76
  }>;
56
- /** Start the in-process scheduler; safe to call more than once. */
77
+ /** Start the current workspace's scheduler; safe to call more than once. Ticks run in that workspace. */
57
78
  export declare function startRoutineScheduler(): void;
58
79
  export {};
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parse, stringify } from "yaml";
4
4
  import { getOutputDir } from "./context.js";
5
+ import { perWorkspace } from "./workspace.js";
5
6
  import { getAgent, listAgents, safeAgentSlug, agentsRoot } from "./agents.js";
6
7
  import { chatAwaitingApproval, createChat, postMessage } from "./chat/sessions.js";
7
8
  import { pushNotification } from "./notifications.js";
@@ -166,6 +167,30 @@ async function writeState(state) {
166
167
  await fs.mkdir(getOutputDir(), { recursive: true });
167
168
  await fs.writeFile(stateFile(), JSON.stringify(state, null, 2));
168
169
  }
170
+ /**
171
+ * Whether a routine's prompt names a workflow: its slug or its name as a whole word
172
+ * ("Run the daily-ceo-report workflow", "Run helpdesk workflow"). Routines are an
173
+ * agent's prompts, so this is how a workflow learns it runs on a schedule.
174
+ */
175
+ export function mentionsWorkflow(prompt, w) {
176
+ const text = prompt.toLowerCase();
177
+ return [w.slug, w.name].some((n) => {
178
+ if (!n?.trim())
179
+ return false;
180
+ const esc = n.trim().toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
181
+ return new RegExp(`(^|[^a-z0-9_-])${esc}($|[^a-z0-9_-])`).test(text);
182
+ });
183
+ }
184
+ /** Every agent's routines with their run state, each with the workflows its prompt names (GET /api/routines). */
185
+ export async function allRoutines(workflows) {
186
+ const agents = (await listAgents()).filter((a) => !a.archived);
187
+ const per = await Promise.all(agents.map(async (a) => (await routineStatuses(a.slug).catch(() => [])).map((r) => ({
188
+ ...r,
189
+ agent: { slug: a.slug, name: a.name, emoji: a.emoji },
190
+ workflows: workflows.filter((w) => mentionsWorkflow(r.prompt, w)).map((w) => w.slug),
191
+ }))));
192
+ return per.flat();
193
+ }
169
194
  export async function routineStatuses(slug) {
170
195
  const [routines, state] = await Promise.all([listRoutines(slug), readState()]);
171
196
  return routines.map((r) => {
@@ -227,7 +252,8 @@ export async function runRoutineNow(slug, id) {
227
252
  }
228
253
  }
229
254
  /* ---------- scheduler ---------- */
230
- let timer = null;
255
+ /** The scheduler's timer, one per workspace. */
256
+ const scheduler = perWorkspace(() => ({ timer: null }));
231
257
  async function tick() {
232
258
  const now = new Date();
233
259
  const p = (n) => String(n).padStart(2, "0");
@@ -271,10 +297,11 @@ async function tick() {
271
297
  if (dirty)
272
298
  await writeState(state);
273
299
  }
274
- /** Start the in-process scheduler; safe to call more than once. */
300
+ /** Start the current workspace's scheduler; safe to call more than once. Ticks run in that workspace. */
275
301
  export function startRoutineScheduler() {
276
- if (timer)
302
+ const s = scheduler();
303
+ if (s.timer)
277
304
  return;
278
- timer = setInterval(() => void tick().catch(() => { }), 20_000);
279
- timer.unref?.();
305
+ s.timer = setInterval(() => void tick().catch(() => { }), 20_000);
306
+ s.timer.unref?.();
280
307
  }
@@ -2,6 +2,7 @@ import { spawn, spawnSync } from "node:child_process";
2
2
  import { mkdirSync, openSync, closeSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { getProjectRoot } from "./context.js";
5
+ import { perWorkspace } from "./workspace.js";
5
6
  import { RUN_ID_RE, getRun, safeRunDir } from "./runs.js";
6
7
  import { pushNotification } from "./notifications.js";
7
8
  import { newRunId } from "../workflow.js";
@@ -29,7 +30,7 @@ export function dockerStatus() {
29
30
  return { available: true, image: image.status === 0 };
30
31
  }
31
32
  /** Launchers this inspector started and that have not exited yet, by run id. */
32
- const live = new Map();
33
+ const launchers = perWorkspace(() => new Map());
33
34
  export function triggerRun(opts) {
34
35
  if (opts.sandbox) {
35
36
  const docker = dockerStatus();
@@ -57,7 +58,7 @@ export function triggerRun(opts) {
57
58
  });
58
59
  child.unref();
59
60
  closeSync(log);
60
- live.set(runId, child);
61
+ launchers().set(runId, child);
61
62
  // Detached, but as long as the inspector lives it still hears the exit:
62
63
  // that is when the run's outcome goes to the bell (read from the trace,
63
64
  // the exit code alone does not say whether a gate blocked). The exit
@@ -65,7 +66,7 @@ export function triggerRun(opts) {
65
66
  // framework wrote a trace (missing env var, npx failure) shows as failed
66
67
  // instead of running until the stale timeout.
67
68
  child.on("exit", (code, signal) => {
68
- live.delete(runId);
69
+ launchers().delete(runId);
69
70
  try {
70
71
  writeFileSync(path.join(runDir, "trigger.json"), JSON.stringify({ exitCode: code ?? 1, signal, finishedAt: new Date().toISOString() }, null, 2) + "\n");
71
72
  }
@@ -93,7 +94,7 @@ export function stopRun(runId) {
93
94
  return false;
94
95
  // A local run this inspector launched: the launcher is detached into its
95
96
  // own process group, so the negative pid ends npx and the framework under it.
96
- const child = live.get(runId);
97
+ const child = launchers().get(runId);
97
98
  if (child?.pid && child.exitCode == null) {
98
99
  try {
99
100
  process.kill(-child.pid, "SIGTERM");
@@ -1,6 +1,6 @@
1
1
  import http from "node:http";
2
- /** Exit code the `kraftwerk ui` supervisor treats as "relaunch me". */
3
- export declare const RESTART_EXIT_CODE = 75;
2
+ import { RESTART_EXIT_CODE } from "./version.js";
3
+ export { RESTART_EXIT_CODE };
4
4
  export interface InspectorOptions {
5
5
  outputDir: string;
6
6
  staticDir: string;