@netnodeag/kraftwerk 0.35.0 → 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 +26 -19
- package/dist/cli/init.js +3 -3
- package/dist/cli/routines.js +9 -9
- package/dist/config.d.ts +2 -2
- package/dist/config.js +1 -1
- 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/instances.d.ts +12 -0
- package/dist/inspector/instances.js +28 -0
- 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 +34 -29
- package/dist/inspector/skills.d.ts +6 -6
- package/dist/inspector/skills.js +8 -8
- package/inspector/dist/assets/{editor-BABv2LmA.js → editor-7ImnDC3D.js} +3 -3
- package/inspector/dist/assets/{index-DbWUNbt-.js → index--2I7vpcS.js} +1 -1
- package/inspector/dist/assets/index-B2ThRS2S.js +217 -0
- package/inspector/dist/assets/{index-BXspF2KR.css → index-BA7nx4V5.css} +1 -1
- package/inspector/dist/assets/{index-CdwTmXTV.js → index-BC7aIJ-h.js} +1 -1
- package/inspector/dist/assets/{index-DuPp3MUc.js → index-BIXYBiw7.js} +1 -1
- package/inspector/dist/assets/{index-Bmm4zJNU.js → index-BZeYO5wT.js} +1 -1
- package/inspector/dist/assets/{index-sLxulPOm.js → index-B_CIhKa4.js} +1 -1
- package/inspector/dist/assets/{index-DCR7u4Xa.js → index-BuRajvjQ.js} +1 -1
- package/inspector/dist/assets/{index-Ca9Qs5LZ.js → index-C3Ob1O7W.js} +1 -1
- package/inspector/dist/assets/{index-HBdsdJhD.js → index-CIjv7yxE.js} +1 -1
- package/inspector/dist/assets/{index-wkYwz0Tl.js → index-CRAwmMfk.js} +1 -1
- package/inspector/dist/assets/{index-ByWpqzAI.js → index-Ct_jZSyD.js} +1 -1
- package/inspector/dist/assets/{index-hkVov8T4.js → index-DKrBjjGQ.js} +1 -1
- package/inspector/dist/assets/{index-Gx93brFq.js → index-DL_bJiOj.js} +1 -1
- package/inspector/dist/assets/{index-BbTer62d.js → index-DPBAv_kj.js} +1 -1
- package/inspector/dist/assets/{index-CCACy6uF.js → index-DbmSSHvr.js} +1 -1
- package/inspector/dist/assets/{index-DuwmzZAr.js → index-DjRBLY9N.js} +1 -1
- package/inspector/dist/assets/{index-CzPQygR7.js → index-YZtBJhWD.js} +1 -1
- package/inspector/dist/assets/{index-CjlJeW9z.js → index-fWUFhIGR.js} +1 -1
- package/inspector/dist/index.html +2 -2
- package/package.json +1 -1
- package/inspector/dist/assets/index-uEig4--d.js +0 -217
|
@@ -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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentSummary } from "./agents.js";
|
|
1
2
|
/** Durable per-project record. The root is the key; everything else derives from it. */
|
|
2
3
|
export interface ProjectRecord {
|
|
3
4
|
root: string;
|
|
@@ -6,6 +7,8 @@ export interface ProjectRecord {
|
|
|
6
7
|
/** Set on clean shutdown; older than lastStarted means the last run died. */
|
|
7
8
|
lastStopped?: string;
|
|
8
9
|
startCount: number;
|
|
10
|
+
/** Active agents, as last seen by the instance serving this root. */
|
|
11
|
+
agents?: AgentSummary[];
|
|
9
12
|
}
|
|
10
13
|
/** A verified running instance, shaped like a switcher entry. */
|
|
11
14
|
export interface DiscoveredInstance {
|
|
@@ -39,12 +42,21 @@ export interface WorkspaceEntry {
|
|
|
39
42
|
export declare const tildify: (p: string) => string;
|
|
40
43
|
/** Write this instance's registry file (call once the server listens). */
|
|
41
44
|
export declare function registerInstance(port: number, root: string): Promise<void>;
|
|
45
|
+
/** Where this instance answers, as the switcher links it (null before listen). */
|
|
46
|
+
export declare const currentInstanceUrl: () => string | null;
|
|
42
47
|
/** Remove this instance's registry file. Sync so exit handlers can call it. */
|
|
43
48
|
export declare function unregisterInstance(): void;
|
|
44
49
|
/** All other running instances, verified live. Cached briefly — /api/meta is polled. */
|
|
45
50
|
export declare function discoverInstances(): Promise<DiscoveredInstance[]>;
|
|
46
51
|
/** Upsert the project record for a root (call on every inspector start). */
|
|
47
52
|
export declare function registerProject(root: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Write the roster into the project's record. Called on every roster read
|
|
55
|
+
* (the UI polls it, routines tick it), so a record only changes when the
|
|
56
|
+
* roster did. Roots with no record (a CLI run in a project that never
|
|
57
|
+
* started the inspector) are left alone.
|
|
58
|
+
*/
|
|
59
|
+
export declare function syncProjectAgents(root: string, agents: AgentSummary[]): Promise<void>;
|
|
48
60
|
/** Stamp lastStopped on clean shutdown. Sync so exit handlers can call it. */
|
|
49
61
|
export declare function markProjectStopped(): void;
|
|
50
62
|
/** All known projects, most recently started first. */
|
|
@@ -20,6 +20,9 @@ import { selfCommand } from "./self-command.js";
|
|
|
20
20
|
* the root's kraftwerk.yml at read time, so it is always current. A
|
|
21
21
|
* project that is not running can be started again from the switcher or
|
|
22
22
|
* `kraftwerk projects start` — that is what makes a killed UI findable.
|
|
23
|
+
* The record also carries the project's agent roster (written whenever the
|
|
24
|
+
* instance reads it), so the ⌘K palette can list every workspace's agents
|
|
25
|
+
* from a handful of small files without probing anything.
|
|
23
26
|
*/
|
|
24
27
|
const HOME = path.join(os.homedir(), ".kraftwerk");
|
|
25
28
|
const INSTANCES_DIR = path.join(HOME, "instances");
|
|
@@ -47,6 +50,8 @@ export async function registerInstance(port, root) {
|
|
|
47
50
|
}
|
|
48
51
|
catch { } // best-effort — without it discovery just won't see us
|
|
49
52
|
}
|
|
53
|
+
/** Where this instance answers, as the switcher links it (null before listen). */
|
|
54
|
+
export const currentInstanceUrl = () => (selfPort ? `http://localhost:${selfPort}` : null);
|
|
50
55
|
/** Remove this instance's registry file. Sync so exit handlers can call it. */
|
|
51
56
|
export function unregisterInstance() {
|
|
52
57
|
try {
|
|
@@ -137,6 +142,7 @@ async function readProject(file) {
|
|
|
137
142
|
lastStarted: rec.lastStarted ?? rec.firstSeen ?? "",
|
|
138
143
|
lastStopped: rec.lastStopped,
|
|
139
144
|
startCount: rec.startCount ?? 1,
|
|
145
|
+
...(Array.isArray(rec.agents) ? { agents: rec.agents } : {}),
|
|
140
146
|
};
|
|
141
147
|
}
|
|
142
148
|
catch {
|
|
@@ -155,11 +161,33 @@ export async function registerProject(root) {
|
|
|
155
161
|
firstSeen: prev?.firstSeen || now,
|
|
156
162
|
lastStarted: now,
|
|
157
163
|
startCount: (prev?.startCount ?? 0) + 1,
|
|
164
|
+
...(prev?.agents ? { agents: prev.agents } : {}),
|
|
158
165
|
};
|
|
159
166
|
await fs.writeFile(projectFile(abs), JSON.stringify(rec, null, 2));
|
|
160
167
|
}
|
|
161
168
|
catch { } // best-effort, like the instance file
|
|
162
169
|
}
|
|
170
|
+
let syncedAgents = "";
|
|
171
|
+
/**
|
|
172
|
+
* Write the roster into the project's record. Called on every roster read
|
|
173
|
+
* (the UI polls it, routines tick it), so a record only changes when the
|
|
174
|
+
* roster did. Roots with no record (a CLI run in a project that never
|
|
175
|
+
* started the inspector) are left alone.
|
|
176
|
+
*/
|
|
177
|
+
export async function syncProjectAgents(root, agents) {
|
|
178
|
+
const key = `${root}\n${JSON.stringify(agents)}`;
|
|
179
|
+
if (key === syncedAgents)
|
|
180
|
+
return;
|
|
181
|
+
try {
|
|
182
|
+
const file = projectFile(root);
|
|
183
|
+
const rec = await readProject(file);
|
|
184
|
+
if (!rec)
|
|
185
|
+
return;
|
|
186
|
+
await fs.writeFile(file, JSON.stringify({ ...rec, agents }, null, 2));
|
|
187
|
+
syncedAgents = key;
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
}
|
|
163
191
|
/** Stamp lastStopped on clean shutdown. Sync so exit handlers can call it. */
|
|
164
192
|
export function markProjectStopped() {
|
|
165
193
|
if (!selfRoot)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Routines: per-
|
|
3
|
-
* weekday at 9". Definitions live next to the
|
|
2
|
+
* Routines: per-agent scheduled prompts — "message your agent every
|
|
3
|
+
* weekday at 9". Definitions live next to the agent (git-tracked):
|
|
4
4
|
*
|
|
5
5
|
* agents/<slug>/routines.yml # list of { id, name, schedule, prompt, enabled }
|
|
6
6
|
*
|
|
7
7
|
* Runtime state (last run / last session / errors) is run-state, not
|
|
8
8
|
* config, so it lives in <output>/routines-state.json. The scheduler is a
|
|
9
9
|
* plain interval inside the inspector server: each due routine opens a new
|
|
10
|
-
* session (chat) for the
|
|
11
|
-
* the
|
|
10
|
+
* session (chat) for the agent and posts the prompt — the run shows up in
|
|
11
|
+
* the agent's sessions sidebar like any conversation. Schedules use
|
|
12
12
|
* standard 5-field cron in the server's local time.
|
|
13
13
|
*/
|
|
14
14
|
export interface Routine {
|
|
@@ -2,7 +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 {
|
|
5
|
+
import { getAgent, listAgents, safeAgentSlug, agentsRoot } from "./agents.js";
|
|
6
6
|
import { createChat, postMessage } from "./chat/sessions.js";
|
|
7
7
|
/* ---------- cron ---------- */
|
|
8
8
|
const ALIASES = {
|
|
@@ -86,7 +86,7 @@ export function nextRun(schedule, from = new Date()) {
|
|
|
86
86
|
}
|
|
87
87
|
/* ---------- definition CRUD (agents/<slug>/routines.yml) ---------- */
|
|
88
88
|
async function routinesFile(slug) {
|
|
89
|
-
return path.join(await
|
|
89
|
+
return path.join(await agentsRoot(), safeAgentSlug(slug), "routines.yml");
|
|
90
90
|
}
|
|
91
91
|
function normalizeRoutine(raw) {
|
|
92
92
|
const id = String(raw.id ?? "").trim();
|
|
@@ -179,14 +179,14 @@ export async function routineStatuses(slug) {
|
|
|
179
179
|
});
|
|
180
180
|
}
|
|
181
181
|
/* ---------- firing ---------- */
|
|
182
|
-
/** Open a fresh session for the
|
|
182
|
+
/** Open a fresh session for the agent and post the routine's prompt. */
|
|
183
183
|
async function fireRoutine(slug, routine) {
|
|
184
|
-
const
|
|
185
|
-
if (!
|
|
186
|
-
throw new Error(`
|
|
184
|
+
const def = await getAgent(slug);
|
|
185
|
+
if (!def)
|
|
186
|
+
throw new Error(`agent "${slug}" not found`);
|
|
187
187
|
const meta = await createChat({
|
|
188
|
-
agent:
|
|
189
|
-
scope: { kind: "
|
|
188
|
+
agent: def.harness,
|
|
189
|
+
scope: { kind: "agent", slug: slug, routine: routine.id },
|
|
190
190
|
title: `⏰ ${routine.name}`,
|
|
191
191
|
});
|
|
192
192
|
const { error } = await postMessage(meta.id, routine.prompt);
|
|
@@ -219,16 +219,16 @@ async function tick() {
|
|
|
219
219
|
const now = new Date();
|
|
220
220
|
const p = (n) => String(n).padStart(2, "0");
|
|
221
221
|
const minuteKey = `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())}T${p(now.getHours())}:${p(now.getMinutes())}`;
|
|
222
|
-
let
|
|
222
|
+
let agents;
|
|
223
223
|
try {
|
|
224
|
-
|
|
224
|
+
agents = await listAgents();
|
|
225
225
|
}
|
|
226
226
|
catch {
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
229
|
const state = await readState();
|
|
230
230
|
let dirty = false;
|
|
231
|
-
for (const m of
|
|
231
|
+
for (const m of agents) {
|
|
232
232
|
for (const r of await listRoutines(m.slug).catch(() => [])) {
|
|
233
233
|
if (!r.enabled)
|
|
234
234
|
continue;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type AgentSummary } from "./agents.js";
|
|
2
|
+
/**
|
|
3
|
+
* The ⌘K palette: every active agent in every workspace this machine
|
|
4
|
+
* knows, so a user can jump to one without switching workspaces first.
|
|
5
|
+
* This instance reads its own roster from disk; the others come from the
|
|
6
|
+
* project registry, where each instance records its roster whenever it
|
|
7
|
+
* reads it (see instances.ts). Stopped workspaces are listed too — the
|
|
8
|
+
* palette starts them on the way to the agent.
|
|
9
|
+
*/
|
|
10
|
+
export interface WorkspaceAgents {
|
|
11
|
+
name: string;
|
|
12
|
+
icon?: string;
|
|
13
|
+
/** Where the workspace's UI lives — hits link to `${url}/#/agents/<slug>`. */
|
|
14
|
+
url: string;
|
|
15
|
+
/** Project root (what /api/projects/start takes for a stopped workspace). */
|
|
16
|
+
root?: string;
|
|
17
|
+
/** The instance answering this request: hits navigate in place. */
|
|
18
|
+
current: boolean;
|
|
19
|
+
live: boolean;
|
|
20
|
+
agents: AgentSummary[];
|
|
21
|
+
}
|
|
22
|
+
export interface AgentSearch {
|
|
23
|
+
workspaces: WorkspaceAgents[];
|
|
24
|
+
}
|
|
25
|
+
export declare function searchAgents(): Promise<AgentSearch>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveProject } from "../config.js";
|
|
3
|
+
import { getProjectRoot } from "./context.js";
|
|
4
|
+
import { listAgents, toSummary } from "./agents.js";
|
|
5
|
+
import { currentInstanceUrl, discoverWorkspaces, listProjects } from "./instances.js";
|
|
6
|
+
export async function searchAgents() {
|
|
7
|
+
const project = await resolveProject(getProjectRoot()).catch(() => null);
|
|
8
|
+
const self = {
|
|
9
|
+
name: project?.config.name ?? path.basename(project?.root ?? getProjectRoot()),
|
|
10
|
+
icon: project?.config.icon || undefined,
|
|
11
|
+
url: currentInstanceUrl() ?? "",
|
|
12
|
+
root: project?.root ?? getProjectRoot(),
|
|
13
|
+
current: true,
|
|
14
|
+
live: true,
|
|
15
|
+
agents: (await listAgents().catch(() => [])).filter((a) => !a.archived).map(toSummary),
|
|
16
|
+
};
|
|
17
|
+
const [workspaces, projects] = await Promise.all([discoverWorkspaces(), listProjects()]);
|
|
18
|
+
const rosters = new Map(projects.map((p) => [p.root, p.agents ?? []]));
|
|
19
|
+
const others = workspaces
|
|
20
|
+
.filter((w) => w.root && w.exists !== false)
|
|
21
|
+
.map((w) => ({
|
|
22
|
+
name: w.name,
|
|
23
|
+
icon: w.icon,
|
|
24
|
+
url: w.url,
|
|
25
|
+
root: w.root,
|
|
26
|
+
current: false,
|
|
27
|
+
live: w.live,
|
|
28
|
+
agents: rosters.get(w.root) ?? [],
|
|
29
|
+
}));
|
|
30
|
+
return { workspaces: [self, ...others] };
|
|
31
|
+
}
|
package/dist/inspector/server.js
CHANGED
|
@@ -8,11 +8,12 @@ import { listWorkflows, getWorkflow } from "./workflows.js";
|
|
|
8
8
|
import { dockerStatus, triggerRun, stopRun } from "./runner.js";
|
|
9
9
|
import { cancelChat, createChat, deleteChat, disposeAllBackends, getChat, listChats, postMessage, resolvePermission, subscribeChat, } from "./chat/sessions.js";
|
|
10
10
|
import { bundleDetail, conceptDetail, createBundle, knowledgeIndex, putConcept, verifyFromUi, } from "./knowledge.js";
|
|
11
|
-
import {
|
|
11
|
+
import { deleteAgent, getAgent, listAgents, saveAgent, setAgentArchived, agentsRoot, } from "./agents.js";
|
|
12
12
|
import { deleteAgentSkill, getSkill, listAgentSkills, listSkills, readSkill, saveAgentSkill, skillsRoot, } from "./skills.js";
|
|
13
13
|
import { discoverWorkspaces, forgetProject, listWorkspacesDetailed, markProjectStopped, registerInstance, registerProject, startProject, stopProject, tildify, unregisterInstance, } from "./instances.js";
|
|
14
14
|
import { gitCommit, gitDiff, gitFetch, gitPull, gitPush, gitStatus, startGitSync, } from "./git.js";
|
|
15
15
|
import { getSettings, saveSettings } from "./settings.js";
|
|
16
|
+
import { searchAgents } from "./search.js";
|
|
16
17
|
import { deleteRoutine, routineStatuses, runRoutineNow, saveRoutine, startRoutineScheduler, } from "./routines.js";
|
|
17
18
|
/**
|
|
18
19
|
* The inspector server: a plain node:http server with no dependencies.
|
|
@@ -249,6 +250,10 @@ async function handleApi(req, res, url) {
|
|
|
249
250
|
const result = await run();
|
|
250
251
|
return json(res, result, result.ok ? 200 : 409);
|
|
251
252
|
}
|
|
253
|
+
// GET /api/search/agents — active agents of every reachable workspace (the ⌘K palette)
|
|
254
|
+
if (seg.length === 3 && seg[1] === "search" && seg[2] === "agents" && method === "GET") {
|
|
255
|
+
return json(res, await searchAgents());
|
|
256
|
+
}
|
|
252
257
|
// GET /api/projects — every known workspace incl. this one, with state + counts (admin screen)
|
|
253
258
|
if (seg.length === 2 && seg[1] === "projects" && method === "GET") {
|
|
254
259
|
return json(res, await listWorkspacesDetailed());
|
|
@@ -516,33 +521,33 @@ async function handleApi(req, res, url) {
|
|
|
516
521
|
const skill = await getSkill(decodeURIComponent(seg[2]));
|
|
517
522
|
return skill ? json(res, skill) : json(res, { error: "not found" }, 404);
|
|
518
523
|
}
|
|
519
|
-
// GET/POST /api/
|
|
520
|
-
if (seg.length === 2 && seg[1] === "
|
|
524
|
+
// GET/POST /api/agents — member list / create a member
|
|
525
|
+
if (seg.length === 2 && seg[1] === "agents") {
|
|
521
526
|
if (method === "GET") {
|
|
522
|
-
return json(res, { root: await
|
|
527
|
+
return json(res, { root: await agentsRoot(), agents: await listAgents() });
|
|
523
528
|
}
|
|
524
529
|
if (method === "POST") {
|
|
525
530
|
try {
|
|
526
531
|
const body = JSON.parse(await readBody(req));
|
|
527
|
-
return json(res, await
|
|
532
|
+
return json(res, await saveAgent({ ...body, slug: undefined }));
|
|
528
533
|
}
|
|
529
534
|
catch (err) {
|
|
530
535
|
return json(res, { error: err.message }, 400);
|
|
531
536
|
}
|
|
532
537
|
}
|
|
533
538
|
}
|
|
534
|
-
// POST /api/
|
|
535
|
-
if (seg.length === 4 && seg[1] === "
|
|
539
|
+
// POST /api/agents/:slug/archive — archive/unarchive a member ({ archived: boolean })
|
|
540
|
+
if (seg.length === 4 && seg[1] === "agents" && seg[3] === "archive" && method === "POST") {
|
|
536
541
|
try {
|
|
537
542
|
const body = JSON.parse(await readBody(req));
|
|
538
|
-
return json(res, await
|
|
543
|
+
return json(res, await setAgentArchived(decodeURIComponent(seg[2]), body.archived === true));
|
|
539
544
|
}
|
|
540
545
|
catch (err) {
|
|
541
546
|
return json(res, { error: err.message }, 400);
|
|
542
547
|
}
|
|
543
548
|
}
|
|
544
|
-
// GET/POST /api/
|
|
545
|
-
if (seg.length === 4 && seg[1] === "
|
|
549
|
+
// GET/POST /api/agents/:slug/skills — the agent's own skills / create or update one
|
|
550
|
+
if (seg.length === 4 && seg[1] === "agents" && seg[3] === "skills") {
|
|
546
551
|
const slug = decodeURIComponent(seg[2]);
|
|
547
552
|
try {
|
|
548
553
|
if (method === "GET")
|
|
@@ -556,8 +561,8 @@ async function handleApi(req, res, url) {
|
|
|
556
561
|
return json(res, { error: err.message }, 400);
|
|
557
562
|
}
|
|
558
563
|
}
|
|
559
|
-
// GET/DELETE /api/
|
|
560
|
-
if (seg.length === 5 && seg[1] === "
|
|
564
|
+
// GET/DELETE /api/agents/:slug/skills/:name — one agent skill with content / delete
|
|
565
|
+
if (seg.length === 5 && seg[1] === "agents" && seg[3] === "skills") {
|
|
561
566
|
const slug = decodeURIComponent(seg[2]);
|
|
562
567
|
const name = decodeURIComponent(seg[4]);
|
|
563
568
|
try {
|
|
@@ -576,8 +581,8 @@ async function handleApi(req, res, url) {
|
|
|
576
581
|
return json(res, { error: err.message }, 400);
|
|
577
582
|
}
|
|
578
583
|
}
|
|
579
|
-
// GET/POST /api/
|
|
580
|
-
if (seg.length === 4 && seg[1] === "
|
|
584
|
+
// GET/POST /api/agents/:slug/routines — list (with run state) / upsert
|
|
585
|
+
if (seg.length === 4 && seg[1] === "agents" && seg[3] === "routines") {
|
|
581
586
|
const slug = decodeURIComponent(seg[2]);
|
|
582
587
|
try {
|
|
583
588
|
if (method === "GET")
|
|
@@ -591,8 +596,8 @@ async function handleApi(req, res, url) {
|
|
|
591
596
|
return json(res, { error: err.message }, 400);
|
|
592
597
|
}
|
|
593
598
|
}
|
|
594
|
-
// DELETE /api/
|
|
595
|
-
if (seg.length >= 5 && seg[1] === "
|
|
599
|
+
// DELETE /api/agents/:slug/routines/:id | POST /api/agents/:slug/routines/:id/run
|
|
600
|
+
if (seg.length >= 5 && seg[1] === "agents" && seg[3] === "routines") {
|
|
596
601
|
const slug = decodeURIComponent(seg[2]);
|
|
597
602
|
const id = decodeURIComponent(seg[4]);
|
|
598
603
|
try {
|
|
@@ -608,20 +613,20 @@ async function handleApi(req, res, url) {
|
|
|
608
613
|
return json(res, { error: err.message }, 400);
|
|
609
614
|
}
|
|
610
615
|
}
|
|
611
|
-
// GET/PUT/DELETE /api/
|
|
612
|
-
if (seg.length === 3 && seg[1] === "
|
|
616
|
+
// GET/PUT/DELETE /api/agents/:slug
|
|
617
|
+
if (seg.length === 3 && seg[1] === "agents") {
|
|
613
618
|
const slug = decodeURIComponent(seg[2]);
|
|
614
619
|
try {
|
|
615
620
|
if (method === "GET") {
|
|
616
|
-
const
|
|
617
|
-
return
|
|
621
|
+
const agent = await getAgent(slug);
|
|
622
|
+
return agent ? json(res, agent) : json(res, { error: "not found" }, 404);
|
|
618
623
|
}
|
|
619
624
|
if (method === "PUT") {
|
|
620
625
|
const body = JSON.parse(await readBody(req));
|
|
621
|
-
return json(res, await
|
|
626
|
+
return json(res, await saveAgent({ ...body, slug }));
|
|
622
627
|
}
|
|
623
628
|
if (method === "DELETE") {
|
|
624
|
-
await
|
|
629
|
+
await deleteAgent(slug);
|
|
625
630
|
return json(res, { ok: true });
|
|
626
631
|
}
|
|
627
632
|
}
|
|
@@ -643,13 +648,13 @@ async function handleApi(req, res, url) {
|
|
|
643
648
|
}
|
|
644
649
|
let agent = body.agent;
|
|
645
650
|
let scope;
|
|
646
|
-
if (body.scope?.kind === "
|
|
647
|
-
//
|
|
648
|
-
const
|
|
649
|
-
if (!
|
|
650
|
-
return json(res, { error: "
|
|
651
|
-
scope = { kind: "
|
|
652
|
-
agent =
|
|
651
|
+
if (body.scope?.kind === "agent" && body.scope.slug) {
|
|
652
|
+
// Agent sessions run on the agent's configured harness.
|
|
653
|
+
const def = await getAgent(body.scope.slug).catch(() => null);
|
|
654
|
+
if (!def)
|
|
655
|
+
return json(res, { error: "agent not found" }, 404);
|
|
656
|
+
scope = { kind: "agent", slug: def.slug };
|
|
657
|
+
agent = def.harness;
|
|
653
658
|
}
|
|
654
659
|
else if (body.scope?.kind === "run" && body.scope.runId) {
|
|
655
660
|
scope = { kind: "run", runId: body.scope.runId };
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
* skill. Skills are surfaced to every chat as context (and expanded on
|
|
14
14
|
* /<name> invocation), so they work identically on claude, codex, and pi.
|
|
15
15
|
*
|
|
16
|
-
* On top of the shared roots, each
|
|
16
|
+
* On top of the shared roots, each agent can carry private skills in
|
|
17
17
|
*
|
|
18
18
|
* agents/<slug>/skills/<name>/SKILL.md (agent, git-tracked)
|
|
19
19
|
*
|
|
20
20
|
* Agent skills are visible only to that agent's sessions, always apply
|
|
21
|
-
* (the
|
|
21
|
+
* (the agent's allowlist narrows shared skills only), and shadow
|
|
22
22
|
* same-named shared skills.
|
|
23
23
|
*/
|
|
24
24
|
export interface SkillInfo {
|
|
@@ -31,7 +31,7 @@ export interface SkillInfo {
|
|
|
31
31
|
dir: string;
|
|
32
32
|
/** workspace = kraftwerk skills root; project = .claude/skills; user = ~/.claude/skills; agent = agents/<slug>/skills. */
|
|
33
33
|
source: "workspace" | "project" | "user" | "agent";
|
|
34
|
-
/** For source "agent": the owning
|
|
34
|
+
/** For source "agent": the owning agent's slug. */
|
|
35
35
|
agent?: string;
|
|
36
36
|
}
|
|
37
37
|
export interface SkillDetail extends SkillInfo {
|
|
@@ -42,15 +42,15 @@ export interface SkillDetail extends SkillInfo {
|
|
|
42
42
|
}
|
|
43
43
|
/** Absolute workspace skills root (kraftwerk.yml `skills`, default skills/). */
|
|
44
44
|
export declare function skillsRoot(): Promise<string>;
|
|
45
|
-
/** The private skills root of one
|
|
45
|
+
/** The private skills root of one agent: agents/<slug>/skills. */
|
|
46
46
|
export declare function agentSkillsRoot(slug: string): Promise<string>;
|
|
47
47
|
/** One agent's private skills (agents/<slug>/skills/<name>/SKILL.md). */
|
|
48
48
|
export declare function listAgentSkills(slug: string): Promise<SkillInfo[]>;
|
|
49
49
|
/**
|
|
50
|
-
* All discovered skills; agent (when
|
|
50
|
+
* All discovered skills; agent (when an agent slug is given) shadows workspace
|
|
51
51
|
* shadows project shadows user (global).
|
|
52
52
|
*/
|
|
53
|
-
export declare function listSkills(
|
|
53
|
+
export declare function listSkills(agent?: string): Promise<SkillInfo[]>;
|
|
54
54
|
/** Create or update one agent skill; writes agents/<slug>/skills/<name>/SKILL.md. */
|
|
55
55
|
export declare function saveAgentSkill(slug: string, name: string, content: string): Promise<SkillInfo & {
|
|
56
56
|
content: string;
|
package/dist/inspector/skills.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { parse } from "yaml";
|
|
5
5
|
import { resolveProject } from "../config.js";
|
|
6
6
|
import { getProjectRoot } from "./context.js";
|
|
7
|
-
import {
|
|
7
|
+
import { safeAgentSlug, agentsRoot } from "./agents.js";
|
|
8
8
|
const NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
|
9
9
|
function frontmatter(md) {
|
|
10
10
|
const m = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/.exec(md);
|
|
@@ -58,9 +58,9 @@ async function scanRoot(root, source) {
|
|
|
58
58
|
}));
|
|
59
59
|
return skills.filter(Boolean).sort((a, b) => a.name.localeCompare(b.name));
|
|
60
60
|
}
|
|
61
|
-
/** The private skills root of one
|
|
61
|
+
/** The private skills root of one agent: agents/<slug>/skills. */
|
|
62
62
|
export async function agentSkillsRoot(slug) {
|
|
63
|
-
return path.join(await
|
|
63
|
+
return path.join(await agentsRoot(), safeAgentSlug(slug), "skills");
|
|
64
64
|
}
|
|
65
65
|
/** One agent's private skills (agents/<slug>/skills/<name>/SKILL.md). */
|
|
66
66
|
export async function listAgentSkills(slug) {
|
|
@@ -68,17 +68,17 @@ export async function listAgentSkills(slug) {
|
|
|
68
68
|
return skills.map((s) => ({ ...s, agent: slug }));
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* All discovered skills; agent (when
|
|
71
|
+
* All discovered skills; agent (when an agent slug is given) shadows workspace
|
|
72
72
|
* shadows project shadows user (global).
|
|
73
73
|
*/
|
|
74
|
-
export async function listSkills(
|
|
75
|
-
const [
|
|
76
|
-
|
|
74
|
+
export async function listSkills(agent) {
|
|
75
|
+
const [own, workspace, project, user] = await Promise.all([
|
|
76
|
+
agent ? listAgentSkills(agent).catch(() => []) : [],
|
|
77
77
|
skillsRoot().then((root) => scanRoot(root, "workspace")),
|
|
78
78
|
scanRoot(path.join(getProjectRoot(), ".claude", "skills"), "project"),
|
|
79
79
|
scanRoot(path.join(os.homedir(), ".claude", "skills"), "user"),
|
|
80
80
|
]);
|
|
81
|
-
const out = [...
|
|
81
|
+
const out = [...own];
|
|
82
82
|
const seen = new Set(out.map((s) => s.name.toLowerCase()));
|
|
83
83
|
for (const s of [...workspace, ...project, ...user]) {
|
|
84
84
|
if (seen.has(s.name.toLowerCase()))
|