clanka 0.2.10 → 0.2.12
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/AgentExecutor.d.ts +1 -4
- package/dist/AgentExecutor.d.ts.map +1 -1
- package/dist/AgentExecutor.js +25 -3
- package/dist/AgentExecutor.js.map +1 -1
- package/dist/AgentTools.d.ts +3 -12
- package/dist/AgentTools.d.ts.map +1 -1
- package/dist/AgentTools.js +7 -12
- package/dist/AgentTools.js.map +1 -1
- package/dist/CodeChunker.test.js +2 -1
- package/dist/CodeChunker.test.js.map +1 -1
- package/dist/OutputFormatter.d.ts +3 -1
- package/dist/OutputFormatter.d.ts.map +1 -1
- package/dist/OutputFormatter.js +60 -47
- package/dist/OutputFormatter.js.map +1 -1
- package/dist/ScriptPreprocessing.d.ts +2 -0
- package/dist/ScriptPreprocessing.d.ts.map +1 -0
- package/dist/ScriptPreprocessing.js +314 -0
- package/dist/ScriptPreprocessing.js.map +1 -0
- package/dist/ScriptPreprocessing.test.d.ts +2 -0
- package/dist/ScriptPreprocessing.test.d.ts.map +1 -0
- package/dist/ScriptPreprocessing.test.js +108 -0
- package/dist/ScriptPreprocessing.test.js.map +1 -0
- package/package.json +1 -1
- package/src/AgentExecutor.ts +27 -3
- package/src/AgentTools.ts +8 -12
- package/src/CodeChunker.test.ts +2 -1
- package/src/OutputFormatter.ts +75 -57
- package/src/ScriptPreprocessing.test.ts +171 -0
- package/src/ScriptPreprocessing.ts +420 -0
- package/src/fixtures/patch-broken.txt +187 -0
- package/src/fixtures/patch-fixed.txt +187 -0
- package/src/fixtures/patch2-broken.txt +257 -0
- package/src/fixtures/patch2-fixed.txt +257 -0
package/src/OutputFormatter.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { AgentFinished, Output } from "./Agent.ts"
|
|
|
11
11
|
import chalk from "chalk"
|
|
12
12
|
import type * as Prompt from "effect/unstable/ai/Prompt"
|
|
13
13
|
import * as Cause from "effect/Cause"
|
|
14
|
+
import { identity } from "effect/Function"
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* @since 1.0.0
|
|
@@ -24,70 +25,87 @@ export type OutputFormatter = <E, R>(
|
|
|
24
25
|
* @since 1.0.0
|
|
25
26
|
* @category Pretty
|
|
26
27
|
*/
|
|
27
|
-
export const pretty
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
export const pretty = (options?: {
|
|
29
|
+
readonly outputTruncation?: number | boolean | undefined
|
|
30
|
+
}): OutputFormatter => {
|
|
31
|
+
const maxLines =
|
|
32
|
+
typeof options?.outputTruncation === "number"
|
|
33
|
+
? options.outputTruncation
|
|
34
|
+
: 20
|
|
35
|
+
const truncate =
|
|
36
|
+
options?.outputTruncation === true ||
|
|
37
|
+
typeof options?.outputTruncation === "number"
|
|
38
|
+
? (text: string): string => {
|
|
39
|
+
const lines = text.split("\n")
|
|
40
|
+
if (lines.length > maxLines) {
|
|
41
|
+
return (
|
|
42
|
+
lines.slice(0, maxLines).join("\n") +
|
|
43
|
+
`\n... (truncated, total ${lines.length} lines)`
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return text
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
: identity
|
|
49
|
+
return (stream) =>
|
|
50
|
+
stream.pipe(
|
|
51
|
+
Stream.map((output) => {
|
|
52
|
+
let prefix = ""
|
|
53
|
+
if (output._tag === "SubagentPart") {
|
|
54
|
+
prefix = chalk.magenta(`Subagent #${output.id}:`) + " "
|
|
55
|
+
output = output.part
|
|
56
|
+
}
|
|
57
|
+
switch (output._tag) {
|
|
58
|
+
case "AgentStart": {
|
|
59
|
+
return `${chalkAgentHeading(`${subagentIcon} Agent #${output.id} starting (${output.modelAndProvider})`)}\n\n${promptToString(output.prompt)}\n\n`
|
|
60
|
+
}
|
|
61
|
+
case "SubagentStart": {
|
|
62
|
+
return `${chalkSubagentHeading(`${subagentIcon} Subagent #${output.id} starting (${output.modelAndProvider})`)}
|
|
41
63
|
|
|
42
64
|
${chalk.dim(output.prompt)}\n\n`
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
}
|
|
66
|
+
case "SubagentComplete": {
|
|
67
|
+
return `${chalkSubagentHeading(`${subagentIcon} Subagent #${output.id} complete`)}
|
|
46
68
|
|
|
47
69
|
${output.summary}\n\n`
|
|
70
|
+
}
|
|
71
|
+
case "ReasoningStart": {
|
|
72
|
+
return (
|
|
73
|
+
prefix + chalkReasoningHeading(`${thinkingIcon} Thinking:`) + " "
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
case "ReasoningDelta": {
|
|
77
|
+
return output.delta
|
|
78
|
+
}
|
|
79
|
+
case "ReasoningEnd": {
|
|
80
|
+
return "\n\n"
|
|
81
|
+
}
|
|
82
|
+
case "ScriptStart": {
|
|
83
|
+
return `${prefix}${chalkScriptHeading(`${scriptIcon} Executing script`)}\n\n`
|
|
84
|
+
}
|
|
85
|
+
case "ScriptDelta": {
|
|
86
|
+
return chalk.dim(output.delta)
|
|
87
|
+
}
|
|
88
|
+
case "ScriptEnd": {
|
|
89
|
+
return "\n\n"
|
|
90
|
+
}
|
|
91
|
+
case "ScriptOutput": {
|
|
92
|
+
return `${prefix}${chalkScriptHeading(`${scriptIcon} Script output`)}\n\n${chalk.dim(truncate(output.output))}\n\n`
|
|
93
|
+
}
|
|
94
|
+
case "ErrorRetry": {
|
|
95
|
+
return `${prefix}${chalk.red(`Error: ${output.error.reason._tag}. Retrying...`)}\n\n${chalk.dim(Cause.pretty(Cause.fail(output.error)))}\n\n`
|
|
96
|
+
}
|
|
97
|
+
case "Usage": {
|
|
98
|
+
return `${prefix}${chalkInfoHeading(`${infoIcon} Usage:`)} ${numberFormat.format(output.contextTokens)} context / ${numberFormat.format(output.inputTokens)} input / ${numberFormat.format(output.outputTokens)} output\n\n`
|
|
99
|
+
}
|
|
48
100
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
case "ReasoningDelta": {
|
|
55
|
-
return output.delta
|
|
56
|
-
}
|
|
57
|
-
case "ReasoningEnd": {
|
|
58
|
-
return "\n\n"
|
|
59
|
-
}
|
|
60
|
-
case "ScriptStart": {
|
|
61
|
-
return `${prefix}${chalkScriptHeading(`${scriptIcon} Executing script`)}\n\n`
|
|
62
|
-
}
|
|
63
|
-
case "ScriptDelta": {
|
|
64
|
-
return chalk.dim(output.delta)
|
|
65
|
-
}
|
|
66
|
-
case "ScriptEnd": {
|
|
67
|
-
return "\n\n"
|
|
68
|
-
}
|
|
69
|
-
case "ScriptOutput": {
|
|
70
|
-
const lines = output.output.split("\n")
|
|
71
|
-
const truncated =
|
|
72
|
-
lines.length > 20
|
|
73
|
-
? lines.slice(0, 20).join("\n") + "\n... (truncated)"
|
|
74
|
-
: output.output
|
|
75
|
-
return `${prefix}${chalkScriptHeading(`${scriptIcon} Script output`)}\n\n${chalk.dim(truncated)}\n\n`
|
|
76
|
-
}
|
|
77
|
-
case "ErrorRetry": {
|
|
78
|
-
return `${prefix}${chalk.red(`Error: ${output.error.reason._tag}. Retrying...`)}\n\n${chalk.dim(Cause.pretty(Cause.fail(output.error)))}\n\n`
|
|
79
|
-
}
|
|
80
|
-
case "Usage": {
|
|
81
|
-
return `${prefix}${chalkInfoHeading(`${infoIcon} Usage:`)} ${numberFormat.format(output.contextTokens)} context / ${numberFormat.format(output.inputTokens)} input / ${numberFormat.format(output.outputTokens)} output\n\n`
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}),
|
|
85
|
-
Stream.catchTag("AgentFinished", (finished) =>
|
|
86
|
-
Stream.succeed(
|
|
87
|
-
`\n${chalk.bold.green(`${doneIcon} Task complete:`)}\n\n${(finished as AgentFinished).summary}`,
|
|
101
|
+
}),
|
|
102
|
+
Stream.catchTag("AgentFinished", (finished) =>
|
|
103
|
+
Stream.succeed(
|
|
104
|
+
`\n${chalk.bold.green(`${doneIcon} Task complete:`)}\n\n${(finished as AgentFinished).summary}`,
|
|
105
|
+
),
|
|
88
106
|
),
|
|
89
|
-
)
|
|
90
|
-
|
|
107
|
+
)
|
|
108
|
+
}
|
|
91
109
|
|
|
92
110
|
const promptToString = (prompt: Prompt.Prompt): string => {
|
|
93
111
|
let textParts: Array<string> = []
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { assert, describe, it } from "@effect/vitest"
|
|
2
|
+
import { preprocessScript } from "./ScriptPreprocessing.ts"
|
|
3
|
+
import { readFileSync } from "node:fs"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
const tick = "`"
|
|
7
|
+
const escaped = "\\`"
|
|
8
|
+
const escapedInterpolation = "\\${"
|
|
9
|
+
const wrapTemplate = (value: string): string => `${tick}${value}${tick}`
|
|
10
|
+
|
|
11
|
+
describe("preprocessScript", () => {
|
|
12
|
+
it("escapes internal backticks in applyPatch templates", () => {
|
|
13
|
+
const input = [
|
|
14
|
+
"await applyPatch(`",
|
|
15
|
+
"*** Begin Patch",
|
|
16
|
+
"*** Update File: src/example.ts",
|
|
17
|
+
"@@",
|
|
18
|
+
"-const oldValue = `old`",
|
|
19
|
+
"+const newValue = `new`",
|
|
20
|
+
"*** End Patch",
|
|
21
|
+
"`)",
|
|
22
|
+
].join("\n")
|
|
23
|
+
|
|
24
|
+
const output = preprocessScript(input)
|
|
25
|
+
|
|
26
|
+
assert.strictEqual(
|
|
27
|
+
output.includes(`-const oldValue = ${escaped}old${escaped}`),
|
|
28
|
+
true,
|
|
29
|
+
)
|
|
30
|
+
assert.strictEqual(
|
|
31
|
+
output.includes(`+const newValue = ${escaped}new${escaped}`),
|
|
32
|
+
true,
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("escapes internal interpolations in applyPatch templates", () => {
|
|
37
|
+
const input = [
|
|
38
|
+
"await applyPatch(`",
|
|
39
|
+
"*** Begin Patch",
|
|
40
|
+
"*** Update File: src/example.ts",
|
|
41
|
+
"@@",
|
|
42
|
+
"+const value = ${nextValue}",
|
|
43
|
+
"*** End Patch",
|
|
44
|
+
"`)",
|
|
45
|
+
].join("\n")
|
|
46
|
+
|
|
47
|
+
const output = preprocessScript(input)
|
|
48
|
+
|
|
49
|
+
assert.strictEqual(
|
|
50
|
+
output.includes(`+const value = ${escapedInterpolation}nextValue}`),
|
|
51
|
+
true,
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("escapes internal backticks in writeFile content templates", () => {
|
|
56
|
+
const input = [
|
|
57
|
+
"await writeFile({",
|
|
58
|
+
' path: "src/example.ts",',
|
|
59
|
+
" content: `const value = `next``,",
|
|
60
|
+
"})",
|
|
61
|
+
].join("\n")
|
|
62
|
+
|
|
63
|
+
const output = preprocessScript(input)
|
|
64
|
+
|
|
65
|
+
assert.strictEqual(
|
|
66
|
+
output.includes(
|
|
67
|
+
`content: ${wrapTemplate(`const value = ${escaped}next${escaped}`)},`,
|
|
68
|
+
),
|
|
69
|
+
true,
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("escapes internal backticks in taskComplete templates", () => {
|
|
74
|
+
const input = "await taskComplete(`Implemented `TypeBuilder` updates.`)"
|
|
75
|
+
|
|
76
|
+
const output = preprocessScript(input)
|
|
77
|
+
|
|
78
|
+
assert.strictEqual(
|
|
79
|
+
output,
|
|
80
|
+
`await taskComplete(${wrapTemplate(`Implemented ${escaped}TypeBuilder${escaped} updates.`)})`,
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("does not change scripts when target templates are already escaped", () => {
|
|
85
|
+
const input = [
|
|
86
|
+
`await applyPatch(${wrapTemplate(`const value = ${escaped}safe${escaped}`)})`,
|
|
87
|
+
`await applyPatch(${wrapTemplate(`const value = ${escapedInterpolation}safe}`)})`,
|
|
88
|
+
`await writeFile({ path: "src/example.ts", content: ${wrapTemplate(`already ${escaped}safe${escaped}`)} })`,
|
|
89
|
+
`await taskComplete(${wrapTemplate(`All done with ${escaped}safe${escaped} backticks.`)})`,
|
|
90
|
+
].join("\n")
|
|
91
|
+
|
|
92
|
+
assert.strictEqual(preprocessScript(input), input)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("does not modify non-target function calls", () => {
|
|
96
|
+
const input = "await otherTool(`Keep `this` untouched.`)"
|
|
97
|
+
|
|
98
|
+
assert.strictEqual(preprocessScript(input), input)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it("escapes internal backticks in applyPatch templates assigned to variables", () => {
|
|
102
|
+
const input = [
|
|
103
|
+
"const patch = `*** Begin Patch",
|
|
104
|
+
"*** Update File: src/example.ts",
|
|
105
|
+
"@@",
|
|
106
|
+
"-const oldValue = `old`",
|
|
107
|
+
"+const newValue = `new`",
|
|
108
|
+
"*** End Patch`;",
|
|
109
|
+
"await applyPatch(patch)",
|
|
110
|
+
].join("\n")
|
|
111
|
+
|
|
112
|
+
const output = preprocessScript(input)
|
|
113
|
+
|
|
114
|
+
assert.strictEqual(
|
|
115
|
+
output.includes(`-const oldValue = ${escaped}old${escaped}`),
|
|
116
|
+
true,
|
|
117
|
+
)
|
|
118
|
+
assert.strictEqual(
|
|
119
|
+
output.includes(`+const newValue = ${escaped}new${escaped}`),
|
|
120
|
+
true,
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("escapes internal backticks in taskComplete templates assigned to variables", () => {
|
|
125
|
+
const input = [
|
|
126
|
+
"const summary = `Implemented `TypeBuilder` updates.`;",
|
|
127
|
+
"await taskComplete(summary)",
|
|
128
|
+
].join("\n")
|
|
129
|
+
|
|
130
|
+
const output = preprocessScript(input)
|
|
131
|
+
|
|
132
|
+
assert.strictEqual(
|
|
133
|
+
output,
|
|
134
|
+
[
|
|
135
|
+
`const summary = ${wrapTemplate(`Implemented ${escaped}TypeBuilder${escaped} updates.`)};`,
|
|
136
|
+
"await taskComplete(summary)",
|
|
137
|
+
].join("\n"),
|
|
138
|
+
)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it("escapes internal backticks in writeFile content assigned to variables", () => {
|
|
142
|
+
const input = [
|
|
143
|
+
"const body = `const value = `next``;",
|
|
144
|
+
"await writeFile({",
|
|
145
|
+
' path: "src/example.ts",',
|
|
146
|
+
" content: body,",
|
|
147
|
+
"})",
|
|
148
|
+
].join("\n")
|
|
149
|
+
|
|
150
|
+
const output = preprocessScript(input)
|
|
151
|
+
|
|
152
|
+
assert.strictEqual(
|
|
153
|
+
output.includes(
|
|
154
|
+
`const body = ${wrapTemplate(`const value = ${escaped}next${escaped}`)};`,
|
|
155
|
+
),
|
|
156
|
+
true,
|
|
157
|
+
)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it.each(["patch", "patch2"])("fixes broken %s", (fixture) => {
|
|
161
|
+
const content = readFileSync(
|
|
162
|
+
join(__dirname, "fixtures", `${fixture}-broken.txt`),
|
|
163
|
+
"utf-8",
|
|
164
|
+
)
|
|
165
|
+
const fixed = readFileSync(
|
|
166
|
+
join(__dirname, "fixtures", `${fixture}-fixed.txt`),
|
|
167
|
+
"utf-8",
|
|
168
|
+
)
|
|
169
|
+
assert.equal(preprocessScript(content), fixed)
|
|
170
|
+
})
|
|
171
|
+
})
|