clanka 0.0.3 → 0.0.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.
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import { Sink } from "effect";
5
- import type { Output } from "./Agent.ts";
4
+ import { Stream } from "effect";
5
+ import { type Output, AgentFinished } from "./Agent.ts";
6
6
  /**
7
7
  * @since 1.0.0
8
8
  * @category Models
9
9
  */
10
- export type OutputFormatter<E = never, R = never> = Sink.Sink<void, Output, never, E, R>;
10
+ export type OutputFormatter<E = never, R = never> = (stream: Stream.Stream<Output, AgentFinished>) => Stream.Stream<string, E, R>;
11
11
  /**
12
12
  * @since 1.0.0
13
13
  * @category Pretty
@@ -1 +1 @@
1
- {"version":3,"file":"OutputFormatter.d.ts","sourceRoot":"","sources":["../src/OutputFormatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAU,IAAI,EAAU,MAAM,QAAQ,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxC;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,IAAI,CAC3D,IAAI,EACJ,MAAM,EACN,KAAK,EACL,CAAC,EACD,CAAC,CACF,CAAA;AAyED;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,eAAoC,CAAA"}
1
+ {"version":3,"file":"OutputFormatter.d.ts","sourceRoot":"","sources":["../src/OutputFormatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,KAAK,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAGvD;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CAClD,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,KACzC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AAEhC;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,eAgDlB,CAAA"}
@@ -1,67 +1,56 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import { Effect, Sink, Stream } from "effect";
4
+ import { Stream } from "effect";
5
+ import { AgentFinished } from "./Agent.js";
5
6
  import chalk from "chalk";
6
- const prettyPrefixed = (prefix) => Sink.suspend(() => {
7
- let hadReasoningDelta = false;
8
- return Sink.forEach((output) => {
9
- switch (output._tag) {
10
- case "SubagentPart": {
11
- console.log(chalkSubagentHeading(`${subagentIcon} Subagent #${output.id} starting (${output.modelAndProvider})`));
12
- console.log("");
13
- console.log(chalk.dim(output.prompt));
14
- console.log("");
15
- const prefix = chalk.magenta(`Subagent #${output.id}:`) + " ";
16
- return output.output.pipe(Stream.run(prettyPrefixed(prefix)), Effect.catch((finished) => {
17
- console.log(chalkSubagentHeading(`${subagentIcon} Subagent #${output.id} complete`));
18
- console.log("");
19
- console.log(finished.summary);
20
- console.log("");
21
- return Effect.void;
22
- }), Effect.forkChild);
23
- }
24
- case "ReasoningDelta": {
25
- process.stdout.write(output.delta);
26
- hadReasoningDelta = true;
27
- break;
28
- }
29
- case "ReasoningEnd": {
30
- if (hadReasoningDelta) {
31
- console.log("\n");
32
- hadReasoningDelta = false;
33
- }
34
- break;
35
- }
36
- case "ScriptStart": {
37
- console.log(prefix + chalkScriptHeading(`${scriptIcon} Executing script`));
38
- console.log("");
39
- console.log(chalk.dim(output.script));
40
- console.log("");
41
- break;
42
- }
43
- case "ScriptEnd": {
44
- console.log(prefix + chalkScriptHeading(`${scriptIcon} Script output`));
45
- console.log("");
46
- const lines = output.output.split("\n");
47
- const truncated = lines.length > 20
48
- ? lines.slice(0, 20).join("\n") + "\n... (truncated)"
49
- : output.output;
50
- console.log(chalk.dim(truncated));
51
- console.log(chalk.reset(""));
52
- break;
53
- }
54
- }
55
- return Effect.void;
56
- });
57
- });
58
7
  /**
59
8
  * @since 1.0.0
60
9
  * @category Pretty
61
10
  */
