@j-o-r/hello-dave 0.1.0 → 0.1.4
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.
- package/CHANGELOG.md +42 -25
- package/README.md +81 -221
- package/TODO.md +173 -35
- package/agents/agent_creator.js +105 -0
- package/agents/agent_creator.prompt.md +371 -0
- package/agents/ask_agent.js +64 -127
- package/agents/claude_agent.js +68 -0
- package/agents/code_agent.js +55 -135
- package/agents/code_agent.prompt.md +50 -0
- package/agents/echo_agent.js +76 -0
- package/agents/financial_expert.js +75 -0
- package/agents/gpt_agent.js +52 -103
- package/agents/gpt_code.js +81 -0
- package/agents/grok_agent.js +58 -114
- package/agents/minimax_agent.js +92 -0
- package/agents/mureka_agent.js +77 -0
- package/agents/planner_agent.js +172 -0
- package/agents/stability_agent.js +87 -0
- package/agents/test_agent.js +75 -157
- package/agents/weather_agent.js +73 -0
- package/agents/workflow_agent.js +189 -0
- package/bin/dave.js +436 -184
- package/docs/bin-dave.md +85 -35
- package/docs/cdn-ssh.md +100 -0
- package/docs/creating-agents.md +301 -0
- package/docs/creating-toolsets.md +336 -0
- package/docs/docs-organization.md +48 -0
- package/docs/project-overview.md +86 -51
- package/lib/API/elevenlabs.io/music.compose.md +441 -0
- package/lib/API/elevenlabs.io/music.create-composition-plan.md +370 -0
- package/lib/API/elevenlabs.io/music.stream.md +425 -0
- package/lib/API/lalal.ai/lalal.js +445 -0
- package/lib/API/lalal.ai/openapi.json +2614 -0
- package/lib/API/minimax/ImageToolset.js +82 -37
- package/lib/API/minimax/MusicToolset.js +125 -79
- package/lib/API/minimax/VideoToolset.js +170 -167
- package/lib/API/minimax/image.js +5 -1
- package/lib/API/minimax/music.js +210 -23
- package/lib/API/minimax/video.js +242 -53
- package/lib/API/mureka/MusicToolset.js +646 -0
- package/lib/API/mureka/README.md +41 -0
- package/lib/API/mureka/index.js +7 -0
- package/lib/API/mureka/music.js +658 -0
- package/lib/API/openai.com/index.js +7 -0
- package/lib/API/openai.com/{reponses/text.js → responses.js} +64 -18
- package/lib/API/openai.com/video.create.character.md +40 -0
- package/lib/API/openai.com/video.create.md +219 -0
- package/lib/API/openai.com/video.delete.md +44 -0
- package/lib/API/openai.com/video.download.md +31 -0
- package/lib/API/openai.com/video.edit.md +155 -0
- package/lib/API/openai.com/video.extend.md +166 -0
- package/lib/API/openai.com/video.fetch.character.md +43 -0
- package/lib/API/openai.com/video.js +784 -0
- package/lib/API/openai.com/video.list.md +201 -0
- package/lib/API/openai.com/video.remix.md +175 -0
- package/lib/API/openai.com/video.retrieve.md +139 -0
- package/lib/API/openai.com/videoToolset.js +616 -0
- package/lib/API/stability.ai/ImageToolset.js +131 -40
- package/lib/API/stability.ai/MusicToolset.js +79 -47
- package/lib/API/stability.ai/audio.js +63 -131
- package/lib/API/x.ai/chat.responses.md +1040 -0
- package/lib/API/x.ai/image.js +229 -59
- package/lib/API/x.ai/imageToolset.js +376 -0
- package/lib/API/x.ai/index.js +1 -3
- package/lib/API/x.ai/responses.js +9 -18
- package/lib/Agent.js +271 -0
- package/lib/Agent.js.old +284 -0
- package/lib/AgentLauncher.js +562 -0
- package/lib/Cli.js +87 -13
- package/lib/Prompt.js +23 -1
- package/lib/Session.js +5 -4
- package/lib/ToolSet.js +102 -6
- package/lib/agentLoader.js +369 -0
- package/lib/cdn.js +67 -231
- package/lib/{CdnToolset.js → cdnToolset.js} +47 -64
- package/lib/defaultToolsets.js +43 -0
- package/lib/fafs.js +1 -1
- package/lib/genericToolset.js +442 -119
- package/lib/handOffToolset.js +179 -0
- package/lib/index.js +34 -27
- package/lib/toolsetLoader.js +248 -0
- package/package.json +11 -5
- package/types/API/lalal.ai/lalal.d.ts +116 -0
- package/types/API/minimax/image.d.ts +2 -1
- package/types/API/minimax/music.d.ts +189 -26
- package/types/API/minimax/video.d.ts +100 -31
- package/types/API/mureka/index.d.ts +7 -0
- package/types/API/mureka/music.d.ts +472 -0
- package/types/API/openai.com/index.d.ts +7 -0
- package/types/API/openai.com/{reponses/text.d.ts → responses.d.ts} +11 -11
- package/types/API/openai.com/video.d.ts +409 -0
- package/types/API/openai.com/videoToolset.d.ts +24 -0
- package/types/API/stability.ai/audio.d.ts +14 -103
- package/types/API/stability.ai/image.d.ts +2 -2
- package/types/API/x.ai/image.d.ts +138 -26
- package/types/API/x.ai/imageToolset.d.ts +3 -0
- package/types/API/x.ai/index.d.ts +1 -3
- package/types/API/x.ai/responses.d.ts +4 -4
- package/types/Agent.d.ts +123 -0
- package/types/AgentLauncher.d.ts +222 -0
- package/types/Cli.d.ts +28 -8
- package/types/Prompt.d.ts +23 -5
- package/types/Session.d.ts +1 -1
- package/types/ToolSet.d.ts +10 -0
- package/types/agentLoader.d.ts +78 -0
- package/types/cdn.d.ts +15 -90
- package/types/defaultToolsets.d.ts +9 -0
- package/types/fafs.d.ts +1 -1
- package/types/genericToolset.d.ts +1 -1
- package/types/handOffToolset.d.ts +28 -0
- package/types/index.d.ts +19 -16
- package/types/toolsetLoader.d.ts +114 -0
- package/utils/format_log.js +101 -23
- package/utils/launch_agent.js +18 -0
- package/utils/list_sessions.sh +13 -5
- package/utils/search_sessions.sh +65 -29
- package/utils/toolsets.js +33 -0
- package/README.md.bak.1779452127 +0 -240
- package/agents/codeserver.sh +0 -47
- package/agents/daisy_agent.js +0 -173
- package/agents/docs_agent.js +0 -148
- package/agents/memory_agent.js +0 -263
- package/agents/minimax.js +0 -173
- package/agents/npm_agent.js +0 -202
- package/agents/prompt_agent.js +0 -133
- package/agents/readme_agent.js +0 -148
- package/agents/spawn_agent.js +0 -160
- package/agents/stability.js +0 -173
- package/agents/todo_agent.js +0 -175
- package/bin/codeDave +0 -58
- package/docs/agent-dave-websocket-protocol.md +0 -180
- package/docs/agent-manager.md +0 -244
- package/docs/codeserver-pattern.md +0 -191
- package/docs/generic-toolset.md +0 -326
- package/docs/howtos/agent-networking.md +0 -253
- package/docs/howtos/spawn-agents.md.bak +0 -200
- package/docs/howtos/spawn-agents.md.bak_new +0 -200
- package/docs/multi-agent-clusters.md +0 -265
- package/docs/music-toolsets.md +0 -137
- package/docs/path-resolution-best-practices.md +0 -104
- package/docs/plans/minimax-music-generation.md +0 -80
- package/docs/plans/unified-agent-architecture.md +0 -146
- package/docs/plans/websocket-streaming-plan.md.bak +0 -317
- package/docs/prompt/spawn_agent.md +0 -175
- package/docs/prompt/spawn_agent.md.bak +0 -201
- package/docs/prompt/task_clarification_and_documentation.md +0 -35
- package/docs/prompt-class.md +0 -141
- package/docs/todo-archive-infra-2026-04-21.md +0 -15
- package/docs/todo-archive-v0.0.8.md +0 -1
- package/docs/todo-archive-v0.1.0.md +0 -32
- package/docs/todo-archive.md +0 -44
- package/docs/tools-syntax-validation.md +0 -121
- package/docs/toolset.md +0 -164
- package/docs/xai-responses.md +0 -111
- package/docs/xai_collections.md +0 -106
- package/lib/API/x.ai/ImageToolset.js +0 -165
- package/lib/API/x.ai/text.js +0 -415
- package/lib/AgentClient.js +0 -248
- package/lib/AgentManager.js +0 -245
- package/lib/AgentServer.js +0 -404
- package/lib/wsCli.js +0 -287
- package/lib/wsIO.js +0 -90
- package/types/API/x.ai/text.d.ts +0 -286
- package/types/AgentClient.d.ts +0 -109
- package/types/AgentManager.d.ts +0 -100
- package/types/AgentServer.d.ts +0 -89
- package/types/wsCli.d.ts +0 -17
- package/types/wsIO.d.ts +0 -30
- package/utils/test.sh +0 -46
- /package/docs/{suggestions.md → _notes/token-counts.md} +0 -0
- /package/lib/API/openai.com/{reponses/MESSAGES.md → MESSAGES.md} +0 -0
- /package/types/API/{x.ai/ImageToolset.d.ts → mureka/MusicToolset.d.ts} +0 -0
- /package/types/{CdnToolset.d.ts → cdnToolset.d.ts} +0 -0
|
@@ -0,0 +1,562 @@
|
|
|
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
|
+
/**
|
|
532
|
+
* Returns the Prompt belonging to the active agent (or null).
|
|
533
|
+
*
|
|
534
|
+
* Useful for advanced scenarios where you need direct access to the
|
|
535
|
+
* underlying prompt (e.g. to listen to events manually).
|
|
536
|
+
*
|
|
537
|
+
* @returns {import('./Prompt.js').default|null}
|
|
538
|
+
*/
|
|
539
|
+
getPrompt() {
|
|
540
|
+
return this.#activeAgent?.getPrompt() || null;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Returns the Session belonging to the active agent (or null).
|
|
545
|
+
*
|
|
546
|
+
* @returns {import('./Session.js').default|null}
|
|
547
|
+
*/
|
|
548
|
+
getSession() {
|
|
549
|
+
return this.#activeAgent?.getSession() || null;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* The name of the currently active agent (or 'no_name' if none loaded).
|
|
554
|
+
*
|
|
555
|
+
* @type {string}
|
|
556
|
+
*/
|
|
557
|
+
get name() {
|
|
558
|
+
return this.#name;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export default AgentLauncher;
|