Skip to content

Commit 567c004

Browse files
committed
wire extension factory integrating all primitives
1 parent 42aa4ce commit 567c004

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

index.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* Agenticoding v2 — Extension factory.
3+
*
4+
* Wires together the three primitives:
5+
* spawn — delegate isolated work to child contexts
6+
* ledger — sparse continuity cache
7+
* handoff — deliberate task pivot via compaction
8+
*
9+
* Also registers:
10+
* - watchdog (advisory primacy-zone reminder after each turn)
11+
* - system prompt injection (CONTEXT_PRIMER, nudge, ledger listing)
12+
* - state reset on /new
13+
*/
14+
15+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
16+
import { createState, resetState, type AgenticodingState } from "./state.js";
17+
import { CONTEXT_PRIMER } from "./system-prompt.js";
18+
import { buildNudge, registerWatchdog } from "./watchdog.js";
19+
import { registerLedgerTools } from "./ledger/tools.js";
20+
import { registerLedgerRehydration } from "./ledger/rehydration.js";
21+
import { registerHandoffTool } from "./handoff/tool.js";
22+
import { registerHandoffCommand } from "./handoff/command.js";
23+
import { registerHandoffCompaction } from "./handoff/compact.js";
24+
import { registerSpawnTool } from "./spawn/index.js";
25+
26+
/** Build a status bar preview from ledger entries. */
27+
function formatLedgerPreview(state: AgenticodingState): string {
28+
const names = Array.from(state.ledger.keys()).sort();
29+
if (names.length === 0) return "(empty)";
30+
return names
31+
.map((name) => {
32+
const content = state.ledger.get(name)!;
33+
const firstLine = (content.split("\n")[0] ?? "").slice(0, 60);
34+
return `${name}: ${firstLine}`;
35+
})
36+
.join("\n");
37+
}
38+
39+
/** Update TUI indicators: context usage + ledger count. */
40+
function updateIndicators(ctx: ExtensionContext, state: AgenticodingState): void {
41+
if (!ctx.hasUI) return;
42+
43+
const theme = ctx.ui.theme;
44+
45+
// Context usage
46+
const usage = ctx.getContextUsage();
47+
if (usage && usage.percent !== null) {
48+
const pct = Math.round(usage.percent);
49+
const tone = pct >= 70 ? "error" : pct >= 50 ? "warning" : pct >= 30 ? "accent" : "dim";
50+
ctx.ui.setStatus("agenticoding-ctx", theme.fg("dim", "ctx ") + theme.fg(tone, `${pct}%`));
51+
} else {
52+
ctx.ui.setStatus("agenticoding-ctx", theme.fg("dim", "ctx --%"));
53+
}
54+
55+
// Ledger count
56+
const count = state.ledger.size;
57+
ctx.ui.setStatus("agenticoding-ledger", count > 0 ? `\u{1F4D2} ${count}` : "");
58+
}
59+
60+
export default function (pi: ExtensionAPI): void {
61+
const state: AgenticodingState = createState();
62+
63+
// ── Register all tools ──────────────────────────────────────────
64+
registerLedgerTools(pi, state);
65+
registerHandoffTool(pi, state);
66+
registerSpawnTool(pi, state);
67+
68+
// ── Register event handlers ─────────────────────────────────────
69+
registerWatchdog(pi, state);
70+
registerLedgerRehydration(pi, state);
71+
registerHandoffCompaction(pi, state);
72+
73+
// ── Register commands ───────────────────────────────────────────
74+
registerHandoffCommand(pi, state);
75+
76+
// ── /ledger command — show entries in overlay ───────────────────
77+
pi.registerCommand("ledger", {
78+
description: "Show ledger entries with name, line count, and first-line preview",
79+
handler: async (_args, ctx) => {
80+
const preview = formatLedgerPreview(state);
81+
ctx.ui.notify(`Ledger (${state.ledger.size} entries):\n${preview}`, "info");
82+
},
83+
});
84+
85+
// ── before_agent_start: inject context primer + ledger ─────────
86+
pi.on("before_agent_start", async (event, ctx: ExtensionContext) => {
87+
// Update TUI indicators before each user-prompt agent run
88+
updateIndicators(ctx, state);
89+
90+
const parts: string[] = [event.systemPrompt];
91+
92+
// Inject context management primer at the end of the system prompt
93+
parts.push("\n" + CONTEXT_PRIMER);
94+
95+
// Inject ledger listing so the LLM always knows what's available
96+
const entryNames = Array.from(state.ledger.keys()).sort();
97+
if (entryNames.length > 0) {
98+
const listing = entryNames
99+
.map((name) => {
100+
const content = state.ledger.get(name)!;
101+
const firstLine = (content.split("\n")[0] ?? "").slice(0, 80);
102+
return ` ${name}: ${firstLine}`;
103+
})
104+
.join("\n");
105+
parts.push(
106+
`\n## Active Ledger Entries\n` +
107+
`The following entries are available via ledger_get by name:\n${listing}\n` +
108+
`Reference entries by name — never paste bodies into prompts.`,
109+
);
110+
}
111+
112+
return { systemPrompt: parts.join("\n\n") };
113+
});
114+
115+
// ── context: inject primacy-zone nudge before each LLM call ────
116+
pi.on("context", async (event, ctx: ExtensionContext) => {
117+
const usage = ctx.getContextUsage();
118+
if (!usage || usage.percent === null || usage.percent < 30) {
119+
return;
120+
}
121+
122+
state.lastContextPercent = usage.percent;
123+
return {
124+
messages: [
125+
...event.messages,
126+
{
127+
role: "custom",
128+
customType: "agenticoding-watchdog",
129+
content: buildNudge(usage.percent),
130+
display: false,
131+
timestamp: Date.now(),
132+
},
133+
],
134+
};
135+
});
136+
137+
// ── session_start: reset state + update indicators ─────────────
138+
pi.on("session_start", async (event, ctx: ExtensionContext) => {
139+
if (event.reason === "new") {
140+
resetState(state);
141+
}
142+
updateIndicators(ctx, state);
143+
});
144+
145+
// ── update TUI indicators after each turn ───────────────────────
146+
pi.on("turn_end", async (_event, ctx: ExtensionContext) => {
147+
updateIndicators(ctx, state);
148+
});
149+
}

0 commit comments

Comments
 (0)