@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.
Files changed (43) hide show
  1. package/README.md +26 -19
  2. package/dist/cli/init.js +3 -3
  3. package/dist/cli/routines.js +9 -9
  4. package/dist/config.d.ts +2 -2
  5. package/dist/config.js +1 -1
  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/instances.d.ts +12 -0
  13. package/dist/inspector/instances.js +28 -0
  14. package/dist/inspector/routines.d.ts +4 -4
  15. package/dist/inspector/routines.js +11 -11
  16. package/dist/inspector/search.d.ts +25 -0
  17. package/dist/inspector/search.js +31 -0
  18. package/dist/inspector/server.js +34 -29
  19. package/dist/inspector/skills.d.ts +6 -6
  20. package/dist/inspector/skills.js +8 -8
  21. package/inspector/dist/assets/{editor-BABv2LmA.js → editor-7ImnDC3D.js} +3 -3
  22. package/inspector/dist/assets/{index-DbWUNbt-.js → index--2I7vpcS.js} +1 -1
  23. package/inspector/dist/assets/index-B2ThRS2S.js +217 -0
  24. package/inspector/dist/assets/{index-BXspF2KR.css → index-BA7nx4V5.css} +1 -1
  25. package/inspector/dist/assets/{index-CdwTmXTV.js → index-BC7aIJ-h.js} +1 -1
  26. package/inspector/dist/assets/{index-DuPp3MUc.js → index-BIXYBiw7.js} +1 -1
  27. package/inspector/dist/assets/{index-Bmm4zJNU.js → index-BZeYO5wT.js} +1 -1
  28. package/inspector/dist/assets/{index-sLxulPOm.js → index-B_CIhKa4.js} +1 -1
  29. package/inspector/dist/assets/{index-DCR7u4Xa.js → index-BuRajvjQ.js} +1 -1
  30. package/inspector/dist/assets/{index-Ca9Qs5LZ.js → index-C3Ob1O7W.js} +1 -1
  31. package/inspector/dist/assets/{index-HBdsdJhD.js → index-CIjv7yxE.js} +1 -1
  32. package/inspector/dist/assets/{index-wkYwz0Tl.js → index-CRAwmMfk.js} +1 -1
  33. package/inspector/dist/assets/{index-ByWpqzAI.js → index-Ct_jZSyD.js} +1 -1
  34. package/inspector/dist/assets/{index-hkVov8T4.js → index-DKrBjjGQ.js} +1 -1
  35. package/inspector/dist/assets/{index-Gx93brFq.js → index-DL_bJiOj.js} +1 -1
  36. package/inspector/dist/assets/{index-BbTer62d.js → index-DPBAv_kj.js} +1 -1
  37. package/inspector/dist/assets/{index-CCACy6uF.js → index-DbmSSHvr.js} +1 -1
  38. package/inspector/dist/assets/{index-DuwmzZAr.js → index-DjRBLY9N.js} +1 -1
  39. package/inspector/dist/assets/{index-CzPQygR7.js → index-YZtBJhWD.js} +1 -1
  40. package/inspector/dist/assets/{index-CjlJeW9z.js → index-fWUFhIGR.js} +1 -1
  41. package/inspector/dist/index.html +2 -2
  42. package/package.json +1 -1
  43. 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
  }
@@ -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 {
@@ -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-team-member scheduled prompts — "message your agent every
3
- * weekday at 9". Definitions live next to the member (git-tracked):
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 member and posts the prompt — the run shows up in
11
- * the member's sessions sidebar like any conversation. Schedules use
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 { getMember, listMembers, safeMemberSlug, teamRoot } from "./team.js";
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 teamRoot(), safeMemberSlug(slug), "routines.yml");
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 member and post the routine's prompt. */
182
+ /** Open a fresh session for the agent and post the routine's prompt. */
183
183
  async function fireRoutine(slug, routine) {
184
- const member = await getMember(slug);
185
- if (!member)
186
- throw new Error(`team agent "${slug}" not found`);
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: member.harness,
189
- scope: { kind: "team", member: slug, routine: routine.id },
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 members;
222
+ let agents;
223
223
  try {
224
- members = await listMembers();
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 members) {
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
+ }
@@ -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 { deleteMember, getMember, listMembers, saveMember, setMemberArchived, teamRoot, } from "./team.js";
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/team — member list / create a member
520
- if (seg.length === 2 && seg[1] === "team") {
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 teamRoot(), members: await listMembers() });
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 saveMember({ ...body, slug: undefined }));
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/team/:slug/archive — archive/unarchive a member ({ archived: boolean })
535
- if (seg.length === 4 && seg[1] === "team" && seg[3] === "archive" && method === "POST") {
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 setMemberArchived(decodeURIComponent(seg[2]), body.archived === true));
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/team/:slug/skills — the agent's own skills / create or update one
545
- if (seg.length === 4 && seg[1] === "team" && seg[3] === "skills") {
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/team/:slug/skills/:name — one agent skill with content / delete
560
- if (seg.length === 5 && seg[1] === "team" && seg[3] === "skills") {
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/team/:slug/routines — list (with run state) / upsert
580
- if (seg.length === 4 && seg[1] === "team" && seg[3] === "routines") {
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/team/:slug/routines/:id | POST /api/team/:slug/routines/:id/run
595
- if (seg.length >= 5 && seg[1] === "team" && seg[3] === "routines") {
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/team/:slug
612
- if (seg.length === 3 && seg[1] === "team") {
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 member = await getMember(slug);
617
- return member ? json(res, member) : json(res, { error: "not found" }, 404);
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 saveMember({ ...body, slug }));
626
+ return json(res, await saveAgent({ ...body, slug }));
622
627
  }
623
628
  if (method === "DELETE") {
624
- await deleteMember(slug);
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 === "team" && body.scope.member) {
647
- // Team sessions run on the member's configured harness.
648
- const member = await getMember(body.scope.member).catch(() => null);
649
- if (!member)
650
- return json(res, { error: "team agent not found" }, 404);
651
- scope = { kind: "team", member: member.slug };
652
- agent = member.harness;
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 team agent can carry private skills in
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 member's allowlist narrows shared skills only), and shadow
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 member's slug. */
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 team agent: agents/<slug>/skills. */
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 a member is given) shadows workspace
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(member?: string): Promise<SkillInfo[]>;
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;
@@ -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 { safeMemberSlug, teamRoot } from "./team.js";
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 team agent: agents/<slug>/skills. */
61
+ /** The private skills root of one agent: agents/<slug>/skills. */
62
62
  export async function agentSkillsRoot(slug) {
63
- return path.join(await teamRoot(), safeMemberSlug(slug), "skills");
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 a member is given) shadows workspace
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(member) {
75
- const [agent, workspace, project, user] = await Promise.all([
76
- member ? listAgentSkills(member).catch(() => []) : [],
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 = [...agent];
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()))