atom-agent 1.1.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 (74) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +18 -8
  3. package/atom.example.json +11 -0
  4. package/dist/App.js +1637 -255
  5. package/dist/adapters.js +112 -21
  6. package/dist/agent/gates.js +14 -1
  7. package/dist/agent/goal-evaluator.js +69 -0
  8. package/dist/agent/loop-guard.js +11 -13
  9. package/dist/agent/loop.js +716 -132
  10. package/dist/agent/normalize.js +9 -2
  11. package/dist/cli.js +25 -3
  12. package/dist/compact.js +169 -17
  13. package/dist/config.js +43 -7
  14. package/dist/context-manager.js +16 -198
  15. package/dist/context-windows.js +4 -2
  16. package/dist/env-block.js +46 -8
  17. package/dist/extension-commands.js +196 -0
  18. package/dist/extension-ui.js +153 -0
  19. package/dist/extensions.js +1571 -0
  20. package/dist/goal.js +583 -0
  21. package/dist/project-trust.js +96 -0
  22. package/dist/providers.js +6 -6
  23. package/dist/scheduler.js +159 -41
  24. package/dist/session.js +23 -5
  25. package/dist/sessions.js +543 -0
  26. package/dist/system.js +89 -13
  27. package/dist/telemetry-dashboard.js +28 -0
  28. package/dist/telemetry.js +39 -0
  29. package/dist/tools/compaction-hooks.js +165 -0
  30. package/dist/tools/custom.js +189 -0
  31. package/dist/tools/dir-cache.js +7 -0
  32. package/dist/tools/filesystem.js +3 -2
  33. package/dist/tools/intercept.js +145 -0
  34. package/dist/tools/overrides.js +105 -0
  35. package/dist/tools/provider-hooks.js +224 -0
  36. package/dist/tools/registry.js +247 -17
  37. package/dist/tools/ripgrep.js +256 -0
  38. package/dist/tools/search.js +119 -58
  39. package/dist/tools/shared.js +39 -0
  40. package/dist/tools/shell.js +7 -5
  41. package/dist/tools/web.js +6 -6
  42. package/dist/tools.js +45 -0
  43. package/dist/ui/diff-view.js +7 -2
  44. package/dist/ui/live-host.js +18 -0
  45. package/dist/ui/live-tail.js +9 -3
  46. package/dist/ui/markdown.js +26 -2
  47. package/dist/ui/palette.js +3 -1
  48. package/dist/ui/side-by-side.js +2 -2
  49. package/dist/ui/status-bar.js +80 -5
  50. package/dist/ui/status-host.js +22 -0
  51. package/dist/ui/stream-store.js +48 -0
  52. package/dist/ui/tool-inspector.js +7 -1
  53. package/dist/ui/transcript.js +92 -38
  54. package/dist/zen.js +370 -87
  55. package/documentation/architecture.md +114 -0
  56. package/documentation/cli.md +82 -0
  57. package/documentation/compaction.md +50 -0
  58. package/documentation/configuration.md +111 -0
  59. package/documentation/development.md +62 -0
  60. package/documentation/extensions.md +160 -0
  61. package/documentation/getting-started.md +63 -0
  62. package/documentation/goals.md +41 -0
  63. package/documentation/index.md +41 -0
  64. package/documentation/observability.md +70 -0
  65. package/documentation/permissions.md +66 -0
  66. package/documentation/providers.md +78 -0
  67. package/documentation/sessions.md +92 -0
  68. package/documentation/skills.md +57 -0
  69. package/documentation/tools.md +94 -0
  70. package/documentation/troubleshooting.md +54 -0
  71. package/examples/extensions/01-audit-gate.js +24 -0
  72. package/examples/extensions/02-notes-tool.js +32 -0
  73. package/examples/extensions/03-custom-command.js +32 -0
  74. package/package.json +6 -2
