@oh-my-pi/pi-coding-agent 4.6.0 → 4.8.0

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.
@@ -3,6 +3,7 @@ import type { AgentSessionEvent } from "../../../core/agent-session";
3
3
  import { detectNotificationProtocol, isNotificationSuppressed, sendNotification } from "../../../core/terminal-notify";
4
4
  import { AssistantMessageComponent } from "../components/assistant-message";
5
5
  import { ReadToolGroupComponent } from "../components/read-tool-group";
6
+ import { TodoReminderComponent } from "../components/todo-reminder";
6
7
  import { ToolExecutionComponent } from "../components/tool-execution";
7
8
  import { TtsrNotificationComponent } from "../components/ttsr-notification";
8
9
  import { getSymbolTheme, theme } from "../theme/theme";
@@ -374,6 +375,13 @@ export class EventController {
374
375
  this.ctx.ui.requestRender();
375
376
  break;
376
377
  }
378
+
379
+ case "todo_reminder": {
380
+ const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
381
+ this.ctx.chatContainer.addChild(component);
382
+ this.ctx.ui.requestRender();
383
+ break;
384
+ }
377
385
  }
378
386
  }
379
387
 
@@ -25,7 +25,7 @@ export class ExtensionUiController {
25
25
  async initHooksAndCustomTools(): Promise<void> {
26
26
  // Create and set hook & tool UI context
27
27
  const uiContext: ExtensionUIContext = {
28
- select: (title, options, _dialogOptions) => this.showHookSelector(title, options),
28
+ select: (title, options, dialogOptions) => this.showHookSelector(title, options, dialogOptions?.initialIndex),
29
29
  confirm: (title, message, _dialogOptions) => this.showHookConfirm(title, message),
30
30
  input: (title, placeholder, _dialogOptions) => this.showHookInput(title, placeholder),
31
31
  notify: (message, type) => this.showHookNotify(message, type),
@@ -141,6 +141,7 @@ export class ExtensionUiController {
141
141
  this.ctx.chatContainer.addChild(
142
142
  new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
143
143
  );
144
+ await this.ctx.reloadTodos();
144
145
  this.ctx.ui.requestRender();
145
146
 
146
147
  return { cancelled: false };
@@ -154,6 +155,7 @@ export class ExtensionUiController {
154
155
  // Update UI
155
156
  this.ctx.chatContainer.clear();
156
157
  this.ctx.renderInitialMessages();
158
+ await this.ctx.reloadTodos();
157
159
  this.ctx.editor.setText(result.selectedText);
158
160
  this.ctx.showStatus("Branched to new session");
159
161
 
@@ -168,6 +170,7 @@ export class ExtensionUiController {
168
170
  // Update UI
169
171
  this.ctx.chatContainer.clear();
170
172
  this.ctx.renderInitialMessages();
173
+ await this.ctx.reloadTodos();
171
174
  if (result.editorText) {
172
175
  this.ctx.editor.setText(result.editorText);
173
176
  }
@@ -289,6 +292,7 @@ export class ExtensionUiController {
289
292
  this.ctx.chatContainer.addChild(
290
293
  new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
291
294
  );
295
+ await this.ctx.reloadTodos();
292
296
  this.ctx.ui.requestRender();
293
297
 
294
298
  return { cancelled: false };
@@ -305,6 +309,7 @@ export class ExtensionUiController {
305
309
  // Update UI
306
310
  this.ctx.chatContainer.clear();
307
311
  this.ctx.renderInitialMessages();
312
+ await this.ctx.reloadTodos();
308
313
  this.ctx.editor.setText(result.selectedText);
309
314
  this.ctx.showStatus("Branched to new session");
310
315
 
@@ -322,6 +327,7 @@ export class ExtensionUiController {
322
327
  // Update UI
323
328
  this.ctx.chatContainer.clear();
324
329
  this.ctx.renderInitialMessages();
330
+ await this.ctx.reloadTodos();
325
331
  if (result.editorText) {
326
332
  this.ctx.editor.setText(result.editorText);
327
333
  }
@@ -425,7 +431,7 @@ export class ExtensionUiController {
425
431
  /**
426
432
  * Show a selector for hooks.
427
433
  */
428
- showHookSelector(title: string, options: string[]): Promise<string | undefined> {
434
+ showHookSelector(title: string, options: string[], initialIndex?: number): Promise<string | undefined> {
429
435
  return new Promise((resolve) => {
430
436
  this.ctx.hookSelector = new HookSelectorComponent(
431
437
  title,
@@ -438,6 +444,7 @@ export class ExtensionUiController {
438
444
  this.hideHookSelector();
439
445
  resolve(undefined);
440
446
  },
447
+ { initialIndex },
441
448
  );
442
449
 
443
450
  this.ctx.editorContainer.clear();
@@ -309,7 +309,7 @@ export class InputController {
309
309
  .then(async (title) => {
310
310
  if (title) {
311
311
  await this.ctx.sessionManager.setSessionTitle(title);
312
- setTerminalTitle(`omp: ${title}`);
312
+ setTerminalTitle(`π: ${title}`);
313
313
  }
314
314
  })
315
315
  .catch(() => {});
@@ -428,6 +428,7 @@ export class SelectorController {
428
428
  // Update UI
429
429
  this.ctx.chatContainer.clear();
430
430
  this.ctx.renderInitialMessages();
431
+ await this.ctx.reloadTodos();
431
432
  if (result.editorText) {
432
433
  this.ctx.editor.setText(result.editorText);
433
434
  }
@@ -500,6 +501,7 @@ export class SelectorController {
500
501
  // Clear and re-render the chat
501
502
  this.ctx.chatContainer.clear();
502
503
  this.ctx.renderInitialMessages();
504
+ await this.ctx.reloadTodos();
503
505
  this.ctx.showStatus("Resumed session");
504
506
  }
505
507
 
@@ -446,7 +446,7 @@ export class InteractiveMode implements InteractiveModeContext {
446
446
 
447
447
  if (!this.todoExpanded && visibleTodos.length < this.todoItems.length) {
448
448
  const remaining = this.todoItems.length - visibleTodos.length;
449
- lines.push(theme.fg("muted", `${indent}${hook} +${remaining} more (Ctrl+T to expand)`));
449
+ lines.push(theme.fg("muted", `${indent} ${hook} +${remaining} more (Ctrl+T to expand)`));
450
450
  }
451
451
 
452
452
  this.todoContainer.addChild(new Text(lines.join("\n"), 1, 0));
@@ -757,6 +757,11 @@ export class InteractiveMode implements InteractiveModeContext {
757
757
  this.ui.requestRender();
758
758
  }
759
759
 
760
+ async reloadTodos(): Promise<void> {
761
+ await this.loadTodoList();
762
+ this.ui.requestRender();
763
+ }
764
+
760
765
  openExternalEditor(): void {
761
766
  this.inputController.openExternalEditor();
762
767
  }
@@ -810,8 +815,8 @@ export class InteractiveMode implements InteractiveModeContext {
810
815
  this.extensionUiController.setHookStatus(key, text);
811
816
  }
812
817
 
813
- showHookSelector(title: string, options: string[]): Promise<string | undefined> {
814
- return this.extensionUiController.showHookSelector(title, options);
818
+ showHookSelector(title: string, options: string[], initialIndex?: number): Promise<string | undefined> {
819
+ return this.extensionUiController.showHookSelector(title, options, initialIndex);
815
820
  }
816
821
 
817
822
  hideHookSelector(): void {
@@ -124,6 +124,7 @@ export interface InteractiveModeContext {
124
124
  updateEditorBorderColor(): void;
125
125
  rebuildChatFromMessages(): void;
126
126
  setTodos(todos: TodoItem[]): void;
127
+ reloadTodos(): Promise<void>;
127
128
  toggleTodoExpansion(): void;
128
129
 
129
130
  // Command handling
@@ -184,7 +185,7 @@ export interface InteractiveModeContext {
184
185
  ): Promise<void>;
185
186
  setHookWidget(key: string, content: unknown): void;
186
187
  setHookStatus(key: string, text: string | undefined): void;
187
- showHookSelector(title: string, options: string[]): Promise<string | undefined>;
188
+ showHookSelector(title: string, options: string[], initialIndex?: number): Promise<string | undefined>;
188
189
  hideHookSelector(): void;
189
190
  showHookInput(title: string, placeholder?: string): Promise<string | undefined>;
190
191
  hideHookInput(): void;
@@ -13,11 +13,25 @@ Tips:
13
13
  - 2-5 concise, distinct options
14
14
  - Users can always select "Other" for custom input
15
15
 
16
+ **Do NOT include an "Other" option in your options array.** The UI automatically adds "Other (type your own)" to every question. Adding your own creates duplicate "Other" options.
17
+
16
18
  <example>
17
19
  question: "Which authentication method should this API use?"
18
20
  options: [{"label": "JWT (Recommended)"}, {"label": "OAuth2"}, {"label": "Session cookies"}]
19
21
  </example>
20
22
 
23
+ ## Multi-part questions
24
+
25
+ When you have multiple related questions, use the `questions` array instead of asking one at a time. Each question has its own id, options, and optional `multi` flag.
26
+
27
+ <example>
28
+ questions: [
29
+ {"id": "auth", "question": "Which auth method?", "options": [{"label": "JWT"}, {"label": "OAuth2"}]},
30
+ {"id": "cache", "question": "Enable caching?", "options": [{"label": "Yes"}, {"label": "No"}]},
31
+ {"id": "features", "question": "Which features to include?", "options": [{"label": "Logging"}, {"label": "Metrics"}, {"label": "Tracing"}], "multi": true}
32
+ ]
33
+ </example>
34
+
21
35
  ## Critical: Resolve before asking
22
36
 
23
37
  **Exhaust all other options before asking.** Questions interrupt user flow.