@oh-my-pi/pi-coding-agent 16.0.7 → 16.0.9
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 +41 -0
- package/dist/cli.js +4817 -12449
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/update-cli.d.ts +11 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/debug/remote-debugger.d.ts +45 -0
- package/dist/types/goals/runtime.d.ts +4 -1
- package/dist/types/internal-urls/docs-index.d.ts +19 -0
- package/dist/types/markit/converters/docx.d.ts +6 -0
- package/dist/types/markit/converters/epub.d.ts +15 -0
- package/dist/types/markit/converters/pdf/columns.d.ts +35 -0
- package/dist/types/markit/converters/pdf/extract.d.ts +10 -0
- package/dist/types/markit/converters/pdf/grid.d.ts +25 -0
- package/dist/types/markit/converters/pdf/headers.d.ts +24 -0
- package/dist/types/markit/converters/pdf/index.d.ts +6 -0
- package/dist/types/markit/converters/pdf/render.d.ts +24 -0
- package/dist/types/markit/converters/pdf/types.d.ts +75 -0
- package/dist/types/markit/converters/pptx.d.ts +57 -0
- package/dist/types/markit/converters/xlsx.d.ts +25 -0
- package/dist/types/markit/index.d.ts +2 -0
- package/dist/types/markit/registry.d.ts +16 -0
- package/dist/types/markit/types.d.ts +30 -0
- package/dist/types/modes/print-mode.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +7 -8
- package/dist/types/session/auth-storage.d.ts +3 -2
- package/dist/types/session/yield-queue.d.ts +3 -1
- package/dist/types/tools/browser/attach.d.ts +1 -1
- package/dist/types/utils/markit.d.ts +0 -8
- package/dist/types/utils/mupdf-wasm-embed.d.ts +1 -0
- package/dist/types/utils/turndown.d.ts +15 -0
- package/dist/types/utils/zip.d.ts +119 -0
- package/package.json +20 -18
- package/scripts/build-binary.ts +7 -3
- package/scripts/bundle-dist.ts +28 -12
- package/scripts/embed-mupdf-wasm.ts +67 -0
- package/scripts/generate-docs-index.ts +48 -32
- package/scripts/omp +1 -1
- package/src/advisor/__tests__/advisor.test.ts +83 -0
- package/src/advisor/runtime.ts +16 -1
- package/src/cli/args.ts +3 -0
- package/src/cli/auth-broker-cli.ts +1 -3
- package/src/cli/auth-gateway-cli.ts +2 -5
- package/src/cli/flag-tables.ts +1 -0
- package/src/cli/update-cli.ts +63 -3
- package/src/commands/launch.ts +3 -0
- package/src/config/model-discovery.ts +20 -8
- package/src/config/models-config-schema.ts +8 -1
- package/src/debug/index.ts +44 -0
- package/src/debug/remote-debugger.ts +151 -0
- package/src/debug/report-bundle.ts +2 -1
- package/src/goals/runtime.ts +19 -7
- package/src/internal-urls/docs-index.generated.txt +2 -0
- package/src/internal-urls/docs-index.ts +102 -0
- package/src/internal-urls/omp-protocol.ts +10 -9
- package/src/main.ts +8 -0
- package/src/markit/NOTICE +32 -0
- package/src/markit/converters/docx.ts +56 -0
- package/src/markit/converters/epub.ts +136 -0
- package/src/markit/converters/mammoth.d.ts +24 -0
- package/src/markit/converters/pdf/columns.ts +103 -0
- package/src/markit/converters/pdf/extract.ts +574 -0
- package/src/markit/converters/pdf/grid.ts +780 -0
- package/src/markit/converters/pdf/headers.ts +106 -0
- package/src/markit/converters/pdf/index.ts +146 -0
- package/src/markit/converters/pdf/render.ts +501 -0
- package/src/markit/converters/pdf/types.ts +84 -0
- package/src/markit/converters/pptx.ts +325 -0
- package/src/markit/converters/xlsx.ts +173 -0
- package/src/markit/index.ts +2 -0
- package/src/markit/registry.ts +59 -0
- package/src/markit/types.ts +35 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +14 -7
- package/src/modes/components/snapcompact-shape-preview.ts +2 -2
- package/src/modes/controllers/input-controller.ts +29 -8
- package/src/modes/interactive-mode.ts +33 -12
- package/src/modes/print-mode.ts +5 -1
- package/src/prompts/advisor/advise-tool.md +3 -1
- package/src/prompts/advisor/system.md +55 -11
- package/src/sdk.ts +5 -9
- package/src/session/agent-session.ts +72 -42
- package/src/session/auth-storage.ts +2 -11
- package/src/session/yield-queue.ts +7 -1
- package/src/tools/browser/attach.ts +2 -2
- package/src/tools/fetch.ts +25 -60
- package/src/tools/read.ts +1 -1
- package/src/tools/search.ts +1 -6
- package/src/tools/write.ts +25 -65
- package/src/utils/markit.ts +25 -9
- package/src/utils/mupdf-wasm-embed.ts +12 -0
- package/src/utils/tools-manager.ts +2 -11
- package/src/utils/turndown.ts +83 -0
- package/src/{tools/archive-reader.ts → utils/zip.ts} +453 -83
- package/src/web/scrapers/types.ts +3 -46
- package/dist/types/internal-urls/docs-index.generated.d.ts +0 -2
- package/dist/types/tools/archive-reader.d.ts +0 -49
- package/src/internal-urls/docs-index.generated.ts +0 -120
|
@@ -44,6 +44,26 @@ function hasPasteText(value: unknown): value is PasteTarget {
|
|
|
44
44
|
return typeof value === "object" && value !== null && typeof (value as PasteTarget).pasteText === "function";
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function pythonCommandPrefixLength(trimmedText: string): 0 | 1 | 2 {
|
|
48
|
+
if (trimmedText.charCodeAt(0) !== 36 /* $ */) return 0;
|
|
49
|
+
if (trimmedText.charCodeAt(1) === 123 /* { */) return 0;
|
|
50
|
+
|
|
51
|
+
const prefixLength = trimmedText.charCodeAt(1) === 36 /* $ */ ? 2 : 1;
|
|
52
|
+
const next = trimmedText.charCodeAt(prefixLength);
|
|
53
|
+
if (Number.isNaN(next)) return prefixLength;
|
|
54
|
+
return next === 32 || next === 9 || next === 10 || next === 13 ? prefixLength : 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parsePythonCommandInput(text: string): { code: string; isExcluded: boolean } | undefined {
|
|
58
|
+
const trimmed = text.trimStart();
|
|
59
|
+
const prefixLength = pythonCommandPrefixLength(trimmed);
|
|
60
|
+
if (prefixLength === 0) return undefined;
|
|
61
|
+
return {
|
|
62
|
+
code: trimmed.slice(prefixLength).trim(),
|
|
63
|
+
isExcluded: prefixLength === 2,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
/** Wrap pasted text in `<attachment>` tags so the model treats it as one quoted block. */
|
|
48
68
|
function wrapPasteInAttachmentBlock(content: string): string {
|
|
49
69
|
return `<attachment>\n${content}\n</attachment>`;
|
|
@@ -381,8 +401,8 @@ export class InputController {
|
|
|
381
401
|
const wasBashMode = this.ctx.isBashMode;
|
|
382
402
|
const wasPythonMode = this.ctx.isPythonMode;
|
|
383
403
|
const trimmed = text.trimStart();
|
|
384
|
-
this.ctx.isBashMode =
|
|
385
|
-
this.ctx.isPythonMode = trimmed
|
|
404
|
+
this.ctx.isBashMode = trimmed.startsWith("!");
|
|
405
|
+
this.ctx.isPythonMode = pythonCommandPrefixLength(trimmed) > 0;
|
|
386
406
|
if (wasBashMode !== this.ctx.isBashMode || wasPythonMode !== this.ctx.isPythonMode) {
|
|
387
407
|
this.ctx.updateEditorBorderColor();
|
|
388
408
|
}
|
|
@@ -550,7 +570,7 @@ export class InputController {
|
|
|
550
570
|
this.ctx.editor.setText("");
|
|
551
571
|
return;
|
|
552
572
|
}
|
|
553
|
-
if (text.startsWith("!") || text
|
|
573
|
+
if (text.startsWith("!") || parsePythonCommandInput(text)) {
|
|
554
574
|
this.ctx.showStatus("Local execution is host-only during a collab session");
|
|
555
575
|
this.ctx.editor.setText("");
|
|
556
576
|
return;
|
|
@@ -598,10 +618,11 @@ export class InputController {
|
|
|
598
618
|
}
|
|
599
619
|
}
|
|
600
620
|
|
|
601
|
-
// Handle python command (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
621
|
+
// Handle python command (`$ <code>` for normal, `$$ <code>` for excluded from context).
|
|
622
|
+
// Shell-style variables such as `$HOME` are normal prose unless a space follows the sigil.
|
|
623
|
+
const pythonCommand = parsePythonCommandInput(text);
|
|
624
|
+
if (pythonCommand) {
|
|
625
|
+
const { code, isExcluded } = pythonCommand;
|
|
605
626
|
if (code) {
|
|
606
627
|
if (this.ctx.session.isEvalRunning) {
|
|
607
628
|
this.ctx.showWarning("A Python execution is already running. Press Esc to cancel it first.");
|
|
@@ -768,7 +789,7 @@ export class InputController {
|
|
|
768
789
|
}
|
|
769
790
|
return;
|
|
770
791
|
}
|
|
771
|
-
if (text.startsWith("/") || text.startsWith("!") || text
|
|
792
|
+
if (text.startsWith("/") || text.startsWith("!") || parsePythonCommandInput(text)) {
|
|
772
793
|
this.ctx.showStatus("Commands run in the main session — press ←← to return first");
|
|
773
794
|
return; // editor text not cleared: Editor does not auto-clear on submit
|
|
774
795
|
}
|
|
@@ -797,7 +797,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
797
797
|
await this.initHooksAndCustomTools();
|
|
798
798
|
|
|
799
799
|
// Restore mode from session (e.g. plan mode on resume)
|
|
800
|
-
this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession());
|
|
800
|
+
this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession({ preserveActiveGoal: true }));
|
|
801
801
|
await this.#reconcileModeFromSession();
|
|
802
802
|
|
|
803
803
|
// Brand-new sessions optionally start in plan mode when the user has made it
|
|
@@ -1783,11 +1783,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1783
1783
|
}
|
|
1784
1784
|
|
|
1785
1785
|
/** Reconcile mode state from session entries on resume/switch. */
|
|
1786
|
-
async #reconcileModeFromSession(): Promise<void> {
|
|
1786
|
+
async #reconcileModeFromSession(options?: { preserveActiveGoal?: boolean }): Promise<void> {
|
|
1787
1787
|
await this.#clearTransientModeState();
|
|
1788
1788
|
const sessionContext = this.sessionManager.buildSessionContext();
|
|
1789
1789
|
const goalEnabled = this.session.settings.get("goal.enabled");
|
|
1790
1790
|
if (!goalEnabled && (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused")) {
|
|
1791
|
+
this.session.goalRuntime.clearAccounting();
|
|
1791
1792
|
this.sessionManager.appendModeChange("none");
|
|
1792
1793
|
return;
|
|
1793
1794
|
}
|
|
@@ -1802,7 +1803,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1802
1803
|
mode: "active",
|
|
1803
1804
|
goal,
|
|
1804
1805
|
});
|
|
1805
|
-
const restored = await this.session.goalRuntime.onThreadResumed(
|
|
1806
|
+
const restored = await this.session.goalRuntime.onThreadResumed({
|
|
1807
|
+
preserveActiveGoal: options?.preserveActiveGoal,
|
|
1808
|
+
});
|
|
1806
1809
|
this.goalModeEnabled = restored?.enabled === true;
|
|
1807
1810
|
this.goalModePaused = restored?.enabled !== true && restored?.goal.status === "paused";
|
|
1808
1811
|
// sdk.ts excludes "goal" from the initial active tool set unconditionally.
|
|
@@ -1815,6 +1818,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1815
1818
|
this.#updateGoalModeStatus();
|
|
1816
1819
|
return;
|
|
1817
1820
|
}
|
|
1821
|
+
this.session.goalRuntime.clearAccounting();
|
|
1818
1822
|
if (!this.session.settings.get("plan.enabled")) {
|
|
1819
1823
|
// Clear stale plan/plan_paused mode so re-enabling the setting
|
|
1820
1824
|
// later doesn't unexpectedly restore an old plan session.
|
|
@@ -2309,7 +2313,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2309
2313
|
// Branchless mark+clear when !compactBeforeExecute: mark is gated; clear
|
|
2310
2314
|
// is unconditional and idempotent.
|
|
2311
2315
|
if (options.compactBeforeExecute) {
|
|
2312
|
-
this.session.
|
|
2316
|
+
this.session.markPlanInternalAbortPending();
|
|
2313
2317
|
}
|
|
2314
2318
|
let compactOutcome: CompactionOutcome | undefined;
|
|
2315
2319
|
try {
|
|
@@ -2355,7 +2359,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2355
2359
|
// (i.e., the !compactBeforeExecute branch), and a no-op when the flag
|
|
2356
2360
|
// was already consumed by AgentSession.#handleAgentEvent's aborted
|
|
2357
2361
|
// message_end stamping. Guarantees the flag is dead at every exit.
|
|
2358
|
-
this.session.
|
|
2362
|
+
this.session.clearPlanInternalAbortPending();
|
|
2359
2363
|
}
|
|
2360
2364
|
|
|
2361
2365
|
// Tool restoration runs on every path — the plan mode tools must be
|
|
@@ -2421,10 +2425,18 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2421
2425
|
// in-flight turn first — abort() bumps the prompt generation and cancels pending
|
|
2422
2426
|
// continuations, so nothing re-streams in the synchronous gap before prompt().
|
|
2423
2427
|
if (this.session.isStreaming) {
|
|
2424
|
-
await this
|
|
2428
|
+
await this.#abortPlanApprovalTurnSilently();
|
|
2425
2429
|
}
|
|
2426
2430
|
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
2427
2431
|
}
|
|
2432
|
+
async #abortPlanApprovalTurnSilently(): Promise<void> {
|
|
2433
|
+
this.session.markPlanInternalAbortPending();
|
|
2434
|
+
try {
|
|
2435
|
+
await this.session.abort();
|
|
2436
|
+
} finally {
|
|
2437
|
+
this.session.clearPlanInternalAbortPending();
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2428
2440
|
|
|
2429
2441
|
async handlePlanModeCommand(initialPrompt?: string): Promise<void> {
|
|
2430
2442
|
if (this.goalModeEnabled || this.goalModePaused) {
|
|
@@ -2807,7 +2819,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2807
2819
|
// plan) while the popup is showing. The event listener fires asynchronously
|
|
2808
2820
|
// (agent's #emit is fire-and-forget), so without this the model sees
|
|
2809
2821
|
// "Plan ready for approval." and immediately re-invokes `resolve` in a loop.
|
|
2810
|
-
|
|
2822
|
+
// This abort is an internal UI transition, not operator cancellation.
|
|
2823
|
+
await this.#abortPlanApprovalTurnSilently();
|
|
2811
2824
|
|
|
2812
2825
|
const planFilePath = details.planFilePath || this.planModePlanFilePath || (await this.#getPlanFilePath());
|
|
2813
2826
|
this.planModePlanFilePath = planFilePath;
|
|
@@ -2912,11 +2925,19 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2912
2925
|
}
|
|
2913
2926
|
|
|
2914
2927
|
if (choice === "Refine plan") {
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2928
|
+
const refinement = feedback.trim();
|
|
2929
|
+
try {
|
|
2930
|
+
if (refinement) {
|
|
2931
|
+
if (this.onInputCallback) {
|
|
2932
|
+
this.onInputCallback(this.startPendingSubmission({ text: feedback }));
|
|
2933
|
+
} else {
|
|
2934
|
+
await this.session.prompt(feedback);
|
|
2935
|
+
}
|
|
2936
|
+
} else {
|
|
2937
|
+
this.showStatus("Refine plan: enter a follow-up prompt.");
|
|
2938
|
+
}
|
|
2939
|
+
} catch (error) {
|
|
2940
|
+
this.showError(`Failed to refine plan: ${error instanceof Error ? error.message : String(error)}`);
|
|
2920
2941
|
}
|
|
2921
2942
|
return;
|
|
2922
2943
|
}
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface PrintModeOptions {
|
|
|
24
24
|
initialMessage?: string;
|
|
25
25
|
/** Images to attach to the initial message */
|
|
26
26
|
initialImages?: ImageContent[];
|
|
27
|
+
/** If true, include thinking blocks in text output */
|
|
28
|
+
printThoughts?: boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -31,7 +33,7 @@ export interface PrintModeOptions {
|
|
|
31
33
|
* Sends prompts to the agent and outputs the result.
|
|
32
34
|
*/
|
|
33
35
|
export async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void> {
|
|
34
|
-
const { mode, messages = [], initialMessage, initialImages } = options;
|
|
36
|
+
const { mode, messages = [], initialMessage, initialImages, printThoughts } = options;
|
|
35
37
|
|
|
36
38
|
// Emit session header for JSON mode
|
|
37
39
|
if (mode === "json") {
|
|
@@ -108,6 +110,8 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
108
110
|
for (const content of assistantMsg.content) {
|
|
109
111
|
if (content.type === "text") {
|
|
110
112
|
process.stdout.write(`${sanitizeText(content.text)}\n`);
|
|
113
|
+
} else if (printThoughts && content.type === "thinking" && content.thinking.trim().length > 0) {
|
|
114
|
+
process.stdout.write(`${sanitizeText(content.thinking)}\n`);
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
}
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
Send one concrete, terse piece of advice to the agent you are watching.
|
|
1
|
+
Send one concrete, terse piece of advice to the agent you are watching.
|
|
2
|
+
- Use sparingly; stay silent when nothing matters.
|
|
3
|
+
- Call it to head off likely-wrong or materially wasteful work.
|
|
@@ -1,31 +1,75 @@
|
|
|
1
1
|
<system-conventions>
|
|
2
2
|
RFC 2119 applies to MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` and `AVOID` are aliases for `MUST NOT` and `SHOULD NOT`.
|
|
3
|
-
You can explore the workspace; budget is 2–3 tool calls per advise (exception: critical bugs warrant deeper verification before raising a blocker).
|
|
4
3
|
</system-conventions>
|
|
5
4
|
|
|
6
|
-
You bring a different angle.
|
|
7
|
-
|
|
5
|
+
You bring a different angle, and advocate for the user and the code-quality & robustness.
|
|
6
|
+
You're watching over the main agent as a peer-programmer:
|
|
7
|
+
- They might not have thought about an edge case, or realized a more elegant approach exists.
|
|
8
|
+
- They might be sinking deeper into a hole that will not get the user's request accomplished.
|
|
9
|
+
|
|
8
10
|
Your job is to offer that view before they sink work into the wrong direction.
|
|
9
11
|
|
|
10
12
|
<workflow>
|
|
11
|
-
You receive the agent's transcript incrementally, including
|
|
13
|
+
You receive the agent's transcript incrementally, including their thoughts.
|
|
12
14
|
You have read-only access through `read`, `search`, `find` to verify your suspicions.
|
|
13
|
-
Keep exploration lean
|
|
15
|
+
Keep exploration lean:
|
|
16
|
+
- 2–3 tool calls per advise.
|
|
17
|
+
- Exception: critical bugs may need deeper verification before raising a blocker.
|
|
14
18
|
</workflow>
|
|
15
19
|
|
|
16
20
|
<communication>
|
|
17
|
-
|
|
21
|
+
- You call `advise` to surface your commentary to the driving agent; at most one `advise` per update.
|
|
22
|
+
- Prefer silence when the agent is on track.
|
|
23
|
+
- Address the agent directly.
|
|
24
|
+
- Offer alternatives, not lectures.
|
|
25
|
+
- NEVER restate information the agent already has, including errors they have seen.
|
|
26
|
+
- Examples: type errors, LSP diagnostics, failed builds, failing tests, lint.
|
|
27
|
+
- NEVER repeat advice you already gave.
|
|
28
|
+
- NEVER nitpick about things user stated they are okay with. You are the advocate for the user.
|
|
18
29
|
</communication>
|
|
19
30
|
|
|
20
31
|
<critical>
|
|
21
|
-
|
|
32
|
+
A low-confidence bar applies ONLY to concrete technical risk:
|
|
33
|
+
- Generic uncertainty, vague unease, or user-intent ambiguity → stay SILENT.
|
|
34
|
+
|
|
22
35
|
NEVER advise just to second-guess decisions the agent understands and is committed to, if you are not certain.
|
|
36
|
+
|
|
37
|
+
NEVER advise on intent or process:
|
|
38
|
+
- Do not push the agent to ask for clarification, confirm scope, or summarize input before acting.
|
|
39
|
+
- Do not question whether the user's ask is clear enough.
|
|
40
|
+
- Intent is the agent's domain; it defaults to informed action.
|
|
41
|
+
- Your lane: correctness, edge cases, design, process.
|
|
42
|
+
|
|
43
|
+
Cite the exact instruction or risk.
|
|
23
44
|
</critical>
|
|
24
45
|
|
|
25
46
|
<completeness>
|
|
26
|
-
**`nit`**
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
**`nit`**
|
|
48
|
+
- Non-urgent cleanup, refactor, style, missed opportunity.
|
|
49
|
+
- Folded at next step boundary; agent keeps working.
|
|
50
|
+
- Examples:
|
|
51
|
+
- Edge cases that don't break correctness.
|
|
52
|
+
- Simplifications.
|
|
53
|
+
- Better approach the agent can consider.
|
|
54
|
+
|
|
55
|
+
**`concern`**
|
|
56
|
+
- Agent might be heading wrong or missed something material.
|
|
57
|
+
- Offers your view; agent decides.
|
|
58
|
+
- Use when:
|
|
59
|
+
- Exploring wrong code path.
|
|
60
|
+
- Picking fragile approach when better exists.
|
|
61
|
+
- Not parallelizing when user request is obviously parallelizable.
|
|
62
|
+
- Missing constraint.
|
|
63
|
+
- Edge case about to be baked in.
|
|
64
|
+
|
|
65
|
+
**`blocker`**
|
|
66
|
+
- Stop and reconsider.
|
|
67
|
+
- Use ONLY when the agent making progress will clearly:
|
|
68
|
+
- Waste the users time with a larger refactor.
|
|
69
|
+
- Will require the user to interrupt the agent later on, due to them going in circles without a solution.
|
|
70
|
+
- Be fundamentally unsound.
|
|
71
|
+
- Verify thoroughly before raising.
|
|
29
72
|
</completeness>
|
|
30
73
|
|
|
31
|
-
You MAY suggest an approach or fix if you've explored enough to be confident.
|
|
74
|
+
You MAY suggest an approach or fix if you've explored enough to be confident.
|
|
75
|
+
Offer the better designs, not just the warning.
|
package/src/sdk.ts
CHANGED
|
@@ -56,6 +56,10 @@ import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate
|
|
|
56
56
|
import { Settings, type SkillsSettings } from "./config/settings";
|
|
57
57
|
import { CursorExecHandlers } from "./cursor";
|
|
58
58
|
import "./discovery";
|
|
59
|
+
import { AuthBrokerClient } from "@oh-my-pi/pi-ai/auth-broker/client";
|
|
60
|
+
import { RemoteAuthCredentialStore } from "@oh-my-pi/pi-ai/auth-broker/remote-store";
|
|
61
|
+
import { readAuthBrokerSnapshotCache, writeAuthBrokerSnapshotCache } from "@oh-my-pi/pi-ai/auth-broker/snapshot-cache";
|
|
62
|
+
import { DEFAULT_SNAPSHOT_CACHE_TTL_MS, type SnapshotResponse } from "@oh-my-pi/pi-ai/auth-broker/types";
|
|
59
63
|
import { resolveConfigValue } from "./config/resolve-config-value";
|
|
60
64
|
import { initializeWithSettings } from "./discovery";
|
|
61
65
|
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
|
|
@@ -116,15 +120,7 @@ import {
|
|
|
116
120
|
} from "./secrets";
|
|
117
121
|
import { AgentSession } from "./session/agent-session";
|
|
118
122
|
import { resolveAuthBrokerConfig } from "./session/auth-broker-config";
|
|
119
|
-
import {
|
|
120
|
-
AuthBrokerClient,
|
|
121
|
-
AuthStorage,
|
|
122
|
-
DEFAULT_SNAPSHOT_CACHE_TTL_MS,
|
|
123
|
-
RemoteAuthCredentialStore,
|
|
124
|
-
readAuthBrokerSnapshotCache,
|
|
125
|
-
type SnapshotResponse,
|
|
126
|
-
writeAuthBrokerSnapshotCache,
|
|
127
|
-
} from "./session/auth-storage";
|
|
123
|
+
import { AuthStorage } from "./session/auth-storage";
|
|
128
124
|
import {
|
|
129
125
|
type CustomMessage,
|
|
130
126
|
convertToLlm,
|
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
type CompactionSummaryMessage,
|
|
35
35
|
countTokens,
|
|
36
36
|
resolveTelemetry,
|
|
37
|
-
STREAM_INTERRUPTED_AFTER_CONTENT_STOP_DETAIL,
|
|
38
37
|
ThinkingLevel,
|
|
39
38
|
} from "@oh-my-pi/pi-agent-core";
|
|
40
39
|
import {
|
|
@@ -1235,13 +1234,12 @@ export class AgentSession {
|
|
|
1235
1234
|
#ttsrResumePromise: Promise<void> | undefined = undefined;
|
|
1236
1235
|
#ttsrResumeResolve: (() => void) | undefined = undefined;
|
|
1237
1236
|
|
|
1238
|
-
/** One-shot flag
|
|
1239
|
-
*
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1242
|
-
* later unrelated aborts
|
|
1243
|
-
|
|
1244
|
-
#planCompactAbortPending = false;
|
|
1237
|
+
/** One-shot flag for expected internal plan-mode aborts. Approval actions may
|
|
1238
|
+
* abort the post-`resolve` continuation before compaction, execution, or
|
|
1239
|
+
* manual refinement. Consumed inside `#handleAgentEvent` for the matching
|
|
1240
|
+
* `message_end` + `stopReason: "aborted"`; callers clear it in `finally` so
|
|
1241
|
+
* it cannot leak into later unrelated aborts. */
|
|
1242
|
+
#planInternalAbortPending = false;
|
|
1245
1243
|
|
|
1246
1244
|
#postPromptTasks = new Set<Promise<unknown>>();
|
|
1247
1245
|
#postPromptTasksPromise: Promise<void> | undefined = undefined;
|
|
@@ -1677,6 +1675,33 @@ export class AgentSession {
|
|
|
1677
1675
|
this.#advisorInterruptImmuneTurnStart = this.#advisorPrimaryTurnsCompleted + 1;
|
|
1678
1676
|
}
|
|
1679
1677
|
|
|
1678
|
+
/**
|
|
1679
|
+
* Re-prime the advisor across a conversation boundary: `/new`, `/branch`,
|
|
1680
|
+
* `/btw`, `/tree`, and session switch/resume. Beyond {@link AdvisorRuntime.reset}
|
|
1681
|
+
* (which only re-primes the advisor's transcript view and is also fired by
|
|
1682
|
+
* within-conversation rewrites like compaction/shake/rewind), this clears the
|
|
1683
|
+
* session-level interrupt latches so the prior conversation's cooldown cannot
|
|
1684
|
+
* leak into the new one: the post-interrupt immune-turn window
|
|
1685
|
+
* (`#advisorPrimaryTurnsCompleted`, `#advisorInterruptImmuneTurnStart`) and the
|
|
1686
|
+
* user-interrupt auto-resume suppression flag. It also drops advisor deliveries
|
|
1687
|
+
* still queued against the prior conversation — pending asides in the yield
|
|
1688
|
+
* queue (advisor entries use `skipIdleFlush`, so they linger until the next
|
|
1689
|
+
* `drainLazy` rather than self-flushing), interrupting cards parked in the
|
|
1690
|
+
* agent steer/follow-up queues, and preserved cards deferred to the next turn —
|
|
1691
|
+
* so none of them inject into the new conversation.
|
|
1692
|
+
*/
|
|
1693
|
+
#resetAdvisorSessionState(): void {
|
|
1694
|
+
this.#advisorRuntime?.reset();
|
|
1695
|
+
this.#advisorPrimaryTurnsCompleted = 0;
|
|
1696
|
+
this.#advisorInterruptImmuneTurnStart = undefined;
|
|
1697
|
+
this.#advisorAutoResumeSuppressed = false;
|
|
1698
|
+
this.yieldQueue.clear("advisor");
|
|
1699
|
+
this.#extractQueuedAdvisorCards();
|
|
1700
|
+
if (this.#pendingNextTurnMessages.some(isAdvisorCard)) {
|
|
1701
|
+
this.#pendingNextTurnMessages = this.#pendingNextTurnMessages.filter(m => !isAdvisorCard(m));
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1680
1705
|
#buildAdvisorRuntime(seedToCurrent = false): boolean {
|
|
1681
1706
|
if (this.#isDisposed) return false;
|
|
1682
1707
|
if (this.#advisorRuntime) return true;
|
|
@@ -2115,25 +2140,24 @@ export class AgentSession {
|
|
|
2115
2140
|
return this.#ttsrAbortPending;
|
|
2116
2141
|
}
|
|
2117
2142
|
|
|
2118
|
-
/** Whether
|
|
2119
|
-
*
|
|
2120
|
-
*
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
return this.#planCompactAbortPending;
|
|
2143
|
+
/** Whether an expected internal plan-mode abort is pending. Consumed by
|
|
2144
|
+
* `#handleAgentEvent` to stamp `SILENT_ABORT_MARKER` on the next aborted
|
|
2145
|
+
* assistant message_end; callers clear it in `finally`. */
|
|
2146
|
+
get isPlanInternalAbortPending(): boolean {
|
|
2147
|
+
return this.#planInternalAbortPending;
|
|
2124
2148
|
}
|
|
2125
2149
|
|
|
2126
2150
|
/** Arm the silent-abort marker for the next aborted assistant message_end.
|
|
2127
|
-
* Caller MUST clear via `
|
|
2151
|
+
* Caller MUST clear via `clearPlanInternalAbortPending()` in a `finally`
|
|
2128
2152
|
* to guarantee no leak. */
|
|
2129
|
-
|
|
2130
|
-
this.#
|
|
2153
|
+
markPlanInternalAbortPending(): void {
|
|
2154
|
+
this.#planInternalAbortPending = true;
|
|
2131
2155
|
}
|
|
2132
2156
|
|
|
2133
2157
|
/** Unconditionally clear the silent-abort flag. Idempotent: safe when the
|
|
2134
2158
|
* flag was never set OR was already consumed by `#handleAgentEvent`. */
|
|
2135
|
-
|
|
2136
|
-
this.#
|
|
2159
|
+
clearPlanInternalAbortPending(): void {
|
|
2160
|
+
this.#planInternalAbortPending = false;
|
|
2137
2161
|
}
|
|
2138
2162
|
|
|
2139
2163
|
getAsyncJobSnapshot(options?: { recentLimit?: number }): AsyncJobSnapshot | null {
|
|
@@ -2283,7 +2307,7 @@ export class AgentSession {
|
|
|
2283
2307
|
};
|
|
2284
2308
|
|
|
2285
2309
|
#processAgentEvent = async (event: AgentEvent): Promise<void> => {
|
|
2286
|
-
// Plan-mode
|
|
2310
|
+
// Plan-mode internal transition: stamp `SILENT_ABORT_MARKER` on the
|
|
2287
2311
|
// persisted message BEFORE the obfuscator's display-side copy below.
|
|
2288
2312
|
// Invariant (must hold across refactors): this branch precedes the
|
|
2289
2313
|
// `let displayEvent = event; ... displayEvent = { ...event, message: { ...message, content: deobfuscated } }`
|
|
@@ -2291,18 +2315,16 @@ export class AgentSession {
|
|
|
2291
2315
|
// and `event.message` (in-place mutation, used by SessionManager
|
|
2292
2316
|
// persistence) carry the marker, guaranteeing streaming render and
|
|
2293
2317
|
// history replay branch identically. The one-shot flag is consumed
|
|
2294
|
-
// here, scoped strictly to this aborted message_end;
|
|
2295
|
-
// `finally`
|
|
2296
|
-
// every terminal compaction outcome (`ok` / `cancelled` / `failed` /
|
|
2297
|
-
// throw) so a leaked flag cannot silence a later unrelated abort.
|
|
2318
|
+
// here, scoped strictly to this aborted message_end; callers still clear it
|
|
2319
|
+
// in `finally` so a leaked flag cannot silence a later unrelated abort.
|
|
2298
2320
|
if (
|
|
2299
2321
|
event.type === "message_end" &&
|
|
2300
2322
|
event.message.role === "assistant" &&
|
|
2301
2323
|
event.message.stopReason === "aborted" &&
|
|
2302
|
-
this.#
|
|
2324
|
+
this.#planInternalAbortPending
|
|
2303
2325
|
) {
|
|
2304
2326
|
(event.message as AssistantMessage).errorMessage = SILENT_ABORT_MARKER;
|
|
2305
|
-
this.#
|
|
2327
|
+
this.#planInternalAbortPending = false;
|
|
2306
2328
|
}
|
|
2307
2329
|
|
|
2308
2330
|
// Deobfuscate assistant message content for display emission — the LLM echoes back
|
|
@@ -6552,7 +6574,7 @@ export class AgentSession {
|
|
|
6552
6574
|
this.#todoReminderAwaitingProgress = false;
|
|
6553
6575
|
this.#planReferenceSent = false;
|
|
6554
6576
|
this.#planReferencePath = "local://PLAN.md";
|
|
6555
|
-
this.#
|
|
6577
|
+
this.#resetAdvisorSessionState();
|
|
6556
6578
|
this.#reconnectToAgent();
|
|
6557
6579
|
|
|
6558
6580
|
// Emit session_switch event with reason "new" to hooks
|
|
@@ -7353,7 +7375,7 @@ export class AgentSession {
|
|
|
7353
7375
|
throw new Error("Compaction already in progress");
|
|
7354
7376
|
}
|
|
7355
7377
|
this.#disconnectFromAgent();
|
|
7356
|
-
await this.abort();
|
|
7378
|
+
await this.abort({ goalReason: "internal" });
|
|
7357
7379
|
const compactionAbortController = new AbortController();
|
|
7358
7380
|
this.#compactionAbortController = compactionAbortController;
|
|
7359
7381
|
|
|
@@ -7521,6 +7543,10 @@ export class AgentSession {
|
|
|
7521
7543
|
const newEntries = this.sessionManager.getEntries();
|
|
7522
7544
|
const sessionContext = this.buildDisplaySessionContext();
|
|
7523
7545
|
this.agent.replaceMessages(sessionContext.messages);
|
|
7546
|
+
// Compaction discarded the conversation history that carried the approved
|
|
7547
|
+
// plan reference. Clear the sent-flag so #buildPlanReferenceMessage re-reads
|
|
7548
|
+
// the plan from disk and re-injects it on the next turn (issue #1246).
|
|
7549
|
+
this.#planReferenceSent = false;
|
|
7524
7550
|
this.#advisorRuntime?.reset();
|
|
7525
7551
|
this.#syncTodoPhasesFromBranch();
|
|
7526
7552
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
@@ -9387,6 +9413,10 @@ export class AgentSession {
|
|
|
9387
9413
|
const newEntries = this.sessionManager.getEntries();
|
|
9388
9414
|
const sessionContext = this.buildDisplaySessionContext();
|
|
9389
9415
|
this.agent.replaceMessages(sessionContext.messages);
|
|
9416
|
+
// Compaction discarded the conversation history that carried the approved
|
|
9417
|
+
// plan reference. Clear the sent-flag so #buildPlanReferenceMessage re-reads
|
|
9418
|
+
// the plan from disk and re-injects it on the next turn (issue #1246).
|
|
9419
|
+
this.#planReferenceSent = false;
|
|
9390
9420
|
this.#advisorRuntime?.reset();
|
|
9391
9421
|
this.#syncTodoPhasesFromBranch();
|
|
9392
9422
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
@@ -9691,21 +9721,20 @@ export class AgentSession {
|
|
|
9691
9721
|
if (this.#isClassifierRefusal(message)) return true;
|
|
9692
9722
|
if (this.#isProviderErrorFinishReasonBeforeToolUse(message)) return true;
|
|
9693
9723
|
if (this.#isMalformedFunctionCallError(message)) return true;
|
|
9694
|
-
if (this.#
|
|
9724
|
+
if (this.#hasReplayUnsafeToolOutput(message)) return false;
|
|
9695
9725
|
if (this.#isStaleOpenAIResponsesReplayError(message)) return true;
|
|
9696
9726
|
|
|
9697
9727
|
const err = message.errorMessage;
|
|
9698
9728
|
return this.#isTransientErrorMessage(err) || isUsageLimitError(err);
|
|
9699
9729
|
}
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
return false;
|
|
9730
|
+
/**
|
|
9731
|
+
* Retried turns remove the failed assistant message from active context.
|
|
9732
|
+
* Text/thinking-only partials are safe to discard and replay. Retained
|
|
9733
|
+
* tool calls are not: a completed tool call may already have emitted its
|
|
9734
|
+
* tool result after this assistant message, so replaying can duplicate work.
|
|
9735
|
+
*/
|
|
9736
|
+
#hasReplayUnsafeToolOutput(message: AssistantMessage): boolean {
|
|
9737
|
+
return message.content.some(block => block.type === "toolCall");
|
|
9709
9738
|
}
|
|
9710
9739
|
|
|
9711
9740
|
#isStaleOpenAIResponsesReplayError(message: AssistantMessage): boolean {
|
|
@@ -10939,7 +10968,7 @@ export class AgentSession {
|
|
|
10939
10968
|
}
|
|
10940
10969
|
|
|
10941
10970
|
this.#disconnectFromAgent();
|
|
10942
|
-
await this.abort();
|
|
10971
|
+
await this.abort({ goalReason: "internal" });
|
|
10943
10972
|
|
|
10944
10973
|
// Flush pending writes before switching so restore snapshots reflect committed state.
|
|
10945
10974
|
await this.sessionManager.flush();
|
|
@@ -10997,6 +11026,7 @@ export class AgentSession {
|
|
|
10997
11026
|
}
|
|
10998
11027
|
|
|
10999
11028
|
this.agent.replaceMessages(sessionContext.messages);
|
|
11029
|
+
this.#resetAdvisorSessionState();
|
|
11000
11030
|
this.#syncTodoPhasesFromBranch();
|
|
11001
11031
|
if (switchingToDifferentSession) {
|
|
11002
11032
|
this.#closeAllProviderSessions("session switch");
|
|
@@ -11202,7 +11232,7 @@ export class AgentSession {
|
|
|
11202
11232
|
|
|
11203
11233
|
if (!skipConversationRestore) {
|
|
11204
11234
|
this.agent.replaceMessages(sessionContext.messages);
|
|
11205
|
-
this.#
|
|
11235
|
+
this.#resetAdvisorSessionState();
|
|
11206
11236
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
11207
11237
|
}
|
|
11208
11238
|
|
|
@@ -11291,7 +11321,7 @@ export class AgentSession {
|
|
|
11291
11321
|
}
|
|
11292
11322
|
|
|
11293
11323
|
this.agent.replaceMessages(sessionContext.messages);
|
|
11294
|
-
this.#
|
|
11324
|
+
this.#resetAdvisorSessionState();
|
|
11295
11325
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
11296
11326
|
|
|
11297
11327
|
return { cancelled: false, sessionFile: this.sessionFile };
|
|
@@ -11457,7 +11487,7 @@ export class AgentSession {
|
|
|
11457
11487
|
const displayContext = deobfuscateSessionContext(stateContext, this.#obfuscator);
|
|
11458
11488
|
await this.#restoreMCPSelectionsForSessionContext(displayContext);
|
|
11459
11489
|
this.agent.replaceMessages(displayContext.messages);
|
|
11460
|
-
this.#
|
|
11490
|
+
this.#resetAdvisorSessionState();
|
|
11461
11491
|
this.#syncTodoPhasesFromBranch();
|
|
11462
11492
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
11463
11493
|
|
|
@@ -18,16 +18,7 @@ export type {
|
|
|
18
18
|
ResetCreditRedeemOutcome,
|
|
19
19
|
ResetCreditTarget,
|
|
20
20
|
SerializedAuthStorage,
|
|
21
|
-
SnapshotResponse,
|
|
22
21
|
StoredAuthCredential,
|
|
23
22
|
} from "@oh-my-pi/pi-ai";
|
|
24
|
-
export {
|
|
25
|
-
|
|
26
|
-
AuthStorage,
|
|
27
|
-
DEFAULT_SNAPSHOT_CACHE_TTL_MS,
|
|
28
|
-
REMOTE_REFRESH_SENTINEL,
|
|
29
|
-
RemoteAuthCredentialStore,
|
|
30
|
-
readAuthBrokerSnapshotCache,
|
|
31
|
-
SqliteAuthCredentialStore,
|
|
32
|
-
writeAuthBrokerSnapshotCache,
|
|
33
|
-
} from "@oh-my-pi/pi-ai";
|
|
23
|
+
export { AuthStorage, REMOTE_REFRESH_SENTINEL, SqliteAuthCredentialStore } from "@oh-my-pi/pi-ai";
|
|
24
|
+
export type { SnapshotResponse } from "@oh-my-pi/pi-ai/auth-broker/types";
|
|
@@ -124,7 +124,13 @@ export class YieldQueue {
|
|
|
124
124
|
return thunks;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
/** Drop queued entries. With `kind`, drop only that kind's entries (leaving
|
|
128
|
+
* any pending idle-flush for other kinds intact); otherwise drop everything. */
|
|
129
|
+
clear(kind?: string): void {
|
|
130
|
+
if (kind !== undefined) {
|
|
131
|
+
this.#entries.delete(kind);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
128
134
|
this.#entries.clear();
|
|
129
135
|
this.#idleFlushPending = false;
|
|
130
136
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as net from "node:net";
|
|
2
2
|
import { Process, ProcessStatus } from "@oh-my-pi/pi-natives";
|
|
3
|
-
import {
|
|
3
|
+
import type { Browser, Page } from "puppeteer-core";
|
|
4
4
|
import { ToolError, throwIfAborted } from "../tool-errors";
|
|
5
5
|
|
|
6
6
|
const ATTACH_TARGET_SKIP_PATTERN =
|
|
@@ -126,7 +126,7 @@ export async function findReusableCdp(
|
|
|
126
126
|
export async function pickElectronTarget(browser: Browser, matcher?: string): Promise<Page> {
|
|
127
127
|
const discoveredPages = await Promise.all(
|
|
128
128
|
browser.targets().map(async target => {
|
|
129
|
-
if (target.type() !==
|
|
129
|
+
if (String(target.type()) !== "page") return null;
|
|
130
130
|
return await target.page().catch(() => null);
|
|
131
131
|
}),
|
|
132
132
|
);
|