@oh-my-pi/pi-coding-agent 17.3.5 → 17.3.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 (129) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/{CHANGELOG-tt9k4jpr.md → CHANGELOG-vr9cckb4.md} +80 -0
  3. package/dist/cli.js +2993 -3001
  4. package/dist/docs-index.generated.txt +1 -1
  5. package/dist/{tool-views.generated-jdfmzwmn.js → tool-views.generated-dd2km5r2.js} +19 -19
  6. package/dist/types/advisor/advise-tool.d.ts +4 -2
  7. package/dist/types/cli/auth-broker-cli.d.ts +15 -0
  8. package/dist/types/cli/stats-cli.d.ts +1 -6
  9. package/dist/types/cli/update-cli.d.ts +8 -0
  10. package/dist/types/cli-commands.d.ts +10 -2
  11. package/dist/types/commands/stats.d.ts +4 -0
  12. package/dist/types/config/settings-schema.d.ts +28 -0
  13. package/dist/types/config/settings.d.ts +9 -0
  14. package/dist/types/extensibility/extensions/runner.d.ts +23 -4
  15. package/dist/types/extensibility/extensions/types.d.ts +58 -0
  16. package/dist/types/launch/presence.d.ts +4 -1
  17. package/dist/types/mcp/oauth-credentials.d.ts +23 -0
  18. package/dist/types/mcp/oauth-flow.d.ts +11 -0
  19. package/dist/types/mnemopi/backend.d.ts +12 -0
  20. package/dist/types/modes/components/tool-execution.d.ts +12 -0
  21. package/dist/types/modes/controllers/input-controller.d.ts +2 -0
  22. package/dist/types/modes/interactive-mode.d.ts +25 -3
  23. package/dist/types/modes/types.d.ts +10 -0
  24. package/dist/types/session/agent-session.d.ts +3 -0
  25. package/dist/types/session/prewalk.d.ts +4 -0
  26. package/dist/types/session/session-entries.d.ts +0 -1
  27. package/dist/types/session/session-manager.d.ts +12 -0
  28. package/dist/types/session/session-stats.d.ts +13 -1
  29. package/dist/types/session/skill-title-input.d.ts +13 -0
  30. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +1 -0
  31. package/dist/types/subprocess/worker-client.d.ts +7 -4
  32. package/dist/types/task/label.d.ts +2 -0
  33. package/dist/types/task/render.d.ts +2 -0
  34. package/dist/types/tiny/completion-prompt.d.ts +2 -0
  35. package/dist/types/tiny/title-client.d.ts +6 -4
  36. package/dist/types/tiny/title-protocol.d.ts +1 -0
  37. package/dist/types/tiny/worker.d.ts +27 -0
  38. package/dist/types/tools/bash.d.ts +1 -1
  39. package/dist/types/tools/file-write-fallback.d.ts +124 -0
  40. package/dist/types/tools/index.d.ts +1 -0
  41. package/dist/types/tools/path-utils.d.ts +23 -0
  42. package/dist/types/tools/read-format.d.ts +6 -0
  43. package/dist/types/tools/read-summary.d.ts +7 -1
  44. package/dist/types/utils/block-context.d.ts +14 -0
  45. package/dist/types/utils/fetch-timeout.d.ts +15 -0
  46. package/dist/types/utils/git.d.ts +25 -1
  47. package/dist/types/web/search/providers/tinyfish.d.ts +4 -0
  48. package/package.json +13 -13
  49. package/src/advisor/advise-tool.ts +5 -3
  50. package/src/cli/auth-broker-cli.ts +36 -1
  51. package/src/cli/profile-bootstrap.ts +2 -6
  52. package/src/cli/stats-cli.ts +6 -72
  53. package/src/cli/update-cli.ts +63 -11
  54. package/src/cli-commands.ts +61 -7
  55. package/src/commands/completions.ts +2 -1
  56. package/src/commands/stats.ts +7 -4
  57. package/src/commit/agentic/index.ts +15 -2
  58. package/src/commit/git/diff.ts +6 -2
  59. package/src/config/model-resolver.ts +52 -6
  60. package/src/config/models-config.ts +2 -2
  61. package/src/config/settings-schema.ts +33 -0
  62. package/src/config/settings.ts +159 -30
  63. package/src/discovery/helpers.ts +45 -2
  64. package/src/discovery/omp-plugins.ts +2 -1
  65. package/src/discovery/opencode.ts +56 -3
  66. package/src/edit/hashline/filesystem.ts +9 -3
  67. package/src/edit/modes/patch.ts +31 -5
  68. package/src/eval/js/process-entry.ts +4 -4
  69. package/src/export/html/tool-views.generated.js +19 -19
  70. package/src/extensibility/extensions/loader.ts +11 -0
  71. package/src/extensibility/extensions/runner.ts +118 -5
  72. package/src/extensibility/extensions/types.ts +60 -0
  73. package/src/extensibility/extensions/wrapper.ts +10 -1
  74. package/src/extensibility/plugins/legacy-pi-compat.ts +47 -0
  75. package/src/launch/client.ts +9 -4
  76. package/src/launch/presence.ts +19 -4
  77. package/src/lsp/defaults.json +1 -1
  78. package/src/lsp/writethrough.ts +20 -10
  79. package/src/mcp/manager.ts +41 -20
  80. package/src/mcp/oauth-credentials.ts +38 -0
  81. package/src/mcp/oauth-flow.ts +21 -0
  82. package/src/mcp/tool-bridge.ts +32 -16
  83. package/src/mnemopi/backend.ts +35 -3
  84. package/src/modes/components/model-hub.ts +37 -4
  85. package/src/modes/components/settings-selector.ts +17 -11
  86. package/src/modes/components/tool-execution.ts +97 -29
  87. package/src/modes/components/tree-selector.ts +7 -2
  88. package/src/modes/controllers/event-controller.ts +12 -2
  89. package/src/modes/controllers/input-controller.ts +64 -27
  90. package/src/modes/controllers/mcp-command-controller.ts +13 -4
  91. package/src/modes/interactive-mode.ts +79 -11
  92. package/src/modes/types.ts +11 -0
  93. package/src/prompts/system/memory-extraction-system.md +5 -22
  94. package/src/prompts/system/system-prompt.md +1 -1
  95. package/src/session/agent-session.ts +52 -6
  96. package/src/session/messages.ts +6 -0
  97. package/src/session/prewalk.ts +25 -7
  98. package/src/session/session-entries.ts +0 -1
  99. package/src/session/session-maintenance.ts +10 -1
  100. package/src/session/session-manager.ts +15 -0
  101. package/src/session/session-stats.ts +24 -3
  102. package/src/session/settings-stream-fn.ts +7 -0
  103. package/src/session/skill-title-input.ts +32 -0
  104. package/src/session/turn-recovery.ts +23 -18
  105. package/src/slash-commands/builtin-session.ts +1 -1
  106. package/src/slash-commands/helpers/stats-dashboard.ts +23 -9
  107. package/src/subprocess/worker-client.ts +8 -5
  108. package/src/task/executor.ts +11 -0
  109. package/src/task/index.ts +2 -0
  110. package/src/task/label.ts +14 -1
  111. package/src/task/persisted-revive.ts +13 -0
  112. package/src/task/render.ts +1 -1
  113. package/src/task/structured-subagent.ts +5 -2
  114. package/src/tiny/completion-prompt.ts +16 -0
  115. package/src/tiny/title-client.ts +15 -6
  116. package/src/tiny/title-protocol.ts +8 -1
  117. package/src/tiny/worker.ts +21 -19
  118. package/src/tools/bash.ts +7 -1
  119. package/src/tools/file-write-fallback.ts +467 -0
  120. package/src/tools/index.ts +1 -0
  121. package/src/tools/path-utils.ts +79 -0
  122. package/src/tools/read-format.ts +16 -2
  123. package/src/tools/read-summary.ts +9 -4
  124. package/src/tools/read.ts +306 -72
  125. package/src/utils/block-context.ts +15 -1
  126. package/src/utils/fetch-timeout.ts +33 -0
  127. package/src/utils/git.ts +54 -11
  128. package/src/web/search/providers/browser-page.ts +21 -3
  129. package/src/web/search/providers/tinyfish.ts +26 -0
