billion-context-pi 0.1.25 → 0.1.27
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 +10 -6
- package/README.zh-CN.md +10 -6
- package/dist/delegate-tool.d.ts +19 -2
- package/dist/index.js +53 -14
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +1 -0
- package/dist/tool-guardrails.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,17 +86,21 @@ This has two practical implications:
|
|
|
86
86
|
|
|
87
87
|
### acp_delegate — clean-context delegation
|
|
88
88
|
|
|
89
|
-
Hand a self-contained task to a fresh pi process running in a clean context. Five built-in roles, each with a
|
|
89
|
+
Hand a self-contained task to a fresh pi process running in a clean context. Five built-in roles, each with a system prompt and a **soft tool guardrail**:
|
|
90
90
|
|
|
91
91
|
| Role | Tools | Best for |
|
|
92
92
|
|------|-------|----------|
|
|
93
|
-
| `reviewer` | read, bash | Read-only code review (bugs, risks, file:line) |
|
|
94
|
-
| `researcher` | read, bash | Read-only codebase investigation |
|
|
93
|
+
| `reviewer` | read, bash, grep, find, ls + ACP | Read-only code review (bugs, risks, file:line) |
|
|
94
|
+
| `researcher` | read, bash, grep, find, ls + ACP | Read-only codebase investigation |
|
|
95
95
|
| `worker` | read, edit, write, bash | Make code changes |
|
|
96
|
-
| `planner` | read, bash | Analyze + propose a step-by-step plan |
|
|
97
|
-
| `oracle` | read, bash | Answer questions / advise |
|
|
96
|
+
| `planner` | read, bash, grep, find, ls + ACP | Analyze + propose a step-by-step plan |
|
|
97
|
+
| `oracle` | read, bash, grep, find, ls + ACP | Answer questions / advise |
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Read-only roles (reviewer, researcher, planner, oracle) receive a restricted tool allowlist (`read, bash, grep, find, ls`) plus ACP context tools (`compress, decompress, search_context, acp_status`) so they can manage their own context. This prevents accidental file modifications, but `bash` can bypass it - **it is a guardrail, not a security boundary**.
|
|
100
|
+
|
|
101
|
+
Worker runs on Pi's full default toolset - no `--tools` allowlist is applied, so any loaded extension or custom tools (e.g. ACP, LSP, MCP) remain available. This keeps primary-task delegation fully capable. The `read, edit, write, bash` listing above reflects core tools only.
|
|
102
|
+
|
|
103
|
+
The full delegate result is saved to a file (`/tmp/acp-delegate/<runId>.out`); the tool result and injected notification carry only the **task title + file path** (no preview) - use `read` for the details. This keeps the parent context lean.
|
|
100
104
|
|
|
101
105
|
- **Interactive (TUI) & RPC modes**: `async:true` (default) runs the child in the background; a short completion notification is injected into the chat when it finishes.
|
|
102
106
|
- **Print / JSON modes** (`pi -p`, SDK): `async:true` auto-downgrades to **synchronous** — the result returns as the tool result in the same turn (the parent exits after one turn, so background injection would be lost).
|
package/README.zh-CN.md
CHANGED
|
@@ -85,17 +85,21 @@ billion-context 通过拦截 Pi 的 `context` 事件接管上下文管理。**Pi
|
|
|
85
85
|
|
|
86
86
|
### acp_delegate — 干净上下文委派
|
|
87
87
|
|
|
88
|
-
把一个自包含的任务交给一个运行在干净上下文中的新 pi
|
|
88
|
+
把一个自包含的任务交给一个运行在干净上下文中的新 pi 进程。五个内置角色,各自有系统提示和**软工具护栏**:
|
|
89
89
|
|
|
90
90
|
| 角色 | 工具 | 适用场景 |
|
|
91
91
|
|------|------|----------|
|
|
92
|
-
| `reviewer` | read, bash | 只读代码审查(bug、风险、file:line) |
|
|
93
|
-
| `researcher` | read, bash | 只读代码库调研 |
|
|
92
|
+
| `reviewer` | read, bash, grep, find, ls + ACP | 只读代码审查(bug、风险、file:line) |
|
|
93
|
+
| `researcher` | read, bash, grep, find, ls + ACP | 只读代码库调研 |
|
|
94
94
|
| `worker` | read, edit, write, bash | 修改代码 |
|
|
95
|
-
| `planner` | read, bash | 分析 + 提出分步计划 |
|
|
96
|
-
| `oracle` | read, bash | 回答问题 / 建议 |
|
|
95
|
+
| `planner` | read, bash, grep, find, ls + ACP | 分析 + 提出分步计划 |
|
|
96
|
+
| `oracle` | read, bash, grep, find, ls + ACP | 回答问题 / 建议 |
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
只读角色(reviewer、researcher、planner、oracle)获得受限工具白名单(`read, bash, grep, find, ls`)+ ACP 上下文工具(`compress, decompress, search_context, acp_status`),以便管理自己的上下文。这能防止意外修改文件,但 `bash` 可绕过 - **这是护栏,不是安全边界**。
|
|
99
|
+
|
|
100
|
+
Worker 运行在 Pi 的完整默认工具集上 - 不应用 `--tools` 白名单,因此任何已加载的扩展或自定义工具(如 ACP、LSP、MCP)保持可用。这确保主任务委派能力完整。上表中的 `read, edit, write, bash` 仅反映核心工具。
|
|
101
|
+
|
|
102
|
+
委派的完整结果保存到文件(`/tmp/acp-delegate/<runId>.out`);工具结果和注入通知只携带**任务标题 + 文件路径**(无预览)- 需要细节时用 `read` 读取。这让父上下文保持精简。
|
|
99
103
|
|
|
100
104
|
- **交互(TUI)与 RPC 模式**:`async:true`(默认)在后台运行子进程;完成时一条简短通知注入到聊天框。
|
|
101
105
|
- **Print / JSON 模式**(`pi -p`、SDK):`async:true` 自动降级为**同步** — 结果在同一轮作为工具结果返回(父进程一轮后即退出,后台注入会丢失)。
|
package/dist/delegate-tool.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
/** Snapshot of currently-running delegate runs, for the TUI status widget. */
|
|
4
4
|
export declare function runningRunsSnapshot(): {
|
|
5
5
|
runId: string;
|
|
@@ -14,6 +14,7 @@ declare const DelegateParams: Type.TObject<{
|
|
|
14
14
|
model: Type.TOptional<Type.TString>;
|
|
15
15
|
async: Type.TOptional<Type.TBoolean>;
|
|
16
16
|
}>;
|
|
17
|
+
type DelegateArgs = Static<typeof DelegateParams>;
|
|
17
18
|
declare const CancelParams: Type.TObject<{
|
|
18
19
|
runId: Type.TString;
|
|
19
20
|
}>;
|
|
@@ -22,6 +23,22 @@ declare const WaitParams: Type.TObject<{
|
|
|
22
23
|
timeout: Type.TOptional<Type.TInteger>;
|
|
23
24
|
}>;
|
|
24
25
|
export declare function makeDelegateTool(pi: ExtensionAPI): ToolDefinition<typeof DelegateParams>;
|
|
26
|
+
/** If the delegate already delivered its result via a system notification
|
|
27
|
+
* (the close handler injected before this wait was called), return a short
|
|
28
|
+
* "already delivered" message pointing at the result file, so the model
|
|
29
|
+
* never sees the same result twice (once via the injected notification,
|
|
30
|
+
* once via this tool result). Returns null when the run was NOT injected,
|
|
31
|
+
* in which case the caller delivers the full payload via formatRunResult(). */
|
|
32
|
+
export declare function injectedWaitMessage(run: {
|
|
33
|
+
injected?: boolean;
|
|
34
|
+
result?: {
|
|
35
|
+
file: string;
|
|
36
|
+
};
|
|
37
|
+
}, runId: string, remainingLine: string): string | null;
|
|
25
38
|
export declare function makeDelegateWaitTool(_pi: ExtensionAPI): ToolDefinition<typeof WaitParams>;
|
|
26
39
|
export declare function makeDelegateCancelTool(_pi: ExtensionAPI): ToolDefinition<typeof CancelParams>;
|
|
40
|
+
export declare function buildChildArgs(args: DelegateArgs, rolePrompt: string, ctx: ExtensionContext): Promise<{
|
|
41
|
+
cliArgs: string[];
|
|
42
|
+
tmpDir: string;
|
|
43
|
+
}>;
|
|
27
44
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -2429,9 +2429,17 @@ ${argStr}` : argStr || textParts;
|
|
|
2429
2429
|
}
|
|
2430
2430
|
return [{ id, role: "assistant", contentType: "text", text: extractText(msg.content) }];
|
|
2431
2431
|
}
|
|
2432
|
-
const customText = extractText(msg.content);
|
|
2432
|
+
const customText = extractText(msg.content) || fallbackText(msg);
|
|
2433
2433
|
return customText.length > 0 ? [{ id, role: "user", contentType: "text", text: customText }] : [];
|
|
2434
2434
|
}
|
|
2435
|
+
function fallbackText(msg) {
|
|
2436
|
+
const parts = [];
|
|
2437
|
+
if (msg.command) parts.push(`$ ${msg.command}`);
|
|
2438
|
+
const out = extractText(msg.output);
|
|
2439
|
+
if (out) parts.push(out);
|
|
2440
|
+
if (msg.summary) parts.push(msg.summary);
|
|
2441
|
+
return parts.join("\n").trim();
|
|
2442
|
+
}
|
|
2435
2443
|
function stringifyArgs(args) {
|
|
2436
2444
|
if (!args) return "";
|
|
2437
2445
|
if (typeof args === "string") return args;
|
|
@@ -2621,6 +2629,12 @@ function mergeInitialState(parsed) {
|
|
|
2621
2629
|
}
|
|
2622
2630
|
|
|
2623
2631
|
// src/runtime.ts
|
|
2632
|
+
function readContextEntries(sm) {
|
|
2633
|
+
const source = sm;
|
|
2634
|
+
if (typeof source.buildContextEntries === "function") return source.buildContextEntries();
|
|
2635
|
+
if (typeof source.getBranch === "function") return source.getBranch();
|
|
2636
|
+
return [];
|
|
2637
|
+
}
|
|
2624
2638
|
function createRuntime(adapter) {
|
|
2625
2639
|
const core = createCore({ countTokens: defaultCountTokens });
|
|
2626
2640
|
const store = new SessionStateStore();
|
|
@@ -2652,7 +2666,7 @@ function createRuntime(adapter) {
|
|
|
2652
2666
|
async function stateFor(ctx) {
|
|
2653
2667
|
const sm = ctx.sessionManager;
|
|
2654
2668
|
const state = await store.load(sm.getSessionFile() ?? void 0, sm.getSessionId());
|
|
2655
|
-
const entries = sm
|
|
2669
|
+
const entries = readContextEntries(sm);
|
|
2656
2670
|
return { state, coreMessages: entriesToCoreMessages(entries), entries };
|
|
2657
2671
|
}
|
|
2658
2672
|
async function save(state, ctx) {
|
|
@@ -7649,15 +7663,19 @@ var MAX_DEPTH = 2;
|
|
|
7649
7663
|
var SYNC_TIMEOUT_MS = 5 * 6e4;
|
|
7650
7664
|
var RESULT_SUMMARY_CHARS = 500;
|
|
7651
7665
|
var OUT_DIR = join4(tmpdir2(), "acp-delegate");
|
|
7666
|
+
var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
|
|
7667
|
+
var RESTRICTED_TOOLS = "read,bash,grep,find,ls";
|
|
7652
7668
|
var AGENTS = {
|
|
7653
7669
|
reviewer: {
|
|
7654
|
-
tools:
|
|
7670
|
+
tools: RESTRICTED_TOOLS,
|
|
7671
|
+
restricted: true,
|
|
7655
7672
|
prompt: `You are a senior code reviewer with read-only access.
|
|
7656
7673
|
Read the given code and report: bugs, security/safety risks, correctness issues, and concrete improvement suggestions.
|
|
7657
7674
|
Be specific \u2014 cite file:line for every finding. Do NOT modify any files; only read and report.`
|
|
7658
7675
|
},
|
|
7659
7676
|
researcher: {
|
|
7660
|
-
tools:
|
|
7677
|
+
tools: RESTRICTED_TOOLS,
|
|
7678
|
+
restricted: true,
|
|
7661
7679
|
prompt: `You are a code researcher with read-only access.
|
|
7662
7680
|
Investigate the codebase to answer the question thoroughly. Report findings with exact file:line references, function/type signatures, and relevant code snippets.
|
|
7663
7681
|
Do NOT modify any files; only read and report.`
|
|
@@ -7669,13 +7687,15 @@ Make exactly the requested code changes \u2014 minimal, focused, following exist
|
|
|
7669
7687
|
After editing, briefly summarize what you changed and why. Do not expand scope.`
|
|
7670
7688
|
},
|
|
7671
7689
|
planner: {
|
|
7672
|
-
tools:
|
|
7690
|
+
tools: RESTRICTED_TOOLS,
|
|
7691
|
+
restricted: true,
|
|
7673
7692
|
prompt: `You are a technical planner with read-only access.
|
|
7674
7693
|
Analyze the task and produce a concrete, ordered step-by-step implementation plan with rationale for each step.
|
|
7675
7694
|
Cite file:line for code you reference. Do NOT modify any files; only read and propose.`
|
|
7676
7695
|
},
|
|
7677
7696
|
oracle: {
|
|
7678
|
-
tools:
|
|
7697
|
+
tools: RESTRICTED_TOOLS,
|
|
7698
|
+
restricted: true,
|
|
7679
7699
|
prompt: `You are an expert advisor with read-only access.
|
|
7680
7700
|
Answer the question concisely with clear reasoning. Cite file:line when referencing code. Do NOT modify any files.`
|
|
7681
7701
|
}
|
|
@@ -7731,7 +7751,7 @@ var agentListLine = (name) => {
|
|
|
7731
7751
|
planner: "analyze + propose step-by-step plan (read-only)",
|
|
7732
7752
|
oracle: "answer questions / advise (read-only)"
|
|
7733
7753
|
};
|
|
7734
|
-
return ` \u2022 ${name}
|
|
7754
|
+
return ` \u2022 ${name} - ${blurb[name]} [tools: ${def.tools}${def.restricted ? " + ACP context tools" : ""}]`;
|
|
7735
7755
|
};
|
|
7736
7756
|
function makeDelegateTool(pi) {
|
|
7737
7757
|
return {
|
|
@@ -7772,6 +7792,12 @@ function remainingLineForWait(selfRunId) {
|
|
|
7772
7792
|
const remaining = Array.from(runs.values()).filter((r) => r.status === "running" && r.runId !== selfRunId).length;
|
|
7773
7793
|
return remaining > 0 ? ` ${remaining} delegate${remaining === 1 ? " is" : "s are"} still running.` : "";
|
|
7774
7794
|
}
|
|
7795
|
+
function injectedWaitMessage(run, runId, remainingLine) {
|
|
7796
|
+
if (!run.injected) return null;
|
|
7797
|
+
const file = run.result?.file;
|
|
7798
|
+
const fileLine = file ? ` If you need details, read the result file: \`${file}\`.` : "";
|
|
7799
|
+
return `Delegate \`${runId}\` already delivered its result via a system notification when it finished \u2014 no need to wait on it again.${remainingLine}${fileLine}`;
|
|
7800
|
+
}
|
|
7775
7801
|
function makeDelegateWaitTool(_pi) {
|
|
7776
7802
|
return {
|
|
7777
7803
|
name: "acp_delegate_wait",
|
|
@@ -7794,6 +7820,11 @@ function makeDelegateWaitTool(_pi) {
|
|
|
7794
7820
|
return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` was cancelled (no result).${remainingLineForWait(args.runId)}` }] };
|
|
7795
7821
|
}
|
|
7796
7822
|
if (run.status !== "running") {
|
|
7823
|
+
const dedup = injectedWaitMessage(run, args.runId, remainingLineForWait(args.runId));
|
|
7824
|
+
if (dedup) {
|
|
7825
|
+
run.consumed = true;
|
|
7826
|
+
return { details: void 0, content: [{ type: "text", text: dedup }] };
|
|
7827
|
+
}
|
|
7797
7828
|
run.consumed = true;
|
|
7798
7829
|
if (!run.result) {
|
|
7799
7830
|
return { details: void 0, content: [{ type: "text", text: `Delegate \`${args.runId}\` finished but no result is available (persist error).` }] };
|
|
@@ -7958,6 +7989,7 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
7958
7989
|
return;
|
|
7959
7990
|
}
|
|
7960
7991
|
const injected = injectResult(pi, args.agent, runId, args.task, code, file2);
|
|
7992
|
+
run.injected = injected;
|
|
7961
7993
|
debug.event("delegate-done", { runId, code, status: run.status, injected, outLen: output.length, file: file2 });
|
|
7962
7994
|
delegateStatusWidget.poke();
|
|
7963
7995
|
}).catch((err) => {
|
|
@@ -8003,6 +8035,11 @@ async function buildChildArgs(args, rolePrompt, ctx) {
|
|
|
8003
8035
|
|
|
8004
8036
|
Complete the task below.`, "utf8");
|
|
8005
8037
|
const cliArgs = ["-p", "--no-session", "--append-system-prompt", promptFile];
|
|
8038
|
+
const agentDef = AGENTS[args.agent];
|
|
8039
|
+
if (agentDef?.restricted) {
|
|
8040
|
+
const merged = [.../* @__PURE__ */ new Set([...agentDef.tools.split(",").map((s) => s.trim()), ...ACP_TOOLS])];
|
|
8041
|
+
cliArgs.push("--tools", merged.join(","));
|
|
8042
|
+
}
|
|
8006
8043
|
if (args.model && args.model.includes("/")) {
|
|
8007
8044
|
const [providerId, ...rest] = args.model.split("/");
|
|
8008
8045
|
const modelId = rest.join("/");
|
|
@@ -8209,7 +8246,7 @@ async function statusReport(runtime, ctx) {
|
|
|
8209
8246
|
const activeBlocksList = state.blocks.filter((b) => b.active);
|
|
8210
8247
|
const totalBlocksList = state.blocks;
|
|
8211
8248
|
const lines = [];
|
|
8212
|
-
const versionStr = "0.1.
|
|
8249
|
+
const versionStr = "0.1.27" ? `billion-context-pi@${"0.1.27"}` : "";
|
|
8213
8250
|
lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
|
|
8214
8251
|
lines.push("\u2502 ACP Context Analysis \u2502");
|
|
8215
8252
|
lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
|
|
@@ -8353,9 +8390,11 @@ When a background delegate finishes, an automated completion notification is inj
|
|
|
8353
8390
|
|
|
8354
8391
|
// src/tool-guardrails.ts
|
|
8355
8392
|
import {
|
|
8356
|
-
isBashToolResult,
|
|
8357
8393
|
isToolCallEventType
|
|
8358
8394
|
} from "@earendil-works/pi-coding-agent";
|
|
8395
|
+
function isBashToolResult(e) {
|
|
8396
|
+
return e.toolName === "bash";
|
|
8397
|
+
}
|
|
8359
8398
|
function resolveBashTimeout(input, defaultTimeout) {
|
|
8360
8399
|
if (input.timeout !== void 0) return void 0;
|
|
8361
8400
|
const d = defaultTimeout ?? DEFAULT_TOOL_BASH_TIMEOUT;
|
|
@@ -8567,7 +8606,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
8567
8606
|
const data = await res.json();
|
|
8568
8607
|
const latest = data.version;
|
|
8569
8608
|
if (!latest) return;
|
|
8570
|
-
const current = runtimeVersion ?? "0.1.
|
|
8609
|
+
const current = runtimeVersion ?? "0.1.27";
|
|
8571
8610
|
debug.event("update-check", {
|
|
8572
8611
|
current,
|
|
8573
8612
|
latest,
|
|
@@ -8602,7 +8641,7 @@ import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename
|
|
|
8602
8641
|
import { existsSync } from "fs";
|
|
8603
8642
|
import { homedir as homedir4 } from "os";
|
|
8604
8643
|
import { join as join6 } from "path";
|
|
8605
|
-
var
|
|
8644
|
+
var ACP_TOOLS2 = ["compress", "decompress", "search_context", "acp_status"];
|
|
8606
8645
|
var BUILTIN_DEFAULT_TOOLS = {
|
|
8607
8646
|
advisor: ["read", "grep", "find", "ls", "bash", "intercom"],
|
|
8608
8647
|
"context-builder": ["read", "grep", "find", "ls", "bash", "write", "web_search", "intercom"],
|
|
@@ -8622,9 +8661,9 @@ function resolveAgentDir() {
|
|
|
8622
8661
|
}
|
|
8623
8662
|
function desiredTools(existing, name) {
|
|
8624
8663
|
const base = Array.isArray(existing?.tools) && existing.tools.length > 0 ? [...existing.tools] : [...BUILTIN_DEFAULT_TOOLS[name] ?? []];
|
|
8625
|
-
const hasAll =
|
|
8664
|
+
const hasAll = ACP_TOOLS2.every((t) => base.includes(t));
|
|
8626
8665
|
if (hasAll) return { tools: base, changed: false };
|
|
8627
|
-
for (const t of
|
|
8666
|
+
for (const t of ACP_TOOLS2) if (!base.includes(t)) base.push(t);
|
|
8628
8667
|
return { tools: base, changed: true };
|
|
8629
8668
|
}
|
|
8630
8669
|
async function ensureSubagentAcpTools(settingsPath) {
|
|
@@ -8695,7 +8734,7 @@ async function ensureSubagentAcpTools(settingsPath) {
|
|
|
8695
8734
|
if (typeof v !== "object" || v === null) throw new Error("agentOverrides missing after write");
|
|
8696
8735
|
for (const name of Object.keys(BUILTIN_DEFAULT_TOOLS)) {
|
|
8697
8736
|
const tools = v[name]?.tools;
|
|
8698
|
-
if (!Array.isArray(tools) || !
|
|
8737
|
+
if (!Array.isArray(tools) || !ACP_TOOLS2.every((t) => tools.includes(t))) {
|
|
8699
8738
|
throw new Error(`${name} missing ACP tools after write`);
|
|
8700
8739
|
}
|
|
8701
8740
|
}
|