lalph 0.3.97 → 0.3.99
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/cli.mjs +453 -363
- package/package.json +4 -4
- package/src/Agents/planner.ts +10 -0
- package/src/Clanka.ts +50 -0
- package/src/CurrentIssueSource.ts +1 -20
- package/src/commands/root.ts +31 -33
- package/src/domain/CliAgent.ts +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lalph",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.99",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@linear/sdk": "^78.0.0",
|
|
45
45
|
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
|
46
46
|
"@octokit/types": "^16.0.0",
|
|
47
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
48
|
-
"clanka": "^0.2.
|
|
47
|
+
"@typescript/native-preview": "7.0.0-dev.20260322.1",
|
|
48
|
+
"clanka": "^0.2.29",
|
|
49
49
|
"concurrently": "^9.2.1",
|
|
50
50
|
"effect": "4.0.0-beta.36",
|
|
51
51
|
"husky": "^9.1.7",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"prettier": "^3.8.1",
|
|
56
56
|
"tsdown": "^0.21.4",
|
|
57
57
|
"typescript": "^5.9.3",
|
|
58
|
-
"yaml": "^2.8.
|
|
58
|
+
"yaml": "^2.8.3"
|
|
59
59
|
},
|
|
60
60
|
"lint-staged": {
|
|
61
61
|
"*.{ts,tsx}": [
|
package/src/Agents/planner.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { PromptGen } from "../PromptGen.ts"
|
|
|
3
3
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
|
4
4
|
import { Worktree } from "../Worktree.ts"
|
|
5
5
|
import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
|
|
6
|
+
import { runClankaPlan } from "../Clanka.ts"
|
|
6
7
|
|
|
7
8
|
export const agentPlanner = Effect.fnUntraced(function* (options: {
|
|
8
9
|
readonly plan: string
|
|
@@ -16,6 +17,15 @@ export const agentPlanner = Effect.fnUntraced(function* (options: {
|
|
|
16
17
|
const promptGen = yield* PromptGen
|
|
17
18
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
18
19
|
|
|
20
|
+
if (options.preset.cliAgent.id === "clanka") {
|
|
21
|
+
yield* runClankaPlan({
|
|
22
|
+
directory: worktree.directory,
|
|
23
|
+
model: options.preset.extraArgs.join(" "),
|
|
24
|
+
prompt: promptGen.planPrompt(options),
|
|
25
|
+
})
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
yield* pipe(
|
|
20
30
|
options.preset.cliAgent.commandPlan({
|
|
21
31
|
prompt: promptGen.planPrompt(options),
|
package/src/Clanka.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
Stdio,
|
|
13
13
|
Stream,
|
|
14
14
|
} from "effect"
|
|
15
|
+
import { Prompt as CliPrompt } from "effect/unstable/cli"
|
|
15
16
|
import { TaskChooseTools, TaskTools, TaskToolsHandlers } from "./TaskTools.ts"
|
|
16
17
|
import { layerClankaModel, ModelServices } from "./ClankaModels.ts"
|
|
17
18
|
import { withStallTimeout } from "./shared/stream.ts"
|
|
@@ -136,3 +137,52 @@ export const runClanka = Effect.fnUntraced(
|
|
|
136
137
|
),
|
|
137
138
|
Effect.provide([ModelServices, TaskToolsHandlers]),
|
|
138
139
|
)
|
|
140
|
+
|
|
141
|
+
export const runClankaPlan = Effect.fnUntraced(
|
|
142
|
+
function* (options: {
|
|
143
|
+
readonly directory: string
|
|
144
|
+
readonly model: string
|
|
145
|
+
readonly prompt: Prompt.RawInput
|
|
146
|
+
}) {
|
|
147
|
+
const stdio = yield* Stdio.Stdio
|
|
148
|
+
const agent = yield* Agent.Agent
|
|
149
|
+
let nextPrompt = options.prompt
|
|
150
|
+
|
|
151
|
+
while (true) {
|
|
152
|
+
const output = yield* agent.send({
|
|
153
|
+
prompt: nextPrompt,
|
|
154
|
+
system: `ONLY call taskComplete by itself. NEVER call taskComplete alongside other functions, to ensure you first read output before deciding a task is done.`,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
yield* output.pipe(
|
|
158
|
+
OutputFormatter.pretty({
|
|
159
|
+
outputTruncation: 20,
|
|
160
|
+
}),
|
|
161
|
+
Stream.run(stdio.stdout()),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
console.log("")
|
|
165
|
+
nextPrompt = yield* CliPrompt.text({
|
|
166
|
+
message: ">",
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
Effect.scoped,
|
|
171
|
+
(effect, options) =>
|
|
172
|
+
Effect.provide(
|
|
173
|
+
effect,
|
|
174
|
+
Agent.layerLocal({
|
|
175
|
+
directory: options.directory,
|
|
176
|
+
}).pipe(
|
|
177
|
+
Layer.provide(SemanticSearchLayer),
|
|
178
|
+
Layer.merge(layerClankaModel(options.model)),
|
|
179
|
+
),
|
|
180
|
+
{ local: true },
|
|
181
|
+
),
|
|
182
|
+
Effect.provide([
|
|
183
|
+
ModelServices,
|
|
184
|
+
TaskToolsHandlers,
|
|
185
|
+
Agent.ConversationMode.layer(true),
|
|
186
|
+
]),
|
|
187
|
+
Effect.ignore(),
|
|
188
|
+
)
|
|
@@ -17,7 +17,7 @@ import { GithubIssueSource } from "./Github.ts"
|
|
|
17
17
|
import { IssuesChange, IssueSource } from "./IssueSource.ts"
|
|
18
18
|
import { PlatformServices } from "./shared/platform.ts"
|
|
19
19
|
import type { PrdIssue } from "./domain/PrdIssue.ts"
|
|
20
|
-
import type {
|
|
20
|
+
import type { ProjectId } from "./domain/Project.ts"
|
|
21
21
|
import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
|
22
22
|
|
|
23
23
|
const issueSources: ReadonlyArray<typeof CurrentIssueSource.Service> = [
|
|
@@ -202,17 +202,6 @@ const getCurrentIssues = (projectId: ProjectId) =>
|
|
|
202
202
|
pipe(s.ref(projectId), Effect.flatMap(SubscriptionRef.get)),
|
|
203
203
|
)
|
|
204
204
|
|
|
205
|
-
export const checkForWork = Effect.fnUntraced(function* (project: Project) {
|
|
206
|
-
if (project.gitFlow === "ralph") return
|
|
207
|
-
const { issues } = yield* getCurrentIssues(project.id)
|
|
208
|
-
const hasIncomplete = issues.some(
|
|
209
|
-
(issue) => issue.state === "todo" && issue.blockedBy.length === 0,
|
|
210
|
-
)
|
|
211
|
-
if (!hasIncomplete) {
|
|
212
|
-
return yield* new NoMoreWork({})
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
|
|
216
205
|
export const resetInProgress = Effect.gen(function* () {
|
|
217
206
|
const source = yield* IssueSource
|
|
218
207
|
const projectId = yield* CurrentProjectId
|
|
@@ -233,11 +222,3 @@ export const resetInProgress = Effect.gen(function* () {
|
|
|
233
222
|
{ concurrency: 5, discard: true },
|
|
234
223
|
)
|
|
235
224
|
})
|
|
236
|
-
|
|
237
|
-
export class NoMoreWork extends Schema.ErrorClass<NoMoreWork>(
|
|
238
|
-
"lalph/Prd/NoMoreWork",
|
|
239
|
-
)({
|
|
240
|
-
_tag: Schema.tag("NoMoreWork"),
|
|
241
|
-
}) {
|
|
242
|
-
readonly message = "No more work to be done!"
|
|
243
|
-
}
|
package/src/commands/root.ts
CHANGED
|
@@ -24,11 +24,7 @@ import { Prd } from "../Prd.ts"
|
|
|
24
24
|
import { Worktree } from "../Worktree.ts"
|
|
25
25
|
import { Flag, Command, Prompt } from "effect/unstable/cli"
|
|
26
26
|
import { IssueSource, IssueSourceError } from "../IssueSource.ts"
|
|
27
|
-
import {
|
|
28
|
-
checkForWork,
|
|
29
|
-
CurrentIssueSource,
|
|
30
|
-
resetInProgress,
|
|
31
|
-
} from "../CurrentIssueSource.ts"
|
|
27
|
+
import { CurrentIssueSource, resetInProgress } from "../CurrentIssueSource.ts"
|
|
32
28
|
import { GithubCli } from "../Github/Cli.ts"
|
|
33
29
|
import { agentWorker } from "../Agents/worker.ts"
|
|
34
30
|
import { agentChooser, ChosenTaskNotFound } from "../Agents/chooser.ts"
|
|
@@ -542,6 +538,8 @@ const runProject = Effect.fnUntraced(
|
|
|
542
538
|
const iterationsDisplay = isFinite ? options.iterations : "unlimited"
|
|
543
539
|
const semaphore = Semaphore.makeUnsafe(options.project.concurrency)
|
|
544
540
|
const fibers = yield* FiberSet.make()
|
|
541
|
+
const source = yield* IssueSource
|
|
542
|
+
const issuesRef = yield* source.ref(options.project.id)
|
|
545
543
|
|
|
546
544
|
let executionMode: ProjectExecutionMode
|
|
547
545
|
if (options.project.gitFlow === "ralph") {
|
|
@@ -594,28 +592,6 @@ const runProject = Effect.fnUntraced(
|
|
|
594
592
|
})
|
|
595
593
|
}
|
|
596
594
|
|
|
597
|
-
const handleNoMoreWork = (
|
|
598
|
-
currentIteration: number,
|
|
599
|
-
setIterations: (iterations: number) => void,
|
|
600
|
-
) => {
|
|
601
|
-
if (executionMode._tag === "ralph") {
|
|
602
|
-
return Effect.void
|
|
603
|
-
}
|
|
604
|
-
if (isFinite) {
|
|
605
|
-
// If we have a finite number of iterations, we exit when no more
|
|
606
|
-
// work is found
|
|
607
|
-
setIterations(currentIteration)
|
|
608
|
-
return Effect.log(
|
|
609
|
-
`No more work to process, ending after ${currentIteration} iteration(s).`,
|
|
610
|
-
)
|
|
611
|
-
}
|
|
612
|
-
const log =
|
|
613
|
-
Iterable.size(fibers) <= 1
|
|
614
|
-
? Effect.log("No more work to process, waiting 30 seconds...")
|
|
615
|
-
: Effect.void
|
|
616
|
-
return Effect.andThen(log, Effect.sleep(Duration.seconds(30)))
|
|
617
|
-
}
|
|
618
|
-
|
|
619
595
|
yield* resetInProgress.pipe(Effect.withSpan("Main.resetInProgress"))
|
|
620
596
|
|
|
621
597
|
yield* Effect.log(
|
|
@@ -628,6 +604,33 @@ const runProject = Effect.fnUntraced(
|
|
|
628
604
|
|
|
629
605
|
yield* Atom.mount(activeWorkerLoggingAtom)
|
|
630
606
|
|
|
607
|
+
const waitForWork =
|
|
608
|
+
executionMode._tag === "ralph"
|
|
609
|
+
? Effect.void
|
|
610
|
+
: SubscriptionRef.changes(issuesRef).pipe(
|
|
611
|
+
Stream.takeUntilEffect(
|
|
612
|
+
Effect.fnUntraced(function* ({ issues }) {
|
|
613
|
+
const hasIncomplete = issues.some(
|
|
614
|
+
(issue) =>
|
|
615
|
+
issue.state === "todo" && issue.blockedBy.length === 0,
|
|
616
|
+
)
|
|
617
|
+
if (hasIncomplete) return true
|
|
618
|
+
if (isFinite) {
|
|
619
|
+
quit = true
|
|
620
|
+
yield* Effect.log(
|
|
621
|
+
`No more work to process, ending after ${iteration} iteration(s).`,
|
|
622
|
+
)
|
|
623
|
+
return yield* Effect.interrupt
|
|
624
|
+
}
|
|
625
|
+
if (Iterable.size(fibers) <= 1) {
|
|
626
|
+
yield* Effect.log("No more work to process")
|
|
627
|
+
}
|
|
628
|
+
return false
|
|
629
|
+
}),
|
|
630
|
+
),
|
|
631
|
+
Stream.runDrain,
|
|
632
|
+
)
|
|
633
|
+
|
|
631
634
|
while (true) {
|
|
632
635
|
yield* semaphore.take(1)
|
|
633
636
|
if (quit || (isFinite && iteration >= iterations)) {
|
|
@@ -640,7 +643,7 @@ const runProject = Effect.fnUntraced(
|
|
|
640
643
|
let ralphDone = false
|
|
641
644
|
|
|
642
645
|
const gitFlowLayer = resolveGitFlowLayer()
|
|
643
|
-
const fiber = yield*
|
|
646
|
+
const fiber = yield* waitForWork.pipe(
|
|
644
647
|
Effect.andThen(
|
|
645
648
|
resolveRunEffect(startedDeferred).pipe(
|
|
646
649
|
Effect.provide(gitFlowLayer, { local: true }),
|
|
@@ -657,11 +660,6 @@ const runProject = Effect.fnUntraced(
|
|
|
657
660
|
`No more work to process for Ralph, ending after ${currentIteration + 1} iteration(s).`,
|
|
658
661
|
)
|
|
659
662
|
},
|
|
660
|
-
NoMoreWork(_error) {
|
|
661
|
-
return handleNoMoreWork(currentIteration, (newIterations) => {
|
|
662
|
-
iterations = newIterations
|
|
663
|
-
})
|
|
664
|
-
},
|
|
665
663
|
QuitError(_error) {
|
|
666
664
|
quit = true
|
|
667
665
|
return Effect.void
|