@netnodeag/kraftwerk 0.34.2 → 0.36.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 (51) hide show
  1. package/README.md +40 -19
  2. package/dist/cli/init.js +3 -3
  3. package/dist/cli/routines.js +9 -9
  4. package/dist/config.d.ts +26 -2
  5. package/dist/config.js +43 -3
  6. package/dist/inspector/{team.d.ts → agents.d.ts} +21 -12
  7. package/dist/inspector/{team.js → agents.js} +34 -19
  8. package/dist/inspector/chat/backend.d.ts +1 -1
  9. package/dist/inspector/chat/sessions.js +43 -43
  10. package/dist/inspector/chat/store.js +13 -1
  11. package/dist/inspector/chat/types.d.ts +2 -2
  12. package/dist/inspector/git.d.ts +83 -0
  13. package/dist/inspector/git.js +651 -0
  14. package/dist/inspector/instances.d.ts +12 -0
  15. package/dist/inspector/instances.js +37 -1
  16. package/dist/inspector/routines.d.ts +4 -4
  17. package/dist/inspector/routines.js +11 -11
  18. package/dist/inspector/search.d.ts +25 -0
  19. package/dist/inspector/search.js +31 -0
  20. package/dist/inspector/server.js +166 -32
  21. package/dist/inspector/settings.d.ts +11 -1
  22. package/dist/inspector/settings.js +45 -0
  23. package/dist/inspector/skills.d.ts +6 -6
  24. package/dist/inspector/skills.js +8 -8
  25. package/dist/runner/docker.d.ts +1 -0
  26. package/dist/runner/docker.js +1 -1
  27. package/inspector/dist/assets/{editor-CXrDJOv_.js → editor-7ImnDC3D.js} +3 -3
  28. package/inspector/dist/assets/{index-BxEG-IdY.js → index--2I7vpcS.js} +1 -1
  29. package/inspector/dist/assets/index-B2ThRS2S.js +217 -0
  30. package/inspector/dist/assets/index-BA7nx4V5.css +1 -0
  31. package/inspector/dist/assets/{index-Bumwg9SM.js → index-BC7aIJ-h.js} +1 -1
  32. package/inspector/dist/assets/{index-BULB2rC_.js → index-BIXYBiw7.js} +1 -1
  33. package/inspector/dist/assets/{index-C3hpUVfp.js → index-BZeYO5wT.js} +1 -1
  34. package/inspector/dist/assets/{index-BQMgADlc.js → index-B_CIhKa4.js} +1 -1
  35. package/inspector/dist/assets/{index-DrSWAGft.js → index-BuRajvjQ.js} +1 -1
  36. package/inspector/dist/assets/{index-BNcXQYbv.js → index-C3Ob1O7W.js} +1 -1
  37. package/inspector/dist/assets/{index-BIDsfw8C.js → index-CIjv7yxE.js} +1 -1
  38. package/inspector/dist/assets/{index-ChuqpBnv.js → index-CRAwmMfk.js} +1 -1
  39. package/inspector/dist/assets/{index-x5G6Np7T.js → index-Ct_jZSyD.js} +1 -1
  40. package/inspector/dist/assets/{index-7J4zAS6J.js → index-DKrBjjGQ.js} +1 -1
  41. package/inspector/dist/assets/{index-CUCCMUMY.js → index-DL_bJiOj.js} +1 -1
  42. package/inspector/dist/assets/{index-DAi1VtJ8.js → index-DPBAv_kj.js} +1 -1
  43. package/inspector/dist/assets/{index-Upf-BEsx.js → index-DbmSSHvr.js} +1 -1
  44. package/inspector/dist/assets/{index-CNJyAniG.js → index-DjRBLY9N.js} +1 -1
  45. package/inspector/dist/assets/{index-CszaAR9T.js → index-YZtBJhWD.js} +1 -1
  46. package/inspector/dist/assets/{index-CMMYSoL3.js → index-fWUFhIGR.js} +1 -1
  47. package/inspector/dist/index.html +2 -2
  48. package/package.json +5 -2
  49. package/schema/kraftwerk.schema.json +35 -1
  50. package/inspector/dist/assets/index-B7rwNdVj.css +0 -1
  51. package/inspector/dist/assets/index-BBGjdXk1.js +0 -213
@@ -5,7 +5,7 @@ import { knowledgeIndex } from "../knowledge.js";
5
5
  import { getRun, listRuns, safeRunDir } from "../runs.js";
