@clinebot/agents 0.0.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 (90) hide show
  1. package/README.md +145 -0
  2. package/dist/agent-input.d.ts +2 -0
  3. package/dist/agent.d.ts +56 -0
  4. package/dist/extensions.d.ts +21 -0
  5. package/dist/hooks/engine.d.ts +42 -0
  6. package/dist/hooks/index.d.ts +2 -0
  7. package/dist/hooks/lifecycle.d.ts +5 -0
  8. package/dist/hooks/node.d.ts +2 -0
  9. package/dist/hooks/subprocess-runner.d.ts +16 -0
  10. package/dist/hooks/subprocess.d.ts +268 -0
  11. package/dist/index.browser.d.ts +1 -0
  12. package/dist/index.browser.js +49 -0
  13. package/dist/index.d.ts +15 -0
  14. package/dist/index.js +49 -0
  15. package/dist/index.node.d.ts +5 -0
  16. package/dist/index.node.js +49 -0
  17. package/dist/mcp/index.d.ts +4 -0
  18. package/dist/mcp/policies.d.ts +14 -0
  19. package/dist/mcp/tools.d.ts +9 -0
  20. package/dist/mcp/types.d.ts +35 -0
  21. package/dist/message-builder.d.ts +31 -0
  22. package/dist/prompts/cline.d.ts +1 -0
  23. package/dist/prompts/index.d.ts +1 -0
  24. package/dist/runtime/agent-runtime-bus.d.ts +13 -0
  25. package/dist/runtime/conversation-store.d.ts +16 -0
  26. package/dist/runtime/lifecycle-orchestrator.d.ts +28 -0
  27. package/dist/runtime/tool-orchestrator.d.ts +39 -0
  28. package/dist/runtime/turn-processor.d.ts +21 -0
  29. package/dist/teams/index.d.ts +3 -0
  30. package/dist/teams/multi-agent.d.ts +566 -0
  31. package/dist/teams/spawn-agent-tool.d.ts +85 -0
  32. package/dist/teams/team-tools.d.ts +51 -0
  33. package/dist/tools/ask-question.d.ts +12 -0
  34. package/dist/tools/create.d.ts +59 -0
  35. package/dist/tools/execution.d.ts +61 -0
  36. package/dist/tools/formatting.d.ts +20 -0
  37. package/dist/tools/index.d.ts +11 -0
  38. package/dist/tools/registry.d.ts +26 -0
  39. package/dist/tools/validation.d.ts +27 -0
  40. package/dist/types.d.ts +826 -0
  41. package/package.json +54 -0
  42. package/src/agent-input.ts +116 -0
  43. package/src/agent.test.ts +931 -0
  44. package/src/agent.ts +1050 -0
  45. package/src/example.test.ts +564 -0
  46. package/src/extensions.ts +337 -0
  47. package/src/hooks/engine.test.ts +163 -0
  48. package/src/hooks/engine.ts +537 -0
  49. package/src/hooks/index.ts +6 -0
  50. package/src/hooks/lifecycle.ts +239 -0
  51. package/src/hooks/node.ts +18 -0
  52. package/src/hooks/subprocess-runner.ts +140 -0
  53. package/src/hooks/subprocess.test.ts +180 -0
  54. package/src/hooks/subprocess.ts +620 -0
  55. package/src/index.browser.ts +1 -0
  56. package/src/index.node.ts +21 -0
  57. package/src/index.ts +133 -0
  58. package/src/mcp/index.ts +17 -0
  59. package/src/mcp/policies.test.ts +51 -0
  60. package/src/mcp/policies.ts +53 -0
  61. package/src/mcp/tools.test.ts +76 -0
  62. package/src/mcp/tools.ts +60 -0
  63. package/src/mcp/types.ts +41 -0
  64. package/src/message-builder.test.ts +175 -0
  65. package/src/message-builder.ts +429 -0
  66. package/src/prompts/cline.ts +49 -0
  67. package/src/prompts/index.ts +1 -0
  68. package/src/runtime/agent-runtime-bus.ts +53 -0
  69. package/src/runtime/conversation-store.ts +61 -0
  70. package/src/runtime/lifecycle-orchestrator.ts +90 -0
  71. package/src/runtime/tool-orchestrator.ts +177 -0
  72. package/src/runtime/turn-processor.ts +250 -0
  73. package/src/streaming.test.ts +197 -0
  74. package/src/streaming.ts +307 -0
  75. package/src/teams/index.ts +63 -0
  76. package/src/teams/multi-agent.lifecycle.test.ts +48 -0
  77. package/src/teams/multi-agent.ts +1866 -0
  78. package/src/teams/spawn-agent-tool.test.ts +172 -0
  79. package/src/teams/spawn-agent-tool.ts +223 -0
  80. package/src/teams/team-tools.test.ts +448 -0
  81. package/src/teams/team-tools.ts +929 -0
  82. package/src/tools/ask-question.ts +78 -0
  83. package/src/tools/create.ts +104 -0
  84. package/src/tools/execution.ts +311 -0
  85. package/src/tools/formatting.ts +73 -0
  86. package/src/tools/index.ts +45 -0
  87. package/src/tools/registry.ts +52 -0
  88. package/src/tools/tools.test.ts +292 -0
  89. package/src/tools/validation.ts +73 -0
  90. package/src/types.ts +966 -0
