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
|
@@ -143,10 +143,6 @@ export function missingArgumentsNote(argumentHint: string): { before: string; af
|
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
/** Fast pre-check before the regex walk; also the marker `redactOffSkillText` scans for. */
|
|
147
|
-
const SKILL_BLOCK_START = '<skill name="';
|
|
148
|
-
const SKILL_BLOCK_RE = /<skill name="([^"]+)"[^>]*>[\s\S]*?<\/skill>/g;
|
|
149
|
-
|
|
150
146
|
/**
|
|
151
147
|
* The hidden message a `/skill:` command is delivered as. `details.input` is
|
|
152
148
|
* the text the user typed, so an extension that saw that text at `input` (the
|
|
@@ -160,69 +156,6 @@ export interface SkillInvocationDetails {
|
|
|
160
156
|
input?: string;
|
|
161
157
|
}
|
|
162
158
|
|
|
163
|
-
export const offSkillNotice = (name: string): string =>
|
|
164
|
-
`[Skill "${name}" is turned off — its instructions were removed. The user can re-enable it from /skills or /plugins.]`;
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Replace every `<skill>` block belonging to a turned-off skill with a short
|
|
168
|
-
* refusal notice. Returns the rewritten text, or undefined when nothing
|
|
169
|
-
* needed redacting (so callers keep the original object untouched).
|
|
170
|
-
*
|
|
171
|
-
* This is the fail-closed backstop behind the `input`-hook interception:
|
|
172
|
-
* pi's `steer()`/`followUp()` expand `/skill:<name>` natively WITHOUT firing
|
|
173
|
-
* the `input` event (queued interactive messages after the first, RPC
|
|
174
|
-
* steer/followUp), so an off skill's instructions can land in the session
|
|
175
|
-
* history. Redacting on the `context` hook strips them from every outgoing
|
|
176
|
-
* request — the session file keeps the original bytes, and the rewrite is
|
|
177
|
-
* deterministic for a fixed override state, so the request prefix stays
|
|
178
|
-
* byte-stable across turns (prompt-cache friendly).
|
|
179
|
-
*/
|
|
180
|
-
export function redactOffSkillText(text: string, isOff: (name: string) => boolean): string | undefined {
|
|
181
|
-
if (!text.includes(SKILL_BLOCK_START)) return undefined;
|
|
182
|
-
let changed = false;
|
|
183
|
-
const redacted = text.replace(SKILL_BLOCK_RE, (block, name: string) => {
|
|
184
|
-
if (!isOff(name)) return block;
|
|
185
|
-
changed = true;
|
|
186
|
-
return offSkillNotice(name);
|
|
187
|
-
});
|
|
188
|
-
return changed ? redacted : undefined;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Apply redactOffSkillText across a request's messages (user and custom roles,
|
|
193
|
-
* string or text-block content). Returns a rewritten copy, or undefined when
|
|
194
|
-
* no message needed redacting — callers must then keep the original array so
|
|
195
|
-
* untouched requests stay byte-identical.
|
|
196
|
-
*/
|
|
197
|
-
export function redactOffSkillMessages<M extends { role: string; content?: unknown }>(
|
|
198
|
-
messages: readonly M[],
|
|
199
|
-
isOff: (name: string) => boolean,
|
|
200
|
-
): M[] | undefined {
|
|
201
|
-
let changed = false;
|
|
202
|
-
const out = messages.map((message) => {
|
|
203
|
-
if (message.role !== "user" && message.role !== "custom") return message;
|
|
204
|
-
if (typeof message.content === "string") {
|
|
205
|
-
const redacted = redactOffSkillText(message.content, isOff);
|
|
206
|
-
if (redacted === undefined) return message;
|
|
207
|
-
changed = true;
|
|
208
|
-
return { ...message, content: redacted };
|
|
209
|
-
}
|
|
210
|
-
if (!Array.isArray(message.content)) return message;
|
|
211
|
-
let blockChanged = false;
|
|
212
|
-
const blocks = message.content.map((block: { type?: string; text?: string }) => {
|
|
213
|
-
if (block?.type !== "text" || typeof block.text !== "string") return block;
|
|
214
|
-
const redacted = redactOffSkillText(block.text, isOff);
|
|
215
|
-
if (redacted === undefined) return block;
|
|
216
|
-
blockChanged = true;
|
|
217
|
-
return { ...block, text: redacted };
|
|
218
|
-
});
|
|
219
|
-
if (!blockChanged) return message;
|
|
220
|
-
changed = true;
|
|
221
|
-
return { ...message, content: blocks };
|
|
222
|
-
});
|
|
223
|
-
return changed ? out : undefined;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
159
|
// ---------------------------------------------------------------------------
|
|
227
160
|
// Bare `/<skill>` commands (Claude Code's invocation shape)
|
|
228
161
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Subdirectories are searched recursively.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
17
|
+
import { readdirSync, readFileSync, type Stats, statSync } from "node:fs";
|
|
18
18
|
import { basename, dirname, join } from "node:path";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { parseFrontmatterLoosely } from "../lib/frontmatter.ts";
|
|
@@ -42,32 +42,54 @@ export interface AgentDefinition {
|
|
|
42
42
|
source: string;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
/** Visit every `.md` file under `dir`, at any depth, with its stats. */
|
|
46
|
+
function walkMarkdownFiles(dir: string, visit: (path: string, stats: Stats) => void): void {
|
|
46
47
|
let entries: string[];
|
|
47
48
|
try {
|
|
48
49
|
entries = readdirSync(dir);
|
|
49
50
|
} catch {
|
|
50
|
-
return
|
|
51
|
+
return; // missing or unreadable directory: nothing to discover
|
|
51
52
|
}
|
|
52
53
|
for (const entry of entries) {
|
|
53
54
|
const full = join(dir, entry);
|
|
54
55
|
// A dangling symlink or an unreadable entry must not take the whole
|
|
55
56
|
// catalog (and every Agent call) down with it — skip it (review L1).
|
|
56
|
-
let
|
|
57
|
+
let stats: Stats;
|
|
57
58
|
try {
|
|
58
|
-
|
|
59
|
+
stats = statSync(full);
|
|
59
60
|
} catch {
|
|
60
61
|
continue;
|
|
61
62
|
}
|
|
62
|
-
if (isDirectory) {
|
|
63
|
-
|
|
63
|
+
if (stats.isDirectory()) {
|
|
64
|
+
walkMarkdownFiles(full, visit);
|
|
64
65
|
} else if (entry.endsWith(".md")) {
|
|
65
|
-
|
|
66
|
+
visit(full, stats);
|
|
66
67
|
}
|
|
67
68
|
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function collectMarkdownFiles(dir: string): string[] {
|
|
72
|
+
const out: string[] = [];
|
|
73
|
+
walkMarkdownFiles(dir, (path) => out.push(path));
|
|
68
74
|
return out;
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* A stamp of the agent files `discoverAgents` would read from these sources:
|
|
79
|
+
* each source, then each file's path, times and size. It changes when a file
|
|
80
|
+
* is added, removed, renamed or written, so a catalog built from the sources
|
|
81
|
+
* can be reused while the stamp holds, without reading or parsing any file.
|
|
82
|
+
*/
|
|
83
|
+
export function agentSourcesStamp(sources: Array<string | AgentSource>): string {
|
|
84
|
+
const parts: string[] = [];
|
|
85
|
+
for (const source of sources) {
|
|
86
|
+
const { dir, namespace } = typeof source === "string" ? { dir: source, namespace: undefined } : source;
|
|
87
|
+
parts.push(`${namespace ?? ""}@${dir}`);
|
|
88
|
+
walkMarkdownFiles(dir, (path, stats) => parts.push(`${path}:${stats.mtimeMs}:${stats.ctimeMs}:${stats.size}`));
|
|
89
|
+
}
|
|
90
|
+
return parts.join("\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
71
93
|
/**
|
|
72
94
|
* Split one frontmatter tool string the way Claude Code does: on commas and
|
|
73
95
|
* spaces, except inside a rule's parentheses, so `Bash(git push:*), Write` is
|
|
@@ -26,10 +26,13 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
28
28
|
import { DEFER_CHANNEL } from "../lib/deferred.ts";
|
|
29
|
-
import { type AffinityModel, affinityHeaders, captureMatches, forkOutputRoom, forkRequestPayload, MIN_REPLAY_OUTPUT_TOKENS, type RequestCapture,
|
|
29
|
+
import { type AffinityModel, affinityHeaders, captureMatches, forkOutputRoom, forkRequestPayload, forkBaselineNames, MIN_REPLAY_OUTPUT_TOKENS, type RequestCapture, uncoveredMessages } from "../lib/request-replay.ts";
|
|
30
30
|
|
|
31
31
|
type ModelRef = { api?: string; provider?: string; id?: string };
|
|
32
32
|
|
|
33
|
+
/** The APIs whose fork splice (`forkRequestPayload`) handles the body's shape. */
|
|
34
|
+
const SPLICED_FORK_APIS: ReadonlySet<string> = new Set(["anthropic-messages", "openai-completions", "openai-responses", "azure-openai-responses", "openai-codex-responses"]);
|
|
35
|
+
|
|
33
36
|
export function forkCacheExtension(capture: RequestCapture) {
|
|
34
37
|
return (pi: ExtensionAPI) => {
|
|
35
38
|
// The fork advertises the parent's tools, where SendMessage is deferred
|
|
@@ -46,7 +49,10 @@ export function forkCacheExtension(capture: RequestCapture) {
|
|
|
46
49
|
pi.on("context_with_system", (event, ctx) => {
|
|
47
50
|
shaped = false;
|
|
48
51
|
const covers = capture.covers;
|
|
49
|
-
|
|
52
|
+
// Bedrock and Google nest their tools and need joined turns and the
|
|
53
|
+
// fork's own abort signal, which the fork splice does not handle: their
|
|
54
|
+
// forks go out whole instead.
|
|
55
|
+
if (!covers || !SPLICED_FORK_APIS.has(capture.api) || !captureMatches(capture, ctx.model as ModelRef | undefined)) return undefined;
|
|
50
56
|
const tail = uncoveredMessages(event.messages as { role: string; timestamp?: number }[], covers);
|
|
51
57
|
if (!tail) return undefined;
|
|
52
58
|
// Too little output room left after the prefix: send the fork whole
|
|
@@ -69,7 +75,7 @@ export function forkCacheExtension(capture: RequestCapture) {
|
|
|
69
75
|
if (!shaped || !captureMatches(capture, ctx.model as ModelRef | undefined)) return undefined;
|
|
70
76
|
shaped = false;
|
|
71
77
|
const payload = event.payload as Record<string, unknown>;
|
|
72
|
-
baseline ??=
|
|
78
|
+
baseline ??= forkBaselineNames(payload, capture.api);
|
|
73
79
|
return forkRequestPayload(capture, payload, baseline);
|
|
74
80
|
});
|
|
75
81
|
};
|