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

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 (164) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/cli.js +3682 -3615
  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/cli/update-cli.d.ts +15 -0
  10. package/dist/types/collab/guest.d.ts +29 -0
  11. package/dist/types/collab/replication-shrink.d.ts +39 -0
  12. package/dist/types/config/provider-globals.d.ts +7 -0
  13. package/dist/types/config/settings-schema.d.ts +81 -0
  14. package/dist/types/debug/log-viewer.d.ts +1 -0
  15. package/dist/types/debug/raw-sse.d.ts +1 -0
  16. package/dist/types/edit/hashline/diff.d.ts +0 -11
  17. package/dist/types/extensibility/tool-event-input.d.ts +7 -0
  18. package/dist/types/extensibility/utils.d.ts +12 -0
  19. package/dist/types/mcp/transports/index.d.ts +1 -0
  20. package/dist/types/mcp/transports/sse.d.ts +20 -0
  21. package/dist/types/memories/index.d.ts +20 -1
  22. package/dist/types/modes/components/advisor-config.d.ts +59 -0
  23. package/dist/types/modes/components/index.d.ts +1 -0
  24. package/dist/types/modes/components/model-selector.d.ts +9 -1
  25. package/dist/types/modes/components/settings-selector.d.ts +1 -0
  26. package/dist/types/modes/components/status-line/component.d.ts +35 -1
  27. package/dist/types/modes/components/status-line/types.d.ts +13 -1
  28. package/dist/types/modes/controllers/command-controller.d.ts +3 -3
  29. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  30. package/dist/types/modes/interactive-mode.d.ts +11 -4
  31. package/dist/types/modes/skill-command.d.ts +32 -0
  32. package/dist/types/modes/types.d.ts +9 -3
  33. package/dist/types/session/agent-session.d.ts +58 -10
  34. package/dist/types/session/indexed-session-storage.d.ts +7 -1
  35. package/dist/types/session/messages.d.ts +26 -0
  36. package/dist/types/session/messages.test.d.ts +1 -0
  37. package/dist/types/session/session-entries.d.ts +31 -3
  38. package/dist/types/session/session-history-format.d.ts +6 -0
  39. package/dist/types/session/session-loader.d.ts +9 -1
  40. package/dist/types/session/session-manager.d.ts +8 -7
  41. package/dist/types/session/session-storage.d.ts +11 -0
  42. package/dist/types/session/session-title-slot.d.ts +19 -0
  43. package/dist/types/ssh/connection-manager.d.ts +47 -0
  44. package/dist/types/ssh/utils.d.ts +16 -0
  45. package/dist/types/task/executor.d.ts +3 -16
  46. package/dist/types/task/render.d.ts +0 -5
  47. package/dist/types/task/renderer.d.ts +13 -0
  48. package/dist/types/task/types.d.ts +16 -0
  49. package/dist/types/task/yield-assembly.d.ts +28 -0
  50. package/dist/types/tiny/text.d.ts +8 -0
  51. package/dist/types/tools/render-utils.d.ts +2 -0
  52. package/dist/types/tools/review.d.ts +6 -4
  53. package/dist/types/tools/ssh.d.ts +1 -1
  54. package/dist/types/tools/todo.d.ts +6 -0
  55. package/dist/types/tools/yield.d.ts +8 -3
  56. package/dist/types/utils/thinking-display.d.ts +4 -0
  57. package/package.json +12 -12
  58. package/src/advisor/__tests__/advisor.test.ts +242 -10
  59. package/src/advisor/__tests__/config.test.ts +173 -0
  60. package/src/advisor/advise-tool.ts +11 -6
  61. package/src/advisor/config.ts +256 -0
  62. package/src/advisor/index.ts +1 -0
  63. package/src/advisor/runtime.ts +12 -2
  64. package/src/advisor/transcript-recorder.ts +25 -2
  65. package/src/advisor/watchdog.ts +57 -31
  66. package/src/autolearn/controller.ts +13 -22
  67. package/src/autoresearch/index.ts +7 -2
  68. package/src/cli/gc-cli.ts +17 -10
  69. package/src/cli/update-cli.ts +254 -0
  70. package/src/collab/guest.ts +43 -7
  71. package/src/collab/host.ts +13 -8
  72. package/src/collab/replication-shrink.ts +111 -0
  73. package/src/config/model-registry.ts +80 -18
  74. package/src/config/provider-globals.ts +25 -0
  75. package/src/config/settings-schema.ts +77 -0
  76. package/src/debug/index.ts +32 -7
  77. package/src/debug/log-viewer.ts +111 -53
  78. package/src/debug/raw-sse.ts +68 -48
  79. package/src/discovery/codex.ts +13 -5
  80. package/src/edit/hashline/diff.ts +57 -4
  81. package/src/eval/__tests__/julia-prelude.test.ts +2 -2
  82. package/src/eval/js/shared/local-module-loader.ts +23 -1
  83. package/src/export/html/template.js +13 -7
  84. package/src/extensibility/extensions/loader.ts +5 -3
  85. package/src/extensibility/extensions/wrapper.ts +9 -3
  86. package/src/extensibility/hooks/loader.ts +3 -3
  87. package/src/extensibility/hooks/tool-wrapper.ts +13 -4
  88. package/src/extensibility/plugins/manager.ts +2 -1
  89. package/src/extensibility/tool-event-input.ts +23 -0
  90. package/src/extensibility/utils.ts +74 -0
  91. package/src/internal-urls/docs-index.generated.txt +1 -1
  92. package/src/mcp/client.ts +3 -1
  93. package/src/mcp/manager.ts +12 -5
  94. package/src/mcp/transports/index.ts +1 -0
  95. package/src/mcp/transports/sse.ts +377 -0
  96. package/src/memories/index.ts +130 -15
  97. package/src/memory-backend/local-backend.ts +5 -3
  98. package/src/modes/components/advisor-config.ts +555 -0
  99. package/src/modes/components/advisor-message.ts +9 -2
  100. package/src/modes/components/agent-hub.ts +9 -4
  101. package/src/modes/components/index.ts +2 -0
  102. package/src/modes/components/model-selector.ts +79 -48
  103. package/src/modes/components/settings-selector.ts +1 -0
  104. package/src/modes/components/status-line/component.ts +150 -5
  105. package/src/modes/components/status-line/segments.ts +46 -21
  106. package/src/modes/components/status-line/types.ts +13 -1
  107. package/src/modes/components/tool-execution.ts +47 -6
  108. package/src/modes/controllers/command-controller.ts +27 -36
  109. package/src/modes/controllers/event-controller.ts +113 -1
  110. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  111. package/src/modes/controllers/input-controller.ts +61 -61
  112. package/src/modes/controllers/selector-controller.ts +100 -9
  113. package/src/modes/interactive-mode.ts +74 -25
  114. package/src/modes/skill-command.ts +116 -0
  115. package/src/modes/types.ts +9 -3
  116. package/src/modes/utils/ui-helpers.ts +41 -23
  117. package/src/prompts/agents/reviewer.md +11 -10
  118. package/src/prompts/goals/goal-todo-context.md +12 -0
  119. package/src/prompts/review-custom-request.md +1 -2
  120. package/src/prompts/review-request.md +1 -2
  121. package/src/prompts/system/interrupted-thinking.md +7 -0
  122. package/src/prompts/system/recap-user.md +9 -0
  123. package/src/prompts/system/subagent-system-prompt.md +8 -5
  124. package/src/prompts/system/subagent-yield-reminder.md +6 -5
  125. package/src/prompts/system/system-prompt.md +0 -1
  126. package/src/prompts/tools/irc.md +2 -2
  127. package/src/prompts/tools/read.md +2 -2
  128. package/src/sdk.ts +28 -24
  129. package/src/session/agent-session.ts +899 -429
  130. package/src/session/indexed-session-storage.ts +86 -13
  131. package/src/session/messages.test.ts +125 -0
  132. package/src/session/messages.ts +172 -9
  133. package/src/session/redis-session-storage.ts +49 -2
  134. package/src/session/session-entries.ts +39 -2
  135. package/src/session/session-history-format.ts +29 -2
  136. package/src/session/session-listing.ts +54 -24
  137. package/src/session/session-loader.ts +66 -3
  138. package/src/session/session-manager.ts +115 -22
  139. package/src/session/session-persistence.ts +95 -1
  140. package/src/session/session-storage.ts +36 -0
  141. package/src/session/session-title-slot.ts +141 -0
  142. package/src/session/sql-session-storage.ts +71 -11
  143. package/src/slash-commands/builtin-registry.ts +23 -24
  144. package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
  145. package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
  146. package/src/ssh/connection-manager.ts +139 -12
  147. package/src/ssh/file-transfer.ts +23 -18
  148. package/src/ssh/ssh-executor.ts +2 -13
  149. package/src/ssh/utils.ts +19 -0
  150. package/src/task/executor.ts +21 -23
  151. package/src/task/render.ts +162 -20
  152. package/src/task/renderer.ts +14 -0
  153. package/src/task/types.ts +17 -0
  154. package/src/task/yield-assembly.ts +207 -0
  155. package/src/tiny/text.ts +23 -0
  156. package/src/tools/ask.ts +55 -4
  157. package/src/tools/render-utils.ts +2 -0
  158. package/src/tools/renderers.ts +8 -2
  159. package/src/tools/review.ts +17 -7
  160. package/src/tools/ssh.ts +8 -4
  161. package/src/tools/todo.ts +17 -1
  162. package/src/tools/yield.ts +140 -31
  163. package/src/utils/thinking-display.ts +15 -0
  164. package/src/prompts/system/autolearn-nudge.md +0 -5
