@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.
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 -3
  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 +562 -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 +11 -5
  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 -3
  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 +222 -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 -16
  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
package/lib/Cli.js CHANGED
@@ -71,6 +71,8 @@ e.g. >edit< , >paste< , >#!bash [command]<
71
71
  * - Event-driven updates from Prompt (messages, truncated, finished).
72
72
  * - Built-in help, reset, load session, copy last message (Alt+m).
73
73
  * - Customizable intro/help via options.
74
+ * - Supports rebinding to a new Prompt/Session (for in-process agent handoff) without creating a new CLI instance.
75
+ * Use rebind() then start(initialContext) for clean handoff.
74
76
  *
75
77
  * @example
76
78
  * const cli = new Cli({ prompt: myPrompt, session: mySession });
@@ -89,6 +91,7 @@ class Cli {
89
91
  #prompt;
90
92
  #description = '';
91
93
  #lastMessage = '';
94
+ #globalInitialized = false;
92
95
 
93
96
  /**
94
97
  * Initializes the Cli instance with required Prompt and Session.
@@ -104,17 +107,27 @@ class Cli {
104
107
  this.#session = options.session;
105
108
  this.#description = options.description ? options.description + '\n\n' + INTRO : INTRO;
106
109
  this.#HELP = options.help || HELP;
107
- this.#setupCLI();
110
+
111
+ // One-time global setup (input handler, key mappings, base roles)
112
+ this.#initializeGlobal();
113
+
114
+ // Attach prompt-specific listeners (can be re-done on rebind)
115
+ this.#attachPromptListeners();
108
116
  }
109
117
 
110
- /** @private */
111
- #setupCLI() {
118
+ /** @private One-time global CLI setup (safe to call only once) */
119
+ #initializeGlobal() {
120
+ if (this.#globalInitialized) return;
121
+
112
122
  cli.setRole('assistant', `${this.#session.name}: > `, 'brightGreen');
113
123
  cli.setRole('util', ``, 'brightYellow');
114
124
  cli.setRole('error', ``, 'red');
115
- cli.setRole('log', '', 'brightBlue');
125
+ cli.setRole('log', '', 'brightBlue');
126
+ cli.setRole('reasoning', 'reasoning: > ', 'brightBlue');
127
+
116
128
  // @ts-ignore
117
129
  cli.inputHandler = async (s, role) => await this.#handleInput(s, role);
130
+
118
131
  cli.registerKeyMappings([
119
132
  { // reset the session, empty the context from the prompt
120
133
  name: 'r', ctrl: false, meta: true, shift: false, handler: async () => {
@@ -146,13 +159,22 @@ class Cli {
146
159
  }
147
160
  }
148
161
  ]);
162
+
163
+ this.#globalInitialized = true;
164
+ }
165
+
166
+ /** @private Attach (or re-attach) listeners to the current prompt */
167
+ #attachPromptListeners() {
168
+ // Remove any previous listeners from this prompt (defensive)
169
+ // NO: the privious prompt should be destructed outside
170
+
149
171
  // Event listeners for Prompt updates
150
172
  this.#prompt.on('message', (msg) => {
151
173
  let write = false;
152
174
  if (msg.role === 'reasoning') {
153
175
  const content = this.#prompt.contentToString(msg.content);
154
- cli.focus('util');
155
- cli.write(`REASONING: ---------\n${content}\n---------\n`);
176
+ cli.focus('reasoning');
177
+ cli.write(`${content}\n`);
156
178
  write = true;
157
179
  } else if (msg.role === 'log') {
158
180
  const content = this.#prompt.contentToString(msg.content);
@@ -173,15 +195,53 @@ class Cli {
173
195
  cli.startSpinner();
174
196
  }
175
197
  });
198
+
176
199
  this.#prompt.on('truncated', () => {
177
200
  cli.focus('log');
178
201
  cli.write('--- prompt truncated ---');
179
202
  });
203
+
180
204
  this.#prompt.on('finished', () => {
181
205
  cli.focus('user', true);
182
206
  });
183
207
  }
184
208
 
