Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/guides/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ Trigger condition:

- Compaction runs once the estimated context usage exceeds `contextWindow - reserveTokens`.
- After compaction, glm keeps approximately `keepRecentTokens` worth of recent context.
- The focused compaction summary also pulls in the repo context pack (AGENTS command/change hints plus common package scripts) and the latest loop/handoff state when available.

`glm inspect --json` prints both the resolved model context window and the effective compaction settings.

Expand Down Expand Up @@ -258,6 +259,7 @@ Session memory:

- `/memory`
- `/memory note <text>`
- `/memory` shows the latest compaction summary, the latest loop result snapshot, and operator notes stored for the session
- `/memory clear-notes`
- `/memory path`

Expand Down
2 changes: 2 additions & 0 deletions docs/guides/cli.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ Compaction 配置(写在任一 `settings.json` 中):

- 当估算的上下文 token 使用量超过 `contextWindow - reserveTokens` 时,会触发 compaction。
- 压缩后会尽量保留约 `keepRecentTokens` 的近期上下文。
- 当可用时,focused compaction summary 还会带入 repo context pack(AGENTS 中的命令/变更规则提示,以及常见 package scripts)与最近一次 loop/handoff 状态。

`glm inspect --json` 会同时输出模型的 `contextWindow` 与最终生效的 compaction 配置。

Expand Down Expand Up @@ -258,6 +259,7 @@ Token / 成本统计:

- `/memory`
- `/memory note <text>`
- `/memory` 会展示该 session 最近一次 compaction 摘要、最近一次 loop 结果快照,以及操作员备注
- `/memory clear-notes`
- `/memory path`

Expand Down
2 changes: 2 additions & 0 deletions docs/references/config-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ The CLI influences runtime behavior via flags. `glm inspect --json` is the easie
- Recommended operator flow is: select `provider`, optionally override `api`, then set `model`.
- `custom` is the generic path for proxy, local, and unknown models. Start with the requested model name, then refine capabilities with `modelOverrides` when the default generic profile is too conservative.
- Loop options are resolved in `src/app/env.ts`.
- Repo context pack assembly lives in `src/runtime/repo-context.ts`. It currently draws from `AGENTS.md` command/change sections and common `package.json` scripts, and the compaction extension reuses the same pack as focused compression input.
- Session memory persistence lives in `src/harness/session-memory.ts` and `resources/extensions/glm-memory/index.ts`. It stores compaction history, operator notes, and the latest loop result snapshot for `/memory`.
- Session paths are derived in `src/session/session-paths.ts`.
- Packaged prompts/extensions are synced by `src/app/resource-sync.ts`.

