atom-agent 1.4.0 → 1.5.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 (67) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +220 -224
  3. package/dist/App.js +922 -341
  4. package/dist/adapters.js +127 -14
  5. package/dist/agent/goal-evaluator.js +3 -0
  6. package/dist/agent/loop.js +211 -430
  7. package/dist/agent/tool-pipeline.js +398 -0
  8. package/dist/agent/turn-events.js +12 -0
  9. package/dist/cli.js +57 -8
  10. package/dist/compact.js +72 -8
  11. package/dist/config.js +19 -0
  12. package/dist/context-manager.js +6 -2
  13. package/dist/extensions.js +6 -0
  14. package/dist/file-diffs.js +108 -0
  15. package/dist/kilo.js +1 -1
  16. package/dist/local-discovery.js +2 -2
  17. package/dist/media.js +276 -0
  18. package/dist/overflow.js +140 -0
  19. package/dist/policy.js +8 -0
  20. package/dist/scheduler.js +38 -9
  21. package/dist/session-revert.js +125 -0
  22. package/dist/sessions.js +101 -0
  23. package/dist/snapshots.js +69 -0
  24. package/dist/system.js +2 -89
  25. package/dist/telemetry.js +26 -1
  26. package/dist/todos.js +241 -0
  27. package/dist/tools/filesystem.js +102 -22
  28. package/dist/tools/registry.js +184 -45
  29. package/dist/tools/ripgrep.js +7 -6
  30. package/dist/tools/search.js +172 -17
  31. package/dist/tools/shared.js +6 -0
  32. package/dist/tools.js +7 -39
  33. package/dist/ui/diff-panel.js +1 -1
  34. package/dist/ui/diff-view.js +13 -5
  35. package/dist/ui/diff.js +67 -0
  36. package/dist/ui/errors.js +20 -6
  37. package/dist/ui/input.js +24 -20
  38. package/dist/ui/live-tail.js +36 -1
  39. package/dist/ui/markdown.js +9 -4
  40. package/dist/ui/modals.js +7 -5
  41. package/dist/ui/paint-scheduler.js +120 -0
  42. package/dist/ui/palette.js +4 -2
  43. package/dist/ui/pickers.js +4 -1
  44. package/dist/ui/side-by-side.js +81 -22
  45. package/dist/ui/status-bar.js +63 -8
  46. package/dist/ui/stream-store.js +7 -0
  47. package/dist/ui/theme.js +23 -1
  48. package/dist/ui/todo-panel.js +5 -2
  49. package/dist/ui/tool-inspector.js +33 -4
  50. package/dist/ui/transcript.js +8 -5
  51. package/dist/web/events.js +93 -0
  52. package/dist/web/runtime.js +790 -0
  53. package/dist/web/server.js +570 -0
  54. package/dist/web/ui/app.js +1925 -0
  55. package/dist/web/ui/index.html +135 -0
  56. package/dist/web/ui/styles.css +515 -0
  57. package/dist/zen.js +115 -4
  58. package/documentation/cli.md +5 -5
  59. package/documentation/configuration.md +11 -6
  60. package/documentation/development.md +4 -3
  61. package/documentation/goals.md +1 -1
  62. package/documentation/index.md +4 -4
  63. package/documentation/providers.md +2 -3
  64. package/documentation/skills.md +3 -3
  65. package/documentation/tools.md +8 -3
  66. package/documentation/troubleshooting.md +1 -1
  67. package/package.json +3 -2
package/dist/adapters.js CHANGED
@@ -6,7 +6,7 @@
6
6
  // Normalized output matches zen ChatResult:
7
7
  // {content, tool_calls:[{id,function:{name,arguments}}], usage?}
8
8
  // so runAgenticLoop/retry/rollback/status code is untouched.
9
- import { allToolDefinitions } from "./tools.js";
9
+ import { chatToolDefinitions } from "./tools.js";
10
10
  import { getProvider, modelsUrlForProvider, } from "./providers.js";
11
11
  export const ANTHROPIC_VERSION = "2023-06-01";
12
12
  export const ANTHROPIC_MAX_TOKENS = 4096;
