@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.2

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 (235) hide show
  1. package/CHANGELOG.md +316 -1
  2. package/package.json +86 -24
  3. package/scripts/format-prompts.ts +2 -2
  4. package/src/autoresearch/apply-contract-to-state.ts +24 -0
  5. package/src/autoresearch/contract.ts +0 -44
  6. package/src/autoresearch/dashboard.ts +1 -2
  7. package/src/autoresearch/git.ts +116 -30
  8. package/src/autoresearch/helpers.ts +49 -0
  9. package/src/autoresearch/index.ts +28 -187
  10. package/src/autoresearch/prompt.md +26 -9
  11. package/src/autoresearch/state.ts +0 -6
  12. package/src/autoresearch/tools/init-experiment.ts +202 -117
  13. package/src/autoresearch/tools/log-experiment.ts +123 -178
  14. package/src/autoresearch/tools/run-experiment.ts +48 -10
  15. package/src/autoresearch/types.ts +2 -2
  16. package/src/capability/index.ts +4 -2
  17. package/src/cli/file-processor.ts +3 -3
  18. package/src/cli/grep-cli.ts +8 -8
  19. package/src/cli/grievances-cli.ts +78 -0
  20. package/src/cli/read-cli.ts +67 -0
  21. package/src/cli/setup-cli.ts +4 -4
  22. package/src/cli/update-cli.ts +3 -3
  23. package/src/cli.ts +2 -0
  24. package/src/commands/grep.ts +6 -1
  25. package/src/commands/grievances.ts +20 -0
  26. package/src/commands/read.ts +33 -0
  27. package/src/commit/agentic/agent.ts +5 -8
  28. package/src/commit/agentic/index.ts +22 -26
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/tools/git-file-diff.ts +3 -6
  31. package/src/commit/agentic/tools/git-hunk.ts +3 -3
  32. package/src/commit/agentic/tools/git-overview.ts +6 -9
  33. package/src/commit/agentic/tools/index.ts +6 -8
  34. package/src/commit/agentic/tools/propose-commit.ts +4 -7
  35. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  36. package/src/commit/agentic/tools/split-commit.ts +4 -4
  37. package/src/commit/agentic/validation.ts +1 -1
  38. package/src/commit/analysis/conventional.ts +4 -4
  39. package/src/commit/analysis/summary.ts +3 -3
  40. package/src/commit/changelog/generate.ts +4 -4
  41. package/src/commit/changelog/index.ts +5 -9
  42. package/src/commit/map-reduce/map-phase.ts +4 -4
  43. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  44. package/src/commit/pipeline.ts +13 -16
  45. package/src/config/keybindings.ts +7 -6
  46. package/src/config/prompt-templates.ts +44 -226
  47. package/src/config/resolve-config-value.ts +4 -2
  48. package/src/config/settings-schema.ts +98 -2
  49. package/src/config/settings.ts +25 -26
  50. package/src/dap/client.ts +674 -0
  51. package/src/dap/config.ts +150 -0
  52. package/src/dap/defaults.json +211 -0
  53. package/src/dap/index.ts +4 -0
  54. package/src/dap/session.ts +1255 -0
  55. package/src/dap/types.ts +600 -0
  56. package/src/debug/log-viewer.ts +3 -2
  57. package/src/discovery/builtin.ts +1 -2
  58. package/src/discovery/codex.ts +2 -2
  59. package/src/discovery/github.ts +2 -1
  60. package/src/discovery/helpers.ts +2 -2
  61. package/src/discovery/opencode.ts +2 -2
  62. package/src/edit/diff.ts +818 -0
  63. package/src/edit/index.ts +309 -0
  64. package/src/edit/line-hash.ts +67 -0
  65. package/src/edit/modes/chunk.ts +454 -0
  66. package/src/{patch → edit/modes}/hashline.ts +741 -361
  67. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  68. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  69. package/src/{patch → edit}/normalize.ts +97 -76
  70. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  71. package/src/exec/bash-executor.ts +4 -2
  72. package/src/exec/idle-timeout-watchdog.ts +126 -0
  73. package/src/exec/non-interactive-env.ts +5 -0
  74. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
  75. package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
  76. package/src/extensibility/custom-commands/loader.ts +1 -2
  77. package/src/extensibility/custom-tools/loader.ts +34 -11
  78. package/src/extensibility/custom-tools/types.ts +1 -1
  79. package/src/extensibility/extensions/loader.ts +9 -4
  80. package/src/extensibility/extensions/runner.ts +24 -1
  81. package/src/extensibility/extensions/types.ts +4 -2
  82. package/src/extensibility/hooks/loader.ts +5 -6
  83. package/src/extensibility/hooks/types.ts +2 -2
  84. package/src/extensibility/plugins/doctor.ts +2 -1
  85. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
  86. package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
  87. package/src/extensibility/slash-commands.ts +3 -7
  88. package/src/index.ts +3 -1
  89. package/src/internal-urls/docs-index.generated.ts +11 -11
  90. package/src/ipy/executor.ts +58 -17
  91. package/src/ipy/gateway-coordinator.ts +6 -4
  92. package/src/ipy/kernel.ts +45 -22
  93. package/src/ipy/runtime.ts +2 -2
  94. package/src/lsp/client.ts +7 -4
  95. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  96. package/src/lsp/config.ts +2 -2
  97. package/src/lsp/defaults.json +688 -154
  98. package/src/lsp/index.ts +234 -45
  99. package/src/lsp/lspmux.ts +2 -2
  100. package/src/lsp/startup-events.ts +13 -0
  101. package/src/lsp/types.ts +12 -1
  102. package/src/lsp/utils.ts +8 -1
  103. package/src/main.ts +125 -47
  104. package/src/memories/index.ts +4 -5
  105. package/src/modes/acp/acp-agent.ts +563 -163
  106. package/src/modes/acp/acp-event-mapper.ts +9 -1
  107. package/src/modes/acp/acp-mode.ts +4 -2
  108. package/src/modes/components/agent-dashboard.ts +3 -4
  109. package/src/modes/components/diff.ts +6 -7
  110. package/src/modes/components/footer.ts +9 -29
  111. package/src/modes/components/hook-editor.ts +3 -3
  112. package/src/modes/components/hook-selector.ts +6 -1
  113. package/src/modes/components/read-tool-group.ts +6 -12
  114. package/src/modes/components/session-observer-overlay.ts +472 -0
  115. package/src/modes/components/settings-defs.ts +24 -0
  116. package/src/modes/components/status-line.ts +15 -61
  117. package/src/modes/components/tool-execution.ts +1 -1
  118. package/src/modes/components/welcome.ts +1 -1
  119. package/src/modes/controllers/btw-controller.ts +2 -2
  120. package/src/modes/controllers/command-controller.ts +4 -2
  121. package/src/modes/controllers/event-controller.ts +59 -2
  122. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  123. package/src/modes/controllers/input-controller.ts +15 -8
  124. package/src/modes/controllers/selector-controller.ts +26 -0
  125. package/src/modes/index.ts +20 -2
  126. package/src/modes/interactive-mode.ts +278 -69
  127. package/src/modes/rpc/host-tools.ts +186 -0
  128. package/src/modes/rpc/rpc-client.ts +178 -13
  129. package/src/modes/rpc/rpc-mode.ts +73 -3
  130. package/src/modes/rpc/rpc-types.ts +53 -1
  131. package/src/modes/session-observer-registry.ts +146 -0
  132. package/src/modes/shared.ts +0 -42
  133. package/src/modes/theme/theme.ts +80 -8
  134. package/src/modes/types.ts +4 -2
  135. package/src/modes/utils/keybinding-matchers.ts +9 -0
  136. package/src/prompts/system/custom-system-prompt.md +5 -0
  137. package/src/prompts/system/system-prompt.md +8 -1
  138. package/src/prompts/tools/chunk-edit.md +219 -0
  139. package/src/prompts/tools/debug.md +43 -0
  140. package/src/prompts/tools/grep.md +3 -0
  141. package/src/prompts/tools/lsp.md +5 -5
  142. package/src/prompts/tools/read-chunk.md +17 -0
  143. package/src/prompts/tools/read.md +19 -5
  144. package/src/sdk.ts +216 -165
  145. package/src/secrets/index.ts +1 -1
  146. package/src/secrets/obfuscator.ts +25 -17
  147. package/src/session/agent-session.ts +381 -286
  148. package/src/session/agent-storage.ts +12 -12
  149. package/src/session/compaction/branch-summarization.ts +3 -3
  150. package/src/session/compaction/compaction.ts +5 -6
  151. package/src/session/compaction/utils.ts +3 -3
  152. package/src/session/history-storage.ts +62 -19
  153. package/src/session/messages.ts +3 -3
  154. package/src/session/session-dump-format.ts +203 -0
  155. package/src/session/session-manager.ts +15 -5
  156. package/src/session/session-storage.ts +4 -2
  157. package/src/session/streaming-output.ts +1 -1
  158. package/src/session/tool-choice-queue.ts +213 -0
  159. package/src/slash-commands/builtin-registry.ts +56 -8
  160. package/src/ssh/connection-manager.ts +2 -2
  161. package/src/ssh/sshfs-mount.ts +5 -5
  162. package/src/stt/downloader.ts +4 -4
  163. package/src/stt/recorder.ts +4 -4
  164. package/src/stt/transcriber.ts +2 -2
  165. package/src/system-prompt.ts +25 -13
  166. package/src/task/agents.ts +5 -6
  167. package/src/task/commands.ts +2 -5
  168. package/src/task/executor.ts +32 -4
  169. package/src/task/index.ts +91 -82
  170. package/src/task/template.ts +2 -2
  171. package/src/task/types.ts +25 -0
  172. package/src/task/worktree.ts +131 -149
  173. package/src/tools/ask.ts +2 -3
  174. package/src/tools/ast-edit.ts +7 -7
  175. package/src/tools/ast-grep.ts +7 -7
  176. package/src/tools/auto-generated-guard.ts +36 -41
  177. package/src/tools/await-tool.ts +2 -2
  178. package/src/tools/bash.ts +5 -23
  179. package/src/tools/browser.ts +4 -5
  180. package/src/tools/calculator.ts +2 -3
  181. package/src/tools/cancel-job.ts +2 -2
  182. package/src/tools/checkpoint.ts +3 -3
  183. package/src/tools/debug.ts +1007 -0
  184. package/src/tools/exit-plan-mode.ts +3 -3
  185. package/src/tools/fetch.ts +67 -3
  186. package/src/tools/find.ts +4 -5
  187. package/src/tools/fs-cache-invalidation.ts +5 -0
  188. package/src/tools/gemini-image.ts +13 -5
  189. package/src/tools/gh.ts +130 -308
  190. package/src/tools/grep.ts +57 -9
  191. package/src/tools/index.ts +44 -22
  192. package/src/tools/inspect-image.ts +4 -4
  193. package/src/tools/output-meta.ts +1 -1
  194. package/src/tools/python.ts +19 -6
  195. package/src/tools/read.ts +211 -146
  196. package/src/tools/render-mermaid.ts +2 -3
  197. package/src/tools/render-utils.ts +20 -6
  198. package/src/tools/renderers.ts +3 -1
  199. package/src/tools/report-tool-issue.ts +80 -0
  200. package/src/tools/resolve.ts +70 -39
  201. package/src/tools/search-tool-bm25.ts +2 -2
  202. package/src/tools/ssh.ts +2 -2
  203. package/src/tools/todo-write.ts +2 -2
  204. package/src/tools/tool-timeouts.ts +1 -0
  205. package/src/tools/write.ts +5 -6
  206. package/src/tui/tree-list.ts +3 -1
  207. package/src/utils/clipboard.ts +80 -0
  208. package/src/utils/commit-message-generator.ts +2 -3
  209. package/src/utils/edit-mode.ts +49 -0
  210. package/src/utils/external-editor.ts +11 -5
  211. package/src/utils/file-display-mode.ts +6 -5
  212. package/src/utils/file-mentions.ts +8 -7
  213. package/src/utils/git.ts +1400 -0
  214. package/src/utils/image-loading.ts +98 -0
  215. package/src/utils/title-generator.ts +2 -3
  216. package/src/utils/tools-manager.ts +6 -6
  217. package/src/web/scrapers/choosealicense.ts +1 -1
  218. package/src/web/search/index.ts +3 -3
  219. package/src/web/search/render.ts +6 -4
  220. package/src/autoresearch/command-initialize.md +0 -34
  221. package/src/commit/git/errors.ts +0 -9
  222. package/src/commit/git/index.ts +0 -210
  223. package/src/commit/git/operations.ts +0 -54
  224. package/src/patch/diff.ts +0 -433
  225. package/src/patch/index.ts +0 -888
  226. package/src/patch/parser.ts +0 -532
  227. package/src/patch/types.ts +0 -292
  228. package/src/prompts/agents/oracle.md +0 -77
  229. package/src/tools/gh-cli.ts +0 -125
  230. package/src/tools/pending-action.ts +0 -49
  231. package/src/utils/child-process.ts +0 -88
  232. package/src/utils/frontmatter.ts +0 -117
  233. package/src/utils/image-input.ts +0 -274
  234. package/src/utils/mime.ts +0 -53
  235. package/src/utils/prompt-format.ts +0 -170
