@oh-my-pi/pi-coding-agent 16.2.4 → 16.2.6

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 (68) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/cli.js +3211 -3231
  3. package/dist/types/cli/bench-cli.d.ts +6 -1
  4. package/dist/types/commands/bench.d.ts +8 -0
  5. package/dist/types/config/model-discovery.d.ts +6 -1
  6. package/dist/types/config/settings-schema.d.ts +1 -1
  7. package/dist/types/edit/index.d.ts +1 -0
  8. package/dist/types/edit/renderer.d.ts +4 -0
  9. package/dist/types/edit/snapshot-details.d.ts +33 -0
  10. package/dist/types/mcp/oauth-flow.d.ts +4 -6
  11. package/dist/types/modes/components/status-line/types.d.ts +10 -0
  12. package/dist/types/modes/controllers/tool-args-reveal.d.ts +9 -2
  13. package/dist/types/modes/theme/theme.d.ts +2 -1
  14. package/dist/types/session/agent-session.d.ts +11 -0
  15. package/dist/types/session/session-manager.d.ts +3 -4
  16. package/dist/types/task/provider-concurrency.d.ts +40 -0
  17. package/dist/types/utils/git.d.ts +11 -0
  18. package/dist/types/web/search/providers/duckduckgo.d.ts +2 -2
  19. package/dist/types/web/search/types.d.ts +1 -1
  20. package/package.json +12 -12
  21. package/src/cli/args.ts +32 -1
  22. package/src/cli/bench-cli.ts +89 -21
  23. package/src/cli/web-search-cli.ts +6 -1
  24. package/src/cli/worktree-cli.ts +8 -5
  25. package/src/commands/bench.ts +10 -1
  26. package/src/config/mcp-schema.json +1 -1
  27. package/src/config/model-discovery.ts +98 -20
  28. package/src/config/model-registry.ts +13 -6
  29. package/src/config/settings-schema.ts +5 -2
  30. package/src/edit/hashline/execute.ts +15 -9
  31. package/src/edit/index.ts +19 -6
  32. package/src/edit/modes/patch.ts +3 -2
  33. package/src/edit/modes/replace.ts +3 -2
  34. package/src/edit/renderer.ts +4 -0
  35. package/src/edit/snapshot-details.ts +77 -0
  36. package/src/extensibility/plugins/legacy-pi-compat.ts +2 -2
  37. package/src/internal-urls/docs-index.generated.txt +1 -1
  38. package/src/mcp/oauth-flow.ts +10 -8
  39. package/src/mcp/transports/stdio.ts +9 -17
  40. package/src/modes/components/status-line/component.ts +29 -2
  41. package/src/modes/components/status-line/segments.ts +22 -8
  42. package/src/modes/components/status-line/types.ts +7 -0
  43. package/src/modes/controllers/event-controller.ts +17 -8
  44. package/src/modes/controllers/input-controller.ts +1 -1
  45. package/src/modes/controllers/tool-args-reveal.ts +100 -22
  46. package/src/modes/theme/theme.ts +6 -0
  47. package/src/prompts/bench.md +4 -10
  48. package/src/prompts/tools/irc.md +19 -29
  49. package/src/prompts/tools/job.md +8 -14
  50. package/src/prompts/tools/lsp.md +19 -30
  51. package/src/prompts/tools/task.md +42 -62
  52. package/src/sdk.ts +14 -5
  53. package/src/session/agent-session.ts +107 -13
  54. package/src/session/session-listing.ts +9 -8
  55. package/src/session/session-loader.ts +98 -3
  56. package/src/session/session-manager.ts +34 -4
  57. package/src/subprocess/worker-client.ts +12 -4
  58. package/src/task/executor.ts +4 -62
  59. package/src/task/provider-concurrency.ts +100 -0
  60. package/src/task/worktree.ts +13 -4
  61. package/src/task/yield-assembly.ts +27 -39
  62. package/src/tools/path-utils.ts +4 -2
  63. package/src/tools/read.ts +11 -1
  64. package/src/utils/git.ts +13 -0
  65. package/src/web/search/index.ts +14 -8
  66. package/src/web/search/providers/duckduckgo.ts +136 -78
  67. package/src/web/search/providers/gemini.ts +268 -185
  68. package/src/web/search/types.ts +1 -1
