@oh-my-pi/pi-coding-agent 16.4.4 → 16.4.6

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 (109) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/cli.js +3799 -3729
  3. package/dist/types/async/job-manager.d.ts +8 -0
  4. package/dist/types/cli/bench-cli.d.ts +1 -7
  5. package/dist/types/cli/usage-cli.d.ts +1 -0
  6. package/dist/types/commands/usage.d.ts +7 -0
  7. package/dist/types/config/settings-schema.d.ts +19 -9
  8. package/dist/types/config/settings.d.ts +3 -2
  9. package/dist/types/discovery/helpers.d.ts +2 -2
  10. package/dist/types/extensibility/extensions/types.d.ts +36 -0
  11. package/dist/types/irc/bus.d.ts +4 -0
  12. package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +1 -0
  13. package/dist/types/modes/components/ask-dialog.d.ts +27 -0
  14. package/dist/types/modes/components/custom-editor.d.ts +3 -8
  15. package/dist/types/modes/components/index.d.ts +2 -1
  16. package/dist/types/modes/components/model-browser.d.ts +100 -0
  17. package/dist/types/modes/components/model-hub.d.ts +52 -0
  18. package/dist/types/modes/components/pause-screen.d.ts +43 -0
  19. package/dist/types/modes/components/session-selector.d.ts +13 -0
  20. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  21. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
  22. package/dist/types/modes/controllers/input-controller.d.ts +2 -0
  23. package/dist/types/modes/interactive-mode.d.ts +3 -0
  24. package/dist/types/modes/queue-input.d.ts +8 -0
  25. package/dist/types/modes/shared.d.ts +1 -1
  26. package/dist/types/modes/types.d.ts +4 -0
  27. package/dist/types/session/agent-session.d.ts +1 -1
  28. package/dist/types/session/agent-storage.d.ts +57 -0
  29. package/dist/types/session/session-context.d.ts +9 -0
  30. package/dist/types/task/executor.d.ts +26 -13
  31. package/dist/types/task/index.d.ts +12 -11
  32. package/dist/types/task/label.d.ts +4 -0
  33. package/dist/types/task/repair-args.d.ts +8 -8
  34. package/dist/types/task/types.d.ts +31 -56
  35. package/dist/types/tools/ask.d.ts +12 -0
  36. package/dist/types/tools/conflict-detect.d.ts +17 -1
  37. package/dist/types/tools/job.d.ts +16 -0
  38. package/package.json +12 -12
  39. package/scripts/build-binary.ts +0 -1
  40. package/scripts/compile-binary.ts +4 -3
  41. package/src/async/job-manager.ts +9 -0
  42. package/src/cli/bench-cli.ts +7 -26
  43. package/src/cli/usage-cli.ts +11 -0
  44. package/src/commands/usage.ts +13 -2
  45. package/src/commit/agentic/tools/analyze-file.ts +2 -3
  46. package/src/config/settings-schema.ts +18 -7
  47. package/src/config/settings.ts +13 -4
  48. package/src/discovery/helpers.ts +3 -4
  49. package/src/extensibility/custom-tools/loader.ts +70 -37
  50. package/src/extensibility/extensions/types.ts +46 -0
  51. package/src/irc/bus.ts +61 -20
  52. package/src/modes/components/__tests__/pause-screen.test.ts +143 -0
  53. package/src/modes/components/advisor-config.ts +32 -22
  54. package/src/modes/components/ask-dialog.ts +888 -0
  55. package/src/modes/components/custom-editor.test.ts +58 -1
  56. package/src/modes/components/custom-editor.ts +42 -11
  57. package/src/modes/components/index.ts +2 -1
  58. package/src/modes/components/model-browser.ts +769 -0
  59. package/src/modes/components/model-hub.ts +2002 -0
  60. package/src/modes/components/pause-screen.ts +208 -0
  61. package/src/modes/components/session-selector.ts +299 -42
  62. package/src/modes/components/tool-execution.ts +2 -0
  63. package/src/modes/components/usage-row.ts +5 -6
  64. package/src/modes/controllers/event-controller.ts +8 -2
  65. package/src/modes/controllers/extension-ui-controller.ts +252 -5
  66. package/src/modes/controllers/input-controller.ts +140 -6
  67. package/src/modes/controllers/selector-controller.ts +160 -97
  68. package/src/modes/controllers/tan-command-controller.ts +1 -1
  69. package/src/modes/controllers/todo-command-controller.ts +1 -2
  70. package/src/modes/interactive-mode.ts +8 -0
  71. package/src/modes/queue-input.ts +132 -0
  72. package/src/modes/shared.ts +1 -1
  73. package/src/modes/theme/theme.ts +3 -3
  74. package/src/modes/types.ts +4 -0
  75. package/src/modes/utils/ui-helpers.ts +50 -24
  76. package/src/prompts/agents/scout.md +0 -1
  77. package/src/prompts/agents/task.md +1 -1
  78. package/src/prompts/system/subagent-system-prompt.md +1 -5
  79. package/src/prompts/system/subagent-yield-reminder.md +10 -0
  80. package/src/prompts/system/task-label.md +23 -0
  81. package/src/prompts/tools/job.md +1 -1
  82. package/src/prompts/tools/task-summary.md +3 -0
  83. package/src/prompts/tools/task.md +17 -18
  84. package/src/session/agent-session.ts +186 -49
  85. package/src/session/agent-storage.ts +330 -3
  86. package/src/session/history-storage.ts +1 -34
  87. package/src/session/session-context.test.ts +73 -0
  88. package/src/session/session-context.ts +43 -26
  89. package/src/slash-commands/builtin-registry.ts +18 -0
  90. package/src/task/agents.ts +2 -0
  91. package/src/task/executor.ts +159 -46
  92. package/src/task/index.ts +377 -239
  93. package/src/task/label.ts +38 -0
  94. package/src/task/render.ts +74 -22
  95. package/src/task/repair-args.ts +20 -31
  96. package/src/task/spawn-policy.test.ts +4 -4
  97. package/src/task/types.ts +46 -66
  98. package/src/tools/ask.ts +233 -40
  99. package/src/tools/conflict-detect.ts +102 -5
  100. package/src/tools/index.ts +1 -0
  101. package/src/tools/irc.ts +20 -11
  102. package/src/tools/job.ts +158 -18
  103. package/src/tools/write.ts +70 -6
  104. package/src/vibe/runtime.ts +1 -1
  105. package/src/web/search/providers/browser-headers.ts +30 -13
  106. package/dist/types/modes/components/model-selector.d.ts +0 -37
  107. package/dist/types/tools/bash-command-fixup.d.ts +0 -3
  108. package/src/modes/components/model-selector.ts +0 -1291
  109. package/src/tools/bash-command-fixup.ts +0 -4
