one-code-extension 0.1.4 → 0.3.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/LICENSE +20 -0
- package/README.md +200 -172
- package/agents/explore.md +9 -6
- package/agents/general-purpose.md +1 -1
- package/agents/plan.md +6 -4
- package/docs/guide/README.md +52 -0
- package/docs/guide/bring-your-claude-code-setup.md +126 -0
- package/docs/guide/configuration.md +171 -0
- package/docs/guide/differences-from-claude-code.md +150 -0
- package/docs/guide/doctor.md +86 -0
- package/docs/guide/hooks.md +109 -0
- package/docs/guide/installation.md +124 -0
- package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
- package/docs/guide/providers-and-models.md +194 -0
- package/docs/guide/reference.md +224 -0
- package/docs/guide/sessions-and-context.md +190 -0
- package/docs/guide/skills-plugins-and-mcp.md +176 -0
- package/docs/guide/subagents-and-workflows.md +193 -0
- package/docs/guide/tasks-and-background-work.md +111 -0
- package/docs/guide/terminal-interface.md +155 -0
- package/docs/guide/tools.md +175 -0
- package/docs/guide/troubleshooting.md +175 -0
- package/extensions/ask-user/index.ts +65 -71
- package/extensions/ask-user/questions.ts +15 -28
- package/extensions/ask-user/widget.ts +533 -0
- package/extensions/auto-mode/actions.ts +9 -26
- package/extensions/auto-mode/classifier-prompt.ts +127 -0
- package/extensions/auto-mode/classifier.ts +305 -227
- package/extensions/auto-mode/config.ts +279 -77
- package/extensions/auto-mode/defaults.ts +17 -60
- package/extensions/auto-mode/instructions.ts +2 -1
- package/extensions/auto-mode/model-picker.ts +53 -31
- package/extensions/auto-mode/model-select.ts +153 -82
- package/extensions/auto-mode/paths.ts +13 -1
- package/extensions/auto-mode/pause.ts +8 -0
- package/extensions/auto-mode/prompt.ts +205 -238
- package/extensions/auto-mode/recoverability.ts +229 -0
- package/extensions/auto-mode/rules.ts +92 -75
- package/extensions/auto-mode/safety-floor.ts +43 -12
- package/extensions/auto-mode/sensitive.ts +11 -0
- package/extensions/auto-mode/setup-run.ts +245 -0
- package/extensions/auto-mode/setup.ts +235 -0
- package/extensions/auto-mode/shell-analysis.ts +332 -26
- package/extensions/auto-mode/transcript.ts +102 -0
- package/extensions/background/index.ts +256 -43
- package/extensions/background/monitor-batch.ts +58 -0
- package/extensions/background/registry.ts +9 -0
- package/extensions/background/wakeup.ts +45 -0
- package/extensions/bash/background.ts +47 -22
- package/extensions/bash/guards.ts +229 -0
- package/extensions/bash/index.ts +81 -19
- package/extensions/branding/index.ts +44 -18
- package/extensions/branding/startup.ts +23 -8
- package/extensions/claude-compat/index.ts +17 -2
- package/extensions/claude-context/index.ts +41 -12
- package/extensions/clear/index.ts +27 -0
- package/extensions/compaction/fit.ts +121 -0
- package/extensions/compaction/index.ts +176 -71
- package/extensions/compaction/prompt.ts +21 -1
- package/extensions/context-budget/budget.ts +61 -0
- package/extensions/context-budget/index.ts +66 -0
- package/extensions/context-management/index.ts +40 -16
- package/extensions/doctor/build.ts +226 -0
- package/extensions/doctor/cli.ts +99 -0
- package/extensions/doctor/compat.ts +532 -0
- package/extensions/doctor/dependencies.ts +186 -0
- package/extensions/doctor/fix-prompt.ts +125 -0
- package/extensions/doctor/index.ts +308 -0
- package/extensions/doctor/models.ts +231 -0
- package/extensions/doctor/presets.ts +208 -0
- package/extensions/doctor/report.ts +189 -0
- package/extensions/doctor/update-lookup.ts +46 -0
- package/extensions/doctor/viewer.ts +124 -0
- package/extensions/effort/index.ts +41 -4
- package/extensions/effort/slider.ts +33 -2
- package/extensions/file-tracker/index.ts +143 -20
- package/extensions/file-tracker/replay.ts +46 -0
- package/extensions/file-tracker/tracker.ts +51 -4
- package/extensions/footer/footer-line.ts +207 -0
- package/extensions/footer/index.ts +152 -0
- package/extensions/footer/pr.ts +53 -0
- package/extensions/hooks/executor.ts +57 -20
- package/extensions/hooks/formatter-notice.ts +76 -0
- package/extensions/hooks/index.ts +314 -73
- package/extensions/hooks/matcher.ts +35 -1
- package/extensions/hooks/plugin-hooks.ts +3 -3
- package/extensions/hooks/protocol.ts +58 -0
- package/extensions/hooks/subagent-bridge.ts +77 -0
- package/extensions/hooks/trust.ts +15 -4
- package/extensions/init/index.ts +26 -0
- package/extensions/init/prompt.ts +40 -0
- package/extensions/interrupted/index.ts +35 -0
- package/extensions/interrupted/line.ts +9 -0
- package/extensions/lib/abort.ts +15 -0
- package/extensions/lib/agent-loader.ts +183 -0
- package/extensions/lib/anthropic-payload.ts +15 -0
- package/extensions/lib/atomic-write.ts +56 -0
- package/extensions/lib/bridge-watch.ts +20 -0
- package/extensions/lib/cache-retention.ts +60 -0
- package/extensions/lib/capability-index.ts +466 -0
- package/extensions/lib/claude-context.ts +318 -27
- package/extensions/lib/claude-settings.ts +64 -0
- package/extensions/lib/consent-preview.ts +30 -0
- package/extensions/lib/deferred.ts +144 -13
- package/extensions/lib/favorites.ts +43 -0
- package/extensions/lib/fetch-timeout.ts +15 -0
- package/extensions/lib/frontmatter.ts +31 -0
- package/extensions/lib/git.ts +44 -2
- package/extensions/lib/hook-gate.ts +90 -0
- package/extensions/lib/interrupt.ts +94 -0
- package/extensions/lib/mcp-overrides.ts +61 -0
- package/extensions/lib/mcp-share.ts +25 -0
- package/extensions/lib/mcp-status.ts +34 -0
- package/extensions/lib/memory.ts +41 -6
- package/extensions/lib/model-facts.generated.json +2480 -0
- package/extensions/lib/model-facts.ts +162 -0
- package/extensions/lib/model-policy.ts +145 -189
- package/extensions/lib/model-tier.ts +480 -64
- package/extensions/lib/model-unusable.ts +37 -0
- package/extensions/lib/notifications.ts +320 -1
- package/extensions/lib/one-code-settings.ts +63 -0
- package/extensions/lib/original-command.ts +71 -0
- package/extensions/lib/package-version.ts +31 -0
- package/extensions/lib/paths.ts +39 -4
- package/extensions/lib/permission-gate.ts +192 -0
- package/extensions/lib/persisted-output.ts +18 -0
- package/extensions/lib/pi-ai-estimate.ts +160 -0
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/plan-mode-channels.ts +11 -0
- package/extensions/lib/plugin-overrides.ts +24 -0
- package/extensions/lib/plugin-root.ts +44 -0
- package/extensions/lib/plugins.ts +233 -54
- package/extensions/lib/prefix-warm-gate.ts +157 -0
- package/extensions/lib/process-tree.ts +56 -0
- package/extensions/lib/reminders.ts +321 -68
- package/extensions/lib/scratchpad.ts +11 -8
- package/extensions/lib/session-lifecycle.ts +28 -0
- package/extensions/lib/settings-channels.ts +14 -0
- package/extensions/lib/shell-tasks.ts +54 -0
- package/extensions/lib/skill-overrides.ts +86 -0
- package/extensions/lib/skill-scan.ts +110 -0
- package/extensions/lib/text-width.ts +267 -0
- package/extensions/lib/tui-render.ts +281 -54
- package/extensions/lib/usage-bus.ts +75 -0
- package/extensions/lib/usage-tracker.ts +67 -0
- package/extensions/lib/worktree-isolation.ts +170 -0
- package/extensions/lsp/client.ts +69 -12
- package/extensions/lsp/format.ts +12 -2
- package/extensions/lsp/index.ts +273 -84
- package/extensions/lsp/install-hints.ts +43 -0
- package/extensions/lsp/keep-alive.ts +19 -0
- package/extensions/lsp/plugin-servers.ts +331 -0
- package/extensions/lsp/watcher.ts +157 -0
- package/extensions/mcp/announce.ts +99 -0
- package/extensions/mcp/client.ts +181 -22
- package/extensions/mcp/config.ts +55 -24
- package/extensions/mcp/index.ts +586 -86
- package/extensions/mcp/oauth/browser.ts +26 -0
- package/extensions/mcp/oauth/callback.ts +104 -0
- package/extensions/mcp/oauth/flow.ts +68 -0
- package/extensions/mcp/oauth/provider.ts +147 -0
- package/extensions/mcp/oauth/store.ts +86 -0
- package/extensions/mcp/panel/keys.ts +34 -0
- package/extensions/mcp/panel/model.ts +62 -0
- package/extensions/mcp/panel/render.ts +141 -0
- package/extensions/mcp/panel/state.ts +108 -0
- package/extensions/mcp/schema.ts +83 -6
- package/extensions/mcp/trust.ts +340 -0
- package/extensions/memory/entries.ts +163 -0
- package/extensions/memory/index.ts +119 -2
- package/extensions/memory/open-external.ts +97 -0
- package/extensions/memory/panel.ts +124 -0
- package/extensions/model-default/index.ts +74 -0
- package/extensions/notebook/index.ts +36 -18
- package/extensions/notebook/notebook.ts +110 -17
- package/extensions/permissions/index.ts +1012 -247
- package/extensions/permissions/matcher.ts +488 -57
- package/extensions/permissions/modes.ts +55 -7
- package/extensions/permissions/project-trust.ts +81 -0
- package/extensions/permissions/protected-paths.ts +16 -3
- package/extensions/permissions/rule-prose.ts +79 -0
- package/extensions/permissions/session-grant.ts +91 -0
- package/extensions/permissions/settings.ts +112 -33
- package/extensions/permissions/subagent-gate.ts +57 -0
- package/extensions/plan-mode/index.ts +143 -33
- package/extensions/plan-mode/reminder.ts +16 -19
- package/extensions/plan-mode/viewer.ts +40 -31
- package/extensions/plugins/counts.ts +68 -0
- package/extensions/plugins/index.ts +496 -32
- package/extensions/plugins/install/install.ts +167 -0
- package/extensions/plugins/install/paths.ts +23 -0
- package/extensions/plugins/install/registry.ts +72 -0
- package/extensions/plugins/marketplace/git.ts +103 -0
- package/extensions/plugins/marketplace/official.ts +36 -0
- package/extensions/plugins/marketplace/parse.ts +94 -0
- package/extensions/plugins/marketplace/registry.ts +60 -0
- package/extensions/plugins/marketplace/sync.ts +146 -0
- package/extensions/plugins/marketplace/types.ts +128 -0
- package/extensions/plugins/panel/keys.ts +52 -0
- package/extensions/plugins/panel/render.ts +315 -0
- package/extensions/plugins/panel/rows.ts +230 -0
- package/extensions/plugins/panel/state.ts +333 -0
- package/extensions/recap/index.ts +215 -0
- package/extensions/recap/prompt.ts +44 -0
- package/extensions/recap/scheduler.ts +87 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +7 -5
- package/extensions/skill/index.ts +360 -34
- package/extensions/skill/invoke.ts +185 -0
- package/extensions/skill/listing.ts +21 -0
- package/extensions/skill/panel/keys.ts +41 -0
- package/extensions/skill/panel/render.ts +91 -0
- package/extensions/skill/panel/state.ts +115 -0
- package/extensions/spinner/index.ts +110 -0
- package/extensions/spinner/line.ts +51 -0
- package/extensions/spinner/verbs.ts +201 -0
- package/extensions/subagents/agents.ts +88 -44
- package/extensions/subagents/default-model.ts +62 -32
- package/extensions/subagents/delegation-steer.ts +23 -0
- package/extensions/subagents/hand-back-review.ts +67 -0
- package/extensions/subagents/index.ts +1545 -346
- package/extensions/subagents/live-runs.ts +293 -0
- package/extensions/subagents/model-select.ts +252 -82
- package/extensions/subagents/outcome.ts +90 -0
- package/extensions/subagents/panel-keys.ts +41 -0
- package/extensions/subagents/panel-render.ts +357 -0
- package/extensions/subagents/panel-widget.ts +330 -0
- package/extensions/subagents/pending-claim.ts +45 -0
- package/extensions/subagents/prose.ts +105 -0
- package/extensions/subagents/runner.ts +674 -0
- package/extensions/subagents/runs.ts +68 -1
- package/extensions/subagents/send-to-main-tool.ts +55 -0
- package/extensions/subagents/session-turns.ts +138 -0
- package/extensions/subagents/shell-panel.ts +198 -0
- package/extensions/subagents/usage.ts +16 -4
- package/extensions/subagents/worktree.ts +13 -0
- package/extensions/system-prompt/environment.ts +5 -3
- package/extensions/system-prompt/git-status.ts +93 -0
- package/extensions/system-prompt/index.ts +23 -1
- package/extensions/system-prompt/template.ts +34 -4
- package/extensions/system-prompt/tiers/common.ts +31 -6
- package/extensions/system-prompt/tiers/low.ts +13 -3
- package/extensions/system-prompt/tiers/mid.ts +21 -8
- package/extensions/system-reminder/index.ts +53 -9
- package/extensions/tasks/index.ts +65 -16
- package/extensions/tasks/store.ts +76 -3
- package/extensions/tool-search/announce.ts +42 -0
- package/extensions/tool-search/index.ts +136 -13
- package/extensions/turn-duration/index.ts +60 -0
- package/extensions/turn-duration/line.ts +22 -0
- package/extensions/turn-duration/span.ts +41 -0
- package/extensions/turn-duration/verbs.ts +24 -0
- package/extensions/web/backends.ts +421 -0
- package/extensions/web/index.ts +178 -21
- package/extensions/web-fetch/index.ts +74 -18
- package/extensions/web-fetch/summarize.ts +1 -1
- package/extensions/workflow/agent-session.ts +182 -112
- package/extensions/workflow/globals.ts +129 -106
- package/extensions/workflow/index.ts +343 -28
- package/extensions/workflow/records.ts +118 -0
- package/extensions/workflow/run-manager.ts +80 -31
- package/extensions/workflow/saved-workflows.ts +2 -1
- package/extensions/workflow/script-worker.mjs +110 -0
- package/extensions/workflow/types.ts +74 -8
- package/extensions/workflow/viewer.ts +562 -0
- package/extensions/workflow/vm-runtime.ts +118 -22
- package/extensions/workflow/widget.ts +126 -18
- package/extensions/worktree/guards.ts +240 -0
- package/extensions/worktree/index.ts +30 -5
- package/extensions/worktree/rewrite.ts +32 -16
- package/package.json +20 -10
- package/skills/code-review/SKILL.md +165 -0
- package/skills/fewer-permission-prompts/SKILL.md +61 -0
- package/skills/security-review/SKILL.md +178 -0
- package/skills/simplify/SKILL.md +70 -0
- package/themes/{one-code-light.json → onecode-light.json} +6 -1
- package/themes/{one-code.json → onecode.json} +6 -1
- package/types/app.d.ts +8 -1
- package/types/community-packages.d.ts +45 -2
- package/docs/decisions/auto-mode.md +0 -526
- package/docs/decisions/branding.md +0 -127
- package/docs/decisions/code-review-remediation.md +0 -106
- package/docs/decisions/compaction.md +0 -140
- package/docs/decisions/distribution.md +0 -168
- package/docs/decisions/hooks.md +0 -64
- package/docs/decisions/lsp.md +0 -58
- package/docs/decisions/mcp.md +0 -104
- package/docs/decisions/memory-state.md +0 -170
- package/docs/decisions/model-policy.md +0 -126
- package/docs/decisions/modes.md +0 -178
- package/docs/decisions/skills-plugins.md +0 -56
- package/docs/decisions/subagents-workflows.md +0 -225
- package/docs/decisions/system-prompt.md +0 -98
- package/docs/decisions/tools.md +0 -407
- package/docs/decisions/tui.md +0 -217
- package/docs/decisions.md +0 -148
- package/extensions/subagents/child.ts +0 -414
- package/extensions/subagents/rpc-turns.ts +0 -123
- package/extensions/todo/index.ts +0 -133
- package/extensions/workflow/permission-gate.ts +0 -71
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recap extension — Claude Code's "while you were away" summary (the ※ line).
|
|
3
|
+
*
|
|
4
|
+
* CC fires it when the terminal has been blurred for 5 minutes; pi exposes no
|
|
5
|
+
* terminal focus/blur event, so the closest faithful trigger is an idle timer:
|
|
6
|
+
* armed when a turn ends, reset on any keystroke (onTerminalInput) and cleared
|
|
7
|
+
* while a turn runs, it fires after CC's 5-minute lull with no interaction. At
|
|
8
|
+
* most one recap per user turn (firedSinceTurn), matching CC's
|
|
9
|
+
* hasSummarySinceLastUserTurn guard.
|
|
10
|
+
*
|
|
11
|
+
* Generation mirrors CC's awaySummary.ts: a cheap same-containment model
|
|
12
|
+
* (pickEconomicalContainedModel — the getSmallFastModel analog), CC's verbatim
|
|
13
|
+
* prompt, and only the last 30 messages. It deliberately does NOT reuse the
|
|
14
|
+
* session prompt cache the way compaction does — CC makes a small standalone
|
|
15
|
+
* call here (skipCacheWrite). The result is a display-only entry (appendEntry,
|
|
16
|
+
* not in LLM context). Best-effort throughout: any failure just shows nothing.
|
|
17
|
+
*
|
|
18
|
+
* Deviations from CC, logged in docs/decisions: the session-memory block is
|
|
19
|
+
* omitted (decoupling), and name-only stubs of the active tools are sent
|
|
20
|
+
* rather than an empty tool list — some providers reject a history carrying
|
|
21
|
+
* tool_use blocks with no tools declared, and the small-token instruction keeps
|
|
22
|
+
* the model answering in text rather than calling one. A failed recap does not
|
|
23
|
+
* retry until the next turn (one attempt per turn, success or not). CC_RECAP=0
|
|
24
|
+
* opts out; CC_RECAP_IDLE_MS overrides the 5-minute delay.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
28
|
+
import type { Api, Model, Tool } from "@earendil-works/pi-ai";
|
|
29
|
+
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
30
|
+
import { convertToLlm, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
31
|
+
import { withReasoningFallback } from "../lib/model-policy.ts";
|
|
32
|
+
import { recordUsage } from "../lib/usage-bus.ts";
|
|
33
|
+
import { pickEconomicalContainedModel } from "../lib/model-tier.ts";
|
|
34
|
+
import { dimMarkedLine } from "../lib/tui-render.ts";
|
|
35
|
+
import { RECAP_PROMPT, recapLine, recentForRecap, REFERENCE_MARK } from "./prompt.ts";
|
|
36
|
+
import { RecapScheduler } from "./scheduler.ts";
|
|
37
|
+
|
|
38
|
+
const ENTRY_TYPE = "one-code:recap";
|
|
39
|
+
const DEFAULT_IDLE_MS = 5 * 60_000; // CC's BLUR_DELAY_MS
|
|
40
|
+
const RECAP_MAX_TOKENS = 256; // 1-3 short sentences
|
|
41
|
+
const RECAP_TIMEOUT_MS = 30_000;
|
|
42
|
+
|
|
43
|
+
/** The idle delay before a recap, overridable via CC_RECAP_IDLE_MS (floor 1s). */
|
|
44
|
+
function idleMs(): number {
|
|
45
|
+
const raw = Number(process.env.CC_RECAP_IDLE_MS);
|
|
46
|
+
return Number.isFinite(raw) && raw >= 1000 ? raw : DEFAULT_IDLE_MS;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface RecapData {
|
|
50
|
+
content: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Name-only tool declarations: enough for a provider to accept the tool_use
|
|
55
|
+
* blocks already in the history, without the descriptions and schemas (the
|
|
56
|
+
* recap never calls a tool, and its prompt asks for text).
|
|
57
|
+
*/
|
|
58
|
+
export function toolStubs(names: readonly string[]): Tool[] {
|
|
59
|
+
return names.map(
|
|
60
|
+
(name) =>
|
|
61
|
+
({
|
|
62
|
+
name,
|
|
63
|
+
description: "Unavailable during this summary; answer in text.",
|
|
64
|
+
parameters: { type: "object", properties: {} },
|
|
65
|
+
}) as unknown as Tool,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default function recapExtension(pi: ExtensionAPI) {
|
|
70
|
+
pi.registerEntryRenderer<RecapData>(ENTRY_TYPE, (entry, _options, theme) => {
|
|
71
|
+
const content = entry.data?.content;
|
|
72
|
+
if (!content) return undefined;
|
|
73
|
+
return dimMarkedLine(theme, REFERENCE_MARK, recapLine(content));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// The exact request messages the session last sent (after every extension's
|
|
77
|
+
// mutations), captured like the compaction extension does.
|
|
78
|
+
let capturedMessages: AgentMessage[] | undefined;
|
|
79
|
+
pi.on("context", (event) => {
|
|
80
|
+
capturedMessages = event.messages;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let lastCtx: ExtensionContext | undefined;
|
|
84
|
+
let inFlight: AbortController | undefined;
|
|
85
|
+
let inputHookRegistered = false;
|
|
86
|
+
|
|
87
|
+
const abortInFlight = () => {
|
|
88
|
+
inFlight?.abort();
|
|
89
|
+
inFlight = undefined;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const scheduler = new RecapScheduler(
|
|
93
|
+
{
|
|
94
|
+
set: (cb, ms) => {
|
|
95
|
+
const handle = setTimeout(cb, ms);
|
|
96
|
+
handle.unref?.();
|
|
97
|
+
return handle;
|
|
98
|
+
},
|
|
99
|
+
clear: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
100
|
+
},
|
|
101
|
+
idleMs,
|
|
102
|
+
() => process.env.CC_RECAP !== "0",
|
|
103
|
+
() => void generate(),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
async function generate() {
|
|
107
|
+
const ctx = lastCtx;
|
|
108
|
+
const messages = capturedMessages;
|
|
109
|
+
if (!ctx?.hasUI || !messages?.length || scheduler.hasFiredSinceTurn) return;
|
|
110
|
+
abortInFlight();
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
inFlight = controller;
|
|
113
|
+
try {
|
|
114
|
+
const model = ctx.model;
|
|
115
|
+
if (!model) return;
|
|
116
|
+
const choice = pickEconomicalContainedModel(ctx.modelRegistry.getAvailable(), model);
|
|
117
|
+
if (!choice) return;
|
|
118
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(choice.model);
|
|
119
|
+
if (!auth.ok) return;
|
|
120
|
+
const baseUrl = (auth as { baseUrl?: string }).baseUrl;
|
|
121
|
+
|
|
122
|
+
// Name-only stubs of the active tools, so a history carrying tool_use
|
|
123
|
+
// blocks stays valid on strict providers (see the header note) without
|
|
124
|
+
// shipping every full schema (~6k tokens) to a call that must answer
|
|
125
|
+
// in text.
|
|
126
|
+
const tools = toolStubs(pi.getActiveTools());
|
|
127
|
+
|
|
128
|
+
const recent = recentForRecap(messages);
|
|
129
|
+
const recapMessages = [...convertToLlm(recent), { role: "user" as const, content: RECAP_PROMPT, timestamp: Date.now() }];
|
|
130
|
+
// Thinking off unless the model cannot disable it; withReasoningFallback
|
|
131
|
+
// sends a level up front for catalog-marked models and retries on the 400
|
|
132
|
+
// for the rest. Fires on a 5-min idle timer, so no cross-call memo.
|
|
133
|
+
const result = await withReasoningFallback(choice.model, (reasoning) => {
|
|
134
|
+
const timeout = AbortSignal.timeout(RECAP_TIMEOUT_MS);
|
|
135
|
+
return completeSimple(
|
|
136
|
+
baseUrl ? ({ ...choice.model, baseUrl } as Model<Api>) : choice.model,
|
|
137
|
+
{ systemPrompt: "", messages: recapMessages, tools },
|
|
138
|
+
{
|
|
139
|
+
apiKey: auth.apiKey,
|
|
140
|
+
headers: auth.headers,
|
|
141
|
+
env: auth.env,
|
|
142
|
+
signal: AbortSignal.any([controller.signal, timeout]),
|
|
143
|
+
maxTokens: RECAP_MAX_TOKENS,
|
|
144
|
+
...(reasoning ? { reasoning } : {}),
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
}, undefined, (usage) => recordUsage(pi, "recap", usage));
|
|
148
|
+
if (controller.signal.aborted) return;
|
|
149
|
+
const content = result.content
|
|
150
|
+
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
|
151
|
+
.map((block) => block.text)
|
|
152
|
+
.join("\n")
|
|
153
|
+
.trim();
|
|
154
|
+
if (!content) return;
|
|
155
|
+
scheduler.markFired();
|
|
156
|
+
pi.appendEntry<RecapData>(ENTRY_TYPE, { content });
|
|
157
|
+
} catch {
|
|
158
|
+
// Best-effort: a failed recap shows nothing — and does not retry. Without
|
|
159
|
+
// this the failure re-arms on every keystroke (interacted() → arm()),
|
|
160
|
+
// hammering the provider once per idle period until a turn resets it.
|
|
161
|
+
// An abort means a new turn or session already reset the scheduler; a
|
|
162
|
+
// late markFired there would suppress that turn's own recap.
|
|
163
|
+
if (!controller.signal.aborted) scheduler.markFired();
|
|
164
|
+
} finally {
|
|
165
|
+
if (inFlight === controller) inFlight = undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pi.on("session_start", (_event, ctx) => {
|
|
170
|
+
lastCtx = ctx;
|
|
171
|
+
// A new session (including after /clear): drop any timer armed by the
|
|
172
|
+
// previous session and its captured messages, so a stale recap can never
|
|
173
|
+
// surface against the fresh conversation.
|
|
174
|
+
scheduler.reset();
|
|
175
|
+
abortInFlight();
|
|
176
|
+
capturedMessages = undefined;
|
|
177
|
+
if (!ctx.hasUI) return;
|
|
178
|
+
// Reset the idle clock on any keystroke — the closest signal to CC's
|
|
179
|
+
// terminal-blur trigger pi exposes. Registered ONCE (session_start fires
|
|
180
|
+
// again on every /clear and session switch, so an unguarded registration
|
|
181
|
+
// would leak a growing stack of listeners); the handler drives the
|
|
182
|
+
// scheduler, which reads only its own state.
|
|
183
|
+
if (inputHookRegistered) return;
|
|
184
|
+
inputHookRegistered = true;
|
|
185
|
+
ctx.ui.onTerminalInput(() => {
|
|
186
|
+
scheduler.interacted();
|
|
187
|
+
return undefined; // observe only; never consume the keystroke
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
pi.on("agent_start", (_event, ctx) => {
|
|
192
|
+
lastCtx = ctx;
|
|
193
|
+
scheduler.turnStarted();
|
|
194
|
+
abortInFlight();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Settle, not agent_end: `agent_end` fires per run, and arming the idle
|
|
198
|
+
// timer there could fire a recap during a retry backoff longer than the
|
|
199
|
+
// configured idle (CC_RECAP_IDLE_MS floors at 1s), burning the
|
|
200
|
+
// one-per-turn budget mid-turn.
|
|
201
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
202
|
+
lastCtx = ctx;
|
|
203
|
+
// Unconditional: turnEnded() both clears turn state and arms the idle
|
|
204
|
+
// timer, so gating it on hasUI could leave turnRunning stuck true (and
|
|
205
|
+
// block all future re-arming) if a turn ever ends without UI. Arming
|
|
206
|
+
// without UI is harmless — generate() bails on !hasUI and the timer is
|
|
207
|
+
// unref'd, so a one-shot -p process exits before it can fire.
|
|
208
|
+
scheduler.turnEnded();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
pi.on("session_shutdown", () => {
|
|
212
|
+
scheduler.reset();
|
|
213
|
+
abortInFlight();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure pieces of Claude Code's "while you were away" recap (the ※ line CC shows
|
|
3
|
+
* when you come back after stepping away). Prompt text is verbatim from CC's
|
|
4
|
+
* services/awaySummary.ts; the display format matches CC's away_summary render
|
|
5
|
+
* (dim, led by the ※ reference mark). The session-memory block CC optionally
|
|
6
|
+
* prepends is omitted here (kept decoupled from One Code's memory extension).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** CC's REFERENCE_MARK (figures.ts) — the away-summary/recap marker. */
|
|
10
|
+
export const REFERENCE_MARK = "※";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Recap only needs recent context; CC truncates to the last 30 messages to
|
|
14
|
+
* avoid "prompt too long" on large sessions (≈15 exchanges — plenty for
|
|
15
|
+
* "where we left off").
|
|
16
|
+
*/
|
|
17
|
+
export const RECENT_MESSAGE_WINDOW = 30;
|
|
18
|
+
|
|
19
|
+
/** The away-summary instruction, verbatim from CC's buildAwaySummaryPrompt (no memory block). */
|
|
20
|
+
export const RECAP_PROMPT =
|
|
21
|
+
"The user stepped away and is coming back. Write exactly 1-3 short sentences. Start by stating the high-level task — what they are building or debugging, not implementation details. Next: the concrete next step. Skip status reports and commit recaps.";
|
|
22
|
+
|
|
23
|
+
/** The transcript text after the ※ mark, matching CC's shipped "recap: …" line. */
|
|
24
|
+
export function recapLine(content: string): string {
|
|
25
|
+
return `recap: ${content.trim()}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The recent-message window for the recap: the last `window` messages, trimmed
|
|
30
|
+
* forward to the first clean turn boundary — a `user` prompt or an `assistant`
|
|
31
|
+
* reply. A raw slice can begin mid-exchange with a leading tool-result message
|
|
32
|
+
* whose matching assistant tool_use fell outside the window; convertToLlm would
|
|
33
|
+
* then emit an orphan tool_result block that strict providers (e.g. Anthropic)
|
|
34
|
+
* reject, silently failing the recap. An assistant start is safe — its own tool
|
|
35
|
+
* results follow within the window. When the window holds no user/assistant
|
|
36
|
+
* message at all (e.g. one long tool-only stretch), nothing is safe to send, so
|
|
37
|
+
* it returns empty and the recap runs on the instruction alone. Generic over
|
|
38
|
+
* `{ role }` so it is testable with plain objects.
|
|
39
|
+
*/
|
|
40
|
+
export function recentForRecap<T extends { role: string }>(messages: readonly T[], window = RECENT_MESSAGE_WINDOW): T[] {
|
|
41
|
+
const tail = messages.slice(-window);
|
|
42
|
+
const start = tail.findIndex((message) => message.role === "user" || message.role === "assistant");
|
|
43
|
+
return start === -1 ? [] : tail.slice(start);
|
|
44
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure idle-timer state machine for the recap (no pi imports, so it is
|
|
3
|
+
* unit-tested directly; index.ts is thin wiring).
|
|
4
|
+
*
|
|
5
|
+
* The recap fires after a lull with no interaction following a turn — the
|
|
6
|
+
* closest faithful substitute for Claude Code's 5-minute terminal-blur trigger,
|
|
7
|
+
* which pi exposes no event for. The rules:
|
|
8
|
+
* - armed when a turn ends, re-armed on any keystroke, cleared while a turn runs;
|
|
9
|
+
* - at most one recap per user turn (CC's hasSummarySinceLastUserTurn guard);
|
|
10
|
+
* - `reset()` drops a pending fire and all turn state, so a timer armed by a
|
|
11
|
+
* previous session can never fire against a new one (after /clear).
|
|
12
|
+
*
|
|
13
|
+
* The clock is injected (`TimerOps`) so tests drive it deterministically; the
|
|
14
|
+
* async model call it triggers stays in the extension.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface TimerOps {
|
|
18
|
+
/** Schedule `cb` after `ms`; return a handle for `clear`. */
|
|
19
|
+
set(cb: () => void, ms: number): unknown;
|
|
20
|
+
clear(handle: unknown): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class RecapScheduler {
|
|
24
|
+
private turnRunning = false;
|
|
25
|
+
private firedSinceTurn = false;
|
|
26
|
+
private handle: unknown;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly timer: TimerOps,
|
|
30
|
+
/** The idle delay in ms, read at arm time. */
|
|
31
|
+
private readonly idleMs: () => number,
|
|
32
|
+
/** Whether the recap is enabled (e.g. CC_RECAP !== "0"). */
|
|
33
|
+
private readonly enabled: () => boolean,
|
|
34
|
+
/** Called when the idle timer elapses. */
|
|
35
|
+
private readonly onFire: () => void,
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
private clear(): void {
|
|
39
|
+
if (this.handle !== undefined) {
|
|
40
|
+
this.timer.clear(this.handle);
|
|
41
|
+
this.handle = undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private arm(): void {
|
|
46
|
+
this.clear();
|
|
47
|
+
if (this.turnRunning || this.firedSinceTurn || !this.enabled()) return;
|
|
48
|
+
this.handle = this.timer.set(() => {
|
|
49
|
+
this.handle = undefined;
|
|
50
|
+
this.onFire();
|
|
51
|
+
}, this.idleMs());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A turn began: cancel any pending recap and allow a fresh one after it. */
|
|
55
|
+
turnStarted(): void {
|
|
56
|
+
this.turnRunning = true;
|
|
57
|
+
this.firedSinceTurn = false;
|
|
58
|
+
this.clear();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A turn ended: start the idle countdown. */
|
|
62
|
+
turnEnded(): void {
|
|
63
|
+
this.turnRunning = false;
|
|
64
|
+
this.arm();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Any interaction between turns resets the idle clock. */
|
|
68
|
+
interacted(): void {
|
|
69
|
+
if (!this.turnRunning) this.arm();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** New session or shutdown: drop the pending fire and all turn state. */
|
|
73
|
+
reset(): void {
|
|
74
|
+
this.turnRunning = false;
|
|
75
|
+
this.firedSinceTurn = false;
|
|
76
|
+
this.clear();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A recap was emitted; suppress another until the next turn. */
|
|
80
|
+
markFired(): void {
|
|
81
|
+
this.firedSinceTurn = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get hasFiredSinceTurn(): boolean {
|
|
85
|
+
return this.firedSinceTurn;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
Binary file
|
|
@@ -9,13 +9,15 @@ import type { PromptTier } from "../lib/model-tier.ts";
|
|
|
9
9
|
export const SEARCH_TOOLS = ["grep", "find", "ls"] as const;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Returns the active-tool list adjusted for the tier:
|
|
13
|
-
* tools (deduped, appended in a stable order)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Returns the active-tool list adjusted for the tier: only `tiny` gains the
|
|
13
|
+
* search tools (deduped, appended in a stable order); frontier/workhorse/cheap
|
|
14
|
+
* lose them. Claude Code ships no dedicated search tools on any tier (Haiku
|
|
15
|
+
* included — bash covers search), so `cheap` matches CC exactly; the grep/find/ls
|
|
16
|
+
* crutch is reserved for the sub-Haiku `tiny` models CC never serves. Order of
|
|
17
|
+
* the existing names is preserved so the rebuilt system prompt stays byte-stable.
|
|
16
18
|
*/
|
|
17
19
|
export function withSearchTools(active: string[], tier: PromptTier): string[] {
|
|
18
|
-
if (tier
|
|
20
|
+
if (tier !== "tiny") {
|
|
19
21
|
const drop = new Set<string>(SEARCH_TOOLS);
|
|
20
22
|
return active.filter((name) => !drop.has(name));
|
|
21
23
|
}
|