@oh-my-pi/pi-coding-agent 15.0.1 → 15.1.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/CHANGELOG.md +94 -1
- package/examples/custom-tools/README.md +11 -7
- package/examples/custom-tools/hello/index.ts +2 -2
- package/examples/extensions/README.md +19 -8
- package/examples/extensions/api-demo.ts +15 -19
- package/examples/extensions/hello.ts +5 -6
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/extensions/reload-runtime.ts +4 -3
- package/examples/extensions/with-deps/index.ts +4 -3
- package/examples/sdk/06-extensions.ts +4 -2
- package/package.json +8 -18
- package/src/autoresearch/tools/init-experiment.ts +38 -41
- package/src/autoresearch/tools/log-experiment.ts +32 -41
- package/src/autoresearch/tools/run-experiment.ts +3 -3
- package/src/autoresearch/tools/update-notes.ts +11 -11
- package/src/commands/commit.ts +10 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -4
- package/src/commit/agentic/tools/git-file-diff.ts +4 -4
- package/src/commit/agentic/tools/git-hunk.ts +5 -5
- package/src/commit/agentic/tools/git-overview.ts +4 -4
- package/src/commit/agentic/tools/propose-changelog.ts +13 -13
- package/src/commit/agentic/tools/propose-commit.ts +6 -6
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/schemas.ts +28 -28
- package/src/commit/agentic/tools/split-commit.ts +22 -21
- package/src/commit/analysis/summary.ts +4 -4
- package/src/commit/changelog/generate.ts +7 -11
- package/src/commit/shared-llm.ts +22 -34
- package/src/config/config-file.ts +35 -13
- package/src/config/model-registry.ts +40 -191
- package/src/config/models-config-schema.ts +166 -0
- package/src/config/settings-schema.ts +29 -0
- package/src/discovery/claude-plugins.ts +19 -7
- package/src/edit/index.ts +2 -2
- package/src/edit/modes/apply-patch.ts +7 -6
- package/src/edit/modes/patch.ts +18 -25
- package/src/edit/modes/replace.ts +18 -20
- package/src/eval/js/shared/rewrite-imports.ts +131 -10
- package/src/eval/py/executor.ts +233 -623
- package/src/eval/py/kernel.ts +27 -2
- package/src/eval/py/runner.py +42 -11
- package/src/eval/py/runtime.ts +1 -0
- package/src/exa/factory.ts +5 -4
- package/src/exa/mcp-client.ts +1 -1
- package/src/exa/researcher.ts +9 -20
- package/src/exa/search.ts +26 -52
- package/src/exa/types.ts +1 -1
- package/src/exa/websets.ts +54 -53
- package/src/exec/bash-executor.ts +2 -1
- package/src/extensibility/custom-commands/loader.ts +5 -3
- package/src/extensibility/custom-commands/types.ts +4 -2
- package/src/extensibility/custom-tools/loader.ts +5 -3
- package/src/extensibility/custom-tools/types.ts +7 -6
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/get-commands-handler.ts +77 -0
- package/src/extensibility/extensions/loader.ts +7 -3
- package/src/extensibility/extensions/types.ts +9 -5
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/hooks/loader.ts +3 -1
- package/src/extensibility/hooks/tool-wrapper.ts +1 -1
- package/src/extensibility/hooks/types.ts +4 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +78 -31
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/typebox.ts +391 -0
- package/src/goals/tools/goal-tool.ts +6 -12
- package/src/hashline/input.ts +2 -1
- package/src/hashline/parser.ts +27 -3
- package/src/hashline/types.ts +4 -4
- package/src/hindsight/state.ts +2 -2
- package/src/index.ts +0 -2
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/router.ts +8 -0
- package/src/internal-urls/types.ts +21 -0
- package/src/lsp/config.ts +15 -6
- package/src/lsp/defaults.json +6 -2
- package/src/lsp/types.ts +30 -38
- package/src/mcp/manager.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -1
- package/src/modes/acp/acp-agent.ts +248 -50
- package/src/modes/components/session-observer-overlay.ts +12 -1
- package/src/modes/components/status-line/segments.ts +39 -4
- package/src/modes/controllers/command-controller.ts +27 -2
- package/src/modes/controllers/event-controller.ts +3 -4
- package/src/modes/controllers/extension-ui-controller.ts +3 -2
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/rpc/host-tools.ts +1 -1
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-mode.ts +27 -1
- package/src/modes/rpc/rpc-types.ts +58 -1
- package/src/modes/runtime-init.ts +2 -1
- package/src/modes/theme/defaults/dark-poimandres.json +1 -0
- package/src/modes/theme/defaults/light-poimandres.json +1 -0
- package/src/modes/theme/theme.ts +117 -117
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/prompts/tools/github.md +4 -4
- package/src/prompts/tools/hashline.md +22 -26
- package/src/prompts/tools/read.md +55 -37
- package/src/sdk.ts +31 -8
- package/src/session/agent-session.ts +74 -104
- package/src/session/messages.ts +16 -51
- package/src/session/session-manager.ts +22 -2
- package/src/session/streaming-output.ts +16 -6
- package/src/task/discovery.ts +5 -2
- package/src/task/executor.ts +210 -87
- package/src/task/index.ts +15 -11
- package/src/task/render.ts +32 -5
- package/src/task/types.ts +54 -39
- package/src/tools/ask.ts +12 -12
- package/src/tools/ast-edit.ts +11 -15
- package/src/tools/ast-grep.ts +9 -10
- package/src/tools/bash-command-fixup.ts +47 -0
- package/src/tools/bash.ts +48 -38
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser.ts +39 -53
- package/src/tools/calculator.ts +12 -11
- package/src/tools/checkpoint.ts +7 -7
- package/src/tools/debug.ts +40 -43
- package/src/tools/eval.ts +16 -10
- package/src/tools/find.ts +10 -13
- package/src/tools/gh.ts +108 -132
- package/src/tools/hindsight-recall.ts +4 -6
- package/src/tools/hindsight-reflect.ts +5 -5
- package/src/tools/hindsight-retain.ts +15 -17
- package/src/tools/image-gen.ts +31 -81
- package/src/tools/index.ts +4 -1
- package/src/tools/inspect-image.ts +8 -9
- package/src/tools/irc.ts +15 -27
- package/src/tools/job.ts +30 -28
- package/src/tools/output-meta.ts +26 -0
- package/src/tools/read.ts +39 -12
- package/src/tools/recipe/index.ts +7 -9
- package/src/tools/render-mermaid.ts +12 -12
- package/src/tools/report-tool-issue.ts +4 -4
- package/src/tools/resolve.ts +11 -11
- package/src/tools/review.ts +14 -26
- package/src/tools/search-tool-bm25.ts +7 -9
- package/src/tools/search.ts +19 -22
- package/src/tools/ssh.ts +10 -9
- package/src/tools/todo-write.ts +26 -34
- package/src/tools/vim.ts +10 -26
- package/src/tools/write.ts +25 -5
- package/src/tools/yield.ts +100 -54
- package/src/web/search/index.ts +9 -24
- package/src/web/search/providers/anthropic.ts +5 -0
- package/src/web/search/providers/exa.ts +3 -0
- package/src/web/search/providers/gemini.ts +5 -0
- package/src/web/search/providers/jina.ts +5 -2
- package/src/web/search/providers/zai.ts +5 -2
- package/src/prompts/compaction/branch-summary-context.md +0 -5
- package/src/prompts/compaction/branch-summary-preamble.md +0 -2
- package/src/prompts/compaction/branch-summary.md +0 -30
- package/src/prompts/compaction/compaction-short-summary.md +0 -9
- package/src/prompts/compaction/compaction-summary-context.md +0 -5
- package/src/prompts/compaction/compaction-summary.md +0 -38
- package/src/prompts/compaction/compaction-turn-prefix.md +0 -17
- package/src/prompts/compaction/compaction-update-summary.md +0 -45
- package/src/prompts/system/auto-handoff-threshold-focus.md +0 -1
- package/src/prompts/system/file-operations.md +0 -10
- package/src/prompts/system/handoff-document.md +0 -49
- package/src/prompts/system/summarization-system.md +0 -3
- package/src/session/compaction/branch-summarization.ts +0 -324
- package/src/session/compaction/compaction.ts +0 -1420
- package/src/session/compaction/errors.ts +0 -31
- package/src/session/compaction/index.ts +0 -8
- package/src/session/compaction/pruning.ts +0 -91
- package/src/session/compaction/utils.ts +0 -184
|
@@ -26,6 +26,23 @@ import {
|
|
|
26
26
|
type AgentTool,
|
|
27
27
|
ThinkingLevel,
|
|
28
28
|
} from "@oh-my-pi/pi-agent-core";
|
|
29
|
+
import {
|
|
30
|
+
AUTO_HANDOFF_THRESHOLD_FOCUS,
|
|
31
|
+
CompactionCancelledError,
|
|
32
|
+
type CompactionPreparation,
|
|
33
|
+
type CompactionResult,
|
|
34
|
+
calculateContextTokens,
|
|
35
|
+
calculatePromptTokens,
|
|
36
|
+
collectEntriesForBranchSummary,
|
|
37
|
+
compact,
|
|
38
|
+
estimateTokens,
|
|
39
|
+
generateBranchSummary,
|
|
40
|
+
generateHandoff,
|
|
41
|
+
prepareCompaction,
|
|
42
|
+
type SummaryOptions,
|
|
43
|
+
shouldCompact,
|
|
44
|
+
} from "@oh-my-pi/pi-agent-core/compaction";
|
|
45
|
+
import { DEFAULT_PRUNE_CONFIG, pruneToolOutputs } from "@oh-my-pi/pi-agent-core/compaction/pruning";
|
|
29
46
|
import type {
|
|
30
47
|
AssistantMessage,
|
|
31
48
|
Context,
|
|
@@ -126,9 +143,7 @@ import { resolveMemoryBackend } from "../memory-backend";
|
|
|
126
143
|
import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
127
144
|
import type { PlanModeState } from "../plan-mode/state";
|
|
128
145
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
129
|
-
import autoHandoffThresholdFocusPrompt from "../prompts/system/auto-handoff-threshold-focus.md" with { type: "text" };
|
|
130
146
|
import eagerTodoPrompt from "../prompts/system/eager-todo.md" with { type: "text" };
|
|
131
|
-
import handoffDocumentPrompt from "../prompts/system/handoff-document.md" with { type: "text" };
|
|
132
147
|
import ircIncomingTemplate from "../prompts/system/irc-incoming.md" with { type: "text" };
|
|
133
148
|
import planModeActivePrompt from "../prompts/system/plan-mode-active.md" with { type: "text" };
|
|
134
149
|
import planModeReferencePrompt from "../prompts/system/plan-mode-reference.md" with { type: "text" };
|
|
@@ -160,21 +175,6 @@ import { extractFileMentions, generateFileMentionMessages } from "../utils/file-
|
|
|
160
175
|
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
161
176
|
import type { AuthStorage } from "./auth-storage";
|
|
162
177
|
import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome } from "./client-bridge";
|
|
163
|
-
import {
|
|
164
|
-
CompactionCancelledError,
|
|
165
|
-
type CompactionPreparation,
|
|
166
|
-
type CompactionResult,
|
|
167
|
-
calculateContextTokens,
|
|
168
|
-
calculatePromptTokens,
|
|
169
|
-
collectEntriesForBranchSummary,
|
|
170
|
-
compact,
|
|
171
|
-
estimateTokens,
|
|
172
|
-
generateBranchSummary,
|
|
173
|
-
prepareCompaction,
|
|
174
|
-
type SummaryOptions,
|
|
175
|
-
shouldCompact,
|
|
176
|
-
} from "./compaction";
|
|
177
|
-
import { DEFAULT_PRUNE_CONFIG, pruneToolOutputs } from "./compaction/pruning";
|
|
178
178
|
import {
|
|
179
179
|
type BashExecutionMessage,
|
|
180
180
|
type CompactionSummaryMessage,
|
|
@@ -334,6 +334,17 @@ export interface PromptOptions {
|
|
|
334
334
|
skipCompactionCheck?: boolean;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
/** Result from a handoff operation. */
|
|
338
|
+
export interface HandoffResult {
|
|
339
|
+
document: string;
|
|
340
|
+
savedPath?: string;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export interface SessionHandoffOptions {
|
|
344
|
+
autoTriggered?: boolean;
|
|
345
|
+
signal?: AbortSignal;
|
|
346
|
+
}
|
|
347
|
+
|
|
337
348
|
/** Result from cycleModel() */
|
|
338
349
|
export interface ModelCycleResult {
|
|
339
350
|
model: Model;
|
|
@@ -369,17 +380,6 @@ export interface SessionStats {
|
|
|
369
380
|
cost: number;
|
|
370
381
|
}
|
|
371
382
|
|
|
372
|
-
/** Result from handoff() */
|
|
373
|
-
export interface HandoffResult {
|
|
374
|
-
document: string;
|
|
375
|
-
savedPath?: string;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
interface HandoffOptions {
|
|
379
|
-
autoTriggered?: boolean;
|
|
380
|
-
signal?: AbortSignal;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
383
|
/** Internal marker for hook messages queued through the agent loop */
|
|
384
384
|
// ============================================================================
|
|
385
385
|
// Constants
|
|
@@ -387,8 +387,6 @@ interface HandoffOptions {
|
|
|
387
387
|
|
|
388
388
|
/** Standard thinking levels */
|
|
389
389
|
|
|
390
|
-
const AUTO_HANDOFF_THRESHOLD_FOCUS = prompt.render(autoHandoffThresholdFocusPrompt);
|
|
391
|
-
|
|
392
390
|
type RetryFallbackChains = Record<string, string[]>;
|
|
393
391
|
|
|
394
392
|
type RetryFallbackRevertPolicy = "never" | "cooldown-expiry";
|
|
@@ -512,6 +510,15 @@ const noOpUIContext: ExtensionUIContext = {
|
|
|
512
510
|
setToolsExpanded: () => {},
|
|
513
511
|
};
|
|
514
512
|
|
|
513
|
+
function createHandoffContext(document: string): string {
|
|
514
|
+
return `<handoff-context>\n${document}\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.`;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function createHandoffFileName(date = new Date()): string {
|
|
518
|
+
const fileTimestamp = date.toISOString().replace(/[:.]/g, "-");
|
|
519
|
+
return `handoff-${fileTimestamp}.md`;
|
|
520
|
+
}
|
|
521
|
+
|
|
515
522
|
// ============================================================================
|
|
516
523
|
// ACP Permission Gate
|
|
517
524
|
// ============================================================================
|
|
@@ -4999,6 +5006,7 @@ export class AgentSession {
|
|
|
4999
5006
|
promptOverride: compactionPrep.hookPrompt,
|
|
5000
5007
|
extraContext: compactionPrep.hookContext,
|
|
5001
5008
|
remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
|
|
5009
|
+
convertToLlm,
|
|
5002
5010
|
},
|
|
5003
5011
|
);
|
|
5004
5012
|
summary = result.summary;
|
|
@@ -5135,16 +5143,13 @@ export class AgentSession {
|
|
|
5135
5143
|
}
|
|
5136
5144
|
|
|
5137
5145
|
/**
|
|
5138
|
-
* Generate a handoff document
|
|
5139
|
-
*
|
|
5140
|
-
* This prompts the current agent to write a comprehensive handoff document,
|
|
5141
|
-
* waits for completion, then starts a fresh session with the handoff as context.
|
|
5146
|
+
* Generate a handoff document with a oneshot LLM call, then start a new session with it.
|
|
5142
5147
|
*
|
|
5143
5148
|
* @param customInstructions Optional focus for the handoff document
|
|
5144
5149
|
* @param options Handoff execution options
|
|
5145
5150
|
* @returns The handoff document text, or undefined if cancelled/failed
|
|
5146
5151
|
*/
|
|
5147
|
-
async handoff(customInstructions?: string, options?:
|
|
5152
|
+
async handoff(customInstructions?: string, options?: SessionHandoffOptions): Promise<HandoffResult | undefined> {
|
|
5148
5153
|
const entries = this.sessionManager.getBranch();
|
|
5149
5154
|
const messageCount = entries.filter(e => e.type === "message").length;
|
|
5150
5155
|
|
|
@@ -5158,10 +5163,6 @@ export class AgentSession {
|
|
|
5158
5163
|
const handoffAbortController = this.#handoffAbortController;
|
|
5159
5164
|
const handoffSignal = handoffAbortController.signal;
|
|
5160
5165
|
const sourceSignal = options?.signal;
|
|
5161
|
-
const onHandoffAbort = () => {
|
|
5162
|
-
this.agent.abort();
|
|
5163
|
-
};
|
|
5164
|
-
handoffSignal.addEventListener("abort", onHandoffAbort, { once: true });
|
|
5165
5166
|
const onSourceAbort = () => {
|
|
5166
5167
|
if (!handoffSignal.aborted) {
|
|
5167
5168
|
handoffAbortController.abort();
|
|
@@ -5174,71 +5175,36 @@ export class AgentSession {
|
|
|
5174
5175
|
}
|
|
5175
5176
|
}
|
|
5176
5177
|
|
|
5177
|
-
// Build the handoff prompt
|
|
5178
|
-
const handoffPrompt = prompt.render(handoffDocumentPrompt, {
|
|
5179
|
-
additionalFocus: customInstructions,
|
|
5180
|
-
});
|
|
5181
|
-
|
|
5182
|
-
// Create a promise that resolves when the agent completes
|
|
5183
|
-
let handoffText: string | undefined;
|
|
5184
|
-
const { promise: completionPromise, resolve: resolveCompletion } = Promise.withResolvers<void>();
|
|
5185
|
-
let handoffCancelled = false;
|
|
5186
|
-
let unsubscribe: (() => void) | undefined;
|
|
5187
|
-
const onCompletionAbort = () => {
|
|
5188
|
-
unsubscribe?.();
|
|
5189
|
-
handoffCancelled = true;
|
|
5190
|
-
resolveCompletion();
|
|
5191
|
-
};
|
|
5192
|
-
if (handoffSignal.aborted) {
|
|
5193
|
-
onCompletionAbort();
|
|
5194
|
-
} else {
|
|
5195
|
-
handoffSignal.addEventListener("abort", onCompletionAbort, { once: true });
|
|
5196
|
-
}
|
|
5197
|
-
unsubscribe = this.subscribe(event => {
|
|
5198
|
-
if (event.type === "agent_end") {
|
|
5199
|
-
unsubscribe?.();
|
|
5200
|
-
handoffSignal.removeEventListener("abort", onCompletionAbort);
|
|
5201
|
-
// Extract text from the last assistant message
|
|
5202
|
-
const messages = this.agent.state.messages;
|
|
5203
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5204
|
-
const msg = messages[i];
|
|
5205
|
-
if (msg.role === "assistant") {
|
|
5206
|
-
const content = (msg as AssistantMessage).content;
|
|
5207
|
-
const textParts = content
|
|
5208
|
-
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
5209
|
-
.map(c => c.text);
|
|
5210
|
-
if (textParts.length > 0) {
|
|
5211
|
-
handoffText = textParts.join("\n");
|
|
5212
|
-
break;
|
|
5213
|
-
}
|
|
5214
|
-
}
|
|
5215
|
-
}
|
|
5216
|
-
resolveCompletion();
|
|
5217
|
-
}
|
|
5218
|
-
});
|
|
5219
|
-
|
|
5220
5178
|
try {
|
|
5221
|
-
// Send the prompt and wait for completion
|
|
5222
5179
|
if (handoffSignal.aborted) {
|
|
5223
5180
|
throw new Error("Handoff cancelled");
|
|
5224
5181
|
}
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
{
|
|
5230
|
-
role: "developer",
|
|
5231
|
-
content: [{ type: "text", text: handoffPrompt }],
|
|
5232
|
-
attribution: "agent",
|
|
5233
|
-
timestamp: Date.now(),
|
|
5234
|
-
},
|
|
5235
|
-
]);
|
|
5236
|
-
} finally {
|
|
5237
|
-
this.#endInFlight();
|
|
5182
|
+
|
|
5183
|
+
const model = this.model;
|
|
5184
|
+
if (!model) {
|
|
5185
|
+
throw new Error("No model selected for handoff");
|
|
5238
5186
|
}
|
|
5239
|
-
await
|
|
5187
|
+
const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
|
|
5188
|
+
if (!apiKey) {
|
|
5189
|
+
throw new Error(`No API key for ${model.provider}`);
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
const handoffText = await generateHandoff(
|
|
5193
|
+
this.agent.state.messages,
|
|
5194
|
+
model,
|
|
5195
|
+
apiKey,
|
|
5196
|
+
{
|
|
5197
|
+
systemPrompt: this.#baseSystemPrompt,
|
|
5198
|
+
tools: this.agent.state.tools,
|
|
5199
|
+
customInstructions,
|
|
5200
|
+
convertToLlm,
|
|
5201
|
+
initiatorOverride: "agent",
|
|
5202
|
+
metadata: this.agent.metadataForProvider(model.provider),
|
|
5203
|
+
},
|
|
5204
|
+
handoffSignal,
|
|
5205
|
+
);
|
|
5240
5206
|
|
|
5241
|
-
if (
|
|
5207
|
+
if (handoffSignal.aborted) {
|
|
5242
5208
|
throw new Error("Handoff cancelled");
|
|
5243
5209
|
}
|
|
5244
5210
|
if (!handoffText) {
|
|
@@ -5261,15 +5227,14 @@ export class AgentSession {
|
|
|
5261
5227
|
this.#todoReminderCount = 0;
|
|
5262
5228
|
|
|
5263
5229
|
// Inject the handoff document as a custom message
|
|
5264
|
-
const handoffContent =
|
|
5230
|
+
const handoffContent = createHandoffContext(handoffText);
|
|
5265
5231
|
this.sessionManager.appendCustomMessageEntry("handoff", handoffContent, true, undefined, "agent");
|
|
5266
5232
|
await this.sessionManager.ensureOnDisk();
|
|
5267
5233
|
let savedPath: string | undefined;
|
|
5268
5234
|
if (options?.autoTriggered && this.settings.get("compaction.handoffSaveToDisk")) {
|
|
5269
5235
|
const artifactsDir = this.sessionManager.getArtifactsDir();
|
|
5270
5236
|
if (artifactsDir) {
|
|
5271
|
-
const
|
|
5272
|
-
const handoffFilePath = path.join(artifactsDir, `handoff-${fileTimestamp}.md`);
|
|
5237
|
+
const handoffFilePath = path.join(artifactsDir, createHandoffFileName());
|
|
5273
5238
|
try {
|
|
5274
5239
|
await Bun.write(handoffFilePath, `${handoffText}\n`);
|
|
5275
5240
|
savedPath = handoffFilePath;
|
|
@@ -5290,10 +5255,12 @@ export class AgentSession {
|
|
|
5290
5255
|
this.#syncTodoPhasesFromBranch();
|
|
5291
5256
|
|
|
5292
5257
|
return { document: handoffText, savedPath };
|
|
5258
|
+
} catch (error) {
|
|
5259
|
+
if (handoffSignal.aborted || (error instanceof Error && error.name === "AbortError")) {
|
|
5260
|
+
throw new Error("Handoff cancelled");
|
|
5261
|
+
}
|
|
5262
|
+
throw error;
|
|
5293
5263
|
} finally {
|
|
5294
|
-
unsubscribe?.();
|
|
5295
|
-
handoffSignal.removeEventListener("abort", onCompletionAbort);
|
|
5296
|
-
handoffSignal.removeEventListener("abort", onHandoffAbort);
|
|
5297
5264
|
sourceSignal?.removeEventListener("abort", onSourceAbort);
|
|
5298
5265
|
this.#handoffAbortController = undefined;
|
|
5299
5266
|
}
|
|
@@ -5954,6 +5921,7 @@ export class AgentSession {
|
|
|
5954
5921
|
return await compact(preparation, candidate, apiKey, customInstructions, signal, {
|
|
5955
5922
|
...options,
|
|
5956
5923
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
5924
|
+
convertToLlm,
|
|
5957
5925
|
});
|
|
5958
5926
|
} catch (error) {
|
|
5959
5927
|
if (!this.#isCompactionAuthFailure(error)) {
|
|
@@ -6206,6 +6174,7 @@ export class AgentSession {
|
|
|
6206
6174
|
remoteInstructions: this.#baseSystemPrompt.join("\n\n"),
|
|
6207
6175
|
metadata: this.agent.metadataForProvider(candidate.provider),
|
|
6208
6176
|
initiatorOverride: "agent",
|
|
6177
|
+
convertToLlm,
|
|
6209
6178
|
});
|
|
6210
6179
|
break;
|
|
6211
6180
|
} catch (error) {
|
|
@@ -7809,6 +7778,7 @@ export class AgentSession {
|
|
|
7809
7778
|
customInstructions: options.customInstructions,
|
|
7810
7779
|
reserveTokens: branchSummarySettings.reserveTokens,
|
|
7811
7780
|
metadata: this.agent.metadataForProvider(model.provider),
|
|
7781
|
+
convertToLlm,
|
|
7812
7782
|
});
|
|
7813
7783
|
this.#branchSummaryAbortController = undefined;
|
|
7814
7784
|
if (result.aborted) {
|
package/src/session/messages.ts
CHANGED
|
@@ -5,24 +5,31 @@
|
|
|
5
5
|
* and provides a transformer to convert them to LLM-compatible messages.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import {
|
|
9
|
+
type BranchSummaryMessage,
|
|
10
|
+
type CompactionSummaryMessage,
|
|
11
|
+
renderBranchSummaryContext,
|
|
12
|
+
renderCompactionSummaryContext,
|
|
13
|
+
} from "@oh-my-pi/pi-agent-core/compaction/messages";
|
|
8
14
|
import type {
|
|
9
15
|
AssistantMessage,
|
|
10
16
|
ImageContent,
|
|
11
17
|
Message,
|
|
12
18
|
MessageAttribution,
|
|
13
|
-
ProviderPayload,
|
|
14
19
|
TextContent,
|
|
15
20
|
ToolResultMessage,
|
|
16
21
|
} from "@oh-my-pi/pi-ai";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
type BranchSummaryMessage,
|
|
25
|
+
type CompactionSummaryMessage,
|
|
26
|
+
createBranchSummaryMessage,
|
|
27
|
+
createCompactionSummaryMessage,
|
|
28
|
+
} from "@oh-my-pi/pi-agent-core/compaction/messages";
|
|
29
|
+
|
|
20
30
|
import type { OutputMeta } from "../tools/output-meta";
|
|
21
31
|
import { formatOutputNotice } from "../tools/output-meta";
|
|
22
32
|
|
|
23
|
-
const COMPACTION_SUMMARY_TEMPLATE = compactionSummaryContextPrompt;
|
|
24
|
-
const BRANCH_SUMMARY_TEMPLATE = branchSummaryContextPrompt;
|
|
25
|
-
|
|
26
33
|
export const SKILL_PROMPT_MESSAGE_TYPE = "skill-prompt";
|
|
27
34
|
|
|
28
35
|
export interface SkillPromptDetails {
|
|
@@ -168,22 +175,6 @@ export interface HookMessage<T = unknown> {
|
|
|
168
175
|
timestamp: number;
|
|
169
176
|
}
|
|
170
177
|
|
|
171
|
-
export interface BranchSummaryMessage {
|
|
172
|
-
role: "branchSummary";
|
|
173
|
-
summary: string;
|
|
174
|
-
fromId: string;
|
|
175
|
-
timestamp: number;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export interface CompactionSummaryMessage {
|
|
179
|
-
role: "compactionSummary";
|
|
180
|
-
summary: string;
|
|
181
|
-
shortSummary?: string;
|
|
182
|
-
tokensBefore: number;
|
|
183
|
-
providerPayload?: ProviderPayload;
|
|
184
|
-
timestamp: number;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
178
|
/**
|
|
188
179
|
* Message type for auto-read file mentions via @filepath syntax.
|
|
189
180
|
*/
|
|
@@ -254,32 +245,6 @@ export function pythonExecutionToText(msg: PythonExecutionMessage): string {
|
|
|
254
245
|
return text;
|
|
255
246
|
}
|
|
256
247
|
|
|
257
|
-
export function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage {
|
|
258
|
-
return {
|
|
259
|
-
role: "branchSummary",
|
|
260
|
-
summary,
|
|
261
|
-
fromId,
|
|
262
|
-
timestamp: new Date(timestamp).getTime(),
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
export function createCompactionSummaryMessage(
|
|
267
|
-
summary: string,
|
|
268
|
-
tokensBefore: number,
|
|
269
|
-
timestamp: string,
|
|
270
|
-
shortSummary?: string,
|
|
271
|
-
providerPayload?: ProviderPayload,
|
|
272
|
-
): CompactionSummaryMessage {
|
|
273
|
-
return {
|
|
274
|
-
role: "compactionSummary",
|
|
275
|
-
summary,
|
|
276
|
-
shortSummary,
|
|
277
|
-
tokensBefore,
|
|
278
|
-
providerPayload,
|
|
279
|
-
timestamp: new Date(timestamp).getTime(),
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
248
|
export function sanitizeRehydratedOpenAIResponsesAssistantMessage(message: AssistantMessage): AssistantMessage {
|
|
284
249
|
if (message.providerPayload?.type !== "openaiResponsesHistory") {
|
|
285
250
|
return message;
|
|
@@ -376,7 +341,7 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
376
341
|
content: [
|
|
377
342
|
{
|
|
378
343
|
type: "text" as const,
|
|
379
|
-
text:
|
|
344
|
+
text: renderBranchSummaryContext(m.summary),
|
|
380
345
|
},
|
|
381
346
|
],
|
|
382
347
|
attribution: "agent",
|
|
@@ -388,7 +353,7 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
388
353
|
content: [
|
|
389
354
|
{
|
|
390
355
|
type: "text" as const,
|
|
391
|
-
text:
|
|
356
|
+
text: renderCompactionSummaryContext(m.summary),
|
|
392
357
|
},
|
|
393
358
|
],
|
|
394
359
|
attribution: "agent",
|
|
@@ -1351,6 +1351,11 @@ class NdjsonFileWriter {
|
|
|
1351
1351
|
getError(): Error | undefined {
|
|
1352
1352
|
return this.#error;
|
|
1353
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
/** True while the writer accepts new writes (not closing or closed). */
|
|
1356
|
+
isOpen(): boolean {
|
|
1357
|
+
return !this.#closed && !this.#closing;
|
|
1358
|
+
}
|
|
1354
1359
|
}
|
|
1355
1360
|
|
|
1356
1361
|
/** Get recent sessions for display in welcome screen */
|
|
@@ -2106,7 +2111,15 @@ export class SessionManager {
|
|
|
2106
2111
|
#ensurePersistWriter(): NdjsonFileWriter | undefined {
|
|
2107
2112
|
if (!this.persist || !this.#sessionFile) return undefined;
|
|
2108
2113
|
if (this.#persistError) throw this.#persistError;
|
|
2109
|
-
if (this.#persistWriter && this.#persistWriterPath === this.#sessionFile)
|
|
2114
|
+
if (this.#persistWriter && this.#persistWriterPath === this.#sessionFile) {
|
|
2115
|
+
if (this.#persistWriter.isOpen()) return this.#persistWriter;
|
|
2116
|
+
// Cached writer for the current file is mid-close (queued
|
|
2117
|
+
// `#closePersistWriterInternal` has flipped `#closing` but not yet
|
|
2118
|
+
// cleared `#persistWriter`). Returning it would make `writeSync`
|
|
2119
|
+
// throw "Writer closed". Defer to the caller — `_persist` routes
|
|
2120
|
+
// the entry through the async rewrite path so it still lands on disk.
|
|
2121
|
+
return undefined;
|
|
2122
|
+
}
|
|
2110
2123
|
// Note: caller must await _closePersistWriter() before calling this if switching files
|
|
2111
2124
|
this.#persistWriter = new NdjsonFileWriter(this.storage, this.#sessionFile, {
|
|
2112
2125
|
onError: err => {
|
|
@@ -2457,7 +2470,14 @@ export class SessionManager {
|
|
|
2457
2470
|
// line referencing them is written.
|
|
2458
2471
|
try {
|
|
2459
2472
|
const writer = this.#ensurePersistWriter();
|
|
2460
|
-
if (!writer)
|
|
2473
|
+
if (!writer) {
|
|
2474
|
+
// `#ensurePersistWriter` returns undefined here only when the cached
|
|
2475
|
+
// writer is mid-close (the `!persist`/`!sessionFile` cases are
|
|
2476
|
+
// rejected above). Route through `#rewriteFile` so the entry — which
|
|
2477
|
+
// is already in `#fileEntries` — persists once the close drains.
|
|
2478
|
+
this.#rewriteFile().catch(() => {});
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2461
2481
|
const persistedEntry = prepareEntryForPersistenceSync(entry, this.#blobStore);
|
|
2462
2482
|
writer.writeSync(persistedEntry);
|
|
2463
2483
|
} catch (err) {
|
|
@@ -644,6 +644,7 @@ export class OutputSink {
|
|
|
644
644
|
#head = "";
|
|
645
645
|
#headBytes = 0;
|
|
646
646
|
#headLines = 0; // newline count inside #head
|
|
647
|
+
#headRetentionDisabled = false;
|
|
647
648
|
#totalLines = 0; // newline count
|
|
648
649
|
#totalBytes = 0;
|
|
649
650
|
#sawData = false;
|
|
@@ -740,7 +741,7 @@ export class OutputSink {
|
|
|
740
741
|
// exhausted, then forward any leftover to the tail buffer.
|
|
741
742
|
let tailChunk = capped;
|
|
742
743
|
let tailBytes = cappedBytes;
|
|
743
|
-
if (this.#headLimit > 0 && this.#headBytes < this.#headLimit) {
|
|
744
|
+
if (this.#headLimit > 0 && !this.#headRetentionDisabled && this.#headBytes < this.#headLimit) {
|
|
744
745
|
const room = this.#headLimit - this.#headBytes;
|
|
745
746
|
if (cappedBytes <= room) {
|
|
746
747
|
this.#head += capped;
|
|
@@ -919,12 +920,16 @@ export class OutputSink {
|
|
|
919
920
|
}
|
|
920
921
|
|
|
921
922
|
/**
|
|
922
|
-
* Replace the in-memory buffer with the given text
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
* captured output after the raw bytes have already been streamed.
|
|
923
|
+
* Replace the in-memory buffer with the given text. Used when an upstream
|
|
924
|
+
* minimizer rewrites the captured output after the raw bytes have already
|
|
925
|
+
* been streamed.
|
|
926
926
|
*
|
|
927
|
-
*
|
|
927
|
+
* After this call the buffer is authoritative: streaming counters realign
|
|
928
|
+
* to the replacement, the retained head window is cleared, and head
|
|
929
|
+
* retention is disabled so subsequent `push()` calls append directly to the
|
|
930
|
+
* tail buffer instead of repopulating the (now meaningless) head window
|
|
931
|
+
* — which would otherwise reorder content and trip the middle-elision
|
|
932
|
+
* branch in `dump()` against stale totals.
|
|
928
933
|
*/
|
|
929
934
|
replace(text: string): void {
|
|
930
935
|
this.#buffer = text;
|
|
@@ -932,6 +937,11 @@ export class OutputSink {
|
|
|
932
937
|
this.#head = "";
|
|
933
938
|
this.#headBytes = 0;
|
|
934
939
|
this.#headLines = 0;
|
|
940
|
+
this.#headRetentionDisabled = true;
|
|
941
|
+
this.#totalBytes = this.#bufferBytes;
|
|
942
|
+
this.#totalLines = countNewlines(text);
|
|
943
|
+
this.#sawData = text.length > 0;
|
|
944
|
+
this.#truncated = false;
|
|
935
945
|
this.#currentLineBytes = 0;
|
|
936
946
|
this.#columnEllipsisAdded = false;
|
|
937
947
|
this.#columnDroppedBytes = 0;
|
package/src/task/discovery.ts
CHANGED
|
@@ -15,6 +15,7 @@ import * as fs from "node:fs/promises";
|
|
|
15
15
|
import * as os from "node:os";
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
18
|
+
import { isProviderEnabled } from "../capability";
|
|
18
19
|
import { findAllNearestProjectConfigDirs, getConfigDirs } from "../config";
|
|
19
20
|
import { listClaudePluginRoots } from "../discovery/helpers";
|
|
20
21
|
import { loadBundledAgents, parseAgent } from "./agents";
|
|
@@ -87,8 +88,10 @@ export async function discoverAgents(cwd: string, home: string = os.homedir()):
|
|
|
87
88
|
if (user) orderedDirs.push({ dir: user.path, source: "user" });
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
// Load agents from Claude Code marketplace plugins
|
|
91
|
-
const { roots: pluginRoots } =
|
|
91
|
+
// Load agents from Claude Code marketplace plugins (respects disabledProviders)
|
|
92
|
+
const { roots: pluginRoots } = isProviderEnabled("claude-plugins")
|
|
93
|
+
? await listClaudePluginRoots(home, resolvedCwd)
|
|
94
|
+
: { roots: [] };
|
|
92
95
|
const sortedPluginRoots = [...pluginRoots].sort((a, b) => {
|
|
93
96
|
if (a.scope === b.scope) return 0;
|
|
94
97
|
return a.scope === "project" ? -1 : 1;
|