@netnodeag/kraftwerk 0.57.3 → 0.58.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 +85 -2
- package/dist/acp.js +2 -1
- package/dist/cli/journal.d.ts +10 -0
- package/dist/cli/journal.js +36 -0
- package/dist/cli/knowledge.js +17 -0
- package/dist/cli/kraftwerk.js +6 -0
- package/dist/cli/projects.js +3 -3
- package/dist/cli/repos.js +1 -1
- package/dist/cli/trash.d.ts +2 -0
- package/dist/cli/trash.js +70 -0
- package/dist/cli/vibeables.js +3 -3
- package/dist/inspector/agent-status.d.ts +21 -0
- package/dist/inspector/agent-status.js +25 -0
- package/dist/inspector/agents.d.ts +5 -0
- package/dist/inspector/agents.js +10 -1
- package/dist/inspector/attention.d.ts +48 -0
- package/dist/inspector/attention.js +66 -0
- package/dist/inspector/channels.js +2 -1
- package/dist/inspector/chat/sessions.d.ts +52 -1
- package/dist/inspector/chat/sessions.js +160 -27
- package/dist/inspector/chat/store.d.ts +2 -0
- package/dist/inspector/chat/store.js +5 -1
- package/dist/inspector/files.d.ts +87 -0
- package/dist/inspector/files.js +316 -0
- package/dist/inspector/git.js +3 -1
- package/dist/inspector/journal.d.ts +31 -0
- package/dist/inspector/journal.js +72 -0
- package/dist/inspector/knowledge.d.ts +2 -0
- package/dist/inspector/knowledge.js +7 -0
- package/dist/inspector/projects.d.ts +1 -1
- package/dist/inspector/projects.js +5 -2
- package/dist/inspector/repos.js +2 -1
- package/dist/inspector/runs.d.ts +1 -1
- package/dist/inspector/runs.js +3 -2
- package/dist/inspector/server.js +111 -1
- package/dist/inspector/skills.d.ts +1 -1
- package/dist/inspector/skills.js +3 -2
- package/dist/inspector/status-line.d.ts +14 -0
- package/dist/inspector/status-line.js +44 -0
- package/dist/inspector/trash.d.ts +37 -0
- package/dist/inspector/trash.js +148 -0
- package/dist/inspector/vibeables.d.ts +1 -1
- package/dist/inspector/vibeables.js +3 -2
- package/inspector/dist/assets/dist-B2o7tVdJ.js +23 -0
- package/inspector/dist/assets/{dist-Ceimhh7q.js → dist-BA7UaMHM.js} +1 -1
- package/inspector/dist/assets/dist-BXQtebmM.js +1 -0
- package/inspector/dist/assets/dist-BYDYk38f.js +1 -0
- package/inspector/dist/assets/{dist-CPlSMtdY.js → dist-BdZ5cNRp.js} +1 -1
- package/inspector/dist/assets/{dist-DGoW6poO.js → dist-BgakrfmB.js} +1 -1
- package/inspector/dist/assets/{dist-CprubYd5.js → dist-BpUghI2_.js} +1 -1
- package/inspector/dist/assets/{dist-BdimISeT.js → dist-BpYRdsio.js} +1 -1
- package/inspector/dist/assets/{dist-C9wdXHwY.js → dist-CHSqB1rM.js} +1 -1
- package/inspector/dist/assets/{dist-B1xH7Mxz.js → dist-CVpPKkOb.js} +1 -1
- package/inspector/dist/assets/{dist-H3rerbMg.js → dist-CYzOYk_e.js} +9 -9
- package/inspector/dist/assets/dist-CzkTIkYD.js +1 -0
- package/inspector/dist/assets/{dist-rE2oRtJp.js → dist-D7O3CloR.js} +1 -1
- package/inspector/dist/assets/dist-DJzkq2t-.js +1 -0
- package/inspector/dist/assets/dist-Dctj7Mr3.js +1 -0
- package/inspector/dist/assets/{dist-BA9oX2Qo.js → dist-DtEBn0Pj.js} +1 -1
- package/inspector/dist/assets/{dist-CmUexHfa.js → dist-DwpQHmP7.js} +1 -1
- package/inspector/dist/assets/{dist-CF7An11X.js → dist-KG4lrraG.js} +1 -1
- package/inspector/dist/assets/{dist-5wk4daHl.js → dist-Ko3fuaif.js} +1 -1
- package/inspector/dist/assets/{dist-ZMc9InB9.js → dist-f7v359XP.js} +1 -1
- package/inspector/dist/assets/{dist-yFKhRrHN.js → dist-itDgDZqI.js} +1 -1
- package/inspector/dist/assets/{dist-CXS6W3P9.js → dist-p_qW90fR.js} +1 -1
- package/inspector/dist/assets/{dist-DNBDudvB.js → dist-xi1nJU9x.js} +1 -1
- package/inspector/dist/assets/editor-BkGiMzB_.js +120 -0
- package/inspector/dist/assets/editor-CZMrhTTL.css +1 -0
- package/inspector/dist/assets/index-Bb27CwDb.css +2 -0
- package/inspector/dist/assets/index-D4h-VE-4.js +229 -0
- package/inspector/dist/assets/material-symbols-rounded-Dni8jOUD.woff2 +0 -0
- package/inspector/dist/index.html +2 -2
- package/package.json +1 -1
- package/inspector/dist/assets/dist-9zOw_mLO.js +0 -1
- package/inspector/dist/assets/dist-D3a-4Le2.js +0 -1
- package/inspector/dist/assets/dist-D8nmL5S9.js +0 -1
- package/inspector/dist/assets/dist-DN5yqrtf.js +0 -1
- package/inspector/dist/assets/dist-DaG9QYNU.js +0 -1
- package/inspector/dist/assets/dist-_9tJHb24.js +0 -23
- package/inspector/dist/assets/editor-B1Jq9YIq.css +0 -1
- package/inspector/dist/assets/editor-DtVtHids.js +0 -117
- package/inspector/dist/assets/index-B-sri0As.js +0 -225
- package/inspector/dist/assets/index-Da4dQf-y.css +0 -1
- package/inspector/dist/assets/material-symbols-rounded-DzDTtRam.woff2 +0 -0
package/README.md
CHANGED
|
@@ -158,6 +158,8 @@ kraftwerk knowledge # Knowledge: OKF bundles (list/get/put/v
|
|
|
158
158
|
kraftwerk ui # inspector web UI on http://localhost:1981; --port, --output
|
|
159
159
|
kraftwerk workspaces # every workspace on this machine; workspaces start|stop|forget <ref>
|
|
160
160
|
kraftwerk projects # goal-scoped project folders; projects create|show|link|log|remove
|
|
161
|
+
kraftwerk trash # what was deleted; trash restore|purge <id>, trash empty
|
|
162
|
+
kraftwerk journal <agent> ["<entry>"] # an agent's memory across sessions: print it, add a line (--kind)
|
|
161
163
|
kraftwerk doctor # preflight: harness CLIs, docker, workflows, declared env vars
|
|
162
164
|
kraftwerk validate # all discovered: schema + semantics + files, exit 1 on failure
|
|
163
165
|
kraftwerk validate src/workflows/pitch # specific paths
|
|
@@ -169,6 +171,15 @@ kraftwerk tunnel setup kw.example.com # Cloudflare Tunnel to the inspector: lo
|
|
|
169
171
|
kraftwerk tunnel # run that tunnel alone (the UI runs elsewhere)
|
|
170
172
|
```
|
|
171
173
|
|
|
174
|
+
Nothing is deleted at once. Removing an agent, a project, a knowledge bundle,
|
|
175
|
+
a vibeable, a channel, a repository, a chat or a run, in the inspector or the
|
|
176
|
+
CLI, moves its folder to `kraftwerk-data/trash/`. The trash mirrors the
|
|
177
|
+
workspace (`trash/agents/<slug>`, `trash/knowledge/<bundle>`,
|
|
178
|
+
`trash/projects/<slug>`, `trash/chats/<id>`, …) and ignores itself in git.
|
|
179
|
+
The **trash** screen (`#/trash`) or `kraftwerk trash restore <id>` puts an entry
|
|
180
|
+
back where it came from. Deleting it there, or `kraftwerk trash empty`, makes it
|
|
181
|
+
final. A chat's agent transcripts are only removed then, so a restored chat can resume.
|
|
182
|
+
|
|
172
183
|
The inspector binds `127.0.0.1` — it has no authentication of its own and
|
|
173
184
|
its chat runs coding agents against the repo, so it stays off the LAN, and
|
|
174
185
|
while bound there it only answers requests whose `Host` is a loopback name
|
|
@@ -510,6 +521,11 @@ directory stays the workspace root, so linked repositories and knowledge
|
|
|
510
521
|
are reachable by path; opening a vibeable moves it into the app folder as
|
|
511
522
|
in any chat.
|
|
512
523
|
|
|
524
|
+
**In the rail.** The agents linked to a project and its apps (the linked
|
|
525
|
+
vibeables) are listed under the project. Pin an app from the vibeables tab
|
|
526
|
+
of the project's context column, or unpin it there; clicking it in the rail
|
|
527
|
+
opens the project with the app beside its chat (`#/projects/<slug>?app=<vibeable>`).
|
|
528
|
+
|
|
513
529
|
**Coworkers.** "Add coworker" on a project chat works as on an agent
|
|
514
530
|
session: the chat becomes a channel whose members are the agents you pick,
|
|
515
531
|
with everything said so far and the project's brief, state, records and
|
|
@@ -530,6 +546,37 @@ kraftwerk projects remove relaunch-netnode-ch
|
|
|
530
546
|
The registry of workspaces on this machine, which answered to
|
|
531
547
|
`kraftwerk projects` until 0.48, is `kraftwerk workspaces` now.
|
|
532
548
|
|
|
549
|
+
## Files
|
|
550
|
+
|
|
551
|
+
Files are the material of the work, in any format: briefings, contracts,
|
|
552
|
+
designs, spreadsheets, deliverables. They are plain folders with two kinds
|
|
553
|
+
of owner, chosen by how long a file lives:
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
kraftwerk-data/files/ # workspace files: shared, outlive any project
|
|
557
|
+
kraftwerk-data/projects/<slug>/files/ # a project's files: archived and trashed with the project
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Agents own no files. They work in the project's files when they work on
|
|
561
|
+
a project, and may link workspace folders in `agent.yml` (`files: [contracts]`)
|
|
562
|
+
the way they link knowledge. Every session's context names the folders, the
|
|
563
|
+
file count and the latest changes, not every file, and tells the agent to put
|
|
564
|
+
what it produces in `files/deliverables/` (or `files/inbox/` without a
|
|
565
|
+
project). Because they are plain folders, agents read and write them like any
|
|
566
|
+
directory, and you can drop files in with Finder.
|
|
567
|
+
|
|
568
|
+
The **files** page (`#/files`) has the workspace and every project on the
|
|
569
|
+
left, the folder in the middle, and a preview on the right for images, PDFs,
|
|
570
|
+
video, audio, markdown, CSV and text. Drop files anywhere on the list to
|
|
571
|
+
upload. A project's chat has the same browser in its context column. Files
|
|
572
|
+
are versioned in the workspace git; a file over 25 MB stays local, listed in
|
|
573
|
+
the files root's own `.gitignore` and marked "local only". Deleting goes to
|
|
574
|
+
the trash (`trash/files/…`, mirroring the folders).
|
|
575
|
+
|
|
576
|
+
Files are not knowledge. A knowledge bundle holds curated, verified concepts,
|
|
577
|
+
and a file becomes part of one as evidence, in the bundle's `references/`
|
|
578
|
+
folder, which concepts cite in `sources` (OKF §5.1, §6.3).
|
|
579
|
+
|
|
533
580
|
## Persistent agents
|
|
534
581
|
|
|
535
582
|
The inspector's "agents" screen turns chat agents into persistent teammates. An
|
|
@@ -539,6 +586,7 @@ agent is one folder under the project's `agents/` root:
|
|
|
539
586
|
agents/max/
|
|
540
587
|
agent.yml # name, emoji, description, harness, model, effort, workflows, knowledge, vibeables
|
|
541
588
|
system.md # the agent's system prompt (its role)
|
|
589
|
+
journal.md # the agent's own memory across sessions (written through `kraftwerk journal`)
|
|
542
590
|
```
|
|
543
591
|
|
|
544
592
|
```yaml
|
|
@@ -564,6 +612,40 @@ and write knowledge through `kraftwerk knowledge`, with writes stamped with
|
|
|
564
612
|
the agent's own actor, `<slug>/<harness>`. So the agent triggers its own
|
|
565
613
|
workflows when a request matches, and keeps its bundles current.
|
|
566
614
|
|
|
615
|
+
Every agent keeps a journal, `agents/<slug>/journal.md`: what it learned,
|
|
616
|
+
decided, promised and finished, one line per entry under a dated heading,
|
|
617
|
+
newest first. The recent days (about 4,000 characters) are part of every
|
|
618
|
+
new session's context, together with how to add to it:
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
kraftwerk journal max # print it
|
|
622
|
+
kraftwerk journal max "send the summary on Friday" --kind promised # learned | decided | promised | done | note
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
The agent's own lines are not signed; a line from anyone else is stamped
|
|
626
|
+
with its actor. In the inspector, the agent's context column has a
|
|
627
|
+
**journal** tab, where you can also add a note for the agent to remember
|
|
628
|
+
(`GET`/`POST /api/agents/:slug/journal`). That is what makes an agent the
|
|
629
|
+
same colleague next week, whichever chat you open.
|
|
630
|
+
|
|
631
|
+
**What needs you.** Everything that blocks an agent, an approval or a
|
|
632
|
+
question it asked, plus failures nobody has opened yet, is one list
|
|
633
|
+
(`GET /api/attention`), oldest first, each item with its owner: the agent,
|
|
634
|
+
the project or the channel. The rail puts a red count on every row that
|
|
635
|
+
leads there: the agent, the project (its own chats and its agents'), the
|
|
636
|
+
channel, Ralv, and the projects header while folded. The bell lists these
|
|
637
|
+
items under "needs you", grouped by owner, with finished runs and routines
|
|
638
|
+
below as "updates". **next** in the top bar (⌥N) opens the next waiting
|
|
639
|
+
item where it lives: the chat, scrolled to the card, which lights up, with
|
|
640
|
+
the project's or agent's context beside it. Answer, press next again.
|
|
641
|
+
Notification titles name the owner first ("🦊 Lisa needs approval").
|
|
642
|
+
|
|
643
|
+
Under each agent's name the rail shows what it is doing now: waiting for
|
|
644
|
+
you (an approval or a question, with a red light), working (in which chat,
|
|
645
|
+
for how long, with a pulsing light), the next routine due within a day and
|
|
646
|
+
a half, or when it was last active (`GET /api/agent-status`). Agents linked
|
|
647
|
+
to a project are listed under the project too.
|
|
648
|
+
|
|
567
649
|
A chat keeps the agent's own session. The ACP session id is stored with the
|
|
568
650
|
chat (`sessions` in `meta.json`), and the next process — after an inspector
|
|
569
651
|
restart, or once the idle reaper released the agent — resumes it over
|
|
@@ -593,8 +675,8 @@ and cost plus the model and thinking settings it lets you change live
|
|
|
593
675
|
(`POST /api/chats/:id/config`). The agent's slash commands join the
|
|
594
676
|
skills in the `/` menu. Each turn ends with the files the agent says it
|
|
595
677
|
changed. Background tasks that can be stopped have a stop button
|
|
596
|
-
(`POST /api/chats/:id/task-stop`). Deleting a chat
|
|
597
|
-
sessions
|
|
678
|
+
(`POST /api/chats/:id/task-stop`). Deleting a chat moves it to the trash; the
|
|
679
|
+
agent's own sessions go when it is deleted from there, and "continue a session" on the new-chat screen lists
|
|
598
680
|
the agent's sessions in the project (`GET /api/agent-sessions`) to pick
|
|
599
681
|
one up as a chat.
|
|
600
682
|
|
|
@@ -821,6 +903,7 @@ kraftwerk knowledge search "refund" # full-text across bundl
|
|
|
821
903
|
kraftwerk knowledge verify customer-support/playbooks/refunds --by human:user
|
|
822
904
|
kraftwerk knowledge validate # OKF conformance + warnings
|
|
823
905
|
kraftwerk knowledge fsck --fix # heal out-of-band edits (reindex)
|
|
906
|
+
kraftwerk knowledge remove customer-support # move the bundle to the trash
|
|
824
907
|
```
|
|
825
908
|
|
|
826
909
|
Actors follow the OKF convention: `<producer>/<version>` for agents,
|
package/dist/acp.js
CHANGED
|
@@ -157,7 +157,8 @@ export function divertExtensionUpdates(source, onExtension) {
|
|
|
157
157
|
* representation of that work.
|
|
158
158
|
*/
|
|
159
159
|
export async function connectAcp(agent, opts) {
|
|
160
|
-
|
|
160
|
+
// KRAFTWERK_ACP_ADAPTER: a script standing in for the adapter (the tests' scripted fake agent).
|
|
161
|
+
const entry = process.env.KRAFTWERK_ACP_ADAPTER || fileURLToPath(import.meta.resolve(ADAPTERS[agent]));
|
|
161
162
|
const child = spawn(process.execPath, [entry], {
|
|
162
163
|
cwd: opts.cwd,
|
|
163
164
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `kraftwerk journal` — an agent's own memory across sessions
|
|
4
|
+
* (agents/<slug>/journal.md). Agents write it from their sessions; every
|
|
5
|
+
* new session starts with the recent part of it:
|
|
6
|
+
*
|
|
7
|
+
* kraftwerk journal <agent> print the journal
|
|
8
|
+
* kraftwerk journal <agent> "<entry>" [--kind ..] add a line under today (learned|decided|promised|done|note)
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerJournalCommands(program: Command): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { appendJournal, JOURNAL_KINDS, readJournal } from "../inspector/journal.js";
|
|
3
|
+
import { initContext as prepare } from "./routines.js";
|
|
4
|
+
/**
|
|
5
|
+
* `kraftwerk journal` — an agent's own memory across sessions
|
|
6
|
+
* (agents/<slug>/journal.md). Agents write it from their sessions; every
|
|
7
|
+
* new session starts with the recent part of it:
|
|
8
|
+
*
|
|
9
|
+
* kraftwerk journal <agent> print the journal
|
|
10
|
+
* kraftwerk journal <agent> "<entry>" [--kind ..] add a line under today (learned|decided|promised|done|note)
|
|
11
|
+
*/
|
|
12
|
+
export function registerJournalCommands(program) {
|
|
13
|
+
program
|
|
14
|
+
.command("journal")
|
|
15
|
+
.description("An agent's journal: print it, or add one line (what it learned, decided, promised, did)")
|
|
16
|
+
.argument("<agent>", "Agent slug (folder under agents/)")
|
|
17
|
+
.argument("[entry]", "One line to add under today's date")
|
|
18
|
+
.option("--kind <kind>", `Kind of entry: ${JOURNAL_KINDS.join(" | ")}`, "note")
|
|
19
|
+
.option("--actor <actor>", "Who writes (default: the agent itself; KRAFTWERK_ACTOR)")
|
|
20
|
+
.action(async (agent, entry, opts) => {
|
|
21
|
+
await prepare();
|
|
22
|
+
try {
|
|
23
|
+
if (!entry) {
|
|
24
|
+
const text = await readJournal(agent);
|
|
25
|
+
console.log(text.trim() || chalk.dim(`${agent}'s journal is empty.`));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
await appendJournal(agent, entry, { kind: opts.kind, actor: opts.actor ?? process.env.KRAFTWERK_ACTOR });
|
|
29
|
+
console.log(`${chalk.green("✔")} added to ${chalk.cyan(agent)}'s journal`);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.error(chalk.red(err.message));
|
|
33
|
+
process.exit(2);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
package/dist/cli/knowledge.js
CHANGED
|
@@ -3,6 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { resolveProject } from "../config.js";
|
|
5
5
|
import { fsck, getConcept, initBundle, knowledgeRoot, listBundles, listConcepts, searchKnowledge, validateBundle, verifyConcept, writeConcept, } from "../okf.js";
|
|
6
|
+
import { deleteBundle } from "../inspector/knowledge.js";
|
|
7
|
+
import { initContext as prepare } from "./routines.js";
|
|
6
8
|
/**
|
|
7
9
|
* `kraftwerk knowledge` — the CLI surface over OKF bundles (the
|
|
8
10
|
* inspector's Knowledge screen). This is the enforced write path: `put` stamps provenance
|
|
@@ -182,6 +184,21 @@ export function registerKnowledgeCommands(program) {
|
|
|
182
184
|
process.exit(2);
|
|
183
185
|
}
|
|
184
186
|
});
|
|
187
|
+
knowledge
|
|
188
|
+
.command("remove")
|
|
189
|
+
.description("Move a bundle to the trash (`kraftwerk trash` puts it back)")
|
|
190
|
+
.argument("<bundle>", "Bundle name")
|
|
191
|
+
.action(async (bundle) => {
|
|
192
|
+
try {
|
|
193
|
+
await prepare();
|
|
194
|
+
await deleteBundle(bundle);
|
|
195
|
+
console.log(`${chalk.green("✔")} moved ${chalk.cyan(bundle)} to the trash`);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
console.error(chalk.red(err.message));
|
|
199
|
+
process.exit(2);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
185
202
|
knowledge
|
|
186
203
|
.command("validate")
|
|
187
204
|
.description("OKF conformance check (errors) + provenance/staleness warnings")
|
package/dist/cli/kraftwerk.js
CHANGED
|
@@ -16,6 +16,8 @@ import { registerProjectCommands } from "./projects.js";
|
|
|
16
16
|
import { registerWorkspaceCommands } from "./workspaces.js";
|
|
17
17
|
import { registerRepoCommands } from "./repos.js";
|
|
18
18
|
import { registerVibeableCommands } from "./vibeables.js";
|
|
19
|
+
import { registerTrashCommands } from "./trash.js";
|
|
20
|
+
import { registerJournalCommands } from "./journal.js";
|
|
19
21
|
import { registerTunnelCommands } from "./tunnel.js";
|
|
20
22
|
import { applyDotenv } from "../dotenv.js";
|
|
21
23
|
import { resolveProject } from "../config.js";
|
|
@@ -36,6 +38,8 @@ import { runUi } from "./ui.js";
|
|
|
36
38
|
* kraftwerk projects ... goal-scoped project folders (list/create/show/link/log/remove)
|
|
37
39
|
* kraftwerk vibeables ... small apps built live in a chat (list/create/remove)
|
|
38
40
|
* kraftwerk repos ... repositories the agents work on (list/add/update/remove)
|
|
41
|
+
* kraftwerk journal <agent> [entry] an agent's memory across sessions: print it, add a line
|
|
42
|
+
* kraftwerk trash ... what was deleted: list, restore, purge, empty
|
|
39
43
|
* kraftwerk tunnel [setup <host>] Cloudflare Tunnel to the inspector: run it alone, or set one up
|
|
40
44
|
* kraftwerk doctor preflight: harness CLIs, docker, workflows, env
|
|
41
45
|
* kraftwerk validate [paths...] validate without executing
|
|
@@ -286,6 +290,8 @@ registerWorkspaceCommands(program);
|
|
|
286
290
|
registerProjectCommands(program);
|
|
287
291
|
registerRepoCommands(program);
|
|
288
292
|
registerVibeableCommands(program);
|
|
293
|
+
registerTrashCommands(program);
|
|
294
|
+
registerJournalCommands(program);
|
|
289
295
|
registerTunnelCommands(program);
|
|
290
296
|
program
|
|
291
297
|
.command("ui")
|
package/dist/cli/projects.js
CHANGED
|
@@ -19,7 +19,7 @@ import { fmtAgo } from "./workspaces.js";
|
|
|
19
19
|
* kraftwerk projects link <slug> <kind> <name> add a knowledge|vibeables|repos|workflows|agents link
|
|
20
20
|
* kraftwerk projects unlink <slug> <kind> <name> remove one
|
|
21
21
|
* kraftwerk projects log <slug> "<entry>" append a stamped line to log.md (--actor)
|
|
22
|
-
* kraftwerk projects remove <slug>
|
|
22
|
+
* kraftwerk projects remove <slug> move the folder to the trash
|
|
23
23
|
*
|
|
24
24
|
* The workspace registry answered to this name until 0.48; it is
|
|
25
25
|
* `kraftwerk workspaces` now (workspaces.ts).
|
|
@@ -208,13 +208,13 @@ export function registerProjectCommands(program) {
|
|
|
208
208
|
});
|
|
209
209
|
projects
|
|
210
210
|
.command("remove")
|
|
211
|
-
.description("
|
|
211
|
+
.description("Move a project folder to the trash (`kraftwerk trash` puts it back)")
|
|
212
212
|
.argument("<slug>", "Folder name under the root")
|
|
213
213
|
.action(async (slug) => {
|
|
214
214
|
await prepare();
|
|
215
215
|
try {
|
|
216
216
|
await deleteProject(slug);
|
|
217
|
-
console.log(`${chalk.green("✔")}
|
|
217
|
+
console.log(`${chalk.green("✔")} moved ${chalk.cyan(slug)} to the trash`);
|
|
218
218
|
}
|
|
219
219
|
catch (err) {
|
|
220
220
|
die(err.message);
|
package/dist/cli/repos.js
CHANGED
|
@@ -110,6 +110,6 @@ export function registerRepoCommands(program) {
|
|
|
110
110
|
const r = await removeRepo(name, !!opts.force).catch((err) => ({ ok: false, error: err.message }));
|
|
111
111
|
if (!r.ok)
|
|
112
112
|
die(r.error ?? "remove failed");
|
|
113
|
-
console.log(`${chalk.green("✔")}
|
|
113
|
+
console.log(`${chalk.green("✔")} moved ${chalk.cyan(name)} to the trash`);
|
|
114
114
|
});
|
|
115
115
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import Table from "cli-table3";
|
|
3
|
+
import { emptyTrash, listTrash, purgeFromTrash, restoreFromTrash, trashRoot } from "../inspector/trash.js";
|
|
4
|
+
import { fmtAgo } from "./workspaces.js";
|
|
5
|
+
import { initContext as prepare } from "./routines.js";
|
|
6
|
+
/**
|
|
7
|
+
* `kraftwerk trash` — what was deleted (agents, projects, knowledge bundles,
|
|
8
|
+
* vibeables, channels, repos, chats, runs), kept under kraftwerk-data/trash
|
|
9
|
+
* until it is deleted from there:
|
|
10
|
+
*
|
|
11
|
+
* kraftwerk trash list: id, what it was, when
|
|
12
|
+
* kraftwerk trash restore <id> put an entry back where it came from
|
|
13
|
+
* kraftwerk trash purge <id> delete one entry for good
|
|
14
|
+
* kraftwerk trash empty delete everything in the trash for good
|
|
15
|
+
*/
|
|
16
|
+
const die = (msg) => {
|
|
17
|
+
console.error(chalk.red(msg));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
};
|
|
20
|
+
export function registerTrashCommands(program) {
|
|
21
|
+
const trash = program.command("trash").description("What was deleted: list, restore, purge one, empty");
|
|
22
|
+
trash
|
|
23
|
+
.command("list", { isDefault: true })
|
|
24
|
+
.description("List the trash, newest first")
|
|
25
|
+
.option("--json", "Machine-readable output")
|
|
26
|
+
.action(async (opts) => {
|
|
27
|
+
await prepare();
|
|
28
|
+
const entries = await listTrash();
|
|
29
|
+
if (opts.json) {
|
|
30
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (entries.length === 0) {
|
|
34
|
+
console.log(chalk.dim("The trash is empty."));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const table = new Table({ head: ["id", "was", "deleted"].map((h) => chalk.bold(h)), wordWrap: true });
|
|
38
|
+
for (const e of entries)
|
|
39
|
+
table.push([chalk.cyan(e.id), chalk.dim(e.from), chalk.dim(fmtAgo(e.trashedAt))]);
|
|
40
|
+
console.log(table.toString());
|
|
41
|
+
console.log(chalk.dim(await trashRoot()));
|
|
42
|
+
});
|
|
43
|
+
for (const [verb, fn, done] of [
|
|
44
|
+
["restore", restoreFromTrash, "restored"],
|
|
45
|
+
["purge", purgeFromTrash, "deleted for good:"],
|
|
46
|
+
]) {
|
|
47
|
+
trash
|
|
48
|
+
.command(verb)
|
|
49
|
+
.description(verb === "restore" ? "Put an entry back where it came from" : "Delete one entry for good")
|
|
50
|
+
.argument("<id>", "Entry id as `kraftwerk trash` lists it, e.g. knowledge/customer-support")
|
|
51
|
+
.action(async (id) => {
|
|
52
|
+
await prepare();
|
|
53
|
+
try {
|
|
54
|
+
const e = await fn(id);
|
|
55
|
+
console.log(`${chalk.green("✔")} ${done} ${chalk.cyan(e.from)}`);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
die(err.message);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
trash
|
|
63
|
+
.command("empty")
|
|
64
|
+
.description("Delete everything in the trash for good")
|
|
65
|
+
.action(async () => {
|
|
66
|
+
await prepare();
|
|
67
|
+
const n = await emptyTrash();
|
|
68
|
+
console.log(`${chalk.green("✔")} deleted ${n} entr${n === 1 ? "y" : "ies"} for good`);
|
|
69
|
+
});
|
|
70
|
+
}
|
package/dist/cli/vibeables.js
CHANGED
|
@@ -11,7 +11,7 @@ import { initContext as prepare } from "./routines.js";
|
|
|
11
11
|
*
|
|
12
12
|
* kraftwerk vibeables list: name, mode, state
|
|
13
13
|
* kraftwerk vibeables create <name> new app from the starter (index.html, vibeable.yml)
|
|
14
|
-
* kraftwerk vibeables remove <name>
|
|
14
|
+
* kraftwerk vibeables remove <name> move the folder to the trash
|
|
15
15
|
*/
|
|
16
16
|
const die = (msg) => {
|
|
17
17
|
console.error(chalk.red(msg));
|
|
@@ -74,13 +74,13 @@ export function registerVibeableCommands(program) {
|
|
|
74
74
|
});
|
|
75
75
|
vibeables
|
|
76
76
|
.command("remove")
|
|
77
|
-
.description("
|
|
77
|
+
.description("Move an app folder to the trash (`kraftwerk trash` puts it back)")
|
|
78
78
|
.argument("<name>", "Folder name under the root")
|
|
79
79
|
.action(async (name) => {
|
|
80
80
|
await prepare();
|
|
81
81
|
try {
|
|
82
82
|
await deleteVibeable(name);
|
|
83
|
-
console.log(`${chalk.green("✔")}
|
|
83
|
+
console.log(`${chalk.green("✔")} moved ${chalk.cyan(name)} to the trash`);
|
|
84
84
|
}
|
|
85
85
|
catch (err) {
|
|
86
86
|
die(err.message);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type AgentActivity } from "./chat/sessions.js";
|
|
2
|
+
/**
|
|
3
|
+
* What each agent is up to, for the status line under its name: waiting on
|
|
4
|
+
* a human, working (where, since when), the next scheduled routine, and
|
|
5
|
+
* when it was last active. The UI picks the one line that matters most.
|
|
6
|
+
*/
|
|
7
|
+
export interface AgentStatus {
|
|
8
|
+
/** Chats where it waits for an approval or an answer. */
|
|
9
|
+
waiting: AgentActivity[];
|
|
10
|
+
/** Chats where it is mid-turn. */
|
|
11
|
+
working: AgentActivity[];
|
|
12
|
+
/** The next enabled routine due. */
|
|
13
|
+
nextRoutine?: {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
at: string;
|
|
17
|
+
};
|
|
18
|
+
/** Newest change in any of its sessions or channels. */
|
|
19
|
+
lastActiveAt?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function agentStatuses(): Promise<Record<string, AgentStatus>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { listAgents } from "./agents.js";
|
|
2
|
+
import { agentActivity, listChats } from "./chat/sessions.js";
|
|
3
|
+
import { routineStatuses } from "./routines.js";
|
|
4
|
+
export async function agentStatuses() {
|
|
5
|
+
const [agents, chats] = await Promise.all([listAgents(), listChats()]);
|
|
6
|
+
const live = agentActivity();
|
|
7
|
+
const out = {};
|
|
8
|
+
await Promise.all(agents.map(async (a) => {
|
|
9
|
+
const act = live.get(a.slug);
|
|
10
|
+
const routines = await routineStatuses(a.slug).catch(() => []);
|
|
11
|
+
const next = routines
|
|
12
|
+
.filter((r) => r.nextRunAt)
|
|
13
|
+
.sort((x, y) => x.nextRunAt.localeCompare(y.nextRunAt))[0];
|
|
14
|
+
// A channel chat counts once the agent has a session there (sessions are keyed by agent slug).
|
|
15
|
+
const mine = chats.filter((c) => (c.scope.kind === "agent" && c.scope.slug === a.slug) || (c.scope.kind === "channel" && c.sessions?.[a.slug]));
|
|
16
|
+
const last = mine.reduce((m, c) => (!m || c.updatedAt > m ? c.updatedAt : m), undefined);
|
|
17
|
+
out[a.slug] = {
|
|
18
|
+
waiting: act?.waiting ?? [],
|
|
19
|
+
working: act?.working ?? [],
|
|
20
|
+
...(next ? { nextRoutine: { id: next.id, name: next.name, at: next.nextRunAt } } : {}),
|
|
21
|
+
...(last ? { lastActiveAt: last } : {}),
|
|
22
|
+
};
|
|
23
|
+
}));
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
@@ -27,6 +27,8 @@ export interface Agent {
|
|
|
27
27
|
knowledge: string[];
|
|
28
28
|
/** Vibeables (folder names under the vibeables root) this member builds and maintains. */
|
|
29
29
|
vibeables: string[];
|
|
30
|
+
/** Workspace file folders (paths under kraftwerk-data/files) this agent works with; agents own no files. */
|
|
31
|
+
files?: string[];
|
|
30
32
|
/**
|
|
31
33
|
* Skill allowlist (names from .claude/skills, project or user level).
|
|
32
34
|
* Absent = all discovered skills; empty list = no skills.
|
|
@@ -67,6 +69,8 @@ export interface SaveAgentInput {
|
|
|
67
69
|
workflows?: string[];
|
|
68
70
|
knowledge?: string[];
|
|
69
71
|
vibeables?: string[];
|
|
72
|
+
/** Linked workspace file folders; omitted = keep what agent.yml has. */
|
|
73
|
+
files?: string[];
|
|
70
74
|
/** Omit for "all skills"; a list (possibly empty) restricts to those names. */
|
|
71
75
|
skills?: string[];
|
|
72
76
|
system?: string;
|
|
@@ -74,4 +78,5 @@ export interface SaveAgentInput {
|
|
|
74
78
|
export declare function saveAgent(input: SaveAgentInput): Promise<AgentDetail>;
|
|
75
79
|
/** Archive/unarchive a member: toggles `archived:` in agent.yml, nothing else. */
|
|
76
80
|
export declare function setAgentArchived(slug: string, archived: boolean): Promise<AgentDetail>;
|
|
81
|
+
/** Move the agent's folder to the trash. */
|
|
77
82
|
export declare function deleteAgent(slug: string): Promise<void>;
|
package/dist/inspector/agents.js
CHANGED
|
@@ -4,6 +4,7 @@ import { parse, stringify } from "yaml";
|
|
|
4
4
|
import { resolveProject } from "../config.js";
|
|
5
5
|
import { getProjectRoot } from "./context.js";
|
|
6
6
|
import { syncWorkspaceAgents } from "./instances.js";
|
|
7
|
+
import { moveToTrash } from "./trash.js";
|
|
7
8
|
/**
|
|
8
9
|
* Agents: persistent agents, defined on the filesystem. Each one
|
|
9
10
|
* is one folder under the project's agents/ root:
|
|
@@ -60,6 +61,7 @@ function normalize(slug, raw) {
|
|
|
60
61
|
workflows: Array.isArray(raw.workflows) ? raw.workflows.map(String) : [],
|
|
61
62
|
knowledge: Array.isArray(raw.knowledge) ? raw.knowledge.map(String) : [],
|
|
62
63
|
vibeables: Array.isArray(raw.vibeables) ? raw.vibeables.map(String) : [],
|
|
64
|
+
...(Array.isArray(raw.files) && raw.files.length ? { files: raw.files.map(String) } : {}),
|
|
63
65
|
...(Array.isArray(raw.skills) ? { skills: raw.skills.map(String) } : {}),
|
|
64
66
|
...(raw.archived === true ? { archived: true } : {}),
|
|
65
67
|
};
|
|
@@ -135,6 +137,12 @@ export async function saveAgent(input) {
|
|
|
135
137
|
knowledge: (input.knowledge ?? []).map(String),
|
|
136
138
|
// Empty list left out: most agents build nothing, and agent.yml stays as short as before.
|
|
137
139
|
...(input.vibeables?.length ? { vibeables: [...new Set(input.vibeables.map((v) => String(v).trim()).filter(Boolean))] } : {}),
|
|
140
|
+
...(() => {
|
|
141
|
+
// Linked folders survive an edit from a form that does not know them.
|
|
142
|
+
const files = input.files ?? (Array.isArray(existing?.files) ? existing.files.map(String) : []);
|
|
143
|
+
const clean = [...new Set(files.map((f) => String(f).trim().replace(/^\/+|\/+$/g, "")).filter(Boolean))];
|
|
144
|
+
return clean.length ? { files: clean } : {};
|
|
145
|
+
})(),
|
|
138
146
|
...(input.skills ? { skills: input.skills.map(String) } : {}),
|
|
139
147
|
...(existing?.archived === true ? { archived: true } : {}),
|
|
140
148
|
};
|
|
@@ -160,7 +168,8 @@ export async function setAgentArchived(slug, archived) {
|
|
|
160
168
|
await fs.writeFile(path.join(dir, "agent.yml"), stringify(yml));
|
|
161
169
|
return (await getAgent(slug));
|
|
162
170
|
}
|
|
171
|
+
/** Move the agent's folder to the trash. */
|
|
163
172
|
export async function deleteAgent(slug) {
|
|
164
173
|
const dir = path.join(await agentsRoot(), safeAgentSlug(slug));
|
|
165
|
-
await
|
|
174
|
+
await moveToTrash("agents", slug, dir);
|
|
166
175
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What needs you: every place where work is stuck on a human, with whose it
|
|
3
|
+
* is. Two sources: requests waiting in a live chat (an approval, a question
|
|
4
|
+
* — the agent cannot go on until someone answers) and failures nobody has
|
|
5
|
+
* looked at yet (a routine or a workflow run that ended badly; unread
|
|
6
|
+
* failure notifications). Oldest first: what waits longest comes first.
|
|
7
|
+
*
|
|
8
|
+
* The inspector builds everything urgent from this one list: the counts on
|
|
9
|
+
* the rail, the "needs you" part of the bell, the tab title and "next",
|
|
10
|
+
* which walks through it and lands on each item with its context.
|
|
11
|
+
*/
|
|
12
|
+
/** Who an item belongs to — what the rail lights up and the bell groups by. */
|
|
13
|
+
export interface AttentionOwner {
|
|
14
|
+
project?: {
|
|
15
|
+
slug: string;
|
|
16
|
+
title: string;
|
|
17
|
+
};
|
|
18
|
+
agent?: {
|
|
19
|
+
slug: string;
|
|
20
|
+
name: string;
|
|
21
|
+
emoji?: string;
|
|
22
|
+
};
|
|
23
|
+
channel?: string;
|
|
24
|
+
/** A chat with Ralv, the workspace's general assistant. */
|
|
25
|
+
general?: boolean;
|
|
26
|
+
/** A workflow run. */
|
|
27
|
+
workflow?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface AttentionItem {
|
|
30
|
+
/** The request id (live requests) or the notification id (failures). */
|
|
31
|
+
id: string;
|
|
32
|
+
kind: "approval" | "question" | "failed";
|
|
33
|
+
/** What is asked, or what went wrong. */
|
|
34
|
+
title: string;
|
|
35
|
+
/** The session it sits in. */
|
|
36
|
+
chatId?: string;
|
|
37
|
+
chatTitle?: string;
|
|
38
|
+
/** Where to go: a chat route with ?focus=<request id>, or the failed run or routine. */
|
|
39
|
+
href: string;
|
|
40
|
+
since: string;
|
|
41
|
+
owner: AttentionOwner;
|
|
42
|
+
/** Failures: the notification to mark read once the item was opened. */
|
|
43
|
+
notificationId?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface AttentionView {
|
|
46
|
+
items: AttentionItem[];
|
|
47
|
+
}
|
|
48
|
+
export declare function listAttention(): Promise<AttentionView>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { listAgents } from "./agents.js";
|
|
2
|
+
import { waitingRequests } from "./chat/sessions.js";
|
|
3
|
+
import { chatHref, listNotifications } from "./notifications.js";
|
|
4
|
+
import { listProjects } from "./projects.js";
|
|
5
|
+
export async function listAttention() {
|
|
6
|
+
const [agents, projects, notes] = await Promise.all([
|
|
7
|
+
listAgents().catch(() => []),
|
|
8
|
+
listProjects().catch(() => ({ projects: [] })),
|
|
9
|
+
listNotifications().catch(() => ({ items: [] })),
|
|
10
|
+
]);
|
|
11
|
+
const agentOf = (slug) => {
|
|
12
|
+
const a = agents.find((x) => x.slug === slug);
|
|
13
|
+
return { slug, name: a?.name ?? slug, ...(a?.emoji ? { emoji: a.emoji } : {}) };
|
|
14
|
+
};
|
|
15
|
+
const projectOf = (slug) => ({
|
|
16
|
+
slug,
|
|
17
|
+
title: projects.projects.find((p) => p.slug === slug)?.title ?? slug,
|
|
18
|
+
});
|
|
19
|
+
const waiting = waitingRequests().map((w) => {
|
|
20
|
+
const { scope } = w.meta;
|
|
21
|
+
const owner = {};
|
|
22
|
+
if (scope.kind === "agent")
|
|
23
|
+
owner.agent = agentOf(scope.slug);
|
|
24
|
+
if (scope.kind === "project")
|
|
25
|
+
owner.project = projectOf(scope.slug);
|
|
26
|
+
if (scope.kind === "channel") {
|
|
27
|
+
owner.channel = scope.slug;
|
|
28
|
+
owner.agent = agentOf(w.seat);
|
|
29
|
+
if (w.meta.project)
|
|
30
|
+
owner.project = projectOf(w.meta.project);
|
|
31
|
+
}
|
|
32
|
+
if (scope.kind === "general" || scope.kind === "kraftwerk" || scope.kind === "knowledge")
|
|
33
|
+
owner.general = true;
|
|
34
|
+
return {
|
|
35
|
+
id: w.requestId,
|
|
36
|
+
kind: w.kind,
|
|
37
|
+
title: w.title,
|
|
38
|
+
chatId: w.meta.id,
|
|
39
|
+
...(w.meta.title ? { chatTitle: w.meta.title } : {}),
|
|
40
|
+
href: `${chatHref(w.meta)}?focus=${encodeURIComponent(w.requestId)}`,
|
|
41
|
+
since: w.since,
|
|
42
|
+
owner,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
const failed = notes.items
|
|
46
|
+
.filter((n) => !n.readAt && (n.kind === "routine_failed" || n.kind === "run_failed"))
|
|
47
|
+
.map((n) => {
|
|
48
|
+
const owner = {};
|
|
49
|
+
if (n.diagnose?.kind === "routine")
|
|
50
|
+
owner.agent = agentOf(n.diagnose.agent);
|
|
51
|
+
if (n.diagnose?.kind === "run")
|
|
52
|
+
owner.workflow = n.title.replace(/ run .*$/, "");
|
|
53
|
+
return {
|
|
54
|
+
id: n.id,
|
|
55
|
+
kind: "failed",
|
|
56
|
+
title: n.body ? `${n.title}: ${n.body.split("\n")[0]}` : n.title,
|
|
57
|
+
...(n.diagnose?.kind === "routine" && n.diagnose.chatId ? { chatId: n.diagnose.chatId } : {}),
|
|
58
|
+
href: n.href,
|
|
59
|
+
since: n.at,
|
|
60
|
+
owner,
|
|
61
|
+
notificationId: n.id,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
const items = [...waiting, ...failed].sort((a, b) => a.since.localeCompare(b.since));
|
|
65
|
+
return { items };
|
|
66
|
+
}
|
|
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse, stringify } from "yaml";
|
|
4
4
|
import { getProjectRoot } from "./context.js";
|
|
5
|
+
import { moveToTrash } from "./trash.js";
|
|
5
6
|
import { resolveProject } from "../config.js";
|
|
6
7
|
import { listAgents, safeAgentSlug, slugFromName } from "./agents.js";
|
|
7
8
|
import { syncWorkspaceChannels } from "./instances.js";
|
|
@@ -130,7 +131,7 @@ export async function saveChannel(input) {
|
|
|
130
131
|
return channel;
|
|
131
132
|
}
|
|
132
133
|
export async function deleteChannel(slug) {
|
|
133
|
-
await
|
|
134
|
+
await moveToTrash("channels", slug, path.join(await channelsRoot(), safeChannelSlug(slug)));
|
|
134
135
|
}
|
|
135
136
|
/**
|
|
136
137
|
* Who a channel message wakes. @mentions of members win (in text order, each
|