atom-agent 1.2.0 → 1.3.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 (54) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +13 -4
  3. package/atom.example.json +11 -0
  4. package/dist/App.js +923 -200
  5. package/dist/adapters.js +82 -13
  6. package/dist/agent/goal-evaluator.js +69 -0
  7. package/dist/agent/loop.js +517 -76
  8. package/dist/cli.js +11 -3
  9. package/dist/compact.js +41 -15
  10. package/dist/config.js +43 -7
  11. package/dist/context-manager.js +16 -198
  12. package/dist/context-windows.js +4 -2
  13. package/dist/env-block.js +5 -5
  14. package/dist/extension-commands.js +196 -0
  15. package/dist/extension-ui.js +153 -0
  16. package/dist/extensions.js +1571 -0
  17. package/dist/goal.js +583 -0
  18. package/dist/project-trust.js +96 -0
  19. package/dist/providers.js +6 -6
  20. package/dist/scheduler.js +74 -36
  21. package/dist/session.js +23 -5
  22. package/dist/sessions.js +25 -6
  23. package/dist/telemetry-dashboard.js +28 -0
  24. package/dist/telemetry.js +39 -0
  25. package/dist/tools/compaction-hooks.js +165 -0
  26. package/dist/tools/custom.js +189 -0
  27. package/dist/tools/intercept.js +145 -0
  28. package/dist/tools/overrides.js +105 -0
  29. package/dist/tools/provider-hooks.js +224 -0
  30. package/dist/tools/registry.js +246 -17
  31. package/dist/tools.js +44 -0
  32. package/dist/ui/palette.js +1 -1
  33. package/dist/ui/status-bar.js +80 -5
  34. package/dist/zen.js +305 -75
  35. package/documentation/architecture.md +114 -0
  36. package/documentation/cli.md +82 -0
  37. package/documentation/compaction.md +50 -0
  38. package/documentation/configuration.md +111 -0
  39. package/documentation/development.md +62 -0
  40. package/documentation/extensions.md +160 -0
  41. package/documentation/getting-started.md +63 -0
  42. package/documentation/goals.md +41 -0
  43. package/documentation/index.md +41 -0
  44. package/documentation/observability.md +70 -0
  45. package/documentation/permissions.md +66 -0
  46. package/documentation/providers.md +78 -0
  47. package/documentation/sessions.md +92 -0
  48. package/documentation/skills.md +57 -0
  49. package/documentation/tools.md +94 -0
  50. package/documentation/troubleshooting.md +54 -0
  51. package/examples/extensions/01-audit-gate.js +24 -0
  52. package/examples/extensions/02-notes-tool.js +32 -0
  53. package/examples/extensions/03-custom-command.js +32 -0
  54. package/package.json +6 -2
@@ -12,6 +12,47 @@ import React from "react";
12
12
  import { Box, Text } from "ink";
13
13
  import { formatTokenSegment } from "../context-windows.js";
14
14
  import { theme } from "./theme.js";
15
+ // Default objective budget for the goal segment: compact enough to share the
16
+ // line with the pinned model/token/mode segments at 100 columns.
17
+ export const GOAL_STATUS_OBJECTIVE_CHARS = 32;
18
+ // Truncate an objective to n chars max (`…` tail keeps the start, which
19
+ // carries the verb). n < 4 yields "" (the caller drops the segment instead).
20
+ export function truncateGoalObjective(objective, max = GOAL_STATUS_OBJECTIVE_CHARS) {
21
+ const text = typeof objective === "string" ? objective : "";
22
+ if (text.length <= max)
23
+ return text;
24
+ if (max < 4)
25
+ return "";
26
+ return `${text.slice(0, max - 1)}…`;
27
+ }
28
+ // Full goal segment at the default budget, or null when no goal is live.
29
+ // Paused reads distinct from active (`[paused]` vs `[active]`).
30
+ export function formatGoalSegment(goal, max = GOAL_STATUS_OBJECTIVE_CHARS) {
31
+ if (!goal || typeof goal.objective !== "string" || goal.objective.length === 0)
32
+ return null;
33
+ const state = goal.active === true ? "active" : "paused";
34
+ return `goal: ${truncateGoalObjective(goal.objective, max)} [${state}]`;
35
+ }
36
+ // Fit the goal segment into `room` chars (the width left after every other
37
+ // segment): full text when it fits, a shorter truncation when it almost
38
+ // fits, null (drop the segment) when even a stub would displace the line.
39
+ // Never throws; never returns "".
40
+ export function fitGoalSegment(goal, room) {
41
+ if (!goal || typeof goal.objective !== "string" || goal.objective.length === 0)
42
+ return null;
43
+ if (typeof room !== "number" || !Number.isFinite(room) || room <= 0)
44
+ return null;
45
+ const state = goal.active === true ? "active" : "paused";
46
+ const full = `goal: ${goal.objective} [${state}]`;
47
+ if (full.length <= room)
48
+ return full;
49
+ // Room for at least 4 objective chars plus the fixed framing, else drop.
50
+ const overhead = `goal: [${state}]`.length + 1;
51
+ const allow = Math.floor(room - overhead);
52
+ if (allow < 4)
53
+ return null;
54
+ return `goal: ${truncateGoalObjective(goal.objective, allow)} [${state}]`;
55
+ }
15
56
  // ~/… collapse + tail-cut: informative, never a full scroll of nesting.
