@sztlink/pi-ensemble 0.1.0-alpha.12
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/CHANGELOG.md +103 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/SECURITY.md +27 -0
- package/bin/ensemble.mjs +217 -0
- package/docs/ADAPTERS.md +159 -0
- package/docs/AUDIT.md +20 -0
- package/docs/CLAUDE_AGENT_TEAMS.md +149 -0
- package/docs/LANDSCAPE.md +452 -0
- package/docs/QUICKSTART.md +149 -0
- package/docs/ROADMAP.md +313 -0
- package/docs/RUNTIME_RECIPES.md +111 -0
- package/docs/SPEC.md +132 -0
- package/examples/claude-agent-teams-lead.md +38 -0
- package/examples/ensemble-tmux +136 -0
- package/extensions/pi-ensemble.ts +212 -0
- package/lib/core.mjs +517 -0
- package/package.json +54 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Claude Agent Teams lead prompt for pi-ensemble
|
|
2
|
+
|
|
3
|
+
Use this as a starting prompt for a Claude Code lead session that will coordinate native Agent Teams while mirroring durable milestones into `pi-ensemble`.
|
|
4
|
+
|
|
5
|
+
```txt
|
|
6
|
+
You are the Claude Code lead for a pi-ensemble coordinated task.
|
|
7
|
+
|
|
8
|
+
Project root: <PROJECT_ROOT>
|
|
9
|
+
Your ensemble agent name: claude-lead
|
|
10
|
+
Sender to report back to: <pi|human|other-agent>
|
|
11
|
+
|
|
12
|
+
Protocol:
|
|
13
|
+
1. `cd <PROJECT_ROOT>`
|
|
14
|
+
2. Run `ensemble status`.
|
|
15
|
+
3. Read new inbox items: `ensemble inbox --agent claude-lead --since-last-read`.
|
|
16
|
+
4. Ack the handoff id: `ensemble ack msg_xxx --from claude-lead --body "<task summary>"`.
|
|
17
|
+
5. Claim paths before any edits: `ensemble claim <path> --agent claude-lead`.
|
|
18
|
+
6. If useful, create a Claude Code Agent Team internally. Use teammates only for independent work.
|
|
19
|
+
7. Mirror only durable milestones into pi-ensemble:
|
|
20
|
+
- accepted task frame;
|
|
21
|
+
- claims/releases;
|
|
22
|
+
- durable findings;
|
|
23
|
+
- blocker/question;
|
|
24
|
+
- result pointer;
|
|
25
|
+
- final handoff.
|
|
26
|
+
8. Do not mirror internal teammate chatter.
|
|
27
|
+
9. When complete, send: `ensemble send <SENDER> "Result: <summary + exact paths/URLs>" --from claude-lead --type result`.
|
|
28
|
+
10. Release claims.
|
|
29
|
+
|
|
30
|
+
Quality bar:
|
|
31
|
+
- exact file paths;
|
|
32
|
+
- no hidden state required to understand outcome;
|
|
33
|
+
- do not edit paths you did not claim;
|
|
34
|
+
- if Agent Teams state breaks, leave enough in pi-ensemble for Pi/human to resume.
|
|
35
|
+
|
|
36
|
+
Task:
|
|
37
|
+
<TASK>
|
|
38
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Example tmux wake adapter for pi-ensemble.
|
|
5
|
+
# Core state stays in .pi-ensemble/. This script only writes messages via the
|
|
6
|
+
# CLI and optionally pastes shell-safe wake prompts into tmux panes.
|
|
7
|
+
|
|
8
|
+
CONFIG="${ENSEMBLE_TMUX_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/pi-ensemble/tmux.env}"
|
|
9
|
+
if [[ -f "$CONFIG" ]]; then
|
|
10
|
+
# shellcheck source=/dev/null
|
|
11
|
+
source "$CONFIG"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
ROOT="${ENSEMBLE_ROOT:-$(pwd)}"
|
|
15
|
+
FROM="${ENSEMBLE_FROM:-${ENSEMBLE_DEFAULT_FROM:-manual}}"
|
|
16
|
+
|
|
17
|
+
usage() {
|
|
18
|
+
cat <<'HELP'
|
|
19
|
+
ensemble-tmux — example tmux wake adapter for pi-ensemble
|
|
20
|
+
|
|
21
|
+
Commands:
|
|
22
|
+
ensemble-tmux status
|
|
23
|
+
ensemble-tmux panes
|
|
24
|
+
ensemble-tmux wake AGENT [--message TEXT] [--pane TARGET]
|
|
25
|
+
ensemble-tmux send AGENT MESSAGE [--from NAME] [--type note|handoff|question|result|ack] [--no-wake] [--pane TARGET]
|
|
26
|
+
ensemble-tmux note MESSAGE [--from NAME]
|
|
27
|
+
|
|
28
|
+
Config:
|
|
29
|
+
$XDG_CONFIG_HOME/pi-ensemble/tmux.env or $HOME/.config/pi-ensemble/tmux.env
|
|
30
|
+
ENSEMBLE_ROOT=/path/to/workspace
|
|
31
|
+
ENSEMBLE_TMUX_<AGENT>_PANE='session:window.pane'
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
ENSEMBLE_TMUX_CLAUDE_LEAD_PANE='myproject:1.1'
|
|
35
|
+
HELP
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
agent_pane() {
|
|
39
|
+
local agent="$1"
|
|
40
|
+
local upper
|
|
41
|
+
upper="$(printf '%s' "$agent" | tr '[:lower:]-' '[:upper:]_')"
|
|
42
|
+
local var="ENSEMBLE_TMUX_${upper}_PANE"
|
|
43
|
+
printf '%s' "${!var:-}"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
send_keys() {
|
|
47
|
+
local pane="$1"
|
|
48
|
+
local text="$2"
|
|
49
|
+
printf '%s' "$text" | tmux load-buffer -
|
|
50
|
+
tmux paste-buffer -t "$pane"
|
|
51
|
+
tmux send-keys -t "$pane" Enter
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
wake_agent() {
|
|
55
|
+
local agent="$1"; shift || true
|
|
56
|
+
local pane=""
|
|
57
|
+
local message=""
|
|
58
|
+
while [[ $# -gt 0 ]]; do
|
|
59
|
+
case "$1" in
|
|
60
|
+
--pane) pane="${2:-}"; shift 2 ;;
|
|
61
|
+
--message) message="${2:-}"; shift 2 ;;
|
|
62
|
+
*) message="$*"; break ;;
|
|
63
|
+
esac
|
|
64
|
+
done
|
|
65
|
+
|
|
66
|
+
pane="${pane:-$(agent_pane "$agent")}"
|
|
67
|
+
[[ -n "$pane" ]] || { echo "No tmux pane configured for agent '$agent'" >&2; exit 2; }
|
|
68
|
+
tmux has-session -t "$pane" 2>/dev/null || { echo "tmux target not found: $pane" >&2; exit 1; }
|
|
69
|
+
|
|
70
|
+
# Shell-safe: if this lands in a bash pane, it is just a comment.
|
|
71
|
+
# Agent-safe: if this lands in a Pi/Claude prompt, it is readable markdown.
|
|
72
|
+
local prompt="# pi-ensemble: new inbox item. Run from any cwd: PI_ENSEMBLE_ROOT=$ROOT ensemble inbox --agent $agent --since-last-read"
|
|
73
|
+
if [[ -n "$message" ]]; then
|
|
74
|
+
prompt="$prompt — $message"
|
|
75
|
+
fi
|
|
76
|
+
send_keys "$pane" "$prompt"
|
|
77
|
+
echo "woke $agent ($pane)"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
cmd="${1:-help}"
|
|
81
|
+
shift || true
|
|
82
|
+
case "$cmd" in
|
|
83
|
+
status)
|
|
84
|
+
echo "ROOT=$ROOT"
|
|
85
|
+
echo "CONFIG=$CONFIG"
|
|
86
|
+
(cd "$ROOT" && ensemble status)
|
|
87
|
+
echo
|
|
88
|
+
echo "tmux panes:"
|
|
89
|
+
tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_current_command} #{pane_current_path} #{pane_title}' 2>/dev/null || true
|
|
90
|
+
;;
|
|
91
|
+
panes)
|
|
92
|
+
tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index} #{pane_current_command} #{pane_current_path} #{pane_title}'
|
|
93
|
+
;;
|
|
94
|
+
wake)
|
|
95
|
+
[[ $# -ge 1 ]] || { echo "usage: ensemble-tmux wake AGENT [--message TEXT] [--pane TARGET]" >&2; exit 2; }
|
|
96
|
+
agent="$1"; shift
|
|
97
|
+
wake_agent "$agent" "$@"
|
|
98
|
+
;;
|
|
99
|
+
send)
|
|
100
|
+
[[ $# -ge 2 ]] || { echo "usage: ensemble-tmux send AGENT MESSAGE [--from NAME] [--type TYPE] [--no-wake] [--pane TARGET]" >&2; exit 2; }
|
|
101
|
+
agent="$1"; shift
|
|
102
|
+
type="handoff"
|
|
103
|
+
no_wake=0
|
|
104
|
+
pane=""
|
|
105
|
+
args=()
|
|
106
|
+
while [[ $# -gt 0 ]]; do
|
|
107
|
+
case "$1" in
|
|
108
|
+
--from) FROM="${2:-}"; shift 2 ;;
|
|
109
|
+
--type) type="${2:-}"; shift 2 ;;
|
|
110
|
+
--no-wake) no_wake=1; shift ;;
|
|
111
|
+
--pane) pane="${2:-}"; shift 2 ;;
|
|
112
|
+
*) args+=("$1"); shift ;;
|
|
113
|
+
esac
|
|
114
|
+
done
|
|
115
|
+
message="${args[*]}"
|
|
116
|
+
[[ -n "$message" ]] || { echo "message is required" >&2; exit 2; }
|
|
117
|
+
(cd "$ROOT" && ensemble send "$agent" "$message" --from "$FROM" --type "$type")
|
|
118
|
+
if [[ "$no_wake" != 1 ]]; then
|
|
119
|
+
wake_agent "$agent" --pane "$pane" --message "from $FROM [$type]"
|
|
120
|
+
fi
|
|
121
|
+
;;
|
|
122
|
+
note)
|
|
123
|
+
[[ $# -ge 1 ]] || { echo "usage: ensemble-tmux note MESSAGE [--from NAME]" >&2; exit 2; }
|
|
124
|
+
args=()
|
|
125
|
+
while [[ $# -gt 0 ]]; do
|
|
126
|
+
case "$1" in
|
|
127
|
+
--from) FROM="${2:-}"; shift 2 ;;
|
|
128
|
+
*) args+=("$1"); shift ;;
|
|
129
|
+
esac
|
|
130
|
+
done
|
|
131
|
+
(cd "$ROOT" && ensemble note "${args[*]}" --from "$FROM")
|
|
132
|
+
;;
|
|
133
|
+
help|--help|-h|*)
|
|
134
|
+
usage
|
|
135
|
+
;;
|
|
136
|
+
esac
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
2
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import {
|
|
5
|
+
ack,
|
|
6
|
+
claim,
|
|
7
|
+
claims,
|
|
8
|
+
defaultAgent,
|
|
9
|
+
doctor,
|
|
10
|
+
done,
|
|
11
|
+
init,
|
|
12
|
+
messages,
|
|
13
|
+
note,
|
|
14
|
+
overview,
|
|
15
|
+
readAudit,
|
|
16
|
+
readBoard,
|
|
17
|
+
readInbox,
|
|
18
|
+
release,
|
|
19
|
+
requireWorkspaceRoot,
|
|
20
|
+
send,
|
|
21
|
+
status,
|
|
22
|
+
timeline,
|
|
23
|
+
} from "../lib/core.mjs";
|
|
24
|
+
|
|
25
|
+
const MessageType = StringEnum(["note", "handoff", "question", "result", "ack"] as const);
|
|
26
|
+
const ActionType = StringEnum(["init", "status", "note", "send", "ack", "done", "messages", "inbox", "board", "claims", "audit", "timeline", "overview", "doctor", "claim", "release"] as const);
|
|
27
|
+
|
|
28
|
+
function parseArgs(input: string): string[] {
|
|
29
|
+
const out: string[] = [];
|
|
30
|
+
const re = /"([^"]*)"|'([^']*)'|\S+/g;
|
|
31
|
+
let m: RegExpExecArray | null;
|
|
32
|
+
while ((m = re.exec(input))) out.push(m[1] ?? m[2] ?? m[0]);
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function asText(value: unknown): string {
|
|
37
|
+
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function takeFlag(argv: string[], name: string, fallback?: string): string | undefined {
|
|
41
|
+
const i = argv.indexOf(name);
|
|
42
|
+
if (i === -1) return fallback;
|
|
43
|
+
const value = argv[i + 1];
|
|
44
|
+
argv.splice(i, 2);
|
|
45
|
+
return value ?? fallback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function rootFromCwd(ctx: { cwd: string }, explicitRoot?: string) {
|
|
49
|
+
return requireWorkspaceRoot(explicitRoot || process.env.PI_ENSEMBLE_ROOT || ctx.cwd);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default function (pi: ExtensionAPI) {
|
|
53
|
+
pi.registerCommand("ensemble", {
|
|
54
|
+
description: "Local blackboard/mailbox for parallel coding agents",
|
|
55
|
+
handler: async (args, ctx) => {
|
|
56
|
+
const argv = parseArgs(args || "");
|
|
57
|
+
let explicitRoot = takeFlag(argv, "--root", undefined);
|
|
58
|
+
const cmd = argv.shift() || "status";
|
|
59
|
+
if (!explicitRoot) explicitRoot = takeFlag(argv, "--root", undefined);
|
|
60
|
+
try {
|
|
61
|
+
if (cmd === "init") {
|
|
62
|
+
const agent = argv[0] || defaultAgent();
|
|
63
|
+
const r = init(explicitRoot || process.env.PI_ENSEMBLE_ROOT || ctx.cwd, { agent });
|
|
64
|
+
ctx.ui.notify(`pi-ensemble initialized: ${r.dir}`, "success");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (cmd === "status") {
|
|
68
|
+
ctx.ui.notify(asText(status(rootFromCwd(ctx, explicitRoot))), "info");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (cmd === "note") {
|
|
72
|
+
note(rootFromCwd(ctx, explicitRoot), { from: defaultAgent(), body: argv.join(" ") });
|
|
73
|
+
ctx.ui.notify("pi-ensemble note added", "success");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (cmd === "send") {
|
|
77
|
+
const type = takeFlag(argv, "--type", "handoff") as "note" | "handoff" | "question" | "result" | "ack";
|
|
78
|
+
const to = argv.shift();
|
|
79
|
+
const result = send(rootFromCwd(ctx, explicitRoot), { from: defaultAgent(), to, type, body: argv.join(" ") });
|
|
80
|
+
ctx.ui.notify(`pi-ensemble sent to ${to}: ${result.messageId}`, "success");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (cmd === "ack") {
|
|
84
|
+
const from = takeFlag(argv, "--from", defaultAgent()) || defaultAgent();
|
|
85
|
+
const body = takeFlag(argv, "--body", "") || "";
|
|
86
|
+
const messageId = argv.shift();
|
|
87
|
+
const result = ack(rootFromCwd(ctx, explicitRoot), { from, messageId, body: body || argv.join(" ") });
|
|
88
|
+
ctx.ui.notify(`pi-ensemble acked ${result.messageId}`, "success");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (cmd === "done") {
|
|
92
|
+
const from = takeFlag(argv, "--from", defaultAgent()) || defaultAgent();
|
|
93
|
+
const body = takeFlag(argv, "--body", "") || "";
|
|
94
|
+
const messageId = argv.shift();
|
|
95
|
+
const result = done(rootFromCwd(ctx, explicitRoot), { from, messageId, body: body || argv.join(" ") });
|
|
96
|
+
ctx.ui.notify(`pi-ensemble resolved ${result.messageId}`, "success");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (cmd === "messages") {
|
|
100
|
+
const limit = Number(takeFlag(argv, "--limit", "50"));
|
|
101
|
+
const open = argv.includes("--open");
|
|
102
|
+
ctx.ui.notify(asText(messages(rootFromCwd(ctx, explicitRoot), { limit: Number.isFinite(limit) ? limit : 50, open })), "info");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (cmd === "inbox") {
|
|
106
|
+
const sinceLastRead = argv.includes("--since-last-read");
|
|
107
|
+
const noClear = argv.includes("--no-clear") || sinceLastRead;
|
|
108
|
+
const agent = argv.find(arg => !arg.startsWith("--")) || defaultAgent();
|
|
109
|
+
const content = readInbox(rootFromCwd(ctx, explicitRoot), { agent, clear: !noClear, sinceLastRead });
|
|
110
|
+
ctx.ui.notify(content || "Inbox empty", "info");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (cmd === "board") {
|
|
114
|
+
ctx.ui.notify(readBoard(rootFromCwd(ctx, explicitRoot)), "info");
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (cmd === "claims") {
|
|
118
|
+
ctx.ui.notify(asText(claims(rootFromCwd(ctx, explicitRoot))), "info");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (cmd === "audit") {
|
|
122
|
+
const limit = Number(takeFlag(argv, "--limit", "50"));
|
|
123
|
+
ctx.ui.notify(asText(readAudit(rootFromCwd(ctx, explicitRoot), { limit: Number.isFinite(limit) ? limit : 50 })), "info");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (cmd === "timeline") {
|
|
127
|
+
const limit = Number(takeFlag(argv, "--limit", "50"));
|
|
128
|
+
ctx.ui.notify(asText(timeline(rootFromCwd(ctx, explicitRoot), { limit: Number.isFinite(limit) ? limit : 50 })), "info");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (cmd === "overview") {
|
|
132
|
+
const limit = Number(takeFlag(argv, "--limit", "10"));
|
|
133
|
+
ctx.ui.notify(asText(overview(rootFromCwd(ctx, explicitRoot), { limit: Number.isFinite(limit) ? limit : 10 })), "info");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (cmd === "doctor") {
|
|
137
|
+
ctx.ui.notify(asText(doctor(rootFromCwd(ctx, explicitRoot))), "info");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (cmd === "claim") {
|
|
141
|
+
claim(rootFromCwd(ctx, explicitRoot), { agent: defaultAgent(), targetPath: argv.join(" ") });
|
|
142
|
+
ctx.ui.notify("pi-ensemble path claimed", "success");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (cmd === "release") {
|
|
146
|
+
release(rootFromCwd(ctx, explicitRoot), { agent: defaultAgent(), targetPath: argv.join(" ") });
|
|
147
|
+
ctx.ui.notify("pi-ensemble path released", "success");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
ctx.ui.notify("Usage: /ensemble init|status|note|send|ack|done|messages|inbox|board|claims|audit|timeline|overview|doctor|claim|release", "warning");
|
|
151
|
+
} catch (err) {
|
|
152
|
+
ctx.ui.notify(err instanceof Error ? err.message : String(err), "error");
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
pi.registerTool({
|
|
158
|
+
name: "ensemble",
|
|
159
|
+
label: "Ensemble",
|
|
160
|
+
description: "Read/write local .pi-ensemble blackboard, inboxes, and worktree claims. File-only: no network, no process spawning, no code execution.",
|
|
161
|
+
promptSnippet: "Coordinate with local coding agents via .pi-ensemble blackboard and inbox files",
|
|
162
|
+
promptGuidelines: [
|
|
163
|
+
"Use ensemble only for local coding-agent coordination inside a repository that has .pi-ensemble initialized.",
|
|
164
|
+
"Never put credentials, tokens, cookies, or private secrets into ensemble messages.",
|
|
165
|
+
"ensemble does not spawn agents, run commands, access the network, or wake remote sessions.",
|
|
166
|
+
],
|
|
167
|
+
parameters: Type.Object({
|
|
168
|
+
action: ActionType,
|
|
169
|
+
agent: Type.Optional(Type.String({ description: "Current/local agent name" })),
|
|
170
|
+
to: Type.Optional(Type.String({ description: "Target agent for send" })),
|
|
171
|
+
type: Type.Optional(MessageType),
|
|
172
|
+
body: Type.Optional(Type.String({ description: "Message body" })),
|
|
173
|
+
messageId: Type.Optional(Type.String({ description: "Message id for ack/done" })),
|
|
174
|
+
path: Type.Optional(Type.String({ description: "Path to claim or release" })),
|
|
175
|
+
clear: Type.Optional(Type.Boolean({ description: "Clear inbox after reading", default: true })),
|
|
176
|
+
sinceLastRead: Type.Optional(Type.Boolean({ description: "Return only messages newer than this agent's last read timestamp", default: false })),
|
|
177
|
+
force: Type.Optional(Type.Boolean({ description: "Override claim ownership conflicts", default: false })),
|
|
178
|
+
limit: Type.Optional(Type.Number({ description: "Maximum audit/message records to return", default: 50 })),
|
|
179
|
+
open: Type.Optional(Type.Boolean({ description: "For messages: return only messages not marked done", default: false })),
|
|
180
|
+
root: Type.Optional(Type.String({ description: "Workspace root or descendant containing .pi-ensemble" })),
|
|
181
|
+
}),
|
|
182
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
183
|
+
try {
|
|
184
|
+
const agent = params.agent || defaultAgent();
|
|
185
|
+
if (params.action === "init") {
|
|
186
|
+
const result = init(params.root || process.env.PI_ENSEMBLE_ROOT || ctx.cwd, { agent });
|
|
187
|
+
return { content: [{ type: "text", text: `Initialized ${result.dir}` }], details: result };
|
|
188
|
+
}
|
|
189
|
+
const root = rootFromCwd(ctx, params.root);
|
|
190
|
+
let result: unknown;
|
|
191
|
+
if (params.action === "status") result = status(root);
|
|
192
|
+
else if (params.action === "note") result = note(root, { from: agent, body: params.body || "" });
|
|
193
|
+
else if (params.action === "send") result = send(root, { from: agent, to: params.to, type: params.type || "handoff", body: params.body || "" });
|
|
194
|
+
else if (params.action === "ack") result = ack(root, { from: agent, messageId: params.messageId, body: params.body || "" });
|
|
195
|
+
else if (params.action === "done") result = done(root, { from: agent, messageId: params.messageId, body: params.body || "" });
|
|
196
|
+
else if (params.action === "messages") result = messages(root, { limit: params.limit ?? 50, open: params.open === true });
|
|
197
|
+
else if (params.action === "inbox") result = readInbox(root, { agent, clear: params.sinceLastRead === true ? false : params.clear !== false, sinceLastRead: params.sinceLastRead === true });
|
|
198
|
+
else if (params.action === "board") result = readBoard(root);
|
|
199
|
+
else if (params.action === "claims") result = claims(root);
|
|
200
|
+
else if (params.action === "audit") result = readAudit(root, { limit: params.limit ?? 50 });
|
|
201
|
+
else if (params.action === "timeline") result = timeline(root, { limit: params.limit ?? 50 });
|
|
202
|
+
else if (params.action === "overview") result = overview(root, { limit: params.limit ?? 10 });
|
|
203
|
+
else if (params.action === "doctor") result = doctor(root);
|
|
204
|
+
else if (params.action === "claim") result = claim(root, { agent, targetPath: params.path, force: params.force === true });
|
|
205
|
+
else if (params.action === "release") result = release(root, { agent, targetPath: params.path, force: params.force === true });
|
|
206
|
+
return { content: [{ type: "text", text: asText(result) }], details: { result } };
|
|
207
|
+
} catch (err) {
|
|
208
|
+
return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|