one-code-extension 0.5.0 → 0.6.0
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/docs/guide/differences-from-claude-code.md +9 -6
- package/docs/guide/installation.md +3 -4
- package/docs/guide/troubleshooting.md +1 -1
- package/extensions/auto-mode/denials.ts +3 -2
- package/extensions/background/cron.ts +127 -0
- package/extensions/background/index.ts +135 -47
- package/extensions/background/loop-fire.ts +39 -2
- package/extensions/background/switch-guard.ts +10 -6
- package/extensions/background/wakeup.ts +21 -2
- package/extensions/bash/index.ts +8 -30
- package/extensions/btw/index.ts +32 -18
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-context/index.ts +157 -172
- package/extensions/compaction/index.ts +11 -8
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +15 -14
- package/extensions/context-management/index.ts +11 -28
- package/extensions/effort/index.ts +70 -7
- package/extensions/file-tracker/index.ts +111 -44
- package/extensions/file-tracker/replay.ts +76 -49
- package/extensions/file-tracker/restore.ts +12 -9
- package/extensions/file-tracker/tracker.ts +14 -1
- package/extensions/hooks/index.ts +60 -76
- package/extensions/hooks/prompt-context.ts +59 -0
- package/extensions/hooks/trust.ts +13 -7
- package/extensions/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +17 -1
- package/extensions/lib/agent-loader.ts +3 -2
- package/extensions/lib/anthropic-auth.ts +98 -0
- package/extensions/lib/child-extensions.ts +14 -4
- package/extensions/lib/context-facts.ts +75 -56
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +152 -12
- package/extensions/lib/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +24 -6
- package/extensions/lib/model-catalog/huggingface.json +5 -1
- package/extensions/lib/model-catalog/models-dev.json +10535 -10299
- package/extensions/lib/model-catalog/openrouter.json +191 -393
- package/extensions/lib/notifications.ts +57 -17
- package/extensions/lib/pi-version.ts +25 -8
- package/extensions/lib/plan-mode-channels.ts +29 -7
- package/extensions/lib/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +3 -2
- package/extensions/lib/reminders.ts +327 -621
- package/extensions/lib/request-replay.ts +186 -78
- package/extensions/lib/side-call.ts +11 -0
- package/extensions/lib/subagent-handback.ts +0 -1
- package/extensions/lib/tool-additions.ts +2 -22
- package/extensions/lib/turn-budget-layout.ts +18 -2
- package/extensions/lsp/index.ts +6 -8
- package/extensions/mcp/announce.ts +63 -98
- package/extensions/mcp/index.ts +51 -90
- package/extensions/mcp/trust.ts +13 -6
- package/extensions/model-default/index.ts +86 -19
- package/extensions/permissions/index.ts +157 -48
- package/extensions/permissions/modes.ts +5 -0
- package/extensions/plan-mode/index.ts +142 -45
- package/extensions/plan-mode/run-state.ts +26 -0
- package/extensions/plugins/index.ts +4 -1
- package/extensions/powershell/index.ts +2 -0
- package/extensions/recap/index.ts +11 -14
- package/extensions/search-tools/index.ts +2 -0
- package/extensions/skill/announce.ts +30 -0
- package/extensions/skill/index.ts +65 -48
- package/extensions/skill/invoke.ts +0 -67
- package/extensions/subagents/agents.ts +30 -8
- package/extensions/subagents/fork-cache.ts +9 -3
- package/extensions/subagents/index.ts +180 -107
- package/extensions/subagents/outcome.ts +3 -0
- package/extensions/subagents/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +29 -2
- package/extensions/subagents/runs.ts +5 -0
- package/extensions/system-prompt/index.ts +40 -6
- package/extensions/system-prompt/template.ts +10 -1
- package/extensions/system-reminder/index.ts +174 -202
- package/extensions/tool-search/index.ts +241 -167
- package/extensions/workflow/agent-session.ts +4 -1
- package/extensions/workflow/index.ts +16 -4
- package/extensions/workflow/run-manager.ts +4 -0
- package/extensions/worktree/index.ts +50 -14
- package/package.json +5 -2
- package/extensions/lib/context-stack.ts +0 -81
- package/extensions/subagents/announce.ts +0 -69
- package/extensions/tool-search/announce.ts +0 -58
package/extensions/bash/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ import { perCwd } from "../lib/per-cwd.ts";
|
|
|
27
27
|
import { bashSpawn, piShellEnv, withChildProcessEnv } from "../lib/shell-spawn.ts";
|
|
28
28
|
import { registerShellTool } from "../lib/shell-tool.ts";
|
|
29
29
|
import { type DescriptionForm, followDescriptionForm } from "../lib/tool-variants.ts";
|
|
30
|
-
import { CONTEXT_BASELINE_CHANNEL, restoredContext } from "../lib/context-stack.ts";
|
|
31
30
|
import { PERMISSION_STATUS_CHANNEL, type PermissionStatus } from "../permissions/modes.ts";
|
|
32
31
|
import { BASH_PARAMS, bashDescription } from "./description.ts";
|
|
33
32
|
import { bashGuardReason } from "./guards.ts";
|
|
@@ -70,23 +69,15 @@ export function bashTool(pi: ExtensionAPI, { announce }: { announce: boolean })
|
|
|
70
69
|
const spawnHook = (context: BashSpawnContext): BashSpawnContext => ({ ...context, env: childProcessEnv(context.env) });
|
|
71
70
|
const base = createBashToolDefinition(process.cwd(), { shellPath, spawnHook });
|
|
72
71
|
|
|
73
|
-
// The
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
72
|
+
// The description follows the tier and the permission mode. It reaches the
|
|
73
|
+
// wire only when a request opens an epoch: inside one the ledger keeps the
|
|
74
|
+
// tools array as recorded (extensions/ledger), so a mode switch never moves
|
|
75
|
+
// the tool or re-caches the prefix; mode reminders and the live permission
|
|
76
|
+
// gate carry the change instead.
|
|
77
77
|
let form: DescriptionForm = "short";
|
|
78
78
|
// Tiny's dedicated search tools replace the long form's shell-find advice.
|
|
79
79
|
let searchTools = false;
|
|
80
80
|
let autoMode = false;
|
|
81
|
-
let liveAutoMode = false;
|
|
82
|
-
let modeFrozen = false;
|
|
83
|
-
const restoredAutoMode = (): boolean | undefined => {
|
|
84
|
-
const baselines = restoredContext(pi.events)?.baselines;
|
|
85
|
-
// Older snapshots' last mode chose the last request's description.
|
|
86
|
-
const saved = baselines?.["bash-auto-mode"];
|
|
87
|
-
if (typeof saved === "boolean") return saved;
|
|
88
|
-
return typeof baselines?.["permission-mode"] === "string" ? baselines["permission-mode"] === "auto" : undefined;
|
|
89
|
-
};
|
|
90
81
|
const describe = () => bashDescription(form, autoMode, searchTools);
|
|
91
82
|
const setDescription = registerShellTool(pi, {
|
|
92
83
|
name: "bash",
|
|
@@ -104,24 +95,11 @@ export function bashTool(pi: ExtensionAPI, { announce }: { announce: boolean })
|
|
|
104
95
|
setDescription(describe());
|
|
105
96
|
});
|
|
106
97
|
pi.events.on(PERMISSION_STATUS_CHANNEL, (data) => {
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
// Honor a restored variant even here: a temporary re-registration would
|
|
111
|
-
// move the tool despite putting its original description back afterwards.
|
|
112
|
-
autoMode = restoredAutoMode() ?? liveAutoMode;
|
|
98
|
+
const next = (data as PermissionStatus).mode === "auto";
|
|
99
|
+
if (next === autoMode) return;
|
|
100
|
+
autoMode = next;
|
|
113
101
|
setDescription(describe());
|
|
114
102
|
});
|
|
115
|
-
pi.on("session_start", () => {
|
|
116
|
-
const saved = restoredAutoMode();
|
|
117
|
-
modeFrozen = saved !== undefined;
|
|
118
|
-
autoMode = saved ?? liveAutoMode;
|
|
119
|
-
setDescription(describe());
|
|
120
|
-
});
|
|
121
|
-
pi.on("turn_start", () => {
|
|
122
|
-
modeFrozen = true;
|
|
123
|
-
pi.events.emit(CONTEXT_BASELINE_CHANNEL, { key: "bash-auto-mode", value: autoMode });
|
|
124
|
-
});
|
|
125
103
|
|
|
126
104
|
// The user's own `!` commands, under the bundled app only: pi runs them with
|
|
127
105
|
// its full process environment, so they get the same restore. Outside the app
|
package/extensions/btw/index.ts
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* reads the cache the session wrote (lib/request-replay.ts; the compaction
|
|
15
15
|
* extension publishes the capture). Without a capture for the session model,
|
|
16
16
|
* or on a provider API the replay does not cover, it falls back to a
|
|
17
|
-
* standalone call: the
|
|
18
|
-
*
|
|
17
|
+
* standalone call: the current branch's messages (read from the session at
|
|
18
|
+
* call time), the earlier side exchanges, an empty system prompt,
|
|
19
19
|
* name-only tool stubs so a history carrying tool_use blocks stays valid on
|
|
20
20
|
* strict providers, and withReasoningFallback for models that cannot disable
|
|
21
21
|
* thinking. Both run on the SESSION model: a side question deserves the same
|
|
@@ -23,17 +23,18 @@
|
|
|
23
23
|
*
|
|
24
24
|
* Like Claude Code, the session keeps a side history: each answered question
|
|
25
25
|
* is threaded into the next one and listed in the panel, where it can be
|
|
26
|
-
* browsed and cleared.
|
|
26
|
+
* browsed and cleared. A /tree keeps only the exchanges asked from a point
|
|
27
|
+
* the selected branch holds. `f` forks the current answer into a background fork
|
|
27
28
|
* subagent through the subagents extension (lib/btw-fork.ts).
|
|
28
29
|
*/
|
|
29
30
|
|
|
30
|
-
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
31
31
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
32
32
|
import {
|
|
33
33
|
convertToLlm,
|
|
34
34
|
copyToClipboard,
|
|
35
35
|
type ExtensionAPI,
|
|
36
36
|
type ExtensionCommandContext,
|
|
37
|
+
type ExtensionContext,
|
|
37
38
|
getAgentDir,
|
|
38
39
|
SettingsManager,
|
|
39
40
|
UserMessageComponent,
|
|
@@ -43,7 +44,7 @@ import { btwForkedLine, type BtwForkResult, requestBtwFork } from "../lib/btw-fo
|
|
|
43
44
|
import { canShowCustomUi, notifyOrPrint, printAnswer } from "../lib/headless-output.ts";
|
|
44
45
|
import { announcePromptOptions } from "../lib/prompt-options.ts";
|
|
45
46
|
import { followLastExchange, replaySideCall } from "../lib/replay-call.ts";
|
|
46
|
-
import { answerText, stripImageBlocks, toolStubs, trimToTurnBoundary, withoutSystemMessages } from "../lib/side-call.ts";
|
|
47
|
+
import { answerText, branchMessages, stripImageBlocks, toolStubs, trimToTurnBoundary, withoutSystemMessages } from "../lib/side-call.ts";
|
|
47
48
|
import { boundedDockHeight, linesComponent, safeThemePaint, truncateLine } from "../lib/tui-render.ts";
|
|
48
49
|
import { CONVERSATION_CACHE_PLACEMENT } from "../lib/side-call-cache.ts";
|
|
49
50
|
import { releaseSideCalls, runSideCall } from "../lib/side-call-run.ts";
|
|
@@ -88,18 +89,23 @@ export default function btwExtension(pi: ExtensionAPI) {
|
|
|
88
89
|
return linesComponent((width) => [...row.render(width), truncateLine(` ⎿ ${paint("dim", btwForkedLine(data.name, data.taskId))}`, width)]);
|
|
89
90
|
});
|
|
90
91
|
|
|
91
|
-
// The
|
|
92
|
-
// mutations), captured like recap and the compaction extension do.
|
|
93
|
-
let capturedMessages: AgentMessage[] | undefined;
|
|
94
|
-
pi.on("context", (event) => {
|
|
95
|
-
capturedMessages = event.messages;
|
|
96
|
-
});
|
|
97
|
-
// The same request as it went on the wire, and the reply that answered it.
|
|
92
|
+
// The session's last request as it went on the wire, and the reply that answered it.
|
|
98
93
|
const exchange = followLastExchange(pi);
|
|
99
94
|
|
|
100
95
|
// The session's answered side questions, oldest first: sent before each new
|
|
101
|
-
// question and listed in the panel. `x` in the panel clears them.
|
|
102
|
-
|
|
96
|
+
// question and listed in the panel. `x` in the panel clears them. Each one
|
|
97
|
+
// keeps the entry the conversation stood at when it was asked, since it was
|
|
98
|
+
// answered from the conversation up to there.
|
|
99
|
+
let history: Array<BtwExchange & { askedAt: string | undefined }> = [];
|
|
100
|
+
/** The entry the branch ends at: where a side question is asked from (undefined in an empty session). */
|
|
101
|
+
const leafOf = (ctx: ExtensionContext): string | undefined => ctx.sessionManager.getBranch().at(-1)?.id;
|
|
102
|
+
/** Whether the selected branch holds the point a side question was asked from. */
|
|
103
|
+
const onBranch = (ctx: ExtensionContext, askedAt: string | undefined): boolean =>
|
|
104
|
+
askedAt === undefined || ctx.sessionManager.getBranch().some((entry) => entry.id === askedAt);
|
|
105
|
+
/** Thread an answer into the side history, unless a /tree since left the point it was asked from. */
|
|
106
|
+
const keep = (ctx: ExtensionContext, exchange: BtwExchange, askedAt: string | undefined) => {
|
|
107
|
+
if (exchange.answer && onBranch(ctx, askedAt)) history.push({ ...exchange, askedAt });
|
|
108
|
+
};
|
|
103
109
|
|
|
104
110
|
// The controller for an open panel's in-flight call. Aborted when the session
|
|
105
111
|
// is replaced or torn down, so a late resolve cannot repaint a disposed `tui`
|
|
@@ -116,7 +122,6 @@ export default function btwExtension(pi: ExtensionAPI) {
|
|
|
116
122
|
sessionEpoch++;
|
|
117
123
|
// Claude Code's `[question]` placeholder after a bare `/btw` in the prompt.
|
|
118
124
|
pi.events.emit(ARGUMENT_HINT_CHANNEL, { command: "btw", hint: "[question]" } satisfies ArgumentHint);
|
|
119
|
-
capturedMessages = undefined;
|
|
120
125
|
history = [];
|
|
121
126
|
inFlight?.abort();
|
|
122
127
|
inFlight = undefined;
|
|
@@ -127,6 +132,12 @@ export default function btwExtension(pi: ExtensionAPI) {
|
|
|
127
132
|
inFlight = undefined;
|
|
128
133
|
releaseSideCalls(ctx.sessionManager.getSessionId());
|
|
129
134
|
});
|
|
135
|
+
// A /tree keeps the side exchanges asked from a point the selected branch
|
|
136
|
+
// holds; the others were answered from the abandoned branch's conversation.
|
|
137
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
138
|
+
const ids = new Set(ctx.sessionManager.getBranch().map((entry) => entry.id));
|
|
139
|
+
history = history.filter((exchange) => exchange.askedAt === undefined || ids.has(exchange.askedAt));
|
|
140
|
+
});
|
|
130
141
|
|
|
131
142
|
// The answer renders as Markdown, like the transcript; plain wrapped text
|
|
132
143
|
// until the renderer loads, or if it cannot.
|
|
@@ -157,7 +168,9 @@ export default function btwExtension(pi: ExtensionAPI) {
|
|
|
157
168
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
158
169
|
if (!auth.ok) throw new Error("No API key is available for the current model.");
|
|
159
170
|
const tools = toolStubs(pi.getActiveTools(), STUB_REASON);
|
|
160
|
-
|
|
171
|
+
// The current branch, read now: a /tree or a compaction since the last
|
|
172
|
+
// request changed it, and pi fires no `context` event for either.
|
|
173
|
+
const context = trimToTurnBoundary(withoutSystemMessages(branchMessages(ctx)));
|
|
161
174
|
// btw runs on a cheap, possibly text-only reader and answers a text question,
|
|
162
175
|
// so images the conversation carried are stripped before the call
|
|
163
176
|
// (working-docs/decisions/model-policy.md).
|
|
@@ -194,6 +207,7 @@ export default function btwExtension(pi: ExtensionAPI) {
|
|
|
194
207
|
ctx.ui.notify("Ask a side question: /btw <question>", "info");
|
|
195
208
|
return;
|
|
196
209
|
}
|
|
210
|
+
const askedAt = leafOf(ctx);
|
|
197
211
|
// The panel can fork a background agent whose completion opens the
|
|
198
212
|
// session's first turn; this context carries the options that turn
|
|
199
213
|
// is built from (lib/prompt-options.ts).
|
|
@@ -208,7 +222,7 @@ export default function btwExtension(pi: ExtensionAPI) {
|
|
|
208
222
|
try {
|
|
209
223
|
const answer = await ask(ctx, question, history, controller.signal);
|
|
210
224
|
if (controller.signal.aborted) return;
|
|
211
|
-
|
|
225
|
+
keep(ctx, { question, answer }, askedAt);
|
|
212
226
|
if (ctx.mode === "rpc") ctx.ui.notify(answer || "(no answer)", "info");
|
|
213
227
|
else printAnswer(ctx, answer || "(no answer)");
|
|
214
228
|
} catch (error) {
|
|
@@ -259,7 +273,7 @@ export default function btwExtension(pi: ExtensionAPI) {
|
|
|
259
273
|
const answer = await ask(ctx, question, earlier, controller.signal);
|
|
260
274
|
if (controller.signal.aborted) return;
|
|
261
275
|
view.answer = answer;
|
|
262
|
-
|
|
276
|
+
keep(ctx, { question, answer }, askedAt);
|
|
263
277
|
} catch (error) {
|
|
264
278
|
if (controller.signal.aborted) return;
|
|
265
279
|
view.error = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cache-marks extension — keeps the previous request's conversation breakpoint
|
|
3
|
+
* on an Anthropic request, so a request that adds many blocks at once still
|
|
4
|
+
* reads the cache the previous one wrote (lib/previous-mark.ts has the why).
|
|
5
|
+
*
|
|
6
|
+
* The previous breakpoint comes from the ledger's plan, not from memory: inside
|
|
7
|
+
* an epoch the request extends a recorded base, and the base carried its tail
|
|
8
|
+
* mark on the last block before its trailing directive. So a resume, /reload,
|
|
9
|
+
* /tree and a pi cache warm mark the same block the base did.
|
|
10
|
+
*
|
|
11
|
+
* Load order: after the ledger's payload hook (extensions/ledger/wire.ts), the
|
|
12
|
+
* last extension that edits the messages. First-party Anthropic only: the
|
|
13
|
+
* Messages API is where the look-back was measured, and its four-breakpoint
|
|
14
|
+
* limit is checked here. `CC_CACHE_MARKS=0` turns it off, to compare a run with
|
|
15
|
+
* and without the mark.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import { looksLikeAnthropicRequest } from "../lib/anthropic-payload.ts";
|
|
20
|
+
import { isFirstPartyAnthropic } from "../lib/anthropic-server-tools.ts";
|
|
21
|
+
import { LEDGER_PLAN_CHANNEL, type RequestPlan } from "../lib/ledger/plan.ts";
|
|
22
|
+
import { baseMark, withPreviousMark } from "../lib/previous-mark.ts";
|
|
23
|
+
|
|
24
|
+
export default function cacheMarksExtension(pi: ExtensionAPI) {
|
|
25
|
+
if (process.env.CC_CACHE_MARKS === "0") return;
|
|
26
|
+
/** The plan of the request being sent; a warm reuses it. */
|
|
27
|
+
let plan: RequestPlan | undefined;
|
|
28
|
+
pi.events.on(LEDGER_PLAN_CHANNEL, (data) => {
|
|
29
|
+
plan = data as RequestPlan;
|
|
30
|
+
});
|
|
31
|
+
pi.on("session_start", () => {
|
|
32
|
+
plan = undefined;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
36
|
+
const payload = event.payload as Record<string, unknown>;
|
|
37
|
+
if (!ctx.model || !isFirstPartyAnthropic(ctx.model) || !looksLikeAnthropicRequest(payload)) return undefined;
|
|
38
|
+
const base = plan && !plan.epoch ? plan.base : undefined;
|
|
39
|
+
const next = withPreviousMark(payload, base ? baseMark(payload.messages, base.list, ctx.model.api) : undefined);
|
|
40
|
+
return next === payload ? undefined : next;
|
|
41
|
+
});
|
|
42
|
+
}
|