@@ -0,0 +1,146 @@
1
+ import type { AgentProgress, SubagentLifecyclePayload, SubagentProgressPayload } from "../task";
2
+ import { TASK_SUBAGENT_LIFECYCLE_CHANNEL, TASK_SUBAGENT_PROGRESS_CHANNEL } from "../task";
3
+ import type { EventBus } from "../utils/event-bus";
4
+
5
+ export interface ObservableSession {
6
+ id: string;
7
+ kind: "main" | "subagent";
8
+ label: string;
9
+ agent?: string;
10
+ description?: string;
11
+ status: "active" | "completed" | "failed" | "aborted";
12
+ sessionFile?: string;
13
+ lastUpdate: number;
14
+ /** Latest progress snapshot from the subagent executor */
15
+ progress?: AgentProgress;
16
+ }
17
+
18
+ const STATUS_MAP: Record<string, ObservableSession["status"]> = {
19
+ started: "active",
20
+ completed: "completed",
21
+ failed: "failed",
22
+ aborted: "aborted",
23
+ };
24
+
25
+ export class SessionObserverRegistry {
26
+ #sessions = new Map<string, ObservableSession>();
27
+ #listeners = new Set<() => void>();
28
+ #eventBusUnsubscribers: Array<() => void> = [];
29
+
30
+ /** Add a change listener. Returns unsubscribe function. */
31
+ onChange(cb: () => void): () => void {
32
+ this.#listeners.add(cb);
33
+ return () => this.#listeners.delete(cb);
34
+ }
35
+
36
+ #notifyListeners(): void {
37
+ for (const cb of this.#listeners) cb();
38
+ }
39
+
40
+ setMainSession(sessionFile?: string): void {
41
+ const existing = this.#sessions.get("main");
42
+ this.#sessions.set("main", {
43
+ id: "main",
44
+ kind: "main",
45
+ label: "Main Session",
46
+ status: "active",
47
+ sessionFile: sessionFile ?? existing?.sessionFile,
48
+ lastUpdate: Date.now(),
49
+ });
50
+ this.#notifyListeners();
51
+ }
52
+
53
+ getSessions(): ObservableSession[] {
54
+ const sessions = [...this.#sessions.values()];
55
+ sessions.sort((a, b) => {
56
+ if (a.kind === "main") return -1;
57
+ if (b.kind === "main") return 1;
58
+ return a.lastUpdate - b.lastUpdate;
59
+ });
60
+ return sessions;
61
+ }
62
+
63
+ getActiveSubagentCount(): number {
64
+ let count = 0;
65
+ for (const s of this.#sessions.values()) {
66
+ if (s.kind === "subagent" && s.status === "active") count++;
67
+ }
68
+ return count;
69
+ }
70
+
71
+ /** Clear all tracked sessions (e.g. on session switch). Keeps EventBus subscriptions and listeners. */
72
+ resetSessions(): void {
73
+ this.#sessions.clear();
74
+ this.#notifyListeners();
75
+ }
76
+
77
+ dispose(): void {
78
+ for (const unsub of this.#eventBusUnsubscribers) unsub();
79
+ this.#eventBusUnsubscribers = [];
80
+ this.#sessions.clear();
81
+ this.#listeners.clear();
82
+ }
83
+
84
+ subscribeToEventBus(eventBus: EventBus): void {
85
+ // Dispose previous EventBus subscriptions if called again
86
+ for (const unsub of this.#eventBusUnsubscribers) unsub();
87
+ this.#eventBusUnsubscribers = [];
88
+
89
+ this.#eventBusUnsubscribers.push(
90
+ eventBus.on(TASK_SUBAGENT_LIFECYCLE_CHANNEL, data => {
91
+ const payload = data as SubagentLifecyclePayload;
92
+ const status = STATUS_MAP[payload.status];
93
+ if (!status) return;
94
+
95
+ const existing = this.#sessions.get(payload.id);
96
+ if (existing) {
97
+ existing.status = status;
98
+ existing.lastUpdate = Date.now();
99
+ if (payload.description) existing.description = payload.description;
100
+ if (payload.sessionFile) existing.sessionFile = payload.sessionFile;
101
+ } else {
102
+ this.#sessions.set(payload.id, {
103
+ id: payload.id,
104
+ kind: "subagent",
105
+ label: payload.description ?? `Subagent #${payload.index}`,
106
+ agent: payload.agent,
107
+ description: payload.description,
108
+ status,
109
+ sessionFile: payload.sessionFile,
110
+ lastUpdate: Date.now(),
111
+ });
112
+ }
113
+ this.#notifyListeners();
114
+ }),
115
+ );
116
+
117
+ this.#eventBusUnsubscribers.push(
118
+ eventBus.on(TASK_SUBAGENT_PROGRESS_CHANNEL, data => {
119
+ const payload = data as SubagentProgressPayload;
120
+ const progress = payload.progress;
121
+ const id = progress.id;
122
+ const existing = this.#sessions.get(id);
123
+
124
+ if (existing) {
125
+ existing.lastUpdate = Date.now();
126
+ existing.progress = progress;
127
+ if (progress.description) existing.description = progress.description;
128
+ if (payload.sessionFile) existing.sessionFile = payload.sessionFile;
129
+ } else {
130
+ this.#sessions.set(id, {
131
+ id,
132
+ kind: "subagent",
133
+ label: progress.description ?? `Subagent #${payload.index}`,
134
+ agent: payload.agent,
135
+ description: progress.description,
136
+ status: "active",
137
+ sessionFile: payload.sessionFile,
138
+ lastUpdate: Date.now(),
139
+ progress,
140
+ });
141
+ }
142
+ this.#notifyListeners();
143
+ }),
144
+ );
145
+ }
146
+ }
@@ -1,7 +1,4 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
1
  import type { TabBarTheme } from "@oh-my-pi/pi-tui";
