@oh-my-pi/pi-coding-agent 16.2.1 → 16.2.3

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 (187) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/cli.js +3621 -3579
  3. package/dist/types/advisor/__tests__/config.test.d.ts +1 -0
  4. package/dist/types/advisor/advise-tool.d.ts +8 -4
  5. package/dist/types/advisor/config.d.ts +88 -0
  6. package/dist/types/advisor/index.d.ts +1 -0
  7. package/dist/types/advisor/runtime.d.ts +15 -1
  8. package/dist/types/advisor/transcript-recorder.d.ts +13 -2
  9. package/dist/types/advisor/watchdog.d.ts +20 -0
  10. package/dist/types/collab/guest.d.ts +29 -0
  11. package/dist/types/config/model-roles.d.ts +1 -1
  12. package/dist/types/config/settings-schema.d.ts +113 -12
  13. package/dist/types/debug/log-viewer.d.ts +1 -0
  14. package/dist/types/debug/raw-sse.d.ts +1 -0
  15. package/dist/types/discovery/omp-extension-roots.d.ts +3 -3
  16. package/dist/types/edit/hashline/diff.d.ts +0 -11
  17. package/dist/types/edit/index.d.ts +18 -0
  18. package/dist/types/edit/streaming.d.ts +30 -0
  19. package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
  20. package/dist/types/extensibility/shared-events.d.ts +1 -0
  21. package/dist/types/extensibility/tool-event-input.d.ts +7 -0
  22. package/dist/types/extensibility/utils.d.ts +12 -0
  23. package/dist/types/mcp/oauth-discovery.d.ts +0 -11
  24. package/dist/types/mcp/transports/index.d.ts +1 -0
  25. package/dist/types/mcp/transports/sse.d.ts +20 -0
  26. package/dist/types/modes/components/advisor-config.d.ts +59 -0
  27. package/dist/types/modes/components/index.d.ts +1 -0
  28. package/dist/types/modes/components/model-selector.d.ts +9 -1
  29. package/dist/types/modes/components/settings-selector.d.ts +1 -0
  30. package/dist/types/modes/components/status-line/component.d.ts +30 -3
  31. package/dist/types/modes/components/status-line/types.d.ts +13 -1
  32. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  33. package/dist/types/modes/interactive-mode.d.ts +10 -4
  34. package/dist/types/modes/skill-command.d.ts +32 -0
  35. package/dist/types/modes/types.d.ts +7 -2
  36. package/dist/types/sdk.d.ts +1 -1
  37. package/dist/types/session/agent-session.d.ts +84 -12
  38. package/dist/types/session/indexed-session-storage.d.ts +7 -1
  39. package/dist/types/session/messages.d.ts +32 -7
  40. package/dist/types/session/messages.test.d.ts +1 -0
  41. package/dist/types/session/session-entries.d.ts +31 -3
  42. package/dist/types/session/session-history-format.d.ts +6 -0
  43. package/dist/types/session/session-loader.d.ts +9 -1
  44. package/dist/types/session/session-manager.d.ts +6 -4
  45. package/dist/types/session/session-storage.d.ts +11 -0
  46. package/dist/types/session/session-title-slot.d.ts +19 -0
  47. package/dist/types/session/settings-stream-fn.d.ts +21 -0
  48. package/dist/types/session/turn-persistence.d.ts +88 -0
  49. package/dist/types/ssh/connection-manager.d.ts +47 -0
  50. package/dist/types/ssh/utils.d.ts +16 -0
  51. package/dist/types/task/executor.d.ts +3 -16
  52. package/dist/types/task/render.d.ts +0 -5
  53. package/dist/types/task/renderer.d.ts +13 -0
  54. package/dist/types/task/types.d.ts +16 -0
  55. package/dist/types/task/yield-assembly.d.ts +28 -0
  56. package/dist/types/tiny/text.d.ts +8 -0
  57. package/dist/types/tools/render-utils.d.ts +2 -0
  58. package/dist/types/tools/review.d.ts +6 -4
  59. package/dist/types/tools/ssh.d.ts +1 -1
  60. package/dist/types/tools/todo.d.ts +6 -0
  61. package/dist/types/tools/yield.d.ts +8 -3
  62. package/dist/types/utils/thinking-display.d.ts +4 -0
  63. package/package.json +12 -12
  64. package/src/advisor/__tests__/advisor.test.ts +438 -10
  65. package/src/advisor/__tests__/config.test.ts +173 -0
  66. package/src/advisor/advise-tool.ts +11 -6
  67. package/src/advisor/config.ts +256 -0
  68. package/src/advisor/index.ts +1 -0
  69. package/src/advisor/runtime.ts +77 -4
  70. package/src/advisor/transcript-recorder.ts +25 -2
  71. package/src/advisor/watchdog.ts +57 -31
  72. package/src/auto-thinking/classifier.ts +2 -2
  73. package/src/autoresearch/index.ts +7 -2
  74. package/src/cli/gc-cli.ts +17 -10
  75. package/src/collab/guest.ts +43 -7
  76. package/src/config/model-registry.ts +80 -18
  77. package/src/config/model-resolver.ts +5 -1
  78. package/src/config/model-roles.ts +3 -3
  79. package/src/config/settings-schema.ts +107 -8
  80. package/src/debug/index.ts +32 -7
  81. package/src/debug/log-viewer.ts +111 -53
  82. package/src/debug/raw-sse.ts +68 -48
  83. package/src/discovery/codex.ts +13 -5
  84. package/src/discovery/omp-extension-roots.ts +38 -13
  85. package/src/edit/hashline/diff.ts +57 -4
  86. package/src/edit/index.ts +21 -0
  87. package/src/edit/streaming.ts +170 -0
  88. package/src/eval/js/shared/local-module-loader.ts +23 -1
  89. package/src/export/html/template.js +13 -7
  90. package/src/extensibility/custom-tools/types.ts +1 -0
  91. package/src/extensibility/extensions/loader.ts +5 -3
  92. package/src/extensibility/extensions/wrapper.ts +9 -3
  93. package/src/extensibility/hooks/loader.ts +3 -3
  94. package/src/extensibility/hooks/tool-wrapper.ts +13 -4
  95. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +18 -1
  96. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +59 -2
  97. package/src/extensibility/plugins/manager.ts +76 -5
  98. package/src/extensibility/shared-events.ts +1 -0
  99. package/src/extensibility/tool-event-input.ts +23 -0
  100. package/src/extensibility/utils.ts +74 -0
  101. package/src/internal-urls/docs-index.generated.txt +1 -1
  102. package/src/mcp/client.ts +3 -1
  103. package/src/mcp/manager.ts +12 -5
  104. package/src/mcp/oauth-discovery.ts +5 -29
  105. package/src/mcp/transports/http.ts +3 -1
  106. package/src/mcp/transports/index.ts +1 -0
  107. package/src/mcp/transports/sse.ts +377 -0
  108. package/src/memories/index.ts +15 -6
  109. package/src/mnemopi/backend.ts +2 -2
  110. package/src/modes/acp/acp-agent.ts +1 -1
  111. package/src/modes/components/advisor-config.ts +555 -0
  112. package/src/modes/components/advisor-message.ts +9 -2
  113. package/src/modes/components/agent-hub.ts +9 -4
  114. package/src/modes/components/assistant-message.ts +5 -5
  115. package/src/modes/components/index.ts +2 -0
  116. package/src/modes/components/model-selector.ts +79 -48
  117. package/src/modes/components/settings-selector.ts +1 -0
  118. package/src/modes/components/status-line/component.ts +145 -14
  119. package/src/modes/components/status-line/segments.ts +47 -22
  120. package/src/modes/components/status-line/types.ts +13 -1
  121. package/src/modes/components/tool-execution.ts +47 -6
  122. package/src/modes/controllers/command-controller.ts +23 -2
  123. package/src/modes/controllers/event-controller.ts +114 -11
  124. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  125. package/src/modes/controllers/input-controller.ts +61 -61
  126. package/src/modes/controllers/selector-controller.ts +100 -9
  127. package/src/modes/interactive-mode.ts +65 -10
  128. package/src/modes/print-mode.ts +1 -1
  129. package/src/modes/skill-command.ts +116 -0
  130. package/src/modes/types.ts +7 -2
  131. package/src/modes/utils/transcript-render-helpers.ts +2 -2
  132. package/src/modes/utils/ui-helpers.ts +46 -27
  133. package/src/prompts/agents/reviewer.md +11 -10
  134. package/src/prompts/review-custom-request.md +1 -2
  135. package/src/prompts/review-request.md +1 -2
  136. package/src/prompts/system/interrupted-thinking.md +7 -0
  137. package/src/prompts/system/recap-user.md +9 -0
  138. package/src/prompts/system/subagent-system-prompt.md +8 -5
  139. package/src/prompts/system/subagent-yield-reminder.md +6 -5
  140. package/src/prompts/system/system-prompt.md +0 -1
  141. package/src/prompts/tools/irc.md +2 -2
  142. package/src/prompts/tools/read.md +2 -2
  143. package/src/sdk.ts +40 -50
  144. package/src/session/agent-session.ts +1139 -600
  145. package/src/session/indexed-session-storage.ts +86 -13
  146. package/src/session/messages.test.ts +125 -0
  147. package/src/session/messages.ts +192 -21
  148. package/src/session/redis-session-storage.ts +49 -2
  149. package/src/session/session-entries.ts +39 -2
  150. package/src/session/session-history-format.ts +29 -2
  151. package/src/session/session-listing.ts +54 -24
  152. package/src/session/session-loader.ts +66 -3
  153. package/src/session/session-manager.ts +113 -19
  154. package/src/session/session-persistence.ts +96 -3
  155. package/src/session/session-storage.ts +36 -0
  156. package/src/session/session-title-slot.ts +141 -0
  157. package/src/session/settings-stream-fn.ts +49 -0
  158. package/src/session/sql-session-storage.ts +71 -11
  159. package/src/session/turn-persistence.ts +142 -0
  160. package/src/session/unexpected-stop-classifier.ts +2 -2
  161. package/src/slash-commands/builtin-registry.ts +16 -3
  162. package/src/slash-commands/helpers/mcp.ts +2 -1
  163. package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
  164. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
  165. package/src/ssh/connection-manager.ts +139 -12
  166. package/src/ssh/file-transfer.ts +23 -18
  167. package/src/ssh/ssh-executor.ts +2 -13
  168. package/src/ssh/utils.ts +19 -0
  169. package/src/task/executor.ts +21 -23
  170. package/src/task/render.ts +162 -20
  171. package/src/task/renderer.ts +14 -0
  172. package/src/task/types.ts +17 -0
  173. package/src/task/yield-assembly.ts +207 -0
  174. package/src/tiny/models.ts +8 -6
  175. package/src/tiny/text.ts +37 -7
  176. package/src/tools/ask.ts +55 -4
  177. package/src/tools/image-gen.ts +2 -1
  178. package/src/tools/render-utils.ts +2 -0
  179. package/src/tools/renderers.ts +8 -2
  180. package/src/tools/review.ts +17 -7
  181. package/src/tools/ssh.ts +8 -4
  182. package/src/tools/todo.ts +17 -1
  183. package/src/tools/tts.ts +2 -1
  184. package/src/tools/yield.ts +140 -31
  185. package/src/utils/thinking-display.ts +15 -0
  186. package/src/utils/title-generator.ts +1 -1
  187. package/src/web/search/providers/tavily.ts +36 -19