@@ -23,6 +23,8 @@ type TaskRenderOptions = RenderResultOptions & {
23
23
  renderContext?: TaskRenderContext;
24
24
  };
25
25
  export declare function formatTaskId(id: string): string;
26
+ /** Dim `⟨agent⟩` badge for a non-default agent type; empty for the generic worker. */
27
+ export declare function agentTypeBadge(agent: string | undefined, theme: Theme): string;
26
28
  /**
27
29
  * Render the tool call arguments.
28
30
  */
@@ -0,0 +1,2 @@
1
+ import type { TextGenerationPipeline } from "@huggingface/transformers";
2
+ export declare function buildCompletionPrompt(tokenizer: TextGenerationPipeline["tokenizer"], promptText: string, systemPrompt?: string): string;
@@ -18,6 +18,11 @@ export interface TinyTitleGenerateOptions {
18
18
  signal?: AbortSignal;
19
19
  systemPrompt?: string;
20
20
  }
21
+ export interface TinyModelCompletionOptions {
22
+ maxTokens?: number;
23
+ signal?: AbortSignal;
24
+ systemPrompt?: string;
25
+ }
21
26
  /**
22
27
  * Hidden subcommand on the main CLI that boots the tiny-model worker in the
23
28
  * spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
@@ -62,10 +67,7 @@ export declare class TinyTitleClient {
62
67
  prewarm(modelKey: string): void;
63
68
  generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null>;
64
69
  generate(modelKey: string, message: string, options?: TinyTitleGenerateOptions): Promise<string | null>;
65
- complete(modelKey: string, prompt: string, options?: {
66
- maxTokens?: number;
67
- signal?: AbortSignal;
68
- }): Promise<string | null>;
70
+ complete(modelKey: string, prompt: string, options?: TinyModelCompletionOptions): Promise<string | null>;
69
71
  downloadModel(modelKey: string, options?: TinyTitleDownloadOptions): Promise<TinyTitleDownloadResult>;
70
72
  terminate(): Promise<void>;
71
73
  }
@@ -31,6 +31,7 @@ export type TinyTitleWorkerInbound = {
31
31
  modelKey: TinyLocalModelKey;
32
32
  prompt: string;
33
33
  maxTokens?: number;
34
+ systemPrompt?: string;
34
35
  } | {
35
36
  type: "download";
36
37
  id: string;
@@ -1,2 +1,29 @@
1
+ import type { ProgressInfo, TextGenerationPipeline, StoppingCriteria as TransformersStoppingCriteria } from "@huggingface/transformers";
2
+ import { type TransformersRuntimeMetadata } from "../subprocess/worker-runtime.js";
3
+ import { type TinyModelDevice } from "./device.js";
4
+ import { type TinyModelDtype } from "./dtype.js";
1
5
  import type { TinyTitleTransport } from "./title-protocol.js";
6
+ export interface TransformersRuntime extends TransformersRuntimeMetadata {
7
+ env: {
8
+ cacheDir?: string;
9
+ allowLocalModels?: boolean;
10
+ logLevel?: unknown;
11
+ };
12
+ LogLevel: {
13
+ ERROR: unknown;
14
+ };
15
+ StoppingCriteria: new () => TransformersStoppingCriteria;
16
+ pipeline: (task: "text-generation", model: string, options: {
17
+ device: TinyModelDevice;
18
+ dtype: TinyModelDtype;
19
+ progress_callback: (info: ProgressInfo) => void;
20
+ }) => Promise<TextGenerationPipeline>;
21
+ }
22
+ /** Stops generation at the first occurrence of `text` in the *generated* tokens.
23
+ *
24
+ * The window must be anchored to the generation boundary, not to the end of the
25
+ * whole sequence: a prompt that itself contains the stop string (chat-level
26
+ * few-shot examples ending in `</title>`, for instance) would otherwise match on
27
+ * prompt tokens and stop before the model emits anything. */
28
+ export declare function createStopOnTextCriteria(transformers: TransformersRuntime, tokenizer: TextGenerationPipeline["tokenizer"], text: string): TransformersStoppingCriteria;
2
29
  export declare function startTinyTitleWorker(transport: TinyTitleTransport): void;