@@ -0,0 +1,537 @@
1
+ import type {
2
+ AgentHookControl,
3
+ HookDispatchResult,
4
+ HookEventEnvelope,
5
+ HookHandlerResult,
6
+ HookPolicies,
7
+ HookStage,
8
+ HookStagePolicy,
9
+ HookStagePolicyInput,
10
+ } from "../types.js";
11
+
12
+ export interface HookHandler {
13
+ name: string;
14
+ stage: HookStage;
15
+ priority?: number;
16
+ handle: (
17
+ event: HookEventEnvelope,
18
+ ) => Promise<AgentHookControl | undefined> | AgentHookControl | undefined;
19
+ }
20
+
21
+ export interface HookDispatchInput<TPayload = unknown> {
22
+ stage: HookStage;
23
+ runId: string;
24
+ agentId: string;
25
+ conversationId: string;
26
+ parentAgentId: string | null;
27
+ iteration?: number;
28
+ parentEventId?: string;
29
+ payload: TPayload;
30
+ }
31
+
32
+ export interface HookEngineOptions {
33
+ policies?: HookPolicies;
34
+ onDispatchError?: (
35
+ error: Error,
36
+ event: HookEventEnvelope,
37
+ handlerName: string,
38
+ ) => void;
39
+ onDroppedEvent?: (event: HookEventEnvelope, policy: HookStagePolicy) => void;
40
+ }
41
+
42
+ interface QueueItem {
43
+ event: HookEventEnvelope;
44
+ stagePolicy: HookStagePolicy;
45
+ handlers: HookHandler[];
46
+ }
47
+
48
+ interface StageQueueState {
49
+ activeCount: number;
50
+ items: QueueItem[];
51
+ }
52
+
53
+ function compareHandlers(a: HookHandler, b: HookHandler): number {
54
+ const priorityDiff = (b.priority ?? 0) - (a.priority ?? 0);
55
+ if (priorityDiff !== 0) {
56
+ return priorityDiff;
57
+ }
58
+ return a.name.localeCompare(b.name);
59
+ }
60
+
61
+ const STAGE_DEFAULTS: Record<HookStage, HookStagePolicy> = {
62
+ input: {
63
+ mode: "blocking",
64
+ timeoutMs: 2500,
65
+ retries: 0,
66
+ retryDelayMs: 100,
67
+ failureMode: "fail_open",
68
+ maxConcurrency: 1,
69
+ queueLimit: 100,
70
+ },
71
+ runtime_event: {
72
+ mode: "async",
73
+ timeoutMs: 1500,
74
+ retries: 0,
75
+ retryDelayMs: 100,
76
+ failureMode: "fail_open",
77
+ maxConcurrency: 4,
78
+ queueLimit: 2000,
79
+ },
80
+ session_start: {
81
+ mode: "blocking",
82
+ timeoutMs: 2500,
83
+ retries: 0,
84
+ retryDelayMs: 100,
85
+ failureMode: "fail_open",
86
+ maxConcurrency: 1,
87
+ queueLimit: 100,
88
+ },
89
+ run_start: {
90
+ mode: "blocking",
91
+ timeoutMs: 2500,
92
+ retries: 0,
93
+ retryDelayMs: 100,
94
+ failureMode: "fail_open",
95
+ maxConcurrency: 1,
96
+ queueLimit: 100,
97
+ },
98
+ iteration_start: {
99
+ mode: "blocking",
100
+ timeoutMs: 2000,
101
+ retries: 0,
102
+ retryDelayMs: 100,
103
+ failureMode: "fail_open",
104
+ maxConcurrency: 1,
105
+ queueLimit: 200,
106
+ },
107
+ turn_start: {
108
+ mode: "blocking",
109
+ timeoutMs: 2000,
110
+ retries: 0,
111
+ retryDelayMs: 100,
112
+ failureMode: "fail_open",
113
+ maxConcurrency: 1,
114
+ queueLimit: 200,
115
+ },
116
+ before_agent_start: {
117
+ mode: "blocking",
118
+ timeoutMs: 3000,
119
+ retries: 0,
120
+ retryDelayMs: 100,
121
+ failureMode: "fail_open",
122
+ maxConcurrency: 1,
123
+ queueLimit: 200,
124
+ },
125
+ tool_call_before: {
126
+ mode: "blocking",
127
+ timeoutMs: 4000,
128
+ retries: 1,
129
+ retryDelayMs: 150,
130
+ failureMode: "fail_open",
131
+ maxConcurrency: 1,
132
+ queueLimit: 500,
133
+ },
134
+ tool_call_after: {
135
+ mode: "blocking",
136
+ timeoutMs: 3000,
137
+ retries: 1,
138
+ retryDelayMs: 200,
139
+ failureMode: "fail_open",
140
+ maxConcurrency: 1,
141
+ queueLimit: 1000,
142
+ },
143
+ turn_end: {
144
+ mode: "blocking",
145
+ timeoutMs: 3000,
146
+ retries: 1,
147
+ retryDelayMs: 200,
148
+ failureMode: "fail_open",
149
+ maxConcurrency: 1,
150
+ queueLimit: 500,
151
+ },
152
+ iteration_end: {
153
+ mode: "async",
154
+ timeoutMs: 3000,
155
+ retries: 1,
156
+ retryDelayMs: 200,
157
+ failureMode: "fail_open",
158
+ maxConcurrency: 2,
159
+ queueLimit: 500,
160
+ },
161
+ run_end: {
162
+ mode: "async",
163
+ timeoutMs: 3000,
164
+ retries: 1,
165
+ retryDelayMs: 200,
166
+ failureMode: "fail_open",
167
+ maxConcurrency: 2,
168
+ queueLimit: 500,
169
+ },
170
+ session_shutdown: {
171
+ mode: "async",
172
+ timeoutMs: 3000,
173
+ retries: 1,
174
+ retryDelayMs: 200,
175
+ failureMode: "fail_open",
176
+ maxConcurrency: 2,
177
+ queueLimit: 500,
178
+ },
179
+ error: {
180
+ mode: "async",
181
+ timeoutMs: 1500,
182
+ retries: 0,
183
+ retryDelayMs: 100,
184
+ failureMode: "fail_open",
185
+ maxConcurrency: 2,
186
+ queueLimit: 500,
187
+ },
188
+ };
189
+
190
+ function mergeControl(
191
+ base: AgentHookControl | undefined,
192
+ next: AgentHookControl | undefined,
193
+ ): AgentHookControl | undefined {
194
+ if (!base && !next) {
195
+ return undefined;
196
+ }
197
+
198
+ const appendMessages = [
199
+ ...(Array.isArray(base?.appendMessages) ? base.appendMessages : []),
200
+ ...(Array.isArray(next?.appendMessages) ? next.appendMessages : []),
201
+ ];
202
+
203
+ const merged: AgentHookControl = {
204
+ cancel: !!(base?.cancel || next?.cancel),
205
+ review: !!(base?.review || next?.review),
206
+ context: [base?.context, next?.context]
207
+ .filter((value): value is string => typeof value === "string" && !!value)
208
+ .join("\n"),
209
+ overrideInput: Object.hasOwn(next ?? {}, "overrideInput")
210
+ ? next?.overrideInput
211
+ : base?.overrideInput,
212
+ };
213
+
214
+ const systemPrompt =
215
+ typeof next?.systemPrompt === "string"
216
+ ? next.systemPrompt
217
+ : base?.systemPrompt;
218
+ if (typeof systemPrompt === "string") {
219
+ merged.systemPrompt = systemPrompt;
220
+ }
221
+ if (appendMessages.length > 0) {
222
+ merged.appendMessages = appendMessages;
223
+ }
224
+ return merged;
225
+ }
226
+
227
+ function normalizePolicy(
228
+ base: HookStagePolicy,
229
+ patch?: HookStagePolicyInput,
230
+ ): HookStagePolicy {
231
+ return {
232
+ mode: patch?.mode ?? base.mode,
233
+ timeoutMs: patch?.timeoutMs ?? base.timeoutMs,
234
+ retries: patch?.retries ?? base.retries,
235
+ retryDelayMs: patch?.retryDelayMs ?? base.retryDelayMs,
236
+ failureMode: patch?.failureMode ?? base.failureMode,
237
+ maxConcurrency: Math.max(1, patch?.maxConcurrency ?? base.maxConcurrency),
238
+ queueLimit: Math.max(1, patch?.queueLimit ?? base.queueLimit),
239
+ };
240
+ }
241
+
242
+ function asError(error: unknown): Error {
243
+ return error instanceof Error ? error : new Error(String(error));
244
+ }
245
+
246
+ function isControl(value: unknown): value is AgentHookControl {
247
+ return !!value && typeof value === "object";
248
+ }
249
+
250
+ async function withTimeout<T>(
251
+ promise: Promise<T>,
252
+ timeoutMs: number,
253
+ handlerName: string,
254
+ stage: HookStage,
255
+ ): Promise<T> {
256
+ if (timeoutMs <= 0) {
257
+ return await promise;
258
+ }
259
+ let timeoutId: NodeJS.Timeout | undefined;
260
+ try {
261
+ return await Promise.race([
262
+ promise,
263
+ new Promise<T>((_, reject) => {
264
+ timeoutId = setTimeout(() => {
265
+ reject(
266
+ new Error(
267
+ `Hook handler "${handlerName}" timed out after ${timeoutMs}ms at stage "${stage}"`,
268
+ ),
269
+ );
270
+ }, timeoutMs);
271
+ }),
272
+ ]);
273
+ } finally {
274
+ if (timeoutId) {
275
+ clearTimeout(timeoutId);
276
+ }
277
+ }
278
+ }
279
+
280
+ export class HookEngine {
281
+ private readonly handlers = new Map<HookStage, HookHandler[]>();
282
+ private readonly options: HookEngineOptions;
283
+ private sequence = 0;
284
+ private eventCounter = 0;
285
+ private readonly stageQueues = new Map<HookStage, StageQueueState>();
286
+ private readonly inFlight = new Set<Promise<void>>();
287
+
288
+ constructor(options: HookEngineOptions = {}) {
289
+ this.options = options;
290
+ }
291
+
292
+ register(handler: HookHandler): void {
293
+ const list = this.handlers.get(handler.stage) ?? [];
294
+ list.push(handler);
295
+ list.sort(compareHandlers);
296
+ this.handlers.set(handler.stage, list);
297
+ }
298
+
299
+ async dispatch<TPayload>(
300
+ input: HookDispatchInput<TPayload>,
301
+ ): Promise<HookDispatchResult> {
302
+ const event: HookEventEnvelope<TPayload> = {
303
+ eventId: `hook_evt_${String(++this.eventCounter).padStart(8, "0")}`,
304
+ stage: input.stage,
305
+ createdAt: new Date(),
306
+ sequence: ++this.sequence,
307
+ runId: input.runId,
308
+ agentId: input.agentId,
309
+ conversationId: input.conversationId,
310
+ parentAgentId: input.parentAgentId,
311
+ iteration: input.iteration,
312
+ parentEventId: input.parentEventId,
313
+ payload: input.payload,
314
+ };
315
+
316
+ const handlers = this.getHandlers(input.stage);
317
+ if (handlers.length === 0) {
318
+ return {
319
+ event,
320
+ queued: false,
321
+ dropped: false,
322
+ control: undefined,
323
+ results: [],
324
+ };
325
+ }
326
+
327
+ const stagePolicy = this.resolveStagePolicy(input.stage);
328
+ if (stagePolicy.mode === "async") {
329
+ const state = this.getStageQueueState(input.stage);
330
+ if (state.items.length >= stagePolicy.queueLimit) {
331
+ this.options.onDroppedEvent?.(event, stagePolicy);
332
+ return {
333
+ event,
334
+ queued: false,
335
+ dropped: true,
336
+ control: undefined,
337
+ results: [],
338
+ };
339
+ }
340
+
341
+ state.items.push({ event, stagePolicy, handlers });
342
+ this.kickQueue(input.stage);
343
+ return {
344
+ event,
345
+ queued: true,
346
+ dropped: false,
347
+ control: undefined,
348
+ results: [],
349
+ };
350
+ }
351
+
352
+ const results = await this.executeHandlers(event, handlers, stagePolicy);
353
+ return {
354
+ event,
355
+ queued: false,
356
+ dropped: false,
357
+ control: this.mergeControlsFromResults(results),
358
+ results,
359
+ };
360
+ }
361
+
362
+ async shutdown(drainTimeoutMs = 3000): Promise<void> {
363
+ const start = Date.now();
364
+ while (this.inFlight.size > 0) {
365
+ if (Date.now() - start >= drainTimeoutMs) {
366
+ break;
367
+ }
368
+ await Promise.race([...this.inFlight]);
369
+ }
370
+ }
371
+
372
+ private getHandlers(stage: HookStage): HookHandler[] {
373
+ const handlers = this.handlers.get(stage) ?? [];
374
+ return [...handlers];
375
+ }
376
+
377
+ private getStageQueueState(stage: HookStage): StageQueueState {
378
+ const existing = this.stageQueues.get(stage);
379
+ if (existing) {
380
+ return existing;
381
+ }
382
+ const created: StageQueueState = { activeCount: 0, items: [] };
383
+ this.stageQueues.set(stage, created);
384
+ return created;
385
+ }
386
+
387
+ private kickQueue(stage: HookStage): void {
388
+ const state = this.getStageQueueState(stage);
389
+ const stagePolicy = this.resolveStagePolicy(stage);
390
+ while (
391
+ state.activeCount < stagePolicy.maxConcurrency &&
392
+ state.items.length > 0
393
+ ) {
394
+ const item = state.items.shift();
395
+ if (!item) {
396
+ return;
397
+ }
398
+ state.activeCount += 1;
399
+ const job: Promise<void> = this.executeHandlers(
400
+ item.event,
401
+ item.handlers,
402
+ item.stagePolicy,
403
+ )
404
+ .then(() => undefined)
405
+ .catch(() => {
406
+ // Failures are already surfaced via onDispatchError.
407
+ })
408
+ .finally(() => {
409
+ state.activeCount -= 1;
410
+ this.inFlight.delete(job);
411
+ this.kickQueue(stage);
412
+ });
413
+ this.inFlight.add(job);
414
+ }
415
+ }
416
+
417
+ private resolveStagePolicy(stage: HookStage): HookStagePolicy {
418
+ const base = normalizePolicy(
419
+ STAGE_DEFAULTS[stage],
420
+ this.options.policies?.defaultPolicy,
421
+ );
422
+ return normalizePolicy(base, this.options.policies?.stages?.[stage]);
423
+ }
424
+
425
+ private resolveHandlerPolicy(
426
+ stage: HookStage,
427
+ handlerName: string,
428
+ ): HookStagePolicy {
429
+ const stagePolicy = this.resolveStagePolicy(stage);
430
+ return normalizePolicy(
431
+ stagePolicy,
432
+ this.options.policies?.handlers?.[handlerName],
433
+ );
434
+ }
435
+
436
+ private async executeHandlers(
437
+ event: HookEventEnvelope,
438
+ handlers: HookHandler[],
439
+ stagePolicy: HookStagePolicy,
440
+ ): Promise<HookHandlerResult[]> {
441
+ const results: HookHandlerResult[] = [];
442
+
443
+ for (const handler of handlers) {
444
+ const policy = this.resolveHandlerPolicy(event.stage, handler.name);
445
+ if (stagePolicy.mode === "async" && policy.mode === "blocking") {
446
+ results.push({
447
+ handlerName: handler.name,
448
+ stage: event.stage,
449
+ status: "skipped",
450
+ attempts: 0,
451
+ durationMs: 0,
452
+ });
453
+ continue;
454
+ }
455
+
456
+ const result = await this.executeHandler(event, handler, policy);
457
+ results.push(result);
458
+
459
+ if (
460
+ (result.status === "error" || result.status === "timeout") &&
461
+ policy.failureMode === "fail_closed"
462
+ ) {
463
+ throw (
464
+ result.error ??
465
+ new Error(
466
+ `Hook handler "${handler.name}" failed at stage "${event.stage}"`,
467
+ )
468
+ );
469
+ }
470
+ }
471
+
472
+ return results;
473
+ }
474
+
475
+ private async executeHandler(
476
+ event: HookEventEnvelope,
477
+ handler: HookHandler,
478
+ policy: HookStagePolicy,
479
+ ): Promise<HookHandlerResult> {
480
+ const startedAt = Date.now();
481
+ let attempt = 0;
482
+ let lastError: Error | undefined;
483
+ let lastStatus: HookHandlerResult["status"] = "error";
484
+
485
+ while (attempt <= policy.retries) {
486
+ attempt += 1;
487
+ try {
488
+ const value = await withTimeout(
489
+ Promise.resolve(handler.handle(event)),
490
+ policy.timeoutMs,
491
+ handler.name,
492
+ event.stage,
493
+ );
494
+ const control = isControl(value) ? value : undefined;
495
+ return {
496
+ handlerName: handler.name,
497
+ stage: event.stage,
498
+ status: "ok",
499
+ attempts: attempt,
500
+ durationMs: Date.now() - startedAt,
501
+ control,
502
+ };
503
+ } catch (error) {
504
+ lastError = asError(error);
505
+ lastStatus = /timed out/i.test(lastError.message) ? "timeout" : "error";
506
+ this.options.onDispatchError?.(lastError, event, handler.name);
507
+ if (attempt <= policy.retries && policy.retryDelayMs > 0) {
508
+ await new Promise((resolve) =>
509
+ setTimeout(resolve, policy.retryDelayMs),
510
+ );
511
+ }
512
+ }
513
+ }
514
+
515
+ return {
516
+ handlerName: handler.name,
517
+ stage: event.stage,
518
+ status: lastStatus,
519
+ attempts: attempt,
520
+ durationMs: Date.now() - startedAt,
521
+ error: lastError,
522
+ };
523
+ }
524
+
525
+ private mergeControlsFromResults(
526
+ results: HookHandlerResult[],
527
+ ): AgentHookControl | undefined {
528
+ let merged: AgentHookControl | undefined;
529
+ for (const result of results) {
530
+ if (result.status !== "ok") {
531
+ continue;
532
+ }
533
+ merged = mergeControl(merged, result.control);
534
+ }
535
+ return merged;
536
+ }
537
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ type HookDispatchInput,
3
+ HookEngine,
4
+ type HookHandler,
5
+ } from "./engine.js";
6
+ export { registerLifecycleHandlers } from "./lifecycle.js";