@j-o-r/hello-dave 0.1.1 → 0.1.5

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 (173) hide show
  1. package/CHANGELOG.md +42 -25
  2. package/README.md +81 -221
  3. package/TODO.md +173 -35
  4. package/agents/agent_creator.js +105 -0
  5. package/agents/agent_creator.prompt.md +371 -0
  6. package/agents/ask_agent.js +64 -127
  7. package/agents/claude_agent.js +68 -0
  8. package/agents/code_agent.js +55 -135
  9. package/agents/code_agent.prompt.md +50 -0
  10. package/agents/echo_agent.js +76 -0
  11. package/agents/financial_expert.js +75 -0
  12. package/agents/gpt_agent.js +52 -103
  13. package/agents/gpt_code.js +81 -0
  14. package/agents/grok_agent.js +58 -114
  15. package/agents/minimax_agent.js +92 -0
  16. package/agents/mureka_agent.js +77 -0
  17. package/agents/planner_agent.js +172 -0
  18. package/agents/stability_agent.js +87 -0
  19. package/agents/test_agent.js +75 -157
  20. package/agents/weather_agent.js +73 -0
  21. package/agents/workflow_agent.js +189 -0
  22. package/bin/dave.js +436 -184
  23. package/docs/bin-dave.md +85 -35
  24. package/docs/cdn-ssh.md +100 -0
  25. package/docs/creating-agents.md +301 -0
  26. package/docs/creating-toolsets.md +336 -0
  27. package/docs/docs-organization.md +48 -0
  28. package/docs/project-overview.md +86 -51
  29. package/lib/API/elevenlabs.io/music.compose.md +441 -0
  30. package/lib/API/elevenlabs.io/music.create-composition-plan.md +370 -0
  31. package/lib/API/elevenlabs.io/music.stream.md +425 -0
  32. package/lib/API/lalal.ai/lalal.js +445 -0
  33. package/lib/API/lalal.ai/openapi.json +2614 -0
  34. package/lib/API/minimax/ImageToolset.js +82 -37
  35. package/lib/API/minimax/MusicToolset.js +125 -79
  36. package/lib/API/minimax/VideoToolset.js +170 -167
  37. package/lib/API/minimax/image.js +5 -1
  38. package/lib/API/minimax/music.js +210 -23
  39. package/lib/API/minimax/video.js +242 -53
  40. package/lib/API/mureka/MusicToolset.js +646 -0
  41. package/lib/API/mureka/README.md +41 -0
  42. package/lib/API/mureka/index.js +7 -0
  43. package/lib/API/mureka/music.js +658 -0
  44. package/lib/API/openai.com/index.js +7 -0
  45. package/lib/API/openai.com/{reponses/text.js → responses.js} +64 -18
  46. package/lib/API/openai.com/video.create.character.md +40 -0
  47. package/lib/API/openai.com/video.create.md +219 -0
  48. package/lib/API/openai.com/video.delete.md +44 -0
  49. package/lib/API/openai.com/video.download.md +31 -0
  50. package/lib/API/openai.com/video.edit.md +155 -0
  51. package/lib/API/openai.com/video.extend.md +166 -0
  52. package/lib/API/openai.com/video.fetch.character.md +43 -0
  53. package/lib/API/openai.com/video.js +784 -0
  54. package/lib/API/openai.com/video.list.md +201 -0
  55. package/lib/API/openai.com/video.remix.md +175 -0
  56. package/lib/API/openai.com/video.retrieve.md +139 -0
  57. package/lib/API/openai.com/videoToolset.js +616 -0
  58. package/lib/API/stability.ai/ImageToolset.js +131 -40
  59. package/lib/API/stability.ai/MusicToolset.js +79 -47
  60. package/lib/API/stability.ai/audio.js +63 -131
  61. package/lib/API/x.ai/chat.responses.md +1040 -0
  62. package/lib/API/x.ai/image.js +229 -59
  63. package/lib/API/x.ai/imageToolset.js +376 -0
  64. package/lib/API/x.ai/index.js +1 -1
  65. package/lib/API/x.ai/responses.js +9 -18
  66. package/lib/Agent.js +271 -0
  67. package/lib/Agent.js.old +284 -0
  68. package/lib/AgentLauncher.js +593 -0
  69. package/lib/Cli.js +87 -13
  70. package/lib/Prompt.js +23 -1
  71. package/lib/Session.js +5 -4
  72. package/lib/ToolSet.js +102 -6
  73. package/lib/agentLoader.js +369 -0
  74. package/lib/cdn.js +67 -231
  75. package/lib/{CdnToolset.js → cdnToolset.js} +47 -64
  76. package/lib/defaultToolsets.js +43 -0
  77. package/lib/fafs.js +1 -1
  78. package/lib/genericToolset.js +442 -119
  79. package/lib/handOffToolset.js +179 -0
  80. package/lib/index.js +34 -27
  81. package/lib/toolsetLoader.js +248 -0
  82. package/package.json +10 -4
  83. package/types/API/lalal.ai/lalal.d.ts +116 -0
  84. package/types/API/minimax/image.d.ts +2 -1
  85. package/types/API/minimax/music.d.ts +189 -26
  86. package/types/API/minimax/video.d.ts +100 -31
  87. package/types/API/mureka/index.d.ts +7 -0
  88. package/types/API/mureka/music.d.ts +472 -0
  89. package/types/API/openai.com/index.d.ts +7 -0
  90. package/types/API/openai.com/{reponses/text.d.ts → responses.d.ts} +11 -11
  91. package/types/API/openai.com/video.d.ts +409 -0
  92. package/types/API/openai.com/videoToolset.d.ts +24 -0
  93. package/types/API/stability.ai/audio.d.ts +14 -103
  94. package/types/API/stability.ai/image.d.ts +2 -2
  95. package/types/API/x.ai/image.d.ts +138 -26
  96. package/types/API/x.ai/imageToolset.d.ts +3 -0
  97. package/types/API/x.ai/index.d.ts +1 -1
  98. package/types/API/x.ai/responses.d.ts +4 -4
  99. package/types/Agent.d.ts +123 -0
  100. package/types/AgentLauncher.d.ts +250 -0
  101. package/types/Cli.d.ts +28 -8
  102. package/types/Prompt.d.ts +23 -5
  103. package/types/Session.d.ts +1 -1
  104. package/types/ToolSet.d.ts +10 -0
  105. package/types/agentLoader.d.ts +78 -0
  106. package/types/cdn.d.ts +15 -90
  107. package/types/defaultToolsets.d.ts +9 -0
  108. package/types/fafs.d.ts +1 -1
  109. package/types/genericToolset.d.ts +1 -1
  110. package/types/handOffToolset.d.ts +28 -0
  111. package/types/index.d.ts +19 -17
  112. package/types/toolsetLoader.d.ts +114 -0
  113. package/utils/format_log.js +101 -23
  114. package/utils/launch_agent.js +18 -0
  115. package/utils/list_sessions.sh +13 -5
  116. package/utils/search_sessions.sh +65 -29
  117. package/utils/toolsets.js +33 -0
  118. package/README.md.bak.1779452127 +0 -240
  119. package/agents/codeserver.sh +0 -47
  120. package/agents/daisy_agent.js +0 -173
  121. package/agents/docs_agent.js +0 -148
  122. package/agents/memory_agent.js +0 -263
  123. package/agents/minimax.js +0 -173
  124. package/agents/npm_agent.js +0 -202
  125. package/agents/prompt_agent.js +0 -133
  126. package/agents/readme_agent.js +0 -148
  127. package/agents/spawn_agent.js +0 -160
  128. package/agents/stability.js +0 -173
  129. package/agents/todo_agent.js +0 -175
  130. package/bin/codeDave +0 -58
  131. package/docs/agent-dave-websocket-protocol.md +0 -180
  132. package/docs/agent-manager.md +0 -244
  133. package/docs/codeserver-pattern.md +0 -191
  134. package/docs/generic-toolset.md +0 -326
  135. package/docs/howtos/agent-networking.md +0 -253
  136. package/docs/howtos/spawn-agents.md.bak +0 -200
  137. package/docs/howtos/spawn-agents.md.bak_new +0 -200
  138. package/docs/multi-agent-clusters.md +0 -265
  139. package/docs/music-toolsets.md +0 -137
  140. package/docs/path-resolution-best-practices.md +0 -104
  141. package/docs/plans/minimax-music-generation.md +0 -80
  142. package/docs/plans/unified-agent-architecture.md +0 -146
  143. package/docs/plans/websocket-streaming-plan.md.bak +0 -317
  144. package/docs/prompt/spawn_agent.md +0 -175
  145. package/docs/prompt/spawn_agent.md.bak +0 -201
  146. package/docs/prompt/task_clarification_and_documentation.md +0 -35
  147. package/docs/prompt-class.md +0 -141
  148. package/docs/todo-archive-infra-2026-04-21.md +0 -15
  149. package/docs/todo-archive-v0.0.8.md +0 -1
  150. package/docs/todo-archive-v0.1.0.md +0 -32
  151. package/docs/todo-archive.md +0 -44
  152. package/docs/tools-syntax-validation.md +0 -121
  153. package/docs/toolset.md +0 -164
  154. package/docs/xai-responses.md +0 -111
  155. package/docs/xai_collections.md +0 -106
  156. package/lib/API/x.ai/ImageToolset.js +0 -165
  157. package/lib/API/x.ai/text.js +0 -415
  158. package/lib/AgentClient.js +0 -248
  159. package/lib/AgentManager.js +0 -245
  160. package/lib/AgentServer.js +0 -404
  161. package/lib/wsCli.js +0 -287
  162. package/lib/wsIO.js +0 -90
  163. package/types/API/x.ai/text.d.ts +0 -286
  164. package/types/AgentClient.d.ts +0 -109
  165. package/types/AgentManager.d.ts +0 -100
  166. package/types/AgentServer.d.ts +0 -89
  167. package/types/wsCli.d.ts +0 -17
  168. package/types/wsIO.d.ts +0 -30
  169. package/utils/test.sh +0 -46
  170. /package/docs/{suggestions.md → _notes/token-counts.md} +0 -0
  171. /package/lib/API/openai.com/{reponses/MESSAGES.md → MESSAGES.md} +0 -0
  172. /package/types/API/{x.ai/ImageToolset.d.ts → mureka/MusicToolset.d.ts} +0 -0
  173. /package/types/{CdnToolset.d.ts → cdnToolset.d.ts} +0 -0
