taskplane 0.22.18 → 0.23.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.
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Agent Bridge Extension — Minimal agent-side tools for Runtime V2
3
+ *
4
+ * Loaded into worker/reviewer/merger Pi agent processes to provide
5
+ * structured communication back to the supervisor and lane-runner
6
+ * without requiring agents to hand-roll JSON via bash/write.
7
+ *
8
+ * Tools:
9
+ * - notify_supervisor: send a reply or acknowledgment to supervisor
10
+ * - escalate_to_supervisor: escalate a blocker or ambiguity
11
+ *
12
+ * This extension is intentionally minimal and protocol-focused.
13
+ * It does NOT own:
14
+ * - review_step (deferred to TP-105+ lane-runner bridge work)
15
+ * - wait_for_review (deferred to persistent reviewer work)
16
+ * - request_segment_expansion (deferred to TP-086)
17
+ *
18
+ * File I/O only — writes to the agent's outbox directory.
19
+ * The lane-runner or engine polls outbox and surfaces to supervisor.
20
+ *
21
+ * @module taskplane/agent-bridge-extension
22
+ * @since TP-106
23
+ */
24
+
25
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
26
+ import { Type } from "@mariozechner/pi-ai";
27
+ import { writeFileSync, mkdirSync, renameSync } from "fs";
28
+ import { join } from "path";
29
+ import { randomBytes } from "crypto";
30
+
31
+ /**
32
+ * Resolve the outbox directory from environment variables.
33
+ *
34
+ * The lane-runner sets TASKPLANE_OUTBOX_DIR when launching workers
35
+ * with the bridge extension. Falls back to .pi/bridge-outbox/ in cwd.
36
+ */
37
+ function resolveOutboxDir(): string {
38
+ return process.env.TASKPLANE_OUTBOX_DIR || join(process.cwd(), ".pi", "bridge-outbox");
39
+ }
40
+
41
+ /**
42
+ * Write a message to the agent's outbox.
43
+ */
44
+ function writeOutbox(type: "reply" | "escalate", content: string, replyTo?: string): { id: string } {
45
+ const outboxDir = resolveOutboxDir();
46
+ mkdirSync(outboxDir, { recursive: true });
47
+
48
+ const contentBytes = Buffer.byteLength(content, "utf8");
49
+ if (contentBytes > 4096) {
50
+ throw new Error(`Outbox message exceeds 4096 bytes (${contentBytes})`);
51
+ }
52
+
53
+ const timestamp = Date.now();
54
+ const nonce = randomBytes(3).toString("hex").slice(0, 5);
55
+ const id = `${timestamp}-${nonce}`;
56
+
57
+ const message = {
58
+ id,
59
+ batchId: process.env.ORCH_BATCH_ID || "unknown",
60
+ from: process.env.TASKPLANE_AGENT_ID || "agent",
61
+ to: "supervisor",
62
+ timestamp,
63
+ type,
64
+ content,
65
+ expectsReply: type === "escalate",
66
+ replyTo: replyTo || null,
67
+ };
68
+
69
+ const tmpPath = join(outboxDir, `${id}.msg.json.tmp`);
70
+ const finalPath = join(outboxDir, `${id}.msg.json`);
71
+ writeFileSync(tmpPath, JSON.stringify(message, null, 2) + "\n", "utf-8");
72
+ renameSync(tmpPath, finalPath);
73
+
74
+ return { id };
75
+ }
76
+
77
+ export default function (pi: ExtensionAPI) {
78
+ pi.registerTool({
79
+ name: "notify_supervisor",
80
+ label: "Notify Supervisor",
81
+ description:
82
+ "Send a reply or acknowledgment to the supervisor. " +
83
+ "Use this to confirm you've received a steering message, " +
84
+ "report a status update, or share a discovery.",
85
+ promptSnippet: "notify_supervisor(content, replyTo?) — send reply to supervisor",
86
+ promptGuidelines: [
87
+ "Use notify_supervisor to acknowledge steering messages or share status updates.",
88
+ "Keep content concise (max 4KB).",
89
+ "Include replyTo with the message ID you're responding to, if applicable.",
90
+ ],
91
+ parameters: Type.Object({
92
+ content: Type.String({
93
+ description: "Reply content (max 4KB)",
94
+ }),
95
+ replyTo: Type.Optional(Type.String({
96
+ description: "Message ID being replied to (from a steering message)",
97
+ })),
98
+ }),
99
+ async execute(_toolCallId, params) {
100
+ try {
101
+ const result = writeOutbox("reply", params.content, params.replyTo);
102
+ return {
103
+ content: [{
104
+ type: "text" as const,
105
+ text: `✅ Reply sent to supervisor (ID: ${result.id})`,
106
+ }],
107
+ details: undefined,
108
+ };
109
+ } catch (err) {
110
+ return {
111
+ content: [{
112
+ type: "text" as const,
113
+ text: `❌ Failed to send reply: ${err instanceof Error ? err.message : String(err)}`,
114
+ }],
115
+ details: undefined,
116
+ };
117
+ }
118
+ },
119
+ });
120
+
121
+ pi.registerTool({
122
+ name: "escalate_to_supervisor",
123
+ label: "Escalate to Supervisor",
124
+ description:
125
+ "Escalate a blocker, ambiguity, or question to the supervisor. " +
126
+ "Use this when you're stuck, confused, or need guidance before proceeding.",
127
+ promptSnippet: "escalate_to_supervisor(content) — escalate blocker to supervisor",
128
+ promptGuidelines: [
129
+ "Use escalate_to_supervisor when you're blocked and need human/supervisor guidance.",
130
+ "Clearly describe what you're stuck on and what options you see.",
131
+ "The supervisor will respond via a steering message.",
132
+ ],
133
+ parameters: Type.Object({
134
+ content: Type.String({
135
+ description: "Description of the blocker or question (max 4KB)",
136
+ }),
137
+ }),
138
+ async execute(_toolCallId, params) {
139
+ try {
140
+ const result = writeOutbox("escalate", params.content);
141
+ return {
142
+ content: [{
143
+ type: "text" as const,
144
+ text: `⚠️ Escalation sent to supervisor (ID: ${result.id}). Continue working on other items while waiting for guidance.`,
145
+ }],
146
+ details: undefined,
147
+ };
148
+ } catch (err) {
149
+ return {
150
+ content: [{
151
+ type: "text" as const,
152
+ text: `❌ Failed to escalate: ${err instanceof Error ? err.message : String(err)}`,
153
+ }],
154
+ details: undefined,
155
+ };
156
+ }
157
+ },
158
+ });
159
+ }