@easynet/agent-runtime 1.0.2 → 1.0.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 (205) hide show
  1. package/.github/workflows/ci.yml +9 -24
  2. package/.github/workflows/release.yml +38 -26
  3. package/agent-runtime/.github/workflows/ci.yml +69 -0
  4. package/agent-runtime/.github/workflows/release.yml +118 -0
  5. package/agent-runtime/.releaserc.cjs +26 -0
  6. package/agent-runtime/config/agent.deep.yaml +25 -0
  7. package/agent-runtime/config/agent.react.yaml +24 -0
  8. package/agent-runtime/example/basic-usage.ts +49 -0
  9. package/agent-runtime/package-lock.json +7740 -0
  10. package/agent-runtime/package.json +49 -0
  11. package/agent-runtime/pnpm-lock.yaml +3712 -0
  12. package/agent-runtime/scripts/resolve-deps.js +54 -0
  13. package/agent-runtime/src/agents/deep-agent.ts +165 -0
  14. package/agent-runtime/src/agents/react-agent.helpers.ts +227 -0
  15. package/agent-runtime/src/agents/react-agent.ts +584 -0
  16. package/{src → agent-runtime/src/agents}/sub-agent.ts +2 -2
  17. package/agent-runtime/src/cli/args.ts +15 -0
  18. package/agent-runtime/src/cli/event-listener.ts +162 -0
  19. package/agent-runtime/src/cli/interactive.ts +144 -0
  20. package/agent-runtime/src/cli/runtime.ts +31 -0
  21. package/agent-runtime/src/cli/spinner.ts +23 -0
  22. package/agent-runtime/src/cli/terminal-render.ts +322 -0
  23. package/agent-runtime/src/cli/types.ts +33 -0
  24. package/agent-runtime/src/cli.ts +134 -0
  25. package/agent-runtime/src/config/helpers.ts +179 -0
  26. package/agent-runtime/src/config/index.ts +245 -0
  27. package/agent-runtime/src/config/types.ts +62 -0
  28. package/agent-runtime/src/core/context.ts +266 -0
  29. package/agent-runtime/src/index.ts +55 -0
  30. package/agent-runtime/tsconfig.json +18 -0
  31. package/apps/imessagebot/README.md +38 -0
  32. package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/README.md +33 -0
  33. package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/package-lock.json +15257 -0
  34. package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/package.json +55 -0
  35. package/apps/imessagebot/config/agents/deep/agent.yaml +31 -0
  36. package/apps/imessagebot/config/agents/react/agent.yaml +58 -0
  37. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/README.md +33 -0
  38. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/package-lock.json +15457 -0
  39. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/package.json +55 -0
  40. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/README.md +33 -0
  41. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/package-lock.json +15257 -0
  42. package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/package.json +62 -0
  43. package/apps/imessagebot/config/agents/shared/memory.yaml +31 -0
  44. package/apps/imessagebot/config/agents/shared/model.yaml +23 -0
  45. package/apps/imessagebot/config/agents/shared/tool.yaml +13 -0
  46. package/apps/imessagebot/config/app.yaml +14 -0
  47. package/apps/imessagebot/package-lock.json +53695 -0
  48. package/apps/imessagebot/package.json +41 -0
  49. package/apps/imessagebot/pnpm-lock.yaml +1589 -0
  50. package/apps/imessagebot/scripts/resolve-deps.js +41 -0
  51. package/apps/imessagebot/scripts/test-llm.mjs +27 -0
  52. package/apps/imessagebot/scripts/validate-tools-config.mjs +174 -0
  53. package/apps/imessagebot/src/config.ts +76 -0
  54. package/apps/imessagebot/src/context.ts +35 -0
  55. package/apps/imessagebot/src/index.ts +17 -0
  56. package/apps/imessagebot/tsconfig.json +18 -0
  57. package/apps/itermbot/.github/workflows/ci.yml +61 -0
  58. package/apps/itermbot/.github/workflows/release.yml +80 -0
  59. package/apps/itermbot/.releaserc.cjs +26 -0
  60. package/apps/itermbot/README.md +82 -0
  61. package/apps/itermbot/config/app.yaml +29 -0
  62. package/apps/itermbot/config/tsconfig.json +18 -0
  63. package/apps/itermbot/macos_disk_usage_agent_plan.md +244 -0
  64. package/apps/itermbot/package-lock.json +53697 -0
  65. package/apps/itermbot/package.json +57 -0
  66. package/apps/itermbot/pnpm-lock.yaml +3966 -0
  67. package/apps/itermbot/scripts/patch-buildin-cache.sh +25 -0
  68. package/apps/itermbot/scripts/resolve-deps.js +41 -0
  69. package/apps/itermbot/scripts/test-llm.mjs +32 -0
  70. package/apps/itermbot/skills/command-explain-and-guard/SKILL.md +39 -0
  71. package/apps/itermbot/skills/command-explain-and-guard/handler.js +86 -0
  72. package/apps/itermbot/skills/disk-usage-investigate/SKILL.md +44 -0
  73. package/apps/itermbot/skills/disk-usage-investigate/handler.js +12 -0
  74. package/apps/itermbot/skills/gpu-ssh-monitor/SKILL.md +64 -0
  75. package/apps/itermbot/skills/repo-triage/SKILL.md +40 -0
  76. package/apps/itermbot/skills/repo-triage/handler.js +56 -0
  77. package/apps/itermbot/skills/test-failure-diagnose/SKILL.md +43 -0
  78. package/apps/itermbot/skills/test-failure-diagnose/handler.js +107 -0
  79. package/apps/itermbot/src/config.ts +95 -0
  80. package/apps/itermbot/src/context.ts +35 -0
  81. package/apps/itermbot/src/index.ts +223 -0
  82. package/apps/itermbot/src/iterm/session-hint.ts +40 -0
  83. package/apps/itermbot/src/iterm/target-routing.ts +419 -0
  84. package/apps/itermbot/src/startup/colors.ts +317 -0
  85. package/apps/itermbot/src/startup/diagnostics.ts +97 -0
  86. package/apps/itermbot/src/startup/ui.ts +141 -0
  87. package/config/agent.deep.yaml +25 -0
  88. package/config/agent.react.yaml +24 -0
  89. package/dist/agents/deep-agent.d.ts +37 -0
  90. package/dist/agents/deep-agent.d.ts.map +1 -0
  91. package/dist/agents/deep-agent.js +115 -0
  92. package/dist/agents/deep-agent.js.map +1 -0
  93. package/dist/agents/react-agent.d.ts +40 -0
  94. package/dist/agents/react-agent.d.ts.map +1 -0
  95. package/dist/agents/react-agent.helpers.d.ts +40 -0
  96. package/dist/agents/react-agent.helpers.d.ts.map +1 -0
  97. package/dist/agents/react-agent.helpers.js +196 -0
  98. package/dist/agents/react-agent.helpers.js.map +1 -0
  99. package/dist/agents/react-agent.js +400 -0
  100. package/dist/agents/react-agent.js.map +1 -0
  101. package/dist/agents/sub-agent.d.ts +34 -0
  102. package/dist/agents/sub-agent.d.ts.map +1 -0
  103. package/dist/agents/sub-agent.js +53 -0
  104. package/dist/agents/sub-agent.js.map +1 -0
  105. package/dist/cli/args.d.ts +8 -0
  106. package/dist/cli/args.d.ts.map +1 -0
  107. package/dist/cli/args.js +9 -0
  108. package/dist/cli/args.js.map +1 -0
  109. package/dist/cli/event-listener.d.ts +3 -0
  110. package/dist/cli/event-listener.d.ts.map +1 -0
  111. package/dist/cli/event-listener.js +131 -0
  112. package/dist/cli/event-listener.js.map +1 -0
  113. package/dist/cli/interactive.d.ts +4 -0
  114. package/dist/cli/interactive.d.ts.map +1 -0
  115. package/dist/cli/interactive.js +118 -0
  116. package/dist/cli/interactive.js.map +1 -0
  117. package/dist/cli/runtime.d.ts +8 -0
  118. package/dist/cli/runtime.d.ts.map +1 -0
  119. package/dist/cli/runtime.js +27 -0
  120. package/dist/cli/runtime.js.map +1 -0
  121. package/dist/cli/spinner.d.ts +2 -0
  122. package/dist/cli/spinner.d.ts.map +1 -0
  123. package/dist/cli/spinner.js +22 -0
  124. package/dist/cli/spinner.js.map +1 -0
  125. package/dist/cli/terminal-render.d.ts +7 -0
  126. package/dist/cli/terminal-render.d.ts.map +1 -0
  127. package/dist/cli/terminal-render.js +282 -0
  128. package/dist/cli/terminal-render.js.map +1 -0
  129. package/dist/cli/types.d.ts +29 -0
  130. package/dist/cli/types.d.ts.map +1 -0
  131. package/dist/cli/types.js +3 -0
  132. package/dist/cli/types.js.map +1 -0
  133. package/dist/cli.d.ts +4 -41
  134. package/dist/cli.d.ts.map +1 -1
  135. package/dist/cli.js +84 -588
  136. package/dist/cli.js.map +1 -1
  137. package/dist/config/helpers.d.ts +6 -0
  138. package/dist/config/helpers.d.ts.map +1 -0
  139. package/dist/config/helpers.js +164 -0
  140. package/dist/config/helpers.js.map +1 -0
  141. package/dist/config/index.d.ts +15 -0
  142. package/dist/config/index.d.ts.map +1 -0
  143. package/dist/config/index.js +160 -0
  144. package/dist/config/index.js.map +1 -0
  145. package/dist/config/types.d.ts +57 -0
  146. package/dist/config/types.d.ts.map +1 -0
  147. package/dist/config/types.js +2 -0
  148. package/dist/config/types.js.map +1 -0
  149. package/dist/context.d.ts +8 -69
  150. package/dist/context.d.ts.map +1 -1
  151. package/dist/context.js +44 -24
  152. package/dist/context.js.map +1 -1
  153. package/dist/core/context.d.ts +66 -0
  154. package/dist/core/context.d.ts.map +1 -0
  155. package/dist/core/context.js +149 -0
  156. package/dist/core/context.js.map +1 -0
  157. package/dist/deep-agent.d.ts +5 -2
  158. package/dist/deep-agent.d.ts.map +1 -1
  159. package/dist/deep-agent.js +44 -11
  160. package/dist/deep-agent.js.map +1 -1
  161. package/dist/index.d.ts +6 -6
  162. package/dist/index.d.ts.map +1 -1
  163. package/dist/index.js +6 -6
  164. package/dist/index.js.map +1 -1
  165. package/dist/middleware/malformed-tool-call-middleware.d.ts +8 -0
  166. package/dist/middleware/malformed-tool-call-middleware.d.ts.map +1 -0
  167. package/dist/middleware/malformed-tool-call-middleware.js +191 -0
  168. package/dist/middleware/malformed-tool-call-middleware.js.map +1 -0
  169. package/dist/react-agent.d.ts +2 -2
  170. package/dist/react-agent.d.ts.map +1 -1
  171. package/dist/react-agent.js +28 -9
  172. package/dist/react-agent.js.map +1 -1
  173. package/package.json +1 -1
  174. package/scripts/resolve-deps.js +54 -0
  175. package/src/agents/deep-agent.ts +165 -0
  176. package/src/agents/react-agent.helpers.ts +227 -0
  177. package/src/agents/react-agent.ts +584 -0
  178. package/src/agents/sub-agent.ts +82 -0
  179. package/src/cli/args.ts +15 -0
  180. package/src/cli/event-listener.ts +162 -0
  181. package/src/cli/interactive.ts +144 -0
  182. package/src/cli/runtime.ts +31 -0
  183. package/src/cli/spinner.ts +23 -0
  184. package/src/cli/terminal-render.ts +322 -0
  185. package/src/cli/types.ts +33 -0
  186. package/src/cli.ts +91 -702
  187. package/src/config/helpers.ts +179 -0
  188. package/src/config/index.ts +245 -0
  189. package/src/config/types.ts +62 -0
  190. package/src/core/context.ts +266 -0
  191. package/src/index.ts +13 -11
  192. package/src/middleware/malformed-tool-call-middleware.ts +239 -0
  193. package/src/types/markdown-it-terminal.d.ts +4 -0
  194. package/src/types/marked-terminal.d.ts +16 -0
  195. package/dist/config.d.ts +0 -86
  196. package/dist/config.d.ts.map +0 -1
  197. package/dist/config.js +0 -84
  198. package/dist/config.js.map +0 -1
  199. package/src/config.ts +0 -177
  200. package/src/context.ts +0 -247
  201. package/src/deep-agent.ts +0 -104
  202. package/src/react-agent.ts +0 -576
  203. /package/{src → agent-runtime/src/middleware}/malformed-tool-call-middleware.ts +0 -0
  204. /package/{src → agent-runtime/src/types}/markdown-it-terminal.d.ts +0 -0
  205. /package/{src → agent-runtime/src/types}/marked-terminal.d.ts +0 -0
