@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.
- package/README.md +40 -19
- package/dist/cli/init.js +3 -3
- package/dist/cli/routines.js +9 -9
- package/dist/config.d.ts +26 -2
- package/dist/config.js +43 -3
- package/dist/inspector/{team.d.ts → agents.d.ts} +21 -12
- package/dist/inspector/{team.js → agents.js} +34 -19
- package/dist/inspector/chat/backend.d.ts +1 -1
- package/dist/inspector/chat/sessions.js +43 -43
- package/dist/inspector/chat/store.js +13 -1
- package/dist/inspector/chat/types.d.ts +2 -2
- package/dist/inspector/git.d.ts +83 -0
- package/dist/inspector/git.js +651 -0
- package/dist/inspector/instances.d.ts +12 -0
- package/dist/inspector/instances.js +37 -1
- package/dist/inspector/routines.d.ts +4 -4
- package/dist/inspector/routines.js +11 -11
- package/dist/inspector/search.d.ts +25 -0
- package/dist/inspector/search.js +31 -0
- package/dist/inspector/server.js +166 -32
- package/dist/inspector/settings.d.ts +11 -1
- package/dist/inspector/settings.js +45 -0
- package/dist/inspector/skills.d.ts +6 -6
- package/dist/inspector/skills.js +8 -8
- package/dist/runner/docker.d.ts +1 -0
- package/dist/runner/docker.js +1 -1
- package/inspector/dist/assets/{editor-CXrDJOv_.js → editor-7ImnDC3D.js} +3 -3
- package/inspector/dist/assets/{index-BxEG-IdY.js → index--2I7vpcS.js} +1 -1
- package/inspector/dist/assets/index-B2ThRS2S.js +217 -0
- package/inspector/dist/assets/index-BA7nx4V5.css +1 -0
- package/inspector/dist/assets/{index-Bumwg9SM.js → index-BC7aIJ-h.js} +1 -1
- package/inspector/dist/assets/{index-BULB2rC_.js → index-BIXYBiw7.js} +1 -1
- package/inspector/dist/assets/{index-C3hpUVfp.js → index-BZeYO5wT.js} +1 -1
- package/inspector/dist/assets/{index-BQMgADlc.js → index-B_CIhKa4.js} +1 -1
- package/inspector/dist/assets/{index-DrSWAGft.js → index-BuRajvjQ.js} +1 -1
- package/inspector/dist/assets/{index-BNcXQYbv.js → index-C3Ob1O7W.js} +1 -1
- package/inspector/dist/assets/{index-BIDsfw8C.js → index-CIjv7yxE.js} +1 -1
- package/inspector/dist/assets/{index-ChuqpBnv.js → index-CRAwmMfk.js} +1 -1
- package/inspector/dist/assets/{index-x5G6Np7T.js → index-Ct_jZSyD.js} +1 -1
- package/inspector/dist/assets/{index-7J4zAS6J.js → index-DKrBjjGQ.js} +1 -1
- package/inspector/dist/assets/{index-CUCCMUMY.js → index-DL_bJiOj.js} +1 -1
- package/inspector/dist/assets/{index-DAi1VtJ8.js → index-DPBAv_kj.js} +1 -1
- package/inspector/dist/assets/{index-Upf-BEsx.js → index-DbmSSHvr.js} +1 -1
- package/inspector/dist/assets/{index-CNJyAniG.js → index-DjRBLY9N.js} +1 -1
- package/inspector/dist/assets/{index-CszaAR9T.js → index-YZtBJhWD.js} +1 -1
- package/inspector/dist/assets/{index-CMMYSoL3.js → index-fWUFhIGR.js} +1 -1
- package/inspector/dist/index.html +2 -2
- package/package.json +5 -2
- package/schema/kraftwerk.schema.json +35 -1
- package/inspector/dist/assets/index-B7rwNdVj.css +0 -1
- 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 {
|
|
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
|
|
78
|
-
* sessions), narrowed by the
|
|
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 === "
|
|
84
|
-
if (scope.kind !== "
|
|
83
|
+
const all = await listSkills(scope.kind === "agent" ? scope.slug : undefined).catch(() => []);
|
|
84
|
+
if (scope.kind !== "agent")
|
|
85
85
|
return all;
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
86
|
+
const def = await getAgent(scope.slug).catch(() => null);
|
|
87
|
+
if (!def || def.skills === undefined)
|
|
88
88
|
return all;
|
|
89
|
-
const allowed = new Set(
|
|
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
|
|
126
|
-
async function
|
|
127
|
-
if (
|
|
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 =
|
|
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 ${
|
|
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 === "
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
152
|
-
return `You were opened as
|
|
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) =>
|
|
156
|
-
const missing =
|
|
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 ${
|
|
161
|
-
`(defined in agents/${
|
|
162
|
-
`is a conversation with the same ${
|
|
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${
|
|
165
|
-
(
|
|
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
|
|
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
|
|
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,
|
|
215
|
+
const [{ workflows }, runs, knowledge, agents] = await Promise.all([
|
|
216
216
|
listWorkflows(),
|
|
217
217
|
listRuns(),
|
|
218
218
|
knowledgeIndex().catch(() => ({ bundles: [] })),
|
|
219
|
-
|
|
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
|
|
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
|
-
`##
|
|
248
|
-
`These are persistent
|
|
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
|
-
/**
|
|
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 === "
|
|
292
|
-
const
|
|
293
|
-
if (
|
|
294
|
-
tuning.model =
|
|
295
|
-
if (
|
|
296
|
-
tuning.effort =
|
|
297
|
-
// Claude discovers skills natively;
|
|
298
|
-
if (agent === "claude" &&
|
|
299
|
-
tuning.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 === "
|
|
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 === "
|
|
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
|
}
|
|
@@ -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;
|