@@ -12,6 +12,32 @@ import type { OutputMeta } from "../tools/output-meta";
12
12
  export declare const SKILL_PROMPT_MESSAGE_TYPE = "skill-prompt";
13
13
  export declare const LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE = "lsp-late-diagnostic";
14
14
  export declare const BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE = "background-tan-dispatch";
15
+ /** Custom message type for hidden interrupted-thinking continuity context. */
16
+ export declare const INTERRUPTED_THINKING_MESSAGE_TYPE = "interrupted-thinking";
17
+ /** Metadata persisted with a hidden interrupted-thinking continuity message. */
18
+ export interface InterruptedThinkingDetails {
19
+ interruptedAt: number;
20
+ provider: AssistantMessage["provider"];
21
+ model: string;
22
+ blockCount: number;
23
+ }
24
+ /** Pure helper result for persisting interrupted thinking outside the assistant turn. */
25
+ export interface DemotedInterruptedThinking {
26
+ reasoning: string;
27
+ strippedContent: AssistantMessage["content"];
28
+ blockCount: number;
29
+ }
30
+ /**
31
+ * Demote a trailing run of *incomplete* interrupted-thinking from an assistant
32
+ * message — reasoning that was still streaming when the user aborted.
33
+ *
34
+ * A block joins the run only when it is a non-empty `thinking` block with no
35
+ * `thinkingSignature`. A signed/complete thinking block (Anthropic signature,
36
+ * OpenAI reasoning item id) is safely replayable, so it ends the run and stays
37
+ * in place — as do `redactedThinking` encrypted blobs, text, tool calls,
38
+ * empty-thinking blocks, and trailing empty text placeholders.
39
+ */
40
+ export declare function demoteInterruptedThinking(message: Pick<AssistantMessage, "content">): DemotedInterruptedThinking | undefined;
15
41
  /** Details persisted on a `/tan` background-dispatch breadcrumb. */