@@ -34,6 +34,7 @@ export interface StatusLinePreviewSettings {
34
34
  separator?: StatusLineSeparatorStyle;
35
35
  sessionAccent?: boolean;
36
36
  transparent?: boolean;
37
+ compactThinkingLevel?: boolean;
37
38
  }
38
39
  export interface SettingsCallbacks {
39
40
  /** Called when any setting value changes */
@@ -14,9 +14,43 @@ export declare class StatusLineComponent implements Component {
14
14
  getEffectiveSettingsForTest(): EffectiveStatusLineSettings;
15
15
  setAutoCompactEnabled(enabled: boolean): void;
16
16
  setSubagentCount(count: number): void;
17
+ /**
18
+ * Compatibility shim for callers predating the simplified subagent badge.
19
+ * The status line now intentionally shows only the active count.
20
+ */
21
+ setSubagentHubHint(_hint: string | undefined): void;
17
22
  /** Active subagent count as currently displayed (collab state mirroring). */
18
23
  get subagentCount(): number;
19
- setSessionStartTime(time: number): void;
24
+ /**
25
+ * Reset the currently-attached session's active-time accumulators so
26
+ * the `time_spent` segment starts from zero. Called from `/clear`,
27
+ * fresh-session, and joined-collab paths; both the completed
28
+ * accumulator and any in-flight window are dropped, so a reset
29
+ * mid-turn ignores the running window (the matching `markActivityEnd`
30
+ * will see an idle meter and no-op).
31
+ */
32
+ resetActiveTime(): void;
33
+ /**
34
+ * Mark the currently-attached session as having started a unit of
35
+ * active processing. Idempotent: a second start while a window is
36
+ * already open is a no-op, so reentrant `agent_start` events (e.g.
37
+ * nested auto-compaction loops, focus-controller mid-turn attach onto
38
+ * an already-running window) do not double-count.
39
+ */
40
+ markActivityStart(): void;
41
+ /**
42
+ * Close the currently-attached session's open active-processing
43
+ * window, folding its elapsed time into the accumulator. Idempotent
44
+ * when the meter is already idle so callers can fire it on every
45
+ * `agent_end` without guarding.
46
+ */
47
+ markActivityEnd(): void;
48
+ /**
49
+ * Snapshot of total active-processing time for the currently-attached
50
+ * session, including any in-flight window. Exposed for the segment
51
+ * context builder; tests assert against this too.
52
+ */
53
+ getActiveMs(): number;
20
54
  setPlanModeStatus(status: {
21
55
  enabled: boolean;
22
56
  paused: boolean;
@@ -41,6 +41,9 @@ export interface StatusLineSettings {
41
41
  /** Drop the theme's `statusLineBg` fill and powerline caps so the bar
42
42
  * inherits the terminal's default background. */
43
43
  transparent?: boolean;
44
+ /** Replace the model-segment icon with the thinking-level glyph and drop the
45
+ * " · <level>" suffix, so the thinking level reads as a single compact icon. */
46
+ compactThinkingLevel?: boolean;
44
47
  }
45
48
  export type EffectiveStatusLineSettings = Required<Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">> & StatusLineSettings;
46
49
  export type RGB = readonly [number, number, number];
@@ -51,6 +54,8 @@ export interface SegmentContext {
51
54
  activeRepo: ActiveRepoContext | null;
52
55
  width: number;
53
56
  options: StatusLineSegmentOptions;
57
+ /** Render the model segment's thinking level as a compact leading glyph. */
58
+ compactThinkingLevel: boolean;
54
59
  planMode: {
55
60
  enabled: boolean;
56
61
  paused: boolean;
@@ -78,7 +83,14 @@ export interface SegmentContext {
78
83
  contextWindow: number;
79
84
  autoCompactEnabled: boolean;
80
85
  subagentCount: number;
81
- sessionStartTime: number;
86
+ /**
87
+ * Active processing time accumulated this session, in ms — the union of
88
+ * every `agent_start`→`agent_end` window plus the currently-streaming
89
+ * window if the agent is running. Idle wall-clock never contributes, so
90
+ * this is what {@link StatusLineSegmentId.time_spent} renders instead of
91
+ * `Date.now() - sessionStart`.
92
+ */
93
+ activeMs: number;
82
94
  git: {
83
95
  branch: string | null;
84
96
  status: {
@@ -30,13 +30,13 @@ export declare class CommandController {
30
30
  handleDropCommand(): Promise<void>;
31
31
  handleForkCommand(): Promise<void>;
32
32
  /**
33
- * `/move` — switch to a fresh empty session in a different directory.
33
+ * `/move` — relocate the current session to a different directory.
34
34
  *
35
35
  * With no `targetPath` (TUI only), opens an autocomplete overlay so the user
36
36
  * can pick or type a directory. With a `targetPath`, resolves it directly.
37
37
  * If the target directory does not exist, the user is asked whether to create
38
- * it. A brand-new empty session is then started in the target directory and
39
- * the current session is left behind (resumable via `/resume`).
38
+ * it. The active session file and artifacts are moved into the target
39
+ * directory's session bucket so `/resume` from that directory can find it.
40
40
  */
41
41
  handleMoveCommand(targetPath?: string): Promise<void>;
42
42
  handleRenameCommand(title: string): Promise<void>;
@@ -25,6 +25,7 @@ export declare class SelectorController {
25
25
  focus: Component;
26
26
  }): void;
27
27
  showSettingsSelector(): void;
28
+ showAdvisorConfigure(): void;
28
29
  showHistorySearch(): void;
29
30
  /**
30
31
  * Show the Extension Control Center dashboard.
@@ -83,6 +83,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
83
83
  chatContainer: TranscriptContainer;
84
84
  pendingMessagesContainer: Container;
85
85
  statusContainer: Container;
86
+ todoReminderContainer: Container;
86
87
  todoContainer: Container;
87
88
  subagentContainer: Container;
88
89
  btwContainer: Container;
@@ -109,11 +110,16 @@ export declare class InteractiveMode implements InteractiveModeContext {
109
110
  loopLimit: LoopLimitRuntime | undefined;
110
111
  todoPhases: TodoPhase[];
111
112
  hideThinkingBlock: boolean;
113
+ /** Whether the visible session has produced thinking content the user can reveal. */
114
+ get hasDisplayableThinkingContent(): boolean;
115
+ /** Record received reasoning content so Ctrl+T can reveal it even when model metadata says thinking is off. */
116
+ noteDisplayableThinkingContent(message: AgentMessage): boolean;
112
117
  /**
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).
118
+ * Effective thinking-block visibility: hidden when the user's setting is on,
119
+ * or while thinking is "off" before the session has actually produced
120
+ * displayable thinking content. Some providers return thinking blocks without
121
+ * advertising reasoning support, so observed content unlocks the visibility
122
+ * toggle.
117
123
  */
118
124
  get effectiveHideThinkingBlock(): boolean;
119
125
  proseOnlyThinking: boolean;
@@ -319,6 +325,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
319
325
  executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto?: boolean): Promise<CompactionOutcome>;
320
326
  openInBrowser(urlOrPath: string): void;
321
327
  showSettingsSelector(): void;
328
+ showAdvisorConfigure(): void;
322
329
  showHistorySearch(): void;
323
330
  showExtensionsDashboard(): void;
324
331
  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 {};
@@ -81,6 +81,7 @@ export interface InteractiveModeContext {
81
81
  chatContainer: TranscriptContainer;
82
82
  pendingMessagesContainer: Container;
83
83
  statusContainer: Container;
84
+ todoReminderContainer: Container;
84
85
  todoContainer: Container;
85
86
  subagentContainer: Container;
86
87
  btwContainer: Container;
@@ -104,7 +105,7 @@ export interface InteractiveModeContext {
104
105
  focusParentSession(): Promise<void>;
105
106
  /** Return the view to the main session (delegates to SessionFocusController.unfocus). */
106
107
  unfocusSession(): Promise<void>;
107
- /** Clear loader, status/pending containers, streaming state, and pending tools. */
108
+ /** Clear loader, transient HUD/pending containers, streaming state, and pending tools. */
108
109
  clearTransientSessionUi(): void;
109
110
  settings: Settings;
110
111
  keybindings: KeybindingsManager;
@@ -142,10 +143,14 @@ export interface InteractiveModeContext {
142
143
  hideThinkingBlock: boolean;
143
144
  /**
144
145
  * 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.
146
+ * thinking level is "off" before the session has produced displayable
147
+ * thinking content.
147
148
  */
148
149
  readonly effectiveHideThinkingBlock: boolean;
150
+ /** Whether this visible session has produced thinking content the user can reveal. */
151
+ readonly hasDisplayableThinkingContent: boolean;
152
+ /** Record a message whose thinking content makes Ctrl+T meaningful even at thinking level "off"; returns true on first observation. */
153
+ noteDisplayableThinkingContent(message: AgentMessage): boolean;
149
154
  proseOnlyThinking: boolean;
150
155
  compactionQueuedMessages: CompactionQueuedMessage[];
151
156
  pendingTools: Map<string, ToolExecutionHandle>;
@@ -316,6 +321,7 @@ export interface InteractiveModeContext {
316
321
  refreshSlashCommandState(cwd?: string): Promise<void>;
317
322
  applyCwdChange(newCwd: string): Promise<void>;
318
323
  showSettingsSelector(): void;
324
+ showAdvisorConfigure(): void;
319
325
  showHistorySearch(): void;
320
326
  showExtensionsDashboard(): void;
321
327
  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.
@@ -240,9 +242,8 @@ export declare class SessionManager {
240
242
  /**
241
243
  * Create a fresh empty session file in the default session directory for
242
244
  * `cwd`, writing only the session header. The returned path can be passed to
243
- * `setSessionFile` / `AgentSession.switchSession` to start a new empty
244
- * session in that directory. Used by `/move` to switch projects without
245
- * dragging the current conversation along.
245
+ * `setSessionFile` / `AgentSession.switchSession` when a caller explicitly
246
+ * needs a brand-new persisted session at a cwd-derived path.
246
247
  */
247
248
  static createEmptySessionFile(cwd: string, storage?: SessionStorage): string;
248
249
  /**
@@ -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()`