@@ -0,0 +1,143 @@
1
+ import { afterEach, beforeAll, describe, expect, it } from "bun:test";
2
+ import { agentPauseGate } from "@oh-my-pi/pi-agent-core";
3
+ import type { Component } from "@oh-my-pi/pi-tui";
4
+ import { Settings } from "../../../config/settings";
5
+ import { getThemeByName, setThemeInstance } from "../../theme/theme";
6
+ import { PauseScreenComponent, type PauseScreenHost, renderPauseScreen, runPauseScreen } from "../pause-screen";
7
+
8
+ // Strip SGR colors so assertions see visible text only.
9
+ const stripAnsi = (text: string): string => text.replace(/\x1b\[[0-9;]*m/g, "");
10
+
11
+ interface FakeHost {
12
+ host: PauseScreenHost;
13
+ shown: Component[];
14
+ statuses: string[];
15
+ hiddenCount(): number;
16
+ }
17
+
18
+ function makeHost(rows = 24): FakeHost {
19
+ const shown: Component[] = [];
20
+ const statuses: string[] = [];
21
+ let hidden = 0;
22
+ const host: PauseScreenHost = {
23
+ ui: {
24
+ showOverlay(component) {
25
+ shown.push(component);
26
+ return {
27
+ hide: () => {
28
+ hidden++;
29
+ },
30
+ setHidden() {},
31
+ isHidden: () => false,
32
+ };
33
+ },
34
+ setFocus() {},
35
+ requestRender() {},
36
+ terminal: { rows },
37
+ },
38
+ showStatus(message) {
39
+ statuses.push(message);
40
+ },
41
+ };
42
+ return { host, shown, statuses, hiddenCount: () => hidden };
43
+ }
44
+
45
+ describe("pause screen", () => {
46
+ beforeAll(async () => {
47
+ await Settings.init({ inMemory: true });
48
+ const loaded = await getThemeByName("dark");
49
+ if (!loaded) throw new Error("theme unavailable");
50
+ setThemeInstance(loaded);
51
+ });
52
+
53
+ afterEach(() => {
54
+ // The gate is process-global: never leak an engaged pause into other files.
55
+ agentPauseGate.resume();
56
+ });
57
+
58
+ describe("renderPauseScreen", () => {
59
+ it("paints exactly the requested rows with title, explainer, clock, and hint", () => {
60
+ const lines = renderPauseScreen(80, 24, 65_000);
61
+ expect(lines.length).toBe(24);
62
+ const text = lines.map(stripAnsi).join("\n");
63
+ expect(text).toContain("P A U S E D");
64
+ expect(text).toContain("Main agent, subagents, and advisor");
65
+ expect(text).toContain("paused for 1:05");
66
+ expect(text).toContain("esc · enter · space — resume");
67
+ expect(text).toContain("█".repeat(5));
68
+ });
69
+
70
+ it("drops to the compact card on small terminals", () => {
71
+ const lines = renderPauseScreen(40, 10, 3_000);
72
+ expect(lines.length).toBe(10);
73
+ const text = lines.map(stripAnsi).join("\n");
74
+ expect(text).toContain("▌▌ P A U S E D");
75
+ expect(text).toContain("paused for 0:03");
76
+ expect(text).toContain("esc to resume");
77
+ expect(text).not.toContain("█".repeat(5)); // no room for the big glyph
78
+ });
79
+
80
+ it("rolls the clock into hours past 60 minutes", () => {
81
+ const text = renderPauseScreen(80, 24, 3_725_000).map(stripAnsi).join("\n");
82
+ expect(text).toContain("paused for 1:02:05");
83
+ });
84
+
85
+ it("displays the session name when provided in full mode", () => {
86
+ const lines = renderPauseScreen(80, 24, 65_000, "My Awesome Session");
87
+ const text = lines.map(stripAnsi).join("\n");
88
+ expect(text).toContain("My Awesome Session");
89
+ expect(text).toContain("P A U S E D");
90
+ });
91
+
92
+ it("displays the session name when provided in compact mode", () => {
93
+ const lines = renderPauseScreen(40, 10, 3_000, "Compact Session Title");
94
+ const text = lines.map(stripAnsi).join("\n");
95
+ expect(text).toContain("Compact Session Title");
96
+ expect(text).toContain("▌▌ P A U S E D");
97
+ });
98
+ });
99
+
100
+ describe("runPauseScreen", () => {
101
+ it("engages the gate for the screen's lifetime and releases it on escape", async () => {
102
+ const { host, shown, statuses, hiddenCount } = makeHost();
103
+ expect(agentPauseGate.paused).toBe(false);
104
+
105
+ const run = runPauseScreen(host);
106
+ await Bun.sleep(1);
107
+ expect(agentPauseGate.paused).toBe(true);
108
+ expect(shown.length).toBe(1);
109
+
110
+ const component = shown[0];
111
+ expect(component).toBeInstanceOf(PauseScreenComponent);
112
+ if (component instanceof PauseScreenComponent) {
113
+ component.handleInput("\x1b"); // escape → resume
114
+ }
115
+ await run;
116
+
117
+ expect(agentPauseGate.paused).toBe(false);
118
+ expect(hiddenCount()).toBe(1);
119
+ expect(statuses.some(message => message.includes("Resumed after"))).toBe(true);
120
+ });
121
+
122
+ it("treats ctrl+c as resume, never as abort-and-stay-paused", async () => {
123
+ const { host, shown } = makeHost();
124
+ const run = runPauseScreen(host);
125
+ await Bun.sleep(1);
126
+
127
+ const component = shown[0];
128
+ if (component instanceof PauseScreenComponent) {
129
+ component.handleInput("\x03"); // ctrl+c
130
+ }
131
+ await run;
132
+ expect(agentPauseGate.paused).toBe(false);
133
+ });
134
+
135
+ it("is a no-op when the gate is already engaged elsewhere", async () => {
136
+ agentPauseGate.pause();
137
+ const { host, shown } = makeHost();
138
+ await runPauseScreen(host); // must resolve immediately, not park
139
+ expect(shown.length).toBe(0);
140
+ expect(agentPauseGate.paused).toBe(true); // foreign pause not stolen
141
+ });
142
+ });
143
+ });
@@ -40,7 +40,7 @@ import { formatModelSelectorValue } from "../../config/model-resolver";
40
40
  import type { Settings } from "../../config/settings";
41
41
  import { getSelectListTheme, theme } from "../theme/theme";
42
42
  import { HookEditorComponent } from "./hook-editor";
43
- import { ModelSelectorComponent } from "./model-selector";
43
+ import { buildBrowserItems, ModelBrowser, sortModelItems } from "./model-browser";
44
44
  import {
45
45
  bottomBorder,
46
46
  divider,
@@ -461,27 +461,37 @@ export class AdvisorConfigOverlayComponent implements Component {
461
461
  }
462
462
 
463
463
  #showModelPicker(index: number): void {
464
- const picker = new ModelSelectorComponent(
465
- this.#tui,
466
- undefined,
467
- this.#settings,
468
- this.#modelRegistry,
469
- this.#scopedModels,
470
- (model, _role, _thinking, selector) => {
471
- const base = selector ?? `${model.provider}/${model.id}`;
472
- const efforts = getSupportedEfforts(model);
473
- if (efforts.length === 0) {
474
- this.#doc.advisors[index].model = base;
475
- this.#dirty = true;
476
- this.#showDetail(index);
477
- } else {
478
- this.#showThinkingPicker(index, base, efforts);
479
- }
480
- },
481
- () => this.#showDetail(index),
482
- { directSelect: true, pickerHint: "Pick this advisor's model · Enter / click select · Esc back" },
483
- );
484
- this.#setScreen("model", picker, "Type to search · Enter / click pick model · Esc back");
464
+ const storage = this.#settings.getStorage();
465
+ const mruOrder = storage?.getModelUsageOrder() ?? [];
466
+ let models: ReadonlyArray<Model>;
467
+ if (this.#scopedModels.length > 0) {
468
+ models = this.#scopedModels.map(scoped => scoped.model);
469
+ } else {
470
+ try {
471
+ models = this.#modelRegistry.getAvailable();
472
+ } catch {
473
+ models = [];
474
+ }
475
+ }
476
+ const items = buildBrowserItems(models);
477
+ sortModelItems(items, { mruOrder });
478
+
479
+ const picker = new ModelBrowser(this.#settings, {});
480
+ picker.setMruOrder(mruOrder);
481
+ picker.setPerfStats(storage?.getModelPerf() ?? new Map());
482
+ picker.setItems(items);
483
+ picker.onActivate = item => {
484
+ const efforts = getSupportedEfforts(item.model);
485
+ if (efforts.length === 0) {
486
+ this.#doc.advisors[index].model = item.selector;
487
+ this.#dirty = true;
488
+ this.#showDetail(index);
489
+ } else {
490
+ this.#showThinkingPicker(index, item.selector, efforts);
491
+ }
492
+ };
493
+ picker.onCancel = () => this.#showDetail(index);
494
+ this.#setScreen("model", picker, "Type to search · Enter / click twice picks · Esc back");
485
495
  }
486
496
 
487
497
  #showThinkingPicker(index: number, selector: string, efforts: readonly string[]): void {