Expand Down
2 changes: 2 additions & 0 deletions docs/references/config-surface.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,5 +203,7 @@ CLI 会通过 flags 影响 runtime 行为。排查时建议直接运行 `glm ins
- 推荐的操作流程是:先选 `provider`,按需覆盖 `api`,再指定 `model`。
- `custom` 适用于代理网关、本地模型和未知模型。可以先用模型名直接试跑,再通过 `modelOverrides` 细化能力参数;默认 generic 能力是保守兜底,不代表最佳参数。
- Loop options 解析在 `src/app/env.ts`。
- Repo context pack 的组装逻辑在 `src/runtime/repo-context.ts`。当前会读取 `AGENTS.md` 中的命令/变更规则片段,以及常见 `package.json` scripts;compaction 扩展也会复用同一份 pack 作为 focused compression 的输入。
- Session memory 持久化逻辑在 `src/harness/session-memory.ts` 与 `resources/extensions/glm-memory/index.ts`。它会为 `/memory` 保留 compaction 历史、操作员备注和最近一次 loop 结果快照。
- Session 路径派生在 `src/session/session-paths.ts`。
- 打包的 prompts/extensions 同步逻辑在 `src/app/resource-sync.ts`。
12 changes: 10 additions & 2 deletions resources/extensions/glm-compaction/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { CompactionResult, ExtensionAPI, SessionEntry } from "@mariozechner/pi-coding-agent";
import { compact } from "@mariozechner/pi-coding-agent";
import { buildRepoContextPack } from "../../../src/runtime/repo-context.js";
import { appendRuntimeEvent, getRuntimeStatus } from "../shared/runtime-state.js";

const LOOP_STATE_ENTRY = "glm.loop.state";
Expand Down Expand Up @@ -87,7 +88,7 @@ function formatLoopResult(value: unknown): string | undefined {
return parts.length ? parts.join(" | ") : undefined;
}

function formatCompactionFocus(): string {
async function formatCompactionFocus(cwd?: string): Promise<string> {
const runtime = getRuntimeStatus();

const lines: string[] = [
Expand All @@ -112,6 +113,13 @@ function formatCompactionFocus(): string {
}
}

if (cwd) {
const repoContextPack = await buildRepoContextPack(cwd);
if (repoContextPack) {
lines.push(repoContextPack);
}
}

return lines.join("\n");
}

Expand Down Expand Up @@ -249,7 +257,7 @@ export default function (pi: ExtensionAPI) {
readLatestCustomEntry(event.branchEntries, LOOP_RESULT_ENTRY),
);

const focusLines = [formatCompactionFocus()];
const focusLines = [await formatCompactionFocus(ctx.cwd)];
if (loopState) {
focusLines.push(`Loop (persisted): ${loopState}`);
}
Expand Down
82 changes: 82 additions & 0 deletions resources/extensions/glm-memory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { appendRuntimeEvent } from "../shared/runtime-state.js";
import {
getSessionMemoryPath,
readSessionMemory,
type SessionMemoryLoopResultSnapshot,
upsertSessionMemoryCompaction,
upsertSessionMemoryOperatorNotes,
type SessionMemory,
} from "../../../src/harness/session-memory.js";

const MEMORY_WIDGET_KEY = "glm.memory";
const LOOP_RESULT_ENTRY = "glm.loop.result";

function emitMemoryMessage(pi: ExtensionAPI, lines: string[]): void {
pi.sendMessage(
Expand All @@ -22,6 +24,70 @@ function emitMemoryMessage(pi: ExtensionAPI, lines: string[]): void {
);
}

function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}

function readString(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value : undefined;
}

function readNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}

function readLatestLoopResult(
entries: Array<{ type?: string; customType?: string; data?: unknown }>,
): SessionMemoryLoopResultSnapshot | undefined {
for (let index = entries.length - 1; index >= 0; index -= 1) {
const entry = entries[index];
if (entry?.type !== "custom" || entry.customType !== LOOP_RESULT_ENTRY || !isRecord(entry.data)) {
continue;
}

const verification = isRecord(entry.data.verification) ? entry.data.verification : undefined;
const status = readString(entry.data.status);
const task = readString(entry.data.task);
const rounds = readNumber(entry.data.rounds);
const completedAt = readString(entry.data.completedAt);
const summary =
readString(entry.data.outcome) ??
readString(verification?.summary) ??
readString(entry.data.summary);

if (!status || !task || rounds === undefined || !summary) {
continue;
}

return {
status,
task,
rounds,
summary,
...(completedAt ? { completedAt } : {}),
...(verification
? {
verification: {
kind: readString(verification.kind) ?? "unknown",
...(readString(verification.command)
? { command: readString(verification.command)! }
: {}),
...(readNumber(verification.exitCode) === undefined
? {}
: { exitCode: readNumber(verification.exitCode)! }),
summary: readString(verification.summary) ?? summary,
...(readString(verification.artifactPath)
? { artifactPath: readString(verification.artifactPath)! }
: {}),
},
}
: {}),
};
}

return undefined;
}

function formatMemoryLines(args: { memoryPath: string; memory?: SessionMemory }): string[] {
if (!args.memory) {
return [
Expand All @@ -37,12 +103,17 @@ function formatMemoryLines(args: { memoryPath: string; memory?: SessionMemory })
const compactionSummary = latest
? `${latest.summary}${latest.tokensBefore ? ` (tokensBefore=${latest.tokensBefore})` : ""}`
: "none";
const latestLoop = args.memory.latestLoopResult;
const latestLoopSummary = latestLoop
? `${latestLoop.status} | ${latestLoop.task} | ${latestLoop.verification?.summary ?? latestLoop.summary}`
: "none";

return [
`Session memory: v${args.memory.version}`,
`Path: ${args.memoryPath}`,
`Updated: ${args.memory.updatedAt}`,
`Compactions: ${args.memory.compactions.length} | latest: ${compactionSummary}`,
`Latest loop result: ${latestLoopSummary}`,
`Operator notes: ${args.memory.operatorNotes ? "set" : "none"}`,
...(args.memory.operatorNotes ? ["", args.memory.operatorNotes] : []),
];
Expand Down Expand Up @@ -169,6 +240,16 @@ export default function (pi: ExtensionAPI) {
const sessionDir = ctx.sessionManager.getSessionDir();
const sessionId = ctx.sessionManager.getSessionId();
const sessionFile = ctx.sessionManager.getSessionFile();
const latestLoopResult =
typeof ctx.sessionManager.getEntries === "function"
? readLatestLoopResult(
ctx.sessionManager.getEntries() as Array<{
type?: string;
customType?: string;
data?: unknown;
}>,
)
: undefined;

await upsertSessionMemoryCompaction({
sessionDir,
Expand All @@ -180,6 +261,7 @@ export default function (pi: ExtensionAPI) {
summary: event.compactionEntry.summary,
tokensBefore: event.compactionEntry.tokensBefore,
},
...(latestLoopResult ? { latestLoopResult } : {}),
});

appendRuntimeEvent({
Expand Down
Loading
Loading