6
6
  import { listSkills, readSkill } from "../skills.js";
7
7
  import { listWorkflows } from "../workflows.js";
8
- import { getMember, listMembers } from "../team.js";
8
+ import { getAgent, listAgents } from "../agents.js";
9
9
  import { startAcpBackend } from "./acp.js";
10
10
  import { startPiBackend } from "./pi.js";
11
11
  import { appendEvent, listChatMetas, newChatId, readEvents, readMeta, safeChatDir, writeMeta, } from "./store.js";
@@ -74,19 +74,19 @@ function emit(state, ev) {
74
74
  /* ---------- skills ---------- */
75
75
  /**
76
76
  * Skills visible to a chat: every discovered skill (workspace skills root
77
- * + .claude/skills roots, plus the member's own agents/<slug>/skills for team
78
- * sessions), narrowed by the team member's allowlist when the chat is a team
77
+ * + .claude/skills roots, plus the agent's own agents/<slug>/skills for agent
78
+ * sessions), narrowed by the agent's allowlist when the chat is an agent
79
79
  * session (absent allowlist = all, empty = none). The agent's own skills
80
80
  * always apply — the allowlist narrows shared skills only.
81
81
  */
82
82
  async function availableSkills(scope) {
83
- const all = await listSkills(scope.kind === "team" ? scope.member : undefined).catch(() => []);
84
- if (scope.kind !== "team")
83
+ const all = await listSkills(scope.kind === "agent" ? scope.slug : undefined).catch(() => []);
84
+ if (scope.kind !== "agent")
85
85
  return all;
86
- const member = await getMember(scope.member).catch(() => null);
87
- if (!member || member.skills === undefined)
86
+ const def = await getAgent(scope.slug).catch(() => null);
87
+ if (!def || def.skills === undefined)
88
88
  return all;
89
- const allowed = new Set(member.skills.map((n) => n.toLowerCase()));
89
+ const allowed = new Set(def.skills.map((n) => n.toLowerCase()));
90
90
  return all.filter((s) => s.source === "agent" || allowed.has(s.name.toLowerCase()));
91
91
  }
92
92
  /** "## Your skills" context block (empty when no skills are visible). */
@@ -122,12 +122,12 @@ async function expandSkillInvocation(scope, text) {
122
122
  `Arguments the user passed to the skill: ${args || "(none)"}`);
123
123
  }
124
124
  /* ---------- scope context ---------- */
