minimal-agent 0.2.0 → 0.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 (107) hide show
  1. package/README.md +50 -72
  2. package/package.json +18 -13
  3. package/plugins/ralph-wiggum/plugin.js +205 -0
  4. package/plugins/ralph-wiggum/src/goalState.js +260 -0
  5. package/plugins/ralph-wiggum/src/{sentinels.ts → sentinels.js} +4 -7
  6. package/plugins/ralph-wiggum/src/stopHookRunner.js +104 -0
  7. package/plugins/ralph-wiggum/src/verificationGate.js +202 -0
  8. package/plugins/workflow-runner/{plugin.ts → plugin.js} +20 -26
  9. package/plugins/workflow-runner/src/expressions.js +369 -0
  10. package/plugins/workflow-runner/src/index.js +174 -0
  11. package/plugins/workflow-runner/src/loader.js +183 -0
  12. package/plugins/workflow-runner/src/runner.js +290 -0
  13. package/plugins/workflow-runner/src/stepExecutors/assert.js +28 -0
  14. package/plugins/workflow-runner/src/stepExecutors/llm.js +44 -0
  15. package/plugins/workflow-runner/src/stepExecutors/skill.js +103 -0
  16. package/plugins/workflow-runner/src/stepExecutors/{tool.ts → tool.js} +19 -25
  17. package/plugins/workflow-runner/src/types.js +59 -0
  18. package/plugins/workflow-runner/src/{workflowState.ts → workflowState.js} +21 -40
  19. package/src/bootstrap/cwdArg.js +22 -0
  20. package/src/bootstrap/workingDir.js +31 -0
  21. package/src/cli/configWizard.js +272 -0
  22. package/src/cli/print.js +192 -0
  23. package/src/config/configFile.js +78 -0
  24. package/src/config.js +118 -0
  25. package/src/context/compact.js +357 -0
  26. package/src/context/microCompactLite.js +151 -0
  27. package/src/context/persistContext.js +109 -0
  28. package/src/context/reactiveCompact.js +121 -0
  29. package/src/context/sessionPath.js +58 -0
  30. package/src/context/snipCompact.js +112 -0
  31. package/src/context/tokenCounter.js +66 -0
  32. package/src/llm/client.js +182 -0
  33. package/src/loop.js +230 -0
  34. package/src/main.js +116 -0
  35. package/src/plugin-sdk.js +24 -0
  36. package/src/plugins/commandRouter.js +169 -0
  37. package/src/plugins/hookEngine.js +258 -0
  38. package/src/plugins/pluginApi.js +23 -0
  39. package/src/plugins/pluginLoader.js +71 -0
  40. package/src/plugins/pluginRunner.js +65 -0
  41. package/src/plugins/transcript.js +171 -0
  42. package/src/prompts/projectInstructions.js +48 -0
  43. package/src/prompts/skillList.js +126 -0
  44. package/src/prompts/system.js +155 -0
  45. package/src/session/runTurn.js +41 -0
  46. package/src/session/sessionState.js +19 -0
  47. package/src/tools/bash/bash.js +352 -0
  48. package/src/tools/bash/semantics.js +85 -0
  49. package/src/tools/bash/warnings.js +98 -0
  50. package/src/tools/edit/edit.js +253 -0
  51. package/src/tools/edit/multi-edit.js +155 -0
  52. package/src/tools/glob/glob.js +97 -0
  53. package/src/tools/grep/grep.js +185 -0
  54. package/src/tools/grep/rgPath.js +173 -0
  55. package/src/tools/index.js +94 -0
  56. package/src/tools/read/read.js +209 -0
  57. package/src/tools/shared/fileState.js +61 -0
  58. package/src/tools/shared/fileUtils.js +281 -0
  59. package/src/tools/shared/schemas.js +16 -0
  60. package/src/tools/types.js +21 -0
  61. package/src/tools/webbrowser/browser.js +55 -0
  62. package/src/tools/webbrowser/webbrowser.js +194 -0
  63. package/src/tools/webfetch/preapproved.js +267 -0
  64. package/src/tools/webfetch/webfetch.js +317 -0
  65. package/src/tools/websearch/websearch.js +161 -0
  66. package/src/tools/write/write.js +125 -0
  67. package/src/types/turndown.d.ts +23 -0
  68. package/src/types.js +16 -0
  69. package/src/ui/App.js +37 -0
  70. package/src/ui/InputBox.js +240 -0
  71. package/src/ui/MessageList.js +28 -0
  72. package/src/ui/Root.js +70 -0
  73. package/src/ui/StatusLine.js +41 -0
  74. package/src/ui/ToolStatus.js +11 -0
  75. package/src/ui/hooks/useChat.js +234 -0
  76. package/src/ui/hooks/usePasteHandler.js +137 -0
  77. package/src/ui/hooks/useTextBuffer.js +55 -0
  78. package/src/ui/hooks/useTokenUsage.js +30 -0
  79. package/src/ui/textBuffer.js +217 -0
  80. package/src/utils/packageRoot.js +37 -0
  81. package/src/utils/resourcePaths.js +49 -0
  82. package/src/utils/zodToJson.js +29 -0
  83. package/dist/main.js +0 -5315
  84. package/plugins/ralph-wiggum/plugin.ts +0 -275
  85. package/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh +0 -203
  86. package/plugins/ralph-wiggum/src/goalState.ts +0 -310
  87. package/plugins/ralph-wiggum/src/stopHookRunner.ts +0 -136
  88. package/plugins/ralph-wiggum/src/verificationGate.ts +0 -252
  89. package/plugins/ralph-wiggum/test/goalState.test.ts +0 -410
  90. package/plugins/ralph-wiggum/test/verificationGate.test.ts +0 -122
  91. package/plugins/workflow-runner/src/expressions.ts +0 -371
  92. package/plugins/workflow-runner/src/index.ts +0 -194
  93. package/plugins/workflow-runner/src/loader.ts +0 -193
  94. package/plugins/workflow-runner/src/runner.ts +0 -313
  95. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -30
  96. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -54
  97. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -115
  98. package/plugins/workflow-runner/src/types.ts +0 -183
  99. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -114
  100. package/plugins/workflow-runner/test/e2e.test.ts +0 -268
  101. package/plugins/workflow-runner/test/expressions.test.ts +0 -140
  102. package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +0 -27
  103. package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +0 -49
  104. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  105. package/plugins/workflow-runner/test/loader.test.ts +0 -216
  106. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -230
  107. package/plugins/workflow-runner/test/runner.test.ts +0 -511
