lalph 0.3.44 → 0.3.45

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.3.44",
4
+ "version": "0.3.45",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -22,18 +22,17 @@
22
22
  "devDependencies": {
23
23
  "@changesets/changelog-github": "^0.6.0",
24
24
  "@changesets/cli": "^2.30.0",
25
- "@effect/ai-openai": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/ai-openai@d440fd4",
26
25
  "@effect/language-service": "^0.79.0",
27
- "@effect/platform-node": "4.0.0-beta.29",
28
- "@linear/sdk": "^76.0.0",
26
+ "@effect/platform-node": "4.0.0-beta.30",
27
+ "@linear/sdk": "^77.0.0",
29
28
  "@octokit/plugin-rest-endpoint-methods": "^17.0.0",
30
29
  "@octokit/types": "^16.0.0",
31
- "@typescript/native-preview": "7.0.0-dev.20260308.1",
32
- "clanka": "^0.0.14",
30
+ "@typescript/native-preview": "7.0.0-dev.20260310.1",
31
+ "clanka": "^0.0.15",
33
32
  "concurrently": "^9.2.1",
34
- "effect": "4.0.0-beta.29",
33
+ "effect": "4.0.0-beta.30",
35
34
  "husky": "^9.1.7",
36
- "lint-staged": "^16.3.2",
35
+ "lint-staged": "^16.3.3",
37
36
  "octokit": "^5.0.5",
38
37
  "oxlint": "^1.52.0",
39
38
  "prettier": "^3.8.1",
@@ -1,4 +1,13 @@
1
- import { Data, Duration, Effect, FileSystem, Path, pipe, Schema } from "effect"
1
+ import {
2
+ Data,
3
+ Deferred,
4
+ Duration,
5
+ Effect,
6
+ FileSystem,
7
+ Path,
8
+ pipe,
9
+ Schema,
10
+ } from "effect"
2
11
  import { PromptGen } from "../PromptGen.ts"
3
12
  import { Prd } from "../Prd.ts"
4
13
  import { ChildProcess } from "effect/unstable/process"
@@ -7,6 +16,8 @@ import { RunnerStalled } from "../domain/Errors.ts"
7
16
  import { makeWaitForFile } from "../shared/fs.ts"
8
17
  import { GitFlow } from "../GitFlow.ts"
9
18
  import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
19
+ import { runClanka } from "../Clanka.ts"
20
+ import { ChosenTaskDeferred } from "../TaskTools.ts"
10
21
 
11
22
  export const agentChooser = Effect.fnUntraced(function* (options: {
12
23
  readonly stallTimeout: Duration.Duration
@@ -20,6 +31,29 @@ export const agentChooser = Effect.fnUntraced(function* (options: {
20
31
  const gitFlow = yield* GitFlow
21
32
  const waitForFile = yield* makeWaitForFile
22
33
 
34
+ // use clanka
35
+ if (!options.preset.cliAgent.command) {
36
+ const deferred = ChosenTaskDeferred.of(Deferred.makeUnsafe())
37
+ const result = yield* runClanka({
38
+ directory: worktree.directory,
39
+ model: options.preset.extraArgs.join(" "),
40
+ prompt: promptGen.promptChooseClanka({ gitFlow }),
41
+ stallTimeout: options.stallTimeout,
42
+ withChoose: true,
43
+ }).pipe(
44
+ Effect.provideService(ChosenTaskDeferred, deferred),
45
+ Effect.flatMap(() => Effect.fail(new ChosenTaskNotFound())),
46
+ Effect.raceFirst(Deferred.await(deferred)),
47
+ )
48
+ const prdTask = yield* prd.findById(result.taskId)
49
+ if (!prdTask) throw new ChosenTaskNotFound()
50
+ return {
51
+ id: result.taskId,
52
+ githubPrNumber: result.githubPrNumber ?? null,
53
+ prd: prdTask,
54
+ }
55
+ }
56
+
23
57
  const taskJsonCreated = waitForFile(
24
58
  pathService.join(worktree.directory, ".lalph"),
25
59
  "task.json",
@@ -4,6 +4,8 @@ import { ChildProcess } from "effect/unstable/process"
4
4
  import { Worktree } from "../Worktree.ts"
5
5
  import { GitFlow } from "../GitFlow.ts"
6
6
  import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
7
+ import { ExitCode } from "effect/unstable/process/ChildProcessSpawner"
8
+ import { runClanka } from "../Clanka.ts"
7
9
 
8
10
  export const agentReviewer = Effect.fnUntraced(function* (options: {
9
11
  readonly specsDirectory: string
@@ -22,6 +24,29 @@ export const agentReviewer = Effect.fnUntraced(function* (options: {
22
24
  Effect.option,
23
25
  )
24
26
 
27
+ // use clanka
28
+ if (!options.preset.cliAgent.command) {
29
+ yield* runClanka({
30
+ directory: worktree.directory,
31
+ model: options.preset.extraArgs.join(" "),
32
+ system: promptGen.systemClanka(options),
33
+ prompt: Option.match(customInstructions, {
34
+ onNone: () =>
35
+ promptGen.promptReview({
36
+ prompt: options.instructions,
37
+ gitFlow,
38
+ }),
39
+ onSome: (prompt) =>
40
+ promptGen.promptReviewCustom({
41
+ prompt,
42
+ specsDirectory: options.specsDirectory,
43
+ }),
44
+ }),
45
+ stallTimeout: options.stallTimeout,
46
+ })
47
+ return ExitCode(0)
48
+ }
49
+
25
50
  const cliCommand = pipe(
26
51
  options.preset.cliAgent.command({
27
52
  prompt: Option.match(customInstructions, {
@@ -3,6 +3,8 @@ import { PromptGen } from "../PromptGen.ts"
3
3
  import { ChildProcess } from "effect/unstable/process"
4
4
  import { Worktree } from "../Worktree.ts"
5
5
  import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
6
+ import { ExitCode } from "effect/unstable/process/ChildProcessSpawner"
7
+ import { runClanka } from "../Clanka.ts"
6
8
 
7
9
  export const agentTasker = Effect.fnUntraced(function* (options: {
8
10
  readonly specsDirectory: string
@@ -13,6 +15,20 @@ export const agentTasker = Effect.fnUntraced(function* (options: {
13
15
  const worktree = yield* Worktree
14
16
  const promptGen = yield* PromptGen
15
17
 
18
+ // use clanka
19
+ if (!options.preset.cliAgent.command) {
20
+ yield* runClanka({
21
+ directory: worktree.directory,
22
+ model: options.preset.extraArgs.join(" "),
23
+ system: promptGen.systemClanka(options),
24
+ prompt: promptGen.promptPlanTasksClanka({
25
+ specsDirectory: options.specsDirectory,
26
+ specificationPath: options.specificationPath,
27
+ }),
28
+ })
29
+ return ExitCode(0)
30
+ }
31
+
16
32
  return yield* pipe(
17
33
  options.preset.cliAgent.command({
18
34
  prompt: promptGen.promptPlanTasks({
@@ -4,6 +4,8 @@ import { ChildProcess } from "effect/unstable/process"
4
4
  import { Worktree } from "../Worktree.ts"
5
5
  import type { PrdIssue } from "../domain/PrdIssue.ts"
6
6
  import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
7
+ import { ExitCode } from "effect/unstable/process/ChildProcessSpawner"
8
+ import { runClanka } from "../Clanka.ts"
7
9
 
8
10
  export const agentTimeout = Effect.fnUntraced(function* (options: {
9
11
  readonly specsDirectory: string
@@ -15,6 +17,21 @@ export const agentTimeout = Effect.fnUntraced(function* (options: {
15
17
  const worktree = yield* Worktree
16
18
  const promptGen = yield* PromptGen
17
19
 
20
+ // use clanka
21
+ if (!options.preset.cliAgent.command) {
22
+ yield* runClanka({
23
+ directory: worktree.directory,
24
+ model: options.preset.extraArgs.join(" "),
25
+ system: promptGen.systemClanka(options),
26
+ prompt: promptGen.promptTimeoutClanka({
27
+ taskId: options.task.id!,
28
+ specsDirectory: options.specsDirectory,
29
+ }),
30
+ stallTimeout: options.stallTimeout,
31
+ })
32
+ return ExitCode(0)
33
+ }
34
+
18
35
  const timeoutCommand = pipe(
19
36
  options.preset.cliAgent.command({
20
37
  prompt: promptGen.promptTimeout({
@@ -2,15 +2,30 @@ import { Duration, Effect, Path, pipe } from "effect"
2
2
  import { ChildProcess } from "effect/unstable/process"
3
3
  import { Worktree } from "../Worktree.ts"
4
4
  import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
5
+ import { runClanka } from "../Clanka.ts"
6
+ import { ExitCode } from "effect/unstable/process/ChildProcessSpawner"
5
7
 
6
8
  export const agentWorker = Effect.fnUntraced(function* (options: {
7
9
  readonly stallTimeout: Duration.Duration
8
10
  readonly preset: CliAgentPreset
11
+ readonly system?: string
9
12
  readonly prompt: string
10
13
  }) {
11
14
  const pathService = yield* Path.Path
12
15
  const worktree = yield* Worktree
13
16
 
17
+ // use clanka
18
+ if (!options.preset.cliAgent.command) {
19
+ yield* runClanka({
20
+ directory: worktree.directory,
21
+ model: options.preset.extraArgs.join(" "),
22
+ system: options.system,
23
+ prompt: options.prompt,
24
+ stallTimeout: options.stallTimeout,
25
+ })
26
+ return ExitCode(0)
27
+ }
28
+
14
29
  const cliCommand = pipe(
15
30
  options.preset.cliAgent.command({
16
31
  prompt: options.prompt,
package/src/Clanka.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  TaskToolsHandlers,
6
6
  TaskToolsWithChoose,
7
7
  } from "./TaskTools.ts"
8
- import { clankaSubagent } from "./ClankaModels.ts"
8
+ import { ClankaModels, clankaSubagent } from "./ClankaModels.ts"
9
9
  import { withStallTimeout } from "./shared/stream.ts"
10
10
  import type { AiError } from "effect/unstable/ai"
11
11
  import type { RunnerStalled } from "./domain/Errors.ts"
@@ -14,18 +14,21 @@ export const runClanka = Effect.fnUntraced(
14
14
  /** The working directory to run the agent in */
15
15
  function* (options: {
16
16
  readonly directory: string
17
+ readonly model: string
17
18
  readonly prompt: string
18
19
  readonly system?: string | undefined
19
20
  readonly stallTimeout?: Duration.Input | undefined
20
21
  readonly withChoose?: boolean | undefined
21
22
  }) {
23
+ const models = yield* ClankaModels
22
24
  const agent = yield* Agent.make({
23
25
  ...options,
24
26
  tools: options.withChoose
25
27
  ? TaskToolsWithChoose
26
28
  : (TaskTools as unknown as typeof TaskToolsWithChoose),
27
- subagentModel: clankaSubagent,
28
- })
29
+ subagentModel: clankaSubagent(models, options.model),
30
+ }).pipe(Effect.provide(models.get(options.model)))
31
+
29
32
  let stream = options.stallTimeout
30
33
  ? withStallTimeout(options.stallTimeout)(agent.output)
31
34
  : agent.output
@@ -1,53 +1,72 @@
1
+ // oxlint-disable typescript/no-explicit-any
1
2
  import { NodeHttpClient } from "@effect/platform-node"
2
- import { Codex } from "clanka"
3
- import { Layer, LayerMap, PlatformError, Schema } from "effect"
3
+ import { Codex, Copilot } from "clanka"
4
+ import { Effect, flow, Layer, LayerMap, Schema } from "effect"
4
5
  import { layerKvs } from "./Kvs.ts"
5
- import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai"
6
6
 
7
- export const CodexLayer: Layer.Layer<
8
- OpenAiClient.OpenAiClient,
9
- PlatformError.PlatformError
10
- > = Codex.layer.pipe(
11
- Layer.provide(NodeHttpClient.layerUndici),
12
- Layer.provide(layerKvs),
7
+ export const ModelServices = NodeHttpClient.layerUndici.pipe(
8
+ Layer.merge(layerKvs),
13
9
  )
14
10
 
15
- export const clankaModels = {
16
- "gpt-5.4-xhigh": OpenAiLanguageModel.model("gpt-5.4", {
17
- reasoning: {
18
- effort: "xhigh",
19
- summary: "auto",
20
- },
21
- }).pipe(Layer.provideMerge(CodexLayer)),
22
- "gpt-5.4-high": OpenAiLanguageModel.model("gpt-5.4", {
23
- reasoning: {
24
- effort: "high",
25
- summary: "auto",
26
- },
27
- }).pipe(Layer.provideMerge(CodexLayer)),
28
- "gpt-5.4-medium": OpenAiLanguageModel.model("gpt-5.4", {
29
- reasoning: {
30
- effort: "high",
31
- summary: "auto",
32
- },
33
- }).pipe(Layer.provideMerge(CodexLayer)),
34
- } as const
35
-
36
- export type ClankaModel = keyof typeof clankaModels
37
- export const ClankaModel = Schema.Literals(
38
- Object.keys(clankaModels) as ClankaModel[],
11
+ const Reasoning = Schema.Literals(["low", "medium", "high", "xhigh"])
12
+ const parseInput = flow(
13
+ Schema.decodeUnknownEffect(
14
+ Schema.Tuple([
15
+ Schema.Literals(["openai", "copilot"]),
16
+ Schema.String,
17
+ Reasoning,
18
+ ]),
19
+ ),
20
+ Effect.orDie,
39
21
  )
40
22
 
41
- export const clankaSubagent = OpenAiLanguageModel.model("gpt-5.4", {
42
- reasoning: {
43
- effort: "low",
44
- summary: "auto",
45
- },
46
- }).pipe(Layer.provideMerge(CodexLayer))
23
+ export const clankaSubagent = Effect.fnUntraced(function* (
24
+ models: ClankaModels["Service"],
25
+ input: string,
26
+ ) {
27
+ const [provider, model] = yield* parseInput(input.split("/"))
28
+ return models.get(`${provider}/${model}/low`)
29
+ }, Layer.unwrap)
47
30
 
48
31
  export class ClankaModels extends LayerMap.Service<ClankaModels>()(
49
32
  "lalph/ClankaModels",
50
33
  {
51
- layers: clankaModels,
34
+ dependencies: [ModelServices],
35
+ lookup: Effect.fnUntraced(function* (input: string) {
36
+ const [provider, model, reasoning] = yield* parseInput(input.split("/"))
37
+ switch (provider) {
38
+ case "openai": {
39
+ return Codex.model(model, {
40
+ reasoning: {
41
+ effort: reasoning,
42
+ },
43
+ })
44
+ }
45
+ case "copilot": {
46
+ return Copilot.model(model, {
47
+ ...reasoningToCopilotConfig(model, reasoning),
48
+ })
49
+ }
50
+ }
51
+ }, Layer.unwrap),
52
52
  },
53
53
  ) {}
54
+
55
+ const reasoningToCopilotConfig = (
56
+ model: string,
57
+ reasoning: typeof Reasoning.Type,
58
+ ) => {
59
+ if (model.startsWith("claude")) {
60
+ switch (reasoning) {
61
+ case "low":
62
+ return {}
63
+ case "medium":
64
+ return { reasoningEffort: 4000 }
65
+ case "high":
66
+ return { thinking_budget: 16000 }
67
+ case "xhigh":
68
+ return { thinking_budget: 31999 }
69
+ }
70
+ }
71
+ return { reasoningEffort: reasoning }
72
+ }
package/src/Presets.ts CHANGED
@@ -5,7 +5,6 @@ import { Prompt } from "effect/unstable/cli"
5
5
  import { allCliAgents, type AnyCliAgent } from "./domain/CliAgent.ts"
6
6
  import { parseCommand } from "./shared/child-process.ts"
7
7
  import { IssueSource } from "./IssueSource.ts"
8
- import { ClankaModel, clankaModels } from "./ClankaModels.ts"
9
8
 
10
9
  export const allCliAgentPresets = new Setting(
11
10
  "cliAgentPresets",
@@ -120,29 +119,12 @@ export const addOrUpdatePreset = Effect.fnUntraced(function* (options?: {
120
119
  options?.existing?.commandPrefix,
121
120
  )
122
121
 
123
- const clankaModel = yield* Prompt.select<ClankaModel | undefined>({
124
- message: "clanka model?",
125
- choices: [
126
- {
127
- title: "none",
128
- value: undefined,
129
- selected: options?.existing?.clankaModel === undefined,
130
- },
131
- ...(Object.keys(clankaModels) as Array<ClankaModel>).map((key) => ({
132
- title: key,
133
- value: key,
134
- selected: options?.existing?.clankaModel === key,
135
- })),
136
- ],
137
- })
138
-
139
122
  let preset = new CliAgentPreset({
140
123
  id,
141
124
  cliAgent,
142
125
  commandPrefix,
143
126
  extraArgs,
144
127
  sourceMetadata: {},
145
- ...(clankaModel ? { clankaModel } : {}),
146
128
  })
147
129
 
148
130
  if (id !== CliAgentPreset.defaultId) {
package/src/PromptGen.ts CHANGED
@@ -90,19 +90,17 @@ Set \`githubPrNumber\` to the PR number if one exists, otherwise use \`null\`.
90
90
 
91
91
  const promptChooseClanka = (options: {
92
92
  readonly gitFlow: GitFlow["Service"]
93
- }) => `Your job is to choose the next task to work on from the current task list.
93
+ }) => `Your job is to choose the next task to work on using "listEligibleTasks".
94
94
  **DO NOT** implement the task yet.
95
95
 
96
96
  The following instructions should be done without interaction or asking for permission.
97
97
 
98
- - Decide which single task to work on next from the task list. This should
98
+ - Decide which single task to work on next from "listEligibleTasks". This should
99
99
  be the task YOU decide as the most important to work on next, not just the
100
- first task in the list.
101
- - Only start tasks that are in a "todo" state.
102
- - You **cannot** start tasks unless they have an empty \`blockedBy\` field.${
100
+ first task in the list.${
103
101
  options.gitFlow.requiresGithubPr
104
102
  ? `
105
- - Check if there is an open Github PR for the chosen task. If there is, note the PR number for inclusion in the task.json file.
103
+ - Check if there is an open Github PR for the chosen task. If there is, note the PR number for inclusion when calling "chooseTask".
106
104
  - Only include "open" PRs that are not yet merged.
107
105
  - The pull request will contain the task id in the title or description.`
108
106
  : ""
@@ -227,16 +225,16 @@ ${keyInformation(options)}`
227
225
  readonly specsDirectory: string
228
226
  readonly githubPrNumber: number | undefined
229
227
  readonly gitFlow: GitFlow["Service"]
230
- }) => `Your job is to implement this task:
228
+ }) => `# ${options.task.title}
231
229
 
232
- ID: ${options.task.id}
233
- Task: ${options.task.title}
234
- Description:
230
+ Task ID: ${options.task.id}
235
231
 
236
232
  ${options.task.description}
237
233
 
238
234
  # Workflow
239
235
 
236
+ All steps must be done before the task can be considered complete.
237
+
240
238
  1. Carefully study the current task list to understand the context of the task, and
241
239
  discover any key learnings from previous work.
242
240
  Also read the ${options.specsDirectory}/README.md file (if available), to see
@@ -324,11 +322,9 @@ permission.
324
322
  1. Investigate why you think the task took too long. Research the codebase
325
323
  further to understand what is needed to complete the task.
326
324
  2. Mark the original task as "done" by updating its \`state\`.
327
- 3. Break down the task into smaller tasks and add them to the prd.yml file.
325
+ 3. Break down the task into smaller tasks and add them to the task list.
328
326
  Read the "### Adding tasks" section below **extremely carefully** for guidelines on creating tasks.
329
- 4. Setup task dependencies using the \`blockedBy\` field as needed. You will need
330
- to wait 5 seconds after adding tasks to the prd.yml file to allow the system
331
- to assign ids to the new tasks before you can setup dependencies.
327
+ - Make sure to setup task dependencies using the \`blockedBy\` field as needed.
332
328
  5. If any specifications need updating based on your new understanding, update them.`
333
329
 
334
330
  const planPrompt = (options: {
@@ -415,6 +411,23 @@ setup dependencies between the tasks using the \`blockedBy\` field.
415
411
 
416
412
  ${prdNotes(options)}`
417
413
 
414
+ const promptPlanTasksClanka = (options: {
415
+ readonly specsDirectory: string
416
+ readonly specificationPath: string
417
+ }) => `Your job is to convert the implementation plan in the specification file at
418
+ \`${options.specificationPath}\` into tasks.
419
+
420
+ Before starting, read the entire task list to understand the context of existing tasks
421
+ and to ensure you do not create duplicate tasks.
422
+
423
+ Make sure each task is small, atomic and independently shippable without failing
424
+ validation checks (typechecks, linting, tests).
425
+ Each task should include a reference to the specification file in its description.
426
+
427
+ Make sure to setup dependencies between the tasks using the \`blockedBy\` field.
428
+
429
+ **Important:** You are only creating or updating a plan, not implementing any tasks yet.`
430
+
418
431
  return {
419
432
  promptChoose,
420
433
  promptChooseClanka,
@@ -426,6 +439,7 @@ ${prdNotes(options)}`
426
439
  promptTimeoutClanka,
427
440
  planPrompt,
428
441
  promptPlanTasks,
442
+ promptPlanTasksClanka,
429
443
  systemClanka,
430
444
  } as const
431
445
  }),
@@ -30,7 +30,6 @@ export const commandAgentsLs = Command.make("ls").pipe(
30
30
  console.log(`Preset: ${preset.id}`)
31
31
  yield* source.cliAgentPresetInfo(preset)
32
32
  console.log(` CLI agent: ${preset.cliAgent.name}`)
33
- console.log(` Clanka model: ${preset.clankaModel ?? "none"}`)
34
33
  if (preset.extraArgs.length > 0) {
35
34
  console.log(` Extra args: ${preset.extraArgs.join(" ")}`)
36
35
  }
@@ -10,6 +10,7 @@ import { commandRoot } from "../root.ts"
10
10
  import { selectCliAgentPreset } from "../../Presets.ts"
11
11
  import { CurrentIssueSource } from "../../CurrentIssueSource.ts"
12
12
  import type { CliAgentPreset } from "../../domain/CliAgentPreset.ts"
13
+ import { ClankaModels } from "../../ClankaModels.ts"
13
14
 
14
15
  const specificationPath = Argument.path("spec", {
15
16
  pathType: "file",
@@ -70,8 +71,9 @@ const generateTasks = Effect.fnUntraced(
70
71
  })
71
72
  },
72
73
  Effect.provide([
74
+ ClankaModels.layer,
73
75
  Settings.layer,
74
76
  PromptGen.layer,
75
- Prd.layerProvided.pipe(Layer.provide(layerProjectIdPrompt)),
77
+ Prd.layerProvided.pipe(Layer.provideMerge(layerProjectIdPrompt)),
76
78
  ]),
77
79
  )
@@ -15,6 +15,7 @@ import { selectCliAgentPreset } from "../Presets.ts"
15
15
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
16
16
  import { parseBranch } from "../shared/git.ts"
17
17
  import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
18
+ import { ClankaModels } from "../ClankaModels.ts"
18
19
 
19
20
  const dangerous = Flag.boolean("dangerous").pipe(
20
21
  Flag.withAlias("d"),
@@ -74,7 +75,13 @@ export const commandPlan = Command.make("plan", {
74
75
  dangerous,
75
76
  preset,
76
77
  }).pipe(Effect.provideService(CurrentProjectId, project.id))
77
- }).pipe(Effect.provide([Settings.layer, CurrentIssueSource.layer]))
78
+ }).pipe(
79
+ Effect.provide([
80
+ Settings.layer,
81
+ CurrentIssueSource.layer,
82
+ ClankaModels.layer,
83
+ ]),
84
+ )
78
85
  }, Effect.provide(Editor.layer)),
79
86
  ),
80
87
  Command.withSubcommands([commandPlanTasks]),