4
- import { getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
5
2
  import { theme } from "./theme/theme";
6
3
 
7
4
  // ═══════════════════════════════════════════════════════════════════════════
@@ -31,42 +28,3 @@ export function getTabBarTheme(): TabBarTheme {
31
28
  }
32
29
 
33
30
  export { parseCommandArgs } from "../utils/command-args";
34
-
35
- // ═══════════════════════════════════════════════════════════════════════════
36
- // Git HEAD Discovery
37
- // ═══════════════════════════════════════════════════════════════════════════
38
-
39
- /** Walk up from the project dir to find .git/HEAD. Returns path and content, or null. */
40
- export async function findGitHeadPathAsync(): Promise<{ path: string; content: string } | null> {
41
- let dir = getProjectDir();
42
- while (true) {
43
- const gitHeadPath = path.join(dir, ".git", "HEAD");
44
- try {
45
- const content = await Bun.file(gitHeadPath).text();
46
- return { path: gitHeadPath, content };
47
- } catch (err) {
48
- if (!isEnoent(err)) throw err;
49
- }
50
- const parent = path.dirname(dir);
51
- if (parent === dir) {
52
- return null;
53
- }
54
- dir = parent;
55
- }
56
- }
57
-
58
- /** Walk up from the project dir to find .git/HEAD. Returns path, or null. */
59
- export function findGitHeadPathSync(): string | null {
60
- let dir = getProjectDir();
61
- while (true) {
62
- const gitHeadPath = path.join(dir, ".git", "HEAD");
63
- if (fs.existsSync(gitHeadPath)) {
64
- return gitHeadPath;
65
- }
66
- const parent = path.dirname(dir);
67
- if (parent === dir) {
68
- return null;
69
- }
70
- dir = parent;
71
- }
72
- }
@@ -4,10 +4,10 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
4
4
  import type { Effort } from "@oh-my-pi/pi-ai";
5
5
  import {
6
6
  detectMacOSAppearance,
7
+ MacAppearanceObserver,
7
8
  type HighlightColors as NativeHighlightColors,
8
9
  highlightCode as nativeHighlightCode,
9
10
  supportsLanguage as nativeSupportsLanguage,
10
- startMacAppearanceObserver as startNativeMacObserver,
11
11
  } from "@oh-my-pi/pi-natives";
12
12
  import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
13
13
  import { adjustHsv, getCustomThemesDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
@@ -1148,9 +1148,14 @@ const langMap: Record<string, SymbolKey> = {
1148
1148
  sh: "lang.shell",
1149
1149
  zsh: "lang.shell",
1150
1150
  fish: "lang.shell",
1151
+ powershell: "lang.shell",
1152
+ just: "lang.shell",
1151
1153
  shell: "lang.shell",
1152
1154
  html: "lang.html",
1153
1155
  htm: "lang.html",
1156
+ astro: "lang.html",
1157
+ vue: "lang.html",
1158
+ svelte: "lang.html",
1154
1159
  css: "lang.css",
1155
1160
  scss: "lang.css",
1156
1161
  sass: "lang.css",
@@ -2057,10 +2062,12 @@ function startMacAppearanceObserver(): void {
2057
2062
  stopMacAppearanceObserver();
2058
2063
  if (!shouldUseMacOSAppearanceFallback()) return;
2059
2064
  try {
2060
- macOSReportedAppearance = detectMacOSAppearance();
2061
- macObserver = startNativeMacObserver(appearance => {
2062
- macOSReportedAppearance = appearance;
2063
- reevaluateAutoTheme("macOS fallback");
2065
+ macOSReportedAppearance = detectMacOSAppearance() ?? undefined;
2066
+ macObserver = MacAppearanceObserver.start((err, appearance) => {
2067
+ if (!err && (appearance === "dark" || appearance === "light")) {
2068
+ macOSReportedAppearance = appearance;
2069
+ reevaluateAutoTheme("macOS fallback");
2070
+ }
2064
2071
  });
2065
2072
  } catch (err) {
2066
2073
  logger.warn("Failed to start macOS appearance observer", { err });
@@ -2315,69 +2322,132 @@ export function getLanguageFromPath(filePath: string): string | undefined {
2315
2322
  ) {
2316
2323
  return "conf";
2317
2324
  }
2325
+ if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
2326
+ return "dockerfile";
2327
+ }
2328
+ if (baseName === "justfile") return "just";
2329
+ if (baseName === "cmakelists.txt") return "cmake";
2318
2330
 
2319
2331
  const ext = filePath.split(".").pop()?.toLowerCase();
2320
2332
  if (!ext) return undefined;
2321
2333
 
2322
2334
  const extToLang: Record<string, string> = {
2323
2335
  ts: "typescript",
2324
- tsx: "typescript",
2336
+ cts: "typescript",
2337
+ mts: "typescript",
2338
+ tsx: "tsx",
2325
2339
  js: "javascript",
2326
2340
  jsx: "javascript",
2327
2341
  mjs: "javascript",
2328
2342
  cjs: "javascript",
2329
2343
  py: "python",
2344
+ pyi: "python",
2330
2345
  rb: "ruby",
2346
+ rbw: "ruby",
2347
+ gemspec: "ruby",
2331
2348
  rs: "rust",
2332
2349
  go: "go",
2333
2350
  java: "java",
2334
2351
  kt: "kotlin",
2352
+ ktm: "kotlin",
2353
+ kts: "kotlin",
2335
2354
  swift: "swift",
2336
2355
  c: "c",
2337
2356
  h: "c",
2338
2357
  cpp: "cpp",
2339
2358
  cc: "cpp",
2340
2359
  cxx: "cpp",
2360
+ hh: "cpp",
2341
2361
  hpp: "cpp",
2362
+ cu: "cpp",
2363
+ ino: "cpp",
2342
2364
  cs: "csharp",
2365
+ clj: "clojure",
2366
+ cljc: "clojure",
2367
+ cljs: "clojure",
2368
+ edn: "clojure",
2343
2369
  php: "php",
2344
2370
  sh: "bash",
2345
2371
  bash: "bash",
2346
2372
  zsh: "bash",
2373
+ ksh: "bash",
2374
+ bats: "bash",
2375
+ tmux: "bash",
2376
+ cgi: "bash",
2377
+ fcgi: "bash",
2378
+ command: "bash",
2379
+ tool: "bash",
2347
2380
  fish: "fish",
2348
2381
  ps1: "powershell",
2382
+ psm1: "powershell",
2349
2383
  sql: "sql",
2350
2384
  html: "html",
2351
2385
  htm: "html",
2386
+ xhtml: "html",
2387
+ astro: "astro",
2388
+ vue: "vue",
2389
+ svelte: "svelte",
2352
2390
  css: "css",
2353
2391
  scss: "scss",
2354
2392
  sass: "sass",
2355
2393
  less: "less",
2356
2394
  json: "json",
2395
+ ipynb: "ipynb",
2396
+ hbs: "handlebars",
2397
+ hsb: "handlebars",
2398
+ handlebars: "handlebars",
2357
2399
  yaml: "yaml",
2358
2400
  yml: "yaml",
2359
2401
  toml: "toml",
2360
2402
  xml: "xml",
2403
+ xsl: "xml",
2404
+ xslt: "xml",
2405
+ svg: "xml",
2406
+ plist: "xml",
2361
2407
  md: "markdown",
2362
2408
  markdown: "markdown",
2409
+ mdx: "markdown",
2410
+ diff: "diff",
2411
+ patch: "diff",
2363
2412
  dockerfile: "dockerfile",
2364
- makefile: "makefile",
2413
+ containerfile: "dockerfile",
2414
+ makefile: "make",
2415
+ justfile: "just",
2416
+ mk: "make",
2417
+ mak: "make",
2365
2418
  cmake: "cmake",
2366
2419
  lua: "lua",
2420
+ jl: "julia",
2421
+ pl: "perl",
2422
+ pm: "perl",
2367
2423
  perl: "perl",
2368
2424
  r: "r",
2369
2425
  scala: "scala",
2370
- clj: "clojure",
2426
+ sc: "scala",
2427
+ sbt: "scala",
2371
2428
  ex: "elixir",
2372
2429
  exs: "elixir",
2373
2430
  erl: "erlang",
2374
2431
  hs: "haskell",
2432
+ nix: "nix",
2433
+ odin: "odin",
2434
+ zig: "zig",
2435
+ star: "starlark",
2436
+ bzl: "starlark",
2437
+ sol: "solidity",
2438
+ v: "verilog",
2439
+ sv: "verilog",
2440
+ svh: "verilog",
2441
+ vh: "verilog",
2442
+ m: "objc",
2443
+ mm: "objc",
2375
2444
  ml: "ocaml",
2376
2445
  vim: "vim",
2377
2446
  graphql: "graphql",
2378
2447
  proto: "protobuf",
2379
2448
  tf: "hcl",
2380
2449
  hcl: "hcl",
2450
+ tfvars: "hcl",
2381
2451
  txt: "text",
2382
2452
  text: "text",
2383
2453
  log: "log",
@@ -2388,6 +2458,8 @@ export function getLanguageFromPath(filePath: string): string | undefined {
2388
2458
  conf: "conf",
2389
2459
  config: "conf",
2390
2460
  properties: "conf",
2461
+ tla: "tlaplus",
2462
+ tlaplus: "tlaplus",
2391
2463
  env: "env",
2392
2464
  };
2393
2465
 
@@ -14,7 +14,7 @@ import type { MCPManager } from "../mcp";
14
14
  import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
15
15
  import type { HistoryStorage } from "../session/history-storage";
16
16
  import type { SessionContext, SessionManager } from "../session/session-manager";
17
- import type { ExitPlanModeDetails } from "../tools";
17
+ import type { ExitPlanModeDetails, LspStartupServerInfo } from "../tools";
18
18
  import type { AssistantMessageComponent } from "./components/assistant-message";
19
19
  import type { BashExecutionComponent } from "./components/bash-execution";
20
20
  import type { CustomEditor } from "./components/custom-editor";
@@ -76,7 +76,7 @@ export interface InteractiveModeContext {
76
76
  agent: AgentSession["agent"];
77
77
  historyStorage?: HistoryStorage;
78
78
  mcpManager?: MCPManager;
79
- lspServers?: Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>;
79
+ lspServers?: LspStartupServerInfo[];
80
80
 
81
81
  // State
82
82
  isInitialized: boolean;
@@ -208,6 +208,8 @@ export interface InteractiveModeContext {
208
208
  showOAuthSelector(mode: "login" | "logout", providerId?: string): Promise<void>;
209
209
  showHookConfirm(title: string, message: string): Promise<boolean>;
210
210
  showDebugSelector(): void;
211
+ showSessionObserver(): void;
212
+ resetObserverRegistry(): void;
211
213
 
212
214
  // Input handling
213
215
  handleCtrlC(): void;
@@ -19,3 +19,12 @@ export function matchesAppInterrupt(data: string): boolean {
19
19
  export function matchesSelectCancel(data: string): boolean {
20
20
  return getKeybindings().matches(data, "tui.select.cancel");
21
21
  }
22
+
23
+ export function matchesAppExternalEditor(data: string): boolean {
24
+ const keybindings = getKeybindings();
25
+ const externalEditorKeys = keybindings.getKeys("app.editor.external");
26
+ if (externalEditorKeys.length > 0) {
27
+ return keybindings.matches(data, "app.editor.external");
28
+ }
29
+ return matchesKey(data, "ctrl+g");
30
+ }
@@ -61,3 +61,8 @@ You **MUST** read `rule://<name>` when working in that domain.
61
61
  {{/if}}
62
62
  Current date: {{date}}
63
63
  Current working directory: {{cwd}}
64
+ {{#if secretsEnabled}}
65
+ <redacted-content>
66
+ Some values in tool output are redacted for security. They appear as `#XXXX#` tokens (4 uppercase-alphanumeric characters wrapped in `#`). These are **not errors** — they are intentional placeholders for sensitive values (API keys, passwords, tokens). Treat them as opaque strings. Do not attempt to decode, fix, or report them as problems.
67
+ </redacted-content>
68
+ {{/if}}
@@ -109,6 +109,7 @@ Most tools resolve custom protocol URLs to internal resources (not web URLs):
109
109
  - `artifact://<id>` — Raw artifact content (truncated tool output)
110
110
  - `local://<TITLE>.md` — Finalized plan artifact created after `exit_plan_mode` approval
111
111
  - `jobs://<job-id>` — Specific job status and result
112
+ - `mcp://<resource-uri>` — MCP resource from a connected server; matched against exact resource URIs first, then RFC 6570 URI templates advertised by connected servers
112
113
  - `pi://..` — Internal documentation files about Oh My Pi, you **MUST NOT** read them unless the user asks about omp/pi itself: its SDK, extensions, themes, skills, TUI, keybindings, or configuration
113
114
 
114
115
  In `bash`, URIs auto-resolve to filesystem paths (e.g., `python skill://my-skill/scripts/init.py`).
@@ -261,7 +262,7 @@ These are inviolable. Violation is system failure.
261
262
  - You **MUST NOT** suppress tests to make code pass. You **MUST NOT** fabricate outputs not observed.
262
263
  - You **MUST NOT** solve the wished-for problem instead of the actual problem.
263
264
  - You **MUST NOT** ask for information obtainable from tools, repo context, or files.
264
- - You **MUST** perform full CUTOVER when refactoring. Replace old usage, not write shims. No gradual migration. Let it error while you fix it.
265
+ - You **MUST** always design a clean solution. You **MUST NOT** introduce unnecessary backwards compatibiltity layers, no shims, no gradual migration, no bridges to old code unless user explicitly asks for it. Let the errors guide you on what to include in the refactoring. **ALWAYS default to performing full CUTOVER!**
265
266
 
266
267
  # Design Integrity
267
268
 
@@ -314,6 +315,12 @@ When a tool call fails, read the full error before doing anything else. When a f
314
315
  - You **SHOULD** run only tests you added/modified unless asked otherwise.
315
316
  - You **MUST NOT** yield without proof when non-trivial work, self-assessment is deceptive: tests, linters, type checks, repro steps… exhaust all external verification.
316
317
 
318
+ {{#if secretsEnabled}}
319
+ <redacted-content>
320
+ Some values in tool output are redacted for security. They appear as `#XXXX#` tokens (4 uppercase-alphanumeric characters wrapped in `#`). These are **not errors** — they are intentional placeholders for sensitive values (API keys, passwords, tokens). Treat them as opaque strings. Do not attempt to decode, fix, or report them as problems.
321
+ </redacted-content>
322
+ {{/if}}
323
+
317
324
  {{SECTION_SEPERATOR "Now"}}
318
325
  The current working directory is '{{cwd}}'.
319
326
  Today is '{{date}}', and your work begins now. Get it right.
@@ -0,0 +1,219 @@
1
+ Edits files via syntax-aware chunks. Run `read(path="file.ts")` first. The edit selector is a chunk path, optionally qualified with a region.
2
+
3
+ <rules>
4
+ - **MUST** `read` first. Never invent chunk paths or CRCs. Copy them from the latest `read` output or edit response.
5
+ - `sel` format:
6
+ - insertions: `chunk` or `chunk@region`
7
+ - replacements: `chunk#CRC` or `chunk#CRC@region`
8
+ - Without a `@region` it defaults to the entire chunk including leading trivia. Valid regions: `head`, `body`, `tail`, `decl`.
9
+ - If the exact chunk path is unclear, run `read(path="file", sel="?")` and copy a selector from that listing.
10
+ - Use a single leading space per indent level in `content`. Write content at indent-level 0 — the tool re-indents it to match the chunk's position in the file. For example, to replace `@body` of a method, write the body starting at column 0:
11
+ ```
12
+ content: "if (x) {\n return true;\n}"
13
+ ```
14
+ The tool adds the correct base indent automatically. Never manually pad with the chunk's own indentation.
15
+ - `@region` only works on container chunks (classes, functions, impl blocks, sections). Do **not** use `@head`/`@body`/`@tail` on leaf chunks (enum variants, fields, single statements) — use the whole chunk instead.
16
+ - `replace` requires the current CRC. Insertions do not.
17
+ - **CRCs change after every edit.** Always use the selectors/CRCs from the most recent `read` or edit response. Never reuse a CRC from a previous edit.
18
+ </rules>
19
+
20
+ <critical>
21
+ You **MUST** use the narrowest region that covers your change. Replacing without a region replaces the **entire chunk including leading comments, decorators, and attributes** — omitting them from `content` deletes them.
22
+ </critical>
23
+
24
+ <regions>
25
+ ```
26
+ @head ··· ┊ /// doc comment
27
+ · ┊ #[attr]
28
+ @decl ··· ┊ fn foo(x: i32) {
29
+ @body ·· ┊ body();
30
+ @tail ·· ┊ }
31
+ ```
32
+ - `@body` — the interior only. **Use for most edits.**
33
+ - `@head` — leading trivia + signature + opening delimiter.
34
+ - `@tail` — the closing delimiter.
35
+ - `@decl` — everything except leading trivia (signature + body + closing delimiter).
36
+ - *(no region)* — the entire chunk including leading trivia. Same as `@head` + `@body` + `@tail`.
37
+
38
+ For leaf chunks (fields, variants, single-line items), `@body` falls back to the full chunk.
39
+
40
+ `append`/`prepend` without a `@region` inserts _outside_ the chunk. To add children _inside_ a class, struct, enum, or function body, use `@body`:
41
+ - `class_Foo@body` + `append` → adds inside the class before `}`
42
+ - `class_Foo@body` + `prepend` → adds inside the class after `{`
43
+ - `class_Foo` + `append` → adds after the entire class (after `}`)
44
+ </regions>
45
+
46
+ <ops>
47
+ |op|sel|effect|
48
+ |---|---|---|
49
+ |`replace`|`chunk#CRC(@region)?`|rewrite the addressed region|
50
+ |`before`|`chunk(@region)?`|insert before the region span|
51
+ |`after`|`chunk(@region)?`|insert after the region span|
52
+ |`prepend`|`chunk(@region)?`|insert at the start inside the region|
53
+ |`append`|`chunk(@region)?`|insert at the end inside the region|
54
+ </ops>
55
+
56
+ <examples>
57
+ Given this `read` output for `example.ts`:
58
+ ```
59
+ | example.ts·34L·ts·#QBMH
60
+ |
61
+ | [<interface_Config#BWTR>]
62
+ 1| interface Config {
63
+ | [<interface_Config.field_host#TTMN>]
64
+ 2| host: string;
65
+ | [<interface_Config.field_port#QSMH>]
66
+ 3| port: number;
67
+ | [<interface_Config.field_debug#JPRR>]
68
+ 4| debug: boolean;
69
+ 5| }
70
+ |
71
+ | [<class_Counter#HZHY>]
72
+ 7| class Counter {
73
+ | [<class_Counter.field_value#QJBY>]
74
+ 8| value: number = 0;
75
+ 9|
76
+ | [<class_Counter.fn_increment#NQWY>]
77
+ 10| increment(): void {
78
+ 11| this.value += 1;
79
+ 12| }
80
+ 13|
81
+ | [<class_Counter.fn_decrement#PMBP>]
82
+ 14| decrement(): void {
83
+ 15| this.value -= 1;
84
+ 16| }
85
+ 17|
86
+ | [<class_Counter.fn_toString#ZQZP>]
87
+ 18| toString(): string {
88
+ 19| return `Counter(${this.value})`;
89
+ 20| }
90
+ 21| }
91
+ |
92
+ | [<enum_Status#HYQJ>]
93
+ 23| enum Status {
94
+ | [<enum_Status.variant_Active#PQNS>]
95
+ 24| Active = "ACTIVE",
96
+ | [<enum_Status.variant_Paused#HHNM>]
97
+ 25| Paused = "PAUSED",
98
+ | [<enum_Status.variant_Stopped#NHTY>]
99
+ 26| Stopped = "STOPPED",
100
+ 27| }
101
+ |
102
+ | [<fn_createCounter#PQQY>]
103
+ 29| function createCounter(initial: number): Counter {
104
+ 30| const counter = new Counter();
105
+ 31| counter.value = initial;
106
+ 32| return counter;
107
+ 33| }
108
+ ```
109
+
110
+ **Replace a whole chunk** (rename a function):
111
+ ```
112
+ { "sel": "fn_createCounter#PQQY", "op": "replace", "content": "function makeCounter(start: number): Counter {\n const c = new Counter();\n c.value = start;\n return c;\n}\n" }
113
+ ```
114
+ Result — the entire chunk is rewritten:
115
+ ```
116
+ function makeCounter(start: number): Counter {
117
+ const c = new Counter();
118
+ c.value = start;
119
+ return c;
120
+ }
121
+ ```
122
+
123
+ **Replace a method body** (`@body`):
124
+ ```
125
+ { "sel": "class_Counter.fn_increment#NQWY@body", "op": "replace", "content": "this.value += 1;\nconsole.log('incremented to', this.value);\n" }
126
+ ```
127
+ Result — only the body changes, signature and braces are kept:
128
+ ```
129
+ increment(): void {
130
+ this.value += 1;
131
+ console.log('incremented to', this.value);
132
+ }
133
+ ```
134
+
135
+ **Replace a function header** (`@head` — signature and doc comment):
136
+ ```
137
+ { "sel": "fn_createCounter#PQQY@head", "op": "replace", "content": "/** Creates a counter with the given start value. */\nfunction createCounter(initial: number, label?: string): Counter {\n" }
138
+ ```
139
+ Result — adds a doc comment and updates the signature, body untouched:
140
+ ```
141
+ /** Creates a counter with the given start value. */
142
+ function createCounter(initial: number, label?: string): Counter {
143
+ const counter = new Counter();
144
+ counter.value = initial;
145
+ return counter;
146
+ }
147
+ ```
148
+
149
+ **Insert before a chunk** (`before`):
150
+ ```
151
+ { "sel": "fn_createCounter", "op": "before", "content": "/** Factory function below. */\n" }
152
+ ```
153
+ Result — a comment is inserted before the function:
154
+ ```
155
+ /** Factory function below. */
156
+
157
+ function createCounter(initial: number): Counter {
158
+ ```
159
+
160
+ **Insert after a chunk** (`after`):
161
+ ```
162
+ { "sel": "enum_Status", "op": "after", "content": "\nfunction isActive(s: Status): boolean {\n return s === Status.Active;\n}\n" }
163
+ ```
164
+ Result — a new function appears after the enum:
165
+ ```
166
+ enum Status {
167
+ Active = "ACTIVE",
168
+ Paused = "PAUSED",
169
+ Stopped = "STOPPED",
170
+ }
171
+
172
+ function isActive(s: Status): boolean {
173
+ return s === Status.Active;
174
+ }
175
+
176
+ function createCounter(initial: number): Counter {
177
+ ```
178
+
179
+ **Prepend inside a container** (`@body` + `prepend`):
180
+ ```
181
+ { "sel": "class_Counter@body", "op": "prepend", "content": "label: string = 'default';\n\n" }
182
+ ```
183
+ Result — a new field is added at the top of the class body, before existing members:
184
+ ```
185
+ class Counter {
186
+ label: string = 'default';
187
+
188
+ value: number = 0;
189
+ ```
190
+
191
+ **Append inside a container** (`@body` + `append`):
192
+ ```
193
+ { "sel": "class_Counter@body", "op": "append", "content": "\nreset(): void {\n this.value = 0;\n}\n" }
194
+ ```
195
+ Result — a new method is added at the end of the class body, before the closing `}`:
196
+ ```
197
+ toString(): string {
198
+ return `Counter(${this.value})`;
199
+ }
200
+
201
+ reset(): void {
202
+ this.value = 0;
203
+ }
204
+ }
205
+ ```
206
+
207
+ **Delete a chunk** (`replace` with empty content):
208
+ ```
209
+ { "sel": "class_Counter.fn_toString#ZQZP", "op": "replace", "content": "" }
210
+ ```
211
+ Result — the method is removed from the class.
212
+ - Indentation rules (important):
213
+ - Use one leading space for each indent level in canonical chunk-edit content. The tool expands those levels to the file's actual style (2-space, 4-space, tabs, etc.).
214
+ - Do NOT include the chunk's base indentation — only indent relative to the region's opening level.
215
+ - For `@body` of a function: write at column 0, e.g. `"return x;\n"`. The tool adds the correct base indent.
216
+ - For `@head`: write at the chunk's own depth. A class member's head uses `"/** doc */\nstart(): void {"`.
217
+ - For a top-level item: start at zero indent. Write `"function foo() {\n return 1;\n}\n"`.
218
+ - The tool strips common leading indentation from your content as a safety net, so accidental over-indentation is corrected.
219
+ </examples>