lalph 0.3.46 → 0.3.47

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 CHANGED
@@ -5353,6 +5353,18 @@ const succeed$8 = succeed$9;
5353
5353
  */
5354
5354
  const fail$10 = fail$11;
5355
5355
  /**
5356
+ * A pre-built `Result<void>` holding `undefined` as its failure value.
5357
+ *
5358
+ * - Use when you need a `Result` that represents "failed with no meaningful value"
5359
+ * - Equivalent to `Result.fail(undefined)` but avoids an extra allocation
5360
+ *
5361
+ * @see {@link fail}
5362
+ *
5363
+ * @category Constructors
5364
+ * @since 4.0.0
5365
+ */
5366
+ const failVoid = /* @__PURE__ */ fail$10(void 0);
5367
+ /**
5356
5368
  * Checks whether a `Result` is a `Failure`.
5357
5369
  *
5358
5370
  * - Acts as a TypeScript type guard, narrowing to `Failure<A, E>`
@@ -13021,7 +13033,7 @@ const repeat$1 = /* @__PURE__ */ dual(2, (self, options) => {
13021
13033
  return repeatOrElse$1(self, typeof options === "function" ? options(identity) : isSchedule(options) ? options : buildFromOptions(options), fail$9);
13022
13034
  });
13023
13035
  /** @internal */
