@oh-my-pi/pi-coding-agent 16.2.2 → 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 (150) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/cli.js +3624 -3568
  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/transcript-recorder.d.ts +13 -2
  8. package/dist/types/advisor/watchdog.d.ts +20 -0
  9. package/dist/types/collab/guest.d.ts +29 -0
  10. package/dist/types/config/settings-schema.d.ts +81 -0
  11. package/dist/types/debug/log-viewer.d.ts +1 -0
  12. package/dist/types/debug/raw-sse.d.ts +1 -0
  13. package/dist/types/edit/hashline/diff.d.ts +0 -11
  14. package/dist/types/extensibility/tool-event-input.d.ts +7 -0
  15. package/dist/types/extensibility/utils.d.ts +12 -0
  16. package/dist/types/mcp/transports/index.d.ts +1 -0
  17. package/dist/types/mcp/transports/sse.d.ts +20 -0
  18. package/dist/types/modes/components/advisor-config.d.ts +59 -0
  19. package/dist/types/modes/components/index.d.ts +1 -0
  20. package/dist/types/modes/components/model-selector.d.ts +9 -1
  21. package/dist/types/modes/components/settings-selector.d.ts +1 -0
  22. package/dist/types/modes/components/status-line/component.d.ts +30 -1
  23. package/dist/types/modes/components/status-line/types.d.ts +13 -1
  24. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  25. package/dist/types/modes/interactive-mode.d.ts +10 -4
  26. package/dist/types/modes/skill-command.d.ts +32 -0
  27. package/dist/types/modes/types.d.ts +7 -2
  28. package/dist/types/session/agent-session.d.ts +58 -10
  29. package/dist/types/session/indexed-session-storage.d.ts +7 -1
  30. package/dist/types/session/messages.d.ts +26 -0
  31. package/dist/types/session/messages.test.d.ts +1 -0
  32. package/dist/types/session/session-entries.d.ts +31 -3
  33. package/dist/types/session/session-history-format.d.ts +6 -0
  34. package/dist/types/session/session-loader.d.ts +9 -1
  35. package/dist/types/session/session-manager.d.ts +6 -4
  36. package/dist/types/session/session-storage.d.ts +11 -0
  37. package/dist/types/session/session-title-slot.d.ts +19 -0
  38. package/dist/types/ssh/connection-manager.d.ts +47 -0
  39. package/dist/types/ssh/utils.d.ts +16 -0
  40. package/dist/types/task/executor.d.ts +3 -16
  41. package/dist/types/task/render.d.ts +0 -5
  42. package/dist/types/task/renderer.d.ts +13 -0
  43. package/dist/types/task/types.d.ts +16 -0
  44. package/dist/types/task/yield-assembly.d.ts +28 -0
  45. package/dist/types/tiny/text.d.ts +8 -0
  46. package/dist/types/tools/render-utils.d.ts +2 -0
  47. package/dist/types/tools/review.d.ts +6 -4
  48. package/dist/types/tools/ssh.d.ts +1 -1
  49. package/dist/types/tools/todo.d.ts +6 -0
  50. package/dist/types/tools/yield.d.ts +8 -3
  51. package/dist/types/utils/thinking-display.d.ts +4 -0
  52. package/package.json +12 -12
  53. package/src/advisor/__tests__/advisor.test.ts +242 -10
  54. package/src/advisor/__tests__/config.test.ts +173 -0
  55. package/src/advisor/advise-tool.ts +11 -6
  56. package/src/advisor/config.ts +256 -0
  57. package/src/advisor/index.ts +1 -0
  58. package/src/advisor/runtime.ts +12 -2
  59. package/src/advisor/transcript-recorder.ts +25 -2
  60. package/src/advisor/watchdog.ts +57 -31
  61. package/src/autoresearch/index.ts +7 -2
  62. package/src/cli/gc-cli.ts +17 -10
  63. package/src/collab/guest.ts +43 -7
  64. package/src/config/model-registry.ts +80 -18
  65. package/src/config/settings-schema.ts +77 -0
  66. package/src/debug/index.ts +32 -7
  67. package/src/debug/log-viewer.ts +111 -53
  68. package/src/debug/raw-sse.ts +68 -48
  69. package/src/discovery/codex.ts +13 -5
  70. package/src/edit/hashline/diff.ts +57 -4
  71. package/src/eval/js/shared/local-module-loader.ts +23 -1
  72. package/src/export/html/template.js +13 -7
  73. package/src/extensibility/extensions/loader.ts +5 -3
  74. package/src/extensibility/extensions/wrapper.ts +9 -3
  75. package/src/extensibility/hooks/loader.ts +3 -3
  76. package/src/extensibility/hooks/tool-wrapper.ts +13 -4
  77. package/src/extensibility/plugins/manager.ts +2 -1
  78. package/src/extensibility/tool-event-input.ts +23 -0
  79. package/src/extensibility/utils.ts +74 -0
  80. package/src/internal-urls/docs-index.generated.txt +1 -1
  81. package/src/mcp/client.ts +3 -1
  82. package/src/mcp/manager.ts +12 -5
  83. package/src/mcp/transports/index.ts +1 -0
  84. package/src/mcp/transports/sse.ts +377 -0
  85. package/src/memories/index.ts +15 -6
  86. package/src/modes/components/advisor-config.ts +555 -0
  87. package/src/modes/components/advisor-message.ts +9 -2
  88. package/src/modes/components/agent-hub.ts +9 -4
  89. package/src/modes/components/index.ts +2 -0
  90. package/src/modes/components/model-selector.ts +79 -48
  91. package/src/modes/components/settings-selector.ts +1 -0
  92. package/src/modes/components/status-line/component.ts +144 -5
  93. package/src/modes/components/status-line/segments.ts +46 -21
  94. package/src/modes/components/status-line/types.ts +13 -1
  95. package/src/modes/components/tool-execution.ts +47 -6
  96. package/src/modes/controllers/command-controller.ts +23 -2
  97. package/src/modes/controllers/event-controller.ts +106 -0
  98. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  99. package/src/modes/controllers/input-controller.ts +61 -61
  100. package/src/modes/controllers/selector-controller.ts +100 -9
  101. package/src/modes/interactive-mode.ts +65 -5
  102. package/src/modes/skill-command.ts +116 -0
  103. package/src/modes/types.ts +7 -2
  104. package/src/modes/utils/ui-helpers.ts +41 -23
  105. package/src/prompts/agents/reviewer.md +11 -10
  106. package/src/prompts/review-custom-request.md +1 -2
  107. package/src/prompts/review-request.md +1 -2
  108. package/src/prompts/system/interrupted-thinking.md +7 -0
  109. package/src/prompts/system/recap-user.md +9 -0
  110. package/src/prompts/system/subagent-system-prompt.md +8 -5
  111. package/src/prompts/system/subagent-yield-reminder.md +6 -5
  112. package/src/prompts/system/system-prompt.md +0 -1
  113. package/src/prompts/tools/irc.md +2 -2
  114. package/src/prompts/tools/read.md +2 -2
  115. package/src/sdk.ts +28 -24
  116. package/src/session/agent-session.ts +867 -428
  117. package/src/session/indexed-session-storage.ts +86 -13
  118. package/src/session/messages.test.ts +125 -0
  119. package/src/session/messages.ts +172 -9
  120. package/src/session/redis-session-storage.ts +49 -2
  121. package/src/session/session-entries.ts +39 -2
  122. package/src/session/session-history-format.ts +29 -2
  123. package/src/session/session-listing.ts +54 -24
  124. package/src/session/session-loader.ts +66 -3
  125. package/src/session/session-manager.ts +113 -19
  126. package/src/session/session-persistence.ts +95 -1
  127. package/src/session/session-storage.ts +36 -0
  128. package/src/session/session-title-slot.ts +141 -0
  129. package/src/session/sql-session-storage.ts +71 -11
  130. package/src/slash-commands/builtin-registry.ts +16 -3
  131. package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
  132. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
  133. package/src/ssh/connection-manager.ts +139 -12
  134. package/src/ssh/file-transfer.ts +23 -18
  135. package/src/ssh/ssh-executor.ts +2 -13
  136. package/src/ssh/utils.ts +19 -0
  137. package/src/task/executor.ts +21 -23
  138. package/src/task/render.ts +162 -20
  139. package/src/task/renderer.ts +14 -0
  140. package/src/task/types.ts +17 -0
  141. package/src/task/yield-assembly.ts +207 -0
  142. package/src/tiny/text.ts +23 -0
  143. package/src/tools/ask.ts +55 -4
  144. package/src/tools/render-utils.ts +2 -0
  145. package/src/tools/renderers.ts +8 -2
  146. package/src/tools/review.ts +17 -7
  147. package/src/tools/ssh.ts +8 -4
  148. package/src/tools/todo.ts +17 -1
  149. package/src/tools/yield.ts +140 -31
  150. package/src/utils/thinking-display.ts +15 -0