@@ -1,5 +1,5 @@
1
1
  import type { ResolvedThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
- import type { Api, ApiKeyResolver, AssistantMessageEventStream, Context, Model, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
2
+ import type { Api, ApiKeyResolver, AssistantMessageEventStream, Context, Model, ServiceTier, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
3
3
  import { type CanonicalModelVariant } from "@oh-my-pi/pi-catalog/identity";
4
4
  import type { ApiKeyResolverModel } from "../config/api-key-resolver";
5
5
  import { type CanonicalModelQueryOptions } from "../config/model-registry";
@@ -10,7 +10,10 @@ export interface BenchCommandArgs {
10
10
  runs?: number;
11
11
  maxTokens?: number;
12
12
  prompt?: string;
13
+ /** Service-tier setting value (`none` omits); overrides the configured `serviceTier` setting. */
14
+ serviceTier?: string;
13
15
  json?: boolean;
16
+ par?: number;
14
17
  };
15
18
  }
16
19
  export interface BenchModelRegistry {
@@ -62,6 +65,8 @@ export interface BenchSummary {
62
65
  maxTokens: number;
63
66
  models: BenchModelReport[];
64
67
  failures: number;
68
+ /** Requested service tier passed to every request; absent when none was requested. Scoped tiers (`openai-only`/`claude-only`) may be dropped per-provider downstream. */
69
+ serviceTier?: ServiceTier;
65
70
  }
66
71
  type BenchStreamSimple = (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
67
72
  export interface BenchDependencies {
@@ -20,9 +20,17 @@ export default class Bench extends Command {
20
20
  prompt: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
21
21
  description: string;
22
22
  };
23
+ "service-tier": import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"string"> & {
24
+ description: string;
25
+ options: readonly ["none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"];
26
+ };
23
27
  json: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"boolean"> & {
24
28
  description: string;
25
29
  };
30
+ par: import("@oh-my-pi/pi-utils/cli").FlagDescriptor<"integer"> & {
31
+ description: string;
32
+ default: number;
33
+ };
26
34
  };
27
35
  static examples: string[];
28
36
  run(): Promise<void>;
@@ -37,10 +37,14 @@ export interface DiscoveryContext {
37
37
  */
38
38
  getBearerApiKeyResolver(provider: string): Promise<ApiKey | undefined>;
39
39
  }
40
+ type LlamaCppDiscoveredModelRuntimeMetadata = {
41
+ contextWindow: number;
42
+ maxTokens: number;
43
+ };
40
44
  export declare function discoverModelsByProviderType(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
41
45
  export declare function discoverOllamaModels(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
42
46
  export declare function discoverLlamaCppModels(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
43
- export declare function discoverLlamaCppModelContextWindow(model: Pick<Model<Api>, "provider" | "id" | "baseUrl" | "headers">, ctx: DiscoveryContext): Promise<number | undefined>;
47
+ export declare function discoverLlamaCppModelRuntimeMetadata(model: Pick<Model<Api>, "provider" | "id" | "baseUrl" | "headers">, ctx: DiscoveryContext): Promise<LlamaCppDiscoveredModelRuntimeMetadata | undefined>;
44
48
  export declare function discoverOpenAIModelsList(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
45
49
  export declare function discoverLiteLLMModels(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
46
50
  /**
@@ -61,3 +65,4 @@ export declare function discoverLiteLLMModels(providerConfig: DiscoveryProviderC
61
65
  export declare function discoverProxyModels(providerConfig: DiscoveryProviderConfig, ctx: DiscoveryContext): Promise<Model<Api>[]>;
62
66
  export declare function normalizeLiteLLMDiscoveryBaseUrl(baseUrl?: string): string;
63
67
  export declare function normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string;
68
+ export {};
@@ -4494,7 +4494,7 @@ export declare const SETTINGS_SCHEMA: {
4494
4494
  }, {
4495
4495
  readonly value: "duckduckgo";
4496
4496
  readonly label: "DuckDuckGo";
4497
- readonly description: "Uses DuckDuckGo Instant Answer API (no API key)";
4497
+ readonly description: "Scrapes the DuckDuckGo HTML frontend (no API key)";
4498
4498
  }];
4499
4499
  };
4500
4500
  };
@@ -18,6 +18,7 @@ export * from "./modes/patch";
18
18
  export * from "./modes/replace";
19
19
  export * from "./normalize";
20
20
  export * from "./renderer";
21
+ export * from "./snapshot-details";
21
22
  export * from "./streaming";
22
23
  type TInput = typeof replaceEditSchema | typeof patchEditSchema | typeof hashlineEditParamsSchema | typeof applyPatchSchema;
23
24
  type HashlineParams = typeof hashlineEditParamsSchema.infer;
@@ -29,6 +29,8 @@ export interface EditToolPerFileResult {
29
29
  oldText?: string;
30
30
  /** Source-of-truth content after the edit; `undefined` for delete operations. */
31
31
  newText?: string;
32
+ /** True when {@link pruneOversizedEditSnapshots} dropped `oldText`/`newText` from this entry. Aggregators check this to suppress misleading combined snapshots when at least one entry of a multi-entry single-path edit was pruned. */
33
+ snapshotsPruned?: boolean;
32
34
  /** Pre-move source path; set only when the edit moved/renamed the file. The header renders `sourcePath → path`. */
33
35
  sourcePath?: string;
34
36
  }
@@ -53,6 +55,8 @@ export interface EditToolDetails {
53
55
  oldText?: string;
54
56
  /** Source-of-truth content after the edit; `undefined` for delete operations. */
55
57
  newText?: string;
58
+ /** True when {@link pruneOversizedEditSnapshots} dropped `oldText`/`newText` from this entry. Aggregators check this to suppress misleading combined snapshots when at least one entry of a multi-entry single-path edit was pruned. */
59
+ snapshotsPruned?: boolean;
56
60
  /** Pre-move source path; set only when the edit moved/renamed the file. The header renders `sourcePath → path`. */
57
61
  sourcePath?: string;
58
62
  }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Bound the size of the `oldText` / `newText` snapshots that edit-tool results
3
+ * carry in `details`. These fields hold the full pre/post file content; for
4
+ * large files they balloon the per-turn JSONL line and the session file
5
+ * (300 KB+ each on the cases reported in #3786) without paying for any LLM
6
+ * context (provider serializers send only `content`, never `details`).
7
+ *
8
+ * Only consumer of the raw snapshots is the ACP event mapper, which builds a
9
+ * `diff` ToolCallContent for ACP clients (Zed). When the snapshots are pruned
10
+ * the mapper returns `undefined` for that file and the text content still
11
+ * flows — diff visualization degrades gracefully for over-threshold edits.
12
+ */
13
+ import type { EditToolDetails, EditToolPerFileResult } from "./renderer";
14
+ /**
15
+ * Combined `oldText` + `newText` character budget for a single edit-tool
16
+ * result. Applies both per-entry (one file at a time) and as an aggregate
17
+ * across `perFileResults` (so a many-small-files batch can't accumulate
18
+ * unbounded snapshot bytes — see #3787 review).
19
+ *
20
+ * Picked so typical code-file edits keep ACP diff visualization while
21
+ * pathological cases (large generated files, full-file rewrites, or
22
+ * many-file batches) drop the raw snapshots before they hit the
23
+ * session JSONL.
24
+ */
25
+ export declare const MAX_EDIT_SNAPSHOT_TEXT_CHARS = 32768;
26
+ /**
27
+ * Prune oversized `oldText` / `newText` from an edit-tool details payload,
28
+ * recursing into `perFileResults` when present. Per-file overload comes first
29
+ * so the more specific shape (required `path`) wins overload resolution at
30
+ * the per-file call sites.
31
+ */
32
+ export declare function pruneOversizedEditSnapshots(details: EditToolPerFileResult): EditToolPerFileResult;
33
+ export declare function pruneOversizedEditSnapshots(details: EditToolDetails): EditToolDetails;
@@ -59,12 +59,10 @@ export interface MCPOAuthConfig {
59
59
  /** OAuth scopes (space-separated) */
60
60
  scopes?: string;
61
61
  /**
62
- * `prompt` parameter for the authorization request. Defaults to `"consent"`
63
- * so the provider always shows its authorize screen instead of silently
64
- * re-approving the browser's current session without it, reauthorizing to
65
- * switch accounts/workspaces is impossible once a session cookie exists
66
- * (RFC 6749 §3.1 requires servers to ignore the param when unsupported).
67
- * Set to `""` to omit the parameter entirely.
62
+ * `prompt` parameter for the authorization request. By default the parameter
63
+ * is omitted, matching the reference MCP SDK, except for `offline_access`
64
+ * requests where OIDC Core requires `prompt=consent` to issue refresh-token
65
+ * access. Set to `""` to omit the parameter entirely.
68
66
  */
69
67
  prompt?: string;
70
68
  /** Exact redirect URI to advertise to the provider */
@@ -103,6 +103,16 @@ export interface SegmentContext {
103
103
  url: string;
104
104
  } | null;
105
105
  };
106
+ /**
107
+ * Set when the path cwd is a *linked* git worktree, naming the shared
108
+ * primary checkout (the project). Lets the path segment collapse the
109
+ * base-prefixed `<base>/<project>/<worktree>` path to the project name —
110
+ * the worktree/branch is already shown by the git segment.
111
+ */
112
+ worktree: {
113
+ projectName: string;
114
+ worktreeName: string;
115
+ } | null;
106
116
  usage: {
107
117
  tier?: string;
108
118
  fiveHour?: {
@@ -6,13 +6,20 @@ type ToolArgsRevealControllerOptions = {
6
6
  getSmoothStreaming(): boolean;
7
7
  requestRender(): void;
8
8
  };
9
+ type ToolArgsRevealTarget = {
10
+ rawInput: boolean;
11
+ exposeRawPartialJson: boolean;
12
+ fullArgs: Record<string, unknown>;
13
+ };
9
14
  /**
10
15
  * Paces streamed tool-call arguments the same way StreamingRevealController
11
16
  * paces assistant text: providers that deliver `partialJson` in large batches
12
17
  * (or throttle their partial parses) would otherwise make write/edit/bash
13
18
  * streaming previews jump in chunks. Each pending tool call reveals its raw
14
19
  * argument stream at the shared 30fps cadence with the same adaptive
15
- * catch-up step, re-parsing the revealed prefix per frame.
20
+ * catch-up step. JSON prefixes are parsed only when enough new bytes arrive to
21
+ * change renderer-visible fields, while raw-prefix consumers still receive
22
+ * fresh `__partialJson` on every reveal frame.
16
23
  *
17
24
  * Reveal units are UTF-16 code units of the raw stream, not graphemes —
18
25
  * the prefix goes through a JSON parser rather than straight to the screen,
@@ -26,7 +33,7 @@ export declare class ToolArgsRevealController {
26
33
  * args to render right now. With smoothing disabled the full target passes
27
34
  * through in the caller's legacy shape (`{ ...args, __partialJson }`).
28
35
  */
29
- setTarget(id: string, partialJson: string, rawInput: boolean, fullArgs: Record<string, unknown>): Record<string, unknown>;
36
+ setTarget(id: string, partialJson: string, target: ToolArgsRevealTarget): Record<string, unknown>;
30
37
  /** Attach the component future ticks push frames into. */
31
38
  bind(id: string, component: ToolArgsRevealComponent): void;
32
39
  /** Final arguments arrived (the JSON closed): drop the reveal so the
@@ -6,7 +6,7 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
6
6
  /**
7
7
  * All available symbol keys organized by category.
8
8
  */
9
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move";
9
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.time" | "icon.pi" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.inspectImage" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move";
10
10
  export type SpinnerType = "status" | "activity";
11
11
  export type ThemeColor = "accent" | "border" | "borderAccent" | "borderMuted" | "success" | "error" | "warning" | "muted" | "dim" | "text" | "thinkingText" | "userMessageText" | "customMessageText" | "customMessageLabel" | "toolTitle" | "toolOutput" | "mdHeading" | "mdLink" | "mdLinkUrl" | "mdCode" | "mdCodeBlock" | "mdCodeBlockBorder" | "mdQuote" | "mdQuoteBorder" | "mdHr" | "mdListBullet" | "toolDiffAdded" | "toolDiffRemoved" | "toolDiffContext" | "syntaxComment" | "syntaxKeyword" | "syntaxFunction" | "syntaxVariable" | "syntaxString" | "syntaxNumber" | "syntaxType" | "syntaxOperator" | "syntaxPunctuation" | "thinkingOff" | "thinkingMinimal" | "thinkingLow" | "thinkingMedium" | "thinkingHigh" | "thinkingXhigh" | "bashMode" | "pythonMode" | "statusLineSep" | "statusLineModel" | "statusLinePath" | "statusLineGitClean" | "statusLineGitDirty" | "statusLineContext" | "statusLineSpend" | "statusLineStaged" | "statusLineDirty" | "statusLineUntracked" | "statusLineOutput" | "statusLineCost" | "statusLineSubagents";
12
12
  /** Check if a string is a valid ThemeColor value */
@@ -164,6 +164,7 @@ export declare class Theme {
164
164
  pause: string;
165
165
  loop: string;
166
166
  folder: string;
167
+ worktree: string;
167
168
  scratchFolder: string;
168
169
  file: string;
169
170
  git: string;
@@ -169,6 +169,13 @@ export interface AgentSessionConfig {
169
169
  * inherits this so its requests undergo the same shaping as the main turn.
170
170
  */
171
171
  transformProviderContext?: (context: Context, model: Model) => Context | Promise<Context>;
172
+ /**
173
+ * Stream wrapper passed to side-channel requests (`/btw`, `/omfg`, IRC
174
+ * auto-replies, and handoff generation) so they apply the same provider
175
+ * shaping and host-level request wrappers as normal agent turns. Defaults
176
+ * to plain `streamSimple` when omitted.
177
+ */
178
+ sideStreamFn?: StreamFn;
172
179
  /**
173
180
  * Stream wrapper passed to the advisor agent so its requests apply the
174
181
  * session's `providers.openrouterVariant`, `providers.antigravityEndpoint`,
@@ -177,6 +184,8 @@ export interface AgentSessionConfig {
177
184
  * main agent. Defaults to plain `streamSimple` when omitted.
178
185
  */
179
186
  advisorStreamFn?: StreamFn;
187
+ /** Hint that OpenAI Codex requests should prefer websocket transport when supported. */
188
+ preferWebsockets?: boolean;
180
189
  /** Provider payload hook used by the active session request path */
181
190
  onPayload?: SimpleStreamOptions["onPayload"];
182
191
  /** Provider response hook used by the active session request path */
@@ -463,6 +472,8 @@ export declare class AgentSession {
463
472
  setSessionSwitchReconciler(reconciler: (() => Promise<void>) | null): void;
464
473
  /** Provider-scoped mutable state store for transport/session caches. */
465
474
  get providerSessionState(): Map<string, ProviderSessionState>;
475
+ /** Hint forwarded to provider calls that support websocket transport. */
476
+ get preferWebsockets(): boolean | undefined;
466
477
  getHindsightSessionState(): HindsightSessionState | undefined;
467
478
  setHindsightSessionState(state: HindsightSessionState | undefined): HindsightSessionState | undefined;
468
479
  getMnemopiSessionState(): MnemopiSessionState | undefined;
@@ -75,10 +75,9 @@ export declare class SessionManager {
75
75
  /** Flush pending writes. Call before switching sessions or on shutdown. */
76
76
  flush(): Promise<void>;
77
77
  /**
78
- * Synchronously flush all in-memory entries to disk. Use when the process may
79
- * exit before an async flush settles (Ctrl+C in the TUI). Software-crash
80
- * durable; not atomic and not power-loss safe a same-process crash never
81
- * lands mid-`writeFileSync`.
78
+ * Synchronously makes the current append-only session durable. Avoid rewriting
79
+ * an already-current file: large restored sessions can contain GiB of compacted
80
+ * history, and Ctrl+C must not rebuild the whole JSONL string just to flush.
82
81
  */
83
82
  flushSync(): void;
84
83
  /** Flush, then close the append writer. */
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Per-provider LLM concurrency cap, applied around each provider HTTP request.
3
+ *
4
+ * The semaphore brackets only the streaming request itself, not the whole
5
+ * agent lifetime: a parent subagent releases its slot the moment its LLM
6
+ * stream finishes producing, so children spawned during tool execution can
7
+ * acquire slots for their own turns. Holding the slot across the parent's
8
+ * full conversation deadlocks any spawn tree whose width exceeds
9
+ * `maxConcurrency` because the parents wait for children that wait for
10
+ * slots the parents are holding (issue
11
+ * [#3749](https://github.com/can1357/oh-my-pi/issues/3749)).
12
+ */
13
+ import type { StreamFn } from "@oh-my-pi/pi-agent-core";
14
+ import type { Settings } from "../config/settings";
15
+ import { Semaphore } from "./parallel";
16
+ /**
17
+ * Resolve the configured concurrency ceiling for a provider, or `undefined`
18
+ * when the provider has no cap concept at all. A configured value `<= 0` means
19
+ * "unlimited" and maps to `Infinity` — still a tracked ceiling, so every run
20
+ * holds a slot and a later finite resize counts work started while unlimited.
21
+ */
22
+ export declare function getProviderConcurrencyLimit(settings: Settings, provider: string): number | undefined;
23
+ /**
24
+ * Hand out the single shared limiter for `provider` (creating one lazily) and
25
+ * resize it in place when the configured limit changes. Replacing the
26
+ * semaphore would orphan in-flight slots on the old instance and let a
27
+ * runtime or mixed limit value exceed the cap (issue #3464 review feedback).
28
+ */
29
+ export declare function getProviderSemaphore(settings: Settings, provider: string): Semaphore | undefined;
30
+ /**
31
+ * Wrap a {@link StreamFn} so every LLM HTTP request acquires the provider's
32
+ * concurrency slot before the request goes out and releases it when the
33
+ * stream finishes producing (success, error, or abort). Providers without a
34
+ * configured cap pass straight through.
35
+ *
36
+ * The acquire bracket is intentionally narrow (one slot per LLM call), so
37
+ * spawn trees deeper than `maxConcurrency` no longer deadlock on themselves —
38
+ * see the module-level comment for the failure mode this fixes.
39
+ */
40
+ export declare function wrapStreamFnWithProviderConcurrency(settings: Settings, base: StreamFn): StreamFn;
@@ -349,6 +349,17 @@ export declare const repo: {
349
349
  * the shared common dir (`foo.git`) because they have no primary checkout.
350
350
  */
351
351
  primaryRootSync(cwd: string): string | null;
352
+ /**
353
+ * Linked-worktree metadata for `cwd`, or `null` when `cwd` is the primary
354
+ * checkout (or outside a repository). `root` is the worktree's own checkout
355
+ * root; `primaryRoot` is the shared main checkout that names the project.
356
+ * Resolves purely via on-disk `.git`/`commondir` walking — no subprocess —
357
+ * so the status line may call it on every render.
358
+ */
359
+ linkedWorktreeSync(cwd: string): {
360
+ root: string;
361
+ primaryRoot: string;
362
+ } | null;
352
363
  /** Full GitRepository metadata (sync). */
353
364
  resolveSync(cwd: string): GitRepository | null;
354
365
  /** Full GitRepository metadata. */
@@ -2,9 +2,9 @@ import type { AuthStorage } from "@oh-my-pi/pi-ai";
2
2
  import type { SearchResponse } from "../../../web/search/types";
3
3
  import type { SearchParams } from "./base";
4
4
  import { SearchProvider } from "./base";
5
- /** Execute DuckDuckGo Instant Answer API search. */
5
+ /** Execute a DuckDuckGo web search via the no-JS HTML frontend. */
6
6
  export declare function searchDuckDuckGo(params: SearchParams): Promise<SearchResponse>;
7
- /** Search provider for DuckDuckGo Instant Answer API. */
7
+ /** Search provider for DuckDuckGo (no API key required). */
8
8
  export declare class DuckDuckGoProvider extends SearchProvider {
9
9
  readonly id = "duckduckgo";
10
10
  readonly label = "DuckDuckGo";
@@ -78,7 +78,7 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
78
78
  }, {
79
79
  readonly value: "duckduckgo";
80
80
  readonly label: "DuckDuckGo";
81
- readonly description: "Uses DuckDuckGo Instant Answer API (no API key)";
81
+ readonly description: "Scrapes the DuckDuckGo HTML frontend (no API key)";
82
82
  }];
83
83
  /** Supported web search providers (every option except `auto`). */
84
84
  export type SearchProviderId = Exclude<(typeof SEARCH_PROVIDER_OPTIONS)[number]["value"], "auto">;
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": "16.2.4",
4
+ "version": "16.2.6",
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",
@@ -55,17 +55,17 @@
55
55
  "@agentclientprotocol/sdk": "0.25.0",
56
56
  "@babel/parser": "^7.29.7",
57
57
  "@mozilla/readability": "^0.6.0",
58
- "@oh-my-pi/hashline": "16.2.4",
59
- "@oh-my-pi/omp-stats": "16.2.4",
60
- "@oh-my-pi/pi-agent-core": "16.2.4",
61
- "@oh-my-pi/pi-ai": "16.2.4",
62
- "@oh-my-pi/pi-catalog": "16.2.4",
63
- "@oh-my-pi/pi-mnemopi": "16.2.4",
64
- "@oh-my-pi/pi-natives": "16.2.4",
65
- "@oh-my-pi/pi-tui": "16.2.4",
66
- "@oh-my-pi/pi-utils": "16.2.4",
67
- "@oh-my-pi/pi-wire": "16.2.4",
68
- "@oh-my-pi/snapcompact": "16.2.4",
58
+ "@oh-my-pi/hashline": "16.2.6",
59
+ "@oh-my-pi/omp-stats": "16.2.6",
60
+ "@oh-my-pi/pi-agent-core": "16.2.6",
61
+ "@oh-my-pi/pi-ai": "16.2.6",
62
+ "@oh-my-pi/pi-catalog": "16.2.6",
63
+ "@oh-my-pi/pi-mnemopi": "16.2.6",
64
+ "@oh-my-pi/pi-natives": "16.2.6",
65
+ "@oh-my-pi/pi-tui": "16.2.6",
66
+ "@oh-my-pi/pi-utils": "16.2.6",
67
+ "@oh-my-pi/pi-wire": "16.2.6",
68
+ "@oh-my-pi/snapcompact": "16.2.6",
69
69
  "@opentelemetry/api": "^1.9.1",
70
70
  "@opentelemetry/context-async-hooks": "^2.7.1",
71
71
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
package/src/cli/args.ts CHANGED
@@ -94,6 +94,35 @@ const PARSE_DEPS: ParseDeps = {
94
94
  thinkingEfforts: CLI_THINKING_LEVELS,
95
95
  };
96
96
 
97
+ const WINDOWS_PATH_VALUE_FLAGS: ReadonlySet<string> = new Set(["--extension", "-e", "--hook"]);
98
+ const WINDOWS_PATH_START_RE =
99
+ /^(?:[A-Za-z]:[\\/]|\\\\[?]\\(?:[A-Za-z]:[\\/]|UNC[\\/])|\\\\[^\\/]+[\\/][^\\/]+[\\/]|\/\/[?]\/(?:[A-Za-z]:\/|UNC\/)|\/\/[^/]+\/[^/]+\/)/;
100
+ const WINDOWS_MODULE_PATH_SUFFIX_RE = /\.(?:[cm]?[jt]sx?)$/i;
101
+
102
+ function consumeBuiltInStringValue(flag: string, args: string[], valueIndex: number): { value: string; index: number } {
103
+ const value = args[valueIndex];
104
+ if (
105
+ value === undefined ||
106
+ !WINDOWS_PATH_VALUE_FLAGS.has(flag) ||
107
+ !WINDOWS_PATH_START_RE.test(value) ||
108
+ WINDOWS_MODULE_PATH_SUFFIX_RE.test(value)
109
+ ) {
110
+ return { value: value ?? "", index: valueIndex };
111
+ }
112
+
113
+ let candidate = value;
114
+ for (let index = valueIndex + 1; index < args.length; index++) {
115
+ const next = args[index];
116
+ if (next === PROFILE_BOOTSTRAP_BOUNDARY_ARG || next.startsWith("-")) break;
117
+ candidate += ` ${next}`;
118
+ if (WINDOWS_MODULE_PATH_SUFFIX_RE.test(candidate)) {
119
+ return { value: candidate, index };
120
+ }
121
+ }
122
+
123
+ return { value, index: valueIndex };
124
+ }
125
+
97
126
  export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { type: "boolean" | "string" }>): Args {
98
127
  // Work on a copy: the `--option=value` handling below splices the value
99
128
  // into the array, and callers reuse the same argv (the post-extension
@@ -161,7 +190,9 @@ export function parseArgs(inputArgs: string[], extensionFlags?: Map<string, { ty
161
190
  // here only when its boolean extension is NOT loaded) would otherwise swallow
162
191
  // the marker as its value and drop the user's trailing message.
163
192
  if (i + 1 < args.length && args[i + 1] !== PROFILE_BOOTSTRAP_BOUNDARY_ARG) {
164
- STRING_SETTERS[arg](result, args[++i], PARSE_DEPS);
193
+ const consumed = consumeBuiltInStringValue(arg, args, i + 1);
194
+ i = consumed.index;
195
+ STRING_SETTERS[arg](result, consumed.value, PARSE_DEPS);
165
196
  }
166
197
  } else if (OPTIONAL_VALUE_FLAGS.has(arg)) {
167
198
  const config = OPTIONAL_FLAGS[arg];