billion-context-omp 0.2.2 → 0.2.4
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/auto-compress.d.ts +8 -7
- package/dist/decompress-tool.d.ts +1 -0
- package/dist/index.js +113 -38
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ omp's built-in `/compact` is intercepted and replaced by an ACP model-summarized
|
|
|
77
77
|
|------|-------------|
|
|
78
78
|
| `compress` | Replace a contiguous message range with a detailed summary |
|
|
79
79
|
| `decompress` | Restore a previously compressed block's content (to file by default; `inline:true` for single messages) |
|
|
80
|
-
| `search_context` | Search compressed block summaries
|
|
80
|
+
| `search_context` | Search compressed block summaries and the original messages folded into them by keyword (visible messages are not indexed) |
|
|
81
81
|
| `acp_status` | Show context usage, compressed blocks, compressible ranges |
|
|
82
82
|
|
|
83
83
|
> The `acp_delegate` sub-agent subsystem from the Pi build is intentionally **not** registered — omp ships its own multi-agent orchestration, and duplicate delegation tools would conflict.
|
package/README.zh-CN.md
CHANGED
|
@@ -77,7 +77,7 @@ omp 内置的 `/compact` 被拦截,替换为 ACP 模型摘要式 compaction,
|
|
|
77
77
|
|------|------|
|
|
78
78
|
| `compress` | 把一段连续消息区间替换为详细摘要 |
|
|
79
79
|
| `decompress` | 恢复之前压缩的块内容(默认写文件;单条消息可 `inline:true`) |
|
|
80
|
-
| `search_context` |
|
|
80
|
+
| `search_context` | 按关键字搜索压缩块摘要及被折叠其中的原始消息(可见消息不建索引) |
|
|
81
81
|
| `acp_status` | 显示上下文用量、压缩块、可压缩区间 |
|
|
82
82
|
|
|
83
83
|
> Pi 版中的 `acp_delegate` 子代理系统在此**有意不注册**——omp 自带多代理编排,重复的委派工具会冲突。
|
package/dist/auto-compress.d.ts
CHANGED
|
@@ -43,8 +43,11 @@ export declare function buildSummaryPrompt(prompts: Prompts): string;
|
|
|
43
43
|
* deliberately NOT used here: fold blocks only replay from in-stream
|
|
44
44
|
* compress tool calls, which the truncation removes. `previousSummary` (an
|
|
45
45
|
* earlier compaction's summary) is folded in so iterative compactions never
|
|
46
|
-
* drop it.
|
|
47
|
-
*
|
|
46
|
+
* drop it. On a failed attempt the LLM call is
|
|
47
|
+
* retried ONCE (stochastic formatting failures recover on a fresh call);
|
|
48
|
+
* returns null only when the retry also fails — the caller then CANCELS the
|
|
49
|
+
* compaction (no native fallback: ACP owns compression, host-default
|
|
50
|
+
* summaries are the failure mode this hook exists to prevent). */
|
|
48
51
|
export declare function summarizeMessages(ctx: ExtensionContext, messages: AgentMessage[], prompts: Prompts, configuredModel?: string | null, opts?: {
|
|
49
52
|
previousSummary?: string;
|
|
50
53
|
customInstructions?: string;
|
|
@@ -58,11 +61,9 @@ export declare function summarizeMessages(ctx: ExtensionContext, messages: Agent
|
|
|
58
61
|
summary: string;
|
|
59
62
|
model: string;
|
|
60
63
|
} | null>;
|
|
61
|
-
/** Generate a summary for a message range using the compression model.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* is unparseable — the caller then returns `undefined` so Pi falls back to
|
|
65
|
-
* its native compaction. */
|
|
64
|
+
/** Generate a summary for a message range using the compression model.
|
|
65
|
+
* Currently unused by the extension (kept as the shared range-summary
|
|
66
|
+
* surface); null = hard failure, caller decides. */
|
|
66
67
|
export declare function summarizeRange(ctx: ExtensionContext, messages: CoreMessage[], state: CompressionState, startRef: string, endRef: string, prompts: Prompts, configuredModel?: string | null): Promise<{
|
|
67
68
|
summary: string;
|
|
68
69
|
model: string;
|
|
@@ -12,4 +12,5 @@ declare const DecompressParams: import("@oh-my-pi/omptype").FluentType<{
|
|
|
12
12
|
toFile?: string | undefined;
|
|
13
13
|
}>;
|
|
14
14
|
export declare function makeDecompressTool(runtime: AcpRuntime): ToolDefinition<typeof DecompressParams>;
|
|
15
|
+
export declare function pruneAutoFiles(): void;
|
|
15
16
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -2769,8 +2769,12 @@ function resolveConfig(adapter, liveContextLimit) {
|
|
|
2769
2769
|
return config;
|
|
2770
2770
|
}
|
|
2771
2771
|
function parsePercent(v) {
|
|
2772
|
-
const
|
|
2773
|
-
if (!Number.isFinite(
|
|
2772
|
+
const raw = typeof v === "number" ? v : v.trim().endsWith("%") ? Number(v.trim().slice(0, -1)) : Number(v);
|
|
2773
|
+
if (!Number.isFinite(raw)) return 0;
|
|
2774
|
+
const n = raw > 1 ? raw / 100 : raw;
|
|
2775
|
+
if (n > 1 || n < 0) {
|
|
2776
|
+
logWarn("config", { event: "percent-clamped", value: String(v), clamped: Math.min(1, Math.max(0, n)) });
|
|
2777
|
+
}
|
|
2774
2778
|
return Math.min(1, Math.max(0, n));
|
|
2775
2779
|
}
|
|
2776
2780
|
|
|
@@ -3159,7 +3163,7 @@ function rangeFingerprints(ranges, coreMessages, byRef, blocks) {
|
|
|
3159
3163
|
|
|
3160
3164
|
// src/runtime.ts
|
|
3161
3165
|
function freshSlot(preserveFrom) {
|
|
3162
|
-
const slot = { identities: [], foldedLen: 0, preview: false, state: createInitialState(), coreMessages: [], appliedCallIds: /* @__PURE__ */ new Set(), rejectStreak: 0 };
|
|
3166
|
+
const slot = { identities: [], foldedLen: 0, preview: false, state: createInitialState(), coreMessages: [], appliedCallIds: /* @__PURE__ */ new Set(), rejectStreak: 0, lastRebuiltOutput: null };
|
|
3163
3167
|
if (preserveFrom) {
|
|
3164
3168
|
slot.state = { ...slot.state, nudge: preserveFrom.state.nudge };
|
|
3165
3169
|
slot.rejectStreak = preserveFrom.rejectStreak;
|
|
@@ -3223,6 +3227,17 @@ function createRuntime(adapter) {
|
|
|
3223
3227
|
slots.set(sid, slot);
|
|
3224
3228
|
}
|
|
3225
3229
|
const ids = stream.map(messageIdentity);
|
|
3230
|
+
const lastOut = slot.lastRebuiltOutput;
|
|
3231
|
+
if (lastOut !== null && ids.length === lastOut.length && ids.every((id, i) => id === lastOut[i])) {
|
|
3232
|
+
const coreMessages2 = streamToCoreMessages(stream);
|
|
3233
|
+
const originalById2 = /* @__PURE__ */ new Map();
|
|
3234
|
+
stream.forEach((message, i) => originalById2.set(`p${i + 1}`, message));
|
|
3235
|
+
slot.identities = ids;
|
|
3236
|
+
slot.foldedLen = ids.length;
|
|
3237
|
+
slot.coreMessages = coreMessages2;
|
|
3238
|
+
debug.event("feedback-reuse", { sid, msgs: ids.length, blocks: slot.state.blocks.length });
|
|
3239
|
+
return { state: slot.state, coreMessages: coreMessages2, originalById: originalById2, streamLen: ids.length };
|
|
3240
|
+
}
|
|
3226
3241
|
let lcp = 0;
|
|
3227
3242
|
while (lcp < Math.min(ids.length, slot.identities.length) && ids[lcp] === slot.identities[lcp]) lcp++;
|
|
3228
3243
|
if (lcp < slot.foldedLen) {
|
|
@@ -3311,6 +3326,10 @@ function createRuntime(adapter) {
|
|
|
3311
3326
|
slot.state = state;
|
|
3312
3327
|
if (toolCallId) slot.appliedCallIds.add(toolCallId);
|
|
3313
3328
|
}
|
|
3329
|
+
function recordRebuiltOutput(ctx, rebuilt) {
|
|
3330
|
+
const slot = slotFor(sidOf(ctx));
|
|
3331
|
+
slot.lastRebuiltOutput = rebuilt.map(messageIdentity);
|
|
3332
|
+
}
|
|
3314
3333
|
function noteCompressOutcome(ctx, ok) {
|
|
3315
3334
|
const slot = slotFor(sidOf(ctx));
|
|
3316
3335
|
slot.rejectStreak = ok ? 0 : slot.rejectStreak + 1;
|
|
@@ -3335,6 +3354,7 @@ function createRuntime(adapter) {
|
|
|
3335
3354
|
foldStream,
|
|
3336
3355
|
stateFor,
|
|
3337
3356
|
commitFoldState,
|
|
3357
|
+
recordRebuiltOutput,
|
|
3338
3358
|
noteCompressOutcome,
|
|
3339
3359
|
forgetSession,
|
|
3340
3360
|
primeFold,
|
|
@@ -3435,11 +3455,13 @@ function makeCompressTool(runtime) {
|
|
|
3435
3455
|
}
|
|
3436
3456
|
async function handleCompress(args, runtime, ctx, toolCallId) {
|
|
3437
3457
|
const ranges = args.content ?? [];
|
|
3438
|
-
if (ranges.length === 0)
|
|
3458
|
+
if (ranges.length === 0) {
|
|
3459
|
+
return rejectionMessage(ctx, runtime, "No ranges provided. No changes applied \u2014 send content: [{ startId, endId, summary }].");
|
|
3460
|
+
}
|
|
3439
3461
|
const invalid = ranges.filter((r) => !r || typeof r.summary !== "string" || !r.summary.trim() || !r.startId || !r.endId);
|
|
3440
3462
|
if (invalid.length > 0) {
|
|
3441
3463
|
logWarn("compress", { sid: ctx.sessionManager.getSessionId(), event: "invalid-ranges", count: invalid.length });
|
|
3442
|
-
return `Every range needs startId, endId and a summary (min 50 chars) \u2014 got ${invalid.length} range(s) missing fields. Re-send the FULL ranges array with summaries included
|
|
3464
|
+
return rejectionMessage(ctx, runtime, `Every range needs startId, endId and a summary (min 50 chars) \u2014 got ${invalid.length} range(s) missing fields. Re-send the FULL ranges array with summaries included. No changes applied \u2014 run acp_status for current refs.`);
|
|
3443
3465
|
}
|
|
3444
3466
|
const releaseLock = await runtime.acquireLock(ctx.sessionManager.getSessionId());
|
|
3445
3467
|
try {
|
|
@@ -3550,10 +3572,12 @@ STOP: ${streak} compress calls rejected in a row. Do NOT retry the same range. R
|
|
|
3550
3572
|
// src/decompress-tool.ts
|
|
3551
3573
|
import { type as type2 } from "@oh-my-pi/omptype";
|
|
3552
3574
|
import { writeFile, mkdir } from "fs/promises";
|
|
3553
|
-
import { realpathSync } from "fs";
|
|
3575
|
+
import { realpathSync, readdirSync, statSync as statSync2, unlinkSync } from "fs";
|
|
3554
3576
|
import { resolve, relative, isAbsolute, join as join2, dirname as dirname2, basename } from "path";
|
|
3555
3577
|
import { tmpdir } from "os";
|
|
3556
|
-
|
|
3578
|
+
function autoDir() {
|
|
3579
|
+
return join2(homeDir() || tmpdir(), ".cache", "omp", "acp-decompress");
|
|
3580
|
+
}
|
|
3557
3581
|
var PREVIEW_CHARS = 600;
|
|
3558
3582
|
var MESSAGE_INLINE_THRESHOLD = 2e3;
|
|
3559
3583
|
var DecompressParams = type2({
|
|
@@ -3618,7 +3642,31 @@ function resolveToFilePath(targetPath) {
|
|
|
3618
3642
|
return resolved;
|
|
3619
3643
|
}
|
|
3620
3644
|
function autoFilePath(blockId) {
|
|
3621
|
-
return join2(
|
|
3645
|
+
return join2(autoDir(), `${blockId}-${Date.now()}.txt`);
|
|
3646
|
+
}
|
|
3647
|
+
var MAX_AUTO_FILES = 100;
|
|
3648
|
+
function pruneAutoFiles() {
|
|
3649
|
+
const dir = autoDir();
|
|
3650
|
+
try {
|
|
3651
|
+
const files = readdirSync(dir).map((f) => {
|
|
3652
|
+
try {
|
|
3653
|
+
return { f, m: statSync2(join2(dir, f)).mtimeMs };
|
|
3654
|
+
} catch {
|
|
3655
|
+
return null;
|
|
3656
|
+
}
|
|
3657
|
+
}).filter((x) => x !== null);
|
|
3658
|
+
if (files.length <= MAX_AUTO_FILES) return;
|
|
3659
|
+
files.sort((a, b) => a.m - b.m);
|
|
3660
|
+
const excess = files.slice(0, files.length - MAX_AUTO_FILES);
|
|
3661
|
+
for (const { f } of excess) {
|
|
3662
|
+
try {
|
|
3663
|
+
unlinkSync(join2(dir, f));
|
|
3664
|
+
} catch {
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
debug.event("decompress-pruned", { dir, removed: excess.length });
|
|
3668
|
+
} catch {
|
|
3669
|
+
}
|
|
3622
3670
|
}
|
|
3623
3671
|
function headPreview(text) {
|
|
3624
3672
|
if (text.length <= PREVIEW_CHARS) return text;
|
|
@@ -3652,8 +3700,9 @@ ${text}`;
|
|
|
3652
3700
|
logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message-path-rejected", ref, toFile: args.toFile });
|
|
3653
3701
|
return targetPath.error;
|
|
3654
3702
|
}
|
|
3655
|
-
await mkdir(
|
|
3703
|
+
await mkdir(autoDir(), { recursive: true }).catch((e) => logError("decompress", { event: "mkdir-failed", dir: autoDir(), error: e instanceof Error ? e.message : String(e) }));
|
|
3656
3704
|
await writeFile(targetPath, text, "utf8");
|
|
3705
|
+
if (!args.toFile) pruneAutoFiles();
|
|
3657
3706
|
debug.event("decompress-message", { ref, ownerBlockId, mode: "file", path: targetPath, chars: text.length });
|
|
3658
3707
|
logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "message", mode: "file", ref, ownerBlockId, path: targetPath, chars: text.length });
|
|
3659
3708
|
return [
|
|
@@ -3695,8 +3744,9 @@ ${text}`;
|
|
|
3695
3744
|
logError("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block-path-rejected", blockId, toFile: args.toFile });
|
|
3696
3745
|
return targetPath.error;
|
|
3697
3746
|
}
|
|
3698
|
-
await mkdir(
|
|
3747
|
+
await mkdir(autoDir(), { recursive: true }).catch((e) => logError("decompress", { event: "mkdir-failed", dir: autoDir(), error: e instanceof Error ? e.message : String(e) }));
|
|
3699
3748
|
await writeFile(targetPath, text, "utf8");
|
|
3749
|
+
if (!args.toFile) pruneAutoFiles();
|
|
3700
3750
|
debug.event("decompress", { blockId, full, count, mode: "file", path: targetPath, chars: text.length });
|
|
3701
3751
|
logInfo("decompress", { sid: ctx.sessionManager.getSessionId(), event: "block", mode: "file", blockId, full, count, path: targetPath, chars: text.length });
|
|
3702
3752
|
const itemWord = count === 1 ? "item" : "items";
|
|
@@ -4508,7 +4558,7 @@ async function statusReport(runtime, ctx) {
|
|
|
4508
4558
|
const coveredIds = collectCoveredMessageIds(state);
|
|
4509
4559
|
const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
|
|
4510
4560
|
const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: sentTokens });
|
|
4511
|
-
const versionStr = "0.2.
|
|
4561
|
+
const versionStr = "0.2.4" ? `billion-context-omp@${"0.2.4"}` : void 0;
|
|
4512
4562
|
return buildStatusPanel({
|
|
4513
4563
|
version: versionStr,
|
|
4514
4564
|
tokenCount: sessionTokens,
|
|
@@ -4757,7 +4807,7 @@ import { join as join3 } from "path";
|
|
|
4757
4807
|
import { complete } from "@oh-my-pi/pi-ai";
|
|
4758
4808
|
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME2 } from "@oh-my-pi/pi-utils";
|
|
4759
4809
|
var TIMEOUT_MS = 6e4;
|
|
4760
|
-
var MAX_OUTPUT_TOKENS =
|
|
4810
|
+
var MAX_OUTPUT_TOKENS = 8e3;
|
|
4761
4811
|
var MAX_SLICE_CHARS = 15e4;
|
|
4762
4812
|
var MAX_MSG_CHARS = 4e3;
|
|
4763
4813
|
function readCompressModel() {
|
|
@@ -4807,9 +4857,10 @@ function parseSummary(text) {
|
|
|
4807
4857
|
try {
|
|
4808
4858
|
const obj = JSON.parse(cleaned);
|
|
4809
4859
|
if (typeof obj.summary === "string" && obj.summary.length > 0) return obj.summary;
|
|
4860
|
+
return null;
|
|
4810
4861
|
} catch {
|
|
4811
4862
|
}
|
|
4812
|
-
return null;
|
|
4863
|
+
return cleaned.length >= 50 ? cleaned : null;
|
|
4813
4864
|
}
|
|
4814
4865
|
function buildSummaryPrompt(prompts) {
|
|
4815
4866
|
return prompts.compressPhilosophy.trim() + "\n\n" + prompts.howToCompressRules.trim() + '\n\nCompress the message range provided below into ONE dense, self-contained technical summary following the rules above. Output ONLY a JSON object: {"summary": "..."} where the value is the full summary as a single string.';
|
|
@@ -4846,14 +4897,32 @@ User instructions for this compaction: ${custom}`;
|
|
|
4846
4897
|
const userText = `ENTIRE conversation to compress (${slice.length} messages, ~${tokens} tokens). Compress it:
|
|
4847
4898
|
|
|
4848
4899
|
` + formatSlice(slice, opts?.messageRefs ? { ...createInitialState(), messageRefs: opts.messageRefs } : createInitialState());
|
|
4849
|
-
const
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4900
|
+
const attempt = async () => {
|
|
4901
|
+
const response = await run(
|
|
4902
|
+
model,
|
|
4903
|
+
{ systemPrompt: [instructions], messages: [{ role: "user", content: [{ type: "text", text: userText }], timestamp: Date.now() }] },
|
|
4904
|
+
{ apiKey: auth.apiKey, headers: auth.headers, maxTokens: MAX_OUTPUT_TOKENS, signal: ac.signal }
|
|
4905
|
+
);
|
|
4906
|
+
return parseSummary(
|
|
4907
|
+
response.content.filter((c) => c.type === "text").map((c) => c.text).join("\n")
|
|
4908
|
+
);
|
|
4909
|
+
};
|
|
4910
|
+
let summary = null;
|
|
4911
|
+
try {
|
|
4912
|
+
summary = await attempt();
|
|
4913
|
+
} catch (e) {
|
|
4914
|
+
if (opts?.signal?.aborted || ac.signal.aborted) throw e;
|
|
4915
|
+
logWarn("summarize-messages", { event: "attempt-failed", model: label, error: String(e) });
|
|
4916
|
+
}
|
|
4917
|
+
if (!summary) {
|
|
4918
|
+
try {
|
|
4919
|
+
summary = await attempt();
|
|
4920
|
+
if (summary) logInfo("summarize-messages", { event: "recovered-on-retry", model: label, messages: slice.length });
|
|
4921
|
+
} catch (e) {
|
|
4922
|
+
if (opts?.signal?.aborted || ac.signal.aborted) throw e;
|
|
4923
|
+
logWarn("summarize-messages", { event: "retry-failed", model: label, error: String(e) });
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4857
4926
|
if (!summary) {
|
|
4858
4927
|
logWarn("summarize-messages", { event: "unparseable-summary", model: label, messages: slice.length });
|
|
4859
4928
|
return null;
|
|
@@ -4893,7 +4962,7 @@ You have four context-management tools:
|
|
|
4893
4962
|
|
|
4894
4963
|
- compress \u2014 Replace a contiguous range of older conversation with a single detailed summary you write. Use when content is genuinely consumed (no longer needed for the current task step). Single range: compress({ content: [{ topic: "Session Opener", startId: "m00150", endId: "m00220", summary: "..." }] }) \u2014 topic is recommended but optional. Batch (multiple unrelated ranges, each with its own topic): compress({ content: [{ topic: "Auth", startId: "m00150", endId: "m00220", summary: "..." }, { topic: "Deploy", startId: "m00300", endId: "m00350", summary: "..." }] }). Call it as a normal tool \u2014 summaries are plain string arguments.
|
|
4895
4964
|
- decompress \u2014 Restore a previously compressed block's content. The block stays compressed \u2014 context and cache prefix are not disrupted. By DEFAULT content is written to an auto-generated file (avoids context bloat); use the read tool to view it. Pass inline:true to return content in the tool result instead (appends to context). full:true recurses to original messages. Example: decompress({ blockId: "b5" }) or decompress({ blockId: "b5", full: true }) or decompress({ blockId: "b5", inline: true }).
|
|
4896
|
-
- search_context \u2014 Search compressed block summaries
|
|
4965
|
+
- search_context \u2014 Search compressed block summaries AND the original messages folded into them by keyword (messages still visible in context are not indexed \u2014 you can already see them). Use BEFORE decompressing to find the right block. Example: search_context({ query: "auth token refresh" }).
|
|
4897
4966
|
- acp_status \u2014 Context status with compressible ranges. No args = overview + totals. scope:"uncompressed" for range view; add view:"messages" for per-message listing. scope:"compressed" for block details.
|
|
4898
4967
|
|
|
4899
4968
|
${prompts.compressPhilosophy}
|
|
@@ -5216,7 +5285,7 @@ async function checkForUpdate(autoUpdate, notify) {
|
|
|
5216
5285
|
const data = await res.json();
|
|
5217
5286
|
const latest = data.version;
|
|
5218
5287
|
if (!latest) return;
|
|
5219
|
-
const current = runtimeVersion ?? "0.2.
|
|
5288
|
+
const current = runtimeVersion ?? "0.2.4";
|
|
5220
5289
|
const hasUpdate = isNewer(latest, current);
|
|
5221
5290
|
debug.event("update-check", {
|
|
5222
5291
|
current,
|
|
@@ -5251,20 +5320,20 @@ async function getRuntimeVersion() {
|
|
|
5251
5320
|
}
|
|
5252
5321
|
|
|
5253
5322
|
// src/dump.ts
|
|
5254
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readdirSync, unlinkSync } from "fs";
|
|
5323
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
5255
5324
|
import * as path2 from "path";
|
|
5256
5325
|
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME4 } from "@oh-my-pi/pi-utils";
|
|
5257
5326
|
var counters = {};
|
|
5258
5327
|
var MAX_FILES_PER_PREFIX = 200;
|
|
5259
5328
|
function pruneDumps(dir, prefixTest, seqOf) {
|
|
5260
5329
|
try {
|
|
5261
|
-
const files =
|
|
5330
|
+
const files = readdirSync2(dir).filter(prefixTest).map((f) => ({ f, n: seqOf(f) })).filter((x) => !Number.isNaN(x.n));
|
|
5262
5331
|
if (files.length <= MAX_FILES_PER_PREFIX) return;
|
|
5263
5332
|
files.sort((a, b) => a.n - b.n);
|
|
5264
5333
|
const excess = files.slice(0, files.length - MAX_FILES_PER_PREFIX);
|
|
5265
5334
|
for (const { f } of excess) {
|
|
5266
5335
|
try {
|
|
5267
|
-
|
|
5336
|
+
unlinkSync2(path2.join(dir, f));
|
|
5268
5337
|
} catch {
|
|
5269
5338
|
}
|
|
5270
5339
|
}
|
|
@@ -5282,7 +5351,7 @@ function dumpContextMessages(messages, meta) {
|
|
|
5282
5351
|
mkdirSync2(dir, { recursive: true });
|
|
5283
5352
|
if (!(dir in counters)) {
|
|
5284
5353
|
try {
|
|
5285
|
-
const existing =
|
|
5354
|
+
const existing = readdirSync2(dir).filter((f) => /^\d{4}\.json$/.test(f));
|
|
5286
5355
|
const max = existing.reduce((mx, f) => {
|
|
5287
5356
|
const n = parseInt(f, 10);
|
|
5288
5357
|
return Number.isNaN(n) ? mx : Math.max(mx, n);
|
|
@@ -5365,7 +5434,7 @@ function dumpProviderRequest(payload, meta) {
|
|
|
5365
5434
|
mkdirSync2(dir, { recursive: true });
|
|
5366
5435
|
if (!("req" in counters)) {
|
|
5367
5436
|
try {
|
|
5368
|
-
const existing =
|
|
5437
|
+
const existing = readdirSync2(dir).filter((f) => /^req_\d+\.json$/.test(f));
|
|
5369
5438
|
const max = existing.reduce((mx, f) => {
|
|
5370
5439
|
const n = parseInt(f.slice(4, -5), 10);
|
|
5371
5440
|
return Number.isNaN(n) ? mx : Math.max(mx, n);
|
|
@@ -5404,10 +5473,11 @@ import * as path3 from "path";
|
|
|
5404
5473
|
import { CONFIG_DIR_NAME as CONFIG_DIR_NAME5 } from "@oh-my-pi/pi-utils";
|
|
5405
5474
|
async function loadUserConfig(cwd) {
|
|
5406
5475
|
const home = homeDir();
|
|
5476
|
+
const globalDir = join8(home, CONFIG_DIR_NAME5);
|
|
5407
5477
|
const merged = {};
|
|
5408
|
-
for (const base of [
|
|
5478
|
+
for (const base of [globalDir, join8(cwd, CONFIG_DIR_NAME5)]) {
|
|
5409
5479
|
const file = join8(base, "acp-omp.json");
|
|
5410
|
-
const allowPrompts = base
|
|
5480
|
+
const allowPrompts = base === globalDir;
|
|
5411
5481
|
try {
|
|
5412
5482
|
const raw = await fs.readFile(file, "utf8");
|
|
5413
5483
|
const parsed = JSON.parse(raw);
|
|
@@ -5504,8 +5574,8 @@ function wireCompactionDisable(pi, runtime) {
|
|
|
5504
5574
|
messageRefs: slot.state.messageRefs
|
|
5505
5575
|
});
|
|
5506
5576
|
if (!result) {
|
|
5507
|
-
ctx.ui?.notify?.("ACP:
|
|
5508
|
-
return
|
|
5577
|
+
ctx.ui?.notify?.("ACP: /compact aborted \u2014 summary generation failed after retry (details in ~/.omp/acp-omp.log)", "error");
|
|
5578
|
+
return { cancel: true };
|
|
5509
5579
|
}
|
|
5510
5580
|
logInfo("compact", {
|
|
5511
5581
|
sid,
|
|
@@ -5532,9 +5602,9 @@ function wireCompactionDisable(pi, runtime) {
|
|
|
5532
5602
|
function wireSessionLifecycle(pi, runtime) {
|
|
5533
5603
|
pi.on("session_start", async (_event, ctx) => {
|
|
5534
5604
|
const sid = ctx.sessionManager.getSessionId();
|
|
5535
|
-
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.2.
|
|
5605
|
+
logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.2.4" : null });
|
|
5536
5606
|
const selfPath = import.meta.url;
|
|
5537
|
-
const conflict = stampAndDetect(selfPath, true ? "0.2.
|
|
5607
|
+
const conflict = stampAndDetect(selfPath, true ? "0.2.4" : null);
|
|
5538
5608
|
if (conflict) {
|
|
5539
5609
|
logWarn("instance", { event: "dual-instance", self: selfPath, other: conflict.path, otherPid: conflict.pid, otherVersion: conflict.version });
|
|
5540
5610
|
try {
|
|
@@ -5573,6 +5643,7 @@ function wireSessionLifecycle(pi, runtime) {
|
|
|
5573
5643
|
async function transformStream(ctx, runtime, input, mode) {
|
|
5574
5644
|
const sid = ctx.sessionManager.getSessionId();
|
|
5575
5645
|
const release = await runtime.acquireLock(sid);
|
|
5646
|
+
let result;
|
|
5576
5647
|
try {
|
|
5577
5648
|
if (input.length === 0) {
|
|
5578
5649
|
debug.event("empty-stream-bypass", { sid });
|
|
@@ -5678,21 +5749,25 @@ ${rendered.text}${example}`);
|
|
|
5678
5749
|
}
|
|
5679
5750
|
}
|
|
5680
5751
|
}
|
|
5752
|
+
if (mode === "context") {
|
|
5753
|
+
runtime.recordRebuiltOutput(ctx, rebuilt);
|
|
5754
|
+
}
|
|
5681
5755
|
dumpContextMessages(rebuilt, {
|
|
5682
5756
|
sid,
|
|
5683
5757
|
injected: nudgeInjected,
|
|
5684
5758
|
emergency: turn.nudge?.breakdown?.emergencyOverride === 1
|
|
5685
5759
|
});
|
|
5686
|
-
|
|
5687
|
-
if (ctx.hasUI) ctx.ui.notify(msg);
|
|
5688
|
-
});
|
|
5689
|
-
return { rebuilt, nudgeInjected };
|
|
5760
|
+
result = { rebuilt, nudgeInjected };
|
|
5690
5761
|
} catch (e) {
|
|
5691
5762
|
logThrow("context", e, { sid, phase: "transform", mode });
|
|
5692
5763
|
throw e;
|
|
5693
5764
|
} finally {
|
|
5694
5765
|
release();
|
|
5695
5766
|
}
|
|
5767
|
+
await checkForUpdate(runtime.adapter.autoUpdate ?? true, (msg) => {
|
|
5768
|
+
if (ctx.hasUI) ctx.ui.notify(msg);
|
|
5769
|
+
});
|
|
5770
|
+
return result;
|
|
5696
5771
|
}
|
|
5697
5772
|
function wireContextTransform(pi, runtime) {
|
|
5698
5773
|
pi.on("context", async (event, ctx) => {
|