@@ -109,11 +109,16 @@ export declare class InteractiveMode implements InteractiveModeContext {
109
109
  loopLimit: LoopLimitRuntime | undefined;
110
110
  todoPhases: TodoPhase[];
111
111
  hideThinkingBlock: boolean;
112
+ /** Whether the visible session has produced thinking content the user can reveal. */
113
+ get hasDisplayableThinkingContent(): boolean;
114
+ /** Record received reasoning content so Ctrl+T can reveal it even when model metadata says thinking is off. */
115
+ noteDisplayableThinkingContent(message: AgentMessage): boolean;
112
116
  /**
113
- * Effective thinking-block visibility: hidden when the user's setting is on
114
- * OR the session thinking level is "off". Some providers (MiniMax, GLM,
115
- * DeepSeek) return thinking blocks even with reasoning disabled; this
116
- * respects the user's intent when they set thinking to "off" (#626).
117
+ * Effective thinking-block visibility: hidden when the user's setting is on,
118
+ * or while thinking is "off" before the session has actually produced
119
+ * displayable thinking content. Some providers return thinking blocks without
120
+ * advertising reasoning support, so observed content unlocks the visibility
121
+ * toggle.
117
122
  */
118
123
  get effectiveHideThinkingBlock(): boolean;
119
124
  proseOnlyThinking: boolean;
@@ -319,6 +324,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
319
324
  executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean): Promise<CompactionOutcome>;
