longer-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/README.zh-CN.md +227 -0
  4. package/agent_templates/executor/agent.yaml +22 -0
  5. package/agent_templates/executor/system_prompt.md +17 -0
  6. package/agent_templates/explorer/agent.yaml +13 -0
  7. package/agent_templates/explorer/system_prompt.md +19 -0
  8. package/agent_templates/main/agent.yaml +7 -0
  9. package/agent_templates/main/system_prompt.md +45 -0
  10. package/configExample.yaml +83 -0
  11. package/dist/agents/agent.d.ts +79 -0
  12. package/dist/agents/agent.d.ts.map +1 -0
  13. package/dist/agents/agent.js +156 -0
  14. package/dist/agents/agent.js.map +1 -0
  15. package/dist/agents/tool-loop.d.ts +140 -0
  16. package/dist/agents/tool-loop.d.ts.map +1 -0
  17. package/dist/agents/tool-loop.js +465 -0
  18. package/dist/agents/tool-loop.js.map +1 -0
  19. package/dist/ask.d.ts +81 -0
  20. package/dist/ask.d.ts.map +1 -0
  21. package/dist/ask.js +34 -0
  22. package/dist/ask.js.map +1 -0
  23. package/dist/auth/openai-oauth.d.ts +66 -0
  24. package/dist/auth/openai-oauth.d.ts.map +1 -0
  25. package/dist/auth/openai-oauth.js +640 -0
  26. package/dist/auth/openai-oauth.js.map +1 -0
  27. package/dist/cli.d.ts +14 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +254 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/commands.d.ts +118 -0
  32. package/dist/commands.d.ts.map +1 -0
  33. package/dist/commands.js +862 -0
  34. package/dist/commands.js.map +1 -0
  35. package/dist/config.d.ts +130 -0
  36. package/dist/config.d.ts.map +1 -0
  37. package/dist/config.js +648 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/context-rendering.d.ts +69 -0
  40. package/dist/context-rendering.d.ts.map +1 -0
  41. package/dist/context-rendering.js +250 -0
  42. package/dist/context-rendering.js.map +1 -0
  43. package/dist/document-projection.d.ts +12 -0
  44. package/dist/document-projection.d.ts.map +1 -0
  45. package/dist/document-projection.js +75 -0
  46. package/dist/document-projection.js.map +1 -0
  47. package/dist/ephemeral-log.d.ts +15 -0
  48. package/dist/ephemeral-log.d.ts.map +1 -0
  49. package/dist/ephemeral-log.js +173 -0
  50. package/dist/ephemeral-log.js.map +1 -0
  51. package/dist/file-attach.d.ts +89 -0
  52. package/dist/file-attach.d.ts.map +1 -0
  53. package/dist/file-attach.js +571 -0
  54. package/dist/file-attach.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +43 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/init-wizard.d.ts +13 -0
  60. package/dist/init-wizard.d.ts.map +1 -0
  61. package/dist/init-wizard.js +328 -0
  62. package/dist/init-wizard.js.map +1 -0
  63. package/dist/log-entry.d.ts +104 -0
  64. package/dist/log-entry.d.ts.map +1 -0
  65. package/dist/log-entry.js +292 -0
  66. package/dist/log-entry.js.map +1 -0
  67. package/dist/log-projection.d.ts +73 -0
  68. package/dist/log-projection.d.ts.map +1 -0
  69. package/dist/log-projection.js +651 -0
  70. package/dist/log-projection.js.map +1 -0
  71. package/dist/mcp-client.d.ts +55 -0
  72. package/dist/mcp-client.d.ts.map +1 -0
  73. package/dist/mcp-client.js +402 -0
  74. package/dist/mcp-client.js.map +1 -0
  75. package/dist/model-selection.d.ts +16 -0
  76. package/dist/model-selection.d.ts.map +1 -0
  77. package/dist/model-selection.js +181 -0
  78. package/dist/model-selection.js.map +1 -0
  79. package/dist/network-retry.d.ts +38 -0
  80. package/dist/network-retry.d.ts.map +1 -0
  81. package/dist/network-retry.js +140 -0
  82. package/dist/network-retry.js.map +1 -0
  83. package/dist/persistence.d.ts +104 -0
  84. package/dist/persistence.d.ts.map +1 -0
  85. package/dist/persistence.js +644 -0
  86. package/dist/persistence.js.map +1 -0
  87. package/dist/primitives/context.d.ts +29 -0
  88. package/dist/primitives/context.d.ts.map +1 -0
  89. package/dist/primitives/context.js +85 -0
  90. package/dist/primitives/context.js.map +1 -0
  91. package/dist/progress.d.ts +51 -0
  92. package/dist/progress.d.ts.map +1 -0
  93. package/dist/progress.js +229 -0
  94. package/dist/progress.js.map +1 -0
  95. package/dist/provider-presets.d.ts +34 -0
  96. package/dist/provider-presets.d.ts.map +1 -0
  97. package/dist/provider-presets.js +181 -0
  98. package/dist/provider-presets.js.map +1 -0
  99. package/dist/providers/anthropic.d.ts +32 -0
  100. package/dist/providers/anthropic.d.ts.map +1 -0
  101. package/dist/providers/anthropic.js +450 -0
  102. package/dist/providers/anthropic.js.map +1 -0
  103. package/dist/providers/base.d.ts +135 -0
  104. package/dist/providers/base.d.ts.map +1 -0
  105. package/dist/providers/base.js +104 -0
  106. package/dist/providers/base.js.map +1 -0
  107. package/dist/providers/glm.d.ts +18 -0
  108. package/dist/providers/glm.d.ts.map +1 -0
  109. package/dist/providers/glm.js +59 -0
  110. package/dist/providers/glm.js.map +1 -0
  111. package/dist/providers/kimi.d.ts +23 -0
  112. package/dist/providers/kimi.d.ts.map +1 -0
  113. package/dist/providers/kimi.js +89 -0
  114. package/dist/providers/kimi.js.map +1 -0
  115. package/dist/providers/minimax.d.ts +20 -0
  116. package/dist/providers/minimax.d.ts.map +1 -0
  117. package/dist/providers/minimax.js +192 -0
  118. package/dist/providers/minimax.js.map +1 -0
  119. package/dist/providers/openai-chat.d.ts +33 -0
  120. package/dist/providers/openai-chat.d.ts.map +1 -0
  121. package/dist/providers/openai-chat.js +543 -0
  122. package/dist/providers/openai-chat.js.map +1 -0
  123. package/dist/providers/openai-responses.d.ts +26 -0
  124. package/dist/providers/openai-responses.d.ts.map +1 -0
  125. package/dist/providers/openai-responses.js +443 -0
  126. package/dist/providers/openai-responses.js.map +1 -0
  127. package/dist/providers/openrouter.d.ts +24 -0
  128. package/dist/providers/openrouter.d.ts.map +1 -0
  129. package/dist/providers/openrouter.js +177 -0
  130. package/dist/providers/openrouter.js.map +1 -0
  131. package/dist/providers/registry.d.ts +7 -0
  132. package/dist/providers/registry.d.ts.map +1 -0
  133. package/dist/providers/registry.js +38 -0
  134. package/dist/providers/registry.js.map +1 -0
  135. package/dist/security/path.d.ts +51 -0
  136. package/dist/security/path.d.ts.map +1 -0
  137. package/dist/security/path.js +187 -0
  138. package/dist/security/path.js.map +1 -0
  139. package/dist/security/sensitive-files.d.ts +3 -0
  140. package/dist/security/sensitive-files.d.ts.map +1 -0
  141. package/dist/security/sensitive-files.js +41 -0
  142. package/dist/security/sensitive-files.js.map +1 -0
  143. package/dist/session.d.ts +446 -0
  144. package/dist/session.d.ts.map +1 -0
  145. package/dist/session.js +4595 -0
  146. package/dist/session.js.map +1 -0
  147. package/dist/settings.d.ts +46 -0
  148. package/dist/settings.d.ts.map +1 -0
  149. package/dist/settings.js +134 -0
  150. package/dist/settings.js.map +1 -0
  151. package/dist/show-context.d.ts +35 -0
  152. package/dist/show-context.d.ts.map +1 -0
  153. package/dist/show-context.js +320 -0
  154. package/dist/show-context.js.map +1 -0
  155. package/dist/skills/loader.d.ts +49 -0
  156. package/dist/skills/loader.d.ts.map +1 -0
  157. package/dist/skills/loader.js +166 -0
  158. package/dist/skills/loader.js.map +1 -0
  159. package/dist/summarize-context.d.ts +29 -0
  160. package/dist/summarize-context.d.ts.map +1 -0
  161. package/dist/summarize-context.js +247 -0
  162. package/dist/summarize-context.js.map +1 -0
  163. package/dist/templates/loader.d.ts +104 -0
  164. package/dist/templates/loader.d.ts.map +1 -0
  165. package/dist/templates/loader.js +514 -0
  166. package/dist/templates/loader.js.map +1 -0
  167. package/dist/tools/basic.d.ts +29 -0
  168. package/dist/tools/basic.d.ts.map +1 -0
  169. package/dist/tools/basic.js +2079 -0
  170. package/dist/tools/basic.js.map +1 -0
  171. package/dist/tools/comm.d.ts +17 -0
  172. package/dist/tools/comm.d.ts.map +1 -0
  173. package/dist/tools/comm.js +192 -0
  174. package/dist/tools/comm.js.map +1 -0
  175. package/dist/tools/web-fetch.d.ts +11 -0
  176. package/dist/tools/web-fetch.d.ts.map +1 -0
  177. package/dist/tools/web-fetch.js +237 -0
  178. package/dist/tools/web-fetch.js.map +1 -0
  179. package/dist/tools/web-search.d.ts +24 -0
  180. package/dist/tools/web-search.d.ts.map +1 -0
  181. package/dist/tools/web-search.js +51 -0
  182. package/dist/tools/web-search.js.map +1 -0
  183. package/dist/tui/app.d.ts +35 -0
  184. package/dist/tui/app.d.ts.map +1 -0
  185. package/dist/tui/app.js +1042 -0
  186. package/dist/tui/app.js.map +1 -0
  187. package/dist/tui/checkbox-picker.d.ts +35 -0
  188. package/dist/tui/checkbox-picker.d.ts.map +1 -0
  189. package/dist/tui/checkbox-picker.js +85 -0
  190. package/dist/tui/checkbox-picker.js.map +1 -0
  191. package/dist/tui/command-picker.d.ts +31 -0
  192. package/dist/tui/command-picker.d.ts.map +1 -0
  193. package/dist/tui/command-picker.js +113 -0
  194. package/dist/tui/command-picker.js.map +1 -0
  195. package/dist/tui/components/ask-panel.d.ts +21 -0
  196. package/dist/tui/components/ask-panel.d.ts.map +1 -0
  197. package/dist/tui/components/ask-panel.js +81 -0
  198. package/dist/tui/components/ask-panel.js.map +1 -0
  199. package/dist/tui/components/conversation-panel.d.ts +68 -0
  200. package/dist/tui/components/conversation-panel.d.ts.map +1 -0
  201. package/dist/tui/components/conversation-panel.js +611 -0
  202. package/dist/tui/components/conversation-panel.js.map +1 -0
  203. package/dist/tui/components/input-panel.d.ts +27 -0
  204. package/dist/tui/components/input-panel.d.ts.map +1 -0
  205. package/dist/tui/components/input-panel.js +725 -0
  206. package/dist/tui/components/input-panel.js.map +1 -0
  207. package/dist/tui/components/logo-panel.d.ts +14 -0
  208. package/dist/tui/components/logo-panel.d.ts.map +1 -0
  209. package/dist/tui/components/logo-panel.js +37 -0
  210. package/dist/tui/components/logo-panel.js.map +1 -0
  211. package/dist/tui/components/plan-panel.d.ts +10 -0
  212. package/dist/tui/components/plan-panel.d.ts.map +1 -0
  213. package/dist/tui/components/plan-panel.js +8 -0
  214. package/dist/tui/components/plan-panel.js.map +1 -0
  215. package/dist/tui/components/status-bar.d.ts +24 -0
  216. package/dist/tui/components/status-bar.d.ts.map +1 -0
  217. package/dist/tui/components/status-bar.js +80 -0
  218. package/dist/tui/components/status-bar.js.map +1 -0
  219. package/dist/tui/input/editor-state.d.ts +22 -0
  220. package/dist/tui/input/editor-state.d.ts.map +1 -0
  221. package/dist/tui/input/editor-state.js +157 -0
  222. package/dist/tui/input/editor-state.js.map +1 -0
  223. package/dist/tui/input/keymap.d.ts +3 -0
  224. package/dist/tui/input/keymap.d.ts.map +1 -0
  225. package/dist/tui/input/keymap.js +72 -0
  226. package/dist/tui/input/keymap.js.map +1 -0
  227. package/dist/tui/input/paste-slots.d.ts +17 -0
  228. package/dist/tui/input/paste-slots.d.ts.map +1 -0
  229. package/dist/tui/input/paste-slots.js +46 -0
  230. package/dist/tui/input/paste-slots.js.map +1 -0
  231. package/dist/tui/input/paste.d.ts +15 -0
  232. package/dist/tui/input/paste.d.ts.map +1 -0
  233. package/dist/tui/input/paste.js +35 -0
  234. package/dist/tui/input/paste.js.map +1 -0
  235. package/dist/tui/input/protocol.d.ts +9 -0
  236. package/dist/tui/input/protocol.d.ts.map +1 -0
  237. package/dist/tui/input/protocol.js +387 -0
  238. package/dist/tui/input/protocol.js.map +1 -0
  239. package/dist/tui/input/sanitize.d.ts +6 -0
  240. package/dist/tui/input/sanitize.d.ts.map +1 -0
  241. package/dist/tui/input/sanitize.js +20 -0
  242. package/dist/tui/input/sanitize.js.map +1 -0
  243. package/dist/tui/input/types.d.ts +18 -0
  244. package/dist/tui/input/types.d.ts.map +1 -0
  245. package/dist/tui/input/types.js +2 -0
  246. package/dist/tui/input/types.js.map +1 -0
  247. package/dist/tui/launch.d.ts +23 -0
  248. package/dist/tui/launch.d.ts.map +1 -0
  249. package/dist/tui/launch.js +104 -0
  250. package/dist/tui/launch.js.map +1 -0
  251. package/dist/tui/theme.d.ts +20 -0
  252. package/dist/tui/theme.d.ts.map +1 -0
  253. package/dist/tui/theme.js +29 -0
  254. package/dist/tui/theme.js.map +1 -0
  255. package/dist/tui/types.d.ts +136 -0
  256. package/dist/tui/types.d.ts.map +1 -0
  257. package/dist/tui/types.js +9 -0
  258. package/dist/tui/types.js.map +1 -0
  259. package/package.json +76 -0
  260. package/prompts/sections/agents_md.md +23 -0
  261. package/prompts/sections/important_log.md +16 -0
  262. package/prompts/sections/system_mechanisms.md +18 -0
  263. package/prompts/tools/apply_patch.md +31 -0
  264. package/prompts/tools/ask.md +18 -0
  265. package/prompts/tools/bash.md +13 -0
  266. package/prompts/tools/bash_background.md +9 -0
  267. package/prompts/tools/bash_output.md +9 -0
  268. package/prompts/tools/check_status.md +3 -0
  269. package/prompts/tools/diff.md +5 -0
  270. package/prompts/tools/edit_file.md +11 -0
  271. package/prompts/tools/glob.md +7 -0
  272. package/prompts/tools/grep.md +20 -0
  273. package/prompts/tools/kill_agent.md +3 -0
  274. package/prompts/tools/kill_shell.md +5 -0
  275. package/prompts/tools/list_dir.md +5 -0
  276. package/prompts/tools/plan.md +252 -0
  277. package/prompts/tools/read_file.md +9 -0
  278. package/prompts/tools/show_context.md +12 -0
  279. package/prompts/tools/skill.md +7 -0
  280. package/prompts/tools/spawn_agent.md +195 -0
  281. package/prompts/tools/summarize_context.md +122 -0
  282. package/prompts/tools/test.md +5 -0
  283. package/prompts/tools/wait.md +17 -0
  284. package/prompts/tools/web_fetch.md +9 -0
  285. package/prompts/tools/web_search.md +5 -0
  286. package/prompts/tools/write_file.md +11 -0
  287. package/skills/.staging/.gitkeep +0 -0
  288. package/skills/explain-code/SKILL.md +15 -0
  289. package/skills/skill-manager/SKILL.md +83 -0