125
- /** "## Your knowledge" block for a team member's connected OKF bundles. */
126
- async function teamKnowledgeContext(member) {
127
- if (member.knowledge.length === 0)
125
+ /** "## Your knowledge" block for a agent's connected OKF bundles. */
126
+ async function agentKnowledgeContext(def) {
127
+ if (def.knowledge.length === 0)
128
128
  return "";
129
129
  const { bundles } = await knowledgeIndex().catch(() => ({ bundles: [] }));
130
- const lines = member.knowledge
130
+ const lines = def.knowledge
131
131
  .map((name) => {
132
132
  const b = bundles.find((x) => x.name === name);
133
133
  return b
@@ -139,30 +139,30 @@ async function teamKnowledgeContext(member) {
139
139
  `part of your job. Consult them before answering questions in their domain, and keep them current ` +
140
140
  `when you learn something durable:\n${lines}\n\n` +
141
141
  `Read with \`npx kraftwerk knowledge list <bundle>\`, \`get <bundle>/<path>\`, \`search <text>\`. ` +
142
- `Write ONLY through \`npx kraftwerk knowledge put <bundle>/<path> --file <tmp.md> --actor ${member.slug}/${member.harness}\` ` +
142
+ `Write ONLY through \`npx kraftwerk knowledge put <bundle>/<path> --file <tmp.md> --actor ${def.slug}/${def.harness}\` ` +
143
143
  `(write the markdown to a temp file first; the CLI stamps provenance and maintains index.md/log.md). ` +
144
144
  `Never hand-edit index.md or log.md, and never add \`verified\` yourself — verification is the human's ` +
145
145
  `click in the UI.\n\n`);
146
146
  }
147
147
  /** Base context per scope; scopeContext() appends the shared skills block. */
148
148
  async function baseScopeContext(scope, agent) {
149
- if (scope.kind === "team") {
150
- const member = await getMember(scope.member).catch(() => null);
151
- if (!member) {
152
- return `You were opened as team agent "${scope.member}", but its definition under agents/ is missing. Tell the user and ask them to recreate it.`;
149
+ if (scope.kind === "agent") {
150
+ const def = await getAgent(scope.slug).catch(() => null);
151
+ if (!def) {
152
+ return `You were opened as agent "${scope.slug}", but its definition under agents/ is missing. Tell the user and ask them to recreate it.`;
153
153
  }
154
154
  const { workflows } = await listWorkflows().catch(() => ({ workflows: [] }));
155
- const connected = workflows.filter((w) => member.workflows.includes(w.slug));
156
- const missing = member.workflows.filter((slug) => !connected.some((w) => w.slug === slug));
155
+ const connected = workflows.filter((w) => def.workflows.includes(w.slug));
156
+ const missing = def.workflows.filter((slug) => !connected.some((w) => w.slug === slug));
157
157
  const wfLines = connected
158
158
  .map((w) => `- ${w.slug}: ${w.description ?? w.name ?? ""} (${w.steps} steps)`)
159
159
  .join("\n");
160
- return (`You are ${member.emoji} ${member.name}, a persistent agent teammate on this project's team ` +
161
- `(defined in agents/${member.slug}/). The user works with you like with a colleague: every session ` +
162
- `is a conversation with the same ${member.name}, so keep this role consistently. You are an AI agent, ` +
160
+ return (`You are ${def.emoji} ${def.name}, a persistent agent of this project ` +
161
+ `(defined in agents/${def.slug}/). The user works with you like with a colleague: every session ` +
162
+ `is a conversation with the same ${def.name}, so keep this role consistently. You are an AI agent, ` +
163
163
  `not a human — never pretend otherwise, but do own your role.\n\n` +
164
- `## Your role\n${member.system || "(no system prompt written yet — ask the user what your job should be)"}\n\n` +
165
- (member.workflows.length > 0
164
+ `## Your role\n${def.system || "(no system prompt written yet — ask the user what your job should be)"}\n\n` +
165
+ (def.workflows.length > 0
166
166
  ? `## Your workflows\nThese kraftwerk workflows are part of your job. When the user's request matches one, ` +
167
167
  `run it yourself instead of doing the work by hand:\n${wfLines || "(none found)"}\n` +
168
168
  (missing.length > 0
@@ -174,9 +174,9 @@ async function baseScopeContext(scope, agent) {
174
174
  `run id and summarize the result. Confirm with the user before starting a long or expensive run ` +
175
175
  `unless they clearly asked for it.\n\n`
176
176
  : "") +
177
- (await teamKnowledgeContext(member)) +
177
+ (await agentKnowledgeContext(def)) +
178
178
  `The working directory is the project root. Stay within your role; if a request is clearly outside it, ` +
179
- `say so and suggest which teammate or tool fits better.` +
179
+ `say so and suggest which agent or tool fits better.` +
180
180
  (scope.routine
181
181
  ? `\n\nThis session was started automatically by your scheduled routine "${scope.routine}" — ` +
182
182
  `nobody may be watching live. Complete the task autonomously (tool permissions are ` +
@@ -212,11 +212,11 @@ async function baseScopeContext(scope, agent) {
212
212
  `verification is the human's click in the UI. Ask the user what knowledge to capture, then author concise, well-typed concepts.`);
213
213
  }
214
214
  if (scope.kind === "kraftwerk") {
215
- const [{ workflows }, runs, knowledge, members] = await Promise.all([
215
+ const [{ workflows }, runs, knowledge, agents] = await Promise.all([
216
216
  listWorkflows(),
217
217
  listRuns(),
218
218
  knowledgeIndex().catch(() => ({ bundles: [] })),
219
- listMembers().catch(() => []),
219
+ listAgents().catch(() => []),
220
220
  ]);
221
221
  const wfLines = workflows
222
222
  .map((w) => `- ${w.slug}: ${w.description ?? w.name ?? ""} (${w.steps} steps, ${w.agents} agents)`)
@@ -228,7 +228,7 @@ async function baseScopeContext(scope, agent) {
228
228
  const bundleLines = knowledge.bundles
229
229
  .map((b) => `- ${b.name} (${b.concepts} concepts${b.updatedAt ? `, updated ${b.updatedAt.slice(0, 10)}` : ""})`)
230
230
  .join("\n");
231
- const memberLines = members
231
+ const agentLines = agents
232
232
  .map((m) => `- ${m.emoji} ${m.name} (${m.slug})${m.description ? `: ${m.description}` : ""}`)
233
233
  .join("\n");
234
234
  return (`You are the assistant inside the kraftwerk inspector, a UI for a workflow-as-code agent framework. ` +
@@ -244,8 +244,8 @@ async function baseScopeContext(scope, agent) {
244
244
  `Read with \`npx kraftwerk knowledge list [bundle]\`, \`get <bundle>/<path>\`, \`search <text>\`. ` +
245
245
  `Write ONLY through \`npx kraftwerk knowledge put <bundle>/<path> --file <tmp.md> --actor kraftwerk-chat/${agent}\` ` +
246
246
  `(stamps provenance, maintains index.md/log.md — never hand-edit those).\n\n` +
247
- `## Team agents\n${memberLines || "(none)"}\n` +
248
- `These are persistent agent teammates (defined under agents/); the user talks to them on the ` +
247
+ `## Agents\n${agentLines || "(none)"}\n` +
248
+ `These are persistent agents (defined under agents/); the user talks to them on the ` +
249
249
  `Agents screen. Point the user there when a request clearly belongs to one of them.\n\n` +
250
250
  `Answer questions about workflows and runs by reading the files above. Do not modify run outputs unless asked.`);
251
251
  }
@@ -285,18 +285,18 @@ async function scopeContext(scope, agent) {
285
285
  return [base, RENDERING_BLOCK, skillsBlock(skills)].filter(Boolean).join("\n\n");
286
286
  }
287
287
  /* ---------- backend lifecycle ---------- */
288
- /** Team chats carry the member's model/effort; resolved live so edits apply to new backends. */
288
+ /** Agent sessions carry the agent's model/effort; resolved live so edits apply to new backends. */
289
289
  async function backendTuning(scope, agent) {
290
290
  const tuning = {};
291
- if (scope.kind === "team") {
292
- const member = await getMember(scope.member).catch(() => null);
293
- if (member?.model)
294
- tuning.model = member.model;
295
- if (member?.effort)
296
- tuning.effort = member.effort;
297
- // Claude discovers skills natively; a defined allowlist narrows that.
298
- if (agent === "claude" && member?.skills)
299
- tuning.skills = member.skills;
291
+ if (scope.kind === "agent") {
292
+ const def = await getAgent(scope.slug).catch(() => null);
293
+ if (def?.model)
294
+ tuning.model = def.model;
295
+ if (def?.effort)
296
+ tuning.effort = def.effort;
297
+ // Claude discovers skills natively; an agentined allowlist narrows that.
298
+ if (agent === "claude" && def?.skills)
299
+ tuning.skills = def.skills;
300
300
  }
301
301
  // Run chats live in the run folder — grant claude the project root so
302
302
  // project-level skills and files stay reachable.
@@ -319,7 +319,7 @@ async function ensureBackend(state) {
319
319
  // Routine-triggered sessions run unattended: auto-approve, but keep
320
320
  // the request/resolution pair in the thread as an audit trail.
321
321
  const scope = state.meta.scope;
322
- if (scope.kind === "team" && scope.routine) {
322
+ if (scope.kind === "agent" && scope.routine) {
323
323
  const allow = options.find((o) => o.kind?.startsWith("allow")) ?? options[0];
324
324
  const requestId = randomUUID();
325
325
  emit(state, { type: "permission_request", requestId, title, options });
@@ -418,7 +418,7 @@ export async function postMessage(id, text) {
418
418
  // Routine sessions are one-shot and unattended: release the agent
419
419
  // process as soon as the turn ends instead of waiting for the reaper.
420
420
  const scope = state.meta.scope;
421
- if (scope.kind === "team" && scope.routine)
421
+ if (scope.kind === "agent" && scope.routine)
422
422
  dropBackend(state);
423
423
  }
424
424
  catch (err) {
@@ -26,12 +26,24 @@ export async function writeMeta(meta) {
26
26
  }
27
27
  export async function readMeta(id) {
28
28
  try {
29
- return JSON.parse(await fs.readFile(path.join(safeChatDir(id), "meta.json"), "utf8"));
29
+ return upgradeMeta(JSON.parse(await fs.readFile(path.join(safeChatDir(id), "meta.json"), "utf8")));
30
30
  }
31
31
  catch {
32
32
  return null;
33
33
  }
34
34
  }
35
+ /**
36
+ * Chats written before 0.36 scoped agent sessions as
37
+ * { kind: "team", member } — read them as { kind: "agent", slug } so the
38
+ * folders on disk never need rewriting.
39
+ */
40
+ function upgradeMeta(meta) {
41
+ const legacy = meta.scope;
42
+ if (legacy?.kind === "team" && typeof legacy.member === "string") {
43
+ meta.scope = { kind: "agent", slug: legacy.member, ...(legacy.routine ? { routine: legacy.routine } : {}) };
44
+ }
45
+ return meta;
46
+ }
35
47
  export async function appendEvent(id, ev) {
36
48
  await fs.appendFile(path.join(safeChatDir(id), "events.jsonl"), JSON.stringify(ev) + "\n");
37
49
  }
@@ -17,8 +17,8 @@ export type ChatScope = {
17
17
  kind: "knowledge";
18
18
  bundle?: string;
19
19
  } | {
20
- kind: "team";
21
- member: string;
20
+ kind: "agent";
21
+ slug: string;
22
22
  routine?: string;
23
23
  };
24
24
  export interface ChatMeta {
@@ -0,0 +1,83 @@
1
+ export interface GitFile {
2
+ /** Path relative to the repo root, which is what git speaks. */
3
+ path: string;
4
+ /** Two-letter porcelain code, e.g. " M", "??", "A ". */
5
+ code: string;
6
+ /** Human label for the code: modified, added, deleted, renamed, untracked. */
7
+ status: string;
8
+ /** Inside a synced root and not denied, so it can be selected. */
9
+ syncable: boolean;
10
+ /** Why it cannot be selected, when it cannot. */
11
+ reason?: string;
12
+ }
13
+ export interface GitStatus {
14
+ enabled: boolean;
15
+ /** Set when the feature is on but the repo cannot be used. */
16
+ error?: string;
17
+ repoRoot?: string;
18
+ branch?: string;
19
+ /** Configured or inferred upstream, absent when the branch tracks nothing. */
20
+ upstream?: string;
21
+ remote?: string;
22
+ ahead?: number;
23
+ behind?: number;
24
+ /** True when the branches have diverged, which needs a terminal. */
25
+ diverged?: boolean;
26
+ files?: GitFile[];
27
+ /** Blocked entries left out of `files` because the list was capped. */
28
+ blockedHidden?: number;
29
+ /** Repo-relative roots that may be committed. */
30
+ scope?: string[];
31
+ autosync?: "off" | "pull";
32
+ interval?: number;
33
+ lastFetch?: string;
34
+ lastPull?: string;
35
+ lastError?: string;
36
+ }
37
+ export interface GitDiff {
38
+ diff: string;
39
+ error?: string;
40
+ /** The diff was cut at MAX_DIFF; the rest is available in a terminal. */
41
+ truncated?: boolean;
42
+ }
43
+ /**
44
+ * Everything the git screen renders. Each call shells out to git a handful
45
+ * of times, and the nav badge polls it from every open tab, so results are
46
+ * cached for a beat.
47
+ */
48
+ export declare function gitStatus(fresh?: boolean): Promise<GitStatus>;
49
+ /**
50
+ * Unified diff for one changed file. Reading is as sensitive as writing
51
+ * here: this endpoint has no auth of its own, and a diff of an untracked
52
+ * .env is the file itself. So the status list is the one source of readable
53
+ * paths: a file not listed there, or listed as blocked, is not shown.
54
+ * Status never lists ignored files, which covers .gitignore too.
55
+ */
56
+ export declare function gitDiff(file: string): Promise<GitDiff>;
57
+ /** Stage the given repo-relative paths and commit just those. */
58
+ export declare function gitCommit(paths: string[], message: string): Promise<{
59
+ ok: boolean;
60
+ error?: string;
61
+ committed?: number;
62
+ }>;
63
+ export declare function gitFetch(): Promise<{
64
+ ok: boolean;
65
+ error?: string;
66
+ }>;
67
+ /** Fast-forward only. A diverged branch is the user's problem, not ours. */
68
+ export declare function gitPull(): Promise<{
69
+ ok: boolean;
70
+ error?: string;
71
+ }>;
72
+ export declare function gitPush(): Promise<{
73
+ ok: boolean;
74
+ error?: string;
75
+ }>;
76
+ /**
77
+ * Fetch on an interval, and fast-forward when `autosync: pull` is set. Never
78
+ * commits. A pull is skipped while the tree is dirty, so nobody's unsaved
79
+ * work is disturbed by a background merge. Ticks are chained, not scheduled
80
+ * on a fixed interval: a fetch against a slow remote must finish (or time
81
+ * out) before the next one starts, or a short interval piles up processes.
82
+ */
83
+ export declare function startGitSync(): void;