@@ -0,0 +1,153 @@
1
+ // Extension UI surface (ticket 10): the dependency-free render model behind
2
+ // ExtensionAPI.setStatusSegment/setWidget/notify/promptUser. No imports, no
3
+ // React — like extension-commands.ts and tools/intercept.ts — so the host,
4
+ // the App, and pure unit tests share it with no cycle: the host validates
5
+ // and stages here, the App renders from here, nobody imports the other.
6
+ //
7
+ // Shapes:
8
+ // - Status: one text slot per extension (upsert by owner name). The bar
9
+ // budget lives with the render (status-bar.tsx states the fixed-width
10
+ // rule); this module only validates shape so a bad segment fails
11
+ // activation loudly instead of corrupting the bar.
12
+ // - Widget: titled text blocks keyed by owner + id (default "main"),
13
+ // rendered by the App in the configured placement. "panel" is the only
14
+ // placement in v1 (the bordered panel above the input zone, beside the
15
+ // todo panel) — unknown placements throw fail-closed so a typo surfaces
16
+ // at activation instead of rendering nowhere.
17
+ // - Notices: transient fire-and-forget strings the App drains into the
18
+ // transcript (one `(owner) message` info line each). Sync and bounded
19
+ // by flow — notify() never blocks, headless or not; the host caps the
20
+ // staged queue drop-oldest (EXT_NOTICE_CAP in extensions.ts).
21
+ // - Dialogs: validated { question, options, allowCustom } specs mirroring
22
+ // the QuestionBox the App fulfills them with (same option caps the modal
23
+ // already assumes). The pending-promise mechanics live in extensions.ts
24
+ // (runtime-local, generation-bound); this module only validates shape.
25
+ export const EXT_STATUS_SEGMENT_MAX = 24;
26
+ export const EXT_STATUS_TOTAL_MAX = 40;
27
+ export const EXT_WIDGET_TITLE_MAX = 48;
28
+ export const EXT_WIDGET_TEXT_MAX = 500;
29
+ export const EXT_NOTIFY_MAX = 200;
30
+ export const EXT_DIALOG_QUESTION_MAX = 200;
31
+ export const EXT_DIALOG_OPTIONS_MAX = 8;
32
+ export const EXT_DIALOG_OPTION_MAX = 80;
33
+ // v1 widget placements. The App renders "panel" above the input zone;
34
+ // anything else is a loud validation error (never a silent nowhere).
35
+ export const EXT_WIDGET_PLACEMENTS = ["panel"];
36
+ const WIDGET_ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
37
+ function isRecord(value) {
38
+ return typeof value === "object" && value !== null && !Array.isArray(value);
39
+ }
40
+ function errorFor(owner, what) {
41
+ return `extension "${owner}" ${what}`;
42
+ }
43
+ /** Validate a status segment. Throws Error on any problem. Returns the trimmed text. */
44
+ export function validateStatusSegment(owner, text) {
45
+ if (typeof text !== "string" || text.trim().length === 0) {
46
+ throw new Error(errorFor(owner, "status segment needs a non-empty string"));
47
+ }
48
+ if (text.length > EXT_STATUS_SEGMENT_MAX * 4) {
49
+ throw new Error(errorFor(owner, `status segment is too long (${text.length} chars, max ${EXT_STATUS_SEGMENT_MAX * 4})`));
50
+ }
51
+ return text;
52
+ }
53
+ /** Validate a widget definition. Throws Error on any problem. */
54
+ export function validateWidgetDef(owner, def) {
55
+ if (!isRecord(def))
56
+ throw new Error(errorFor(owner, "widget definition must be an object"));
57
+ const id = def.id === undefined ? "main" : def.id;
58
+ if (typeof id !== "string" || !WIDGET_ID_RE.test(id)) {
59
+ throw new Error(errorFor(owner, `widget has an invalid id ${JSON.stringify(def.id)} (want 1-32 char a-z0-9_-, default "main")`));
60
+ }
61
+ if (!EXT_WIDGET_PLACEMENTS.includes(def.placement)) {
62
+ throw new Error(errorFor(owner, `widget "${id}" has an unknown placement ${JSON.stringify(def.placement)} (want one of: ${EXT_WIDGET_PLACEMENTS.join(", ")})`));
63
+ }
64
+ if (typeof def.title !== "string" || def.title.trim().length === 0) {
65
+ throw new Error(errorFor(owner, `widget "${id}" needs a non-empty title`));
66
+ }
67
+ if (def.title.length > EXT_WIDGET_TITLE_MAX) {
68
+ throw new Error(errorFor(owner, `widget "${id}" title is too long (${def.title.length} chars, max ${EXT_WIDGET_TITLE_MAX})`));
69
+ }
70
+ if (typeof def.text !== "string" || def.text.trim().length === 0) {
71
+ throw new Error(errorFor(owner, `widget "${id}" needs a non-empty text body`));
72
+ }
73
+ if (def.text.length > EXT_WIDGET_TEXT_MAX) {
74
+ throw new Error(errorFor(owner, `widget "${id}" text is too long (${def.text.length} chars, max ${EXT_WIDGET_TEXT_MAX})`));
75
+ }
76
+ return { id, placement: def.placement, title: def.title, text: def.text };
77
+ }
78
+ /** Validate a notification message. Throws Error on any problem. Returns the message. */
79
+ export function validateNotifyMessage(owner, message) {
80
+ if (typeof message !== "string" || message.trim().length === 0) {
81
+ throw new Error(errorFor(owner, "notification needs a non-empty string"));
82
+ }
83
+ if (message.length > EXT_NOTIFY_MAX * 4) {
84
+ throw new Error(errorFor(owner, `notification is too long (${message.length} chars, max ${EXT_NOTIFY_MAX * 4})`));
85
+ }
86
+ return message;
87
+ }
88
+ /** Validate a dialog spec. Throws Error on any problem. */
89
+ export function validateDialogDef(owner, def) {
90
+ if (!isRecord(def))
91
+ throw new Error(errorFor(owner, "dialog definition must be an object"));
92
+ if (typeof def.question !== "string" || def.question.trim().length === 0) {
93
+ throw new Error(errorFor(owner, "dialog needs a non-empty question"));
94
+ }
95
+ if (def.question.length > EXT_DIALOG_QUESTION_MAX) {
96
+ throw new Error(errorFor(owner, `dialog question is too long (${def.question.length} chars, max ${EXT_DIALOG_QUESTION_MAX})`));
97
+ }
98
+ const options = def.options === undefined ? [] : def.options;
99
+ if (!Array.isArray(options)) {
100
+ throw new Error(errorFor(owner, "dialog options must be an array of strings"));
101
+ }
102
+ if (options.length > EXT_DIALOG_OPTIONS_MAX) {
103
+ throw new Error(errorFor(owner, `dialog has too many options (${options.length}, max ${EXT_DIALOG_OPTIONS_MAX})`));
104
+ }
105
+ for (const o of options) {
106
+ if (typeof o !== "string" || o.trim().length === 0) {
107
+ throw new Error(errorFor(owner, "dialog options must be non-empty strings"));
108
+ }
109
+ if (o.length > EXT_DIALOG_OPTION_MAX) {
110
+ throw new Error(errorFor(owner, `dialog option is too long (${o.length} chars, max ${EXT_DIALOG_OPTION_MAX})`));
111
+ }
112
+ }
113
+ if (!options.length && def.allowCustom !== true) {
114
+ throw new Error(errorFor(owner, "dialog needs options or allowCustom: true (nothing to answer with)"));
115
+ }
116
+ return { question: def.question, options: [...options], allowCustom: def.allowCustom === true };
117
+ }
118
+ // Truncate text to n chars max for tight widths (`…/tail` keeps the
119
+ // meaningful end, the status-bar convention). n < 4 yields "" (the caller
120
+ // drops the segment instead of rendering a stub).
121
+ export function truncateSegment(s, n) {
122
+ if (s.length <= n)
123
+ return s;
124
+ if (n < 4)
125
+ return "";
126
+ return `…/${s.slice(-(n - 3))}`;
127
+ }
128
+ // Pure status-bar text for extension segments (unit-tested; the bar itself
129
+ // only decides fit-or-drop against the terminal width, never the content).
130
+ // Each segment truncates to EXT_STATUS_SEGMENT_MAX, joined with " · "; the
131
+ // total caps at EXT_STATUS_TOTAL_MAX with trailing segments dropped whole
132
+ // (never a mid-segment cut past the per-segment truncation). Null when
133
+ // nothing renderable remains.
134
+ export function formatExtensionStatusText(segments) {
135
+ const parts = [];
136
+ let len = 0;
137
+ for (const raw of segments) {
138
+ if (typeof raw !== "string")
139
+ continue;
140
+ const text = raw.trim();
141
+ if (!text)
142
+ continue;
143
+ const seg = truncateSegment(text, EXT_STATUS_SEGMENT_MAX);
144
+ if (!seg)
145
+ continue;
146
+ const add = (parts.length > 0 ? 3 : 0) + seg.length;
147
+ if (len + add > EXT_STATUS_TOTAL_MAX)
148
+ continue;
149
+ parts.push(seg);
150
+ len += add;
151
+ }
152
+ return parts.length > 0 ? parts.join(" · ") : null;
153
+ }