13024
- const retry$2 = /* @__PURE__ */ dual(2, (self, options) => {
13036
+ const retry$4 = /* @__PURE__ */ dual(2, (self, options) => {
13025
13037
  return retryOrElse$1(self, typeof options === "function" ? options(identity) : isSchedule(options) ? options : buildFromOptions(options), fail$9);
13026
13038
  });
13027
13039
  const passthroughForever = /* @__PURE__ */ passthrough$2(forever$1);
@@ -14800,7 +14812,7 @@ const tapCause = tapCause$1;
14800
14812
  * @since 2.0.0
14801
14813
  * @category Error Handling
14802
14814
  */
14803
- const retry$1 = retry$2;
14815
+ const retry$3 = retry$4;
14804
14816
  /**
14805
14817
  * Discards both the success and failure values of an effect.
14806
14818
  *
@@ -24046,6 +24058,90 @@ const set$9 = /* @__PURE__ */ dual(2, (self, value) => {
24046
24058
  self.current = value;
24047
24059
  return self;
24048
24060
  });
24061
+ /**
24062
+ * Sets the MutableRef to a new value and returns the new value.
24063
+ *
24064
+ * @example
24065
+ * ```ts
24066
+ * import { MutableRef } from "effect"
24067
+ *
24068
+ * const ref = MutableRef.make("old")
24069
+ *
24070
+ * // Set and get the new value
24071
+ * const newValue = MutableRef.setAndGet(ref, "new")
24072
+ * console.log(newValue) // "new"
24073
+ * console.log(MutableRef.get(ref)) // "new"
24074
+ *
24075
+ * // Useful for assignments that need the value
24076
+ * const counter = MutableRef.make(0)
24077
+ * const currentValue = MutableRef.setAndGet(counter, 42)
24078
+ * console.log(`Counter set to: ${currentValue}`) // "Counter set to: 42"
24079
+ *
24080
+ * // Pipe-able version
24081
+ * const setValue = MutableRef.setAndGet("final")
24082
+ * const result = setValue(ref)
24083
+ * console.log(result) // "final"
24084
+ *
24085
+ * // Difference from set: returns value instead of reference
24086
+ * const ref1 = MutableRef.make(1)
24087
+ * const returnedRef = MutableRef.set(ref1, 2) // Returns MutableRef
24088
+ * const returnedValue = MutableRef.setAndGet(ref1, 3) // Returns value
24089
+ * console.log(returnedValue) // 3
24090
+ * ```
24091
+ *
24092
+ * @since 2.0.0
24093
+ * @category general
24094
+ */
24095
+ const setAndGet = /* @__PURE__ */ dual(2, (self, value) => {
24096
+ self.current = value;
24097
+ return self.current;
24098
+ });
24099
+ /**
24100
+ * Updates the MutableRef with the result of applying a function to its current value,
24101
+ * and returns the new value.
24102
+ *
24103
+ * @example
24104
+ * ```ts
24105
+ * import { MutableRef } from "effect"
24106
+ *
24107
+ * const counter = MutableRef.make(5)
24108
+ *
24109
+ * // Increment and get the new value
24110
+ * const newValue = MutableRef.updateAndGet(counter, (n) => n + 1)
24111
+ * console.log(newValue) // 6
24112
+ * console.log(MutableRef.get(counter)) // 6
24113
+ *
24114
+ * // Double the value and get the result
24115
+ * const doubled = MutableRef.updateAndGet(counter, (n) => n * 2)
24116
+ * console.log(doubled) // 12
24117
+ *
24118
+ * // Transform string and get result
24119
+ * const message = MutableRef.make("hello")
24120
+ * const upperCase = MutableRef.updateAndGet(message, (s) => s.toUpperCase())
24121
+ * console.log(upperCase) // "HELLO"
24122
+ *
24123
+ * // Pipe-able version
24124
+ * const increment = MutableRef.updateAndGet((n: number) => n + 1)
24125
+ * const result = increment(counter)
24126
+ * console.log(result) // 13 (new value)
24127
+ *
24128
+ * // Useful for calculations that need the result
24129
+ * const score = MutableRef.make(100)
24130
+ * const bonus = 50
24131
+ * const newScore = MutableRef.updateAndGet(score, (s) => s + bonus)
24132
+ * console.log(`New score: ${newScore}`) // "New score: 150"
24133
+ *
24134
+ * // Array transformations
24135
+ * const list = MutableRef.make<Array<number>>([1, 2, 3])
24136
+ * const newList = MutableRef.updateAndGet(list, (arr) => arr.map((x) => x * 2))
24137
+ * console.log(newList) // [2, 4, 6]
24138
+ * console.log(MutableRef.get(list)) // [2, 4, 6]
24139
+ * ```
24140
+ *
24141
+ * @since 2.0.0
24142
+ * @category general
24143
+ */
24144
+ const updateAndGet = /* @__PURE__ */ dual(2, (self, f) => setAndGet(self, f(get$14(self))));
24049
24145
  //#endregion
24050
24146
  //#region node_modules/.pnpm/effect@4.0.0-beta.30/node_modules/effect/dist/PubSub.js
24051
24147
  /**
@@ -27684,6 +27780,18 @@ const filterArray = /* @__PURE__ */ dual(2, (self, predicate) => transformPull$1
27684
27780
  * @since 4.0.0
27685
27781
  * @category Filtering
27686
27782
  */
27783
+ const filterMapArray = /* @__PURE__ */ dual(2, (self, filter) => transformPull$1(self, (pull) => succeed$3(flatMap$4(pull, function loop(arr) {
27784
+ const passes = [];
27785
+ for (let i = 0; i < arr.length; i++) {
27786
+ const result = filter(arr[i]);
27787
+ if (isSuccess$5(result)) passes.push(result.success);
27788
+ }
27789
+ return isReadonlyArrayNonEmpty(passes) ? succeed$3(passes) : flatMap$4(pull, loop);
27790
+ }))));
27791
+ /**
27792
+ * @since 4.0.0
27793
+ * @category Filtering
27794
+ */
27687
27795
  const filterMapArrayEffect = /* @__PURE__ */ dual(2, (self, filter) => transformPull$1(self, (pull) => succeed$3(flatMap$4(pull, function loop(arr) {
27688
27796
  return flatMap$4(filterMapEffect$1(arr, filter), (passes) => isReadonlyArrayNonEmpty(passes) ? succeed$3(passes) : flatMap$4(pull, loop));
27689
27797
  }))));
@@ -27796,6 +27904,63 @@ const mapError$1 = /* @__PURE__ */ dual(2, (self, f) => catch_$1(self, (err) =>
27796
27904
  */
27797
27905
  const orDie$1 = (self) => catch_$1(self, die$1);
27798
27906
  /**
27907
+ * Returns a new channel that retries this channel according to the specified
27908
+ * schedule whenever it fails.
27909
+ *
27910
+ * @since 4.0.0
27911
+ * @category utils
27912
+ */
27913
+ const retry$2 = /* @__PURE__ */ dual(2, (self, schedule) => suspend$2(() => {
27914
+ let step = void 0;
27915
+ let meta = CurrentMetadata.defaultValue();
27916
+ const withReset = onFirst(provideServiceEffect(self, CurrentMetadata, sync(() => meta)), () => {
27917
+ step = void 0;
27918
+ return void_$1;
27919
+ });
27920
+ const resolvedSchedule = typeof schedule === "function" ? schedule(identity) : schedule;
27921
+ const loop = catch_$1(withReset, fnUntraced(function* (error) {
27922
+ if (!step) step = yield* toStepWithMetadata(resolvedSchedule);
27923
+ meta = yield* step(error);
27924
+ return loop;
27925
+ }, (effect, error) => catchDone(effect, () => succeed$3(fail$3(error))), unwrap$2));
27926
+ return loop;
27927
+ }));
27928
+ /**
27929
+ * Returns a new channel, which sequentially combines this channel, together
27930
+ * with the provided factory function, which creates a second channel based on
27931
+ * the output values of this channel. The result is a channel that will first
27932
+ * perform the functions of this channel, before performing the functions of
27933
+ * the created channel (including yielding its terminal value).
27934
+ *
27935
+ * @example
27936
+ * ```ts
27937
+ * import { Channel, Data } from "effect"
27938
+ *
27939
+ * class SwitchError extends Data.TaggedError("SwitchError")<{
27940
+ * readonly reason: string
27941
+ * }> {}
27942
+ *
27943
+ * // Create a channel that outputs numbers
27944
+ * const numberChannel = Channel.fromIterable([1, 2, 3])
27945
+ *
27946
+ * // Switch to new channels based on each value
27947
+ * const switchedChannel = Channel.switchMap(
27948
+ * numberChannel,
27949
+ * (n) => Channel.fromIterable([`value-${n}`])
27950
+ * )
27951
+ *
27952
+ * // Outputs: "value-1", "value-2", "value-3"
27953
+ * ```
27954
+ *
27955
+ * @since 2.0.0
27956
+ * @category sequencing
27957
+ */
27958
+ const switchMap$1 = /* @__PURE__ */ dual((args) => isChannel(args[0]), (self, f, options) => self.pipe(map$7(f), mergeAll$1({
27959
+ ...options,
27960
+ concurrency: options?.concurrency ?? 1,
27961
+ switch: true
27962
+ })));
27963
+ /**
27799
27964
  * Merges multiple channels with specified concurrency and buffering options.
27800
27965
  *
27801
27966
  * @example
@@ -28066,6 +28231,18 @@ const onExit = /* @__PURE__ */ dual(2, (self, finalizer) => fromTransformBracket
28066
28231
  * @since 4.0.0
28067
28232
  * @category utils
28068
28233
  */
28234
+ const onFirst = /* @__PURE__ */ dual(2, (self, onFirst) => transformPull$1(self, (pull) => sync(() => {
28235
+ let isFirst = true;
28236
+ const pullFirst = tap$1(pull, (element) => {
28237
+ isFirst = false;
28238
+ return onFirst(element);
28239
+ });
28240
+ return suspend$3(() => isFirst ? pullFirst : pull);
28241
+ })));
28242
+ /**
28243
+ * @since 4.0.0
28244
+ * @category utils
28245
+ */
28069
28246
  const onEnd$1 = /* @__PURE__ */ dual(2, (self, onEnd) => transformPull$1(self, (pull) => succeed$3(catchDone(pull, (leftover) => flatMap$4(onEnd, () => done(leftover))))));
28070
28247
  /**
28071
28248
  * Returns a new channel with an attached finalizer. The finalizer is
@@ -28104,6 +28281,11 @@ const runWith$1 = (self, f, onHalt) => suspend$3(() => {
28104
28281
  */
28105
28282
  const provideService$1 = /* @__PURE__ */ dual(3, (self, key, service) => fromTransform$1((upstream, scope) => map$8(provideService$2(toTransform(self)(upstream, scope), key, service), provideService$2(key, service))));
28106
28283
  /**
28284
+ * @since 4.0.0
28285
+ * @category Services
28286
+ */
28287
+ const provideServiceEffect = /* @__PURE__ */ dual(3, (self, key, service) => fromTransform$1((upstream, scope) => flatMap$4(service, (s) => toTransform(provideService$1(self, key, s))(upstream, scope))));
28288
+ /**
28107
28289
  * Runs a channel and discards all output elements, returning only the final result.
28108
28290
  *
28109
28291
  * @example
@@ -29581,6 +29763,30 @@ const tap = /* @__PURE__ */ dual((args) => isStream(args[0]), (self, f, options)
29581
29763
  */
29582
29764
  const flatMap$2 = /* @__PURE__ */ dual((args) => isStream(args[0]), (self, f, options) => self.channel.pipe(flattenArray, flatMap$3((a) => f(a).channel, options), fromChannel));
29583
29765
  /**
29766
+ * Switches to the latest stream produced by the mapping function, interrupting
29767
+ * the previous stream when a new element arrives.
29768
+ *
29769
+ * @example
29770
+ * ```ts
29771
+ * import { Console, Effect, Stream } from "effect"
29772
+ *
29773
+ * const program = Stream.make(1, 2, 3).pipe(
29774
+ * Stream.switchMap((n) => (n === 3 ? Stream.make(n) : Stream.never)),
29775
+ * Stream.runCollect
29776
+ * )
29777
+ *
29778
+ * Effect.gen(function*() {
29779
+ * const result = yield* program
29780
+ * yield* Console.log(result)
29781
+ * // Output: [ 3 ]
29782
+ * })
29783
+ * ```
29784
+ *
29785
+ * @since 4.0.0
29786
+ * @category Sequencing
29787
+ */
29788
+ const switchMap = /* @__PURE__ */ dual((args) => isStream(args[0]), (self, f, options) => self.channel.pipe(flattenArray, switchMap$1((a) => f(a).channel, options), fromChannel));
29789
+ /**
29584
29790
  * Flattens a stream of streams into a single stream by concatenating the
29585
29791
  * inner streams in strict order.
29586
29792
  *
@@ -29764,6 +29970,13 @@ const mergeAll = /* @__PURE__ */ dual(2, (streams, options) => flatten(fromItera
29764
29970
  */
29765
29971
  const filter$3 = /* @__PURE__ */ dual(2, (self, predicate) => fromChannel(filterArray(toChannel(self), predicate)));
29766
29972
  /**
29973
+ * Filters and maps stream elements in one pass using a `Filter`.
29974
+ *
29975
+ * @since 4.0.0
29976
+ * @category Filtering
29977
+ */
29978
+ const filterMap$2 = /* @__PURE__ */ dual(2, (self, filter) => fromChannel(filterMapArray(toChannel(self), filter)));
29979
+ /**
29767
29980
  * Effectfully filters and maps elements in a single pass.
29768
29981
  *
29769
29982
  * @since 4.0.0
@@ -29945,6 +30158,38 @@ const mapError = /* @__PURE__ */ dual(2, (self, f) => fromChannel(mapError$1(sel
29945
30158
  */
29946
30159
  const orDie = (self) => fromChannel(orDie$1(self.channel));
29947
30160
  /**
30161
+ * When the stream fails, retry it according to the given schedule.
30162
+ *
30163
+ * This retries the entire stream, so will re-execute all of the stream's
30164
+ * acquire operations.
30165
+ *
30166
+ * The schedule is reset as soon as the first element passes through the
30167
+ * stream again.
30168
+ *
30169
+ * @example
30170
+ * ```ts
30171
+ * import { Console, Effect, Schedule, Stream } from "effect"
30172
+ *
30173
+ * const program = Effect.gen(function*() {
30174
+ * const values = yield* Stream.make(1).pipe(
30175
+ * Stream.concat(Stream.fail("boom")),
30176
+ * Stream.retry(Schedule.recurs(1)),
30177
+ * Stream.take(2),
30178
+ * Stream.runCollect
30179
+ * )
30180
+ *
30181
+ * yield* Console.log(values)
30182
+ * })
30183
+ *
30184
+ * Effect.runPromise(program)
30185
+ * // Output: [ 1, 1 ]
30186
+ * ```
30187
+ *
30188
+ * @since 2.0.0
30189
+ * @category Error Handling
30190
+ */
30191
+ const retry$1 = /* @__PURE__ */ dual(2, (self, policy) => fromChannel(retry$2(self.channel, policy)));
30192
+ /**
29948
30193
  * Takes the first `n` elements from this stream, returning `Stream.empty` when `n < 1`.
29949
30194
  *
29950
30195
  * @example
@@ -57286,7 +57531,7 @@ const retryTransient = /* @__PURE__ */ dual(2, (self, options) => {
57286
57531
  schedule: passthroughSchedule,
57287
57532
  times,
57288
57533
  while: isTransientResponse
57289
- }), retryOn === "response-only" ? identity : retry$1({
57534
+ }), retryOn === "response-only" ? identity : retry$3({
57290
57535
  while: isOnlySchedule || options.while === void 0 ? isTransientError : or(isTransientError, options.while),
57291
57536
  schedule,
57292
57537
  times
@@ -87378,6 +87623,15 @@ var PrdIssue = class PrdIssue extends Class$1("PrdIssue")({
87378
87623
  autoMerge
87379
87624
  });
87380
87625
  }
87626
+ update(options) {
87627
+ return new PrdIssue({
87628
+ ...this,
87629
+ title: options.title ?? this.title,
87630
+ description: options.description ?? this.description,
87631
+ state: options.state ?? this.state,
87632
+ blockedBy: options.blockedBy ?? this.blockedBy
87633
+ });
87634
+ }
87381
87635
  };
87382
87636
  //#endregion
87383
87637
  //#region node_modules/.pnpm/@linear+sdk@77.0.0_graphql@16.12.0/node_modules/@linear/sdk/dist/chunk-DPPnyiuk.mjs
@@ -179923,7 +180177,7 @@ var CurrentIssueSource = class CurrentIssueSource extends Service$1()("lalph/Cur
179923
180177
  const services$8 = yield* services();
179924
180178
  const refresh = set$4(ref, build$1).pipe(provideServices(services$8));
179925
180179
  const proxy = IssueSource.of({
179926
- issues: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.issues(projectId)), tapErrorTag("IssueSourceError", (e) => logWarning("Rebuilding issue source due to error", fail$7(e)).pipe(andThen(ignore$1(refresh)))), retry$1(refreshSchedule)),
180180
+ issues: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.issues(projectId)), tapErrorTag("IssueSourceError", (e) => logWarning("Rebuilding issue source due to error", fail$7(e)).pipe(andThen(ignore$1(refresh)))), retry$3(refreshSchedule)),
179927
180181
  createIssue: (projectId, options) => get$6(ref).pipe(flatMap$4((source) => source.createIssue(projectId, options))),
179928
180182
  updateIssue: (options) => get$6(ref).pipe(flatMap$4((source) => source.updateIssue(options))),
179929
180183
  cancelIssue: (projectId, issueId) => get$6(ref).pipe(flatMap$4((source) => source.cancelIssue(projectId, issueId))),
@@ -180775,7 +181029,7 @@ var Prd = class extends Service$1()("lalph/Prd", { make: gen(function* () {
180775
181029
  if (currentYaml === nextYaml) return;
180776
181030
  yield* fs.writeFileString(prdFile, nextYaml);
180777
181031
  }, scoped$1, withSpan("Prd.updateSync"), run$3(updateSyncHandle, { onlyIfMissing: true }), syncSemaphore.withPermitsIfAvailable(1));
180778
- yield* fs.watch(lalphDir).pipe(debounce(50), runForEach((_) => clear(updateSyncHandle).pipe(andThen(ignore$1(sync$2)))), retry$1(forever$1), forkScoped);
181032
+ yield* fs.watch(lalphDir).pipe(debounce(50), runForEach((_) => clear(updateSyncHandle).pipe(andThen(ignore$1(sync$2)))), retry$3(forever$1), forkScoped);
180779
181033
  yield* toStreamResult(registry, currentIssuesAtom(projectId)).pipe(runForEach(updateSync), forkScoped);
180780
181034
  const findById = fnUntraced(function* (issueId) {
180781
181035
  return (yield* getCurrentIssues).find((i) => i.id === issueId) ?? null;
@@ -187991,7 +188245,7 @@ var ji = Bt, Ii = Object.assign(Qe, { sync: Bt }), zi = Ut, Bi = Object.assign(e
187991
188245
  });
187992
188246
  Ze.glob = Ze;
187993
188247
  //#endregion
187994
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/ApplyPatch.js
188248
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/ApplyPatch.js
187995
188249
  /**
187996
188250
  * @since 1.0.0
187997
188251
  */
@@ -188320,7 +188574,7 @@ const patchChunks = (file, input, chunks) => {
188320
188574
  return eol === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
188321
188575
  };
188322
188576
  //#endregion
188323
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/AgentTools.js
188577
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/AgentTools.js
188324
188578
  /**
188325
188579
  * @since 1.0.0
188326
188580
  */
@@ -188616,7 +188870,7 @@ const AgentToolHandlers = AgentTools.toLayer(gen(function* () {
188616
188870
  }));
188617
188871
  var ApplyPatchError = class extends TaggedClass$1("ApplyPatchError") {};
188618
188872
  //#endregion
188619
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/Executor.js
188873
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/Executor.js
188620
188874
  /**
188621
188875
  * @since 1.0.0
188622
188876
  */
@@ -188696,7 +188950,7 @@ var QueueWriteStream = class extends Writable {
188696
188950
  }
188697
188951
  };
188698
188952
  //#endregion
188699
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/TypeBuilder.js
188953
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/TypeBuilder.js
188700
188954
  const resolveDocumentation = resolveAt("documentation");
188701
188955
  const identifierPattern = /^[$A-Z_a-z][$0-9A-Z_a-z]*$/u;
188702
188956
  const Precedence = {
@@ -188969,7 +189223,7 @@ const render = (schema, options) => {
188969
189223
  return printNode({ text: documentation === void 0 ? rendered.text : `${renderJsDoc(documentation, 0, printerOptions)}${printerOptions.newLine}${rendered.text}` }, printerOptions);
188970
189224
  };
188971
189225
  //#endregion
188972
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/ToolkitRenderer.js
189226
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/ToolkitRenderer.js
188973
189227
  /**
188974
189228
  * @since 1.0.0
188975
189229
  */
@@ -188991,7 +189245,7 @@ declare function ${name}(${params}): Promise<${render(tool.successSchema)}>`);
188991
189245
  }) });
188992
189246
  };
188993
189247
  //#endregion
188994
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/Agent.js
189248
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/Agent.js
188995
189249
  /**
188996
189250
  * @since 1.0.0
188997
189251
  */
@@ -189231,7 +189485,7 @@ ${prompt}`));
189231
189485
  case "finish": break;
189232
189486
  }
189233
189487
  return void_$1;
189234
- }), retry$1({ while: (err) => {
189488
+ }), retry$3({ while: (err) => {
189235
189489
  response = [];
189236
189490
  return err.isRetryable;
189237
189491
  } }), modelConfig.systemPromptTransform ? (effect) => modelConfig.systemPromptTransform(system, effect) : identity);
@@ -189305,7 +189559,7 @@ const generateSystemMulti = (toolsDts) => {
189305
189559
 
189306
189560
  - Use \`console.log\` to print any output you need.
189307
189561
  - Top level await is supported.
189308
- - **Prefer using the functions provided** over the bash tool
189562
+ - AVOID passing scripts into the "bash" function, and instead write javascript.
189309
189563
 
189310
189564
  **When you have fully completed your task**, call the "taskComplete" function with the final output.
189311
189565
  Make sure every detail of the task is done before calling "taskComplete".
@@ -189323,7 +189577,7 @@ const generateSystemSingle = (toolsDts) => {
189323
189577
 
189324
189578
  - Use \`console.log\` to print any output you need.
189325
189579
  - Top level await is supported.
189326
- - **Prefer using the functions provided** over the bash tool
189580
+ - AVOID passing scripts into the "bash" function, and instead write javascript.
189327
189581
 
189328
189582
  You have the following functions available to you:
189329
189583
 
@@ -199759,7 +200013,7 @@ const transformToolCallParams = /* @__PURE__ */ fnUntraced(function* (tools, too
199759
200013
  })));
199760
200014
  });
199761
200015
  //#endregion
199762
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/CodexAuth.js
200016
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/CodexAuth.js
199763
200017
  /**
199764
200018
  * @since 1.0.0
199765
200019
  */
@@ -199937,7 +200191,7 @@ var CodexAuth = class CodexAuth extends Service$1()("clanka/CodexAuth") {
199937
200191
  user_code: deviceCode.userCode
199938
200192
  }));
199939
200193
  const delayMs = deviceCode.intervalMs + POLLING_SAFETY_MARGIN_MS;
199940
- return yield* httpClient.execute(request).pipe(retry$1({
200194
+ return yield* httpClient.execute(request).pipe(retry$3({
199941
200195
  while: (e) => e.response?.status === 403 || e.response?.status === 404,
199942
200196
  schedule: spaced(delayMs)
199943
200197
  }), mapError$2((cause) => requestDeviceCodeError("Failed to poll Codex device authorization", cause)), flatMap$4((response) => schemaBodyJson(AuthorizationCodeResponseSchema)(response).pipe(mapError$2((cause) => requestDeviceCodeError("Failed to decode the Codex authorization approval response", cause)), map$8((payload) => ({
@@ -199979,7 +200233,7 @@ var CodexAuth = class CodexAuth extends Service$1()("clanka/CodexAuth") {
199979
200233
  static layerClient = this.layerClientNoDeps.pipe(provide$3(CodexAuth.layer));
199980
200234
  };
199981
200235
  //#endregion
199982
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/Codex.js
200236
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/Codex.js
199983
200237
  /**
199984
200238
  * @since 1.0.0
199985
200239
  */
@@ -201294,7 +201548,7 @@ const getUsageDetailNumber = (details, field) => {
201294
201548
  return typeof value === "number" ? value : void 0;
201295
201549
  };
201296
201550
  //#endregion
201297
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/CopilotAuth.js
201551
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/CopilotAuth.js
201298
201552
  /**
201299
201553
  * @since 1.0.0
201300
201554
  */
@@ -201485,7 +201739,7 @@ var GithubCopilotAuth = class GithubCopilotAuth extends Service$1()("clanka/Gith
201485
201739
  static layerClient = this.layerClientNoDeps.pipe(provide$3(GithubCopilotAuth.layer));
201486
201740
  };
201487
201741
  //#endregion
201488
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/Copilot.js
201742
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/Copilot.js
201489
201743
  /**
201490
201744
  * @since 1.0.0
201491
201745
  */
@@ -201908,7 +202162,7 @@ Object.defineProperties(createChalk.prototype, styles);
201908
202162
  const chalk = createChalk();
201909
202163
  createChalk({ level: stderrColor ? stderrColor.level : 0 });
201910
202164
  //#endregion
201911
- //#region node_modules/.pnpm/clanka@0.0.16_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_a5af971af29a90bb95c8868bd6eda6a9/node_modules/clanka/dist/OutputFormatter.js
202165
+ //#region node_modules/.pnpm/clanka@0.0.17_@effect+ai-openai-compat@4.0.0-beta.30_effect@4.0.0-beta.30__@effect+ai-o_aaa5a0e42657b29782e5d1fe76ccda57/node_modules/clanka/dist/OutputFormatter.js
201912
202166
  /**
201913
202167
  * @since 1.0.0
201914
202168
  */
@@ -201952,6 +202206,11 @@ const doneIcon = "";
201952
202206
  //#endregion
201953
202207
  //#region src/TaskTools.ts
201954
202208
  var ChosenTaskDeferred = class extends Reference("lalph/TaskTools/ChosenTaskDeferred", { defaultValue: makeUnsafe$13 }) {};
202209
+ var CurrentTaskRef = class CurrentTaskRef extends Service$1()("lalph/TaskTools/CurrentTaskRef") {
202210
+ static update(f) {
202211
+ return serviceOption(CurrentTaskRef).pipe(map$8(map$15((ref) => updateAndGet(ref, f))));
202212
+ }
202213
+ };
201955
202214
  const TaskList = Array$1(Struct({
201956
202215
  id: String$1.annotate({ documentation: "The unique identifier of the task." }),
201957
202216
  ...pick(PrdIssue.fields, [
@@ -201959,7 +202218,6 @@ const TaskList = Array$1(Struct({
201959
202218
  "description",
201960
202219
  "state",
201961
202220
  "priority",
201962
- "estimate",
201963
202221
  "blockedBy"
201964
202222
  ])
201965
202223
  }));
@@ -201974,7 +202232,6 @@ var TaskTools = class extends make$9(make$7("listTasks", {
201974
202232
  description: PrdIssue.fields.description,
201975
202233
  state: PrdIssue.fields.state,
201976
202234
  priority: PrdIssue.fields.priority,
201977
- estimate: PrdIssue.fields.estimate,
201978
202235
  blockedBy: PrdIssue.fields.blockedBy
201979
202236
  }),
201980
202237
  success: String$1,
@@ -202017,7 +202274,6 @@ const TaskToolsHandlers = TaskToolsWithChoose.toLayer(gen(function* () {
202017
202274
  description: issue.description,
202018
202275
  state: issue.state,
202019
202276
  priority: issue.priority,
202020
- estimate: issue.estimate,
202021
202277
  blockedBy: issue.blockedBy
202022
202278
  }));
202023
202279
  }, orDie$2),
@@ -202030,7 +202286,6 @@ const TaskToolsHandlers = TaskToolsWithChoose.toLayer(gen(function* () {
202030
202286
  description: issue.description,
202031
202287
  state: issue.state,
202032
202288
  priority: issue.priority,
202033
- estimate: issue.estimate,
202034
202289
  blockedBy: issue.blockedBy
202035
202290
  }));
202036
202291
  }, orDie$2),
@@ -202044,12 +202299,14 @@ const TaskToolsHandlers = TaskToolsWithChoose.toLayer(gen(function* () {
202044
202299
  return (yield* source.createIssue(projectId, new PrdIssue({
202045
202300
  ...options,
202046
202301
  id: null,
202302
+ estimate: null,
202047
202303
  autoMerge: false
202048
202304
  }))).id;
202049
202305
  }, orDie$2),
202050
202306
  updateTask: fn("TaskTools.updateTask")(function* (options) {
202051
202307
  yield* log$1(`Calling "updateTask"`).pipe(annotateLogs({ taskId: options.taskId }));
202052
202308
  const projectId = yield* CurrentProjectId;
202309
+ yield* CurrentTaskRef.update((prev) => prev.update(options));
202053
202310
  yield* source.updateIssue({
202054
202311
  projectId,
202055
202312
  issueId: options.taskId,
@@ -202111,10 +202368,15 @@ const runClanka = fnUntraced(
202111
202368
  tools: options.withChoose ? TaskToolsWithChoose : TaskTools,
202112
202369
  subagentModel: clankaSubagent(models, options.model)
202113
202370
  }).pipe(provide$1(models.get(options.model)));
202114
- return yield* (options.stallTimeout ? withStallTimeout(options.stallTimeout)(agent.output) : agent.output).pipe(pretty, runForEachArray((out) => {
202371
+ let stream = options.stallTimeout ? withStallTimeout(options.stallTimeout)(agent.output) : agent.output;
202372
+ if (options.steer) yield* options.steer.pipe(switchMap(fnUntraced(function* (message) {
202373
+ yield* log$1(`Received steer message: ${message}`);
202374
+ yield* agent.steer(message);
202375
+ }, fromEffectDrain)), runDrain, forkScoped);
202376
+ return yield* stream.pipe(pretty, runForEachArray((out) => {
202115
202377
  for (const item of out) process.stdout.write(item);
202116
202378
  return void_$1;
202117
- }), (_) => _);
202379
+ }));
202118
202380
  },
202119
202381
  scoped$1,
202120
202382
  provide$1([layerServices, TaskToolsHandlers])
@@ -202130,8 +202392,9 @@ const agentWorker = fnUntraced(function* (options) {
202130
202392
  model: options.preset.extraArgs.join(" "),
202131
202393
  system: options.system,
202132
202394
  prompt: options.prompt,
202133
- stallTimeout: options.stallTimeout
202134
- });
202395
+ stallTimeout: options.stallTimeout,
202396
+ steer: options.steer
202397
+ }).pipe(options.taskRef ? provideService$2(CurrentTaskRef, options.taskRef) : identity);
202135
202398
  return ExitCode(0);
202136
202399
  }
202137
202400
  return yield* pipe(options.preset.cliAgent.command({
@@ -202446,10 +202709,16 @@ const run = fnUntraced(function* (options) {
202446
202709
  githubPrNumber: chosenTask.githubPrNumber ?? void 0,
202447
202710
  gitFlow
202448
202711
  });
202712
+ const issueRef = make$63(chosenTask.prd.update({ state: "in-progress" }));
202713
+ const steer = yield* taskUpdateSteer({
202714
+ issueId: taskId,
202715
+ current: issueRef
202716
+ });
202449
202717
  yield* log$1(`Agent exited with code: ${yield* agentWorker({
202450
202718
  stallTimeout: options.stallTimeout,
202451
202719
  preset: taskPreset,
202452
- prompt: instructions
202720
+ prompt: instructions,
202721
+ steer
202453
202722
  }).pipe(catchStallInReview, withSpan("Main.agentWorker"))}`);
202454
202723
  if (options.review) {
202455
202724
  registry.update(currentWorker.state, (s) => s.transitionTo(WorkerStatus.Reviewing({ issueId: taskId })));
@@ -202576,6 +202845,19 @@ const watchTaskState = fnUntraced(function* (options) {
202576
202845
  }));
202577
202846
  }), withSpan("Main.watchTaskState"));
202578
202847
  });
202848
+ const taskUpdateSteer = fnUntraced(function* (options) {
202849
+ return toStreamResult(yield* AtomRegistry, currentIssuesAtom(yield* CurrentProjectId)).pipe(drop(1), retry$1(forever$1), orDie, filterMap$2((issues) => {
202850
+ const issue = issues.find((entry) => entry.id === options.issueId);
202851
+ if (!issue) return failVoid;
202852
+ if (!issue.isChangedComparedTo(options.current.current)) return failVoid;
202853
+ set$9(options.current, issue);
202854
+ return succeed$8(`The task has been updated by the user. Here is the latest information:
202855
+
202856
+ # ${issue.title}
202857
+
202858
+ ${issue.description}`);
202859
+ }));
202860
+ });
202579
202861
  //#endregion
202580
202862
  //#region src/Agents/planner.ts
202581
202863
  const agentPlanner = fnUntraced(function* (options) {
@@ -202675,7 +202957,12 @@ var Editor = class extends Service$1()("lalph/Editor", { make: gen(function* ()
202675
202957
  const content = (yield* fs.readFileString(file)).trim();
202676
202958
  if (content === initialContent) return yield* new NoSuchElementError();
202677
202959
  return content;
202678
- }, scoped$1, provideService$2(ChildProcessSpawner, spawner), option$1)
202960
+ }, scoped$1, provideService$2(ChildProcessSpawner, spawner), option$1),
202961
+ saveTemp: fnUntraced(function* (content, options) {
202962
+ const file = yield* fs.makeTempFile({ suffix: options.suffix ?? ".txt" });
202963
+ yield* fs.writeFileString(file, content);
202964
+ return file;
202965
+ })
202679
202966
  };
202680
202967
  }) }) {
202681
202968
  static layer = effect$1(this, this.make).pipe(provide$3(PlatformServices));
@@ -202692,6 +202979,10 @@ const commandPlan = make$46("plan", {
202692
202979
  onSuccess: (path) => fs.readFileString(path).pipe(asSome)
202693
202980
  });
202694
202981
  if (isNone(thePlan)) return;
202982
+ yield* addFinalizer((exit) => {
202983
+ if (isSuccess$3(exit)) return void_$1;
202984
+ return pipe(editor.saveTemp(thePlan.value, { suffix: ".md" }), flatMap$4((file) => log$1(`Saved your plan to: ${file}`)), ignore$1);
202985
+ });
202695
202986
  yield* gen(function* () {
202696
202987
  const project = withNewProject ? yield* addOrUpdateProject() : yield* selectProject;
202697
202988
  const { specsDirectory } = yield* commandRoot;
@@ -202708,7 +202999,7 @@ const commandPlan = make$46("plan", {
202708
202999
  CurrentIssueSource.layer,
202709
203000
  ClankaModels.layer
202710
203001
  ]));
202711
- }, provide$1(Editor.layer))), withSubcommands([commandPlanTasks]));
203002
+ }, scoped$1, provide$1(Editor.layer))), withSubcommands([commandPlanTasks]));
202712
203003
  const plan = fnUntraced(function* (options) {
202713
203004
  const fs = yield* FileSystem;
202714
203005
  const pathService = yield* Path$1;
@@ -202785,12 +203076,18 @@ const FrontMatterSchema = toCodecJson(Struct({
202785
203076
  autoMerge: Boolean$2
202786
203077
  }));
202787
203078
  const handler$1 = flow(withHandler(fnUntraced(function* () {
202788
- const content = yield* (yield* Editor).editTemp({
203079
+ const editor = yield* Editor;
203080
+ const content = yield* editor.editTemp({
202789
203081
  suffix: ".md",
202790
203082
  initialContent: issueTemplate
202791
203083
  });
202792
203084
  if (isNone(content)) return;
202793
- const lines = content.value.split("\n");
203085
+ const contentValue = content.value.trim();
203086
+ yield* addFinalizer((exit) => {
203087
+ if (isSuccess$3(exit)) return void_$1;
203088
+ return pipe(editor.saveTemp(contentValue, { suffix: ".md" }), flatMap$4((file) => log$1(`Saved your issue to: ${file}`)), ignore$1);
203089
+ });
203090
+ const lines = contentValue.split("\n");
202794
203091
  const yamlLines = [];
202795
203092
  let descriptionStartIndex = 0;
202796
203093
  for (let i = 0; i < lines.length; i++) {
@@ -202818,7 +203115,7 @@ const handler$1 = flow(withHandler(fnUntraced(function* () {
202818
203115
  console.log(`Created issue with ID: ${created.id}`);
202819
203116
  console.log(`URL: ${created.url}`);
202820
203117
  }).pipe(provide$1([layerProjectIdPrompt, CurrentIssueSource.layer]));
202821
- })), provide(Editor.layer));
203118
+ }, scoped$1)), provide(Editor.layer));
202822
203119
  const commandIssue = make$46("issue").pipe(withDescription("Create a new issue in your editor."), withAlias("i"), handler$1);
202823
203120
  //#endregion
202824
203121
  //#region src/commands/edit.ts
@@ -202832,7 +203129,7 @@ const commandEdit = make$46("edit").pipe(withDescription("Open the selected proj
202832
203129
  const commandSource = make$46("source").pipe(withDescription("Select the issue source to use (e.g. GitHub Issues or Linear). This applies to all projects."), withHandler(() => selectIssueSource), provide(Settings.layer));
202833
203130
  //#endregion
202834
203131
  //#region package.json
202835
- var version = "0.3.46";
203132
+ var version = "0.3.47";
202836
203133
  //#endregion
202837
203134
  //#region src/commands/projects/ls.ts
202838
203135
  const commandProjectsLs = make$46("ls").pipe(withDescription("List configured projects and how they run (enabled state, concurrency, branch, git flow, review agent)."), withHandler(fnUntraced(function* () {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.3.46",
4
+ "version": "0.3.47",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -30,7 +30,7 @@
30
30
  "@octokit/plugin-rest-endpoint-methods": "^17.0.0",
31
31
  "@octokit/types": "^16.0.0",
32
32
  "@typescript/native-preview": "7.0.0-dev.20260310.1",
33
- "clanka": "^0.0.16",
33
+ "clanka": "^0.0.17",
34
34
  "concurrently": "^9.2.1",
35
35
  "effect": "4.0.0-beta.30",
36
36
  "husky": "^9.1.7",
@@ -1,15 +1,18 @@
1
- import { Duration, Effect, Path, pipe } from "effect"
1
+ import { Duration, Effect, identity, Path, pipe, Stream } 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
5
  import { runClanka } from "../Clanka.ts"
6
6
  import { ExitCode } from "effect/unstable/process/ChildProcessSpawner"
7
+ import { CurrentTaskRef } from "../TaskTools.ts"
7
8
 
8
9
  export const agentWorker = Effect.fnUntraced(function* (options: {
9
10
  readonly stallTimeout: Duration.Duration
10
11
  readonly preset: CliAgentPreset
11
12
  readonly system?: string
12
13
  readonly prompt: string
14
+ readonly steer?: Stream.Stream<string>
15
+ readonly taskRef?: CurrentTaskRef["Service"]
13
16
  }) {
14
17
  const pathService = yield* Path.Path
15
18
  const worktree = yield* Worktree
@@ -22,7 +25,12 @@ export const agentWorker = Effect.fnUntraced(function* (options: {
22
25
  system: options.system,
23
26
  prompt: options.prompt,
24
27
  stallTimeout: options.stallTimeout,
25
- })
28
+ steer: options.steer,
29
+ }).pipe(
30
+ options.taskRef
31
+ ? Effect.provideService(CurrentTaskRef, options.taskRef)
32
+ : identity,
33
+ )
26
34
  return ExitCode(0)
27
35
  }
28
36
 
package/src/Clanka.ts CHANGED
@@ -7,8 +7,6 @@ import {
7
7
  } from "./TaskTools.ts"
8
8
  import { ClankaModels, clankaSubagent } from "./ClankaModels.ts"
9
9
  import { withStallTimeout } from "./shared/stream.ts"
10
- import type { AiError } from "effect/unstable/ai"
11
- import type { RunnerStalled } from "./domain/Errors.ts"
12
10
 
13
11
  export const runClanka = Effect.fnUntraced(
14
12
  /** The working directory to run the agent in */
@@ -18,6 +16,7 @@ export const runClanka = Effect.fnUntraced(
18
16
  readonly prompt: string
19
17
  readonly system?: string | undefined
20
18
  readonly stallTimeout?: Duration.Input | undefined
19
+ readonly steer?: Stream.Stream<string> | undefined
21
20
  readonly withChoose?: boolean | undefined
22
21
  }) {
23
22
  const models = yield* ClankaModels
@@ -33,6 +32,19 @@ export const runClanka = Effect.fnUntraced(
33
32
  ? withStallTimeout(options.stallTimeout)(agent.output)
34
33
  : agent.output
35
34
 
35
+ if (options.steer) {
36
+ yield* options.steer.pipe(
37
+ Stream.switchMap(
38
+ Effect.fnUntraced(function* (message) {
39
+ yield* Effect.log(`Received steer message: ${message}`)
40
+ yield* agent.steer(message)
41
+ }, Stream.fromEffectDrain),
42
+ ),
43
+ Stream.runDrain,
44
+ Effect.forkScoped,
45
+ )
46
+ }
47
+
36
48
  return yield* stream.pipe(
37
49
  OutputFormatter.pretty,
38
50
  Stream.runForEachArray((out) => {
@@ -41,7 +53,6 @@ export const runClanka = Effect.fnUntraced(
41
53
  }
42
54
  return Effect.void
43
55
  }),
44
- (_) => _ as Effect.Effect<void, AiError.AiError | RunnerStalled>,
45
56
  )
46
57
  },
47
58
  Effect.scoped,
package/src/Editor.ts CHANGED
@@ -57,7 +57,18 @@ export class Editor extends ServiceMap.Service<Editor>()("lalph/Editor", {
57
57
  Effect.option,
58
58
  )
59
59
 
60
- return { edit, editTemp } as const
60
+ const saveTemp = Effect.fnUntraced(function* (
61
+ content: string,
62
+ options: { suffix?: string },
63
+ ) {
64
+ const file = yield* fs.makeTempFile({
65
+ suffix: options.suffix ?? ".txt",
66
+ })
67
+ yield* fs.writeFileString(file, content)
68
+ return file
69
+ })
70
+
71
+ return { edit, editTemp, saveTemp } as const
61
72
  }),
62
73
  }) {
63
74
  static layer = Layer.effect(this, this.make).pipe(
package/src/TaskTools.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { Deferred, Effect, Schema, ServiceMap, Struct } from "effect"
1
+ import {
2
+ Deferred,
3
+ Effect,
4
+ MutableRef,
5
+ Option,
6
+ Schema,
7
+ ServiceMap,
8
+ Struct,
9
+ } from "effect"
2
10
  import { Tool, Toolkit } from "effect/unstable/ai"
3
11
  import { PrdIssue } from "./domain/PrdIssue.ts"
4
12
  import { IssueSource } from "./IssueSource.ts"
@@ -14,6 +22,17 @@ export class ChosenTaskDeferred extends ServiceMap.Reference(
14
22
  },
15
23
  ) {}
16
24
 
25
+ export class CurrentTaskRef extends ServiceMap.Service<
26
+ CurrentTaskRef,
27
+ MutableRef.MutableRef<PrdIssue>
28
+ >()("lalph/TaskTools/CurrentTaskRef") {
29
+ static update(f: (prev: PrdIssue) => PrdIssue) {
30
+ return Effect.serviceOption(CurrentTaskRef).pipe(
31
+ Effect.map(Option.map((ref) => MutableRef.updateAndGet(ref, f))),
32
+ )
33
+ }
34
+ }
35
+
17
36
  const TaskList = Schema.Array(
18
37
  Schema.Struct({
19
38
  id: Schema.String.annotate({
@@ -24,7 +43,6 @@ const TaskList = Schema.Array(
24
43
  "description",
25
44
  "state",
26
45
  "priority",
27
- "estimate",
28
46
  "blockedBy",
29
47
  ]),
30
48
  }),
@@ -43,7 +61,6 @@ export class TaskTools extends Toolkit.make(
43
61
  description: PrdIssue.fields.description,
44
62
  state: PrdIssue.fields.state,
45
63
  priority: PrdIssue.fields.priority,
46
- estimate: PrdIssue.fields.estimate,
47
64
  blockedBy: PrdIssue.fields.blockedBy,
48
65
  }),
49
66
  success: Schema.String,
@@ -102,7 +119,6 @@ export const TaskToolsHandlers = TaskToolsWithChoose.toLayer(
102
119
  description: issue.description,
103
120
  state: issue.state,
104
121
  priority: issue.priority,
105
- estimate: issue.estimate,
106
122
  blockedBy: issue.blockedBy,
107
123
  }))
108
124
  }, Effect.orDie),
@@ -118,7 +134,6 @@ export const TaskToolsHandlers = TaskToolsWithChoose.toLayer(
118
134
  description: issue.description,
119
135
  state: issue.state,
120
136
  priority: issue.priority,
121
- estimate: issue.estimate,
122
137
  blockedBy: issue.blockedBy,
123
138
  }))
124
139
  }, Effect.orDie),
@@ -137,6 +152,7 @@ export const TaskToolsHandlers = TaskToolsWithChoose.toLayer(
137
152
  new PrdIssue({
138
153
  ...options,
139
154
  id: null,
155
+ estimate: null,
140
156
  autoMerge: false,
141
157
  }),
142
158
  )
@@ -147,6 +163,7 @@ export const TaskToolsHandlers = TaskToolsWithChoose.toLayer(
147
163
  Effect.annotateLogs({ taskId: options.taskId }),
148
164
  )
149
165
  const projectId = yield* CurrentProjectId
166
+ yield* CurrentTaskRef.update((prev) => prev.update(options))
150
167
  yield* source.updateIssue({
151
168
  projectId,
152
169
  issueId: options.taskId,
@@ -1,6 +1,6 @@
1
1
  import { Command } from "effect/unstable/cli"
2
2
  import { CurrentIssueSource } from "../CurrentIssueSource.ts"
3
- import { Effect, flow, Option, Schema } from "effect"
3
+ import { Effect, Exit, flow, Option, pipe, Schema } from "effect"
4
4
  import { IssueSource } from "../IssueSource.ts"
5
5
  import { PrdIssue } from "../domain/PrdIssue.ts"
6
6
  import * as Yaml from "yaml"
@@ -40,8 +40,18 @@ const handler = flow(
40
40
  if (Option.isNone(content)) {
41
41
  return
42
42
  }
43
+ const contentValue = content.value.trim()
43
44
 
44
- const lines = content.value.split("\n")
45
+ yield* Effect.addFinalizer((exit) => {
46
+ if (Exit.isSuccess(exit)) return Effect.void
47
+ return pipe(
48
+ editor.saveTemp(contentValue, { suffix: ".md" }),
49
+ Effect.flatMap((file) => Effect.log(`Saved your issue to: ${file}`)),
50
+ Effect.ignore,
51
+ )
52
+ })
53
+
54
+ const lines = contentValue.split("\n")
45
55
  const yamlLines: string[] = []
46
56
  let descriptionStartIndex = 0
47
57
  for (let i = 0; i < lines.length; i++) {
@@ -81,7 +91,7 @@ const handler = flow(
81
91
  console.log(`Created issue with ID: ${created.id}`)
82
92
  console.log(`URL: ${created.url}`)
83
93
  }).pipe(Effect.provide([layerProjectIdPrompt, CurrentIssueSource.layer]))
84
- }),
94
+ }, Effect.scoped),
85
95
  ),
86
96
  Command.provide(Editor.layer),
87
97
  )
@@ -1,4 +1,13 @@
1
- import { Data, Effect, FileSystem, Option, Path, pipe, Schema } from "effect"
1
+ import {
2
+ Data,
3
+ Effect,
4
+ Exit,
5
+ FileSystem,
6
+ Option,
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 { Worktree } from "../Worktree.ts"
@@ -48,41 +57,54 @@ export const commandPlan = Command.make("plan", {
48
57
  "Draft a plan in your editor (or use --file); then generate a specification under --specs and create PRD tasks from it. Use --new to create a project first, and --dangerous to skip permission prompts during spec generation.",
49
58
  ),
50
59
  Command.withHandler(
51
- Effect.fnUntraced(function* ({ dangerous, withNewProject, file }) {
52
- const editor = yield* Editor
53
- const fs = yield* FileSystem.FileSystem
54
-
55
- const thePlan = yield* Effect.matchEffect(file.asEffect(), {
56
- onFailure: () => editor.editTemp({ suffix: ".md" }),
57
- onSuccess: (path) => fs.readFileString(path).pipe(Effect.asSome),
58
- })
59
-
60
- if (Option.isNone(thePlan)) return
61
-
62
- // We nest this effect, so we can launch the editor first as fast as
63
- // possible
64
- yield* Effect.gen(function* () {
65
- const project = withNewProject
66
- ? yield* addOrUpdateProject()
67
- : yield* selectProject
68
- const { specsDirectory } = yield* commandRoot
69
- const preset = yield* selectCliAgentPreset
70
-
71
- yield* plan({
72
- plan: thePlan.value,
73
- specsDirectory,
74
- targetBranch: project.targetBranch,
75
- dangerous,
76
- preset,
77
- }).pipe(Effect.provideService(CurrentProjectId, project.id))
78
- }).pipe(
79
- Effect.provide([
80
- Settings.layer,
81
- CurrentIssueSource.layer,
82
- ClankaModels.layer,
83
- ]),
84
- )
85
- }, Effect.provide(Editor.layer)),
60
+ Effect.fnUntraced(
61
+ function* ({ dangerous, withNewProject, file }) {
62
+ const editor = yield* Editor
63
+ const fs = yield* FileSystem.FileSystem
64
+
65
+ const thePlan = yield* Effect.matchEffect(file.asEffect(), {
66
+ onFailure: () => editor.editTemp({ suffix: ".md" }),
67
+ onSuccess: (path) => fs.readFileString(path).pipe(Effect.asSome),
68
+ })
69
+
70
+ if (Option.isNone(thePlan)) return
71
+
72
+ yield* Effect.addFinalizer((exit) => {
73
+ if (Exit.isSuccess(exit)) return Effect.void
74
+ return pipe(
75
+ editor.saveTemp(thePlan.value, { suffix: ".md" }),
76
+ Effect.flatMap((file) => Effect.log(`Saved your plan to: ${file}`)),
77
+ Effect.ignore,
78
+ )
79
+ })
80
+
81
+ // We nest this effect, so we can launch the editor first as fast as
82
+ // possible
83
+ yield* Effect.gen(function* () {
84
+ const project = withNewProject
85
+ ? yield* addOrUpdateProject()
86
+ : yield* selectProject
87
+ const { specsDirectory } = yield* commandRoot
88
+ const preset = yield* selectCliAgentPreset
89
+
90
+ yield* plan({
91
+ plan: thePlan.value,
92
+ specsDirectory,
93
+ targetBranch: project.targetBranch,
94
+ dangerous,
95
+ preset,
96
+ }).pipe(Effect.provideService(CurrentProjectId, project.id))
97
+ }).pipe(
98
+ Effect.provide([
99
+ Settings.layer,
100
+ CurrentIssueSource.layer,
101
+ ClankaModels.layer,
102
+ ]),
103
+ )
104
+ },
105
+ Effect.scoped,
106
+ Effect.provide(Editor.layer),
107
+ ),
86
108
  ),
87
109
  Command.withSubcommands([commandPlanTasks]),
88
110
  )
@@ -6,9 +6,12 @@ import {
6
6
  FiberSet,
7
7
  FileSystem,
8
8
  Iterable,
9
+ MutableRef,
9
10
  Option,
10
11
  Path,
11
12
  PlatformError,
13
+ Result,
14
+ Schedule,
12
15
  Schema,
13
16
  Scope,
14
17
  Semaphore,
@@ -48,6 +51,7 @@ import type { TimeoutError } from "effect/Cause"
48
51
  import type { ChildProcessSpawner } from "effect/unstable/process"
49
52
  import { ClankaModels } from "../ClankaModels.ts"
50
53
  import type { AiError } from "effect/unstable/ai/AiError"
54
+ import type { PrdIssue } from "../domain/PrdIssue.ts"
51
55
 
52
56
  // Main iteration run logic
53
57
 
@@ -217,10 +221,21 @@ const run = Effect.fnUntraced(
217
221
  gitFlow,
218
222
  })
219
223
 
224
+ const issueRef = MutableRef.make(
225
+ chosenTask.prd.update({
226
+ state: "in-progress",
227
+ }),
228
+ )
229
+ const steer = yield* taskUpdateSteer({
230
+ issueId: taskId,
231
+ current: issueRef,
232
+ })
233
+
220
234
  const exitCode = yield* agentWorker({
221
235
  stallTimeout: options.stallTimeout,
222
236
  preset: taskPreset,
223
237
  prompt: instructions,
238
+ steer,
224
239
  }).pipe(catchStallInReview, Effect.withSpan("Main.agentWorker"))
225
240
  yield* Effect.log(`Agent exited with code: ${exitCode}`)
226
241
 
@@ -515,3 +530,33 @@ const watchTaskState = Effect.fnUntraced(function* (options: {
515
530
  Effect.withSpan("Main.watchTaskState"),
516
531
  )
517
532
  })
533
+
534
+ const taskUpdateSteer = Effect.fnUntraced(function* (options: {
535
+ readonly issueId: string
536
+ readonly current: MutableRef.MutableRef<PrdIssue>
537
+ }) {
538
+ const registry = yield* AtomRegistry.AtomRegistry
539
+ const projectId = yield* CurrentProjectId
540
+
541
+ return AtomRegistry.toStreamResult(
542
+ registry,
543
+ currentIssuesAtom(projectId),
544
+ ).pipe(
545
+ Stream.drop(1),
546
+ Stream.retry(Schedule.forever),
547
+ Stream.orDie,
548
+ Stream.filterMap((issues) => {
549
+ const issue = issues.find((entry) => entry.id === options.issueId)
550
+ if (!issue) return Result.failVoid
551
+ if (!issue.isChangedComparedTo(options.current.current)) {
552
+ return Result.failVoid
553
+ }
554
+ MutableRef.set(options.current, issue)
555
+ return Result.succeed(`The task has been updated by the user. Here is the latest information:
556
+
557
+ # ${issue.title}
558
+
559
+ ${issue.description}`)
560
+ }),
561
+ )
562
+ })
@@ -105,4 +105,19 @@ export class PrdIssue extends Schema.Class<PrdIssue>("PrdIssue")({
105
105
  autoMerge,
106
106
  })
107
107
  }
108
+
109
+ update(options: {
110
+ readonly title?: string | undefined
111
+ readonly description?: string | undefined
112
+ readonly state?: PrdIssue["state"] | undefined
113
+ readonly blockedBy?: ReadonlyArray<string> | undefined
114
+ }): PrdIssue {
115
+ return new PrdIssue({
116
+ ...this,
117
+ title: options.title ?? this.title,
118
+ description: options.description ?? this.description,
119
+ state: options.state ?? this.state,
120
+ blockedBy: options.blockedBy ?? this.blockedBy,
121
+ })
122
+ }
108
123
  }