@@ -1,230 +0,0 @@
1
- /**
2
- * ============================================================
3
- * test/workflows/pluginRunner.isolation.test.ts
4
- * ------------------------------------------------------------
5
- * V1 红线:默认 T-A-O-R 不退化。无论 workflows/ 是缺失 / 存在 / 全坏,
6
- * 非 / 输入 100% 走 runQuery,零 workflow 副作用。
7
- *
8
- * 断言重点:
9
- * - 非 / 输入下 runQuery 被调用一次且参数(input + history)原样透传
10
- * - 非 / 输入下 pluginCache 保持空(说明 discoverPlugins 没被调)
11
- * - 没有任何 workflow_* / plugin_* 事件
12
- * - 即使 workflows/ 满是坏 yaml,也不影响以上结论
13
- *
14
- * 这是"在不 / 启动的情况下原始 T-A-O-R 要好使"硬约束的回归门。
15
- * ============================================================
16
- */
17
-
18
- import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
19
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
20
- import { tmpdir } from 'node:os';
21
- import { join } from 'node:path';
22
-
23
- import { _resetWorkingDir } from '../../../src/bootstrap/workingDir.ts';
24
- import {
25
- _resetPluginCache,
26
- discoverPlugins,
27
- } from '../../../src/plugins/commandRouter.ts';
28
- import type { LoopEvent, Message, Provider } from '../../../src/types.ts';
29
- import type { PluginEvent } from '../../../src/plugins/pluginApi.ts';
30
-
31
- type WfOrLoop = LoopEvent | PluginEvent;
32
-
33
- const fakeProvider: Provider = {
34
- name: 'test',
35
- baseURL: '',
36
- apiKey: '',
37
- model: 'm',
38
- contextWindow: 128000,
39
- };
40
-
41
- interface RunQueryCall {
42
- input: string;
43
- historyLen: number;
44
- }
45
-
46
- async function collect(
47
- gen: AsyncGenerator<WfOrLoop, void, void>,
48
- ): Promise<WfOrLoop[]> {
49
- const out: WfOrLoop[] = [];
50
- for await (const ev of gen) out.push(ev);
51
- return out;
52
- }
53
-
54
- describe('pluginRunner V1 红线:非 / 输入不被 workflow 污染', () => {
55
- let tmp: string;
56
- const calls: RunQueryCall[] = [];
57
-
58
- beforeEach(() => {
59
- tmp = mkdtempSync(join(tmpdir(), 'wf-iso-'));
60
- process.env.MINIMAL_AGENT_CWD = tmp;
61
- _resetWorkingDir();
62
- _resetPluginCache();
63
- calls.length = 0;
64
-
65
- mock.module('../../../src/loop.ts', () => ({
66
- runQuery: async function* (input: string, opts: { history: Message[] }) {
67
- calls.push({ input, historyLen: opts.history.length });
68
- yield { type: 'text', delta: 'fake-reply' };
69
- yield { type: 'turn_done' };
70
- },
71
- }));
72
- });
73
-
74
- afterEach(() => {
75
- delete process.env.MINIMAL_AGENT_CWD;
76
- _resetWorkingDir();
77
- _resetPluginCache();
78
- rmSync(tmp, { recursive: true, force: true });
79
- });
80
-
81
- async function run(input: string, history: Message[] = []) {
82
- const { runWithPlugins } = await import('../../../src/plugins/pluginRunner.ts');
83
- return collect(
84
- runWithPlugins(input, {
85
- provider: fakeProvider,
86
- history,
87
- }),
88
- );
89
- }
90
-
91
- it('workflows/ 缺失 + 非 / 输入 → 走 runQuery,无 workflow 事件', async () => {
92
- const events = await run('hello world');
93
- expect(calls.length).toBe(1);
94
- expect(calls[0].input).toBe('hello world');
95
-
96
- const noisyTypes = events
97
- .map((e) => e.type)
98
- .filter(
99
- (t) => t.startsWith('workflow_') || t === 'plugin_progress',
100
- );
101
- expect(noisyTypes).toEqual([]);
102
- });
103
-
104
- it('workflows/ 存在合法 yaml + 非 / 输入 → 仍只走 runQuery(不发现 workflow,不调 listWorkflows)', async () => {
105
- mkdirSync(join(tmp, 'workflows'), { recursive: true });
106
- writeFileSync(
107
- join(tmp, 'workflows', 'pretty.yaml'),
108
- `name: pretty
109
- description: x
110
- steps:
111
- - id: a
112
- type: assert
113
- condition: 'true'
114
- `,
115
- 'utf8',
116
- );
117
-
118
- const events = await run('just a chat please');
119
- expect(calls.length).toBe(1);
120
- expect(events.some((e) => e.type.startsWith('workflow_'))).toBe(false);
121
- });
122
-
123
- it('workflows/ 全是坏 yaml + 非 / 输入 → 仍只走 runQuery', async () => {
124
- mkdirSync(join(tmp, 'workflows'), { recursive: true });
125
- writeFileSync(join(tmp, 'workflows', 'broken.yaml'), '@@@\nbad:::\n', 'utf8');
126
-
127
- const events = await run('hi there');
128
- expect(calls.length).toBe(1);
129
- expect(calls[0].input).toBe('hi there');
130
- expect(events.some((e) => e.type.startsWith('workflow_'))).toBe(false);
131
- });
132
-
133
- it('非 / 输入 history 长度原样透传给 runQuery', async () => {
134
- const history: Message[] = [
135
- { role: 'system', content: 's' },
136
- { role: 'user', content: 'u1' },
137
- { role: 'assistant', content: 'a1' },
138
- ];
139
- await run('next thing', history);
140
- expect(calls[0].historyLen).toBe(3);
141
- });
142
-
143
- it('非 / 输入下 pluginCache 不被填充(discoverPlugins 未被触发)', async () => {
144
- await run('plain input no slash');
145
- // 重置过 cache;非 / 输入应该完全跳过 discoverPlugins
146
- const plugins = Array.from((await stealCache()).values());
147
- expect(plugins.length).toBe(0);
148
- });
149
-
150
- it('/ 输入下 pluginCache 才被填充(对照组)', async () => {
151
- await run('/nonexistent-cmd');
152
- const plugins = Array.from((await stealCache()).values());
153
- // 走过 discoverPlugins,应该至少能发现 ralph-wiggum(workflow 已降为内置能力)
154
- expect(plugins.length).toBeGreaterThan(0);
155
- });
156
- });
157
-
158
- /**
159
- * commandRouter 的 pluginCache 是模块私有变量,没有 getter。
160
- * 用一个 hack:调用 discoverPlugins(),但只看返回 —— 如果之前没被填,
161
- * 这一调用结束时会把所有插件加进 cache。所以我们改为读副作用更直接的接口:
162
- * listAvailableCommands()。如果没 discoverPlugins 过,listAvailableCommands
163
- * 会返回空数组。
164
- */
165
- async function stealCache(): Promise<Map<string, unknown>> {
166
- const { listAvailableCommands } = await import('../../../src/plugins/commandRouter.ts');
167
- const cmds = listAvailableCommands();
168
- const m = new Map<string, unknown>();
169
- for (const c of cmds) m.set(c.plugin, c);
170
- return m;
171
- }
172
-
173
- describe('pluginRunner V2 红线:/workflow 命令通过 plugin.ts.runCommand 接管', () => {
174
- let tmp: string;
175
- const calls: RunQueryCall[] = [];
176
-
177
- beforeEach(() => {
178
- tmp = mkdtempSync(join(tmpdir(), 'wf-iso2-'));
179
- process.env.MINIMAL_AGENT_CWD = tmp;
180
- _resetWorkingDir();
181
- _resetPluginCache();
182
- calls.length = 0;
183
-
184
- mock.module('../../../src/loop.ts', () => ({
185
- runQuery: async function* (input: string, opts: { history: Message[] }) {
186
- calls.push({ input, historyLen: opts.history.length });
187
- yield { type: 'text', delta: 'should-not-be-here' };
188
- yield { type: 'turn_done' };
189
- },
190
- }));
191
- });
192
-
193
- afterEach(() => {
194
- delete process.env.MINIMAL_AGENT_CWD;
195
- _resetWorkingDir();
196
- _resetPluginCache();
197
- rmSync(tmp, { recursive: true, force: true });
198
- });
199
-
200
- it('/workflows 由插件 plugin.ts 接管 → 不走 runQuery,yield 列表 text', async () => {
201
- const { runWithPlugins } = await import('../../../src/plugins/pluginRunner.ts');
202
- const events = await collect(
203
- runWithPlugins('/workflows', { provider: fakeProvider, history: [] }),
204
- );
205
- expect(calls.length).toBe(0);
206
- expect(events.some((e) => e.type === 'text')).toBe(true);
207
- expect(events.some((e) => e.type === 'turn_done')).toBe(true);
208
- });
209
-
210
- it('/workflow book-review-short 缺必填 input → yield error,不走 runQuery', async () => {
211
- const { runWithPlugins } = await import('../../../src/plugins/pluginRunner.ts');
212
- const events = await collect(
213
- runWithPlugins('/workflow book-review-short', {
214
- provider: fakeProvider,
215
- history: [],
216
- }),
217
- );
218
- expect(calls.length).toBe(0);
219
- expect(events.some((e) => e.type === 'error')).toBe(true);
220
- });
221
-
222
- it('/workflow 以插件命令形式出现在 listAvailableCommands', async () => {
223
- await discoverPlugins();
224
- const { listAvailableCommands } = await import('../../../src/plugins/commandRouter.ts');
225
- const cmds = listAvailableCommands();
226
- const wf = cmds.find((c) => c.command === '/workflow');
227
- expect(wf).toBeDefined();
228
- expect(wf?.plugin).toBe('workflow-runner');
229
- });
230
- });