@oh-my-pi/pi-coding-agent 17.1.7 → 17.1.8

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/{CHANGELOG-5k4dq4g6.md → CHANGELOG-vt8ene9g.md} +35 -0
  3. package/dist/cli.js +4087 -7122
  4. package/dist/template-dys3vk5b.js +1671 -0
  5. package/dist/template-f8wx9vfn.css +1355 -0
  6. package/dist/template-qat058wr.html +55 -0
  7. package/dist/tool-views.generated-jdfmzwmn.js +35 -0
  8. package/dist/types/advisor/advise-tool.d.ts +0 -7
  9. package/dist/types/advisor/runtime.d.ts +0 -9
  10. package/dist/types/async/job-manager.d.ts +11 -0
  11. package/dist/types/cleanse/agent.d.ts +19 -0
  12. package/dist/types/cleanse/balance.d.ts +7 -0
  13. package/dist/types/cleanse/checkers.d.ts +13 -0
  14. package/dist/types/cleanse/index.d.ts +16 -0
  15. package/dist/types/cleanse/loop.d.ts +16 -0
  16. package/dist/types/cleanse/parsers.d.ts +13 -0
  17. package/dist/types/cleanse/progress.d.ts +14 -0
  18. package/dist/types/cleanse/types.d.ts +64 -0
  19. package/dist/types/commands/cleanse.d.ts +23 -0
  20. package/dist/types/export/html/index.d.ts +2 -0
  21. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +27 -1
  22. package/dist/types/modes/interactive-mode.d.ts +4 -3
  23. package/dist/types/session/async-job-delivery.d.ts +8 -0
  24. package/dist/types/session/model-controls.d.ts +3 -3
  25. package/dist/types/task/isolation-ownership.d.ts +34 -0
  26. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +1 -2
  27. package/dist/types/tools/browser/tab-worker.d.ts +1 -4
  28. package/dist/types/tui/output-block.d.ts +5 -5
  29. package/package.json +16 -12
  30. package/scripts/bundle-dist.ts +3 -1
  31. package/src/advisor/advise-tool.ts +0 -11
  32. package/src/advisor/runtime.ts +0 -12
  33. package/src/async/job-manager.ts +30 -7
  34. package/src/cleanse/agent.ts +226 -0
  35. package/src/cleanse/balance.ts +79 -0
  36. package/src/cleanse/checkers.ts +996 -0
  37. package/src/cleanse/index.ts +190 -0
  38. package/src/cleanse/loop.ts +51 -0
  39. package/src/cleanse/parsers.ts +726 -0
  40. package/src/cleanse/progress.ts +50 -0
  41. package/src/cleanse/prompts/assignment.md +47 -0
  42. package/src/cleanse/types.ts +72 -0
  43. package/src/cli/update-cli.ts +3 -0
  44. package/src/cli/usage-cli.ts +29 -4
  45. package/src/cli/worktree-cli.ts +28 -11
  46. package/src/cli-commands.ts +1 -0
  47. package/src/cli.ts +2 -1
  48. package/src/commands/cleanse.ts +45 -0
  49. package/src/discovery/claude-plugins.ts +144 -34
  50. package/src/export/html/index.ts +17 -10
  51. package/src/extensibility/legacy-pi-ai-shim.ts +46 -1
  52. package/src/launch/broker.ts +14 -4
  53. package/src/modes/acp/acp-agent.ts +12 -9
  54. package/src/modes/acp/acp-event-mapper.ts +38 -4
  55. package/src/modes/components/settings-selector.ts +9 -1
  56. package/src/modes/interactive-mode.ts +36 -47
  57. package/src/modes/prompt-action-autocomplete.ts +5 -3
  58. package/src/prompts/goals/guided-goal-interview.md +41 -6
  59. package/src/prompts/system/vibe-mode-active.md +4 -1
  60. package/src/prompts/tools/browser.md +1 -0
  61. package/src/prompts/tools/goal.md +1 -1
  62. package/src/session/agent-session.ts +28 -4
  63. package/src/session/async-job-delivery.ts +8 -0
  64. package/src/session/model-controls.ts +4 -4
  65. package/src/session/session-advisors.ts +2 -7
  66. package/src/session/session-history-format.ts +31 -6
  67. package/src/slash-commands/builtin-registry.ts +5 -2
  68. package/src/task/isolation-ownership.ts +106 -0
  69. package/src/task/worktree.ts +8 -0
  70. package/src/tools/ask.ts +3 -3
  71. package/src/tools/ast-edit.ts +9 -2
  72. package/src/tools/browser/cmux/cmux-tab.ts +9 -14
  73. package/src/tools/browser/tab-worker.ts +12 -35
  74. package/src/tools/browser.ts +2 -2
  75. package/src/tools/gh-renderer.ts +3 -3
  76. package/src/tools/index.ts +10 -1
  77. package/src/tools/write.ts +35 -0
  78. package/src/tui/code-cell.ts +4 -4
  79. package/src/tui/output-block.ts +25 -8
  80. package/src/utils/shell-snapshot-fn-env.sh +5 -2
  81. package/src/web/search/render.ts +2 -2
  82. package/dist/types/goals/guided-setup.d.ts +0 -30
  83. package/src/goals/guided-setup.ts +0 -171
  84. package/src/prompts/goals/guided-goal-system.md +0 -33