209
+ /**
210
+ * Rebind this CLI instance to a new Prompt and Session (used for in-process agent handoff).
211
+ *
212
+ * - Unbinds listeners from the old prompt.
213
+ * - Updates internal prompt/session references.
214
+ * - Updates the assistant role prefix to the new agent's name.
215
+ * - Re-attaches prompt event listeners to the *new* prompt.
216
+ * - Optionally updates the description shown on next start().
217
+ *
218
+ * Does **not** re-register global key mappings or inputHandler (prevents duplication and double-typing).
219
+ * Does **not** call start(). Caller should call start() (optionally with initial context) afterwards.
220
+ *
221
+ * @param {Prompt} newPrompt
222
+ * @param {Session} newSession
223
+ * @param {string} [newDescription] - Optional new description/intro (banner + agent info). If omitted, keeps previous.
224
+ */
225
+ rebind(newPrompt, newSession, newDescription = null) {
226
+ if (!newPrompt || !newSession) {
227
+ throw new Error('rebind() requires a valid prompt and session');
228
+ }
229
+
230
+ this.#prompt = newPrompt;
231
+ this.#session = newSession;
232
+
233
+ if (newDescription && typeof newDescription === 'string' && newDescription.trim()) {
234
+ this.#description = newDescription + '\n\n' + INTRO;
235
+ }
236
+ // else keep the existing description
237
+
238
+ // Update the assistant role prefix for the new agent name
239
+ cli.setRole('assistant', `${this.#session.name}: > `, 'brightGreen');
240
+
241
+ // Re-attach listeners to the new prompt only
242
+ this.#attachPromptListeners();
243
+ }
244
+
185
245
  /**
186
246
  * Handles user input, processes via Prompt.call(), captures last message, and manages errors.
187
247
  * Only processes 'user' role inputs.
@@ -190,7 +250,11 @@ class Cli {
190
250
  * @param {string} role - Input role (e.g., 'user').
191
251
  */
192
252
  async #handleInput(s, role) {
193
- if (!s || s.trim() === '') return;
253
+ if (!s || s.trim() === '') {
254
+ cli.focus('util');
255
+ cli.focus('user', true);
256
+ return;
257
+ }
194
258
  if (role !== 'user') return;
195
259
  cli.startSpinner();
