@oh-my-pi/pi-coding-agent 15.7.2 → 15.7.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 (160) hide show
  1. package/CHANGELOG.md +75 -6
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/extension-flags.d.ts +36 -0
  4. package/dist/types/config/config-file.d.ts +4 -0
  5. package/dist/types/config/file-lock.d.ts +23 -0
  6. package/dist/types/config/keybindings.d.ts +2 -1
  7. package/dist/types/config/model-registry.d.ts +6 -0
  8. package/dist/types/config/settings-schema.d.ts +88 -65
  9. package/dist/types/edit/hashline/diff.d.ts +3 -3
  10. package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
  11. package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
  12. package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
  13. package/dist/types/eval/agent-bridge.d.ts +25 -0
  14. package/dist/types/eval/backend.d.ts +17 -2
  15. package/dist/types/eval/budget-bridge.d.ts +29 -0
  16. package/dist/types/eval/idle-timeout.d.ts +28 -0
  17. package/dist/types/eval/js/executor.d.ts +8 -0
  18. package/dist/types/eval/js/tool-bridge.d.ts +2 -1
  19. package/dist/types/eval/py/executor.d.ts +13 -0
  20. package/dist/types/exec/bash-executor.d.ts +1 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  22. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  23. package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
  24. package/dist/types/extensibility/plugins/manager.d.ts +12 -1
  25. package/dist/types/extensibility/shared-events.d.ts +2 -2
  26. package/dist/types/memory-backend/index.d.ts +1 -1
  27. package/dist/types/memory-backend/resolve.d.ts +1 -1
  28. package/dist/types/memory-backend/types.d.ts +3 -3
  29. package/dist/types/mnemopi/backend.d.ts +4 -0
  30. package/dist/types/mnemopi/config.d.ts +29 -0
  31. package/dist/types/mnemopi/state.d.ts +72 -0
  32. package/dist/types/modes/components/custom-editor.d.ts +2 -2
  33. package/dist/types/modes/components/omfg-panel.d.ts +19 -0
  34. package/dist/types/modes/controllers/command-controller.d.ts +7 -0
  35. package/dist/types/modes/controllers/input-controller.d.ts +1 -3
  36. package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
  37. package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
  38. package/dist/types/modes/gradient-highlight.d.ts +5 -1
  39. package/dist/types/modes/interactive-mode.d.ts +7 -3
  40. package/dist/types/modes/magic-keywords.d.ts +14 -0
  41. package/dist/types/modes/markdown-prose.d.ts +27 -0
  42. package/dist/types/modes/orchestrate.d.ts +7 -2
  43. package/dist/types/modes/shared.d.ts +1 -1
  44. package/dist/types/modes/turn-budget.d.ts +18 -0
  45. package/dist/types/modes/types.d.ts +7 -3
  46. package/dist/types/modes/ultrathink.d.ts +7 -2
  47. package/dist/types/modes/workflow.d.ts +15 -0
  48. package/dist/types/sdk.d.ts +13 -3
  49. package/dist/types/session/agent-session.d.ts +40 -17
  50. package/dist/types/session/session-manager.d.ts +18 -0
  51. package/dist/types/session/session-storage.d.ts +6 -0
  52. package/dist/types/session/shake-types.d.ts +24 -0
  53. package/dist/types/task/executor.d.ts +2 -2
  54. package/dist/types/tiny/models.d.ts +15 -1
  55. package/dist/types/tiny/title-protocol.d.ts +4 -0
  56. package/dist/types/tools/index.d.ts +19 -3
  57. package/dist/types/tools/memory-edit.d.ts +1 -1
  58. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  59. package/package.json +10 -10
  60. package/src/autoresearch/tools/run-experiment.ts +45 -113
  61. package/src/cli/args.ts +39 -16
  62. package/src/cli/extension-flags.ts +48 -0
  63. package/src/cli/plugin-cli.ts +11 -2
  64. package/src/config/config-file.ts +98 -13
  65. package/src/config/file-lock.ts +60 -17
  66. package/src/config/keybindings.ts +78 -27
  67. package/src/config/model-registry.ts +7 -1
  68. package/src/config/settings-schema.ts +94 -67
  69. package/src/config/settings.ts +12 -0
  70. package/src/edit/hashline/diff.ts +81 -24
  71. package/src/edit/renderer.ts +16 -12
  72. package/src/eval/__tests__/agent-bridge.test.ts +433 -0
  73. package/src/eval/__tests__/budget-bridge.test.ts +69 -0
  74. package/src/eval/__tests__/idle-timeout.test.ts +66 -0
  75. package/src/eval/__tests__/shared-executors.test.ts +21 -0
  76. package/src/eval/agent-bridge.ts +295 -0
  77. package/src/eval/backend.ts +17 -2
  78. package/src/eval/budget-bridge.ts +48 -0
  79. package/src/eval/idle-timeout.ts +80 -0
  80. package/src/eval/js/executor.ts +35 -7
  81. package/src/eval/js/index.ts +2 -1
  82. package/src/eval/js/shared/prelude.txt +85 -1
  83. package/src/eval/js/tool-bridge.ts +9 -0
  84. package/src/eval/py/executor.ts +41 -14
  85. package/src/eval/py/index.ts +2 -1
  86. package/src/eval/py/prelude.py +132 -1
  87. package/src/exec/bash-executor.ts +2 -3
  88. package/src/extensibility/custom-tools/types.ts +2 -2
  89. package/src/extensibility/extensions/runner.ts +12 -2
  90. package/src/extensibility/plugins/git-url.ts +90 -4
  91. package/src/extensibility/plugins/manager.ts +103 -7
  92. package/src/extensibility/shared-events.ts +2 -2
  93. package/src/internal-urls/docs-index.generated.ts +88 -88
  94. package/src/main.ts +44 -55
  95. package/src/memory-backend/index.ts +1 -1
  96. package/src/memory-backend/resolve.ts +3 -3
  97. package/src/memory-backend/types.ts +3 -3
  98. package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
  99. package/src/{mnemosyne → mnemopi}/config.ts +36 -36
  100. package/src/{mnemosyne → mnemopi}/state.ts +67 -67
  101. package/src/modes/components/agent-dashboard.ts +6 -6
  102. package/src/modes/components/custom-editor.ts +4 -11
  103. package/src/modes/components/extensions/state-manager.ts +3 -4
  104. package/src/modes/components/footer.ts +8 -9
  105. package/src/modes/components/hook-selector.ts +86 -20
  106. package/src/modes/components/oauth-selector.ts +93 -21
  107. package/src/modes/components/omfg-panel.ts +141 -0
  108. package/src/modes/components/settings-defs.ts +2 -2
  109. package/src/modes/components/tips.txt +2 -1
  110. package/src/modes/components/tool-execution.ts +38 -19
  111. package/src/modes/components/tree-selector.ts +4 -3
  112. package/src/modes/components/user-message-selector.ts +94 -19
  113. package/src/modes/components/user-message.ts +8 -1
  114. package/src/modes/controllers/command-controller.ts +57 -0
  115. package/src/modes/controllers/event-controller.ts +60 -2
  116. package/src/modes/controllers/input-controller.ts +14 -11
  117. package/src/modes/controllers/omfg-controller.ts +283 -0
  118. package/src/modes/controllers/omfg-rule.ts +647 -0
  119. package/src/modes/controllers/selector-controller.ts +1 -0
  120. package/src/modes/gradient-highlight.ts +23 -6
  121. package/src/modes/interactive-mode.ts +41 -7
  122. package/src/modes/magic-keywords.ts +20 -0
  123. package/src/modes/markdown-prose.ts +247 -0
  124. package/src/modes/orchestrate.ts +17 -11
  125. package/src/modes/shared.ts +3 -11
  126. package/src/modes/turn-budget.ts +31 -0
  127. package/src/modes/types.ts +7 -1
  128. package/src/modes/ultrathink.ts +16 -10
  129. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  130. package/src/modes/workflow.ts +42 -0
  131. package/src/prompts/system/omfg-user.md +51 -0
  132. package/src/prompts/system/system-prompt.md +1 -0
  133. package/src/prompts/system/workflow-notice.md +70 -0
  134. package/src/prompts/tools/eval.md +13 -1
  135. package/src/prompts/tools/memory-edit.md +1 -1
  136. package/src/sdk.ts +63 -33
  137. package/src/session/agent-session.ts +373 -56
  138. package/src/session/session-manager.ts +32 -0
  139. package/src/session/session-storage.ts +68 -8
  140. package/src/session/shake-types.ts +44 -0
  141. package/src/slash-commands/builtin-registry.ts +41 -16
  142. package/src/task/executor.ts +3 -3
  143. package/src/task/index.ts +6 -6
  144. package/src/tiny/models.ts +30 -2
  145. package/src/tiny/title-protocol.ts +11 -1
  146. package/src/tiny/worker.ts +19 -7
  147. package/src/tools/eval.ts +202 -26
  148. package/src/tools/grouped-file-output.ts +9 -2
  149. package/src/tools/index.ts +17 -5
  150. package/src/tools/memory-edit.ts +4 -4
  151. package/src/tools/memory-recall.ts +5 -5
  152. package/src/tools/memory-reflect.ts +5 -5
  153. package/src/tools/memory-retain.ts +4 -4
  154. package/src/tools/render-utils.ts +2 -1
  155. package/src/tools/search.ts +480 -76
  156. package/dist/types/mnemosyne/backend.d.ts +0 -4
  157. package/dist/types/mnemosyne/config.d.ts +0 -29
  158. package/dist/types/mnemosyne/state.d.ts +0 -72
  159. /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
  160. /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