@@ -0,0 +1,593 @@
1
+ import Agent from './Agent.js';
2
+ import Cli from './Cli.js';
3
+ import cli from '@j-o-r/cli'; // only for optional direct banner if needed
4
+ import { createAgentLoader } from './agentLoader.js';
5
+ import { configureDefaultHandOffToolset } from './handOffToolset.js';
6
+
7
+ /**
8
+ * @module AgentLauncher
9
+ *
10
+ * AgentLauncher provides a clean, reusable way to discover, load, run, and
11
+ * hand-off between "clean" Agent instances (those built with the unified
12
+ * Agent class and exported via `export default agent;`).
13
+ *
14
+ * Primary responsibilities:
15
+ * - Discovery: `list()` returns all agents found in the standard locations
16
+ * without importing them (now includes a short `desc`).
17
+ * - Loading: `load(name)` resolves, imports, and validates a clean Agent.
18
+ * - Execution: `run()` handles CLI argument parsing, one-shot calls,
19
+ * --help/--info, and interactive CLI startup.
20
+ * - In-process handoff: Listens for the 'agent:handoff' event on the active
21
+ * Prompt and seamlessly swaps the active agent (including rebinding the
22
+ * single long-lived Cli instance when in interactive mode).
23
+ *
24
+ * Design highlights:
25
+ * - A single Cli instance is kept for the lifetime of the launcher in
26
+ * interactive mode. On handoff we call `cli.rebind(...)` instead of
27
+ * creating a new Cli (which would duplicate global key handlers).
28
+ * - Handoffs are always "fresh": the new agent starts with only its own
29
+ * system prompt + the provided context. No conversation history is copied.
30
+ * - Same-name "handoff" (self-reset) is supported as a deliberate fresh-start
31
+ * / token-reduction mechanism. The current agent is fully replaced with a
32
+ * newly loaded instance of itself + the new focused context.
33
+ * - Old agents are cleaned up via `destructor()` (removes listeners, aids GC).
34
+ * - Works for both interactive CLI and non-interactive / one-shot usage.
35
+ *
36
+ * Typical usage (from user code or a thin launcher script):
37
+ * ```js
38
+ * import { AgentLauncher } from '@j-o-r/hello-dave';
39
+ *
40
+ * const launcher = new AgentLauncher();
41
+ * const agents = await launcher.list();
42
+ * await launcher.load('code_agent');
43
+ * await launcher.run(); // handles argv, one-shot, or interactive CLI
44
+ * ```
45
+ *
46
+ * Thin launcher example (agents/code_launcher.js style):
47
+ * ```js
48
+ * import AgentLauncher from '../lib/AgentLauncher.js';
49
+ * const launcher = new AgentLauncher();
50
+ * await launcher.load('code_agent');
51
+ * await launcher.run();
52
+ * ```
53
+ *
54
+ * Handoff support is automatic once an agent registers the hand_over / load_agent
55
+ * tools (see lib/handOffToolset.js and agents that call
56
+ * `toolset.addFrom(API.toolset.generic.handoff, 'hand_over')`).
57
+ *
58
+ * @see Agent
59
+ * @see agentLoader
60
+ * @see handOffToolset
61
+ */
62
+
63
+ /**
64
+ * The main launcher for clean Agent-based agents.
65
+ *
66
+ * @class AgentLauncher
67
+ */
68
+ class AgentLauncher {
69
+ /**
70
+ * The currently active Agent instance (after a successful load()).
71
+ * @private
72
+ * @type {Agent|null}
73
+ */
74
+ #activeAgent = null;
75
+
76
+ /**
77
+ * The single long-lived Cli instance used in interactive mode.
78
+ * Rebound (never recreated) on handoff to avoid duplicate global handlers.
79
+ * @private
80
+ * @type {Cli|null}
81
+ */
82
+ #cli = null;
83
+
84
+ /**
85
+ * Cached name of the active agent (mirrors agent.name).
86
+ * @private
87
+ * @type {string}
88
+ */
89
+ #name = 'no_name';
90
+
91
+ /**
92
+ * Whether the launcher is currently running in interactive CLI mode.
93
+ * Set by #startCli() and used to decide handoff behavior (rebind vs. direct trigger).
94
+ * @private
95
+ * @type {boolean}
96
+ */
97
+ #isInteractive = false;
98
+
99
+ /**
100
+ * Project-aware loader used for list/load/handoff operations.
101
+ * @private
102
+ * @type {ReturnType<import('./agentLoader.js').createAgentLoader>}
103
+ */
104
+ #loader;
105
+
106
+ /**
107
+ * Creates a new AgentLauncher.
108
+ *
109
+ * Pass `{ from: import.meta.url }` from the consuming project to resolve
110
+ * project-local agents relative to that project's nearest package.json.
111
+ *
112
+ * @constructor
113
+ * @param {import('./agentLoader.js').AgentLoaderOptions} [options={}] - Loader options.
114
+ *
115
+ * @example
116
+ * const launcher = new AgentLauncher({ from: import.meta.url });
117
+ * await launcher.load('weather_agent');
118
+ * await launcher.run();
119
+ */
120
+ constructor(options = {}) {
121
+ this.#loader = createAgentLoader(options);
122
+
123
+ // Keep the default API.toolset.generic.handoff singleton aligned with this
124
+ // launcher's discovery context for existing agents that borrow that toolset.
125
+ configureDefaultHandOffToolset({
126
+ listAgents: () => this.#loader.listAgents()
127
+ });
128
+ }
129
+
130
+ /**
131
+ * List all discoverable agents without loading or executing any of them.
132
+ *
133
+ * Performs a lightweight filesystem scan of the configured agent directory.
134
+ * For each agent it attempts a safe import to extract
135
+ * a short description (`desc`). Incompatible agents or agents that throw
136
+ * on import (side effects, top-level `await agent.run()`, missing exports, etc.)
137
+ * receive `desc: '[ERROR]'`.
138
+ *
139
+ * Search location when constructed with `{ from: import.meta.url }`:
140
+ * 1. <nearest-package-root-from-from>/agents/
141
+ *
142
+ * Only files whose basename matches the valid agent name pattern are returned.
143
+ * See `lib/agentLoader.js` for the exact regex (`validAgentNameRegex`).
144
+ *
145
+ * @async
146
+ * @returns {Promise<Array<{name: string, path: string, desc: string}>>}
147
+ * Array of discovered agents. Each object contains:
148
+ * - `name`: the agent identifier (basename without `.js`)
149
+ * - `path`: absolute path to the agent module on disk
150
+ * - `desc`: short description (from the agent's `description` / `call_description`),
151
+ * or the literal string `'[ERROR]'` when the agent is incompatible or fails to load
152
+ *
153
+ * @example
154
+ * const launcher = new AgentLauncher();
155
+ * const list = await launcher.list();
156
+ * console.log(list);
157
+ * // [
158
+ * // { name: 'code_agent', path: '/.../agents/code_agent.js', desc: 'Main coding expert. Handles implementation...' },
159
+ * // { name: 'weather_agent', path: '/.../agents/weather_agent.js', desc: 'Weather Agent: Specialized in current weather...' },
160
+ * // { name: 'test_agent', path: '/.../agents/test_agent.js', desc: '[ERROR]' },
161
+ * // ...
162
+ * // ]
163
+ *
164
+ * @example
165
+ * // Just the names + descriptions
166
+ * const summary = (await launcher.list()).map(a => `${a.name}: ${a.desc}`);
167
+ *
168
+ * @see module:agentLoader#listAgents
169
+ */
170
+ async list() {
171
+ return this.#loader.listAgents();
172
+ }
173
+
174
+ /**
175
+ * Load a clean Agent by name and make it the active agent.
176
+ *
177
+ * If `agentName` is omitted, it is read from the first non-flag positional
178
+ * argument in `process.argv` (after the script name).
179
+ *
180
+ * Internally delegates to `loadAgent()` from the agent loader, which:
181
+ * - Resolves the module path (same configured directory as `list()`)
182
+ * - Dynamically imports the module
183
+ * - Extracts the default export (or factory function)
184
+ * - Validates that the result is a proper Agent instance
185
+ *
186
+ * After successful load, the previous active agent (if any) is cleaned up
187
+ * via its `destructor()` method, and the new agent's 'agent:handoff' listener
188
+ * is attached.
189
+ *
190
+ * @async
191
+ * @param {string} [agentName] - Name of the agent to load (e.g. "code_agent").
192
+ * Must be a valid agent name (see agentLoader.js for the pattern).
193
+ * If omitted, read from process.argv.
194
+ * @param {Array<any>} [extraArgs=[]] - Optional arguments passed through to
195
+ * the agent module if it exports a factory function instead of an instance.
196
+ * @returns {Promise<AgentLauncher>} Returns `this` for chaining.
197
+ * @throws {Error} If no agent name can be determined, or if loading/validation fails.
198
+ *
199
+ * @example
200
+ * const launcher = new AgentLauncher();
201
+ * await launcher.load('code_agent');
202
+ *
203
+ * @example
204
+ * // Load from argv (typical when used from a thin launcher script)
205
+ * await launcher.load(); // reads first positional from process.argv
206
+ *
207
+ * @example
208
+ * // With extra args for a factory-style agent
209
+ * await launcher.load('my_agent', ['--debug']);
210
+ *
211
+ * @see module:agentLoader#loadAgent
212
+ * @see #_setActiveAgent
213
+ */
214
+ async load(agentName, extraArgs = []) {
215
+ if (!agentName) {
216
+ const argv = process.argv.slice(2);
217
+ const positionals = argv.filter(a => !a.startsWith('--'));
218
+ agentName = positionals[0];
219
+ }
220
+
221
+ if (!agentName) {
222
+ throw new Error('No agent name provided. Usage: node ... <agent_name> [message]');
223
+ }
224
+
225
+ const agent = await this.#loader.loadAgent(agentName, extraArgs);
226
+
227
+ this.#setActiveAgent(agent);
228
+ return this;
229
+ }
230
+
231
+ /**
232
+ * Internal method that replaces the current active agent and wires up
233
+ * handoff listeners and cleanup.
234
+ *
235
+ * Handoff / replacement safety steps:
236
+ * 1. If an old agent exists, call its `destructor()` (removes all listeners
237
+ * from its Prompt and Session).
238
+ * 2. Swap in the new agent and update the cached name.
239
+ * 3. Attach the handoff listener on the *new* prompt.
240
+ *
241
+ * This method is called by both `load()` (initial load or explicit reload)
242
+ * and `#handleHandoff()` (during in-process agent handoff or self-reset).
243
+ *
244
+ * @private
245
+ * @param {Agent} agent - A validated Agent instance to make active.
246
+ *
247
+ * @see Agent#destructor
248
+ * @see #_handleHandoff
249
+ */
250
+ #setActiveAgent(agent) {
251
+ if (this.#activeAgent) {
252
+ // Remove ALL listeners from the previous agent (via its destructor)
253
+ this.#activeAgent.destructor();
254
+ }
255
+
256
+ this.#activeAgent = agent;
257
+ this.#name = agent.name;
258
+
259
+ // Re-attach handoff listener on the *new* prompt
260
+ this.#activeAgent.getPrompt().on('agent:handoff', this.#handleHandoff.bind(this));
261
+ }
262
+
263
+ /**
264
+ * Handles the 'agent:handoff' event emitted by the active Prompt.
265
+ *
266
+ * This is the core of in-process agent handoff (including same-agent resets).
267
+ * It is automatically registered on every newly loaded agent.
268
+ *
269
+ * Expected payload shape (matches the `hand_over` / `load_agent` tool schema
270
+ * in lib/handOffToolset.js):
271
+ * ```js
272
+ * {
273
+ * agent: 'target_agent_name', // required (can be same as current for reset)
274
+ * context: '...' // required – everything the new/reset agent needs
275
+ * }
276
+ * ```
277
+ *
278
+ * Design decision – "fresh handoff":
279
+ * We deliberately **never** copy previous conversation history into the new
280
+ * agent. Reasons:
281
+ * - Prevents uncontrolled growth of session size / token usage
282
+ * - Keeps the new agent's context clean and task-focused
283
+ * - Forces explicit, high-quality handoff context from the previous agent
284
+ *
285
+ * Same-name handoff (self-reset) is intentionally supported:
286
+ * - Useful for token reduction after long/drifted conversations.
287
+ * - The agent must provide a genuinely new, focused task in the context.
288
+ * - It is **not** intended for meta "reload yourself" commands.
289
+ *
290
+ * Context injection behavior:
291
+ * - The raw `context` from the tool is wrapped with clear markers before injection.
292
+ * - This makes the handoff visible to the user in the CLI.
293
+ * - It also gives the receiving agent an explicit signal ("this is a handoff / fresh start")
294
+ * instead of blindly treating the previous agent's context string as a direct user command.
295
+ * - Different phrasing is used for normal cross-agent handoff vs. same-agent self-reset.
296
+ *
297
+ * Behavior differs by mode:
298
+ * - Interactive (`#isInteractive && #cli`): Rebind the existing Cli instance
299
+ * to the new prompt/session + description (with a handoff/reset banner), then
300
+ * call `cli.start(initialContext)`.
301
+ * - Non-interactive / one-shot: Inject context (if any) as a non-sticky
302
+ * user message and trigger a request on the new prompt.
303
+ *
304
+ * @async
305
+ * @private
306
+ * @param {Object} [payload={}] - Handoff payload from the tool.
307
+ * @param {string} payload.agent - Name of the target agent to load (may equal current name).
308
+ * @param {string} [payload.context] - Context/instructions for the new/reset agent.
309
+ *
310
+ * @see module:handOffToolset
311
+ * @see #_setActiveAgent
312
+ */
313
+ async #handleHandoff(payload = {}) {
314
+ const target = payload.agent || payload.target || payload.name;
315
+
316
+ if (!target || typeof target !== 'string') {
317
+ console.warn('[AgentLauncher] Handoff event received without a valid agent name');
318
+ return;
319
+ }
320
+
321
+ const currentName = this.#name || (this.#activeAgent && this.#activeAgent.name);
322
+ const isSelfReset = currentName && target === currentName;
323
+
324
+ let context = payload.context;
325
+ const hasContext = context && typeof context === 'string' && context.trim().length > 0;
326
+
327
+ // Wrap the raw context string that came from the previous agent's hand_over / load_agent tool call.
328
+ // Reasons:
329
+ // - Makes the injected context clearly visible to the human in the terminal (as a user message).
330
+ // - Gives the newly loaded agent an explicit, intentional signal instead of silently
331
+ // treating the previous agent's internal context as a direct user instruction.
332
+ // - Allows different messaging for normal handoff vs. deliberate same-agent "fresh start" reset.
333
+ if (hasContext) {
334
+ if (isSelfReset) {
335
+ // Self-reset / fresh-start case (same agent name): emphasize clean slate + new focused task.
336
+ context = `---fresh-start-context\n${context}\n---\n\nFresh start. Proceed with the task below.`;
337
+ } else {
338
+ // Normal cross-agent handoff: signal that this is a deliberate transfer to a (different) specialist.
339
+ context = `---handover-context\n${context}\n---\n\nConfirm handover and proceed with the task below.`;
340
+ }
341
+ }
342
+
343
+ if (isSelfReset) {
344
+ console.log(`[AgentLauncher] Self-reset requested for "${target}" (fresh context, no history).`);
345
+ } else {
346
+ console.log(`[AgentLauncher] Handoff requested → loading new agent: "${target}"`);
347
+ }
348
+
349
+ let newAgent;
350
+ try {
351
+ newAgent = await this.#loader.loadAgent(target);
352
+ } catch (err) {
353
+ console.error(`[AgentLauncher] Failed to load handoff target "${target}":`, err.message);
354
+ return;
355
+ }
356
+
357
+ // Replace the active agent completely.
358
+ // #setActiveAgent will:
359
+ // - call destructor on the old agent (removes listeners, helps GC)
360
+ // - swap prompt + session
361
+ // - re-attach the handoff listener
362
+ this.#setActiveAgent(newAgent);
363
+
364
+ if (isSelfReset) {
365
+ console.log(`[AgentLauncher] Agent reset complete. Fresh instance of ${newAgent.name} now active.`);
366
+ } else {
367
+ console.log(`[AgentLauncher] Active agent replaced. Now running: ${newAgent.name || target}`);
368
+ }
369
+
370
+ if (this.#isInteractive && this.#cli) {
371
+ console.log('[AgentLauncher] Rebinding existing CLI to the new/reset agent (no new Cli instance)...');
372
+
373
+ const banner = isSelfReset
374
+ ? `\n\n=== AGENT RESET (FRESH START) ===\nResetting: ${newAgent.name}\n(fresh state — only its own system prompt + new focused context)\nPrevious conversation history was discarded to reduce tokens.\n========================\n`
375
+ : `\n\n=== AGENT HANDOFF ===\nNow running: ${newAgent.name}\n(fresh state — only its own system prompt + injected context)\nNo previous conversation history was transferred.\n========================\n`;
376
+
377
+ const newDescription = banner + (newAgent.cliIntro || newAgent.description || newAgent.name || '');
378
+
379
+ this.#cli.rebind(
380
+ newAgent.getPrompt(),
381
+ newAgent.getSession(),
382
+ newDescription
383
+ );
384
+
385
+ const initialContext = hasContext ? context : '';
386
+
387
+ setTimeout(() => {
388
+ this.#cli.start(initialContext);
389
+ }, 120);
390
+ } else {
391
+ // Non-interactive / one-shot path
392
+ if (hasContext) {
393
+ try {
394
+ const newPrompt = newAgent.getPrompt();
395
+ newPrompt.add('user', context, false);
396
+ } catch (err) {
397
+ console.warn('[AgentLauncher] Failed to inject handoff/reset context (non-interactive):', err.message);
398
+ }
399
+ } else {
400
+ console.log('[AgentLauncher] No context provided with handoff/reset. New agent starts with only its system prompt.');
401
+ }
402
+
403
+ try {
404
+ await newAgent.getPrompt().triggerRequest();
405
+ } catch (err) {
406
+ console.error('[AgentLauncher] Error triggering request after handoff/reset:', err.message);
407
+ }
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Start the interactive CLI for the currently loaded agent.
413
+ *
414
+ * Creates a single Cli instance (if not already present) bound to the
415
+ * active agent's Prompt and Session, then calls `cli.start()` after a
416
+ * short delay (to allow the terminal to be ready).
417
+ *
418
+ * Sets the internal `#isInteractive` flag, which affects handoff behavior.
419
+ *
420
+ * @private
421
+ * @throws {Error} If there is no active agent (call `load()` first).
422
+ *
423
+ * @see Cli
424
+ * @see #run
425
+ */
426
+ #startCli() {
427
+ this.#isInteractive = true;
428
+ const agent = this.#activeAgent;
429
+ if (!agent) {
430
+ throw new Error('AgentLauncher has no active agent. Call load() first.');
431
+ }
432
+
433
+ const prompt = agent.getPrompt();
434
+ const session = agent.getSession();
435
+
436
+ const description = (agent.cliIntro !== '') ? agent.cliIntro : agent.description;
437
+
438
+ this.#cli = new Cli({
439
+ prompt,
440
+ session,
441
+ description
442
+ });
443
+
444
+ setTimeout(() => {
445
+ this.#cli.start();
446
+ }, 1000);
447
+ }
448
+
449
+ /**
450
+ * Main entry point. Orchestrates argument parsing and execution mode.
451
+ *
452
+ * Behavior (checked in this order):
453
+ * 1. `--help` → Print usage and return.
454
+ * 2. `--info` → Print agent.info() (or raw agent) and return.
455
+ * 3. If no active agent → automatically call `load()` (reads from argv).
456
+ * 4. One-shot mode: if a second positional argument exists after the agent
457
+ * name, treat the rest of the line as a message and call
458
+ * `activeAgent.directCall(message)`.
459
+ * 5. Default: start interactive CLI via `#startCli()`.
460
+ *
461
+ * @async
462
+ * @returns {Promise<any>} In one-shot mode, returns the result of directCall.
463
+ * In other modes, returns undefined.
464
+ *
465
+ * @example
466
+ * // From a thin launcher script that already called load()
467
+ * await launcher.run();
468
+ *
469
+ * @example
470
+ * // One-shot usage (message after agent name)
471
+ * // node ... code_agent "Refactor the Session class"
472
+ * const result = await launcher.run();
473
+ *
474
+ * @see #load
475
+ * @see #_startCli
476
+ */
477
+ async run() {
478
+ const args = process.argv.slice(2);
479
+
480
+ if (args.includes('--help')) {
481
+ console.log('Usage: node utils/launch_agent.js <agent_name> [message] [--info] [--help]');
482
+ console.log('');
483
+ console.log(' launcher.run() starts the interactive CLI (after load()).');
484
+ console.log(' A message after the agent name performs a one-shot directCall.');
485
+ console.log('');
486
+ console.log(' --info Print agent info and exit');
487
+ return;
488
+ }
489
+
490
+ if (args.includes('--info')) {
491
+ const agent = this.#activeAgent;
492
+ if (agent && typeof agent.info === 'function') {
493
+ console.log(agent.info());
494
+ } else {
495
+ console.log(agent || 'No active agent');
496
+ }
497
+ return;
498
+ }
499
+
500
+ if (!this.#activeAgent) {
501
+ await this.load();
502
+ }
503
+
504
+ // One-shot: second positional after agent name
505
+ const positionals = args.filter(a => !a.startsWith('--'));
506
+ const message = positionals.length > 1 ? positionals.slice(1).join(' ') : null;
507
+
508
+ if (message) {
509
+ const result = await this.#activeAgent.directCall(message);
510
+ if (result != null) console.log(result);
511
+ return result;
512
+ }
513
+
514
+ // Default: start interactive CLI
515
+ this.#startCli();
516
+ }
517
+
518
+ // ────────────────────────────────────────────────────────────────────────────
519
+ // Public accessors
520
+ // ────────────────────────────────────────────────────────────────────────────
521
+
522
+ /**
523
+ * Returns the currently active Agent instance (or null if none loaded).
524
+ *
525
+ * @returns {Agent|null}
526
+ */
527
+ getActiveAgent() {
528
+ return this.#activeAgent;
529
+ }
530
+ /**
531
+ * Directly assign an already-created Agent instance as the active agent.
532
+ *
533
+ * This is the programmatic alternative to `load()`. Use it when the caller
534
+ * has constructed an agent manually, or when testing an agent module that is
535
+ * outside the configured agent loader search paths.
536
+ *
537
+ * The previous active agent, if any, is cleaned up via `destructor()`. The new
538
+ * agent becomes the launcher's active agent and has the launcher's
539
+ * `agent:handoff` listener attached to its Prompt, just like agents loaded via
540
+ * `load()`.
541
+ *
542
+ * The supplied agent is assumed to be a valid `Agent` instance. Unlike
543
+ * `load()`, this method does not resolve, import, or validate an agent module
544
+ * through the configured agent loader.
545
+ *
546
+ * @param {Agent} newAgent - Existing Agent instance to make active.
547
+ * @returns {AgentLauncher} Returns `this` for chaining.
548
+ *
549
+ * @example
550
+ * const launcher = new AgentLauncher();
551
+ * launcher.setActiveAgent(agent);
552
+ * await launcher.run();
553
+ *
554
+ * @see #load
555
+ * @see #getActiveAgent
556
+ */
557
+ setActiveAgent(newAgent) {
558
+ this.#setActiveAgent(newAgent);
559
+ return this;
560
+ }
561
+
562
+ /**
563
+ * Returns the Prompt belonging to the active agent (or null).
564
+ *
565
+ * Useful for advanced scenarios where you need direct access to the
566
+ * underlying prompt (e.g. to listen to events manually).
567
+ *
568
+ * @returns {import('./Prompt.js').default|null}
569
+ */
570
+ getPrompt() {
571
+ return this.#activeAgent?.getPrompt() || null;
572
+ }
573
+
574
+ /**
575
+ * Returns the Session belonging to the active agent (or null).
576
+ *
577
+ * @returns {import('./Session.js').default|null}
578
+ */
579
+ getSession() {
580
+ return this.#activeAgent?.getSession() || null;
581
+ }
582
+
583
+ /**
584
+ * The name of the currently active agent (or 'no_name' if none loaded).
585
+ *
586
+ * @type {string}
587
+ */
588
+ get name() {
589
+ return this.#name;
590
+ }
591
+ }
592
+
593
+ export default AgentLauncher;