@@ -36,13 +36,6 @@ export declare function formatAdvisorBatchContent(notes: readonly AdvisorNote[])
36
36
  * and `blocker` interrupt; a plain `nit` queues.
37
37
  */
38
38
  export declare function isInterruptingSeverity(severity: AdvisorSeverity | undefined): boolean;
39
- /**
40
- * Append a staleness caveat to an advisor note when newer primary turns arrived
41
- * after the reviewed transcript window (i.e. `hasFreshBacklog` is true on the
42
- * advisor runtime at delivery time). Pure function — no session coupling — so it
43
- * can be unit-tested in isolation and called from `AgentSession#routeAdvice`.
44
- */
45
- export declare function annotateForStaleness(note: string, hasFreshBacklog: boolean): string;
46
39
  /** How an advisor note is routed to the primary. */
47
40
  export type AdvisorDeliveryChannel = "aside" | "steer" | "preserve";
48
41
  /** Half-open turn-count fence for the post-interrupt cooldown. */
@@ -95,15 +95,6 @@ export declare class AdvisorRuntime {
95
95
  get failureNotified(): boolean;
96
96
  /** True after the runtime hard-stopped on repeated or permanent failures. */
97
97
  get halted(): boolean;
98
- /**
99
- * True when `#pending` is non-empty while the drain loop is busy — i.e., newer
100
- * primary turns arrived after the current batch's transcript window was fixed
101
- * but before the advisor model finished processing it. The delivery path uses
102
- * this to annotate advice that was generated without seeing those newer turns.
103
- * Can be true during `agent.prompt()`, a `maintainContext` await, or a retry
104
- * sleep — any time `#drain` is busy and a concurrent `onTurnEnd` pushed.
105
- */
106
- get hasFreshBacklog(): boolean;
107
98
  /**
108
99
  * Called after each primary turn ends. Renders the incremental delta and
109
100
  * queues it for the advisor model.
@@ -129,6 +129,17 @@ export declare class AsyncJobManager {
129
129
  * (used by `dispose()` to nuke the manager's state).
130
130
  */
131
131
  cancelAll(filter?: AsyncJobFilter): void;
132
+ /**
133
+ * Immediately evict completed and failed jobs matching the filter instead of
134
+ * waiting for retention expiry, dropping every queued delivery so a prior
135
+ * session's result can never be injected into a later transcript. Returns the
136
+ * number of jobs evicted.
137
+ *
138
+ * A delivery whose sink call is already in flight (or drained onto a caller's
139
+ * yield queue) is guarded by the owner's delivery generation, not the per-id
140
+ * suppression marker — that marker is cleared when the id is reused.
141
+ */
142
+ evictCompletedJobs(filter?: AsyncJobFilter): number;
132
143
  waitForAll(): Promise<void>;
133
144
  /**
134
145
  * Route completions for jobs owned by `ownerId` to `sink`. Sessions register
@@ -0,0 +1,19 @@
1
+ import type { CleanseAgentOutcome, CleanseAssignment, CleanseDiagnosticReport, CleanseLoopResult } from "./types.js";
2
+ /** Hooks used by the standalone command to render subagent lifecycle progress. */
3
+ export interface CleanseAgentHooks {
4
+ onStart?(name: string, assignment: CleanseAssignment): void;
5
+ onFinish?(outcome: CleanseAgentOutcome, assignment: CleanseAssignment): void;
6
+ }
7
+ /** Persisted parent session that dispatches file-disjoint cleanse workers. */
8
+ export interface CleanseAgentRuntime {
9
+ readonly model: string;
10
+ readonly sessionFile: string;
11
+ dispatch(assignments: CleanseAssignment[], wave: number, report: CleanseDiagnosticReport, signal?: AbortSignal): Promise<CleanseAgentOutcome[]>;
12
+ close(result?: CleanseLoopResult): Promise<void>;
13
+ }
14
+ /** Resolve the requested model and create a fresh persisted cleanse session. */
15
+ export declare function createCleanseAgentRuntime(options: {
16
+ cwd?: string;
17
+ model: string;
18
+ hooks?: CleanseAgentHooks;
19
+ }): Promise<CleanseAgentRuntime>;
@@ -0,0 +1,7 @@
1
+ import type { CleanseAssignment, CleanseDiagnostic, CleanseFileIssues } from "./types.js";
2
+ /** Estimate repair burden from severity and available location/fix evidence. */
3
+ export declare function diagnosticWeight(diagnostic: CleanseDiagnostic): number;
4
+ /** Group diagnostics by file while keeping project-level failures together. */
5
+ export declare function groupDiagnosticsByFile(diagnostics: readonly CleanseDiagnostic[]): CleanseFileIssues[];
6
+ /** Balance whole-file workloads with longest-processing-time bin packing. */
7
+ export declare function balanceDiagnostics(diagnostics: readonly CleanseDiagnostic[], maxAgents: number): CleanseAssignment[];
@@ -0,0 +1,13 @@
1
+ import type { CleanseDiagnosticReport, SkippedCleanseCheck } from "./types.js";
2
+ /** Optional checker families enabled for a cleanse run. */
3
+ export interface CleanseDiagnosticSuiteOptions {
4
+ includeTests?: boolean;
5
+ }
6
+ /** Re-runnable checker set discovered from one project snapshot. */
7
+ export interface CleanseDiagnosticSuite {
8
+ readonly checkCount: number;
9
+ readonly skipped: readonly SkippedCleanseCheck[];
10
+ run(signal?: AbortSignal): Promise<CleanseDiagnosticReport>;
11
+ }
12
+ /** Discover configured language checkers without installing missing tools. */
13
+ export declare function discoverCleanseDiagnosticSuite(projectCwd: string, options?: CleanseDiagnosticSuiteOptions): Promise<CleanseDiagnosticSuite>;
@@ -0,0 +1,16 @@
1
+ import type { CleanseDiagnosticReport } from "./types.js";
2
+ /** User-facing options for `omp cleanse`. */
3
+ export interface CleanseCommandOptions {
4
+ maxAgents?: number;
5
+ model?: string;
6
+ includeTests?: boolean;
7
+ }
8
+ /** Observable completion state returned to the CLI adapter. */
9
+ export interface CleanseCommandResult {
10
+ exitCode: number;
11
+ status: "clean" | "unresolved" | "unsupported" | "cancelled";
12
+ report: CleanseDiagnosticReport;
13
+ sessionFile?: string;
14
+ }
15
+ /** Detect project diagnostics, dispatch one bounded repair batch, and verify it. */
16
+ export declare function runCleanseCommand(options?: CleanseCommandOptions): Promise<CleanseCommandResult>;
@@ -0,0 +1,16 @@
1
+ import type { CleanseAgentOutcome, CleanseAssignment, CleanseDiagnosticReport, CleanseLoopResult } from "./types.js";
2
+ /** Runtime seams for one bounded diagnose, dispatch, and verify pass. */
3
+ export interface CleanseLoopDependencies {
4
+ collect(signal?: AbortSignal): Promise<CleanseDiagnosticReport>;
5
+ dispatch(assignments: CleanseAssignment[], wave: number, report: CleanseDiagnosticReport, signal?: AbortSignal): Promise<CleanseAgentOutcome[]>;
6
+ onWave?(wave: number, assignments: readonly CleanseAssignment[]): void;
7
+ onReport?(wave: number, report: CleanseDiagnosticReport): void;
8
+ }
9
+ /** Inputs controlling one complete cleanse loop. */
10
+ export interface CleanseLoopOptions {
11
+ maxAgents: number;
12
+ initialReport: CleanseDiagnosticReport;
13
+ signal?: AbortSignal;
14
+ }
15
+ /** Dispatch at most `maxAgents` workers once, then verify their combined edits. */
16
+ export declare function runCleanseLoop(options: CleanseLoopOptions, dependencies: CleanseLoopDependencies): Promise<CleanseLoopResult>;
@@ -0,0 +1,13 @@
1
+ import type { CleanseDiagnostic } from "./types.js";
2
+ /** Machine and fallback output formats understood by cleanse. */
3
+ export type CleanseParserKind = "rust" | "rust-test" | "go" | "go-test" | "ruff" | "pyright" | "eslint" | "biome" | "rubocop" | "phpstan" | "psalm" | "swiftlint" | "dart" | "credo" | "shellcheck" | "hlint" | "terraform" | "tflint" | "generic";
4
+ /** Captured checker process output passed to a format parser. */
5
+ export interface CleanseParserInput {
6
+ checker: string;
7
+ projectCwd: string;
8
+ checkerCwd: string;
9
+ stdout: string;
10
+ stderr: string;
11
+ }
12
+ /** Parse one checker invocation into normalized, project-relative diagnostics. */
13
+ export declare function parseCleanseDiagnostics(kind: CleanseParserKind, input: CleanseParserInput): CleanseDiagnostic[];
@@ -0,0 +1,14 @@
1
+ /** Minimal output contract used by the interactive cleanse progress reporter. */
2
+ export interface CleanseProgressOutput {
3
+ isTTY?: boolean;
4
+ write(text: string): boolean;
5
+ }
6
+ /** Renders completed repair workers on one transient terminal line. */
7
+ export interface CleanseProgressReporter {
8
+ readonly interactive: boolean;
9
+ start(total: number): void;
10
+ complete(): void;
11
+ finish(): void;
12
+ }
13
+ /** Create the TTY-only worker completion reporter used by `omp cleanse`. */
14
+ export declare function createCleanseProgressReporter(output?: CleanseProgressOutput): CleanseProgressReporter;
@@ -0,0 +1,64 @@
1
+ /** Severity normalized across checker-specific output formats. */
2
+ export type CleanseSeverity = "error" | "warning" | "info";
3
+ /** One actionable problem reported by a project checker. */
4
+ export interface CleanseDiagnostic {
5
+ checker: string;
6
+ file?: string;
7
+ line?: number;
8
+ column?: number;
9
+ endLine?: number;
10
+ endColumn?: number;
11
+ code?: string;
12
+ severity: CleanseSeverity;
13
+ message: string;
14
+ suggestion?: string;
15
+ }
16
+ /** Result metadata for one checker invocation. */
17
+ export interface CleanseCheckResult {
18
+ id: string;
19
+ label: string;
20
+ language: string;
21
+ cwd: string;
22
+ command: string;
23
+ exitCode: number | null;
24
+ diagnostics: CleanseDiagnostic[];
25
+ }
26
+ /** Checker discovery result omitted because its required executable was unavailable. */
27
+ export interface SkippedCleanseCheck {
28
+ label: string;
29
+ language: string;
30
+ reason: string;
31
+ }
32
+ /** Aggregate diagnostics from every checker selected for a project. */
33
+ export interface CleanseDiagnosticReport {
34
+ checks: CleanseCheckResult[];
35
+ diagnostics: CleanseDiagnostic[];
36
+ skipped: SkippedCleanseCheck[];
37
+ }
38
+ /** All diagnostics attached to one file, which scheduling never splits across agents. */
39
+ export interface CleanseFileIssues {
40
+ file?: string;
41
+ diagnostics: CleanseDiagnostic[];
42
+ weight: number;
43
+ }
44
+ /** One weighted, file-disjoint workload handed to a subagent. */
45
+ export interface CleanseAssignment {
46
+ index: number;
47
+ groups: CleanseFileIssues[];
48
+ weight: number;
49
+ }
50
+ /** Settled result from one cleanse subagent. */
51
+ export interface CleanseAgentOutcome {
52
+ name: string;
53
+ success: boolean;
54
+ output: string;
55
+ error?: string;
56
+ resolvedModel?: string;
57
+ }
58
+ /** Final state after one bounded repair wave and verification pass. */
59
+ export interface CleanseLoopResult {
60
+ status: "clean" | "stalled" | "cancelled";
61
+ waves: number;
62
+ report: CleanseDiagnosticReport;
63
+ outcomes: CleanseAgentOutcome[];
64
+ }
@@ -0,0 +1,23 @@
1
+ import { Command } from "@oh-my-pi/pi-utils/cli";
2
+ export default class Cleanse extends Command {
3
+ static description: string;
4
+ static flags: {
5
+ agents: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"integer"> & {
6
+ char: string;
7
+ description: string;
8
+ default: number;
9
+ };
10
+ model: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
11
+ char: string;
12
+ description: string;
13
+ default: string;
14
+ };
15
+ tests: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
16
+ char: string;
17
+ description: string;
18
+ default: boolean;
19
+ };
20
+ };
21
+ static examples: string[];
22
+ run(): Promise<void>;
23
+ }
@@ -3,6 +3,8 @@ import type { SessionEntry, SessionHeader } from "../../session/session-entries.
3
3
  import { SessionManager } from "../../session/session-manager.js";