@@ -40,7 +40,7 @@ export declare function wrapShellLineForClientTerminal(line: string, shellConfig
40
40
  * while false positives remain actionable through user policy control.
41
41
  * New patterns should target shapes that are virtually never legitimate in automation.
42
42
  */
43
- export declare const CRITICAL_BASH_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
43
+ export declare const CRITICAL_BASH_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
44
44
  declare const bashSchemaBase: import("@oh-my-pi/omptype").FluentType<{
45
45
  command: string;
46
46
  cwd?: string | undefined;
@@ -0,0 +1,124 @@
1
+ import type { BunFile } from "bun";
2
+ import type { ExtensionContext } from "../extensibility/extensions/types.js";
3
+ /** A denied write, captured for a registered fallback to retry through a privileged channel. */
4
+ export interface FileWriteFallbackRequest {
5
+ /**
6
+ * Absolute, symlink-resolved path to write the bytes to.
7
+ *
8
+ * This is where the failed in-process write would itself have landed, which is
9
+ * not necessarily the path the tool was given: `open` follows every component,
10
+ * so a link anywhere in that path redirects the bytes. Resolving it here is
11
+ * what lets a handler's allowlist see the real destination instead of a
12
+ * lexically innocent path, so a handler MUST treat this as authoritative and
13
+ * MUST NOT re-derive the target from anything else.
14
+ */
15
+ dst: string;
16
+ /**
17
+ * Session the denied write was issued from, or `undefined` when the mutation
18
+ * did not happen inside a tool call (an external `applyPatch` caller, a test).
19
+ *
20
+ * The registry is process-wide, so a handler can be consulted for a write from
21
+ * a session other than the one whose extension registered it. Compare this with
22
+ * `ctx.sessionManager.getSessionId()` to tell the two apart — a handler that
23
+ * prompts through `ctx.ui` needs to, since that UI belongs to ITS session and
24
+ * not necessarily to the one being asked about.
25
+ */
26
+ sessionId: string | undefined;
27
+ /** The exact bytes the tool intended to write. */
28
+ content: string;
29
+ /**
30
+ * The error that proves the write hit a permission boundary. Usually the write's
31
+ * own `EPERM`/`EACCES`/`EROFS`; for a write into a directory the host may not
32
+ * create, the denial raised by creating that directory, in which case `dst`'s
33
+ * parent may not exist yet and the handler is responsible for creating it.
34
+ */
35
+ cause: unknown;
36
+ }
37
+ /** Extension-authored handler. Return `true` once `content` is durably on disk at `dst`. */
38
+ export type FileWriteFallbackHandler = (req: FileWriteFallbackRequest, ctx: ExtensionContext) => Promise<boolean>;
39
+ /** A handler already bound to its owning extension's live context. */
40
+ type BoundFileWriteFallbackHandler = (req: FileWriteFallbackRequest) => Promise<boolean>;
41
+ /** A denied unlink, captured for a registered fallback to perform through a privileged channel. */
42
+ export interface FileDeleteFallbackRequest {
43
+ /**
44
+ * Absolute, symlink-resolved path the unlink was denied for.
45
+ *
46
+ * Every component ABOVE the last is resolved, so a handler cannot be walked
47
+ * outside its allowed roots through a link in the path. The last component is
48
+ * deliberately NOT resolved, because `unlink` removes a link itself rather
49
+ * than its target — which is also why this may still name a symlink.
50
+ */
51
+ dst: string;
52
+ /** The `EPERM`/`EACCES`/`EROFS` that proves the unlink hit a permission boundary. */
53
+ cause: unknown;
54
+ /**
55
+ * Whether `dst` was confirmed to be a plain regular file before diverting.
56
+ *
57
+ * `false` means the seam could not establish that, either because the target's
58
+ * own metadata is behind the same boundary that denied the unlink — the common
59
+ * sandbox case, since `unlink` on a directory also reports `EPERM` on Darwin —
60
+ * or because `dst` is a symlink.
61
+ *
62
+ * A handler MUST remove `dst` with a plain unlink. It MUST NOT remove it
63
+ * recursively, and MUST NOT resolve the path first: when this is `false` the
64
+ * target may be a directory, and resolving a symlink would delete whatever it
65
+ * points at instead of the link.
66
+ */
67
+ confirmedFile: boolean;
68
+ /** See {@link FileWriteFallbackRequest.sessionId}. */
69
+ sessionId: string | undefined;
70
+ }
71
+ /** Extension-authored handler. Return `true` once `dst` is gone from disk. */
72
+ export type FileDeleteFallbackHandler = (req: FileDeleteFallbackRequest, ctx: ExtensionContext) => Promise<boolean>;
73
+ /** A handler already bound to its owning extension's live context. */
74
+ type BoundFileDeleteFallbackHandler = (req: FileDeleteFallbackRequest) => Promise<boolean>;
75
+ /** True for `EPERM`, `EACCES`, and `EROFS` — the sandbox-boundary write failures this seam exists for. */
76
+ export declare function isPermissionDeniedError(error: unknown): boolean;
77
+ /** Whether any fallback is registered. Lets a caller skip work that only this seam needs. */
78
+ export declare function hasFileWriteFallback(): boolean;
79
+ /**
80
+ * Append a fallback writer, consulted in registration order when a direct write is
81
+ * permission-denied. Returns a disposer that removes this exact registration; the
82
+ * runner calls it on session shutdown so no handler outlives its session.
83
+ */
84
+ export declare function addFileWriteFallback(handler: BoundFileWriteFallbackHandler): () => void;
85
+ /** Whether any delete fallback is registered. */
86
+ export declare function hasFileDeleteFallback(): boolean;
87
+ /**
88
+ * Append a fallback deleter, consulted in registration order when a direct unlink is
89
+ * permission-denied. Deliberately a separate registry from
90
+ * {@link addFileWriteFallback}: a write handler brokers `content` to `dst`, and
91
+ * handing it a request with no content would let it "broker" an empty write and
92
+ * truncate the file it was asked to remove. Opting in is explicit for that reason.
93
+ */
94
+ export declare function addFileDeleteFallback(handler: BoundFileDeleteFallbackHandler): () => void;
95
+ /**
96
+ * Name the session whose tool call is about to run, so a denied mutation inside it
97
+ * can tell a handler where the request came from.
98
+ *
99
+ * Entered once per tool call by `ExtensionToolWrapper` (`extensibility/extensions/
100
+ * wrapper.ts`), which `sdk.ts` puts around the whole tool registry whenever an
101
+ * `ExtensionRunner` exists — so the component that owns the handlers is the one
102
+ * naming its own session, and no caller has to thread an `AgentToolContext`
103
+ * through for attribution to work.
104
+ *
105
+ * That covers the deferred LSP write batch too: a batch id belongs to one
106
+ * assistant turn of one session, and its flush is awaited inside a tool call of
107
+ * that same session, so a write performed during a later call of the group is
108
+ * still attributed to the session that issued it.
109
+ *
110
+ * Deliberately NOT a general "current session" accessor: nothing else enters this
111
+ * scope, so outside a tool call it is empty by design — an external `applyPatch`
112
+ * caller reports `undefined` rather than borrowing someone else's identity.
113
+ */
114
+ export declare function withFileMutationSession<T>(sessionId: string | undefined, fn: () => T): T;
115
+ /**
116
+ * Remove a file, consulting registered delete fallbacks when the unlink is denied.
117
+ *
118
+ * Unlike the write path there is no masked-`ENOENT` case to see through: nothing is
119
+ * created on the way, so an `ENOENT` here means the file genuinely is not there and
120
+ * must propagate — `edit`'s `REM` turns it into a `NotFoundError`.
121
+ */
122
+ export declare function deleteFileWithFallback(dst: string, file?: BunFile): Promise<void>;
123
+ export declare function writeFileWithFallback(dst: string, content: string, file?: BunFile): Promise<void>;
124
+ export {};
@@ -50,6 +50,7 @@ export * from "./debug.js";
50
50
  export * from "./essential-tools.js";
51
51
  export * from "./eval.js";
52
52
  export * from "./eval-backends.js";
53
+ export * from "./file-write-fallback.js";
53
54
  export * from "./gh.js";
54
55
  export * from "./glob.js";
55
56
  export * from "./grep.js";
@@ -155,6 +155,29 @@ export declare function resolveToCwd(filePath: string, cwd: string): string;
155
155
  * The cwd itself is rejected: a download names a file, never the directory.
156
156
  */
157
157
  export declare function confineToWorkspace(filePath: string, cwd: string): string | null;
158
+ /**
159
+ * Resolve the path a syscall on `filePath` would really act on, or `null` when
160
+ * that cannot be established.
161
+ *
162
+ * A lexical path is not a destination. The kernel follows every component above
163
+ * the last, so `ws/link/file` under a `ws/link -> /elsewhere` link lands outside
164
+ * `ws` while still looking relative and `..`-free. Handing such a path to a
165
+ * privileged helper defeats the defence a helper author reaches for first — a
166
+ * prefix allowlist passes, because the link sits inside the allowed root while
167
+ * its target does not. Callers that hand a path to something more privileged
168
+ * than the syscall that just failed resolve it here first.
169
+ *
170
+ * Rejecting symlinked components outright is not an option: `/var` and `/tmp`
171
+ * are links on macOS, so every path under `os.tmpdir()` traverses one. They are
172
+ * resolved instead, and only a path whose real destination cannot be established
173
+ * is refused, because "where would this land" then has no answer to hand over.
174
+ * {@link confineToWorkspace} refuses an unresolvable link for the same reason.
175
+ *
176
+ * @param followFinal `true` for a syscall that follows a link at the final
177
+ * component (`open`, so every write), `false` for one that acts on the link
178
+ * itself (`unlink`) and therefore needs it left alone.
179
+ */
180
+ export declare function resolveSyscallTarget(filePath: string, followFinal: boolean): Promise<string | null>;
158
181
  export declare function formatPathRelativeToCwd(filePath: string, cwd: string, options?: {
159
182
  trailingSlash?: boolean;
160
183
  }): string;
@@ -10,6 +10,12 @@ export interface HashlineHeaderContext {
10
10
  }
11
11
  export declare function formatReadHashlineHeader(displayPath: string, tag: string): string;
12
12
  export declare function readHashlineHeaderContext(session: ToolSession, absolutePath: string, cwd: string): Promise<HashlineHeaderContext>;
13
+ /**
14
+ * {@link readHashlineHeaderContext} for a caller that already holds the file's
15
+ * full text, so the file is not reopened just to hash it. Line endings are
16
+ * normalized here, exactly as the reading variant does.
17
+ */
18
+ export declare function hashlineHeaderContextForText(session: ToolSession, absolutePath: string, cwd: string, fullText: string): HashlineHeaderContext;
13
19
  export declare function hashlineHeaderContext(displayPath: string, tag: string): HashlineHeaderContext;
14
20
  export declare function prependHashlineHeader(text: string, context: HashlineHeaderContext | undefined): string;
15
21
  export declare function formatTextWithMode(text: string, startNum: number, shouldAddHashLines: boolean, shouldAddLineNumbers: boolean): string;
@@ -10,7 +10,13 @@ export declare function routeReadThroughBridge(session: ToolSession, absolutePat
10
10
  line?: number;
11
11
  limit?: number;
12
12
  }): Promise<string> | undefined;
13
- export declare function trySummarize(session: ToolSession, absolutePath: string, fileSize: number, signal?: AbortSignal): Promise<SummaryResult | null>;
13
+ /**
14
+ * Structural summary of `absolutePath`, or `null` when the file is too large,
15
+ * too short, or unparseable. `diskText` lets a caller that already read the file
16
+ * hand those bytes over instead of forcing a second read; an ACP bridge still
17
+ * wins, since the editor's buffer is the source of truth.
18
+ */
19
+ export declare function trySummarize(session: ToolSession, absolutePath: string, fileSize: number, signal?: AbortSignal, diskText?: string): Promise<SummaryResult | null>;
14
20
  export declare function renderSummary(session: ToolSession, summary: SummaryResult): {
15
21
  text: string;
16
22
  displayText: string;
@@ -6,6 +6,20 @@ export interface LineSpan {
6
6
  export interface BlockContextSource {
7
7
  path?: string;
8
8
  lang?: string;
9
+ /**
10
+ * The whole source `fullLines` was split from, when the caller still holds it.
11
+ * Supplying it skips re-joining every line into a fresh whole-file string on
12
+ * the way to the parser. It MUST be the same content as `fullLines`; a
13
+ * differing trailing newline is the only tolerated variation, since it moves
14
+ * no node's line number.
15
+ *
16
+ * Every current supplier derives both from one buffer in the same breath, so
17
+ * the two cannot drift. Do NOT set it on a source object that is reused
18
+ * across two different line arrays — a before/after diff pair, say — because
19
+ * the boundary lines tree-sitter reports would then be indexed into the wrong
20
+ * array and surface off-by-N context rows.
21
+ */
22
+ text?: string;
9
23
  }
10
24
  export type LineEntry = {
11
25
  kind: "line";
@@ -2,3 +2,18 @@
2
2
  export declare function withTimeoutSignal(timeoutMs: number, signal?: AbortSignal): AbortSignal;
3
3
  /** Detect a timeout raised by an abortable fetch. */
4
4
  export declare function isTimeoutError(error: unknown): boolean;
5
+ /**
6
+ * Detect Bun's `UnsupportedProxyProtocol` fetch rejection, raised when a proxy
7
+ * env var (`HTTPS_PROXY`, `ALL_PROXY`, …) points at a scheme it cannot drive —
8
+ * most commonly a SOCKS proxy (`socks5://`, `socks5h://`). The raw error tells
9
+ * the caller to "pass `verbose: true` in the second argument to fetch()", which
10
+ * is meaningless from a CLI, so callers translate it into
11
+ * {@link unsupportedProxyMessage}.
12
+ */
13
+ export declare function isUnsupportedProxyError(error: unknown): boolean;
14
+ /**
15
+ * Build an actionable CLI message for an {@link isUnsupportedProxyError} failure,
16
+ * naming any set proxy env var whose scheme is not `http(s)://` so the user can
17
+ * see exactly which variable to change.
18
+ */
19
+ export declare function unsupportedProxyMessage(env?: Record<string, string | undefined>): string;
@@ -3,6 +3,8 @@ export interface GitCommandResult {
3
3
  exitCode: number;
4
4
  stdout: string;
5
5
  stderr: string;
6
+ /** True when stdout or stderr hit {@link GIT_COMMAND_OUTPUT_LIMIT_BYTES} and the captured text is incomplete. */
7
+ truncated: boolean;
6
8
  }
7
9
  export interface GitRepository {
8
10
  commonDir: string;
@@ -55,6 +57,7 @@ export interface DiffOptions {
55
57
  readonly numstat?: boolean;
56
58
  readonly signal?: AbortSignal;
57
59
  readonly stat?: boolean;
60
+ readonly requireComplete?: boolean;
58
61
  }
59
62
  export interface StatusOptions {
60
63
  readonly pathspecs?: readonly string[];
@@ -136,6 +139,19 @@ export declare class GitCommandError extends Error {
136
139
  readonly result: GitCommandResult;
137
140
  constructor(args: readonly string[], result: GitCommandResult);
138
141
  }
142
+ /**
143
+ * A git subprocess produced more output than {@link GIT_COMMAND_OUTPUT_LIMIT_BYTES}
144
+ * and its captured stdout was truncated. Thrown only for callers that opt into
145
+ * completeness via `diff({ requireComplete: true })`, where operating on a partial
146
+ * diff would silently corrupt downstream parsing — e.g. the split-commit builder,
147
+ * which would otherwise throw a misleading "No diff found" for files sorting after
148
+ * a large binary blob whose base85 payload pushed the diff past the cap.
149
+ */
150
+ export declare class GitOutputTruncatedError extends Error {
151
+ readonly args: readonly string[];
152
+ readonly result: GitCommandResult;
153
+ constructor(args: readonly string[], result: GitCommandResult);
154
+ }
139
155
  /** Default deadline for git and gh subprocesses spawned by the coding agent. */
140
156
  export declare const GIT_COMMAND_TIMEOUT_MS: number;
141
157
  /**
@@ -367,7 +383,15 @@ export declare const patch: {
367
383
  canApply(cwd: string, patchPath: string, options?: Omit<PatchOptions, "check">): Promise<boolean>;
368
384
  /** Check if a patch string can be applied cleanly. */
369
385
  canApplyText(cwd: string, patchText: string, options?: Omit<PatchOptions, "check">): Promise<boolean>;
370
- /** Join patch parts into a single patch string. */
386
+ /**
387
+ * Join patch parts into a single patch string.
388
+ *
389
+ * Each part is terminated with a single `\n` if it lacks one, then parts are
390
+ * concatenated verbatim — matching git's native multi-file diff layout. Parts
391
+ * are NOT separated by an extra blank line and trailing newlines are NOT
392
+ * stripped: a `GIT binary patch` block ends in a blank line that
393
+ * `git apply --binary` requires, and stripping it corrupts the patch (#8899).
394
+ */
371
395
  join(parts: string[]): string;
372
396
  };
373
397
  export declare const cherryPick: ((cwd: string, revision: string, signal?: AbortSignal) => Promise<void>) & {
@@ -15,6 +15,10 @@ export interface TinyFishSearchParams {
15
15
  page?: number;
16
16
  include_domains?: string[];
17
17
  exclude_domains?: string[];
18
+ /** ISO 3166-1 alpha-2 region, e.g. `IT`. Geolocates results. */
19
+ location?: string;
20
+ /** ISO 639-1 language, e.g. `it`. */
21
+ language?: string;
18
22
  signal?: AbortSignal;
19
23
  timeoutMs?: number;
20
24
  fetch?: FetchImpl;
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.3.5",
4
+ "version": "17.3.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",
@@ -48,18 +48,18 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@babel/parser": "^7.29.7",
51
- "@oh-my-pi/hashline": "17.3.5",
52
- "@oh-my-pi/omp-stats": "17.3.5",
53
- "@oh-my-pi/omptype": "17.3.5",
54
- "@oh-my-pi/pi-agent-core": "17.3.5",
55
- "@oh-my-pi/pi-ai": "17.3.5",
56
- "@oh-my-pi/pi-catalog": "17.3.5",
57
- "@oh-my-pi/pi-mnemopi": "17.3.5",
58
- "@oh-my-pi/pi-natives": "17.3.5",
59
- "@oh-my-pi/pi-tui": "17.3.5",
60
- "@oh-my-pi/pi-utils": "17.3.5",
61
- "@oh-my-pi/pi-wire": "17.3.5",
62
- "@oh-my-pi/snapcompact": "17.3.5",
51
+ "@oh-my-pi/hashline": "17.3.8",
52
+ "@oh-my-pi/omp-stats": "17.3.8",
53
+ "@oh-my-pi/omptype": "17.3.8",
54
+ "@oh-my-pi/pi-agent-core": "17.3.8",
55
+ "@oh-my-pi/pi-ai": "17.3.8",
56
+ "@oh-my-pi/pi-catalog": "17.3.8",
57
+ "@oh-my-pi/pi-mnemopi": "17.3.8",
58
+ "@oh-my-pi/pi-natives": "17.3.8",
59
+ "@oh-my-pi/pi-tui": "17.3.8",
60
+ "@oh-my-pi/pi-utils": "17.3.8",
61
+ "@oh-my-pi/pi-wire": "17.3.8",
62
+ "@oh-my-pi/snapcompact": "17.3.8",
63
63
  "@opentelemetry/api": "^1.9.1",
64
64
  "@opentelemetry/api-logs": "^0.220.0",
65
65
  "@opentelemetry/context-async-hooks": "^2.9.0",
@@ -110,8 +110,10 @@ export function isAdvisorInterruptImmuneTurnActive(opts: {
110
110
  * auto-resume anything, so it is delivered live. Parking it during an active
111
111
  * run instead strands it (it never reaches the running agent) and the withheld
112
112
  * notes dump as one burst at the next user prompt — the bug this guards.
113
- * - During the post-interrupt immune-turn window, further `concern`/`blocker`
114
- * notes are downgraded to asides; preservation still wins.
113
+ * - During the post-interrupt immune-turn window, further `concern` notes are
114
+ * downgraded to asides; preservation still wins. A `blocker` is exempt: it
115
+ * means the agent handed off broken or unexercised work, so it still steers a
116
+ * triggered turn even right after a prior interrupt (#5628).
115
117
  */
116
118
  export function resolveAdvisorDeliveryChannel(opts: {
117
119
  severity: AdvisorSeverity | undefined;
@@ -127,7 +129,7 @@ export function resolveAdvisorDeliveryChannel(opts: {
127
129
  if (opts.autoResumeSuppressed && (opts.aborting || !opts.streaming)) return "preserve";
128
130
  if (opts.terminalAnswerNoQueuedWork && opts.severity !== "blocker" && !opts.streaming && !opts.aborting)
129
131
  return "preserve";
130
- if (opts.interruptImmuneTurnActive) return "aside";
132
+ if (opts.interruptImmuneTurnActive && opts.severity !== "blocker") return "aside";
131
133
  return "steer";
132
134
  }
133
135
 
@@ -33,10 +33,14 @@ import {
33
33
  SqliteAuthCredentialStore,
34
34
  } from "@oh-my-pi/pi-ai";
35
35
  import { AuthBrokerClient, DEFAULT_AUTH_BROKER_BIND, startAuthBroker } from "@oh-my-pi/pi-ai/auth-broker";
36
+ import { refreshOAuthToken } from "@oh-my-pi/pi-ai/oauth";
37
+ import type { OAuthCredentials } from "@oh-my-pi/pi-ai/oauth/types";
36
38
  import { $which, APP_NAME, getAgentDbPath, getConfigRootDir, isEnoent, logger, VERSION } from "@oh-my-pi/pi-utils";
37
39
  import chalk from "@oh-my-pi/pi-utils/chalk";
38
40
  import { setTransports as setLoggerTransports } from "@oh-my-pi/pi-utils/logger";
39
41
  import { $ } from "bun";
42
+ import { refreshManagedMcpOAuthCredential } from "../mcp/oauth-credentials";
43
+ import { isManagedMCPOAuthCredentialId, mcpOAuthServerUrlFromCredentialId } from "../mcp/oauth-flow";
40
44
  import { resolveAuthBrokerConfig } from "../session/auth-broker-config";
41
45
 
42
46
  export type AuthBrokerAction = "serve" | "token" | "login" | "logout" | "status" | "import" | "migrate" | "list";
@@ -119,6 +123,34 @@ async function ensureToken(): Promise<string> {
119
123
  return token;
120
124
  }
121
125
 
126
+ /**
127
+ * OAuth refresh handler for `omp auth-broker serve`'s {@link AuthStorage}.
128
+ *
129
+ * The vault holds provider OAuth rows AND OMP-managed `mcp_oauth:*` rows.
130
+ * Provider rows refresh through the per-provider registry. MCP rows are
131
+ * self-describing — the embedded token endpoint and client credentials are the
132
+ * only refresh material — so they refresh with a generic `refresh_token` grant.
133
+ * The serve process never loads the MCP manager, so this is the only place that
134
+ * teaches the broker to refresh MCP tokens; without it
135
+ * `POST /v1/credential/:id/refresh` fails with "Unknown OAuth provider" and the
136
+ * background refresher lets MCP access tokens expire (issue #8933).
137
+ */
138
+ export function refreshBrokerOAuthCredential(
139
+ provider: string,
140
+ credential: OAuthCredential,
141
+ signal?: AbortSignal,
142
+ ): Promise<OAuthCredentials> {
143
+ if (isManagedMCPOAuthCredentialId(provider)) {
144
+ return refreshManagedMcpOAuthCredential(credential, {
145
+ serverUrl: mcpOAuthServerUrlFromCredentialId(provider),
146
+ signal,
147
+ });
148
+ }
149
+ // Non-MCP rows: same per-provider path AuthStorage would take by default
150
+ // (the serve process registers no custom OAuth providers).
151
+ return refreshOAuthToken(provider as OAuthProvider, credential);
152
+ }
153
+
122
154
  async function runServe(flags: AuthBrokerCommandArgs["flags"]): Promise<void> {
123
155
  // The broker is a long-running headless service: route structured logs to
124
156
  // stdout so a process supervisor (pm2, journald, k8s) captures them, and
@@ -129,7 +161,10 @@ async function runServe(flags: AuthBrokerCommandArgs["flags"]): Promise<void> {
129
161
  const token = await ensureToken();
130
162
  const dbPath = getAgentDbPath();
131
163
  const store = await SqliteAuthCredentialStore.open(dbPath);
132
- const storage = new AuthStorage(store);
164
+ const storage = new AuthStorage(store, {
165
+ refreshOAuthCredential: (provider, _credentialId, credential, signal) =>
166
+ refreshBrokerOAuthCredential(provider, credential, signal),
167
+ });
133
168
  await storage.reload();
134
169
  const handle = startAuthBroker({
135
170
  storage,
@@ -33,7 +33,7 @@
33
33
  * them also activates (`omp --print --profile work`).
34
34
  */
35
35
 
36
- import { isSubcommand } from "../cli-commands";
36
+ import { isSubcommand, LAUNCH_FLAG_COMMANDS } from "../cli-commands";
37
37
  import {
38
38
  EXTENSION_SHADOWABLE_STRING_FLAGS,
39
39
  isUnknownLongValueCandidate,
@@ -43,10 +43,6 @@ import {
43
43
  STRING_VALUE_FLAGS,
44
44
  } from "./flag-tables";
45
45
 
46
- function isProfileBootstrapSubcommand(arg: string): boolean {
47
- return arg === "launch" || arg === "acp";
48
- }
49
-
50
46
  function needsBoundaryAfterGlobalStrip(stripped: readonly string[]): boolean {
51
47
  const previous = stripped[stripped.length - 1];
52
48
  return (
@@ -222,7 +218,7 @@ export function extractProfileFlags(argv: readonly string[]): ProfileBootstrapRe
222
218
  // any other token has been forwarded, later subcommand names are launch text.
223
219
  // `launch` and `acp` are explicit spellings of launch-shaped commands, so
224
220
  // global launch flags that follow them must still be extracted.
225
- if (canDispatchSubcommand && isSubcommand(arg) && !isProfileBootstrapSubcommand(arg)) {
221
+ if (canDispatchSubcommand && isSubcommand(arg) && LAUNCH_FLAG_COMMANDS[arg] !== true) {
226
222
  sawSubcommand = true;
227
223
  }
228
224
  canDispatchSubcommand = false;