@@ -0,0 +1,283 @@
1
+ import * as path from "node:path";
2
+ import { CONFIG_DIR_NAME, prompt } from "@oh-my-pi/pi-utils";
3
+ import type { Rule } from "../../capability/rule";
4
+ import omfgUserPrompt from "../../prompts/system/omfg-user.md" with { type: "text" };
5
+ import { shortenPath } from "../../tools/render-utils";
6
+ import { OmfgPanelComponent } from "../components/omfg-panel";
7
+ import type { InteractiveModeContext } from "../types";
8
+ import {
9
+ buildOmfgRuleForPath,
10
+ extractGeneratedRuleJson,
11
+ type OmfgRuleSourceLevel,
12
+ type ParsedGeneratedRule,
13
+ parseGeneratedRule,
14
+ validateParsedRuleAgainstAssistantHistory,
15
+ } from "./omfg-rule";
16
+
17
+ interface OmfgRequest {
18
+ component: OmfgPanelComponent;
19
+ abortController: AbortController;
20
+ complaint: string;
21
+ }
22
+
23
+ interface OmfgCandidate extends ParsedGeneratedRule {
24
+ validated: boolean;
25
+ }
26
+
27
+ interface GenerateCandidateOptions {
28
+ initialFeedback?: string;
29
+ previousRule?: string;
30
+ }
31
+
32
+ type SaveCandidateResult = { kind: "saved" | "aborted" | "rejected" } | { kind: "amend"; feedback: string };
33
+
34
+ const MAX_ATTEMPTS = 3;
35
+ const PROJECT_OPTION = "This project (.omp/rules)";
36
+ const GLOBAL_OPTION = "Global — all projects (~/.omp/agent/rules)";
37
+ const AMEND_OPTION = "Amend with feedback…";
38
+
39
+ export class OmfgController {
40
+ #activeRequest: OmfgRequest | undefined;
41
+
42
+ constructor(private readonly ctx: InteractiveModeContext) {}
43
+
44
+ hasActiveRequest(): boolean {
45
+ return this.#activeRequest !== undefined;
46
+ }
47
+
48
+ handleEscape(): boolean {
49
+ if (!this.#activeRequest) return false;
50
+ this.#closeActiveRequest({ abort: this.#activeRequest.abortController.signal.aborted === false });
51
+ return true;
52
+ }
53
+
54
+ dispose(): void {
55
+ this.#closeActiveRequest({ abort: true });
56
+ }
57
+
58
+ async start(complaint: string): Promise<void> {
59
+ const trimmedComplaint = complaint.trim();
60
+ if (!trimmedComplaint) {
61
+ this.ctx.showStatus("Usage: /omfg <complaint>");
62
+ return;
63
+ }
64
+
65
+ const model = this.ctx.session.model;
66
+ if (!model) {
67
+ this.ctx.showError("No active model available for /omfg.");
68
+ return;
69
+ }
70
+
71
+ this.#closeActiveRequest({ abort: true });
72
+
73
+ const request: OmfgRequest = {
74
+ component: new OmfgPanelComponent({ complaint: trimmedComplaint, tui: this.ctx.ui }),
75
+ abortController: new AbortController(),
76
+ complaint: trimmedComplaint,
77
+ };
78
+ this.ctx.omfgContainer.clear();
79
+ this.ctx.omfgContainer.addChild(request.component);
80
+ this.ctx.ui.requestRender();
81
+ this.#activeRequest = request;
82
+ void this.#runRequest(request);
83
+ }
84
+
85
+ async #runRequest(request: OmfgRequest): Promise<void> {
86
+ try {
87
+ let candidate = await this.#generateCandidate(request);
88
+ for (;;) {
89
+ if (!this.#isActiveRequest(request)) return;
90
+ if (!candidate) {
91
+ request.component.markError("The model did not return a valid TTSR rule.");
92
+ return;
93
+ }
94
+
95
+ if (!candidate.validated) {
96
+ request.component.setStatus("confirming", "Couldn't confirm a conversation match.");
97
+ const shouldSave = await this.ctx.showHookConfirm(
98
+ "Validation",
99
+ "Couldn't confirm this rule matches the conversation. Save anyway?",
100
+ );
101
+ if (!this.#isActiveRequest(request)) return;
102
+ if (!shouldSave) {
103
+ request.component.markRejected();
104
+ return;
105
+ }
106
+ }
107
+
108
+ const saveResult = await this.#saveCandidate(request, candidate);
109
+ if (!this.#isActiveRequest(request)) return;
110
+ if (saveResult.kind !== "amend") {
111
+ return;
112
+ }
113
+
114
+ candidate = await this.#generateCandidate(request, {
115
+ initialFeedback: `User requested this amendment before saving:\n${saveResult.feedback}`,
116
+ previousRule: candidate.fileContent,
117
+ });
118
+ }
119
+ } catch (error) {
120
+ if (!this.#isActiveRequest(request)) {
121
+ return;
122
+ }
123
+ if (request.abortController.signal.aborted) {
124
+ request.component.markAborted();
125
+ return;
126
+ }
127
+ request.component.markError(error instanceof Error ? error.message : String(error));
128
+ }
129
+ }
130
+
131
+ async #generateCandidate(
132
+ request: OmfgRequest,
133
+ options: GenerateCandidateOptions = {},
134
+ ): Promise<OmfgCandidate | undefined> {
135
+ const failedAttempts = options.initialFeedback ? [options.initialFeedback] : [];
136
+ let previousRule = options.previousRule;
137
+ let lastCandidate: ParsedGeneratedRule | undefined;
138
+
139
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
140
+ if (this.#shouldStop(request)) return undefined;
141
+ request.component.setRule("");
142
+ request.component.setStatus("generating", `Attempt ${attempt}/${MAX_ATTEMPTS} · generating…`);
143
+ const promptText = prompt.render(omfgUserPrompt, {
144
+ complaint: request.complaint,
145
+ feedback: failedAttempts.length > 0 ? failedAttempts.join("\n\n") : undefined,
146
+ previousRule,
147
+ });
148
+ const { replyText } = await this.ctx.session.runEphemeralTurn({
149
+ promptText,
150
+ dedupeReply: false,
151
+ onTextDelta: delta => {
152
+ if (this.#isActiveRequest(request)) {
153
+ request.component.appendDraft(delta);
154
+ }
155
+ },
156
+ signal: request.abortController.signal,
157
+ });
158
+ if (this.#shouldStop(request)) return undefined;
159
+
160
+ const parsed = parseGeneratedRule(replyText);
161
+ if ("error" in parsed) {
162
+ const failedRule = extractGeneratedRuleJson(replyText) ?? replyText.trim();
163
+ failedAttempts.push(
164
+ `Attempt ${attempt} failed: invalid rule (${parsed.error}).\nFailed candidate:\n${failedRule}`,
165
+ );
166
+ previousRule = failedRule;
167
+ request.component.setStatus("validating", `Attempt ${attempt}/${MAX_ATTEMPTS} · ${parsed.error}`);
168
+ continue;
169
+ }
170
+
171
+ request.component.setRule(parsed.fileContent);
172
+ request.component.setStatus("validating", `Attempt ${attempt}/${MAX_ATTEMPTS} · validating…`);
173
+ const validated = validateParsedRuleAgainstAssistantHistory(parsed, this.ctx.session.messages);
174
+ if (validated.repairedCondition) {
175
+ request.component.setRule(validated.candidate.fileContent);
176
+ }
177
+ if (validated.validation.matched) {
178
+ return { ...validated.candidate, validated: true };
179
+ }
180
+
181
+ lastCandidate = validated.candidate;
182
+ const failure =
183
+ validated.validation.feedback ?? "The rule condition did not match any earlier assistant output.";
184
+ failedAttempts.push(
185
+ `Attempt ${attempt} failed validation:\n${failure}\nFailed candidate:\n${validated.candidate.fileContent}`,
186
+ );
187
+ previousRule = validated.candidate.fileContent;
188
+ }
189
+
190
+ return lastCandidate ? { ...lastCandidate, validated: false } : undefined;
191
+ }
192
+
193
+ async #saveCandidate(request: OmfgRequest, candidate: OmfgCandidate): Promise<SaveCandidateResult> {
194
+ if (this.#shouldStop(request)) return { kind: "aborted" };
195
+ request.component.setStatus("saving", "Choose where to save or amend the TTSR rule…");
196
+ const location = await this.ctx.showHookSelector("Save TTSR rule where?", [
197
+ PROJECT_OPTION,
198
+ GLOBAL_OPTION,
199
+ AMEND_OPTION,
200
+ ]);
201
+ if (!this.#isActiveRequest(request)) return { kind: "aborted" };
202
+ if (!location) {
203
+ request.component.markAborted();
204
+ this.#closeActiveRequest({ abort: false });
205
+ return { kind: "aborted" };
206
+ }
207
+
208
+ if (location === AMEND_OPTION) {
209
+ request.component.setStatus("confirming", "Describe how to amend the rule…");
210
+ const amendment = await this.ctx.showHookInput(
211
+ "Amend TTSR rule",
212
+ "e.g. Make it specific to Ruby string eval in tool:write(*.rb)",
213
+ );
214
+ if (!this.#isActiveRequest(request)) return { kind: "aborted" };
215
+ const feedback = amendment?.trim();
216
+ if (!feedback) {
217
+ request.component.markAborted();
218
+ this.#closeActiveRequest({ abort: false });
219
+ return { kind: "aborted" };
220
+ }
221
+ return { kind: "amend", feedback };
222
+ }
223
+
224
+ const target = this.#resolveTarget(location, candidate.rule.name);
225
+ if (await Bun.file(target.filePath).exists()) {
226
+ const shouldOverwrite = await this.ctx.showHookConfirm(
227
+ "Overwrite TTSR rule?",
228
+ `${shortenPath(target.filePath)} already exists. Overwrite it?`,
229
+ );
230
+ if (!this.#isActiveRequest(request)) return { kind: "aborted" };
231
+ if (!shouldOverwrite) {
232
+ request.component.markRejected();
233
+ return { kind: "rejected" };
234
+ }
235
+ }
236
+
237
+ request.component.setStatus("saving", `Saving ${candidate.rule.name}…`);
238
+ await Bun.write(target.filePath, candidate.fileContent);
239
+ if (!this.#isActiveRequest(request)) return { kind: "aborted" };
240
+
241
+ const savedRule = buildOmfgRuleForPath(candidate.rule.name, candidate.fileContent, target.filePath, target.level);
242
+ this.#registerLive(savedRule);
243
+ request.component.markSaved(shortenPath(target.filePath));
244
+ return { kind: "saved" };
245
+ }
246
+
247
+ #resolveTarget(location: string, ruleName: string): { filePath: string; level: OmfgRuleSourceLevel } {
248
+ if (location === GLOBAL_OPTION) {
249
+ return {
250
+ filePath: path.join(this.ctx.settings.getAgentDir(), "rules", `${ruleName}.md`),
251
+ level: "user",
252
+ };
253
+ }
254
+ return {
255
+ filePath: path.join(this.ctx.sessionManager.getCwd(), CONFIG_DIR_NAME, "rules", `${ruleName}.md`),
256
+ level: "project",
257
+ };
258
+ }
259
+
260
+ #registerLive(rule: Rule): void {
261
+ this.ctx.session.ttsrManager?.addRule(rule);
262
+ }
263
+
264
+ #closeActiveRequest(options: { abort: boolean }): void {
265
+ const request = this.#activeRequest;
266
+ if (!request) return;
267
+ this.#activeRequest = undefined;
268
+ if (options.abort) {
269
+ request.abortController.abort();
270
+ }
271
+ request.component.close();
272
+ this.ctx.omfgContainer.clear();
273
+ this.ctx.ui.requestRender();
274
+ }
275
+
276
+ #isActiveRequest(request: OmfgRequest): boolean {
277
+ return this.#activeRequest === request;
278
+ }
279
+
280
+ #shouldStop(request: OmfgRequest): boolean {
281
+ return !this.#isActiveRequest(request) || request.abortController.signal.aborted;
282
+ }
283
+ }