62
- export const pretty = prettyPrefixed("");
11
+ export const pretty = (stream) => stream.pipe(Stream.map((output) => {
12
+ let prefix = "";
13
+ if (output._tag === "SubagentPart") {
14
+ prefix = chalk.magenta(`Subagent #${output.id}:`) + " ";
15
+ output = output.part;
16
+ }
17
+ switch (output._tag) {
18
+ case "SubagentStart": {
19
+ return `${chalkSubagentHeading(`${subagentIcon} Subagent #${output.id} starting (${output.modelAndProvider})`)}
20
+
21
+ ${chalk.dim(output.prompt)}\n\n`;
22
+ }
23
+ case "SubagentComplete": {
24
+ return `${chalkSubagentHeading(`${subagentIcon} Subagent #${output.id} complete`)}
25
+
26
+ ${output.summary}\n\n`;
27
+ }
28
+ case "ReasoningStart": {
29
+ return (prefix + chalkReasoningHeading(`${thinkingIcon} Thinking:`) + " ");
30
+ }
31
+ case "ReasoningDelta": {
32
+ return output.delta;
33
+ }
34
+ case "ReasoningEnd": {
35
+ return "\n\n";
36
+ }
37
+ case "ScriptStart": {
38
+ return `${prefix}${chalkScriptHeading(`${scriptIcon} Executing script`)}\n\n${chalk.dim(output.script)}\n\n`;
39
+ }
40
+ case "ScriptEnd": {
41
+ const lines = output.output.split("\n");
42
+ const truncated = lines.length > 20
43
+ ? lines.slice(0, 20).join("\n") + "\n... (truncated)"
44
+ : output.output;
45
+ return `${prefix}${chalkScriptHeading(`${scriptIcon} Script output`)}\n\n${chalk.dim(truncated)}\n\n`;
46
+ }
47
+ }
48
+ }), Stream.catch((finished) => Stream.succeed(`\n${chalk.bold.green(`${doneIcon} Task complete:`)}\n\n${finished.summary}`)));
63
49
  const chalkScriptHeading = chalk.bold.blue;
64
50
  const chalkSubagentHeading = chalk.bold.magenta;
51
+ const chalkReasoningHeading = chalk.bold.yellow;
65
52
  const scriptIcon = "\u{f0bc1}";
66
53
  const subagentIcon = "\u{ee0d} ";
54
+ const thinkingIcon = "\u{f07f6}";
55
+ const doneIcon = "\u{eab2}";
67
56
  //# sourceMappingURL=OutputFormatter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"OutputFormatter.js","sourceRoot":"","sources":["../src/OutputFormatter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OutputFormatter.js","sourceRoot":"","sources":["../src/OutputFormatter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clanka",
3
3
  "type": "module",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -58,7 +58,7 @@
58
58
  ]
59
59
  },
60
60
  "scripts": {
61
- "check": "oxlint -c .oxlintrc.json --fix && pnpm tsgo --noEmit",
61
+ "check": "oxlint -c .oxlintrc.json --fix && pnpm tsc --noEmit",
62
62
  "test": "vitest run",
63
63
  "build": "tsc -b tsconfig.json"
64
64
  }
package/src/Agent.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import {
5
5
  Array,
6
- Data,
7
6
  Deferred,
8
7
  Effect,
9
8
  FileSystem,
@@ -39,6 +38,16 @@ import type { ChildProcessSpawner } from "effect/unstable/process"
39
38
  */
40
39
  export interface Agent {
41
40
  readonly output: Stream.Stream<Output, AgentFinished>
41
+
42
+ /**
43
+ * Send a message to the agent to steer its behavior. This is useful for
44
+ * providing feedback or new instructions while the agent is running.
45
+ *
46
+ * The effect will only complete once the message has been sent.
47
+ * Interrupting the effect will withdraw the message, so it will not be sent
48
+ * to the agent.
49
+ */
50
+ steer(message: string): Effect.Effect<void>
42
51
  }
43
52
 
44
53
  /**
@@ -111,66 +120,128 @@ export const make: <
111
120
  | ProviderName
112
121
  | ModelName
113
122
  >()
123
+ const pendingMessages = new Set<{
124
+ readonly message: string
125
+ readonly resume: (effect: Effect.Effect<void>) => void
126
+ }>()
114
127
 
115
128
  let system = yield* generateSystem(allTools)
129
+
130
+ const agentsMd = yield* pipe(
131
+ fs.readFileString(pathService.resolve(options.directory, "AGENTS.md")),
132
+ Effect.option,
133
+ )
134
+
135
+ if (Option.isSome(agentsMd)) {
136
+ system += `
137
+ # AGENTS.md
138
+
139
+ The following instructions are from ./AGENTS.md in the current directory.
140
+ You do not need to read this file again.
141
+
142
+ **ALWAYS follow these instructions when completing tasks**:
143
+
144
+ <!-- AGENTS.md start -->
145
+ ${agentsMd.value}
146
+ <!-- AGENTS.md end -->
147
+ `
148
+ }
149
+
116
150
  if (options.system) {
117
- system += `\n${options.system}`
151
+ system += `\n${options.system}\n`
118
152
  }
153
+
119
154
  const withSystemPrompt = OpenAiLanguageModel.withConfigOverride({
120
155
  store: false,
121
156
  instructions: system,
122
157
  })
123
158
 
124
- const agentsMd = yield* pipe(
125
- fs.readFileString(pathService.resolve(options.directory, "AGENTS.md")),
126
- Effect.option,
127
- )
159
+ let agentCounter = 0
128
160
 
129
- let subagentId = 0
161
+ const outputBuffer = new Map<number, Array<Output>>()
162
+ let currentOutputAgent: number | null = null
130
163
 
131
164
  const spawn: (
165
+ agentId: number,
132
166
  prompt: Prompt.Prompt,
133
167
  ) => Stream.Stream<
134
168
  Output,
135
169
  AgentFinished,
136
170
  LanguageModel.LanguageModel | ProviderName
137
- > = Effect.fnUntraced(function* (prompt) {
171
+ > = Effect.fnUntraced(function* (agentId, prompt) {
138
172
  const ai = yield* LanguageModel.LanguageModel
139
173
  const provider = yield* ProviderName
140
174
  const deferred = yield* Deferred.make<string>()
141
175
  const output = yield* Queue.make<Output, AgentFinished>()
142
176
 
177
+ function maybeSend(agentId: number, part: Output, lock = false) {
178
+ if (currentOutputAgent === null || currentOutputAgent === agentId) {
179
+ Queue.offerUnsafe(output, part)
180
+ if (lock) {
181
+ currentOutputAgent = agentId
182
+ }
183
+ return true
184
+ }
185
+ const state = outputBuffer.get(agentId) ?? []
186
+ state.push(part)
187
+ return false
188
+ }
189
+
190
+ function flushBuffer() {
191
+ currentOutputAgent = null
192
+ for (const [id, state] of outputBuffer) {
193
+ outputBuffer.delete(id)
194
+ Queue.offerAllUnsafe(output, state)
195
+ const lastPart = state[state.length - 1]!
196
+ if (lastPart._tag === "ReasoningDelta") {
197
+ currentOutputAgent = id
198
+ break
199
+ }
200
+ }
201
+ }
202
+
143
203
  const taskServices = SubagentContext.serviceMap({
144
204
  spawn: ({ prompt }) => {
145
- let id = ++subagentId
146
- return spawn(
205
+ let id = agentCounter++
206
+ const stream = spawn(
207
+ id,
147
208
  Prompt.make(`You have been spawned using "subagent" to complete the following task:
148
209
 
149
210
  ${prompt}`),
150
- ).pipe(
151
- Stream.broadcast({
152
- capacity: "unbounded",
153
- }),
154
- Effect.flatMap(
155
- Effect.fnUntraced(function* (stream) {
156
- const provider = yield* ProviderName
157
- const model = yield* ModelName
211
+ )
212
+ return Effect.gen(function* () {
213
+ const provider = yield* ProviderName
214
+ const model = yield* ModelName
215
+ Queue.offerUnsafe(
216
+ output,
217
+ new SubagentStart({ id, prompt, model, provider }),
218
+ )
219
+ return yield* stream.pipe(
220
+ Stream.runForEachArray((parts) => {
221
+ for (const part of parts) {
222
+ switch (part._tag) {
223
+ case "SubagentStart":
224
+ case "SubagentComplete":
225
+ case "SubagentPart":
226
+ break
227
+
228
+ default:
229
+ Queue.offerUnsafe(output, new SubagentPart({ id, part }))
230
+ break
231
+ }
232
+ }
233
+ return Effect.void
234
+ }),
235
+ Effect.as(""),
236
+ Effect.catch((finished) => {
158
237
  Queue.offerUnsafe(
159
238
  output,
160
- new SubagentPart({
161
- id,
162
- prompt,
163
- provider,
164
- model,
165
- output: stream,
166
- }),
239
+ new SubagentComplete({ id, summary: finished.summary }),
167
240
  )
168
- return yield* Stream.runDrain(stream)
241
+ return Effect.succeed(finished.summary)
169
242
  }),
170
- ),
171
- Effect.scoped,
172
- Effect.as(""),
173
- Effect.catch((e) => Effect.succeed(e.summary)),
243
+ )
244
+ }).pipe(
174
245
  options.subagentModel
175
246
  ? Effect.provide(Layer.orDie(options.subagentModel))
176
247
  : Effect.provideServices(services),
@@ -181,18 +252,6 @@ ${prompt}`),
181
252
  ServiceMap.add(TaskCompleteDeferred, deferred),
182
253
  )
183
254
 
184
- prompt = Prompt.concat(
185
- prompt,
186
- agentsMd.pipe(
187
- Option.map((md) =>
188
- Prompt.make(`Here is a copy of ./AGENTS.md. ALWAYS follow these instructions when completing the above task:
189
-
190
- ${md}`),
191
- ),
192
- Option.getOrElse(() => Prompt.empty),
193
- ),
194
- )
195
-
196
255
  if (provider !== "openai") {
197
256
  prompt = Prompt.setSystem(prompt, system)
198
257
  }
@@ -201,7 +260,7 @@ ${md}`),
201
260
  yield* Effect.gen(function* () {
202
261
  while (true) {
203
262
  if (currentScript.length > 0) {
204
- Queue.offerUnsafe(output, new ScriptStart({ script: currentScript }))
263
+ maybeSend(agentId, new ScriptStart({ script: currentScript }))
205
264
  const result = yield* pipe(
206
265
  executor.execute({
207
266
  tools,
@@ -209,8 +268,10 @@ ${md}`),
209
268
  }),
