billion-context-pi 0.1.75 → 0.1.76-pr.513.331
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 -7
- package/README.zh-CN.md +10 -7
- package/dist/{chunk-FTCH7KHF.js → chunk-44F6OZJV.js} +2 -2
- package/dist/config.d.ts +11 -0
- package/dist/contract.js +1 -1
- package/dist/delegate-tool.d.ts +16 -0
- package/dist/index.js +153 -27
- package/dist/index.js.map +1 -1
- package/dist/tool-pair-sanitizer.d.ts +8 -0
- package/dist/user-config.d.ts +6 -1
- package/package.json +2 -2
- /package/dist/{chunk-FTCH7KHF.js.map → chunk-44F6OZJV.js.map} +0 -0
package/README.md
CHANGED
|
@@ -10,6 +10,14 @@ The model decides <em>when</em> and <em>what</em> to compress — not a hard lim
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## Community
|
|
14
|
+
|
|
15
|
+
QQ Group:
|
|
16
|
+
1056132097 (full)
|
|
17
|
+
1108730198 (open)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
13
21
|
## 📄 Paper / Preprint
|
|
14
22
|
|
|
15
23
|
- **[Model-Driven Incremental Hierarchical Compression: Training-Free Multi-Generational Context Management for Long-Lived Coding Agents](./paper/model-driven-incremental-hierarchical-compression-training-free-multi-generational-context-management-for-long-lived-coding-agents.md)** (English, v0.2)
|
|
@@ -34,12 +42,6 @@ A production-scale longitudinal study: 4.5 months, three hosts, 174,327 model ca
|
|
|
34
42
|
|
|
35
43
|
> **Host support:** this plugin is for **Pi**. It does **not** support **OMP (oh-my-pi)** — on an OMP host it refuses to run. OMP users: use [billion-context](https://github.com/ranxianglei/billion-context) instead (`bili omp`, built-in plugin). Full client → package table: see [Which do I need?](#which-do-i-need); OMP details: [docs/omp.md](./docs/omp.md).
|
|
36
44
|
|
|
37
|
-
## Community
|
|
38
|
-
|
|
39
|
-
Discussion, help, and updates on QQ — one group covers all three projects (`billion-context`, `billion-context-pi`, `opencode-acp`):
|
|
40
|
-
|
|
41
|
-
**QQ Group: 1056132097**
|
|
42
|
-
|
|
43
45
|
## Why?
|
|
44
46
|
|
|
45
47
|
When conversations get long, the model runs out of context. Most tools hard-truncate — silently dropping earlier messages. **billion-context** gives the model a `compress` tool: the LLM decides **when** and **what** to compress into high-fidelity summaries, preserving critical details (file paths, decisions, error strings) while reclaiming context space.
|
|
@@ -251,11 +253,12 @@ The model receives detailed guidance (in its system prompt) on **when** to compr
|
|
|
251
253
|
|
|
252
254
|
### What gets protected
|
|
253
255
|
|
|
254
|
-
billion-context protects
|
|
256
|
+
billion-context protects four categories of content from compression:
|
|
255
257
|
|
|
256
258
|
1. **Always-protected tools** — `compress` calls are hard-protected (they're load-bearing metadata; compressing them breaks decompress and the "summary is historical" contract).
|
|
257
259
|
2. **Soft recent-zone** — the last N messages (default 5) and last ~5K tokens are soft-protected so the model keeps its working set. Tool results from `decompress`, `search_context`, `read`, and `bash` are **excluded** from this zone: they're large and meant to be compressible once consumed, so they don't eat the protected budget.
|
|
258
260
|
3. **Last user message** — always protected (user intent must survive).
|
|
261
|
+
4. **User-configured tool protection** — `protectedTools` / `protectedLatestTools` in `acp.json` hard-exclude matched tool calls+results (full history vs. latest-only; see [CONFIGURATION.md](./CONFIGURATION.md)).
|
|
259
262
|
|
|
260
263
|
## Session storage & migration
|
|
261
264
|
|
package/README.zh-CN.md
CHANGED
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## 社区
|
|
12
|
+
|
|
13
|
+
QQ群:
|
|
14
|
+
1056132097(已满)
|
|
15
|
+
1108730198(未满)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
11
19
|
## 📄 论文 / 预印本
|
|
12
20
|
|
|
13
21
|
- **[模型驱动的分层增量压缩:面向长寿命编码 Agent 的免训练多代上下文管理](./paper/模型驱动的分层增量压缩-免训练多代上下文管理.md)**(中文版,v0.2)
|
|
@@ -32,12 +40,6 @@
|
|
|
32
40
|
|
|
33
41
|
> **宿主支持:** 本插件面向 **Pi**。它**不支持 OMP(oh-my-pi)** —— 在 OMP 宿主上会拒绝运行。OMP 用户请直接改用 [billion-context](https://github.com/ranxianglei/billion-context)(启动命令 bili omp);其他客户端的完整对照见[该选哪个?](#该选哪个)。OMP 详细说明:[docs/omp.zh-CN.md](./docs/omp.zh-CN.md)。
|
|
34
42
|
|
|
35
|
-
## 社区
|
|
36
|
-
|
|
37
|
-
交流、求助与更新都在 QQ——同一个群覆盖三个项目(`billion-context`、`billion-context-pi`、`opencode-acp`):
|
|
38
|
-
|
|
39
|
-
**QQ 群:1056132097**
|
|
40
|
-
|
|
41
43
|
## 为什么选择 billion-context
|
|
42
44
|
|
|
43
45
|
当对话变长,模型的上下文会耗尽。多数工具采用硬截断 —— 静默丢弃早期消息。**billion-context** 把 `compress` 工具交给模型:由 LLM 决定**何时**压缩、压缩**什么**,将内容压缩成高保真摘要,在回收上下文空间的同时保留关键细节(文件路径、决策、错误字符串)。
|
|
@@ -240,11 +242,12 @@ grep '\[error\]' ~/.pi/acp.log # 汇总所有记录的失败
|
|
|
240
242
|
|
|
241
243
|
### 哪些内容会被保护
|
|
242
244
|
|
|
243
|
-
billion-context
|
|
245
|
+
billion-context 保护四类内容不被压缩:
|
|
244
246
|
|
|
245
247
|
1. **永久保护的工具** — `compress` 调用被硬保护(它们是承载关键元数据的;压缩它们会破坏 decompress 和"摘要是历史"的契约)。
|
|
246
248
|
2. **软近期区** — 最后 N 条消息(默认 5)和最后约 5K token 被软保护,让模型保留工作集。来自 `decompress`、`search_context`、`read`、`bash` 的工具结果被**排除**出此区:它们体量大、消费后就该能压缩,所以不该占用保护预算。
|
|
247
249
|
3. **最后一条用户消息** — 始终保护(用户意图必须存活)。
|
|
250
|
+
4. **用户配置的工具保护** — `acp.json` 中的 `protectedTools` / `protectedLatestTools` 硬排除匹配的工具 call+result(全历史 vs 仅最近一次;见 [CONFIGURATION.zh-CN.md](./CONFIGURATION.zh-CN.md))。
|
|
248
251
|
|
|
249
252
|
## 会话存储与迁移
|
|
250
253
|
|
|
@@ -7,7 +7,7 @@ var __export = (target, all) => {
|
|
|
7
7
|
// src/contract.ts
|
|
8
8
|
var SIDECAR_SCHEMA_VERSION = 1;
|
|
9
9
|
function sidecarProducer() {
|
|
10
|
-
return `billion-context-pi@${true ? "0.1.
|
|
10
|
+
return `billion-context-pi@${true ? "0.1.76" : "dev"}`;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export {
|
|
@@ -15,4 +15,4 @@ export {
|
|
|
15
15
|
SIDECAR_SCHEMA_VERSION,
|
|
16
16
|
sidecarProducer
|
|
17
17
|
};
|
|
18
|
-
//# sourceMappingURL=chunk-
|
|
18
|
+
//# sourceMappingURL=chunk-44F6OZJV.js.map
|
package/dist/config.d.ts
CHANGED
|
@@ -216,7 +216,18 @@ export interface AdapterConfig {
|
|
|
216
216
|
/** When omitted, the adapter reads `ctx.model.contextWindow` live each turn.
|
|
217
217
|
* Set explicitly for tests/headless runs. */
|
|
218
218
|
modelContextLimit?: number;
|
|
219
|
+
/** Tool-name patterns (glob suffix allowed) whose EVERY call+result pair is
|
|
220
|
+
* hard-excluded from compression — matching refs render as BLOCKED in every
|
|
221
|
+
* view. Default: none. Intended for low-frequency high-value tools whose
|
|
222
|
+
* outputs are independent content (e.g. skill loads); do NOT use for chatty
|
|
223
|
+
* tools — protecting every instance grows context unboundedly (#639
|
|
224
|
+
* rationale). Settable via acp.json since #499. */
|
|
219
225
|
protectedTools?: string[];
|
|
226
|
+
/** Tool-name patterns (glob suffix allowed) whose LATEST call+result pair is
|
|
227
|
+
* hard-excluded from compression; older pairs remain compressible. Default:
|
|
228
|
+
* none. Intended for cumulative-snapshot tools where each call supersedes
|
|
229
|
+
* the last. Settable via acp.json since #499. */
|
|
230
|
+
protectedLatestTools?: string[];
|
|
220
231
|
preserveRecentMessages?: number;
|
|
221
232
|
/** Check npm for a newer billion-context-pi on startup and auto-install it. Default: true.
|
|
222
233
|
* Disable via `autoUpdate: false` or env `ACP_AUTO_UPDATE=0` to avoid all
|
package/dist/contract.js
CHANGED
package/dist/delegate-tool.d.ts
CHANGED
|
@@ -278,10 +278,26 @@ export declare function buildChildArgs(args: DelegateArgs, rolePrompt: string, c
|
|
|
278
278
|
useJsonStream: boolean;
|
|
279
279
|
sessionFile: string | null;
|
|
280
280
|
}>;
|
|
281
|
+
interface ChildResult {
|
|
282
|
+
code: number | null;
|
|
283
|
+
signal?: NodeJS.Signals | null;
|
|
284
|
+
stdout: string;
|
|
285
|
+
stderr: string;
|
|
286
|
+
timedOut: boolean;
|
|
287
|
+
}
|
|
281
288
|
/** Model-facing summary of the ACTIVE async watchdog limits — built from the
|
|
282
289
|
* resolved policy so it stays truthful when timeouts are customized or
|
|
283
290
|
* disabled via acp.json/env. */
|
|
284
291
|
export declare function asyncWatchdogDescription(overrideAsyncMs?: number | null): string;
|
|
292
|
+
export declare function formatSyncResult(agent: string, runId: string, task: string, r: ChildResult, file: string): string;
|
|
293
|
+
/** Exit-0 runs that produced no final reply text did not deliver anything
|
|
294
|
+
* actionable (#506): a thinking-only stop (thinking budget exhausted) or a
|
|
295
|
+
* silent no-op termination must not be announced as a plain completion — the
|
|
296
|
+
* parent would trust the notification and never re-dispatch the task. Real
|
|
297
|
+
* non-zero exits and watchdog kills (code null / timedOut) are handled by the
|
|
298
|
+
* existing failure paths and stay excluded here. */
|
|
299
|
+
export declare function genuineNoOutput(code: number | null, timedOut: boolean, output: string): boolean;
|
|
300
|
+
export declare const NO_FINAL_OUTPUT_BODY: string;
|
|
285
301
|
/** Watchdog/EOF finalize arrives with code === null (the child was killed or
|
|
286
302
|
* never exited). If a result was delivered (non-empty reply or stderr), the
|
|
287
303
|
* run counts as completed (0); otherwise it stays null = genuine failure. */
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
SIDECAR_SCHEMA_VERSION,
|
|
3
3
|
__export,
|
|
4
4
|
sidecarProducer
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-44F6OZJV.js";
|
|
6
6
|
|
|
7
7
|
// src/config-dir.ts
|
|
8
8
|
import * as piModule from "@earendil-works/pi-coding-agent";
|
|
@@ -170,6 +170,8 @@ var KNOWN = /* @__PURE__ */ new Set([
|
|
|
170
170
|
"debug",
|
|
171
171
|
"autoUpdate",
|
|
172
172
|
"modelContextLimit",
|
|
173
|
+
"protectedTools",
|
|
174
|
+
"protectedLatestTools",
|
|
173
175
|
"toolBashDefaultTimeout",
|
|
174
176
|
"toolOutputMaxBytes",
|
|
175
177
|
"delegate",
|
|
@@ -195,13 +197,27 @@ function pickKnown(parsed) {
|
|
|
195
197
|
return out;
|
|
196
198
|
}
|
|
197
199
|
function applyUserConfig(adapter, user) {
|
|
198
|
-
|
|
200
|
+
const result = {
|
|
199
201
|
...adapter,
|
|
200
202
|
...user,
|
|
201
203
|
coreOverrides: adapter.coreOverrides,
|
|
202
|
-
protectedTools: adapter.protectedTools,
|
|
203
204
|
preserveRecentMessages: adapter.preserveRecentMessages
|
|
204
205
|
};
|
|
206
|
+
for (const key of ["protectedTools", "protectedLatestTools"]) {
|
|
207
|
+
if (!(key in user)) continue;
|
|
208
|
+
const cleaned = cleanProtectionList(key, user[key]);
|
|
209
|
+
if (cleaned !== void 0) result[key] = cleaned;
|
|
210
|
+
else if (adapter[key] !== void 0) result[key] = adapter[key];
|
|
211
|
+
else delete result[key];
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
function cleanProtectionList(key, value) {
|
|
216
|
+
if (!Array.isArray(value) || value.length === 0 || !value.every((v2) => typeof v2 === "string" && v2.trim() !== "")) {
|
|
217
|
+
console.warn(`[bcp] acp.json "${key}" must be a non-empty array of non-empty strings \u2014 ignoring the value`);
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
return value.map((v2) => v2.trim());
|
|
205
221
|
}
|
|
206
222
|
|
|
207
223
|
// src/index.ts
|
|
@@ -2599,18 +2615,30 @@ function mergeRangesToThreshold(ranges, minChars) {
|
|
|
2599
2615
|
const result = [];
|
|
2600
2616
|
let batch = [];
|
|
2601
2617
|
let batchChars = 0;
|
|
2602
|
-
|
|
2603
|
-
batch.
|
|
2604
|
-
|
|
2605
|
-
if (batchChars >= minChars) {
|
|
2606
|
-
result.push(mergeBatch(batch));
|
|
2618
|
+
const closeBatch = () => {
|
|
2619
|
+
if (batch.length > 0) {
|
|
2620
|
+
if (batchChars >= minChars) result.push(mergeBatch(batch));
|
|
2607
2621
|
batch = [];
|
|
2608
2622
|
batchChars = 0;
|
|
2609
2623
|
}
|
|
2624
|
+
};
|
|
2625
|
+
for (const r of ranges) {
|
|
2626
|
+
const prev = batch[batch.length - 1];
|
|
2627
|
+
if (prev && prev.endIndex !== void 0 && r.startIndex !== void 0 && r.startIndex > prev.endIndex + 1) {
|
|
2628
|
+
closeBatch();
|
|
2629
|
+
}
|
|
2630
|
+
batch.push(r);
|
|
2631
|
+
batchChars += rangeChars(r);
|
|
2632
|
+
if (batchChars >= minChars) closeBatch();
|
|
2610
2633
|
}
|
|
2611
2634
|
if (batch.length > 0 && result.length > 0) {
|
|
2612
2635
|
const prev = result[result.length - 1];
|
|
2613
|
-
|
|
2636
|
+
const contiguous = prev.endIndex === void 0 || batch[0].startIndex === void 0 || batch[0].startIndex <= prev.endIndex + 1;
|
|
2637
|
+
if (contiguous) {
|
|
2638
|
+
result[result.length - 1] = mergeBatch([prev, ...batch]);
|
|
2639
|
+
} else if (batchChars >= minChars) {
|
|
2640
|
+
result.push(mergeBatch(batch));
|
|
2641
|
+
}
|
|
2614
2642
|
}
|
|
2615
2643
|
return result;
|
|
2616
2644
|
}
|
|
@@ -3127,10 +3155,29 @@ function applySingleRange(input) {
|
|
|
3127
3155
|
messages: input.messages,
|
|
3128
3156
|
state: input.state
|
|
3129
3157
|
});
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3158
|
+
const plainRange = resolved.boundaryKind !== "block";
|
|
3159
|
+
const liveCarrierIds = /* @__PURE__ */ new Set();
|
|
3160
|
+
if (plainRange) {
|
|
3161
|
+
for (const message of input.messages) {
|
|
3162
|
+
const carrierOf = message.summaryOfBlockId;
|
|
3163
|
+
if (carrierOf === void 0) continue;
|
|
3164
|
+
if (blockById(input.state, carrierOf)?.active) {
|
|
3165
|
+
liveCarrierIds.add(message.id);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
const adjustedIds = applyPairBoundaryAdjustments(resolved, input.messages);
|
|
3170
|
+
const skippedCarriers = adjustedIds.filter((id) => liveCarrierIds.has(id));
|
|
3171
|
+
if (skippedCarriers.length > 0) {
|
|
3172
|
+
warnings.push(
|
|
3173
|
+
`Excluded ${skippedCarriers.length} checkpoint message(s) ${skippedCarriers.join(
|
|
3174
|
+
", "
|
|
3175
|
+
)} from the compression range \u2014 they carry the visible summary of still-active block(s), which a plain message-ref range does not supersede. The checkpoints stay visible; to fold them, reference the block ids (bN..bM) instead.`
|
|
3176
|
+
);
|
|
3177
|
+
}
|
|
3178
|
+
const rangeMessageIds = adjustedIds.filter(
|
|
3179
|
+
(id) => !isSummaryMessageId(id) && !liveCarrierIds.has(id)
|
|
3180
|
+
);
|
|
3134
3181
|
if (rangeMessageIds.length > resolved.messageIds.length) {
|
|
3135
3182
|
const indexByMessageId = /* @__PURE__ */ new Map();
|
|
3136
3183
|
input.messages.forEach((m2, i) => indexByMessageId.set(m2.id, i));
|
|
@@ -5649,6 +5696,7 @@ function resolveConfig(adapter, liveContextLimit, provider, modelId) {
|
|
|
5649
5696
|
const limit = !Number.isNaN(envLimitNum) && envLimitNum > 0 ? envLimitNum : adapter.modelContextLimit && adapter.modelContextLimit > 0 ? adapter.modelContextLimit : liveContextLimit > 0 ? liveContextLimit : FALLBACK_LIMIT;
|
|
5650
5697
|
const config = defaultConfig(limit, {
|
|
5651
5698
|
protectedTools: adapter.protectedTools ?? [],
|
|
5699
|
+
protectedLatestTools: adapter.protectedLatestTools ?? [],
|
|
5652
5700
|
preserveRecentMessages: adapter.preserveRecentMessages ?? 5,
|
|
5653
5701
|
...adapter.coreOverrides
|
|
5654
5702
|
});
|
|
@@ -12729,7 +12777,7 @@ function resolveActivePack(adapter, cwd, provider, modelId, resolver) {
|
|
|
12729
12777
|
}
|
|
12730
12778
|
var HOST_VERSION = (() => {
|
|
12731
12779
|
try {
|
|
12732
|
-
return "0.1.
|
|
12780
|
+
return "0.1.76";
|
|
12733
12781
|
} catch {
|
|
12734
12782
|
return "dev";
|
|
12735
12783
|
}
|
|
@@ -14271,9 +14319,19 @@ async function runDelegate(pi, args, ctx, signal) {
|
|
|
14271
14319
|
run.exitCode = code;
|
|
14272
14320
|
run.exitSignal = signal2 ?? void 0;
|
|
14273
14321
|
const output = applier.getReplyText().trim();
|
|
14322
|
+
const noOutput = genuineNoOutput(code, Boolean(run.timedOut), output);
|
|
14274
14323
|
let body2;
|
|
14275
|
-
if (code === 0) {
|
|
14324
|
+
if (code === 0 && !noOutput) {
|
|
14276
14325
|
body2 = output || "(no output)";
|
|
14326
|
+
} else if (noOutput) {
|
|
14327
|
+
const parts = [NO_FINAL_OUTPUT_BODY];
|
|
14328
|
+
const err = stderrText.trim();
|
|
14329
|
+
if (err) parts.push(`stderr:
|
|
14330
|
+
${err}`);
|
|
14331
|
+
const tail = activityStream ? await readActivityTail(activityFile) : "";
|
|
14332
|
+
if (tail) parts.push(`last activity (full log: \`${activityFile}\`)
|
|
14333
|
+
${tail}`);
|
|
14334
|
+
body2 = parts.join("\n\n");
|
|
14277
14335
|
} else {
|
|
14278
14336
|
const parts = [];
|
|
14279
14337
|
const err = stderrText.trim();
|
|
@@ -14302,13 +14360,22 @@ ${output}`);
|
|
|
14302
14360
|
try {
|
|
14303
14361
|
const file2 = replyFile;
|
|
14304
14362
|
if (output === "") {
|
|
14305
|
-
|
|
14306
|
-
|
|
14363
|
+
if (noOutput) {
|
|
14364
|
+
const err = stderrText.trim();
|
|
14365
|
+
await appendFile(file2, `${NO_FINAL_OUTPUT_BODY}
|
|
14366
|
+
${err ? `
|
|
14367
|
+
${err}` : ""}
|
|
14368
|
+
`);
|
|
14369
|
+
} else {
|
|
14370
|
+
const fallback = stderrText.trim();
|
|
14371
|
+
await appendFile(file2, fallback ? `${fallback}
|
|
14307
14372
|
` : "(no output)\n");
|
|
14373
|
+
}
|
|
14308
14374
|
}
|
|
14309
14375
|
const effectiveCode = effectiveExitCode(code, output, stderrText);
|
|
14310
14376
|
run.result = { code, file: file2, body: body2 };
|
|
14311
|
-
run.status = effectiveCode === 0 ? "completed" : "failed";
|
|
14377
|
+
run.status = effectiveCode === 0 && !noOutput ? "completed" : "failed";
|
|
14378
|
+
if (noOutput) logWarn("delegate", { event: "no-final-output", runId, agent: args.agent, code });
|
|
14312
14379
|
run.finishedAt = Date.now();
|
|
14313
14380
|
if (run.waiter) {
|
|
14314
14381
|
debug.event("delegate-done", { runId, code, status: run.status, injected: false, via: "wait", outLen: output.length, file: file2 });
|
|
@@ -14400,7 +14467,19 @@ ${output}`);
|
|
|
14400
14467
|
});
|
|
14401
14468
|
const result = await waitForChild(child, signal, effectiveSyncMs);
|
|
14402
14469
|
void cleanupTmp(tmpDir);
|
|
14403
|
-
const
|
|
14470
|
+
const syncNoOutput = genuineNoOutput(result.code, result.timedOut, result.stdout);
|
|
14471
|
+
let body;
|
|
14472
|
+
if (result.timedOut || result.code !== 0) {
|
|
14473
|
+
body = result.stderr.trim() || "(no stderr)";
|
|
14474
|
+
} else if (syncNoOutput) {
|
|
14475
|
+
const err = result.stderr.trim();
|
|
14476
|
+
body = err ? `${NO_FINAL_OUTPUT_BODY}
|
|
14477
|
+
|
|
14478
|
+
stderr:
|
|
14479
|
+
${err}` : NO_FINAL_OUTPUT_BODY;
|
|
14480
|
+
} else {
|
|
14481
|
+
body = result.stdout;
|
|
14482
|
+
}
|
|
14404
14483
|
const file = await persistResult(runId, body);
|
|
14405
14484
|
return formatSyncResult(args.agent, runId, taskText, result, file);
|
|
14406
14485
|
}
|
|
@@ -14528,14 +14607,19 @@ function asyncWatchdogDescription(overrideAsyncMs) {
|
|
|
14528
14607
|
return `A watchdog force-finishes a hung run: ${joined} \u2014 the result reflects whatever was produced.`;
|
|
14529
14608
|
}
|
|
14530
14609
|
function formatSyncResult(agent, runId, task, r, file) {
|
|
14531
|
-
const
|
|
14610
|
+
const noOutput = genuineNoOutput(r.code, r.timedOut, r.stdout);
|
|
14611
|
+
const status = r.timedOut ? "timed out" : r.code === 0 && !noOutput ? "completed" : "FAILED \u26A0\uFE0F";
|
|
14532
14612
|
const header = `Delegate **${agent}** ${status} (runId \`${runId}\`, ${exitLabel(r.code, r.signal)}).`;
|
|
14533
|
-
if (r.code === 0 && !r.timedOut) {
|
|
14613
|
+
if (r.code === 0 && !r.timedOut && !noOutput) {
|
|
14534
14614
|
return formatPayload(header, file, task);
|
|
14535
14615
|
}
|
|
14536
|
-
const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
|
|
14616
|
+
const body = r.timedOut ? "(timed out)" : r.code === 0 ? NO_FINAL_OUTPUT_BODY : r.stderr.trim() || "(no stderr)";
|
|
14537
14617
|
return formatPayload(header, file, task, body);
|
|
14538
14618
|
}
|
|
14619
|
+
function genuineNoOutput(code, timedOut, output) {
|
|
14620
|
+
return !timedOut && code === 0 && output.trim().length === 0;
|
|
14621
|
+
}
|
|
14622
|
+
var NO_FINAL_OUTPUT_BODY = "child exited 0 but produced NO final reply text \u2014 likely a thinking-only stop (thinking budget exhausted) or a no-op termination. Treat the task as NOT delivered: inspect the activity log and session file, then re-dispatch if needed.";
|
|
14539
14623
|
function effectiveExitCode(code, output, stderr) {
|
|
14540
14624
|
return code ?? (output || stderr ? 0 : null);
|
|
14541
14625
|
}
|
|
@@ -19477,7 +19561,7 @@ async function statusReport(runtime, ctx) {
|
|
|
19477
19561
|
const sentTokens = estimateTokens(coreMessages, coveredIds, imageTokens) + systemPromptTokens;
|
|
19478
19562
|
const viewSentTokens = adjustedTokenCount(runtime.core, coreMessages, state, config, sentTokens, imageTokens, systemPromptTokens);
|
|
19479
19563
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: anchorStale ? viewSentTokens : Math.max(viewSentTokens, realUsage?.tokens ?? 0) });
|
|
19480
|
-
const versionStr = "0.1.
|
|
19564
|
+
const versionStr = "0.1.76" ? `billion-context-pi@${"0.1.76"} \xB7 pack: ${resolveSurfaceMeta(runtime.adapter, ctx?.cwd ?? process.cwd(), ctx?.model?.provider, ctx?.model?.id).pack}` : void 0;
|
|
19481
19565
|
let text = buildStatusPanel({
|
|
19482
19566
|
version: versionStr,
|
|
19483
19567
|
tokenCount: sessionTokens,
|
|
@@ -19586,6 +19670,41 @@ function carryHostSystemMessages(rebuilt, input) {
|
|
|
19586
19670
|
return changed ? patched : rebuilt;
|
|
19587
19671
|
}
|
|
19588
19672
|
|
|
19673
|
+
// src/tool-pair-sanitizer.ts
|
|
19674
|
+
function sanitizeToolPairing(messages) {
|
|
19675
|
+
const empty = { messages, droppedResults: [] };
|
|
19676
|
+
if (!Array.isArray(messages) || messages.length === 0) return empty;
|
|
19677
|
+
try {
|
|
19678
|
+
const callIds = /* @__PURE__ */ new Set();
|
|
19679
|
+
for (const raw of messages) {
|
|
19680
|
+
const msg = raw;
|
|
19681
|
+
if (!msg || typeof msg !== "object" || msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
|
|
19682
|
+
for (const b2 of msg.content) {
|
|
19683
|
+
if (b2 && b2.type === "toolCall" && typeof b2.id === "string" && b2.id.length > 0) callIds.add(b2.id);
|
|
19684
|
+
}
|
|
19685
|
+
}
|
|
19686
|
+
let changed = false;
|
|
19687
|
+
const out = [];
|
|
19688
|
+
const droppedResults = [];
|
|
19689
|
+
for (const raw of messages) {
|
|
19690
|
+
const msg = raw;
|
|
19691
|
+
if (msg && typeof msg === "object" && msg.role === "toolResult") {
|
|
19692
|
+
const tcid = msg.toolCallId;
|
|
19693
|
+
const label = typeof tcid === "string" && tcid.length > 0 ? tcid : "(missing toolCallId)";
|
|
19694
|
+
if (typeof tcid !== "string" || tcid.length === 0 || !callIds.has(tcid)) {
|
|
19695
|
+
droppedResults.push(label);
|
|
19696
|
+
changed = true;
|
|
19697
|
+
continue;
|
|
19698
|
+
}
|
|
19699
|
+
}
|
|
19700
|
+
out.push(raw);
|
|
19701
|
+
}
|
|
19702
|
+
return changed ? { messages: out, droppedResults } : empty;
|
|
19703
|
+
} catch {
|
|
19704
|
+
return empty;
|
|
19705
|
+
}
|
|
19706
|
+
}
|
|
19707
|
+
|
|
19589
19708
|
// src/degeneration.ts
|
|
19590
19709
|
var DEFAULT_DEGENERATION_GUARD = { enabled: true, minRun: 200 };
|
|
19591
19710
|
var MIN_VALID_MIN_RUN = 8;
|
|
@@ -20317,7 +20436,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
20317
20436
|
"--no-fund",
|
|
20318
20437
|
"--no-save"
|
|
20319
20438
|
];
|
|
20320
|
-
const prevVersion = (await readPackageJson(join15(extDir, "package.json")))?.version ?? "0.1.
|
|
20439
|
+
const prevVersion = (await readPackageJson(join15(extDir, "package.json")))?.version ?? "0.1.76";
|
|
20321
20440
|
const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
|
|
20322
20441
|
if (code !== 0) {
|
|
20323
20442
|
logWarn("update", {
|
|
@@ -20335,7 +20454,7 @@ async function autoInstallLatest(latest, extDirOverride) {
|
|
|
20335
20454
|
}
|
|
20336
20455
|
const verify = await verifyInstall(npmDir, latest);
|
|
20337
20456
|
if (!verify.ok) {
|
|
20338
|
-
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.
|
|
20457
|
+
const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.76";
|
|
20339
20458
|
logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
|
|
20340
20459
|
const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
|
|
20341
20460
|
logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
|
|
@@ -20410,7 +20529,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
20410
20529
|
}
|
|
20411
20530
|
const latest = await fetchLatestVersion(tag);
|
|
20412
20531
|
if (!latest) return;
|
|
20413
|
-
const current = runtimeVersion ?? "0.1.
|
|
20532
|
+
const current = runtimeVersion ?? "0.1.76";
|
|
20414
20533
|
const hasUpdate = isVersionNewer(latest, current);
|
|
20415
20534
|
debug.event("update-check", {
|
|
20416
20535
|
current,
|
|
@@ -20629,7 +20748,7 @@ function wireSessionLifecycle(pi, runtime, standDownIfProxied) {
|
|
|
20629
20748
|
setDelegatePolicy(DEFAULT_DELEGATE_POLICY);
|
|
20630
20749
|
const sid = ctx.sessionManager.getSessionId();
|
|
20631
20750
|
const modelInfo = ctx.model;
|
|
20632
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.
|
|
20751
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.76" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
|
|
20633
20752
|
let delegateStoodDown = false;
|
|
20634
20753
|
try {
|
|
20635
20754
|
await runtime.reloadConfig(ctx.cwd);
|
|
@@ -20940,6 +21059,13 @@ ${rendered.text}${example}`);
|
|
|
20940
21059
|
rebuilt = withHostSystem;
|
|
20941
21060
|
logInfo("system-passthrough", { sid, event: "carried", systems: event.messages.filter((m2) => m2.role === "system").length, outMsgs: rebuilt.length });
|
|
20942
21061
|
}
|
|
21062
|
+
if (turn.state.blocks.length > 0) {
|
|
21063
|
+
const sanitized = sanitizeToolPairing(rebuilt);
|
|
21064
|
+
if (sanitized.droppedResults.length > 0) {
|
|
21065
|
+
rebuilt = sanitized.messages;
|
|
21066
|
+
logWarn("tool-pair-sanitize", { sid, event: "dropped-orphan-results", count: sanitized.droppedResults.length, droppedResults: sanitized.droppedResults });
|
|
21067
|
+
}
|
|
21068
|
+
}
|
|
20943
21069
|
debug.event("context-out", { outMsgs: rebuilt.length, injected: turn.nudge?.shouldInject ?? false, emergency: turn.nudge?.breakdown?.emergencyOverride === 1 });
|
|
20944
21070
|
const updateCheck = checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
|
|
20945
21071
|
if (ctx.hasUI) ctx.ui.notify(msg);
|