196
260
  try {
@@ -226,18 +290,28 @@ class Cli {
226
290
 
227
291
  /**
228
292
  * Starts the CLI: Shows intro/description, focuses user input, optionally processes initial message.
229
- * @param {string} [s] - Optional initial user message to process.
293
+ *
294
+ * When called with a string `s` (e.g. handoff context), the context is shown as a user message
295
+ * and then processed. This makes the injected handoff context visible in the terminal.
296
+ *
297
+ * @param {string} [s] - Optional initial user message / handoff context to process.
230
298
  * @returns {Promise<void>}
231
- * @example
232
- * await cli.start(); // Interactive mode
233
- * await cli.start('What is the weather?'); // Start with query
234
299
  */
235
300
  async start(s) {
236
301
  cli.focus('util');
237
302
  cli.write(this.#description);
238
- cli.focus('user', true);
303
+
239
304
  if (s && s.trim() !== '') {
305
+ // Explicitly render the injected context as a user message
306
+ // (normal typed input is echoed by the underlying cli library;
307
+ // programmatically injected messages need to be written manually)
308
+ cli.focus('user');
309
+ cli.write(s);
310
+ cli.focus('user', true);
311
+
240
312
  await this.#handleInput(s, 'user');
313
+ } else {
314
+ cli.focus('user', true);
241
315
  }
242
316
  }
243
317
 
@@ -293,4 +367,4 @@ const copyToClipboard = async (text) => {
293
367
  };
294
368
 
295
369
  export default Cli;
296
- export { copyToClipboard };
370
+ export { copyToClipboard };
package/lib/Prompt.js CHANGED
@@ -538,6 +538,27 @@ class Prompt extends EventEmitter {
538
538
  }
539
539
 
540
540
 
541
+ /**
542
+ * Prune resolved function-call I/O from the prompt history, keeping only the
543
+ * most recent complete function request/response pair.
544
+ *
545
+ * A resolved function call is an assistant `function_request` that has a
546
+ * matching tool `function_response` by `call_id` (or `id` fallback). Older
547
+ * resolved pairs are removed from their messages; empty assistant/tool
548
+ * messages are removed entirely. Unresolved function calls and normal
549
+ * user/assistant text messages are preserved.
550
+ *
551
+ * This is useful before serializing history for providers such as the OpenAI
552
+ * Responses API, where repeatedly sending all historical tool transcripts can
553
+ * pollute context and waste tokens.
554
+ *
555
+ * @returns {boolean} True when the prompt history was changed.
556
+ */
557
+ pruneResolvedFunctionCallsExceptLast() {
558
+ return pruneResolvedToolIOByCallIdExceptLastHelper(this.#messages, 'lastCall');
559
+ }
560
+
561
+
541
562
  /**
542
563
  * reduce the prompt length to fit in the contextWindow
543
564
  * or simply reset when there is no contextWindow
@@ -756,7 +777,8 @@ class Prompt extends EventEmitter {
756
777
  const tools = this.toolsetFunctions().join(', ');
757
778
  const context = this.contextLength;
758
779
  const tokens = this.countTokens();
759
- return `Cwd: ${cwd}\nTools: ${tools}\nMax context: ${context}\nUsed Tokens: ${tokens}`
780
+ const options = JSON.stringify(this.#adapter.options, null, ' ')
781
+ return `Cwd: ${cwd}\nFunction_calls: ${tools}\nMax context: ${context}\nUsed Tokens: ${tokens}\nOptions:\n${options}\n`
760
782
  }
761
783
 
762
784
  }
package/lib/Session.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Session manager for recording and retrieving chat sessions tied to a Prompt instance.
3
- * Handles caching of messages, records, and logs in NDJSON files under `.cache/hello-dave/&lt;name&gt;/`.
3
+ * Handles caching of messages, records, and logs in NDJSON files under `.cache/@j-o-r/hello-dave/&lt;name&gt;/`.
4
4
  * Automatically creates session names from first non-sticky user message.
5
5
  * Listens to Prompt events (message, record, reset, truncated, others) for persistence.
6
6
  *
@@ -74,12 +74,12 @@ class Session {
74
74
  * Initializes cache folders and event listeners.
75
75
  * @param {string} promptName - The name of the prompt.
76
76
  * @param {Prompt} prompt - The Prompt instance.
77
- * @param {string} [baseFolder] - Base storage folder; defaults to `.cache/hello-dave`.
77
+ * @param {string} [baseFolder] - Base storage folder; defaults to `.cache/@j-o-r/hello-dave`.
78
78
  * @private
79
79
  */
80
80
  #initializeCache = (promptName, prompt, baseFolder) => {
81
81
  if (!baseFolder) {
82
- baseFolder = path.resolve('.cache', 'hello-dave');
82
+ baseFolder = path.resolve('.cache', '@j-o-r/hello-dave');
83
83
  }
84
84
  const root = new CacheSync(baseFolder, true, 'any');
85
85
  this.name = root.sanitizeKey(promptName);
@@ -121,7 +121,7 @@ class Session {
121
121
  *
122
122
  * @param {string} name - The name of the prompt.
123
123
  * @param {Prompt} prompt - The Prompt instance.
124
- * @param {string} [storage] - The base storage folder; defaults to process.cwd()/.cache/hello-dave.
124
+ * @param {string} [storage] - The base storage folder; defaults to process.cwd()/.cache/@j-o-r/hello-dave.
125
125
  */
126
126
  constructor(name, prompt, storage) {
127
127
  this.#initializeCache(name, prompt, storage);
@@ -164,6 +164,7 @@ class Session {
164
164
  } else if (this.#sessionName !== '') {
165
165
  sessKey = this.#sessionCache.sanitizeKey(`${this.#sessionName}_${this.#sessionCounter}`);
166
166
  }
167
+ // console.log({sessKey});
167
168
  if (sessKey) {
168
169
  if (this.#queue.length > 0) {
169
170
  let i = 0;
package/lib/ToolSet.js CHANGED
@@ -39,6 +39,43 @@ const CHOICES = {
39
39
  */
40
40
  const isValidName = (s) => /^[#!a-z_0-9]{2,}$/.test(s);
41
41
 
42
+ /**
43
+ * Parse function-call parameters from provider output.
44
+ *
45
+ * Providers normally return parameters as a JSON object string, but some model
46
+ * outputs/API layers double-encode that string. This parser accepts objects,
47
+ * normal JSON strings, and double-encoded JSON strings without touching nested
48
+ * string literal values.
49
+ *
50
+ * @param {*} parameters - Raw function-call parameters.
51
+ * @returns {Record<string, *>} Parsed parameter object.
52
+ * @throws {Error} If parameters cannot be parsed as an object.
53
+ */
54
+ const parseFunctionParameters = (parameters) => {
55
+ if (parameters == null || parameters === '') return {};
56
+ if (typeof parameters === 'object' && !Array.isArray(parameters)) return parameters;
57
+ if (typeof parameters !== 'string') {
58
+ throw new Error('Function parameters must be a JSON object or JSON object string');
59
+ }
60
+
61
+ let current = parameters;
62
+ const seen = new Set();
63
+ for (let i = 0; i < 4 && !seen.has(current); i++) {
64
+ seen.add(current);
65
+ const parsed = JSON.parse(current);
66
+ if (typeof parsed === 'string') {
67
+ current = parsed;
68
+ continue;
69
+ }
70
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
71
+ return parsed;
72
+ }
73
+ throw new Error('Function parameters must decode to a JSON object');
74
+ }
75
+
76
+ throw new Error('Function parameters are repeatedly encoded or invalid');
77
+ };
78
+
42
79
  /**
43
80
  * @classdesc ToolSet manages a collection of tools (functions) that can be registered and executed, typically for AI agent function calling.
44
81
  * Supports schema definition compatible with JSON Schema for parameters.
@@ -150,6 +187,25 @@ class ToolSet {
150
187
  return this;
151
188
  }
152
189
 
190
+
191
+ /**
192
+ * Copies ONE tool from another ToolSet into this one.
193
+ * Optionally renames it to avoid name conflicts.
194
+ *
195
+ * @param {ToolSet} sourceToolSet - The source ToolSet
196
+ * @param {string} sourceName - Name of the tool in the source ToolSet
197
+ * @param {string} [newName] - Optional new name for the tool in this ToolSet
198
+ * @returns {ToolSet} Returns this instance for chaining
199
+ */
200
+ addFrom(sourceToolSet, sourceName, newName = null) {
201
+ if (!(sourceToolSet instanceof ToolSet)) {
202
+ throw new Error("addFrom() expects a ToolSet instance");
203
+ }
204
+ const tool = sourceToolSet.get(sourceName);
205
+ const finalName = newName || sourceName;
206
+ this.add(finalName, tool.description, tool.parameters, tool.method);
207
+ return this;
208
+ }
153
209
  /**
154
210
  * Getter for the current tool choice setting.
155
211
  * @returns {string} The tool choice: 'auto', 'none', or 'required'
@@ -196,11 +252,17 @@ class ToolSet {
196
252
  const startTime = new Date().getTime();
197
253
  let response = '';
198
254
  prompt.emit(prompt.EVENTS.tool_request, { name: call.function_request.name, call_id: call.function_request.call_id });
199
- try {
200
- response = await this.call(call.function_request.name, JSON.parse(call.function_request.parameters));
201
- } catch (error) {
202
- response = `Error: ${error.name} - ${error.message}`;
203
- prompt.emit(prompt.EVENTS.tool_error, { name: call.function_request.name, call_id: call.function_request.call_id, error: response });
255
+ if (call.function_request.name === 'load_agent' || call.function_request.name === 'hand_over') {
256
+ // Special handoff tool (Stage 1 prototype). Do not attempt normal execution.
257
+ // A clean response is provided here; the handoff sequence (assistant message + emit) is handled below.
258
+ response = 'Handoff initiated.';
259
+ } else {
260
+ try {
261
+ response = await this.call(call.function_request.name, parseFunctionParameters(call.function_request.parameters));
262
+ } catch (error) {
263
+ response = `Error: ${error.name} - ${error.message}`;
264
+ prompt.emit(prompt.EVENTS.tool_error, { name: call.function_request.name, call_id: call.function_request.call_id, error: response });
265
+ }
204
266
  }
205
267
  const duration = new Date().getTime() - startTime;
206
268
  prompt.emit(prompt.EVENTS.tool_response, { name: call.function_request.name, call_id: call.function_request.call_id, duration });
@@ -221,7 +283,41 @@ class ToolSet {
221
283
  functionResponses.push(responseObj);
222
284
  }
223
285
  prompt.addMultiModal('tool', functionResponses);
286
+
287
+ // === Clean in-process handoff support (matches lib/handOffToolset.js schema) ===
288
+ // After all normal function calls (including their function_responses) have been processed:
289
+ // 1. Normal function_response for load_agent / hand_over is already added above (as "Handoff initiated.").
290
+ // 2. Add assistant message: "agent hand-over to <target>..."
291
+ // 3. Emit 'agent:handoff' with clean payload { agent, context } so AgentLauncher can perform fresh-state replacement.
292
+ // This block activates for calls named exactly 'load_agent' or 'hand_over'.
293
+ // All other behavior (existing tools, error handling, events, etc.) remains 100% unchanged.
294
+ for (let i = 0; i < len; i++) {
295
+ const call = calls[i];
296
+ if (call.function_request.name === 'load_agent' || call.function_request.name === 'hand_over') {
297
+ let params = {};
298
+ try {
299
+ params = parseFunctionParameters(call.function_request.parameters || '{}');
300
+ } catch (_) {
301
+ params = {};
302
+ }
303
+
304
+ // Clean schema from handOffToolset.js: { agent: string (required), context: string (required) }
305
+ const target = params.agent || params.target || params.name || params.agentName;
306
+ const context = params.context || '';
307
+
308
+ if (!target) {
309
+ // Fallback: do not emit if we cannot determine a target
310
+ continue;
311
+ }
312
+
313
+ // 2. Add the specified assistant message (non-sticky)
314
+ prompt.add('assistant', `agent hand-over to ${target}...`, false);
315
+
316
+ // 3. Emit the event with clean payload (AgentLauncher.#handleHandoff expects this)
317
+ prompt.emit('agent:handoff', { agent: target, context });
318
+ }
319
+ }
224
320
  }
225
321
  }
226
322
 
227
- export default ToolSet;
323
+ export default ToolSet;