@@ -64,10 +64,13 @@ export function geminiThinkingLevelFor(effort) {
64
64
  export function isEffortRejection(errorText) {
65
65
  return /reasoning_effort|reasoning effort|thinking_level|budget_tokens|\bthinking\b/i.test(errorText);
66
66
  }
67
- function toolDefs() {
67
+ function toolDefs(includeUpdateGoal = true) {
68
68
  // Builtins plus extension-registered custom tools, so non-OpenAI kinds
69
69
  // see the same model-visible surface as the OpenAI-chat path.
70
- return allToolDefinitions();
70
+ // includeUpdateGoal:false hides update_goal when the turn has no live
71
+ // goal (same contract as chatToolDefinitions — default keeps every
72
+ // existing caller byte-identical).
73
+ return chatToolDefinitions(includeUpdateGoal);
71
74
  }
72
75
  function parseArgsObject(raw) {
73
76
  try {
@@ -81,11 +84,16 @@ function parseArgsObject(raw) {
81
84
  }
82
85
  }
83
86
  import { ephemeralBreakpoint, assemblePrefix } from "./prompt-cache.js";
87
+ import { hasMediaRefs, resolveMediaRefs, stripMedia, } from "./media.js";
84
88
  export function buildAnthropicBody(history, model, opts) {
85
89
  const systems = [];
86
90
  const messages = [];
87
91
  // Group consecutive tool messages into one user message with
88
92
  // multiple tool_result blocks (Anthropic convention).
93
+ // Media lowering (see src/media.ts): histories without descriptors take
94
+ // the string path byte-identically; descriptor-bearing user/tool content
95
+ // expands to text + base64 image blocks (strip mode: prose markers).
96
+ const strip = opts?.stripMedia === true;
89
97
  let pendingToolResults = [];
90
98
  function flushTools() {
91
99
  if (pendingToolResults.length === 0)
@@ -95,20 +103,64 @@ export function buildAnthropicBody(history, model, opts) {
95
103
  }
96
104
  for (const m of history) {
97
105
  if (m.role === "system") {
98
- systems.push(m.content);
106
+ systems.push(strip ? stripMedia(m.content) : m.content);
99
107
  continue;
100
108
  }
101
109
  if (m.role === "tool") {
110
+ if (!hasMediaRefs(m.content)) {
111
+ pendingToolResults.push({
112
+ type: "tool_result",
113
+ tool_use_id: m.tool_call_id,
114
+ content: strip ? stripMedia(m.content) : m.content,
115
+ });
116
+ continue;
117
+ }
118
+ if (strip) {
119
+ pendingToolResults.push({
120
+ type: "tool_result",
121
+ tool_use_id: m.tool_call_id,
122
+ content: stripMedia(m.content),
123
+ });
124
+ continue;
125
+ }
126
+ const { text, media } = resolveMediaRefs(m.content);
127
+ const blocks = [{ type: "text", text }];
128
+ for (const part of media) {
129
+ if (!part.ok)
130
+ continue; // placeholder already inline in text
131
+ blocks.push({
132
+ type: "image",
133
+ source: { type: "base64", media_type: part.mime, data: part.base64 },
134
+ });
135
+ }
102
136
  pendingToolResults.push({
103
137
  type: "tool_result",
104
138
  tool_use_id: m.tool_call_id,
105
- content: m.content,
139
+ content: blocks,
106
140
  });
107
141
  continue;
108
142
  }
109
143
  flushTools();
110
144
  if (m.role === "user") {
111
- messages.push({ role: "user", content: m.content });
145
+ if (!hasMediaRefs(m.content)) {
146
+ messages.push({ role: "user", content: strip ? stripMedia(m.content) : m.content });
147
+ }
148
+ else if (strip) {
149
+ messages.push({ role: "user", content: stripMedia(m.content) });
150
+ }
151
+ else {
152
+ const { text, media } = resolveMediaRefs(m.content);
153
+ const blocks = [{ type: "text", text }];
154
+ for (const part of media) {
155
+ if (!part.ok)
156
+ continue;
157
+ blocks.push({
158
+ type: "image",
159
+ source: { type: "base64", media_type: part.mime, data: part.base64 },
160
+ });
161
+ }
162
+ messages.push({ role: "user", content: blocks });
163
+ }
112
164
  }
113
165
  else {
114
166
  // assistant: text + tool_use blocks
@@ -140,7 +192,7 @@ export function buildAnthropicBody(history, model, opts) {
140
192
  // Compaction path (includeTools:false) omits `tools` + `tool_choice`
141
193
  // entirely — asserted in tests as "no `tools` key".
142
194
  if (includeTools) {
143
- const defs = toolDefs().map((t) => ({
195
+ const defs = toolDefs(opts?.includeUpdateGoal !== false).map((t) => ({
144
196
  name: t.function.name,
145
197
  description: t.function.description,
146
198
  input_schema: t.function.parameters,
@@ -222,10 +274,15 @@ export function geminiHeaders(apiKey) {
222
274
  };
223
275
  }
224
276
  export function buildGeminiBody(history, _model, opts) {
277
+ // Media lowering (see src/media.ts): histories without descriptors take
278
+ // the legacy path byte-identically. Tool-result images ride as inline_data
279
+ // user parts after their functionResponse (function responses stay text);
280
+ // user-message images ride inline. Strip mode: prose markers only.
281
+ const strip = opts?.stripMedia === true;
225
282
  const systems = [];
226
283
  for (const m of history) {
227
284
  if (m.role === "system")
228
- systems.push(m.content);
285
+ systems.push(strip ? stripMedia(m.content) : m.content);
229
286
  }
230
287
  // tool_call_id -> function name (tool messages carry only the id).
231
288
  const nameById = new Map();
@@ -239,27 +296,83 @@ export function buildGeminiBody(history, _model, opts) {
239
296
  }
240
297
  const contents = [];
241
298
  let pendingResponses = [];
299
+ // Images resolved out of tool results (flushed as user inline_data parts
300
+ // right after their functionResponse group).
301
+ let pendingToolImages = [];
242
302
  function flushResponses() {
243
- if (pendingResponses.length === 0)
303
+ if (pendingResponses.length === 0 && pendingToolImages.length === 0)
244
304
  return;
245
- contents.push({ role: "user", parts: pendingResponses });
246
- pendingResponses = [];
305
+ if (pendingResponses.length > 0) {
306
+ contents.push({ role: "user", parts: pendingResponses });
307
+ pendingResponses = [];
308
+ }
309
+ if (pendingToolImages.length > 0) {
310
+ const parts = [
311
+ {
312
+ text: `Image(s) from tool result: ${pendingToolImages.map((i) => i.name).join(", ")}`,
313
+ },
314
+ ];
315
+ for (const img of pendingToolImages) {
316
+ parts.push({ inline_data: { mime_type: img.mime, data: img.data } });
317
+ }
318
+ pendingToolImages = [];
319
+ contents.push({ role: "user", parts });
320
+ }
247
321
  }
248
322
  for (const m of history) {
249
323
  if (m.role === "system")
250
324
  continue;
251
325
  if (m.role === "tool") {
326
+ if (!hasMediaRefs(m.content)) {
327
+ pendingResponses.push({
328
+ functionResponse: {
329
+ name: nameById.get(m.tool_call_id) ?? "unknown",
330
+ response: { result: strip ? stripMedia(m.content) : m.content },
331
+ },
332
+ });
333
+ continue;
334
+ }
335
+ if (strip) {
336
+ pendingResponses.push({
337
+ functionResponse: {
338
+ name: nameById.get(m.tool_call_id) ?? "unknown",
339
+ response: { result: stripMedia(m.content) },
340
+ },
341
+ });
342
+ continue;
343
+ }
344
+ const { text, media } = resolveMediaRefs(m.content);
252
345
  pendingResponses.push({
253
346
  functionResponse: {
254
347
  name: nameById.get(m.tool_call_id) ?? "unknown",
255
- response: { result: m.content },
348
+ response: { result: text },
256
349
  },
257
350
  });
351
+ for (const part of media) {
352
+ if (!part.ok)
353
+ continue;
354
+ pendingToolImages.push({ mime: part.mime, data: part.base64, name: part.name });
355
+ }
258
356
  continue;
259
357
  }
260
358
  flushResponses();
261
359
  if (m.role === "user") {
262
- contents.push({ role: "user", parts: [{ text: m.content }] });
360
+ if (!hasMediaRefs(m.content)) {
361
+ contents.push({ role: "user", parts: [{ text: strip ? stripMedia(m.content) : m.content }] });
362
+ }
363
+ else if (strip) {
364
+ contents.push({ role: "user", parts: [{ text: stripMedia(m.content) }] });
365
+ }
366
+ else {
367
+ const { text, media } = resolveMediaRefs(m.content);
368
+ const parts = [{ text }];
369
+ for (const part of media) {
370
+ if (!part.ok)
371
+ continue;
372
+ parts.push({ inline_data: { mime_type: part.mime, data: part.base64 } });
373
+ }
374
+ contents.push({ role: "user", parts });
375
+ }
263
376
  }
264
377
  else {
265
378
  const am = m;
@@ -290,7 +403,7 @@ export function buildGeminiBody(history, _model, opts) {
290
403
  if (includeTools) {
291
404
  body.tools = [
292
405
  {
293
- functionDeclarations: toolDefs().map((t) => ({
406
+ functionDeclarations: toolDefs(opts?.includeUpdateGoal !== false).map((t) => ({
294
407
  name: t.function.name,
295
408
  description: t.function.description,
296
409
  parameters: stripGeminiSchemaKeys(t.function.parameters),
@@ -51,6 +51,9 @@ export async function requestGoalVerdict(req) {
51
51
  endpointOverride: req.endpointOverride,
52
52
  disableTools: true,
53
53
  maxOutputTokens: req.maxOutputTokens ?? GOAL_JUDGE_MAX_TOKENS,
54
+ // The judge needs prose, not pixels — same strip contract as the
55
+ // compaction summary POST.
56
+ stripMedia: true,
54
57
  ...(req.signal ? { signal: req.signal } : {}),
55
58
  });
56
59
  // Totals keep accumulating: forward real judge usage when present.