@oh-my-pi/pi-coding-agent 14.4.1 → 14.4.3
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 +56 -0
- package/package.json +7 -7
- package/src/cli.ts +0 -1
- package/src/config/prompt-templates.ts +0 -30
- package/src/config/settings-schema.ts +26 -36
- package/src/config/settings.ts +1 -1
- package/src/edit/index.ts +1 -53
- package/src/edit/line-hash.ts +0 -53
- package/src/edit/modes/atom.ts +82 -47
- package/src/edit/modes/hashline.ts +6 -8
- package/src/edit/renderer.ts +6 -8
- package/src/edit/streaming.ts +90 -114
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +10 -15
- package/src/internal-urls/docs-index.generated.ts +1 -2
- package/src/modes/components/settings-defs.ts +0 -5
- package/src/modes/components/tool-execution.ts +2 -5
- package/src/modes/controllers/btw-controller.ts +17 -105
- package/src/modes/controllers/todo-command-controller.ts +537 -0
- package/src/modes/interactive-mode.ts +35 -9
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +17 -0
- package/src/prompts/system/irc-incoming.md +8 -0
- package/src/prompts/system/subagent-system-prompt.md +8 -0
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/atom.md +37 -26
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/grep.md +2 -5
- package/src/prompts/tools/irc.md +49 -0
- package/src/prompts/tools/job.md +11 -0
- package/src/prompts/tools/read.md +12 -13
- package/src/prompts/tools/task.md +1 -1
- package/src/prompts/tools/todo-write.md +14 -5
- package/src/registry/agent-registry.ts +139 -0
- package/src/sdk.ts +35 -0
- package/src/session/agent-session.ts +217 -5
- package/src/session/streaming-output.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +24 -0
- package/src/task/executor.ts +14 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/fetch.ts +18 -6
- package/src/tools/fs-cache-invalidation.ts +0 -5
- package/src/tools/grep.ts +4 -124
- package/src/tools/index.ts +12 -6
- package/src/tools/irc.ts +258 -0
- package/src/tools/job.ts +489 -0
- package/src/tools/match-line-format.ts +7 -6
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/read.ts +36 -126
- package/src/tools/renderers.ts +2 -0
- package/src/tools/todo-write.ts +243 -12
- package/src/utils/edit-mode.ts +1 -2
- package/src/utils/file-display-mode.ts +0 -3
- package/src/cli/read-cli.ts +0 -67
- package/src/commands/read.ts +0 -33
- package/src/edit/modes/chunk.ts +0 -832
- package/src/prompts/tools/cancel-job.md +0 -5
- package/src/prompts/tools/chunk-edit.md +0 -158
- package/src/prompts/tools/poll.md +0 -5
- package/src/prompts/tools/read-chunk.md +0 -73
- package/src/tools/cancel-job.ts +0 -95
- package/src/tools/poll-tool.ts +0 -173
package/src/tools/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { checkPythonKernelAvailability } from "../ipy/kernel";
|
|
|
12
12
|
import { LspTool } from "../lsp";
|
|
13
13
|
import type { DiscoverableMCPSearchIndex, DiscoverableMCPTool } from "../mcp/discoverable-tool-metadata";
|
|
14
14
|
import type { PlanModeState } from "../plan-mode/state";
|
|
15
|
+
import type { AgentRegistry } from "../registry/agent-registry";
|
|
15
16
|
import type { CustomMessage } from "../session/messages";
|
|
16
17
|
import type { ToolChoiceQueue } from "../session/tool-choice-queue";
|
|
17
18
|
import { TaskTool } from "../task";
|
|
@@ -24,7 +25,6 @@ import { AstGrepTool } from "./ast-grep";
|
|
|
24
25
|
import { BashTool } from "./bash";
|
|
25
26
|
import { BrowserTool } from "./browser";
|
|
26
27
|
import { CalculatorTool } from "./calculator";
|
|
27
|
-
import { CancelJobTool } from "./cancel-job";
|
|
28
28
|
import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
|
|
29
29
|
import { DebugTool } from "./debug";
|
|
30
30
|
import { ExitPlanModeTool } from "./exit-plan-mode";
|
|
@@ -32,9 +32,10 @@ import { FindTool } from "./find";
|
|
|
32
32
|
import { GithubTool } from "./gh";
|
|
33
33
|
import { GrepTool } from "./grep";
|
|
34
34
|
import { InspectImageTool } from "./inspect-image";
|
|
35
|
+
import { IrcTool } from "./irc";
|
|
36
|
+
import { JobTool } from "./job";
|
|
35
37
|
import { NotebookTool } from "./notebook";
|
|
36
38
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
37
|
-
import { PollTool } from "./poll-tool";
|
|
38
39
|
import { PythonTool } from "./python";
|
|
39
40
|
import { ReadTool } from "./read";
|
|
40
41
|
import { RenderMermaidTool } from "./render-mermaid";
|
|
@@ -62,7 +63,6 @@ export * from "./ast-grep";
|
|
|
62
63
|
export * from "./bash";
|
|
63
64
|
export * from "./browser";
|
|
64
65
|
export * from "./calculator";
|
|
65
|
-
export * from "./cancel-job";
|
|
66
66
|
export * from "./checkpoint";
|
|
67
67
|
export * from "./debug";
|
|
68
68
|
export * from "./exit-plan-mode";
|
|
@@ -71,8 +71,9 @@ export * from "./gh";
|
|
|
71
71
|
export * from "./grep";
|
|
72
72
|
export * from "./image-gen";
|
|
73
73
|
export * from "./inspect-image";
|
|
74
|
+
export * from "./irc";
|
|
75
|
+
export * from "./job";
|
|
74
76
|
export * from "./notebook";
|
|
75
|
-
export * from "./poll-tool";
|
|
76
77
|
export * from "./python";
|
|
77
78
|
export * from "./read";
|
|
78
79
|
export * from "./render-mermaid";
|
|
@@ -135,6 +136,10 @@ export interface ToolSession {
|
|
|
135
136
|
trackPythonExecution?<T>(execution: Promise<T>, abortController: AbortController): Promise<T>;
|
|
136
137
|
/** Get session ID */
|
|
137
138
|
getSessionId?: () => string | null;
|
|
139
|
+
/** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
|
|
140
|
+
getAgentId?: () => string | null;
|
|
141
|
+
/** Agent registry for IRC routing across live sessions. */
|
|
142
|
+
agentRegistry?: AgentRegistry;
|
|
138
143
|
/** Get artifacts directory for artifact:// URLs */
|
|
139
144
|
getArtifactsDir?: () => string | null;
|
|
140
145
|
/** Allocate a new artifact path and ID for session-scoped truncated output. */
|
|
@@ -218,8 +223,8 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
218
223
|
checkpoint: CheckpointTool.createIf,
|
|
219
224
|
rewind: RewindTool.createIf,
|
|
220
225
|
task: TaskTool.create,
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
job: JobTool.createIf,
|
|
227
|
+
irc: IrcTool.createIf,
|
|
223
228
|
todo_write: s => new TodoWriteTool(s),
|
|
224
229
|
web_search: s => new SearchTool(s),
|
|
225
230
|
search_tool_bm25: SearchToolBm25Tool.createIf,
|
|
@@ -386,6 +391,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
386
391
|
if (name === "calc") return session.settings.get("calc.enabled");
|
|
387
392
|
if (name === "browser") return session.settings.get("browser.enabled");
|
|
388
393
|
if (name === "checkpoint" || name === "rewind") return session.settings.get("checkpoint.enabled");
|
|
394
|
+
if (name === "irc") return session.settings.get("irc.enabled");
|
|
389
395
|
if (name === "task") {
|
|
390
396
|
const maxDepth = session.settings.get("task.maxRecursionDepth") ?? 2;
|
|
391
397
|
const currentDepth = session.taskDepth ?? 0;
|
package/src/tools/irc.ts
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IRC tool — agent-to-agent messaging.
|
|
3
|
+
*
|
|
4
|
+
* Lets any live agent send a short prose message to any other live agent in
|
|
5
|
+
* this process and (optionally) get a prose reply.
|
|
6
|
+
*
|
|
7
|
+
* Routing happens via the global AgentRegistry. Replies are produced by an
|
|
8
|
+
* ephemeral side-channel call (`AgentSession.respondAsBackground`) that
|
|
9
|
+
* mirrors `/btw`: the recipient's current model, system prompt, and message
|
|
10
|
+
* history are used to compute a reply without persisting it through the
|
|
11
|
+
* normal stream path. After the reply is generated, both the incoming
|
|
12
|
+
* message and the auto-reply are queued for injection into the recipient's
|
|
13
|
+
* persisted history (deferred until the recipient is idle), so the model
|
|
14
|
+
* sees the exchange on its next turn.
|
|
15
|
+
*
|
|
16
|
+
* This avoids the deadlock that arises when the recipient is blocked on a
|
|
17
|
+
* long-running tool call: the side-channel call does not depend on the
|
|
18
|
+
* recipient's main agent loop being free.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
22
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
23
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
24
|
+
import ircDescription from "../prompts/tools/irc.md" with { type: "text" };
|
|
25
|
+
import type { AgentRef, AgentRegistry } from "../registry/agent-registry";
|
|
26
|
+
import type { ToolSession } from ".";
|
|
27
|
+
|
|
28
|
+
const ircSchema = Type.Object({
|
|
29
|
+
op: Type.Union(
|
|
30
|
+
[
|
|
31
|
+
Type.Literal("send", { description: "Send a message to one peer or to all peers" }),
|
|
32
|
+
Type.Literal("list", { description: "List currently visible peers" }),
|
|
33
|
+
],
|
|
34
|
+
{ description: "IRC operation" },
|
|
35
|
+
),
|
|
36
|
+
to: Type.Optional(
|
|
37
|
+
Type.String({
|
|
38
|
+
description: 'Recipient agent id (e.g. "0-Main", "0-AuthLoader") or "all" to broadcast',
|
|
39
|
+
examples: ["0-Main", "all"],
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
message: Type.Optional(
|
|
43
|
+
Type.String({
|
|
44
|
+
description: "Message body to deliver",
|
|
45
|
+
examples: ["Should we use JWT or session cookies?"],
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
awaitReply: Type.Optional(
|
|
49
|
+
Type.Boolean({
|
|
50
|
+
description: "Wait for the recipient's prose reply (default: true for DM, false for broadcast)",
|
|
51
|
+
}),
|
|
52
|
+
),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
type IrcParams = Static<typeof ircSchema>;
|
|
56
|
+
|
|
57
|
+
interface IrcReply {
|
|
58
|
+
from: string;
|
|
59
|
+
text: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface IrcDetails {
|
|
63
|
+
op: "send" | "list";
|
|
64
|
+
from?: string;
|
|
65
|
+
to?: string;
|
|
66
|
+
delivered?: string[];
|
|
67
|
+
replies?: IrcReply[];
|
|
68
|
+
failed?: Array<{ id: string; error: string }>;
|
|
69
|
+
notFound?: string[];
|
|
70
|
+
peers?: Array<{ id: string; displayName: string; kind: string; status: string; parentId?: string }>;
|
|
71
|
+
channels?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class IrcTool implements AgentTool<typeof ircSchema, IrcDetails> {
|
|
75
|
+
readonly name = "irc";
|
|
76
|
+
readonly label = "IRC";
|
|
77
|
+
readonly description: string;
|
|
78
|
+
readonly parameters = ircSchema;
|
|
79
|
+
readonly strict = true;
|
|
80
|
+
|
|
81
|
+
constructor(private readonly session: ToolSession) {
|
|
82
|
+
this.description = prompt.render(ircDescription);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static createIf(session: ToolSession): IrcTool | null {
|
|
86
|
+
if (!session.settings.get("irc.enabled")) return null;
|
|
87
|
+
if (!session.agentRegistry || !session.getAgentId) return null;
|
|
88
|
+
return new IrcTool(session);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async execute(
|
|
92
|
+
_toolCallId: string,
|
|
93
|
+
params: IrcParams,
|
|
94
|
+
signal?: AbortSignal,
|
|
95
|
+
_onUpdate?: AgentToolUpdateCallback<IrcDetails>,
|
|
96
|
+
_context?: AgentToolContext,
|
|
97
|
+
): Promise<AgentToolResult<IrcDetails>> {
|
|
98
|
+
const registry = this.session.agentRegistry;
|
|
99
|
+
const senderId = this.session.getAgentId?.() ?? null;
|
|
100
|
+
if (!registry) {
|
|
101
|
+
return errorResult("IRC is unavailable in this session.", { op: params.op });
|
|
102
|
+
}
|
|
103
|
+
if (!senderId) {
|
|
104
|
+
return errorResult("IRC is unavailable: caller has no agent id.", { op: params.op });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (params.op === "list") {
|
|
108
|
+
return this.#executeList(registry, senderId);
|
|
109
|
+
}
|
|
110
|
+
if (params.op === "send") {
|
|
111
|
+
return this.#executeSend(registry, senderId, params, signal);
|
|
112
|
+
}
|
|
113
|
+
return errorResult("Unknown irc op.", { op: params.op as "send" | "list" });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#executeList(registry: AgentRegistry, senderId: string): AgentToolResult<IrcDetails> {
|
|
117
|
+
const peers = registry.listVisibleTo(senderId);
|
|
118
|
+
const lines: string[] = [];
|
|
119
|
+
if (peers.length === 0) {
|
|
120
|
+
lines.push("No other live agents.");
|
|
121
|
+
} else {
|
|
122
|
+
lines.push(`${peers.length} peer(s):`);
|
|
123
|
+
for (const peer of peers) {
|
|
124
|
+
lines.push(`- ${peer.id} [${peer.displayName} · ${peer.kind} · ${peer.status}]`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const channels = ["all", ...peers.map(p => p.id)];
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
130
|
+
details: {
|
|
131
|
+
op: "list",
|
|
132
|
+
from: senderId,
|
|
133
|
+
peers: peers.map(p => ({
|
|
134
|
+
id: p.id,
|
|
135
|
+
displayName: p.displayName,
|
|
136
|
+
kind: p.kind,
|
|
137
|
+
status: p.status,
|
|
138
|
+
parentId: p.parentId,
|
|
139
|
+
})),
|
|
140
|
+
channels,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async #executeSend(
|
|
146
|
+
registry: AgentRegistry,
|
|
147
|
+
senderId: string,
|
|
148
|
+
params: IrcParams,
|
|
149
|
+
signal?: AbortSignal,
|
|
150
|
+
): Promise<AgentToolResult<IrcDetails>> {
|
|
151
|
+
const to = params.to?.trim();
|
|
152
|
+
const message = params.message?.trim();
|
|
153
|
+
if (!to) {
|
|
154
|
+
return errorResult('`to` is required for op="send".', { op: "send", from: senderId });
|
|
155
|
+
}
|
|
156
|
+
if (!message) {
|
|
157
|
+
return errorResult('`message` is required for op="send".', { op: "send", from: senderId });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Resolve target peers.
|
|
161
|
+
let targets: AgentRef[];
|
|
162
|
+
const notFound: string[] = [];
|
|
163
|
+
const isBroadcast = to === "all";
|
|
164
|
+
if (isBroadcast) {
|
|
165
|
+
targets = registry.listVisibleTo(senderId);
|
|
166
|
+
} else {
|
|
167
|
+
const ref = registry.get(to);
|
|
168
|
+
if (!ref || ref.id === senderId) {
|
|
169
|
+
notFound.push(to);
|
|
170
|
+
targets = [];
|
|
171
|
+
} else if (ref.status !== "running" && ref.status !== "idle") {
|
|
172
|
+
notFound.push(to);
|
|
173
|
+
targets = [];
|
|
174
|
+
} else {
|
|
175
|
+
targets = [ref];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const awaitReply = params.awaitReply ?? !isBroadcast;
|
|
180
|
+
|
|
181
|
+
const delivered: string[] = [];
|
|
182
|
+
const replies: IrcReply[] = [];
|
|
183
|
+
const failed: Array<{ id: string; error: string }> = [];
|
|
184
|
+
|
|
185
|
+
// Dispatch to each target in parallel via the recipient's ephemeral
|
|
186
|
+
// side-channel. Independent calls so a slow recipient cannot stall the
|
|
187
|
+
// others. The recipient's main loop never has to be unblocked: the
|
|
188
|
+
// side-channel runs alongside any in-flight tool call.
|
|
189
|
+
const dispatches = targets.map(async target => {
|
|
190
|
+
const targetSession = target.session;
|
|
191
|
+
if (!targetSession) {
|
|
192
|
+
notFound.push(target.id);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const result = await targetSession.respondAsBackground({
|
|
197
|
+
from: senderId,
|
|
198
|
+
message,
|
|
199
|
+
awaitReply,
|
|
200
|
+
signal,
|
|
201
|
+
});
|
|
202
|
+
delivered.push(target.id);
|
|
203
|
+
if (awaitReply && result.replyText) {
|
|
204
|
+
replies.push({ from: target.id, text: result.replyText });
|
|
205
|
+
}
|
|
206
|
+
} catch (err) {
|
|
207
|
+
failed.push({ id: target.id, error: err instanceof Error ? err.message : String(err) });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
await Promise.all(dispatches);
|
|
211
|
+
|
|
212
|
+
const lines: string[] = [];
|
|
213
|
+
if (delivered.length === 0) {
|
|
214
|
+
lines.push("No recipients received the message.");
|
|
215
|
+
} else {
|
|
216
|
+
lines.push(`Delivered to ${delivered.length} peer(s): ${delivered.join(", ")}`);
|
|
217
|
+
}
|
|
218
|
+
if (replies.length > 0) {
|
|
219
|
+
lines.push("");
|
|
220
|
+
lines.push("## Replies");
|
|
221
|
+
for (const reply of replies) {
|
|
222
|
+
lines.push(`### ${reply.from}`);
|
|
223
|
+
lines.push(reply.text);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (failed.length > 0) {
|
|
227
|
+
lines.push("");
|
|
228
|
+
lines.push("## Failed");
|
|
229
|
+
for (const f of failed) {
|
|
230
|
+
lines.push(`- ${f.id}: ${f.error}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (notFound.length > 0) {
|
|
234
|
+
lines.push("");
|
|
235
|
+
lines.push(`Unknown / unavailable peers: ${notFound.join(", ")}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
240
|
+
details: {
|
|
241
|
+
op: "send",
|
|
242
|
+
from: senderId,
|
|
243
|
+
to,
|
|
244
|
+
delivered,
|
|
245
|
+
...(replies.length > 0 ? { replies } : {}),
|
|
246
|
+
...(failed.length > 0 ? { failed } : {}),
|
|
247
|
+
...(notFound.length > 0 ? { notFound } : {}),
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function errorResult(text: string, details: IrcDetails): AgentToolResult<IrcDetails> {
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: "text", text }],
|
|
256
|
+
details,
|
|
257
|
+
};
|
|
258
|
+
}
|