210
269
  Stream.mkString,
211
270
  )
212
- Queue.offerUnsafe(output, new ScriptEnd({ output: result }))
213
- prompt = Prompt.concat(prompt, `Javascript output:\n\n${result}`)
271
+ maybeSend(agentId, new ScriptEnd({ output: result }))
272
+ prompt = Prompt.concat(prompt, [
273
+ { role: "assistant", content: `Javascript output:\n\n${result}` },
274
+ ])
214
275
  currentScript = ""
215
276
  }
216
277
 
@@ -222,15 +283,37 @@ ${md}`),
222
283
  return
223
284
  }
224
285
 
286
+ if (pendingMessages.size > 0) {
287
+ prompt = Prompt.concat(
288
+ prompt,
289
+ Array.Array.from(pendingMessages, ({ message, resume }) => {
290
+ resume(Effect.void)
291
+ return {
292
+ role: "user",
293
+ content: message,
294
+ }
295
+ }),
296
+ )
297
+ pendingMessages.clear()
298
+ }
299
+
225
300
  let response = Array.empty<StreamPart<{}>>()
301
+ let reasoningStarted = false
302
+ let hadReasoningDelta = false
226
303
  yield* pipe(
227
304
  ai.streamText({ prompt }),
228
- Stream.takeUntil(
229
- (part) =>
230
- part.type === "text-end" && currentScript.trim().length > 0,
231
- ),
305
+ Stream.takeUntil((part) => {
306
+ if (part.type === "text-end" && currentScript.trim().length > 0) {
307
+ return true
308
+ }
309
+ if (part.type === "reasoning-end" && pendingMessages.size > 0) {
310
+ return true
311
+ }
312
+ return false
313
+ }),
232
314
  Stream.runForEachArray((parts) => {
233
315
  response.push(...parts)
316
+
234
317
  for (const part of parts) {
235
318
  switch (part.type) {
236
319
  case "text-start":
@@ -240,16 +323,23 @@ ${md}`),
240
323
  currentScript += part.delta
