clanka 0.3.1 → 0.4.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.
- package/dist/Acp.d.ts.map +1 -1
- package/dist/Acp.js +3 -0
- package/dist/Acp.js.map +1 -1
- package/dist/Acp.test.js +58 -0
- package/dist/Acp.test.js.map +1 -1
- package/dist/Agent.d.ts.map +1 -1
- package/dist/Agent.js +86 -8
- package/dist/Agent.js.map +1 -1
- package/dist/Agent.test.js +671 -0
- package/dist/Agent.test.js.map +1 -1
- package/dist/AgentExecutor.d.ts +5 -0
- package/dist/AgentExecutor.d.ts.map +1 -1
- package/dist/AgentExecutor.js +22 -0
- package/dist/AgentExecutor.js.map +1 -1
- package/dist/AgentOutput.d.ts +49 -4
- package/dist/AgentOutput.d.ts.map +1 -1
- package/dist/AgentOutput.js +45 -0
- package/dist/AgentOutput.js.map +1 -1
- package/dist/AgentSkills.d.ts +56 -0
- package/dist/AgentSkills.d.ts.map +1 -0
- package/dist/AgentSkills.js +126 -0
- package/dist/AgentSkills.js.map +1 -0
- package/dist/AgentSkills.test.d.ts +2 -0
- package/dist/AgentSkills.test.d.ts.map +1 -0
- package/dist/AgentSkills.test.js +356 -0
- package/dist/AgentSkills.test.js.map +1 -0
- package/dist/Codex.js +1 -1
- package/dist/Codex.js.map +1 -1
- package/dist/Compaction.d.ts +342 -0
- package/dist/Compaction.d.ts.map +1 -0
- package/dist/Compaction.js +566 -0
- package/dist/Compaction.js.map +1 -0
- package/dist/Compaction.test.d.ts +2 -0
- package/dist/Compaction.test.d.ts.map +1 -0
- package/dist/Compaction.test.js +576 -0
- package/dist/Compaction.test.js.map +1 -0
- package/dist/CompactionTransport.test.d.ts +2 -0
- package/dist/CompactionTransport.test.d.ts.map +1 -0
- package/dist/CompactionTransport.test.js +123 -0
- package/dist/CompactionTransport.test.js.map +1 -0
- package/dist/Copilot.d.ts.map +1 -1
- package/dist/Copilot.js +7 -2
- package/dist/Copilot.js.map +1 -1
- package/dist/OutputFormatter.d.ts.map +1 -1
- package/dist/OutputFormatter.js +9 -0
- package/dist/OutputFormatter.js.map +1 -1
- package/dist/cli.js +13 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Acp.test.ts +85 -0
- package/src/Acp.ts +2 -0
- package/src/Agent.test.ts +994 -0
- package/src/Agent.ts +127 -6
- package/src/AgentExecutor.ts +27 -0
- package/src/AgentOutput.ts +58 -0
- package/src/AgentSkills.test.ts +652 -0
- package/src/AgentSkills.ts +155 -0
- package/src/Codex.ts +3 -1
- package/src/Compaction.test.ts +824 -0
- package/src/Compaction.ts +788 -0
- package/src/CompactionTransport.test.ts +228 -0
- package/src/Copilot.ts +8 -1
- package/src/OutputFormatter.ts +9 -0
- package/src/cli.ts +26 -5
- package/src/index.ts +6 -0
package/src/Agent.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as Model from "effect/unstable/ai/Model"
|
|
5
5
|
import type * as Response from "effect/unstable/ai/Response"
|
|
6
6
|
import * as AgentExecutor from "./AgentExecutor.ts"
|
|
7
|
+
import * as AgentSkills from "./AgentSkills.ts"
|
|
7
8
|
import { stripWrappingCodeFence } from "./ScriptExtraction.ts"
|
|
8
9
|
import type * as Path from "effect/Path"
|
|
9
10
|
import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
|
|
@@ -36,6 +37,8 @@ import * as Duration from "effect/Duration"
|
|
|
36
37
|
import * as Cause from "effect/Cause"
|
|
37
38
|
import * as Latch from "effect/Latch"
|
|
38
39
|
import * as Clock from "effect/Clock"
|
|
40
|
+
import * as Exit from "effect/Exit"
|
|
41
|
+
import * as Compaction from "./Compaction.ts"
|
|
39
42
|
import {
|
|
40
43
|
ScriptEnd,
|
|
41
44
|
ScriptOutput,
|
|
@@ -52,6 +55,9 @@ import {
|
|
|
52
55
|
ScriptStart,
|
|
53
56
|
ScriptDelta,
|
|
54
57
|
AgentStart,
|
|
58
|
+
ExecuteOutputCapped,
|
|
59
|
+
CompactionStarted,
|
|
60
|
+
CompactionEnded,
|
|
55
61
|
} from "./AgentOutput.ts"
|
|
56
62
|
|
|
57
63
|
/**
|
|
@@ -187,6 +193,9 @@ ${content}
|
|
|
187
193
|
const conversationMode = yield* ConversationMode
|
|
188
194
|
const turnTimeout = yield* TurnTimeout
|
|
189
195
|
let finalSummary = Option.none<string>()
|
|
196
|
+
// `inputTokens.total` from the most recent finish part; undefined until
|
|
197
|
+
// the first response and after every compaction.
|
|
198
|
+
let lastContextTokens: number | undefined = undefined
|
|
190
199
|
|
|
191
200
|
const output = yield* Queue.make<Output, AgentFinished | AiError.AiError>()
|
|
192
201
|
let inputTokens = 0
|
|
@@ -319,6 +328,50 @@ ${content}
|
|
|
319
328
|
}
|
|
320
329
|
})
|
|
321
330
|
|
|
331
|
+
// Compaction. `compactWithEvents` applies a successful rewrite and closes
|
|
332
|
+
// the progress event on every exit: success, failure, timeout, interrupt.
|
|
333
|
+
let compactionStarted = false
|
|
334
|
+
const onCompactionStart = (reason: Compaction.CompactionReason) =>
|
|
335
|
+
Effect.sync(() => {
|
|
336
|
+
compactionStarted = true
|
|
337
|
+
maybeSend({ agentId, part: new CompactionStarted({ reason }) })
|
|
338
|
+
})
|
|
339
|
+
const compactWithEvents = <E, R>(
|
|
340
|
+
reason: Compaction.CompactionReason,
|
|
341
|
+
compaction: Effect.Effect<
|
|
342
|
+
Option.Option<Compaction.CompactionResult>,
|
|
343
|
+
E,
|
|
344
|
+
R
|
|
345
|
+
>,
|
|
346
|
+
) =>
|
|
347
|
+
Effect.suspend(() => {
|
|
348
|
+
compactionStarted = false
|
|
349
|
+
return compaction
|
|
350
|
+
}).pipe(
|
|
351
|
+
Effect.onExit((exit) =>
|
|
352
|
+
Effect.sync(() => {
|
|
353
|
+
if (!compactionStarted) return
|
|
354
|
+
const result = Exit.isSuccess(exit) ? exit.value : Option.none()
|
|
355
|
+
let tokensBefore: number
|
|
356
|
+
let tokensAfter: number
|
|
357
|
+
if (Option.isSome(result)) {
|
|
358
|
+
MutableRef.set(prompt, result.value.prompt)
|
|
359
|
+
lastContextTokens = undefined
|
|
360
|
+
tokensBefore = result.value.tokensBefore
|
|
361
|
+
tokensAfter = result.value.tokensAfter
|
|
362
|
+
} else {
|
|
363
|
+
tokensBefore = tokensAfter = Compaction.estimateTokens(
|
|
364
|
+
prompt.current,
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
maybeSend({
|
|
368
|
+
agentId,
|
|
369
|
+
part: new CompactionEnded({ reason, tokensBefore, tokensAfter }),
|
|
370
|
+
})
|
|
371
|
+
}),
|
|
372
|
+
),
|
|
373
|
+
)
|
|
374
|
+
|
|
322
375
|
// Script execution
|
|
323
376
|
const executeScript = Effect.fnUntraced(function* (script: string) {
|
|
324
377
|
yield* toolLatchAcquire
|
|
@@ -335,8 +388,20 @@ ${content}
|
|
|
335
388
|
}),
|
|
336
389
|
Stream.mkString,
|
|
337
390
|
)
|
|
391
|
+
// Always-on cap on what enters the Prompt. The ScriptOutput event keeps
|
|
392
|
+
// the full output for display; the formatter applies its own truncation.
|
|
393
|
+
const capped = Compaction.capOutput(output)
|
|
394
|
+
if (capped.capped) {
|
|
395
|
+
maybeSend({
|
|
396
|
+
agentId,
|
|
397
|
+
part: new ExecuteOutputCapped({
|
|
398
|
+
charsBefore: capped.charsBefore,
|
|
399
|
+
charsAfter: capped.charsAfter,
|
|
400
|
+
}),
|
|
401
|
+
})
|
|
402
|
+
}
|
|
338
403
|
maybeSend({ agentId, part: new ScriptOutput({ output }) })
|
|
339
|
-
return output
|
|
404
|
+
return capped.output
|
|
340
405
|
}, Effect.ensuring(toolLatchRelease))
|
|
341
406
|
|
|
342
407
|
if (!modelConfig.systemPromptTransform) {
|
|
@@ -369,12 +434,29 @@ ${content}
|
|
|
369
434
|
pendingMessages.clear()
|
|
370
435
|
}
|
|
371
436
|
|
|
437
|
+
// Threshold compaction before the next model call
|
|
438
|
+
yield* compactWithEvents(
|
|
439
|
+
"threshold",
|
|
440
|
+
Compaction.compactIfNeeded({
|
|
441
|
+
prompt: prompt.current,
|
|
442
|
+
contextTokens: lastContextTokens,
|
|
443
|
+
onStart: onCompactionStart,
|
|
444
|
+
}).pipe(Effect.timeout(turnTimeout)),
|
|
445
|
+
).pipe(
|
|
446
|
+
Effect.catchTag("TimeoutError", (error) =>
|
|
447
|
+
Effect.logWarning(
|
|
448
|
+
"Compaction timed out, continuing with the uncompacted prompt",
|
|
449
|
+
error,
|
|
450
|
+
),
|
|
451
|
+
),
|
|
452
|
+
)
|
|
453
|
+
|
|
372
454
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
373
455
|
let response = Array.empty<Response.StreamPart<any>>()
|
|
374
456
|
let reasoningStarted = false
|
|
375
457
|
let hadReasoningDelta = false
|
|
376
458
|
let hadToolCall = false
|
|
377
|
-
|
|
459
|
+
const runModel = pipe(
|
|
378
460
|
Stream.suspend(() =>
|
|
379
461
|
ai.streamText({ prompt: prompt.current, toolkit: singleTool }),
|
|
380
462
|
),
|
|
@@ -431,6 +513,7 @@ ${content}
|
|
|
431
513
|
outputTokens += usage.outputTokens.total
|
|
432
514
|
}
|
|
433
515
|
if (usage.inputTokens.total !== undefined) {
|
|
516
|
+
lastContextTokens = usage.inputTokens.total
|
|
434
517
|
inputTokens += usage.inputTokens.total
|
|
435
518
|
maybeSend({
|
|
436
519
|
agentId,
|
|
@@ -449,6 +532,12 @@ ${content}
|
|
|
449
532
|
}
|
|
450
533
|
return Effect.void
|
|
451
534
|
}),
|
|
535
|
+
modelConfig.systemPromptTransform
|
|
536
|
+
? (effect) => modelConfig.systemPromptTransform!(system, effect)
|
|
537
|
+
: identity,
|
|
538
|
+
)
|
|
539
|
+
const attempt = pipe(
|
|
540
|
+
runModel,
|
|
452
541
|
Effect.raceFirst(turnTimeoutEffect),
|
|
453
542
|
Effect.retry({
|
|
454
543
|
while: (err) => {
|
|
@@ -468,6 +557,9 @@ ${content}
|
|
|
468
557
|
response = []
|
|
469
558
|
return true
|
|
470
559
|
}
|
|
560
|
+
// Never retry a context-length error as is: the overflow
|
|
561
|
+
// handler below compacts first.
|
|
562
|
+
if (Compaction.isContextLengthError(err)) return false
|
|
471
563
|
if (err.isRetryable) {
|
|
472
564
|
maybeSend({ agentId, part: new ErrorRetry({ error: err }) })
|
|
473
565
|
switch (err.reason._tag) {
|
|
@@ -488,9 +580,33 @@ ${content}
|
|
|
488
580
|
schedule: retryPolicy,
|
|
489
581
|
}),
|
|
490
582
|
Effect.catchTag("TimeoutError", Effect.die),
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
583
|
+
)
|
|
584
|
+
// Overflow: compact once, retry the attempt once, then fail.
|
|
585
|
+
yield* attempt.pipe(
|
|
586
|
+
Effect.catchIf(
|
|
587
|
+
(err): err is AiError.AiError =>
|
|
588
|
+
Compaction.isContextLengthError(err),
|
|
589
|
+
(err) =>
|
|
590
|
+
Effect.gen(function* () {
|
|
591
|
+
response = []
|
|
592
|
+
const compacted = yield* compactWithEvents(
|
|
593
|
+
"overflow",
|
|
594
|
+
Compaction.compact({
|
|
595
|
+
prompt: prompt.current,
|
|
596
|
+
reason: "overflow",
|
|
597
|
+
onStart: onCompactionStart,
|
|
598
|
+
}).pipe(
|
|
599
|
+
Effect.timeout(turnTimeout),
|
|
600
|
+
Effect.retry({
|
|
601
|
+
while: (error) => error._tag === "TimeoutError",
|
|
602
|
+
times: 1,
|
|
603
|
+
}),
|
|
604
|
+
Effect.catchTag("TimeoutError", Effect.die),
|
|
605
|
+
),
|
|
606
|
+
)
|
|
607
|
+
return Option.isSome(compacted) ? yield* attempt : yield* err
|
|
608
|
+
}),
|
|
609
|
+
),
|
|
494
610
|
)
|
|
495
611
|
MutableRef.update(
|
|
496
612
|
prompt,
|
|
@@ -620,7 +736,12 @@ ${capabilities.toolsDts}
|
|
|
620
736
|
|
|
621
737
|
/** The global Fetch API available for making HTTP requests. */
|
|
622
738
|
declare const fetch: typeof globalThis.fetch
|
|
623
|
-
|
|
739
|
+
\`\`\`${Option.match(AgentSkills.renderCatalog(capabilities.skills), {
|
|
740
|
+
onNone: () => "",
|
|
741
|
+
onSome: (catalog) => `
|
|
742
|
+
|
|
743
|
+
${catalog}`,
|
|
744
|
+
})}`
|
|
624
745
|
|
|
625
746
|
class ScriptExecutor extends Context.Service<
|
|
626
747
|
ScriptExecutor,
|
package/src/AgentExecutor.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
4
|
import * as NodeConsole from "node:console"
|
|
5
|
+
import * as NodeOs from "node:os"
|
|
5
6
|
import * as NodeVm from "node:vm"
|
|
6
7
|
import { Writable } from "node:stream"
|
|
7
8
|
import {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
TaskCompleter,
|
|
14
15
|
} from "./AgentTools.ts"
|
|
15
16
|
import { ToolkitRenderer } from "./ToolkitRenderer.ts"
|
|
17
|
+
import * as AgentSkills from "./AgentSkills.ts"
|
|
16
18
|
import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"
|
|
17
19
|
import type * as HttpClient from "effect/unstable/http/HttpClient"
|
|
18
20
|
import * as Context from "effect/Context"
|
|
@@ -69,6 +71,13 @@ export class Capabilities extends Schema.Class<Capabilities>("Capabilities")({
|
|
|
69
71
|
toolsDts: Schema.String,
|
|
70
72
|
agentsMd: Schema.Option(Schema.String),
|
|
71
73
|
supportsSearch: Schema.Boolean,
|
|
74
|
+
/**
|
|
75
|
+
* Skills available on the executor filesystem.
|
|
76
|
+
*/
|
|
77
|
+
skills: Schema.Array(AgentSkills.Skill).pipe(
|
|
78
|
+
Schema.withDecodingDefaultKey(Effect.succeed([])),
|
|
79
|
+
Schema.withConstructorDefault(Effect.succeed([])),
|
|
80
|
+
),
|
|
72
81
|
}) {}
|
|
73
82
|
|
|
74
83
|
/**
|
|
@@ -201,10 +210,18 @@ export const makeLocal = Effect.fnUntraced(function* <
|
|
|
201
210
|
const agentsMd = yield* Effect.option(
|
|
202
211
|
fs.readFileString(pathService.join(options.directory, "AGENTS.md")),
|
|
203
212
|
)
|
|
213
|
+
const skills = yield* AgentSkills.discover({
|
|
214
|
+
directory: options.directory,
|
|
215
|
+
homeDirectory: homeDirectory(),
|
|
216
|
+
}).pipe(
|
|
217
|
+
Effect.provideService(FileSystem.FileSystem, fs),
|
|
218
|
+
Effect.provideService(Path.Path, pathService),
|
|
219
|
+
)
|
|
204
220
|
return new Capabilities({
|
|
205
221
|
toolsDts,
|
|
206
222
|
agentsMd,
|
|
207
223
|
supportsSearch: hasSearch,
|
|
224
|
+
skills,
|
|
208
225
|
})
|
|
209
226
|
}),
|
|
210
227
|
execute,
|
|
@@ -500,6 +517,16 @@ const compileScript = (script: string): NodeVm.Script => {
|
|
|
500
517
|
|
|
501
518
|
const defaultMain = () => Promise.resolve()
|
|
502
519
|
|
|
520
|
+
const homeDirectory = (): Option.Option<string> => {
|
|
521
|
+
const fromEnv = process.env.HOME
|
|
522
|
+
if (fromEnv) return Option.some(fromEnv)
|
|
523
|
+
try {
|
|
524
|
+
return Option.some(NodeOs.homedir())
|
|
525
|
+
} catch {
|
|
526
|
+
return Option.none()
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
503
530
|
const makeConsole = Effect.fn(function* (
|
|
504
531
|
queue: Queue.Queue<string, Cause.Done>,
|
|
505
532
|
) {
|
package/src/AgentOutput.ts
CHANGED
|
@@ -138,6 +138,58 @@ export class SubagentComplete extends Schema.TaggedClass<SubagentComplete>()(
|
|
|
138
138
|
},
|
|
139
139
|
) {}
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Emitted when an `execute` result exceeded the always-on output cap and was
|
|
143
|
+
* shortened before being appended to the Prompt.
|
|
144
|
+
*
|
|
145
|
+
* @since 1.0.0
|
|
146
|
+
* @category Output
|
|
147
|
+
*/
|
|
148
|
+
export class ExecuteOutputCapped extends Schema.TaggedClass<ExecuteOutputCapped>()(
|
|
149
|
+
"ExecuteOutputCapped",
|
|
150
|
+
{
|
|
151
|
+
charsBefore: Schema.Number,
|
|
152
|
+
charsAfter: Schema.Number,
|
|
153
|
+
},
|
|
154
|
+
) {}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @since 1.0.0
|
|
158
|
+
* @category Output
|
|
159
|
+
*/
|
|
160
|
+
export const CompactionReason = Schema.Literals(["threshold", "overflow"])
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Emitted when auto-compaction begins. `reason` is `threshold` when the
|
|
164
|
+
* context token count crossed the configured limit before a model call, and
|
|
165
|
+
* `overflow` when a context-length error from the provider triggered it.
|
|
166
|
+
*
|
|
167
|
+
* @since 1.0.0
|
|
168
|
+
* @category Output
|
|
169
|
+
*/
|
|
170
|
+
export class CompactionStarted extends Schema.TaggedClass<CompactionStarted>()(
|
|
171
|
+
"CompactionStarted",
|
|
172
|
+
{
|
|
173
|
+
reason: CompactionReason,
|
|
174
|
+
},
|
|
175
|
+
) {}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Emitted when auto-compaction finished and the Prompt was rewritten.
|
|
179
|
+
* `tokensBefore` / `tokensAfter` are estimates.
|
|
180
|
+
*
|
|
181
|
+
* @since 1.0.0
|
|
182
|
+
* @category Output
|
|
183
|
+
*/
|
|
184
|
+
export class CompactionEnded extends Schema.TaggedClass<CompactionEnded>()(
|
|
185
|
+
"CompactionEnded",
|
|
186
|
+
{
|
|
187
|
+
reason: CompactionReason,
|
|
188
|
+
tokensBefore: Schema.Number,
|
|
189
|
+
tokensAfter: Schema.Number,
|
|
190
|
+
},
|
|
191
|
+
) {}
|
|
192
|
+
|
|
141
193
|
export type ContentPart =
|
|
142
194
|
| ReasoningStart
|
|
143
195
|
| ReasoningDelta
|
|
@@ -148,6 +200,9 @@ export type ContentPart =
|
|
|
148
200
|
| ScriptOutput
|
|
149
201
|
| Usage
|
|
150
202
|
| ErrorRetry
|
|
203
|
+
| ExecuteOutputCapped
|
|
204
|
+
| CompactionStarted
|
|
205
|
+
| CompactionEnded
|
|
151
206
|
|
|
152
207
|
export const ContentPart = Schema.Union([
|
|
153
208
|
ReasoningStart,
|
|
@@ -159,6 +214,9 @@ export const ContentPart = Schema.Union([
|
|
|
159
214
|
ScriptOutput,
|
|
160
215
|
Usage,
|
|
161
216
|
ErrorRetry,
|
|
217
|
+
ExecuteOutputCapped,
|
|
218
|
+
CompactionStarted,
|
|
219
|
+
CompactionEnded,
|
|
162
220
|
])
|
|
163
221
|
|
|
164
222
|
/**
|