16
42
  export interface BackgroundTanDispatchDetails {
17
43
  jobId: string;
@@ -43,18 +69,17 @@ export interface SkillPromptDetails {
43
69
  * #buildTranscriptLines`, `runPrintMode`, and `AcpAgent#replayAssistantMessage`
44
70
  * (fallback error emission) read it via `isSilentAbort`. */
45
71
  export declare const SILENT_ABORT_MARKER = "__omp.silent_abort__";
46
- /** Type-guard for `SILENT_ABORT_MARKER`. Renderers MUST branch on this rather
47
- * than string-comparing inline so refactors to the marker constant (e.g.,
48
- * namespacing changes) propagate through every consumer in lockstep. */
49
- export declare function isSilentAbort(errorMessage: string | undefined): boolean;
72
+ /** Type-guard for silent aborts. Renderers MUST call this helper so structured
73
+ * `errorId` and legacy persisted marker messages stay in lockstep. */
74
+ export declare function isSilentAbort(message: Pick<AssistantMessage, "errorId" | "errorMessage">): boolean;
50
75
  /** Reason threaded through `AbortController.abort(reason)` when the user aborts
51
76
  * the turn with Esc (see `AgentSession.abort`). The agent keeps it on the
52
77
  * aborted assistant message's `errorMessage` so queued follow-ups/tool-result
53
78
  * placeholders can distinguish a deliberate interrupt from a bare lifecycle
54
79
  * abort, but interactive renderers suppress this redundant transcript line. */
55
80
  export declare const USER_INTERRUPT_LABEL = "Interrupted by user";
56
- export declare function isUserInterruptAbort(errorMessage: string | undefined): boolean;
57
- export declare function shouldRenderAbortReason(errorMessage: string | undefined): boolean;
81
+ export declare function isUserInterruptAbort(message: Pick<AssistantMessage, "errorId" | "errorMessage">): boolean;
82
+ export declare function shouldRenderAbortReason(message: Pick<AssistantMessage, "errorId" | "errorMessage">): boolean;
58
83
  /** Sentinel `errorMessage` the agent stamps on any abort that carried no custom
59
84
  * reason (bare `abort()`). Renderers treat it as "no specific reason given". */
60
85
  export declare const GENERIC_ABORT_SENTINEL = "Request was aborted";
@@ -63,7 +88,7 @@ export declare const GENERIC_ABORT_SENTINEL = "Request was aborted";
63
88
  * no threaded reason fall back to the retry-aware generic label. Call
64
89
  * `shouldRenderAbortReason` before rendering when user interrupts should stay
65
90
  * visually quiet. */
66
- export declare function resolveAbortLabel(errorMessage: string | undefined, retryAttempt?: number): string;
91
+ export declare function resolveAbortLabel(message: Pick<AssistantMessage, "errorId" | "errorMessage">, retryAttempt?: number): string;
67
92
  /** Extract the optional `__queueChipText` field from a CustomMessage's
68
93
  * `details` blob. Safe over `unknown`; returns undefined when the field is
69
94
  * absent or non-string. */
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,26 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import type { ImageContent, MessageAttribution, ServiceTier, TextContent } from "@oh-my-pi/pi-ai";
3
3
  export declare const CURRENT_SESSION_VERSION = 3;
4
+ export declare const SESSION_TITLE_SLOT_BYTES = 256;
5
+ export declare const SESSION_TITLE_SLOT_ENTRY_TYPE = "title";
6
+ export declare const TITLE_CHANGE_ENTRY_TYPE = "title_change";
7
+ export type SessionTitleSource = "auto" | "user";
8
+ /** Fixed-width first-line slot carrying the mutable current session title. */
9
+ export interface SessionTitleSlotEntry {
10
+ type: typeof SESSION_TITLE_SLOT_ENTRY_TYPE;
11
+ v: 1;
12
+ title: string;
13
+ source?: SessionTitleSource;
14
+ updatedAt: string;
15
+ pad: string;
16
+ }
4
17
  export declare const EPHEMERAL_MODEL_CHANGE_ROLE = "fallback";
5
18
  export interface SessionHeader {
6
19
  type: "session";
7
20
  version?: number;
8
21
  id: string;
9
22
  title?: string;
10
- titleSource?: "auto" | "user";
23
+ titleSource?: SessionTitleSource;
11
24
  timestamp: string;
12
25
  cwd: string;
13
26
  parentSession?: string;
@@ -91,6 +104,19 @@ export interface LabelEntry extends SessionEntryBase {
91
104
  targetId: string;
92
105
  label: string | undefined;
93
106
  }
107
+ /** Append-only audit entry recording a session title change. */
108
+ export interface TitleChangeEntry extends SessionEntryBase {
109
+ type: typeof TITLE_CHANGE_ENTRY_TYPE;
110
+ title: string;
111
+ previousTitle?: string;
112
+ source: SessionTitleSource;
113
+ trigger?: string;
114
+ }
115
+ declare module "@oh-my-pi/pi-agent-core/compaction/entries" {
116
+ interface CustomCompactionSessionEntries {
117
+ titleChange: TitleChangeEntry;
118
+ }
119
+ }
94
120
  /** TTSR injection entry - tracks which time-traveling rules have been injected this session. */
95
121
  export interface TtsrInjectionEntry extends SessionEntryBase {
96
122
  type: "ttsr_injection";
@@ -149,9 +175,11 @@ export interface CustomMessageEntry<T = unknown> extends SessionEntryBase {
149
175
  attribution?: MessageAttribution;
150
176
  }
151
177
  /** Session entry - has id/parentId for tree structure (returned by "read" methods in SessionManager) */
152
- export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TtsrInjectionEntry | MCPToolSelectionEntry | SessionInitEntry | ModeChangeEntry;
153
- /** Raw file entry (includes header) */
178
+ export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | ModelChangeEntry | ServiceTierChangeEntry | CompactionEntry | BranchSummaryEntry | CustomEntry | CustomMessageEntry | LabelEntry | TitleChangeEntry | TtsrInjectionEntry | MCPToolSelectionEntry | SessionInitEntry | ModeChangeEntry;
179
+ /** Raw logical file entry after loaders strip any fixed-width title slot. */
154
180
  export type FileEntry = SessionHeader | SessionEntry;
181
+ /** Physical JSONL entry before slot-aware loaders fold the title slot. */
182
+ export type RawFileEntry = SessionTitleSlotEntry | FileEntry;
155
183
  /** Tree node for getTree() - defensive copy of session structure */
156
184
  export interface SessionTreeNode {
157
185
  entry: SessionEntry;
@@ -18,6 +18,12 @@ export interface HistoryFormatOptions {
18
18
  * still collapse to a one-liner.
19
19
  */
20
20
  expandPrimaryContext?: boolean;
21
+ /**
22
+ * Append the full unified diff (from a tool result's `details.diff`) below
23
+ * edit/apply_patch tool lines, instead of just the path. The advisor sets
24
+ * this so it sees what changed without re-reading the file.
25
+ */
26
+ expandEditDiffs?: boolean;
21
27
  }
22
28
  /**
23
29
  * Hidden custom messages that inject the primary agent's operative *constraints*
@@ -1,7 +1,15 @@
1
1
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
2
  import { BlobStore } from "./blob-store";
3
- import type { FileEntry } from "./session-entries";
3
+ import { type FileEntry, type SessionTitleSlotEntry } from "./session-entries";
4
4
  import { type SessionStorage } from "./session-storage";
5
+ import { type SessionTitleUpdate } from "./session-title-slot";
6
+ /** Parse session JSONL while stripping and folding the optional fixed title slot. */
7
+ export declare function parseSessionContent(content: string): {
8
+ entries: FileEntry[];
9
+ titleSlot: SessionTitleUpdate | undefined;
10
+ };
11
+ /** Read only the fixed-size head window to detect a physical title slot. */
12
+ export declare function readTitleSlotFromFile(filePath: string, storage?: SessionStorage): Promise<SessionTitleSlotEntry | undefined>;
5
13
  /** Exported for compaction.test.ts */
6
14
  export declare function parseSessionEntries(content: string): FileEntry[];
7
15
  /** Exported for testing */
@@ -3,7 +3,7 @@ import { ArtifactManager } from "./artifacts";
3
3
  import { type BlobPutOptions, type BlobPutResult } from "./blob-store";
4
4
  import { type BashExecutionMessage, type CustomMessage, type FileMentionMessage, type HookMessage, type PythonExecutionMessage } from "./messages";
5
5
  import { type BuildSessionContextOptions, type SessionContext } from "./session-context";
6
- import { type NewSessionOptions, type SessionEntry, type SessionHeader, type SessionTreeNode, type UsageStatistics } from "./session-entries";
6
+ import { type NewSessionOptions, type SessionEntry, type SessionHeader, type SessionTitleSource, type SessionTreeNode, type UsageStatistics } from "./session-entries";
7
7
  import { type SessionInfo } from "./session-listing";
8
8
  import { type SessionStorage } from "./session-storage";
9
9
  export type ReadonlySessionManager = Pick<SessionManager, "getCwd" | "getSessionDir" | "getSessionId" | "getSessionFile" | "getSessionName" | "getArtifactsDir" | "getArtifactManager" | "allocateArtifactPath" | "saveArtifact" | "getArtifactPath" | "getLeafId" | "getLeafEntry" | "getEntry" | "getLabel" | "getBranch" | "getHeader" | "getEntries" | "getTree" | "getUsageStatistics" | "putBlob" | "putBlobSync">;
@@ -12,8 +12,10 @@ interface SessionManagerStateSnapshot {
12
12
  sessionDir: string;
13
13
  sessionId: string;
14
14
  sessionName: string | undefined;
15
- titleSource: "auto" | "user" | undefined;
15
+ titleSource: SessionTitleSource | undefined;
16
16
  sessionFile: string | undefined;
17
+ titleUpdatedAt: string;
18
+ hasTitleSlot: boolean;
17
19
  onDisk: boolean;
18
20
  needsRewrite: boolean;
19
21
  header: SessionHeader;
@@ -109,7 +111,7 @@ export declare class SessionManager {
109
111
  saveDraft(text: string): Promise<void>;
110
112
  consumeDraft(): Promise<string | null>;
111
113
  /** The source that set the session name: "user" (manual/RPC) or "auto" (generated title). */
112
- get titleSource(): "auto" | "user" | undefined;
114
+ get titleSource(): SessionTitleSource | undefined;
113
115
  getSessionName(): string | undefined;
114
116
  onSessionNameChanged(cb: () => void): () => void;
115
117
  /**
@@ -117,7 +119,7 @@ export declare class SessionManager {
117
119
  * @param source "user" for explicit renames; "auto" for generated titles.
118
120
  * Auto titles are ignored once the user has set a name.
119
121
  */
120
- setSessionName(name: string, source?: "auto" | "user"): Promise<boolean>;
122
+ setSessionName(name: string, source?: SessionTitleSource, trigger?: string): Promise<boolean>;
121
123
  /**
122
124
  * Append a foreign (host-authored) entry verbatim, preserving its
123
125
  * `id`/`parentId`. Used by collab guests to mirror the host session.
@@ -1,3 +1,4 @@
1
+ import { type SessionTitleUpdate } from "./session-title-slot";
1
2
  export interface SessionStorageStat {
2
3
  size: number;
3
4
  mtimeMs: number;
@@ -22,6 +23,14 @@ export interface SessionStorage {
22
23
  ensureDirSync(dir: string): void;
23
24
  existsSync(path: string): boolean;
24
25
  writeTextSync(path: string, content: string): void;
26
+ /**
27
+ * Update the current session title through the storage backend.
28
+ *
29
+ * File-like backends rewrite the fixed-width JSONL title slot; indexed
30
+ * backends can store the semantic title fields and synthesize the slot when
31
+ * reading.
32
+ */
33
+ updateSessionTitle(path: string, update: SessionTitleUpdate): Promise<void>;
25
34
  statSync(path: string): SessionStorageStat;
26
35
  listFilesSync(dir: string, pattern: string): string[];
27
36
  exists(path: string): Promise<boolean>;
@@ -43,6 +52,7 @@ export declare class FileSessionStorage implements SessionStorage {
43
52
  ensureDirSync(dir: string): void;
44
53
  existsSync(path: string): boolean;
45
54
  writeTextSync(fpath: string, content: string): void;
55
+ updateSessionTitle(fpath: string, update: SessionTitleUpdate): Promise<void>;
46
56
  statSync(path: string): SessionStorageStat;
47
57
  listFilesSync(dir: string, pattern: string): string[];
48
58
  exists(path: string): Promise<boolean>;
@@ -67,6 +77,7 @@ export declare class MemorySessionStorage implements SessionStorage {
67
77
  ensureDirSync(_dir: string): void;
68
78
  existsSync(path: string): boolean;
69
79
  writeTextSync(path: string, content: string): void;
80
+ updateSessionTitle(path: string, update: SessionTitleUpdate): Promise<void>;
70
81
  /**
71
82
  * Internal O(1) append used by {@link MemorySessionStorageWriter}. Lazily
72
83
  * creates the entry. External callers should go through `openWriter()`
@@ -0,0 +1,19 @@
1
+ import { type SessionTitleSlotEntry, type SessionTitleSource } from "./session-entries";
2
+ /** Semantic title update persisted by session storage backends. */
3
+ export interface SessionTitleUpdate {
4
+ title?: string;
5
+ source?: SessionTitleSource;
6
+ updatedAt: string;
7
+ }
8
+ /** Parse a physical title slot JSONL line. Returns undefined for legacy headers. */
9
+ export declare function parseTitleSlotLine(line: string): SessionTitleSlotEntry | undefined;
10
+ /** Parse the fixed-width title slot from a physical session body. */
11
+ export declare function parseTitleSlotFromContent(content: string): SessionTitleSlotEntry | undefined;
12
+ /** Convert a parsed title slot to the semantic storage update shape. */
13
+ export declare function titleUpdateFromSlot(slot: SessionTitleSlotEntry | undefined): SessionTitleUpdate | undefined;
14
+ /** Serialize the fixed-width first-line title slot, exactly 256 UTF-8 bytes including newline. */
15
+ export declare function serializeTitleSlot(options: SessionTitleUpdate): string;
16
+ /** Replace the physical fixed-width title slot in a full session body. */
17
+ export declare function overlayTitleSlotContent(content: string, update: SessionTitleUpdate): string;
18
+ /** Replace the physical fixed-width title slot in a prefix byte window. */
19
+ export declare function overlayTitleSlotPrefix(prefix: string, prefixBytes: number, update: SessionTitleUpdate): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Settings-aware stream wrapper shared by the main agent (sdk.ts) and the
3
+ * advisor agent (AgentSession.#buildAdvisorRuntime).
4
+ *
5
+ * Reads OpenRouter / Antigravity routing variants, Responses-family text
6
+ * verbosity, per-provider in-flight caps, and the loop guard out of `Settings`
7
+ * per request, layering them onto whatever options the caller passed. Before
8
+ * this helper existed, advisor turns called bare `streamSimple` while the main
9
+ * turn went through an inline closure that read these settings — so an advisor on
10
+ * OpenRouter never saw `providers.openrouterVariant`, breaking sticky routing
11
+ * and OpenRouter response-cache hits across advisor calls.
12
+ */
13
+ import type { StreamFn } from "@oh-my-pi/pi-agent-core";
14
+ import { type Settings } from "../config/settings";
15
+ /**
16
+ * Build a {@link StreamFn} that reads provider routing/guard settings from
17
+ * `settings` per call and forwards to `base` (defaults to `streamSimple`).
18
+ *
19
+ * Caller-supplied `streamOptions` always win — the helper only fills holes.
20
+ */
21
+ export declare function createSettingsAwareStreamFn(settings: Settings, base?: StreamFn): StreamFn;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Helpers that share one cheap, structural identity for messages — both during
3
+ * incremental persistence and for the mid-run-compaction ordering check.
4
+ *
5
+ * Previously `AgentSession` carried two near-duplicate routines
6
+ * (`#sessionMessagesReferToSameTurn` + `#messageValueSignature`) that
7
+ * reconstructed the branch path on every check (O(n²) `unshift`) and
8
+ * `JSON.stringify`-compared the full message content on every pairwise hit.
9
+ * Long-running sessions with many subagents fired this thousands of times per
10
+ * minute and froze the TUI loop (see issue #3629). The persistence key already
11
+ * encodes a stable logical identity — timestamp + role-specific discriminators
12
+ * — so the structural compare is now the rare collision tiebreaker (e.g. two
13
+ * provider responses at the same millisecond with `undefined` responseId),
14
+ * not the load-bearing check.
15
+ *
16
+ * The helpers here keep that identity in one place and expose the planner so
17
+ * the persistence-ordering logic is unit-testable without standing up an
18
+ * `AgentSession`.
19
+ */
20
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
21
+ /**
22
+ * Stable identity for messages that pass through {@link AgentSession}'s
23
+ * incremental persistence path.
24
+ *
25
+ * The discriminators chosen per role are precisely the fields that uniquely
26
+ * identify a single logical message instance:
27
+ *
28
+ * - `assistant` — timestamp + provider + model + responseId + stopReason
29
+ * (responseId is the canonical provider-side id when available; the rest
30
+ * disambiguate when it is not, e.g. local/dev models).
31
+ * - `toolResult` — timestamp + toolCallId + toolName (toolCallId is unique
32
+ * per execution; toolName guards against synthetic reuse).
33
+ * - `user` / `developer` — timestamp + attribution (attribution distinguishes
34
+ * user-typed vs hook-injected at the same wall-clock millisecond).
35
+ * - `fileMention` — timestamp.
36
+ *
37
+ * Returns `undefined` for message roles that are not persisted through this
38
+ * path (e.g. `hookMessage`, `custom`, `bashExecution`) — those follow other
39
+ * append paths in `SessionManager`.
40
+ */
41
+ export declare function sessionMessagePersistenceKey(message: AgentMessage): string | undefined;
42
+ /**
43
+ * Slow-path content equality check used when two messages collide on
44
+ * {@link sessionMessagePersistenceKey}. Only the role's content fields are
45
+ * compared (no timestamps, no metadata) because the key already pinned all of
46
+ * those down.
47
+ *
48
+ * Most calls into the persistence path never reach this — keys are unique
49
+ * enough in production that the snapshot lookup short-circuits at the key
50
+ * level. Restoring the structural compare here preserves the pre-#3629
51
+ * contract that two messages with the same metadata BUT different content are
52
+ * distinct (e.g. two assistant turns with `undefined` responseId emitted in
53
+ * the same wall-clock millisecond, which is exactly how the in-memory test
54
+ * harness crafts streamed responses).
55
+ */
56
+ export declare function sameMessageContent(left: AgentMessage, right: AgentMessage): boolean;
57
+ /**
58
+ * Outcome of {@link planTurnPersistence}.
59
+ *
60
+ * `ok` lists the turn-message indices that still need to be appended (in
61
+ * order). `out-of-order` reports the first message whose later sibling is
62
+ * already persisted — the caller bails so it does not silently splice a
63
+ * stale message between newer entries on the live branch.
64
+ */
65
+ export type TurnPersistencePlan = {
66
+ kind: "ok";
67
+ toPersist: readonly number[];
68
+ } | {
69
+ kind: "out-of-order";
70
+ messageIndex: number;
71
+ };
72
+ /**
73
+ * Decide what to do with a turn's messages relative to what's already on the
74
+ * branch, in a single pass over the pre-computed keys.
75
+ *
76
+ * @param turnKeys persistence keys for each turn message, in the order the
77
+ * agent loop emitted them. `undefined` slots represent messages with no
78
+ * persistence key (skipped silently).
79
+ * @param persistedKeys the snapshot of persistence keys currently on the
80
+ * branch (built once per call from {@link sessionMessagePersistenceKey} for
81
+ * each persisted message entry).
82
+ *
83
+ * The check is O(n²) over turn messages — but `n` here is the size of one
84
+ * turn (a handful of tool results), not the size of the branch. That's the
85
+ * point of this refactor: the expensive O(branch) work happens exactly once,
86
+ * inside the caller's snapshot loop, not per-comparison.
87
+ */
88
+ export declare function planTurnPersistence(turnKeys: readonly (string | undefined)[], persistedKeys: ReadonlySet<string>): TurnPersistencePlan;
@@ -14,6 +14,15 @@ export interface SSHHostInfo {
14
14
  version: number;
15
15
  os: SSHHostOs;
16
16
  shell: SSHHostShell;
17
+ /**
18
+ * Shell name OMP verified can execute the POSIX transfer snippets
19
+ * (`head`/`cat`/`mv`/`test`/`ls`) `ssh://` uses. Probed by running
20
+ * `sh -lc` / `bash -lc` / `zsh -lc` against the remote and keeping the
21
+ * first one that round-trips a known marker. Independent of `shell`
22
+ * (the self-reported login shell), which may be noisy, exotic, or simply
23
+ * mis-classified — only `transferShell` gates ssh:// transfers.
24
+ */
25
+ transferShell?: "sh" | "bash" | "zsh";
17
26
  compatShell?: "bash" | "sh";
18
27
  compatEnabled: boolean;
19
28
  }
@@ -22,6 +31,44 @@ interface SSHArgsOptions {
22
31
  /** When true, omit `-n` so the remote command can read from our piped stdin. */
23
32
  allowStdin?: boolean;
24
33
  }
34
+ /**
35
+ * Parse a raw cache-file value (or any unknown) into a normalized
36
+ * {@link SSHHostInfo}, dropping fields that don't pass the per-field guards.
37
+ * Exported so cache-layer round-tripping (incl. the new `transferShell`
38
+ * field, #3719) is testable without touching disk.
39
+ */
40
+ export declare function parseHostInfo(value: unknown): SSHHostInfo | null;
41
+ /**
42
+ * Frame marker emitted by the remote OS/shell probe. The probe wraps its
43
+ * payload in this prefix so the parser can ignore startup-file noise (banners,
44
+ * `motd`, login messages, `Last login: …`) instead of trusting only the first
45
+ * line of stdout. See #3719.
46
+ */
47
+ export declare const HOST_PROBE_MARKER = "PI_HOST_PROBE=";
48
+ /** Marker for the transfer-shell capability probe. */
49
+ export declare const TRANSFER_PROBE_MARKER = "PI_TRANSFER_OK|";
50
+ /**
51
+ * Find the first line of `stdout`/`stderr` that begins with `marker` and
52
+ * return everything after it. Used by the SSH host probe so noisy login
53
+ * dotfiles can't corrupt OS/shell classification by emitting text on the
54
+ * first line of `ssh` output.
55
+ *
56
+ * Returns `null` when no marker line is found in either stream.
57
+ */
58
+ export declare function extractProbePayload(stdout: string, stderr: string, marker?: string): string | null;
59
+ /**
60
+ * Find `marker` anywhere in `stdout` or `stderr` and return everything that
61
+ * follows it, scanning stdout first. Returns `null` when the marker is in
62
+ * neither stream.
63
+ *
64
+ * Used by the transfer-shell capability probe. Some remotes have broken
65
+ * login dotfiles that swap fd 1/2, so the marker can land on stderr even
66
+ * though the probe ran the printf successfully (matches the host-info
67
+ * probe's stderr fallback). See #3719.
68
+ */
69
+ export declare function findProbeMarker(stdout: string, stderr: string, marker: string): string | null;
70
+ /** Classify a POSIX-ish `uname -s` payload from the transfer-shell probe. */
71
+ export declare function osFromUname(value: string): SSHHostOs | undefined;
25
72
  export declare function getHostInfo(hostName: string): Promise<SSHHostInfo | undefined>;
26
73
  export declare function getHostInfoForHost(host: SSHConnectionTarget): Promise<SSHHostInfo | undefined>;
27
74
  /**
@@ -6,3 +6,19 @@ export declare function buildSshTarget(username: string | undefined, host: strin
6
6
  * `ssh://` file-transfer helpers.
7
7
  */
8
8
  export declare function quotePosixPath(value: string): string;
9
+ /**
10
+ * Wrap a POSIX command in `<shell> -c '<command>'` so it runs under the
11
+ * named shell rather than whatever `$SHELL` happens to be on the remote.
12
+ *
13
+ * Used by the `ssh://` transfer helpers and the Windows compat dispatch:
14
+ * OpenSSH passes our snippets to `<login-shell> -c`, so a remote whose
15
+ * login shell is fish/csh/tcsh (or cmd/powershell on Windows compat)
16
+ * can't parse `if [ … ]; then …`. Wrapping forces parsing under the
17
+ * shell OMP actually verified can run the snippet.
18
+ *
19
+ * `-c` (not `-lc`): the transfer snippets only call absolute POSIX
20
+ * builtins (`head`/`cat`/`mv`/`test`/`ls`/`mkdir`/`rm`/`dirname`) and
21
+ * don't need login-profile setup. Capability *probing* still uses
22
+ * `-lc` to mirror the user's real environment.
23
+ */
24
+ export declare function wrapInPosixShell(shell: "sh" | "bash" | "zsh", command: string): string;
@@ -23,7 +23,8 @@ import type { AuthStorage } from "../session/auth-storage";
23
23
  import type { ContextFileEntry } from "../tools";
24
24
  import type { EventBus } from "../utils/event-bus";
25
25
  import type { WorkspaceTree } from "../workspace-tree";
26
- import { type AgentDefinition, type AgentProgress, type ReviewFinding, type SingleResult } from "./types";
26
+ import { type AgentDefinition, type AgentProgress, type ReviewFinding, type SingleResult, type YieldItem } from "./types";
27
+ export type { YieldItem } from "./types";
27
28
  /**
28
29
  * Soft per-agent request budgets (assistant requests per run). When a subagent
29
30
  * crosses its budget it receives ONE steering notice asking it to wrap up; at
@@ -164,20 +165,6 @@ export interface ExecutorOptions {
164
165
  */
165
166
  keepAlive?: boolean;
166
167
  }
167
- export interface YieldItem {
168
- data?: unknown;
169
- status?: "success" | "aborted";
170
- error?: string;
171
- /**
172
- * Set by the in-tool yield validator when it exhausted its retry budget
173
- * (MAX_SCHEMA_RETRIES) and accepted a schema-invalid payload anyway.
174
- * `finalizeSubprocessOutput` honors this by serializing the payload and
175
- * surfacing a stderr warning, instead of re-emitting `schema_violation`
176
- * — which would silently swap the subagent's "accepted" view for a
177
- * different, opaque error blob in the parent's view of the result.
178
- */
179
- schemaOverridden?: boolean;
180
- }
181
168
  interface FinalizeSubprocessOutputArgs {
182
169
  rawOutput: string;
183
170
  exitCode: number;
@@ -187,6 +174,7 @@ interface FinalizeSubprocessOutputArgs {
187
174
  yieldItems?: YieldItem[];
188
175
  reportFindings?: ReviewFinding[];
189
176
  outputSchema: unknown;
177
+ lastAssistantText?: string;
190
178
  }
191
179
  interface FinalizeSubprocessOutputResult {
192
180
  rawOutput: string;
@@ -217,4 +205,3 @@ export declare function finalizeSubagentLifecycle(args: {
217
205
  * Run a single agent in-process.
218
206
  */
219
207
  export declare function runSubprocess(options: ExecutorOptions): Promise<SingleResult>;
220
- export {};
@@ -31,9 +31,4 @@ export declare function renderResult(result: {
31
31
  details?: TaskToolDetails;
32
32
  isError?: boolean;
33
33
  }, options: TaskRenderOptions, theme: Theme, args?: TaskParams): Component;
34
- export declare const taskToolRenderer: {
35
- renderCall: typeof renderCall;
36
- renderResult: typeof renderResult;
37
- mergeCallAndResult: boolean;
38
- };
39
34
  export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Task tool renderer export.
3
+ *
4
+ * Separated from render.ts to avoid circular dependency issues with
5
+ * tools/renderers.ts. This module has no side effects and can be safely
6
+ * imported without triggering the subprocessToolRegistry registration.
7
+ */
8
+ import { renderCall, renderResult } from "./render";
9
+ export declare const taskToolRenderer: {
10
+ readonly renderCall: typeof renderCall;
11
+ readonly renderResult: typeof renderResult;
12
+ readonly mergeCallAndResult: true;
13
+ };
@@ -206,6 +206,22 @@ export interface AgentDefinition {
206
206
  source: AgentSource;
207
207
  filePath?: string;
208
208
  }
209
+ /** Details extracted from a subagent `yield` tool call for final-result assembly and task rendering. */
210
+ export interface YieldItem {
211
+ data?: unknown;
212
+ status?: "success" | "aborted";
213
+ error?: string;
214
+ /** A string label is terminal; a non-empty array of labels is incremental. */
215
+ type?: string | string[];
216
+ /** Resolve this yield's payload from the latest durable assistant text instead of `data`. */
217
+ useLastTurn?: boolean;
218
+ /**
219
+ * Set by the in-tool yield validator when it exhausted its retry budget and
220
+ * accepted schema-invalid data anyway. The executor preserves that override
221
+ * during post-mortem validation.
222
+ */
223
+ schemaOverridden?: boolean;
224
+ }
209
225
  /** Progress tracking for a single agent */
210
226
  export interface AgentProgress {
211
227
  index: number;
@@ -0,0 +1,28 @@
1
+ import type { YieldItem } from "./types";
2
+ /** Outcome of folding a run's yield calls into one payload, with provenance flags. */
3
+ interface AssembledYieldResult {
4
+ data: unknown;
5
+ schemaOverridden: boolean;
6
+ rawText: boolean;
7
+ missingData: boolean;
8
+ }
9
+ /**
10
+ * Top-level output-schema property names declared as arrays (JTD `elements` →
11
+ * JSON `type: "array"`). An incremental yield section for such a label
12
+ * accumulates into a list even when the agent emits exactly one — otherwise a
13
+ * single `type: ["findings"]` yield would assemble as a bare object and fail
14
+ * array-typed schema validation.
15
+ */
16
+ export declare function arrayValuedLabels(outputSchema: unknown): ReadonlySet<string>;
17
+ /**
18
+ * Assemble typed yield calls into the final payload consumed by schema validation.
19
+ *
20
+ * A non-empty array `type` contributes an incremental section and never decides
21
+ * termination by itself. A string `type` with omitted `data` makes the last
22
+ * assistant turn the raw terminal result. Other string-typed yields contribute
23
+ * the terminal labelled section. Untyped terminal yields keep the historical
24
+ * "last yield wins" behavior unless no terminal yield exists, in which case
25
+ * accumulated typed sections finalize on idle.
26
+ */
27
+ export declare function assembleYieldResult(yieldItems: YieldItem[], lastAssistantText?: string, arrayLabels?: ReadonlySet<string>): AssembledYieldResult | undefined;
28
+ export {};
@@ -16,6 +16,14 @@ export declare function stripCodeBlocks(message: string): string;
16
16
  /** Prepare a raw user message for titling: drop code blocks, then bound length. */
17
17
  export declare function prepareTitleInput(message: string): string;
18
18
  export declare function formatTitleUserMessage(message: string): string;
19
+ /** Single recent conversation turn supplied to title refresh after replanning. */
20
+ export interface TitleConversationTurn {
21
+ role: "user" | "assistant";
22
+ text?: string;
23
+ thinking?: string;
24
+ }
25
+ /** Format recent user/assistant context for title generation after a todo replan. */
26
+ export declare function formatTitleConversationContext(turns: readonly TitleConversationTurn[]): string;
19
27
  /**
20
28
  * True when a first user message is too low-signal to title (greeting, ack,
21
29
  * bare number, or empty once code/punctuation/emoji are stripped).
@@ -45,6 +45,8 @@ export declare const TRUNCATE_LENGTHS: {
45
45
  readonly LINE: 110;
46
46
  /** Very short (task previews, badges) */
47
47
  readonly SHORT: 40;
48
+ /** Idle recap status line (~40-word LLM reply) */
49
+ readonly RECAP: 280;
48
50
  };
49
51
  /** Human-readable key currently bound to tool-output expansion, e.g. `Ctrl+O`. */
50
52
  export declare function expandKeyHint(): string;
@@ -1,9 +1,10 @@
1
1
  /**
2
- * Review tools - report_finding for structured code review.
2
+ * Legacy hidden review-finding tool for agents that have not migrated to
3
+ * incremental `yield` sections.
3
4
  *
4
- * Used by the reviewer agent to report findings in a structured way.
5
- * Hidden by default - only enabled when explicitly listed in agent's tools.
6
- * Reviewers finish via `yield` tool with SubmitReviewDetails schema.
5
+ * Hidden by default - only enabled when explicitly listed in an agent's tools.
6
+ * Reviewers now finish via incremental `yield`; this tool remains for
7
+ * compatibility with older or custom review agents.
7
8
  */
8
9
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
9
10
  import type { Theme, ThemeColor } from "../modes/theme/theme";
@@ -15,6 +16,7 @@ export interface FindingPriorityInfo {
15
16
  color: ThemeColor;
16
17
  }
17
18
  export declare const PRIORITY_LABELS: FindingPriority[];
19
+ export declare function isFindingPriority(value: unknown): value is FindingPriority;
18
20
  export declare function getPriorityInfo(priority: FindingPriority): FindingPriorityInfo;
19
21
  declare const ReportFindingParams: import("arktype/internal/variants/object.ts").ObjectType<{
20
22
  title: string;
@@ -67,7 +67,7 @@ export declare const sshToolRenderer: {
67
67
  renderContext?: SshRenderContext;
68
68
  }, uiTheme: Theme, args?: SshRenderArgs): Component;
69
69
  mergeCallAndResult: boolean;
70
- provisionalPendingPreview: string;
70
+ provisionalPendingPreview: boolean;
71
71
  provisionalPartialResult: boolean;
72
72
  };
73
73
  export {};