@@ -0,0 +1,651 @@
1
+ /**
2
+ * Log projection functions — derive TUI entries and API messages from the log.
3
+ *
4
+ * Both real-time conversation and resume use the same projection logic,
5
+ * guaranteeing 100% consistency.
6
+ */
7
+ import { mergeConsecutiveSameRole } from "./context-rendering.js";
8
+ import { truncateSummaryText } from "./summarize-context.js";
9
+ // ------------------------------------------------------------------
10
+ // TuiDisplayKind → ConversationEntryKind mapping
11
+ // ------------------------------------------------------------------
12
+ const DISPLAY_KIND_TO_ENTRY_KIND = {
13
+ user: "user",
14
+ assistant: "assistant",
15
+ reasoning: "reasoning",
16
+ progress: "progress",
17
+ tool_call: "tool_call",
18
+ status: "status",
19
+ error: "error",
20
+ compact_mark: "compact_mark",
21
+ tool_result: "tool_result",
22
+ };
23
+ const INTERRUPTED_MARKER_TEXT = "[Interrupted here.]";
24
+ const INTERRUPTED_MARKER_SUFFIX = ` ${INTERRUPTED_MARKER_TEXT}`;
25
+ const PRIMARY_ROUND_ENTRY_TYPES = new Set([
26
+ "assistant_text",
27
+ "reasoning",
28
+ "tool_call",
29
+ "tool_result",
30
+ ]);
31
+ function isHiddenSubAgentLifecycle(entry) {
32
+ return entry.type === "sub_agent_start";
33
+ }
34
+ function isProjectableTuiEntry(entry) {
35
+ if (entry.discarded)
36
+ return false;
37
+ if (!entry.tuiVisible)
38
+ return false;
39
+ if (entry.type === "summary")
40
+ return false;
41
+ if (isHiddenSubAgentLifecycle(entry))
42
+ return false;
43
+ return true;
44
+ }
45
+ function toConversationEntry(entry, toolElapsedMap) {
46
+ if (entry.type === "sub_agent_end") {
47
+ const subAgentId = entry.meta["subAgentId"];
48
+ const subAgentName = entry.meta["subAgentName"];
49
+ const elapsed = entry.meta["elapsed"];
50
+ const label = [
51
+ typeof subAgentId === "number" ? `#${subAgentId}` : "#?",
52
+ typeof subAgentName === "string" ? subAgentName : "sub-agent",
53
+ ].join(" ");
54
+ const elapsedStr = typeof elapsed === "number" ? elapsed.toFixed(1) : "?";
55
+ return {
56
+ kind: "sub_agent_done",
57
+ text: `[${label}] [done] (${elapsedStr}s)`,
58
+ id: entry.id,
59
+ };
60
+ }
61
+ const kind = entry.displayKind
62
+ ? DISPLAY_KIND_TO_ENTRY_KIND[entry.displayKind]
63
+ : "status";
64
+ const ce = {
65
+ kind,
66
+ text: entry.display,
67
+ id: entry.id,
68
+ };
69
+ if (entry.meta["tuiDim"])
70
+ ce.dim = true;
71
+ // Attach timing info for tool_call entries
72
+ if (entry.type === "tool_call") {
73
+ ce.startedAt = entry.timestamp;
74
+ const toolCallId = entry.meta["toolCallId"];
75
+ if (typeof toolCallId === "string" && toolElapsedMap?.has(toolCallId)) {
76
+ ce.elapsedMs = toolElapsedMap.get(toolCallId);
77
+ }
78
+ }
79
+ return ce;
80
+ }
81
+ function toConversationEntries(entry, toolElapsedMap) {
82
+ const ce = toConversationEntry(entry, toolElapsedMap);
83
+ if (ce.kind !== "assistant") {
84
+ return [ce];
85
+ }
86
+ if (ce.text === INTERRUPTED_MARKER_TEXT) {
87
+ return [
88
+ {
89
+ kind: "interrupted_marker",
90
+ text: INTERRUPTED_MARKER_TEXT,
91
+ id: ce.id,
92
+ },
93
+ ];
94
+ }
95
+ if (!ce.text.endsWith(INTERRUPTED_MARKER_SUFFIX)) {
96
+ return [ce];
97
+ }
98
+ const assistantText = ce.text.slice(0, -INTERRUPTED_MARKER_SUFFIX.length);
99
+ const entries = [];
100
+ if (assistantText.trim().length > 0) {
101
+ entries.push({
102
+ ...ce,
103
+ text: assistantText,
104
+ });
105
+ }
106
+ entries.push({
107
+ kind: "interrupted_marker",
108
+ text: INTERRUPTED_MARKER_TEXT,
109
+ id: ce.id ? `${ce.id}:interrupt` : undefined,
110
+ });
111
+ return entries;
112
+ }
113
+ function isPrimaryRoundEntry(entry) {
114
+ return (isProjectableTuiEntry(entry) &&
115
+ entry.roundIndex !== undefined &&
116
+ PRIMARY_ROUND_ENTRY_TYPES.has(entry.type));
117
+ }
118
+ function buildSubAgentRollup(entries) {
119
+ if (entries.length === 0)
120
+ return null;
121
+ const lastFive = entries.slice(-5);
122
+ const omitted = entries.length - lastFive.length;
123
+ const noun = lastFive.length === 1 ? "tool call" : "tool calls";
124
+ const header = omitted > 0
125
+ ? `${omitted} earlier ${noun} omitted, last ${lastFive.length}:`
126
+ : `Last ${lastFive.length} sub-agent ${noun}:`;
127
+ return {
128
+ kind: "sub_agent_rollup",
129
+ id: `subrollup-${entries[0].id}`,
130
+ text: [header, ...lastFive.map((entry) => entry.display)].join("\n"),
131
+ };
132
+ }
133
+ /**
134
+ * Build a map of toolCallId → elapsed time (ms) by pairing
135
+ * tool_call and tool_result entries.
136
+ */
137
+ function buildToolElapsedMap(entries) {
138
+ const callTimestamps = new Map();
139
+ const elapsed = new Map();
140
+ for (const entry of entries) {
141
+ if (entry.type === "tool_call") {
142
+ const id = entry.meta["toolCallId"];
143
+ if (typeof id === "string") {
144
+ callTimestamps.set(id, entry.timestamp);
145
+ }
146
+ }
147
+ else if (entry.type === "tool_result") {
148
+ const id = entry.meta["toolCallId"];
149
+ if (typeof id === "string" && callTimestamps.has(id)) {
150
+ elapsed.set(id, entry.timestamp - callTimestamps.get(id));
151
+ }
152
+ }
153
+ }
154
+ return elapsed;
155
+ }
156
+ function projectTuiWindow(entries) {
157
+ const result = [];
158
+ const pendingSubAgentCalls = [];
159
+ const toolElapsedMap = buildToolElapsedMap(entries);
160
+ const flushPendingSubAgentCalls = () => {
161
+ const rollup = buildSubAgentRollup(pendingSubAgentCalls);
162
+ pendingSubAgentCalls.length = 0;
163
+ if (rollup)
164
+ result.push(rollup);
165
+ };
166
+ let i = 0;
167
+ while (i < entries.length) {
168
+ const entry = entries[i];
169
+ if (!isProjectableTuiEntry(entry)) {
170
+ i++;
171
+ continue;
172
+ }
173
+ if (entry.type === "sub_agent_tool_call") {
174
+ pendingSubAgentCalls.push(entry);
175
+ i++;
176
+ continue;
177
+ }
178
+ if (isPrimaryRoundEntry(entry)) {
179
+ if (pendingSubAgentCalls.length > 0) {
180
+ flushPendingSubAgentCalls();
181
+ }
182
+ const turnIndex = entry.turnIndex;
183
+ const roundIndex = entry.roundIndex;
184
+ while (i < entries.length) {
185
+ const candidate = entries[i];
186
+ if (!isProjectableTuiEntry(candidate)) {
187
+ i++;
188
+ continue;
189
+ }
190
+ if (candidate.type === "sub_agent_tool_call") {
191
+ pendingSubAgentCalls.push(candidate);
192
+ i++;
193
+ continue;
194
+ }
195
+ if (candidate.turnIndex === turnIndex &&
196
+ candidate.roundIndex === roundIndex &&
197
+ PRIMARY_ROUND_ENTRY_TYPES.has(candidate.type)) {
198
+ result.push(...toConversationEntries(candidate, toolElapsedMap));
199
+ i++;
200
+ continue;
201
+ }
202
+ break;
203
+ }
204
+ if (pendingSubAgentCalls.length > 0) {
205
+ flushPendingSubAgentCalls();
206
+ }
207
+ continue;
208
+ }
209
+ if (pendingSubAgentCalls.length > 0) {
210
+ flushPendingSubAgentCalls();
211
+ }
212
+ result.push(...toConversationEntries(entry, toolElapsedMap));
213
+ i++;
214
+ }
215
+ if (pendingSubAgentCalls.length > 0) {
216
+ flushPendingSubAgentCalls();
217
+ }
218
+ return result;
219
+ }
220
+ /**
221
+ * Project log entries into ConversationEntry[] for TUI rendering.
222
+ *
223
+ * Rules:
224
+ * 1. Determine fold boundary based on compact markers
225
+ * 2. Skip: folded entries, tuiVisible===false, discarded, summary entries
226
+ * 3. Map (displayKind, display) → ConversationEntry
227
+ */
228
+ export function projectToTuiEntries(entries, options) {
229
+ const threshold = options?.compactFoldThreshold ?? 3;
230
+ // Find all compact_marker indices
231
+ const compactMarkerIndices = [];
232
+ for (let i = 0; i < entries.length; i++) {
233
+ if (entries[i].type === "compact_marker" && !entries[i].discarded) {
234
+ compactMarkerIndices.push(i);
235
+ }
236
+ }
237
+ // Determine fold boundary: if N >= threshold, fold entries before the (N - threshold + 1)th marker
238
+ let foldEndIdx = -1; // entries at index <= foldEndIdx are folded
239
+ let foldedCount = 0;
240
+ let foldedCompactCount = 0;
241
+ if (compactMarkerIndices.length >= threshold) {
242
+ const foldUpToMarker = compactMarkerIndices[compactMarkerIndices.length - threshold];
243
+ foldEndIdx = foldUpToMarker;
244
+ foldedCount = projectTuiWindow(entries.slice(0, foldEndIdx + 1)).length;
245
+ foldedCompactCount = compactMarkerIndices.length - threshold + 1;
246
+ }
247
+ const result = [];
248
+ // Add fold placeholder if needed
249
+ if (foldEndIdx >= 0 && foldedCount > 0) {
250
+ result.push({
251
+ kind: "status",
252
+ text: `\u25b8 ${foldedCount} earlier entries (${foldedCompactCount} compacts)`,
253
+ });
254
+ }
255
+ result.push(...projectTuiWindow(entries.slice(foldEndIdx + 1)));
256
+ return result;
257
+ }
258
+ /**
259
+ * Project log entries into InternalMessage[] for provider consumption.
260
+ *
261
+ * Algorithm:
262
+ * 1. Re-render system prompt (or use log's)
263
+ * 2. Find last compact_marker → API window start
264
+ * 3. Insert compact_context if present
265
+ * 4. Iterate entries, skip: apiRole===null, summarized, discarded, archived with null content
266
+ * 5. Group by roundIndex to build assistant messages
267
+ */
268
+ export function projectToApiMessages(entries, options) {
269
+ // Step 1: Find system prompt
270
+ let systemPromptContent = "";
271
+ for (const e of entries) {
272
+ if (e.type === "system_prompt" && !e.discarded) {
273
+ systemPromptContent = options?.systemPrompt ?? e.content;
274
+ break;
275
+ }
276
+ }
277
+ // Step 2: Find last compact_marker → window start
278
+ let windowStartIdx = 0;
279
+ for (let i = entries.length - 1; i >= 0; i--) {
280
+ if (entries[i].type === "compact_marker" && !entries[i].discarded) {
281
+ windowStartIdx = i + 1;
282
+ break;
283
+ }
284
+ }
285
+ // Step 3: Find compact_context for the current window
286
+ let compactContextContent = null;
287
+ let compactContextId;
288
+ for (let i = windowStartIdx; i < entries.length; i++) {
289
+ const e = entries[i];
290
+ if (e.type === "compact_context" && !e.discarded && !e.summarized) {
291
+ compactContextContent = e.content;
292
+ const ctxId = e.meta["contextId"];
293
+ compactContextId = ctxId !== undefined && ctxId !== null ? String(ctxId) : undefined;
294
+ break;
295
+ }
296
+ }
297
+ // Also check just before the window start (compact_context may be right after compact_marker)
298
+ if (!compactContextContent && windowStartIdx > 0) {
299
+ for (let i = windowStartIdx; i < entries.length && i < windowStartIdx + 5; i++) {
300
+ const e = entries[i];
301
+ if (e.type === "compact_context" && !e.discarded && !e.summarized) {
302
+ compactContextContent = e.content;
303
+ const ctxId = e.meta["contextId"];
304
+ compactContextId = ctxId !== undefined && ctxId !== null ? String(ctxId) : undefined;
305
+ break;
306
+ }
307
+ }
308
+ }
309
+ // Copy annotations map so we can delete entries after first injection per group
310
+ const annotations = options?.showContextAnnotations
311
+ ? new Map(options.showContextAnnotations)
312
+ : null;
313
+ // Build messages
314
+ const messages = [];
315
+ // System prompt
316
+ if (systemPromptContent) {
317
+ messages.push({ role: "system", content: systemPromptContent });
318
+ }
319
+ // Compact context (as user message)
320
+ if (compactContextContent) {
321
+ let content = compactContextContent;
322
+ if (compactContextId !== undefined && annotations?.has(compactContextId)) {
323
+ content = prependAnnotation(content, annotations.get(compactContextId));
324
+ annotations.delete(compactContextId);
325
+ }
326
+ const compactMsg = { role: "user", content };
327
+ if (compactContextId !== undefined)
328
+ compactMsg["_context_id"] = compactContextId;
329
+ messages.push(compactMsg);
330
+ }
331
+ // Step 4-5: Collect window entries and group by round
332
+ const windowEntries = entries.slice(windowStartIdx).filter((e) => {
333
+ if (e.summarized)
334
+ return false;
335
+ if (e.discarded)
336
+ return false;
337
+ if (e.archived && e.content === null)
338
+ return false;
339
+ if (e.type === "system_prompt")
340
+ return false; // already handled
341
+ if (e.type === "compact_context")
342
+ return false; // already handled
343
+ // reasoning has apiRole=null but is grouped with assistant entries
344
+ if (e.type === "reasoning")
345
+ return true;
346
+ if (e.apiRole === null)
347
+ return false;
348
+ return true;
349
+ });
350
+ // Group assistant-related entries (assistant_text, reasoning, tool_call, no_reply)
351
+ // by (turnIndex, roundIndex), then emit them as single assistant messages.
352
+ // Non-assistant entries are emitted directly.
353
+ let i = 0;
354
+ while (i < windowEntries.length) {
355
+ const entry = windowEntries[i];
356
+ if ((entry.apiRole === "assistant" || entry.type === "reasoning") &&
357
+ entry.roundIndex !== undefined) {
358
+ // Collect all entries in this round
359
+ const roundIdx = entry.roundIndex;
360
+ const turnIdx = entry.turnIndex;
361
+ const roundEntries = [];
362
+ while (i < windowEntries.length &&
363
+ windowEntries[i].turnIndex === turnIdx &&
364
+ windowEntries[i].roundIndex === roundIdx &&
365
+ (windowEntries[i].apiRole === "assistant" ||
366
+ windowEntries[i].type === "reasoning")) {
367
+ roundEntries.push(windowEntries[i]);
368
+ i++;
369
+ }
370
+ messages.push(buildAssistantMessage(roundEntries, entries));
371
+ }
372
+ else if (entry.apiRole === "user") {
373
+ let content = resolveImageRefs(entry.content, options?.resolveImageRef);
374
+ // Preserve _context_id for spatial index (summarize_context)
375
+ const ctxId = entry.meta["contextId"];
376
+ // Inject show_context annotation if active
377
+ if (ctxId !== undefined && annotations?.has(String(ctxId))) {
378
+ content = prependAnnotation(content, annotations.get(String(ctxId)));
379
+ }
380
+ const userMsg = { role: "user", content };
381
+ if (ctxId !== undefined)
382
+ userMsg["_context_id"] = ctxId;
383
+ // Preserve summary metadata
384
+ if (entry.type === "summary") {
385
+ userMsg["_is_summary"] = true;
386
+ userMsg["_summary_depth"] = entry.meta["summaryDepth"] ?? 1;
387
+ userMsg["_summarized_ids"] = entry.meta["summarizedEntryIds"] ?? [];
388
+ }
389
+ messages.push(userMsg);
390
+ i++;
391
+ }
392
+ else if (entry.apiRole === "tool_result") {
393
+ const resultContent = entry.content;
394
+ let trContent = resultContent.content;
395
+ // Preserve _context_id for spatial index
396
+ const trCtxId = entry.meta["contextId"];
397
+ // Inject show_context annotation into first tool_result per context group
398
+ if (trCtxId !== undefined && annotations?.has(String(trCtxId))) {
399
+ trContent = `${annotations.get(String(trCtxId))}\n\n${trContent}`;
400
+ // Remove from annotations so only the first tool_result per group gets it
401
+ annotations.delete(String(trCtxId));
402
+ }
403
+ // Check for multimodal content blocks in metadata
404
+ const toolMeta = entry.meta["toolMetadata"];
405
+ const contentBlocks = toolMeta?.["_contentBlocks"];
406
+ const trMsg = {
407
+ role: "tool_result",
408
+ tool_call_id: entry.meta.toolCallId,
409
+ tool_name: entry.meta.toolName,
410
+ content: contentBlocks ?? trContent,
411
+ tool_summary: resultContent.toolSummary,
412
+ };
413
+ if (trCtxId !== undefined)
414
+ trMsg["_context_id"] = trCtxId;
415
+ messages.push(trMsg);
416
+ i++;
417
+ }
418
+ else {
419
+ // Fallback
420
+ messages.push({ role: entry.apiRole, content: entry.content });
421
+ i++;
422
+ }
423
+ }
424
+ // Inject important log if provided
425
+ if (options?.importantLog?.trim()) {
426
+ injectImportantLog(messages, options.importantLog);
427
+ }
428
+ // Inject AGENTS.md content if provided
429
+ if (options?.agentsMd?.trim()) {
430
+ injectAgentsMd(messages, options.agentsMd);
431
+ }
432
+ let projected = options?.truncateSummarizeToolArgs === false
433
+ ? messages
434
+ : truncateSummarizeToolArgs(messages);
435
+ if (options?.requiresAlternatingRoles) {
436
+ projected = mergeConsecutiveSameRole(projected);
437
+ }
438
+ return projected;
439
+ }
440
+ // ------------------------------------------------------------------
441
+ // show_context annotation injection
442
+ // ------------------------------------------------------------------
443
+ /**
444
+ * Prepend a show_context annotation to message content.
445
+ * Handles both string content and array content blocks.
446
+ */
447
+ function prependAnnotation(content, annotation) {
448
+ if (typeof content === "string") {
449
+ return `${annotation}\n\n${content}`;
450
+ }
451
+ if (Array.isArray(content)) {
452
+ const copy = content.map((b) => ({ ...b }));
453
+ // Prepend annotation as a text block
454
+ copy.unshift({ type: "text", text: `${annotation}\n\n` });
455
+ return copy;
456
+ }
457
+ return content;
458
+ }
459
+ // ------------------------------------------------------------------
460
+ // Image ref resolution
461
+ // ------------------------------------------------------------------
462
+ /**
463
+ * Resolve image_ref blocks in content to inline base64 for API consumption.
464
+ * If content is a string or resolver is not provided, returns as-is.
465
+ */
466
+ function resolveImageRefs(content, resolver) {
467
+ if (!resolver || !Array.isArray(content))
468
+ return content;
469
+ let hasRef = false;
470
+ for (const block of content) {
471
+ if (block &&
472
+ typeof block === "object" &&
473
+ block["type"] === "image_ref") {
474
+ hasRef = true;
475
+ break;
476
+ }
477
+ }
478
+ if (!hasRef)
479
+ return content;
480
+ return content.map((block) => {
481
+ if (block["type"] !== "image_ref")
482
+ return block;
483
+ const resolved = resolver(block["path"]);
484
+ if (!resolved)
485
+ return block; // fallback: pass through
486
+ return {
487
+ type: "image",
488
+ data: resolved.data,
489
+ media_type: resolved.media_type,
490
+ };
491
+ });
492
+ }
493
+ // ------------------------------------------------------------------
494
+ // Helpers
495
+ // ------------------------------------------------------------------
496
+ /**
497
+ * Build a single assistant API message from grouped round entries.
498
+ */
499
+ function buildAssistantMessage(roundEntries, _allEntries) {
500
+ const msg = { role: "assistant" };
501
+ // Extract reasoning
502
+ const reasoning = roundEntries.find((e) => e.type === "reasoning");
503
+ if (reasoning) {
504
+ msg.reasoning_content = reasoning.content;
505
+ if (reasoning.meta.reasoningState !== undefined) {
506
+ msg._reasoning_state = reasoning.meta.reasoningState;
507
+ }
508
+ }
509
+ // Extract assistant_text
510
+ const text = roundEntries.find((e) => e.type === "assistant_text");
511
+ // Extract tool_calls
512
+ const toolCalls = roundEntries
513
+ .filter((e) => e.type === "tool_call")
514
+ .map((e) => e.content);
515
+ // Extract no_reply
516
+ const noReply = roundEntries.find((e) => e.type === "no_reply");
517
+ if (toolCalls.length > 0) {
518
+ msg.tool_calls = toolCalls;
519
+ if (text) {
520
+ msg.text = text.content;
521
+ }
522
+ }
523
+ else if (noReply) {
524
+ msg.content = noReply.content;
525
+ }
526
+ else if (text) {
527
+ msg.content = text.content;
528
+ }
529
+ // Preserve _context_id from the first entry with one
530
+ for (const e of roundEntries) {
531
+ const ctxId = e.meta["contextId"];
532
+ if (ctxId !== undefined) {
533
+ msg["_context_id"] = ctxId;
534
+ break;
535
+ }
536
+ }
537
+ return msg;
538
+ }
539
+ /**
540
+ * Inject important log content after the system prompt.
541
+ * Merges into the first user message if possible.
542
+ */
543
+ function injectImportantLog(messages, logContent) {
544
+ const header = "[IMPORTANT LOG]\n" +
545
+ "The following is your persistent engineering notebook " +
546
+ "(\"(empty file)\" means the notebook is blank — it is not real content):\n\n";
547
+ const fullContent = header + logContent;
548
+ // Find position after system prompt(s)
549
+ let insertIdx = 0;
550
+ while (insertIdx < messages.length && messages[insertIdx].role === "system") {
551
+ insertIdx++;
552
+ }
553
+ if (insertIdx < messages.length && messages[insertIdx].role === "user") {
554
+ // Merge into first user message
555
+ const first = messages[insertIdx];
556
+ messages[insertIdx] = {
557
+ ...first,
558
+ content: mergeMessageContent(fullContent, first.content),
559
+ };
560
+ }
561
+ else {
562
+ // Insert standalone user message
563
+ messages.splice(insertIdx, 0, { role: "user", content: fullContent });
564
+ }
565
+ }
566
+ /**
567
+ * Inject AGENTS.md content after the system prompt (and after important log).
568
+ * Merges into the first user message if possible.
569
+ */
570
+ function injectAgentsMd(messages, agentsMdContent) {
571
+ const header = "[AGENTS.MD — PERSISTENT MEMORY]\n" +
572
+ "The following is your persistent memory across sessions:\n\n";
573
+ const fullContent = header + agentsMdContent;
574
+ // Find position after system prompt(s)
575
+ let insertIdx = 0;
576
+ while (insertIdx < messages.length && messages[insertIdx].role === "system") {
577
+ insertIdx++;
578
+ }
579
+ if (insertIdx < messages.length && messages[insertIdx].role === "user") {
580
+ // Merge into first user message (important log may already be merged there)
581
+ const first = messages[insertIdx];
582
+ messages[insertIdx] = {
583
+ ...first,
584
+ content: mergeMessageContent(fullContent, first.content),
585
+ };
586
+ }
587
+ else {
588
+ // Insert standalone user message
589
+ messages.splice(insertIdx, 0, { role: "user", content: fullContent });
590
+ }
591
+ }
592
+ function truncateSummarizeToolArgs(messages) {
593
+ return messages.map((msg) => {
594
+ const toolCalls = msg["tool_calls"];
595
+ if (!toolCalls?.length)
596
+ return msg;
597
+ let modified = false;
598
+ const nextToolCalls = toolCalls.map((tc) => {
599
+ if (tc["name"] !== "summarize_context")
600
+ return tc;
601
+ const args = tc["arguments"];
602
+ const operations = args?.["operations"];
603
+ if (!args || !operations?.length)
604
+ return tc;
605
+ let opsModified = false;
606
+ const nextOperations = operations.map((op) => {
607
+ const summary = op["summary"];
608
+ const resultCtxId = op["_result_context_id"];
609
+ if (!summary || summary.length <= 100) {
610
+ if (resultCtxId === undefined)
611
+ return op;
612
+ opsModified = true;
613
+ const { _result_context_id: _removed, ...rest } = op;
614
+ return rest;
615
+ }
616
+ opsModified = true;
617
+ const { _result_context_id: _removed, ...rest } = op;
618
+ return {
619
+ ...rest,
620
+ summary: truncateSummaryText(summary, resultCtxId),
621
+ };
622
+ });
623
+ if (!opsModified)
624
+ return tc;
625
+ modified = true;
626
+ return {
627
+ ...tc,
628
+ arguments: {
629
+ ...args,
630
+ operations: nextOperations,
631
+ },
632
+ };
633
+ });
634
+ if (!modified)
635
+ return msg;
636
+ return { ...msg, tool_calls: nextToolCalls };
637
+ });
638
+ }
639
+ function mergeMessageContent(prefix, existing) {
640
+ if (typeof existing === "string") {
641
+ return `${prefix}\n\n${existing}`;
642
+ }
643
+ if (Array.isArray(existing)) {
644
+ return [
645
+ { type: "text", text: prefix },
646
+ ...existing,
647
+ ];
648
+ }
649
+ return `${prefix}\n\n${String(existing ?? "")}`;
650
+ }
651
+ //# sourceMappingURL=log-projection.js.map