320
325
  openInBrowser(urlOrPath: string): void;
321
326
  showSettingsSelector(): void;
327
+ showAdvisorConfigure(): void;
322
328
  showHistorySearch(): void;
323
329
  showExtensionsDashboard(): void;
324
330
  showAgentsDashboard(): void;
@@ -0,0 +1,32 @@
1
+ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
2
+ import { type CustomMessage, SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../session/messages";
3
+ import type { InteractiveModeContext } from "./types";
4
+ type SkillCommandHost = Pick<InteractiveModeContext, "skillCommands" | "session" | "showError">;
5
+ type SkillPromptMessage = Pick<CustomMessage<SkillPromptDetails>, "customType" | "content" | "display" | "details" | "attribution"> & {
6
+ customType: typeof SKILL_PROMPT_MESSAGE_TYPE;
7
+ content: string | (TextContent | ImageContent)[];
8
+ display: true;
9
+ details: SkillPromptDetails;
10
+ attribution: "user";
11
+ };
12
+ type SkillPromptOptions = {
13
+ streamingBehavior: "steer" | "followUp";
14
+ queueChipText: string;
15
+ };
16
+ interface InvokeSkillCommandOptions {
17
+ propagateErrors?: boolean;
18
+ queueOnly?: boolean;
19
+ images?: ImageContent[];
20
+ }
21
+ /** Built custom-message payload and delivery options for a `/skill:` command. */
22
+ export interface BuiltSkillCommandPrompt {
23
+ message: SkillPromptMessage;
24
+ options: SkillPromptOptions;
25
+ }
26
+ /** Return true when `text` names a registered `/skill:<name>` command. */
27
+ export declare function isKnownSkillCommand(ctx: SkillCommandHost, text: string): boolean;
28
+ /** Build the user-attributed custom message for a registered `/skill:<name>` command. */
29
+ export declare function buildSkillCommandPrompt(ctx: SkillCommandHost, text: string, streamingBehavior: "steer" | "followUp", images?: ImageContent[]): Promise<BuiltSkillCommandPrompt | undefined>;
30
+ /** Invoke a registered `/skill:<name>` command as a user-attributed custom message. */
31
+ export declare function invokeSkillCommandFromText(ctx: SkillCommandHost, text: string, streamingBehavior: "steer" | "followUp", options?: InvokeSkillCommandOptions): Promise<boolean>;
32
+ export {};
@@ -142,10 +142,14 @@ export interface InteractiveModeContext {
142
142
  hideThinkingBlock: boolean;
143
143
  /**
144
144
  * Effective thinking-block visibility: true when hidden by user setting OR
145
- * thinking level is "off". Read this in render paths instead of
146
- * {@link hideThinkingBlock} so blocks are auto-hidden when thinking is off.
145
+ * thinking level is "off" before the session has produced displayable
146
+ * thinking content.
147
147
  */
148
148
  readonly effectiveHideThinkingBlock: boolean;
149
+ /** Whether this visible session has produced thinking content the user can reveal. */
150
+ readonly hasDisplayableThinkingContent: boolean;
151
+ /** Record a message whose thinking content makes Ctrl+T meaningful even at thinking level "off"; returns true on first observation. */
152
+ noteDisplayableThinkingContent(message: AgentMessage): boolean;
149
153
  proseOnlyThinking: boolean;
150
154
  compactionQueuedMessages: CompactionQueuedMessage[];
151
155
  pendingTools: Map<string, ToolExecutionHandle>;
@@ -316,6 +320,7 @@ export interface InteractiveModeContext {
316
320
  refreshSlashCommandState(cwd?: string): Promise<void>;
317
321
  applyCwdChange(newCwd: string): Promise<void>;
318
322
  showSettingsSelector(): void;
323
+ showAdvisorConfigure(): void;
319
324
  showHistorySearch(): void;
320
325
  showExtensionsDashboard(): void;
321
326
  showAgentsDashboard(): void;
@@ -17,6 +17,7 @@ import { Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentT
17
17
  import { type CompactionResult, type ShakeConfig } from "@oh-my-pi/pi-agent-core/compaction";
18
18
  import type { AssistantMessage, Context, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ResetCreditAccountStatus, ResetCreditRedeemOutcome, ResetCreditTarget, ServiceTier, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@oh-my-pi/pi-ai";
19
19
  import { Effort } from "@oh-my-pi/pi-ai";
20
+ import { type AdvisorConfig } from "../advisor";
20
21
  import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
21
22
  import type { Rule } from "../capability/rule";
22
23
  import type { ModelRegistry } from "../config/model-registry";
@@ -247,20 +248,29 @@ export interface AgentSessionConfig {
247
248
  */
248
249
  providerSessionId?: string;
249
250
  /**
250
- * Hard-isolated read-only tools (read/search/find) for the advisor agent,
251
- * pre-built in `createAgentSession` against a distinct `ToolSession` so the
252
- * advisor's reads never share the primary's snapshot/seen-lines/conflict
253
- * caches. Undefined when the advisor is disabled.
251
+ * Full advisor toolset, pre-built in `createAgentSession` against a distinct,
252
+ * advisor-scoped `ToolSession` (its own `-advisor` session/agent id) so the
253
+ * advisor's tool state stays isolated from the primary. The advisor is a full
254
+ * agent; its config `tools` selects a subset (default read/grep/glob). Undefined
255
+ * when the advisor is disabled.
254
256
  */
255
- advisorReadOnlyTools?: AgentTool[];
257
+ advisorTools?: AgentTool[];
256
258
  /** Preloaded watchdog prompt content for the advisor. */
257
259
  advisorWatchdogPrompt?: string;
260
+ /** Preloaded YAML top-level `instructions` shared baseline, kept separate from
261
+ * `advisorWatchdogPrompt` so `/advisor configure` can swap it live. */
262
+ advisorSharedInstructions?: string;
258
263
  /**
259
264
  * Preloaded project context files (AGENTS.md, etc.) rendered as a system-prompt
260
265
  * block for the advisor — the same standing instructions the primary agent
261
266
  * receives, so the reviewer holds the agent to them.
262
267
  */
263
268
  advisorContextPrompt?: string;
269
+ /**
270
+ * Advisors discovered from `WATCHDOG.yml`. Empty/undefined runs a single
271
+ * legacy advisor on the `advisor` role (byte-for-byte the pre-config path).
272
+ */
273
+ advisorConfigs?: AdvisorConfig[];
264
274
  /**
265
275
  * Strip tool descriptions from provider-bound tool specs on side requests
266
276
  * (handoff). Must match the session-start value used to build the system
@@ -356,6 +366,7 @@ export interface SessionStats {
356
366
  tokens: {
357
367
  input: number;
358
368
  output: number;
369
+ reasoning: number;
359
370
  cacheRead: number;
360
371
  cacheWrite: number;
361
372
  total: number;
@@ -373,6 +384,7 @@ export interface AdvisorStats {
373
384
  tokens: {
374
385
  input: number;
375
386
  output: number;
387
+ reasoning: number;
376
388
  cacheRead: number;
377
389
  cacheWrite: number;
378
390
  total: number;
@@ -383,6 +395,18 @@ export interface AdvisorStats {
383
395
  assistant: number;
384
396
  total: number;
385
397
  };
398
+ /** Per-advisor breakdown; one entry per active advisor (single-advisor sessions have one). */
399
+ advisors: PerAdvisorStat[];
400
+ }
401
+ /** One advisor's slice of {@link AdvisorStats}, surfaced for the multi-advisor status panel. */
402
+ export interface PerAdvisorStat {
403
+ name: string;
404
+ model: Model;
405
+ contextWindow: number;
406
+ contextTokens: number;
407
+ tokens: AdvisorStats["tokens"];
408
+ cost: number;
409
+ messages: AdvisorStats["messages"];
386
410
  }
387
411
  export interface FreshSessionResult {
388
412
  previousSessionId: string;
@@ -394,6 +418,7 @@ export type RestoredQueuedMessage = {
394
418
  text: string;
395
419
  images?: ImageContent[];
396
420
  };
421
+ type SessionNameTrigger = "replan";
397
422
  export declare class AgentSession {
398
423
  #private;
399
424
  readonly agent: Agent;
@@ -698,6 +723,7 @@ export declare class AgentSession {
698
723
  prompt(text: string, options?: PromptOptions): Promise<boolean>;
699
724
  promptCustomMessage<T = unknown>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">, options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice"> & {
700
725
  queueChipText?: string;
726
+ queueOnly?: boolean;
701
727
  }): Promise<void>;
702
728
  /**
703
729
  * Queue a steering message to interrupt the agent mid-run.
@@ -797,7 +823,7 @@ export declare class AgentSession {
797
823
  /**
798
824
  * Set a display name for the current session.
799
825
  */
800
- setSessionName(name: string, source?: "auto" | "user"): Promise<boolean>;
826
+ setSessionName(name: string, source?: "auto" | "user", trigger?: SessionNameTrigger): Promise<boolean>;
801
827
  /**
802
828
  * Fork the current session, creating a new session file with the exact same state.
803
829
  * Copies all entries and artifacts to the new session.
@@ -808,16 +834,22 @@ export declare class AgentSession {
808
834
  /**
809
835
  * Set model directly.
810
836
  * Validates that a credential source is configured (synchronously, without
811
- * refreshing OAuth or running command-backed key programs) and saves to the
812
- * active session. Persists settings only when requested. The concrete key is
813
- * resolved lazily per request, so switching never blocks the event loop.
837
+ * refreshing OAuth or running command-backed key programs). The active
838
+ * session switches by default; when `currentContextTokens` is provided and
839
+ * exceeds the refreshed candidate's context window, the live switch is
840
+ * skipped while role persistence still runs. Returns whether the active
841
+ * model actually switched, computed against the refreshed metadata so
842
+ * dynamic providers (e.g. llama.cpp) honor their post-load contextWindow.
814
843
  * @throws Error if no API key available for the model
815
844
  */
816
845
  setModel(model: Model, role?: string, options?: {
817
846
  selector?: string;
818
847
  thinkingLevel?: ThinkingLevel;
819
848
  persist?: boolean;
820
- }): Promise<void>;
849
+ currentContextTokens?: number;
850
+ }): Promise<{
851
+ switched: boolean;
852
+ }>;
821
853
  /**
822
854
  * Set model temporarily (for this session only).
823
855
  * Validates that a credential source is configured (synchronously, without
@@ -1258,6 +1290,15 @@ export declare class AgentSession {
1258
1290
  * @returns true when the advisor is actively running after the call.
1259
1291
  */
1260
1292
  toggleAdvisorEnabled(): boolean;
1293
+ /**
1294
+ * Replace the live advisor roster from an edited `WATCHDOG.yml` (the `/advisor
1295
+ * configure` save path). Swaps the configs + shared baseline, then rebuilds the
1296
+ * runtimes in place so the change applies without a restart. When the advisor is
1297
+ * disabled the new configs are simply stored for the next enable.
1298
+ *
1299
+ * @returns the number of advisors active after the rebuild.
1300
+ */
1301
+ applyAdvisorConfigs(advisors: AdvisorConfig[], sharedInstructions: string | undefined): number;
1261
1302
  /**
1262
1303
  * Whether the advisor setting is enabled for this session.
1263
1304
  */
@@ -1269,6 +1310,13 @@ export declare class AgentSession {
1269
1310
  * not merely the setting. Drives the status-line badge and `/dump advisor`.
1270
1311
  */
1271
1312
  isAdvisorActive(): boolean;
1313
+ /**
1314
+ * The names of the tools available to advisors this session (the pool a
1315
+ * `/advisor configure` editor lists). The advisor is a full agent, so this is the
1316
+ * full built tool set; a tool whose optional factory returns null (e.g. lsp with
1317
+ * no servers) is absent.
1318
+ */
1319
+ getAdvisorAvailableToolNames(): string[];
1272
1320
  /**
1273
1321
  * The live advisor `Agent`, or `undefined` when no advisor runtime is
1274
1322
  * attached. Surfaced for diagnostics (`/dump advisor` already serializes
@@ -1,16 +1,21 @@
1
1
  import type { SessionStorage, SessionStorageStat, SessionStorageWriter } from "./session-storage";
2
+ import { type SessionTitleUpdate } from "./session-title-slot";
2
3
  export interface SessionStorageIndexEntry {
3
4
  path: string;
4
5
  size: number;
5
6
  mtimeMs: number;
7
+ title?: string;
8
+ titleSource?: SessionTitleUpdate["source"];
9
+ titleUpdatedAt?: string;
6
10
  }
7
11
  export interface SessionStorageBackend {
8
12
  init(): Promise<void>;
9
13
  loadIndex(): Promise<Iterable<SessionStorageIndexEntry>>;
10
14
  readFull(path: string): Promise<string | null>;
11
15
  readSlices(path: string, prefixBytes: number, suffixBytes: number): Promise<[string, string]>;
12
- writeFull(path: string, content: string, mtimeMs: number): Promise<void>;
16
+ writeFull(path: string, content: string, mtimeMs: number, title?: SessionTitleUpdate): Promise<void>;
13
17
  append(path: string, line: string, mtimeMs: number): Promise<void>;
18
+ updateSessionTitle(path: string, title: SessionTitleUpdate, mtimeMs: number): Promise<void>;
14
19
  truncate(path: string, mtimeMs: number): Promise<void>;
15
20
  remove(paths: string[]): Promise<void>;
16
21
  move(src: string, dst: string, mtimeMs: number): Promise<void>;
@@ -24,6 +29,7 @@ export declare class IndexedSessionStorage implements SessionStorage {
24
29
  ensureDirSync(_dir: string): void;
25
30
  existsSync(path: string): boolean;
26
31
  writeTextSync(path: string, content: string): void;
32
+ updateSessionTitle(path: string, title: SessionTitleUpdate): Promise<void>;
27
33
  statSync(path: string): SessionStorageStat;
28
34
  listFilesSync(dir: string, pattern: string): string[];
29
35
  exists(path: string): Promise<boolean>;
@@ -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;
@@ -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;
@@ -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 {};