16
57
  // Further shrinking for tight widths goes through shrinkTo below (the bar
17
58
  // measures first and only renders what fits).
@@ -34,9 +75,13 @@ export function shrinkTo(s, n) {
34
75
  // render (same-props parent churn — token paints, keystrokes, unrelated
35
76
  // ticks — must skip it; only changed props repaint).
36
77
  export const statusBarRenderProbe = { count: 0 };
37
- export const StatusBar = React.memo(function StatusBar({ provider, model, usageTotals, contextLoad, reasoningDisplay, mode, trustAll, busy, activity, phaseLabel, elapsedSecs, stalled, approvalPending, cwd, branch, columns = 100, }) {
78
+ export const StatusBar = React.memo(function StatusBar({ provider, model, usageTotals, contextLoad, reasoningDisplay, mode, trustAll, busy, activity, phaseLabel, elapsedSecs, stalled, approvalPending, cwd, branch, columns = 100, extensionStatus, goal, }) {
38
79
  statusBarRenderProbe.count += 1;
39
80
  const bar = theme.symbol.bar;
81
+ // Extension guest slot (ticket 10): pre-budgeted text renders only when
82
+ // the full line still fits — the fixed-width contract above. The `+ 3`
83
+ // is the ` ${bar} ` separator the segment carries with it.
84
+ const hasExt = typeof extensionStatus === "string" && extensionStatus.length > 0;
40
85
  if (!busy) {
41
86
  const token = formatTokenSegment(usageTotals, model, contextLoad);
42
87
  const trust = trustAll && mode !== "plan" ? "+trust" : "";
@@ -46,7 +91,8 @@ export const StatusBar = React.memo(function StatusBar({ provider, model, usageT
46
91
  // order: branch → cwd tail → the whole segment.
47
92
  const tail = `reasoning: ${reasoningDisplay} ${bar} mode: ${mode}${trust}`;
48
93
  const baseLen = `${provider}/${model} ${bar} ${token} ${bar} ${bar} ${tail}`.length;
49
- const avail = columns - baseLen;
94
+ const showExt = hasExt && baseLen + extensionStatus.length + 3 + 2 <= columns;
95
+ const avail = columns - baseLen - (showExt ? extensionStatus.length + 3 : 0);
50
96
  let loc = null;
51
97
  if (cwd) {
52
98
  const branchPart = branch ? ` : ${branch}` : "";
@@ -61,15 +107,44 @@ export const StatusBar = React.memo(function StatusBar({ provider, model, usageT
61
107
  loc = shrunk ? shrunk : null;
62
108
  }
63
109
  }
64
- return (_jsx(Box, { marginTop: theme.spacing.statusMarginTop, children: _jsxs(Text, { dimColor: true, children: [provider, "/", model, " ", bar, " ", token, loc ? (_jsxs(_Fragment, { children: [" ", bar, " ", loc] })) : null, " ", bar, " reasoning: ", reasoningDisplay, " ", bar, " mode: ", mode, trust ? "+trust" : null] }) }));
110
+ // Goal segment (ticket 09): lowest-priority builtin it takes only the
111
+ // width left after every other segment and drops whole rather than push
112
+ // the line past `columns`. Hidden entirely with no goal.
113
+ const lineSoFar = baseLen + (showExt ? extensionStatus.length + 3 : 0) + (loc ? loc.length + 3 : 0);
114
+ const goalSeg = fitGoalSegment(goal ?? null, columns - lineSoFar - 2);
115
+ return (_jsx(Box, { marginTop: theme.spacing.statusMarginTop, children: _jsxs(Text, { dimColor: true, children: [provider, "/", model, " ", bar, " ", token, showExt ? (_jsxs(_Fragment, { children: [" ", bar, " ", extensionStatus] })) : null, loc ? (_jsxs(_Fragment, { children: [" ", bar, " ", loc] })) : null, " ", bar, " reasoning: ", reasoningDisplay, " ", bar, " mode: ", mode, trust ? "+trust" : null, goalSeg ? (_jsxs(_Fragment, { children: [" ", bar, " ", goalSeg] })) : null] }) }));
65
116
  }
66
117
  // Busy layout prioritizes activity + clock + interrupt hint; the mode
67
118
  // stays pinned (it used to vanish while working), and the reasoning
68
119
  // effort stays visible (it used to vanish while working). The activity text
69
120
  // shrinks to fit so `esc stops` never wraps away.
70
121
  const busyTrust = trustAll && mode !== "plan" ? "+trust" : "";
71
- const busyFixed = ` ${bar} ${elapsedSecs}s ${bar} ${formatTokenSegment(usageTotals, model, contextLoad)} ${bar} reasoning: ${reasoningDisplay} ${bar} mode: ${mode}${busyTrust} ${bar} esc stops`;
122
+ const busyToken = formatTokenSegment(usageTotals, model, contextLoad);
123
+ // Goal segment (ticket 09): a guest in the fixed part — capped at 48
124
+ // chars and rendered only when the FULL activity text still fits beside
125
+ // it. Otherwise the goal drops whole and every existing segment renders
126
+ // exactly as with no goal (the goal never displaces, same precedent as
127
+ // the extension guest above). The clock, token, mode, and esc-hint
128
+ // segments never move for it either way.
129
+ const busyGoalSeg = fitGoalSegment(goal ?? null, 48);
130
+ const busyGoalCandidate = busyGoalSeg ? ` ${bar} ${busyGoalSeg}` : "";
131
+ const activityFull = activity ?? phaseLabel;
132
+ const busyCore = ` ${bar} ${elapsedSecs}s ${bar} ${busyToken} ${bar} reasoning: ${reasoningDisplay} ${bar} mode: ${mode}${busyTrust}`;
133
+ const busyTail = ` ${bar} esc stops`;
134
+ const busyExtCandidate = hasExt && `${busyCore}${busyGoalCandidate}${busyTail}`.length + extensionStatus.length + 3 + 2 <= columns
135
+ ? ` ${bar} ${extensionStatus}`
136
+ : "";
137
+ const withGoalFixed = `${busyCore}${busyExtCandidate}${busyGoalCandidate}${busyTail}`;
138
+ // Room check against the unfitted activity text: when it no longer fits
139
+ // whole with the goal aboard, the goal yields (drop whole, recompute).
140
+ const busyGoalPart = busyGoalCandidate !== "" &&
141
+ withGoalFixed.length + activityFull.length + 2 <= columns
142
+ ? busyGoalCandidate
143
+ : "";
144
+ const busyNoExt = `${busyCore}${busyGoalPart}${busyTail}`;
145
+ const showBusyExt = hasExt && busyNoExt.length + extensionStatus.length + 3 + 2 <= columns;
146
+ const busyFixed = ` ${bar} ${elapsedSecs}s${showBusyExt ? ` ${bar} ${extensionStatus}` : ""} ${bar} ${busyToken} ${bar} reasoning: ${reasoningDisplay} ${bar} mode: ${mode}${busyTrust}${busyGoalPart} ${bar} esc stops`;
72
147
  const busyAvail = columns - busyFixed.length - 2;
73
- const activityText = shrinkTo(activity ?? phaseLabel, Math.max(0, busyAvail));
148
+ const activityText = shrinkTo(activityFull, Math.max(0, busyAvail));
74
149
  return (_jsx(Box, { marginTop: theme.spacing.statusMarginTop, children: _jsxs(Text, { dimColor: true, children: [_jsxs(Text, { color: theme.color.activity, children: [theme.symbol.workTool, " ", activityText] }), busyFixed, stalled && !approvalPending ? ` ${bar} waiting${theme.symbol.ellipsis}` : null, approvalPending ? (_jsxs(Text, { color: theme.color.warning, children: [" ", bar, " waiting approval"] })) : null] }) }));
75
150
  });