241
324
  break
242
325
  case "reasoning-start":
243
- Queue.offerUnsafe(output, new ReasoningStart())
326
+ reasoningStarted = true
244
327
  break
245
328
  case "reasoning-delta":
246
- Queue.offerUnsafe(
247
- output,
248
- new ReasoningDelta({ delta: part.delta }),
249
- )
329
+ hadReasoningDelta = true
330
+ if (reasoningStarted) {
331
+ reasoningStarted = false
332
+ maybeSend(agentId, new ReasoningStart(), true)
333
+ }
334
+ maybeSend(agentId, new ReasoningDelta({ delta: part.delta }))
250
335
  break
251
336
  case "reasoning-end":
252
- Queue.offerUnsafe(output, new ReasoningEnd())
337
+ reasoningStarted = false
338
+ if (hadReasoningDelta) {
339
+ hadReasoningDelta = false
340
+ const sent = maybeSend(agentId, new ReasoningEnd())
341
+ if (sent) flushBuffer()
342
+ }
253
343
  break
254
344
  case "finish":
255
345
  // console.log("Tokens used:", part.usage, "\n")
@@ -278,7 +368,7 @@ ${md}`),
278
368
  return Stream.fromQueue(output)
279
369
  }, Stream.unwrap)
280
370
 
281
- const output = yield* spawn(Prompt.make(options.prompt)).pipe(
371
+ const output = yield* spawn(agentCounter++, Prompt.make(options.prompt)).pipe(
282
372
  Stream.broadcast({
283
373
  capacity: "unbounded",
284
374
  }),
@@ -286,6 +376,12 @@ ${md}`),
286
376
 