@@ -0,0 +1,419 @@
1
+ import type { BotContext } from "@easynet/agent-runtime";
2
+ import {
3
+ itermListCurrentWindowSessions,
4
+ itermRename,
5
+ itermSetSessionColors,
6
+ itermSplitPane,
7
+ } from "@easynet/agent-tool-buildin/iterm";
8
+ import {
9
+ CHAT_BG,
10
+ CHAT_FG,
11
+ TARGET_BG,
12
+ TARGET_FG,
13
+ captureSessionColorsByIdSync,
14
+ restoreSessionColorsSync,
15
+ type SessionColorSnapshot,
16
+ } from "../startup/colors.js";
17
+
18
+ interface SessionInfo {
19
+ windowId?: number;
20
+ tabIndex?: number;
21
+ sessionId?: string;
22
+ isCurrentSession?: boolean;
23
+ isCurrentTab?: boolean;
24
+ }
25
+
26
+ export interface TargetRoutingState {
27
+ chatSessionId: string | null;
28
+ chatWindowId: number | null;
29
+ chatTabIndex: number | null;
30
+ targetSessionId: string | null;
31
+ windowId: number | null;
32
+ tabIndex: number | null;
33
+ }
34
+
35
+ type ToolLike = {
36
+ name?: unknown;
37
+ invoke?: (args: unknown) => Promise<unknown>;
38
+ };
39
+
40
+ export function enableDynamicTargetRouting(
41
+ ctx: BotContext,
42
+ state: TargetRoutingState,
43
+ options: {
44
+ intervalMs?: number;
45
+ chatTitle?: string;
46
+ targetTitle?: string;
47
+ } = {},
48
+ ): () => void {
49
+ const intervalMs = options.intervalMs ?? 2000;
50
+ const chatTitle = options.chatTitle ?? "iTermBot Chat";
51
+ const targetTitle = options.targetTitle ?? "iTermBot Target";
52
+ const unpatchFns: Array<() => void> = [];
53
+ let announcedTarget = state.targetSessionId;
54
+ const originalColors = new Map<string, SessionColorSnapshot>();
55
+ const verboseRoutingLog = process.env.ITERMBOT_ROUTING_LOG === "1";
56
+
57
+ const announceRoutingChange = (message: string): void => {
58
+ // Avoid breaking readline prompt in interactive TTY mode.
59
+ if (!verboseRoutingLog) return;
60
+ console.error(message);
61
+ };
62
+
63
+ const isLikelyRoutingError = (error: unknown): boolean => {
64
+ const message =
65
+ error instanceof Error
66
+ ? error.message
67
+ : typeof error === "string"
68
+ ? error
69
+ : "";
70
+ if (!message) return false;
71
+ const lower = message.toLowerCase();
72
+ return (
73
+ lower.includes("session") ||
74
+ lower.includes("window") ||
75
+ lower.includes("tab") ||
76
+ lower.includes("not found") ||
77
+ lower.includes("missing value") ||
78
+ lower.includes("iterm")
79
+ );
80
+ };
81
+
82
+ const setSessionTitle = async (
83
+ sessionId: string,
84
+ windowId: number | null,
85
+ tabIndex: number | null,
86
+ sessionName: string,
87
+ ): Promise<void> => {
88
+ if (!sessionId) return;
89
+ if (typeof windowId !== "number" || typeof tabIndex !== "number") return;
90
+ try {
91
+ await itermRename({
92
+ windowId,
93
+ tabIndex,
94
+ sessionId,
95
+ sessionName,
96
+ });
97
+ } catch {
98
+ // non-fatal
99
+ }
100
+ };
101
+
102
+ const setSessionColors = async (
103
+ sessionId: string,
104
+ windowId: number | null,
105
+ tabIndex: number | null,
106
+ colors: { backgroundHex: string; foregroundHex: string },
107
+ ): Promise<void> => {
108
+ if (!sessionId) return;
109
+ if (typeof windowId !== "number" || typeof tabIndex !== "number") return;
110
+ try {
111
+ await itermSetSessionColors({
112
+ windowId,
113
+ tabIndex,
114
+ sessionId,
115
+ backgroundHex: colors.backgroundHex,
116
+ foregroundHex: colors.foregroundHex,
117
+ });
118
+ } catch {
119
+ // non-fatal
120
+ }
121
+ };
122
+
123
+ const rememberOriginalColors = (
124
+ sessionId: string,
125
+ windowId: number | null,
126
+ tabIndex: number | null,
127
+ ): void => {
128
+ if (!sessionId) return;
129
+ if (originalColors.has(sessionId)) return;
130
+ if (typeof windowId !== "number" || typeof tabIndex !== "number") return;
131
+ const snapshot = captureSessionColorsByIdSync({
132
+ windowId,
133
+ tabIndex,
134
+ sessionId,
135
+ });
136
+ if (!snapshot) return;
137
+ originalColors.set(sessionId, snapshot);
138
+ };
139
+
140
+ const restoreOriginalColors = (sessionId: string | null): void => {
141
+ if (!sessionId) return;
142
+ const snapshot = originalColors.get(sessionId);
143
+ if (!snapshot) return;
144
+ try {
145
+ restoreSessionColorsSync([snapshot]);
146
+ } catch {
147
+ // non-fatal
148
+ }
149
+ };
150
+
151
+ const enforceTitles = async (): Promise<void> => {
152
+ if (state.chatSessionId) {
153
+ await setSessionTitle(state.chatSessionId, state.chatWindowId, state.chatTabIndex, chatTitle);
154
+ await setSessionColors(state.chatSessionId, state.chatWindowId, state.chatTabIndex, {
155
+ backgroundHex: CHAT_BG,
156
+ foregroundHex: CHAT_FG,
157
+ });
158
+ }
159
+ if (state.targetSessionId) {
160
+ rememberOriginalColors(state.targetSessionId, state.windowId, state.tabIndex);
161
+ await setSessionTitle(state.targetSessionId, state.windowId, state.tabIndex, targetTitle);
162
+ await setSessionColors(state.targetSessionId, state.windowId, state.tabIndex, {
163
+ backgroundHex: TARGET_BG,
164
+ foregroundHex: TARGET_FG,
165
+ });
166
+ }
167
+ };
168
+
169
+ const chooseFallbackTarget = (sessions: SessionInfo[], chatSessionId: string | null): SessionInfo | null => {
170
+ const bySameTab = sessions.find((s) =>
171
+ Boolean(s.sessionId) &&
172
+ s.sessionId !== chatSessionId &&
173
+ typeof state.chatTabIndex === "number" &&
174
+ s.tabIndex === state.chatTabIndex,
175
+ );
176
+ if (bySameTab) return bySameTab;
177
+
178
+ const byCurrentTab = sessions.find((s) =>
179
+ Boolean(s.sessionId) &&
180
+ s.sessionId !== chatSessionId &&
181
+ s.isCurrentTab,
182
+ );
183
+ if (byCurrentTab) return byCurrentTab;
184
+
185
+ const byAny = sessions.find((s) =>
186
+ Boolean(s.sessionId) &&
187
+ s.sessionId !== chatSessionId,
188
+ );
189
+ return byAny ?? null;
190
+ };
191
+
192
+ const refreshTargetForCommand = async (opts: {
193
+ allowSplit: boolean;
194
+ adoptFocusedNonChat: boolean;
195
+ }): Promise<{
196
+ changed: boolean;
197
+ autoCreated: boolean;
198
+ }> => {
199
+ const { result } = await itermListCurrentWindowSessions();
200
+ const sessions = (result.sessions ?? []) as SessionInfo[];
201
+ if (sessions.length === 0) {
202
+ return { changed: false, autoCreated: false };
203
+ }
204
+
205
+ const current = sessions.find((s) => s.isCurrentSession && s.sessionId) ?? null;
206
+ const chatSession =
207
+ sessions.find((s) => s.sessionId && state.chatSessionId && s.sessionId === state.chatSessionId) ??
208
+ current ??
209
+ sessions.find((s) => Boolean(s.sessionId)) ??
210
+ null;
211
+
212
+ if (chatSession?.sessionId) {
213
+ const prevChatSessionId = state.chatSessionId;
214
+ state.chatSessionId = chatSession.sessionId;
215
+ if (typeof chatSession.windowId === "number") state.chatWindowId = chatSession.windowId;
216
+ if (typeof chatSession.tabIndex === "number") state.chatTabIndex = chatSession.tabIndex;
217
+ if (prevChatSessionId !== state.chatSessionId) {
218
+ state.targetSessionId = null;
219
+ }
220
+ }
221
+
222
+ if (opts.adoptFocusedNonChat && current?.sessionId && current.sessionId !== state.chatSessionId) {
223
+ const changed = state.targetSessionId !== current.sessionId;
224
+ state.targetSessionId = current.sessionId;
225
+ if (typeof current.windowId === "number") state.windowId = current.windowId;
226
+ if (typeof current.tabIndex === "number") state.tabIndex = current.tabIndex;
227
+ return { changed, autoCreated: false };
228
+ }
229
+
230
+ const currentTarget = sessions.find((s) => s.sessionId && s.sessionId === state.targetSessionId) ?? null;
231
+ const targetIsValid = Boolean(
232
+ currentTarget?.sessionId &&
233
+ (!state.chatSessionId || currentTarget.sessionId !== state.chatSessionId),
234
+ );
235
+ if (targetIsValid) {
236
+ if (currentTarget && typeof currentTarget.windowId === "number") state.windowId = currentTarget.windowId;
237
+ if (currentTarget && typeof currentTarget.tabIndex === "number") state.tabIndex = currentTarget.tabIndex;
238
+ return { changed: false, autoCreated: false };
239
+ }
240
+
241
+ const fallback = chooseFallbackTarget(sessions, state.chatSessionId);
242
+ if (fallback?.sessionId) {
243
+ const changed = state.targetSessionId !== fallback.sessionId;
244
+ state.targetSessionId = fallback.sessionId;
245
+ if (typeof fallback.windowId === "number") state.windowId = fallback.windowId;
246
+ if (typeof fallback.tabIndex === "number") state.tabIndex = fallback.tabIndex;
247
+ return { changed, autoCreated: false };
248
+ }
249
+
250
+ if (!opts.allowSplit || !chatSession?.sessionId) {
251
+ return { changed: false, autoCreated: false };
252
+ }
253
+
254
+ const split = await itermSplitPane({
255
+ windowId: typeof chatSession.windowId === "number" ? chatSession.windowId : undefined,
256
+ tabIndex: typeof chatSession.tabIndex === "number" ? chatSession.tabIndex : undefined,
257
+ sessionId: chatSession.sessionId,
258
+ direction: "vertical",
259
+ activate: false,
260
+ });
261
+
262
+ const splitSessionId = typeof split?.result?.sessionId === "string" ? split.result.sessionId : "";
263
+ if (splitSessionId && splitSessionId !== chatSession.sessionId) {
264
+ const changed = state.targetSessionId !== splitSessionId;
265
+ state.targetSessionId = splitSessionId;
266
+ if (typeof split.result.windowId === "number") state.windowId = split.result.windowId;
267
+ if (typeof split.result.tabIndex === "number") state.tabIndex = split.result.tabIndex;
268
+ return { changed, autoCreated: true };
269
+ }
270
+
271
+ const refreshed = await itermListCurrentWindowSessions();
272
+ const refreshedSessions = (refreshed.result.sessions ?? []) as SessionInfo[];
273
+ const refreshedFallback = chooseFallbackTarget(refreshedSessions, state.chatSessionId);
274
+ if (refreshedFallback?.sessionId) {
275
+ const changed = state.targetSessionId !== refreshedFallback.sessionId;
276
+ state.targetSessionId = refreshedFallback.sessionId;
277
+ if (typeof refreshedFallback.windowId === "number") state.windowId = refreshedFallback.windowId;
278
+ if (typeof refreshedFallback.tabIndex === "number") state.tabIndex = refreshedFallback.tabIndex;
279
+ return { changed, autoCreated: true };
280
+ }
281
+
282
+ return { changed: false, autoCreated: false };
283
+ };
284
+
285
+ // Initial titles
286
+ void enforceTitles();
287
+
288
+ for (const t of ctx.tools as unknown as ToolLike[]) {
289
+ if (!t || typeof t.name !== "string" || typeof t.invoke !== "function") continue;
290
+ if (!t.name.includes("itermRunCommandInSession")) continue;
291
+
292
+ const originalInvoke = t.invoke.bind(t);
293
+ t.invoke = async (args: unknown): Promise<unknown> => {
294
+ const baseArgs =
295
+ args && typeof args === "object"
296
+ ? { ...(args as Record<string, unknown>) }
297
+ : ({} as Record<string, unknown>);
298
+
299
+ const applyRoutingArgs = (target: Record<string, unknown>): void => {
300
+ if (state.targetSessionId) target.sessionId = state.targetSessionId;
301
+ if (typeof state.windowId === "number") target.windowId = state.windowId;
302
+ if (typeof state.tabIndex === "number") target.tabIndex = state.tabIndex;
303
+ };
304
+
305
+ try {
306
+ // Fast path: avoid iTerm preflight on every command.
307
+ // Only preflight when routing state is empty.
308
+ if (!state.targetSessionId) {
309
+ const previousTarget = state.targetSessionId;
310
+ const ensureResult = await refreshTargetForCommand({
311
+ allowSplit: true,
312
+ adoptFocusedNonChat: true,
313
+ });
314
+ if (ensureResult.changed) {
315
+ await enforceTitles();
316
+ if (previousTarget && previousTarget !== state.targetSessionId) {
317
+ restoreOriginalColors(previousTarget);
318
+ }
319
+ if (state.targetSessionId !== announcedTarget) {
320
+ announcedTarget = state.targetSessionId;
321
+ if (ensureResult.autoCreated) {
322
+ announceRoutingChange("Session routing updated: target panel auto-created and linked.");
323
+ } else {
324
+ announceRoutingChange("Session routing updated: target panel linked.");
325
+ }
326
+ }
327
+ }
328
+ }
329
+ } catch {
330
+ // keep previous target when preflight fails
331
+ }
332
+ applyRoutingArgs(baseArgs);
333
+
334
+ try {
335
+ return await originalInvoke(baseArgs);
336
+ } catch (error) {
337
+ if (!isLikelyRoutingError(error)) {
338
+ throw error;
339
+ }
340
+ // Slow path fallback: routing may be stale (target pane closed/moved).
341
+ const previousTarget = state.targetSessionId;
342
+ try {
343
+ const ensureResult = await refreshTargetForCommand({
344
+ allowSplit: true,
345
+ adoptFocusedNonChat: true,
346
+ });
347
+ if (ensureResult.changed) {
348
+ await enforceTitles();
349
+ if (previousTarget && previousTarget !== state.targetSessionId) {
350
+ restoreOriginalColors(previousTarget);
351
+ }
352
+ if (state.targetSessionId !== announcedTarget) {
353
+ announcedTarget = state.targetSessionId;
354
+ if (ensureResult.autoCreated) {
355
+ announceRoutingChange("Session routing updated: target panel auto-created and linked.");
356
+ } else {
357
+ announceRoutingChange("Session routing updated: target panel linked.");
358
+ }
359
+ }
360
+ }
361
+ } catch {
362
+ // If reroute fails, fall through and surface original error.
363
+ }
364
+
365
+ const retryArgs = { ...baseArgs };
366
+ applyRoutingArgs(retryArgs);
367
+ return originalInvoke(retryArgs);
368
+ }
369
+ };
370
+
371
+ unpatchFns.push(() => {
372
+ t.invoke = originalInvoke;
373
+ });
374
+ }
375
+
376
+ let stopped = false;
377
+ let polling = false;
378
+ const timer = setInterval(() => {
379
+ if (stopped || polling) return;
380
+ polling = true;
381
+ void (async () => {
382
+ try {
383
+ const previousTarget = state.targetSessionId;
384
+ const ensured = await refreshTargetForCommand({
385
+ allowSplit: false,
386
+ adoptFocusedNonChat: true,
387
+ });
388
+ if (ensured.changed) {
389
+ await enforceTitles();
390
+ if (previousTarget && previousTarget !== state.targetSessionId) {
391
+ restoreOriginalColors(previousTarget);
392
+ }
393
+ }
394
+ if (state.targetSessionId === announcedTarget) return;
395
+ announcedTarget = state.targetSessionId;
396
+ announceRoutingChange("Session routing updated: target panel linked.");
397
+ } catch {
398
+ // keep previous target when polling fails
399
+ } finally {
400
+ polling = false;
401
+ }
402
+ })();
403
+ }, intervalMs);
404
+
405
+ return () => {
406
+ stopped = true;
407
+ clearInterval(timer);
408
+ if (originalColors.size > 0) {
409
+ try {
410
+ restoreSessionColorsSync(Array.from(originalColors.values()));
411
+ } catch {
412
+ // non-fatal
413
+ }
414
+ }
415
+ for (const unpatch of unpatchFns) {
416
+ unpatch();
417
+ }
418
+ };
419
+ }