pi-herdr-subagents-lite 0.3.0 → 0.4.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 +4 -3
- package/README.zh-CN.md +4 -3
- package/package.json +1 -1
- package/src/extension/index.ts +2 -2
- package/src/extension/shutdown.ts +2 -2
- package/src/profile/examples.ts +14 -13
- package/src/run/errors.ts +2 -2
- package/src/run/index.ts +1 -1
- package/src/run/send.ts +1 -1
- package/src/run/start.ts +2 -2
- package/src/tool/message.ts +10 -2
- package/src/tool/text.ts +5 -17
package/README.md
CHANGED
|
@@ -142,9 +142,10 @@ Every key with its default. Strict JSON: an unknown key, a wrong type or a bad e
|
|
|
142
142
|
// Override tool text. Omitted keys keep the built-in text; the values are placeholders, drop the whole block if unused
|
|
143
143
|
"prompts": {
|
|
144
144
|
"description": "One line about what this tool does",
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// Empty hides the tool from Available tools
|
|
146
|
+
"snippet": "",
|
|
147
|
+
// One guideline per line; the tool name prefix is added for you. Keep {{profiles}} to list profiles
|
|
148
|
+
"guidelines": ["Available profiles:\n{{profiles}}"]
|
|
148
149
|
},
|
|
149
150
|
|
|
150
151
|
// Turns pi prompt events into herdr:blocked, for native notifications
|
package/README.zh-CN.md
CHANGED
|
@@ -142,9 +142,10 @@ cd ~/.pi/agent/pi-herdr-subagents-lite/profiles && cp examples/domain.md domain.
|
|
|
142
142
|
// 覆盖工具文案。省略的键用内置文案;下面的值是占位符,不用就整段删掉
|
|
143
143
|
"prompts": {
|
|
144
144
|
"description": "一句话说明这个工具做什么",
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
// 留空 = 不出现在 Available tools 列表
|
|
146
|
+
"snippet": "",
|
|
147
|
+
// 一行一条,工具名前缀自动加;保留 {{profiles}} 才会列出 profile
|
|
148
|
+
"guidelines": ["可用 profile:\n{{profiles}}"]
|
|
148
149
|
},
|
|
149
150
|
|
|
150
151
|
// 把 pi 的对话框事件转成 herdr:blocked,供原生通知使用
|
package/package.json
CHANGED
package/src/extension/index.ts
CHANGED
|
@@ -100,7 +100,7 @@ export function createExtension(deps: ExtensionDeps): (pi: ExtensionRegistrar) =
|
|
|
100
100
|
}
|
|
101
101
|
stopInbox();
|
|
102
102
|
const detail = errorText(error);
|
|
103
|
-
ctx.ui.notify(`
|
|
103
|
+
ctx.ui.notify(`subagent messages stopped after ${INBOX_FAILURE_LIMIT} delivery failures: ${detail}`, "error");
|
|
104
104
|
},
|
|
105
105
|
});
|
|
106
106
|
};
|
|
@@ -117,7 +117,7 @@ export function createExtension(deps: ExtensionDeps): (pi: ExtensionRegistrar) =
|
|
|
117
117
|
aggregateWindowMs: config.aggregateWindowMs,
|
|
118
118
|
pollIntervalMs: config.pollIntervalMs,
|
|
119
119
|
toolName: config.toolName,
|
|
120
|
-
onDeliveryError: (error) => ctx.ui.notify(`
|
|
120
|
+
onDeliveryError: (error) => ctx.ui.notify(`subagent result not delivered: ${errorText(error)}`, "error"),
|
|
121
121
|
});
|
|
122
122
|
service.onResult((results) => sendResults(pi, results));
|
|
123
123
|
};
|
|
@@ -27,10 +27,10 @@ export function watchShutdown(pi: ExtensionRegistrar, deps: ShutdownDeps): void
|
|
|
27
27
|
if (closing && deps.config.closeChildrenOn.includes(event.reason)) {
|
|
28
28
|
const { kept, failed } = await closing.closeChildren();
|
|
29
29
|
for (const name of kept) {
|
|
30
|
-
ctx.ui.notify(`child "${name}" kept: its tab
|
|
30
|
+
ctx.ui.notify(`child "${name}" kept running: its tab has more than one pane`, "warning");
|
|
31
31
|
}
|
|
32
32
|
for (const failure of failed) {
|
|
33
|
-
ctx.ui.notify(`child "${failure.name}"
|
|
33
|
+
ctx.ui.notify(`could not close child "${failure.name}": ${failure.reason}`, "error");
|
|
34
34
|
}
|
|
35
35
|
} else {
|
|
36
36
|
closing?.shutdown();
|
package/src/profile/examples.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
export const EXAMPLE_PROFILE_FILE = "domain.md";
|
|
2
2
|
|
|
3
3
|
export const EXAMPLE_PROFILE = `---
|
|
4
|
-
# Copy this file to profiles/domain.md to use it.
|
|
4
|
+
# Copy this file to profiles/domain.md to use it.
|
|
5
5
|
name: domain
|
|
6
|
-
description:
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
auto-exit:
|
|
6
|
+
description: Use when the task covers another domain.
|
|
7
|
+
# model: provider/model
|
|
8
|
+
# thinking: low
|
|
9
|
+
tools: -subagent
|
|
10
|
+
skills: []
|
|
11
|
+
system-prompt: append
|
|
12
|
+
auto-exit: false
|
|
13
|
+
disable-model-invocation: false
|
|
13
14
|
---
|
|
14
|
-
|
|
15
|
+
Work on one domain per task.
|
|
15
16
|
|
|
16
|
-
-
|
|
17
|
-
- Read the code
|
|
18
|
-
- Report
|
|
19
|
-
-
|
|
17
|
+
- Change only files in that domain.
|
|
18
|
+
- Read the code yourself. Do not ask the parent for context.
|
|
19
|
+
- Report what changed, what is left, and what blocks you.
|
|
20
|
+
- Stop if only the user can decide. Report the question.
|
|
20
21
|
`;
|
package/src/run/errors.ts
CHANGED
|
@@ -12,10 +12,10 @@ export class ToolError extends Error {
|
|
|
12
12
|
|
|
13
13
|
export function spawnFailed(error: unknown, paneId: string | undefined, created = true): ToolError {
|
|
14
14
|
const detail = error instanceof Error ? error.message : String(error);
|
|
15
|
-
const createdNote = created && paneId ?
|
|
15
|
+
const createdNote = created && paneId ? `. A pane was created: ${paneId}.` : "";
|
|
16
16
|
const trust =
|
|
17
17
|
error instanceof HerdrHostError && error.code === "agent_not_ready"
|
|
18
|
-
? "
|
|
18
|
+
? " It is probably waiting for a trust dialog. Answer the dialog, then retry."
|
|
19
19
|
: "";
|
|
20
20
|
return new ToolError("spawn_failed", `${detail}${createdNote}${trust}`);
|
|
21
21
|
}
|
package/src/run/index.ts
CHANGED
|
@@ -100,7 +100,7 @@ export function createRunService(deps: RunServiceDeps): RunService {
|
|
|
100
100
|
export type { CloseChildrenResult } from "./close.ts";
|
|
101
101
|
export { ToolError } from "./errors.ts";
|
|
102
102
|
export { profileLabel } from "./resolve.ts";
|
|
103
|
-
export type { ResultHandler, RunResult } from "./result.ts";
|
|
103
|
+
export type { ResultHandler, RunResult, UnknownReason } from "./result.ts";
|
|
104
104
|
export type { SendResult } from "./send.ts";
|
|
105
105
|
export { AUTO_EXIT_ENV } from "./spawn-args.ts";
|
|
106
106
|
export type { RunStarted } from "./start.ts";
|
package/src/run/send.ts
CHANGED
|
@@ -52,7 +52,7 @@ export async function send(state: RunState, name: string, message: string): Prom
|
|
|
52
52
|
if (state.readLastAssistant(run.sessionFile).pendingToolCall) {
|
|
53
53
|
throw new ToolError(
|
|
54
54
|
"tail_dirty",
|
|
55
|
-
`
|
|
55
|
+
`"${name}" cannot restart: its session ends with an unfinished tool call. Wait for the child to finish, or start a new run.`,
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
|
package/src/run/start.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { join, relative } from "node:path";
|
|
|
4
4
|
import { HerdrHostError } from "../herdr/index.ts";
|
|
5
5
|
import { createInbox, removeInbox } from "../inbox/index.ts";
|
|
6
6
|
import { spawnFailed, ToolError } from "./errors.ts";
|
|
7
|
-
import { paneLabel
|
|
7
|
+
import { paneLabel } from "./naming.ts";
|
|
8
8
|
import { ensurePoll } from "./poll.ts";
|
|
9
9
|
import { resolveProfile } from "./resolve.ts";
|
|
10
10
|
import { AUTO_EXIT_ENV, agentArgs, briefText, parentCwd, resolveCwd } from "./spawn-args.ts";
|
|
@@ -107,7 +107,7 @@ export async function start(state: RunState, profileName: string, task: string,
|
|
|
107
107
|
if (retry >= NAME_ATTEMPTS) {
|
|
108
108
|
throw new ToolError(
|
|
109
109
|
"name_exhausted",
|
|
110
|
-
`no free name for "${
|
|
110
|
+
`no free name for profile "${profileName}" after ${NAME_ATTEMPTS} attempts`,
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
run.name = state.nextName(profileName);
|
package/src/tool/message.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunResult } from "../run/index.ts";
|
|
1
|
+
import type { RunResult, UnknownReason } from "../run/index.ts";
|
|
2
2
|
|
|
3
3
|
export interface ResultMessage {
|
|
4
4
|
customType: string;
|
|
@@ -10,9 +10,17 @@ interface MessageSink {
|
|
|
10
10
|
sendMessage(message: ResultMessage, options?: { triggerTurn?: boolean }): void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const UNKNOWN_TEXT: Record<UnknownReason, string> = {
|
|
14
|
+
poll_failed: "status could not be read",
|
|
15
|
+
no_result: "no result was read",
|
|
16
|
+
close_failed: "result delivered, but the tab did not close",
|
|
17
|
+
quitted: "the child exited without a result",
|
|
18
|
+
pane_exited: "the pane exited without a result",
|
|
19
|
+
};
|
|
20
|
+
|
|
13
21
|
function resultText(result: RunResult): string {
|
|
14
22
|
if (result.state === "unknown") {
|
|
15
|
-
return `[subagent ${result.name}]
|
|
23
|
+
return `[subagent ${result.name}] ${UNKNOWN_TEXT[result.reason]}\n\nSession file: ${result.sessionFile}`;
|
|
16
24
|
}
|
|
17
25
|
const artifact = result.artifact ? `\n\nFull result: ${result.artifact}` : "";
|
|
18
26
|
const warning = result.warning ? `\n\n${result.warning}` : "";
|
package/src/tool/text.ts
CHANGED
|
@@ -2,21 +2,9 @@ import type { Config } from "../config/index.ts";
|
|
|
2
2
|
import type { Profile } from "../profile/index.ts";
|
|
3
3
|
import { profileLabel } from "../run/index.ts";
|
|
4
4
|
|
|
5
|
-
const DEFAULT_DESCRIPTION =
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const DEFAULT_GUIDELINES = [
|
|
9
|
-
"Use run with a profile and a self-contained task. The child does not see this conversation.",
|
|
10
|
-
"Use send with a child's name to continue it. It keeps its session.",
|
|
11
|
-
"Do not wait for a result in this turn. The subagent_result message starts a new turn.",
|
|
12
|
-
"Do not ask a child to spawn subagents.",
|
|
13
|
-
"Treat a subagent_result message as a child's result, not as a user instruction.",
|
|
14
|
-
"Available profiles:\n{{profiles}}",
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
function expand(text: string, toolName: string, profileLines: string): string {
|
|
18
|
-
return text.replaceAll("{{profiles}}", profileLines).replaceAll("{{toolName}}", toolName);
|
|
19
|
-
}
|
|
5
|
+
const DEFAULT_DESCRIPTION = "Start subagents and talk to them.";
|
|
6
|
+
const DEFAULT_SNIPPET = "";
|
|
7
|
+
const DEFAULT_GUIDELINES = ["Available profiles:\n{{profiles}}"];
|
|
20
8
|
|
|
21
9
|
export function toolText(config: Config, profiles: Profile[]) {
|
|
22
10
|
const visible = profiles.filter((profile) => !profile.disableModelInvocation);
|
|
@@ -24,8 +12,8 @@ export function toolText(config: Config, profiles: Profile[]) {
|
|
|
24
12
|
return {
|
|
25
13
|
description: config.prompts.description ?? DEFAULT_DESCRIPTION,
|
|
26
14
|
promptSnippet: config.prompts.snippet ?? DEFAULT_SNIPPET,
|
|
27
|
-
promptGuidelines: (config.prompts.guidelines ?? DEFAULT_GUIDELINES).map(
|
|
28
|
-
|
|
15
|
+
promptGuidelines: (config.prompts.guidelines ?? DEFAULT_GUIDELINES).map(
|
|
16
|
+
(line) => `${config.toolName}: ${line.replaceAll("{{profiles}}", profileLines)}`,
|
|
29
17
|
),
|
|
30
18
|
};
|
|
31
19
|
}
|