287
377
  return identity<Agent>({
288
378
  output,
379
+ steer: (message) =>
380
+ Effect.callback((resume) => {
381
+ const entry = { message, resume }
382
+ pendingMessages.add(entry)
383
+ return Effect.sync(() => pendingMessages.delete(entry))
384
+ }),
289
385
  })
290
386
  // oxlint-disable-next-line typescript/no-explicit-any
291
387
  }) as any
@@ -294,19 +390,17 @@ ${md}`),
294
390
  const generateSystem = Effect.fn(function* (tools: Toolkit.Toolkit<any>) {
295
391
  const renderer = yield* ToolkitRenderer
296
392
 
297
- return `# Who you are
298
-
299
- You are a professional software engineer. You are precise, thoughtful and concise. You make changes with care and always do the due diligence to ensure the best possible outcome. You make no mistakes.
300
-
301
- # Completing the task
393
+ return `You are a professional software engineer. You are precise, thoughtful and concise. You make changes with care and always do the due diligence to ensure the best possible outcome. You make no mistakes.
302
394
 
303
- To complete the task respond with javascript code that will be executed for you.
395
+ To do your job, only respond with javascript code that will be executed for you.
304
396
 
305
397
  - Do not add any markdown formatting, just code.
306
398
  - Use \`console.log\` to print any output you need.
307
399
  - Top level await is supported.
308
400
  - **Prefer using the functions provided** over the bash tool
309
401
 
402
+ **When you have completed your task**, call the "taskComplete" function with the final output.
403
+
310
404
  You have the following functions available to you:
311
405
 
312
406
  \`\`\`ts
@@ -342,9 +436,7 @@ Javascript output:
342
436
 
343
437
  - Use the current state of the codebase to inform your decisions. Don't look at git history unless explicity asked to.
344
438
  - Only add comments when necessary.
345
- - Repect the users AGENTS.md file and ALWAYS follow the instructions in it.
346
- - Use the "subagent" tool to delegate research / exploration tasks
347
- - When you have fully completed the task, call the "taskComplete" function
439
+ - Use the "subagent" tool to delegate large tasks / exploration. Run multiple subagents in parallel with Promise.all
348
440
  `
349
441
  })
350
442
 