4
4
  import type { ExportThemeNames } from "./args.js";
5
5
  export { type ExportThemeNames, parseExportArgs } from "./args.js";
6
+ /** Resolve a Bun file-loader value without parsing Windows drive letters as URL schemes. */
7
+ export declare function resolveBundledHtmlAssetPath(assetPath: string, moduleDir?: string): string;
6
8
  /** Compose the standalone export template: minified CSS, tool renderers, and viewer JS inlined. */
7
9
  export declare function getTemplate(): string;
8
10
  export interface ExportOptions {
@@ -19,7 +19,7 @@
19
19
  * `types.ts` via the `export *` below — pi-ai still exports both as types,
20
20
  * only the runtime `Type` builder and `StringEnum()` helper were removed.
21
21
  */
22
- import type { Api, Model } from "@oh-my-pi/pi-ai";
22
+ import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
23
23
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
24
24
  import { calculateCost, getBundledModel, getBundledModels, getBundledProviders, modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
25
25
  import { type TSchema, Type } from "./typebox.js";
@@ -32,6 +32,15 @@ export interface StringEnumOptions<T extends string> {
32
32
  export declare function StringEnum<T extends string | number>(values: readonly T[] | Record<string, T>, options?: StringEnumOptions<any>): TSchema;
33
33
  /** Clamp a historical Pi thinking level against OMP's model metadata. */
34
34
  export declare function clampThinkingLevel<TApi extends Api>(model: Model<TApi>, level: Effort | "off"): Effort | "off";
35
+ /**
36
+ * Compatibility implementation of historical pi-ai's `isRetryableAssistantError`.
37
+ *
38
+ * Classifies whether a failed assistant message looks like a transient provider
39
+ * or transport error so legacy extensions can decide if the last assistant turn
40
+ * should be restarted. Account/quota limits are treated as non-retryable. This
41
+ * does not implement any retry policy; callers own budget, backoff, and reporting.
42
+ */
43
+ export declare function isRetryableAssistantError(message: AssistantMessage): boolean;
35
44
  export * from "@oh-my-pi/pi-ai";
36
45
  /**
37
46
  * Compatibility re-exports for catalog symbols that pi-ai historically exposed
@@ -45,3 +54,20 @@ export * from "@oh-my-pi/pi-ai";
45
54
  export { calculateCost, getBundledModel, getBundledModels, getBundledProviders, modelsAreEqual, Type };
46
55
  export declare const getModel: typeof getBundledModel;
47
56
  export declare const getModels: typeof getBundledModels;
57
+ /**
58
+ * Compatibility re-exports for runtime helpers that upstream
59
+ * `@earendil-works/pi-ai` exposed from its package root but omp's
60
+ * `@oh-my-pi/pi-ai` barrel no longer forwards. Each symbol still exists in the
61
+ * host graph — only its root re-export was dropped — so bridging it here keeps
62
+ * legacy extensions importing it from the pi-ai root resolving through Bun's
63
+ * static named-export check (e.g. `omp plugin install pi-blackhole`).
64
+ *
65
+ * This is the full set derived from an audit of the upstream root surface: the
66
+ * error-classification predicate `isContextOverflow` (now under
67
+ * `@oh-my-pi/pi-ai/error`) and the JSON-repair helpers that omp relocated to
68
+ * `@oh-my-pi/pi-utils`. Upstream root symbols with no omp equivalent are
69
+ * intentionally not shimmed — the package has diverged and there is nothing to
70
+ * forward.
71
+ */
72
+ export { isContextOverflow } from "@oh-my-pi/pi-ai/error";
73
+ export { parseJsonWithRepair, parseStreamingJson, repairJson } from "@oh-my-pi/pi-utils";
@@ -257,9 +257,10 @@ export declare class InteractiveMode implements InteractiveModeContext {
257
257
  handlePlanModeCommand(initialPrompt?: string): Promise<void>;
258
258
  /**
259
259
  * `/vibe` toggle. Entering installs the ephemeral vibe tools, strips the
260
- * active toolset down to `read` plus those tools, and injects the director
261
- * context. Exiting unregisters them, restores the previous toolset, and kills
262
- * every worker session so workers cannot outlive the mode that directs them.
260
+ * active toolset down to `read`, optional parent-owned `todo`, plus those
261
+ * tools, and injects the director context. Exiting unregisters them, restores
262
+ * the previous toolset, and kills every worker session so workers cannot
263
+ * outlive the mode that directs them.
263
264
  */
264
265
  handleVibeModeCommand(initialPrompt?: string): Promise<void>;
265
266
  handleGoalModeCommand(rest?: string): Promise<void>;
@@ -14,6 +14,14 @@ export interface AsyncResultEntry {
14
14
  result: string;
15
15
  job: AsyncJob | undefined;
16
16
  durationMs: number | undefined;
17
+ /**
18
+ * Owning session's async-delivery generation at enqueue time. A session
19
+ * transition (`/new`, switch, handoff) bumps the generation, so an entry
20
+ * whose generation no longer matches belongs to a replaced transcript and
21
+ * is dropped at flush — even after its job id has been reused, which clears
22
+ * the manager's per-id suppression marker.
23
+ */
24
+ epoch: number;
17
25
  }
18
26
  type AsyncResultJobDetails = {
19
27
  jobId: string;
@@ -134,9 +134,9 @@ export declare class ModelControls {
134
134
  cycleThinkingLevel(): ConfiguredThinkingLevel | undefined;
135
135
  /**
136
136
  * Classify the current user turn and set the effective thinking level for it.
137
- * Bounded by a timeout + abort; on any failure (no smol model, timeout, parse
138
- * error) it falls back to the provisional concrete level and continues. Never
139
- * throws into the turn, and never clears `#autoThinking` (auto stays active).
137
+ * Bounded by a timeout + abort; on failure it preserves the last classified
138
+ * level, or uses the provisional concrete level before the first resolution.
139
+ * Never throws into the turn, and never clears `#autoThinking`.
140
140
  */
141
141
  applyAutoThinkingLevel(promptText: string, generation: number): Promise<void>;
142
142
  /**
@@ -0,0 +1,34 @@
1
+ /** Marker file written into a task-isolation base dir identifying its owner. */
2
+ export declare const ISOLATION_OWNER_FILE = ".omp-isolation-owner.json";
3
+ /** Recorded owner of a task-isolation sandbox. */
4
+ export interface IsolationOwner {
5
+ /** PID of the omp process that created and owns the sandbox. */
6
+ pid: number;
7
+ /** Task id the sandbox was materialised for. */
8
+ id: string;
9
+ /**
10
+ * Process-instance start-time token for {@link pid}, when the OS can report
11
+ * it. Distinguishes the owning process from an unrelated process that later
12
+ * inherits a recycled pid, so a crashed sandbox is never pinned live.
13
+ */
14
+ startToken?: string;
15
+ }
16
+ /**
17
+ * Record the current process as owner of the sandbox rooted at `baseDir`.
18
+ *
19
+ * Written before the isolation backend materialises `m` so a concurrent
20
+ * `omp worktree clear` never sees an owner-less sandbox mid-creation.
21
+ */
22
+ export declare function writeIsolationOwner(baseDir: string, id: string): Promise<void>;
23
+ /**
24
+ * Whether a live omp process still owns the sandbox at `baseDir`.
25
+ *
26
+ * A missing or malformed marker means no verifiable owner — a crashed run or a
27
+ * sandbox from before markers existed, both safe to reclaim. `process.kill(pid,
28
+ * 0)` can fail with `EPERM` even when the process is alive, so only an explicit
29
+ * `ESRCH` ("no such process") counts as dead; any other error is treated as
30
+ * alive to avoid deleting a sandbox that is actually in use. When the marker
31
+ * carries a {@link IsolationOwner.startToken}, a live pid whose current token no
32
+ * longer matches is a recycled pid — a different process — and counts as dead.
33
+ */
34
+ export declare function hasLiveIsolationOwner(baseDir: string): Promise<boolean>;
@@ -8,7 +8,6 @@ import type { CmuxSocketClient } from "./socket-client.js";
8
8
  interface ScreenshotOptions {
9
9
  selector?: string;
10
10
  fullPage?: boolean;
11
- save?: string;
12
11
  silent?: boolean;
13
12
  encoding?: "base64" | "binary";
14
13
  }
@@ -101,7 +100,7 @@ export declare class CmuxTab {
101
100
  scrollIntoView(selector: string): Promise<void>;
102
101
  select(selector: string, ...values: string[]): Promise<string[]>;
103
102
  extract(format?: ReadableFormat): Promise<string>;
104
- screenshot(opts?: ScreenshotOptions): Promise<ScreenshotResult>;
103
+ screenshot(opts?: ScreenshotOptions): Promise<string>;
105
104
  waitForUrl(pattern: string | RegExp, opts?: {
106
105
  timeout?: number;
107
106
  }): Promise<string>;
@@ -1,5 +1,5 @@
1
1
  import type { HTMLElement } from "linkedom";
2
- import type { ElementHandle, ImageFormat } from "puppeteer-core";
2
+ import type { ElementHandle } from "puppeteer-core";
3
3
  import type { Transport } from "./tab-protocol.js";
4
4
  declare module "puppeteer-core" {
5
5
  interface Frame {
@@ -41,7 +41,6 @@ export declare function resolveWaitTimeout(cellTimeoutMs: number, explicit?: num
41
41
  interface ScreenshotOptions {
42
42
  selector?: string;
43
43
  fullPage?: boolean;
44
- save?: string;
45
44
  silent?: boolean;
46
45
  }
47
46
  export declare function normalizeSelector(selector: string): string;
@@ -67,8 +66,6 @@ export interface InflightOp {
67
66
  }
68
67
  /** Human-readable label for a screenshot op, used in op tracking + timeout errors. */
69
68
  export declare function describeScreenshot(opts?: ScreenshotOptions): string;
70
- /** Map an explicit save path's extension to a puppeteer capture format (default png). */
71
- export declare function imageFormatForPath(filePath: string): ImageFormat;
72
69
  /** Summarize still-running helpers (oldest first) so a cell timeout names what stalled. */
73
70
  export declare function describeInflight(inflight: Map<number, InflightOp>): string;
74
71
  export declare class WorkerCore {
@@ -16,6 +16,7 @@ export interface OutputBlockOptions {
16
16
  width: number;
17
17
  applyBg?: boolean;
18
18
  contentPaddingLeft?: number;
19
+ contentPaddingRight?: number;
19
20
  /** Override the state-derived border color. Used for muted "legacy" tool
20
21
  * frames that should not visually compete with framed-output tools. */
21
22
  borderColor?: ThemeColor;
@@ -28,12 +29,11 @@ export declare function markFramedBlockComponent<T extends Component>(component:
28
29
  export declare function isFramedBlockComponent(component: Component): boolean;
29
30
  /**
30
31
  * Inner content width that {@link renderOutputBlock} wraps its body to, for a
31
- * given outer `width`: both vertical borders (1 cell each) plus the left
32
- * content padding. Renderers that size a tail window MUST budget visual rows
33
- * against this, not the outer width otherwise the block re-wraps their lines
34
- * into more rows than they counted and the box overflows its intended height.
32
+ * given outer `width`: both vertical borders plus symmetric content padding.
33
+ * An explicit left padding of zero keeps legacy flush blocks flush on both
34
+ * sides unless a right padding is provided separately.
35
35
  */
36
- export declare function outputBlockContentWidth(width: number, contentPaddingLeft?: number): number;
36
+ export declare function outputBlockContentWidth(width: number, contentPaddingLeft?: number, contentPaddingRight?: number): number;
37
37
  export declare function renderOutputBlock(options: OutputBlockOptions, theme: Theme): string[];
38
38
  /**
39
39
  * Cached wrapper around `renderOutputBlock`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "17.1.7",
4
+ "version": "17.1.8",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -52,17 +52,17 @@
52
52
  "@agentclientprotocol/sdk": "1.2.1",
53
53
  "@babel/parser": "^7.29.7",
54
54
  "@mozilla/readability": "^0.6.0",
55
- "@oh-my-pi/hashline": "17.1.7",
56
- "@oh-my-pi/omp-stats": "17.1.7",
57
- "@oh-my-pi/pi-agent-core": "17.1.7",
58
- "@oh-my-pi/pi-ai": "17.1.7",
59
- "@oh-my-pi/pi-catalog": "17.1.7",
60
- "@oh-my-pi/pi-mnemopi": "17.1.7",
61
- "@oh-my-pi/pi-natives": "17.1.7",
62
- "@oh-my-pi/pi-tui": "17.1.7",
63
- "@oh-my-pi/pi-utils": "17.1.7",
64
- "@oh-my-pi/pi-wire": "17.1.7",
65
- "@oh-my-pi/snapcompact": "17.1.7",
55
+ "@oh-my-pi/hashline": "17.1.8",
56
+ "@oh-my-pi/omp-stats": "17.1.8",
57
+ "@oh-my-pi/pi-agent-core": "17.1.8",
58
+ "@oh-my-pi/pi-ai": "17.1.8",
59
+ "@oh-my-pi/pi-catalog": "17.1.8",
60
+ "@oh-my-pi/pi-mnemopi": "17.1.8",
61
+ "@oh-my-pi/pi-natives": "17.1.8",
62
+ "@oh-my-pi/pi-tui": "17.1.8",
63
+ "@oh-my-pi/pi-utils": "17.1.8",
64
+ "@oh-my-pi/pi-wire": "17.1.8",
65
+ "@oh-my-pi/snapcompact": "17.1.8",
66
66
  "@opentelemetry/api": "^1.9.1",
67
67
  "@opentelemetry/api-logs": "^0.220.0",
68
68
  "@opentelemetry/context-async-hooks": "^2.9.0",
@@ -107,6 +107,10 @@
107
107
  "dist/cli.js",
108
108
  "dist/CHANGELOG-*.md",
109
109
  "dist/*.node",
110
+ "dist/template-*.css",
111
+ "dist/template-*.html",
112
+ "dist/template-*.js",
113
+ "dist/tool-views.generated-*.js",
110
114
  "scripts",
111
115
  "examples",
112
116
  "README.md",
@@ -9,6 +9,7 @@ const packageDir = path.join(import.meta.dir, "..");
9
9
  const outDir = path.join(packageDir, "dist");
10
10
  const cliPath = path.join(outDir, "cli.js");
11
11
  const shebang = "#!/usr/bin/env bun\n";
12
+ const legacyHtmlExportAssetPattern = /^(?:template-[^.]+\.(?:css|html|js)|tool-views\.generated-[^.]+\.js)$/;
12
13
 
13
14
  // Native / optional / platform-specific deps are loaded from installed files.
14
15
  // `omp-legacy-pi-modules` exists only in compiled binaries via the build plugin;
@@ -80,7 +81,8 @@ async function cleanBundleOutputs(): Promise<void> {
80
81
  entry === "cli.js" ||
81
82
  entry.endsWith(".node") ||
82
83
  entry.endsWith(".js.map") ||
83
- (entry.startsWith("CHANGELOG-") && entry.endsWith(".md")),
84
+ (entry.startsWith("CHANGELOG-") && entry.endsWith(".md")) ||
85
+ legacyHtmlExportAssetPattern.test(entry),
84
86
  )
85
87
  .map(entry => fs.rm(path.join(outDir, entry), { force: true })),
86
88
  );
@@ -75,17 +75,6 @@ export function isInterruptingSeverity(severity: AdvisorSeverity | undefined): b
75
75
  return severity === "concern" || severity === "blocker";
76
76
  }
77
77
 
78
- /**
79
- * Append a staleness caveat to an advisor note when newer primary turns arrived
80
- * after the reviewed transcript window (i.e. `hasFreshBacklog` is true on the
81
- * advisor runtime at delivery time). Pure function — no session coupling — so it
82
- * can be unit-tested in isolation and called from `AgentSession#routeAdvice`.
83
- */
84
- export function annotateForStaleness(note: string, hasFreshBacklog: boolean): string {
85
- if (!hasFreshBacklog) return note;
86
- return `${note}\n\n_(Note: newer primary turns arrived after this reviewed window — verify this still applies.)_`;
87
- }
88
-
89
78
  /** How an advisor note is routed to the primary. */
90
79
  export type AdvisorDeliveryChannel = "aside" | "steer" | "preserve";
91
80
  /** Half-open turn-count fence for the post-interrupt cooldown. */
@@ -340,18 +340,6 @@ export class AdvisorRuntime {
340
340
  return this.#halted;
341
341
  }
342
342
 
343
- /**
344
- * True when `#pending` is non-empty while the drain loop is busy — i.e., newer
345
- * primary turns arrived after the current batch's transcript window was fixed
346
- * but before the advisor model finished processing it. The delivery path uses
347
- * this to annotate advice that was generated without seeing those newer turns.
348
- * Can be true during `agent.prompt()`, a `maintainContext` await, or a retry
349
- * sleep — any time `#drain` is busy and a concurrent `onTurnEnd` pushed.
350
- */
351
- get hasFreshBacklog(): boolean {
352
- return this.#pending.length > 0;
353
- }
354
-
355
343
  /**
356
344
  * Called after each primary turn ends. Renders the incremental delta and
357
345
  * queues it for the advisor model.
@@ -417,6 +417,26 @@ export class AsyncJobManager {
417
417
  }
418
418
  }
419
419
 
420
+ /**
421
+ * Immediately evict completed and failed jobs matching the filter instead of
422
+ * waiting for retention expiry, dropping every queued delivery so a prior
423
+ * session's result can never be injected into a later transcript. Returns the
424
+ * number of jobs evicted.
425
+ *
426
+ * A delivery whose sink call is already in flight (or drained onto a caller's
427
+ * yield queue) is guarded by the owner's delivery generation, not the per-id
428
+ * suppression marker — that marker is cleared when the id is reused.
429
+ */
430
+ evictCompletedJobs(filter?: AsyncJobFilter): number {
431
+ let evicted = 0;
432
+ for (const job of this.#filterJobs(this.#jobs.values(), filter)) {
433
+ if (job.status !== "completed" && job.status !== "failed") continue;
434
+ this.acknowledgeDeliveries([job.id]);
435
+ if (this.#evictJob(job.id)) evicted += 1;
436
+ }
437
+ return evicted;
438
+ }
439
+
420
440
  async waitForAll(): Promise<void> {
421
441
  await Promise.all(Array.from(this.#jobs.values()).map(job => job.promise));
422
442
  }
@@ -579,12 +599,18 @@ export class AsyncJobManager {
579
599
  return candidate;
580
600
  }
581
601
 
602
+ #evictJob(jobId: string): boolean {
603
+ clearTimeout(this.#evictionTimers.get(jobId));
604
+ this.#evictionTimers.delete(jobId);
605
+ this.#suppressedDeliveries.delete(jobId);
606
+ this.#watchedJobs.delete(jobId);
607
+ return this.#jobs.delete(jobId);
608
+ }
609
+
582
610
  #scheduleEviction(jobId: string): void {
583
611
  if (this.#disposed) return;
584
612
  if (this.#retentionMs <= 0) {
585
- this.#jobs.delete(jobId);
586
- this.#suppressedDeliveries.delete(jobId);
587
- this.#watchedJobs.delete(jobId);
613
+ this.#evictJob(jobId);
588
614
  return;
589
615
  }
590
616
  const existing = this.#evictionTimers.get(jobId);
@@ -592,10 +618,7 @@ export class AsyncJobManager {
592
618
  clearTimeout(existing);
593
619
  }
594
620
  const timer = setTimeout(() => {
595
- this.#evictionTimers.delete(jobId);
596
- this.#jobs.delete(jobId);
597
- this.#suppressedDeliveries.delete(jobId);
598
- this.#watchedJobs.delete(jobId);
621
+ this.#evictJob(jobId);
599
622
  }, this.#retentionMs);
600
623
  timer.unref();
601
624
  this.#evictionTimers.set(jobId, timer);