@@ -411,7 +503,40 @@ export class AgentFinished extends Schema.TaggedErrorClass<AgentFinished>()(
411
503
  * @since 1.0.0
412
504
  * @category Output
413
505
  */
414
- export const OutputPart = Schema.Union([
506
+ export class SubagentStart extends Schema.TaggedClass<SubagentStart>()(
507
+ "SubagentStart",
508
+ {
509
+ id: Schema.Number,
510
+ prompt: Schema.String,
511
+ model: Schema.String,
512
+ provider: Schema.String,
513
+ },
514
+ ) {
515
+ get modelAndProvider() {
516
+ return `${this.provider}/${this.model}`
517
+ }
518
+ }
519
+
520
+ /**
521
+ * @since 1.0.0
522
+ * @category Output
523
+ */
524
+ export class SubagentComplete extends Schema.TaggedClass<SubagentComplete>()(
525
+ "SubagentComplete",
526
+ {
527
+ id: Schema.Number,
528
+ summary: Schema.String,
529
+ },
530
+ ) {}
531
+
532
+ export type ContentPart =
533
+ | ReasoningStart
534
+ | ReasoningDelta
535
+ | ReasoningEnd
536
+ | ScriptStart
537
+ | ScriptEnd
538
+
539
+ export const ContentPart = Schema.Union([
415
540
  ReasoningStart,
416
541
  ReasoningDelta,
417
542
  ReasoningEnd,
@@ -423,26 +548,35 @@ export const OutputPart = Schema.Union([
423
548
  * @since 1.0.0
424
549
  * @category Output
425
550
  */
426
- export type OutputPart = typeof OutputPart.Type
551
+ export class SubagentPart extends Schema.TaggedClass<SubagentPart>()(
552
+ "SubagentPart",
553
+ {
554
+ id: Schema.Number,
555
+ part: ContentPart,
556
+ },
557
+ ) {}
427
558
 
428
559
  /**
429
560
  * @since 1.0.0
430
561
  * @category Output
431
562
  */
432
- export class SubagentPart extends Data.TaggedError("SubagentPart")<{
433
- id: number
434
- prompt: string
435
- model: string
436
- provider: string
437
- output: Stream.Stream<Output, AgentFinished>
438
- }> {
439
- get modelAndProvider() {
440
- return `${this.provider}/${this.model}`
441
- }
442
- }
563
+ export type Output =
564
+ | ContentPart
565
+ | SubagentStart
566
+ | SubagentComplete
567
+ | SubagentPart
443
568
 
444
569
  /**
445
570
  * @since 1.0.0
446
571
  * @category Output
447
572
  */
448
- export type Output = OutputPart | SubagentPart
573
+ export const Output = Schema.Union([
574
+ ReasoningStart,
575
+ ReasoningDelta,
576
+ ReasoningEnd,
577
+ ScriptStart,
578
+ ScriptEnd,
579
+ SubagentStart,
580
+ SubagentComplete,
581
+ SubagentPart,
582
+ ])
package/src/AgentTools.ts CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  import { Tool, Toolkit } from "effect/unstable/ai"
17
17
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
18
18
  import * as Glob from "glob"
19
- import * as Rg from "@vscode/ripgrep"
20
19
  import { parsePatch, patchChunks } from "./ApplyPatch.ts"
21
20
 
22
21
  /**
@@ -180,9 +179,9 @@ export const AgentTools = Toolkit.make(
180
179
  }),
181
180
  Tool.make("taskComplete", {
182
181
  description:
183
- "Only call this when you have fully completed the user's task. Provide a markdown summary of the work you have done.",
182
+ "Call this to send a final output message and end the current task.",
184
183
  parameters: Schema.String.annotate({
185
- identifier: "summary",
184
+ identifier: "output",
186
185
  }),
187
186
  dependencies: [TaskCompleteDeferred],
188
187
  }),
@@ -304,7 +303,7 @@ export const AgentToolHandlers = AgentTools.toLayer(
304
303
  args.push(options.pattern)
305
304
  let stream = pipe(
306
305
  spawner.streamLines(
307
- ChildProcess.make(Rg.rgPath, args, {
306
+ ChildProcess.make("rg", args, {
308
307
  cwd,
309
308
  stdin: "ignore",
310
309
  }),
package/src/Executor.ts CHANGED
@@ -96,7 +96,6 @@ ${options.script}
96
96
  }
97
97
  }).pipe(
98
98
  Effect.ensuring(Scope.close(handlerScope, Exit.void)),
99
- Effect.timeout("3 minutes"),
100
99
  Effect.catchCause(Effect.logFatal),
101
100
  Effect.provideService(Console.Console, console),
102
101
  Effect.ensuring(Queue.end(output)),