@qotaq/lalphgram 0.1.7 → 0.1.9

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.
Files changed (68) hide show
  1. package/dist/cjs/lib/StreamJsonParser.js +2 -2
  2. package/dist/cjs/lib/StreamJsonParser.js.map +1 -1
  3. package/dist/cjs/services/ChatMachine.js +9 -2
  4. package/dist/cjs/services/ChatMachine.js.map +1 -1
  5. package/dist/cjs/services/EventLoop.js +7 -1
  6. package/dist/cjs/services/EventLoop.js.map +1 -1
  7. package/dist/cjs/services/LalphConfig.js +18 -1
  8. package/dist/cjs/services/LalphConfig.js.map +1 -1
  9. package/dist/cjs/services/LinearSdkClient.js +13 -5
  10. package/dist/cjs/services/LinearSdkClient.js.map +1 -1
  11. package/dist/cjs/services/PlanSession.js +39 -6
  12. package/dist/cjs/services/PlanSession.js.map +1 -1
  13. package/dist/cjs/services/TaskTracker/GitHubIssueTracker.js +7 -3
  14. package/dist/cjs/services/TaskTracker/GitHubIssueTracker.js.map +1 -1
  15. package/dist/cjs/services/TaskTracker/LinearTracker.js +5 -1
  16. package/dist/cjs/services/TaskTracker/LinearTracker.js.map +1 -1
  17. package/dist/cjs/shim/main.js +4 -1
  18. package/dist/cjs/shim/main.js.map +1 -1
  19. package/dist/dts/lib/StreamJsonParser.d.ts +2 -1
  20. package/dist/dts/lib/StreamJsonParser.d.ts.map +1 -1
  21. package/dist/dts/services/AutoMerge.d.ts +1 -1
  22. package/dist/dts/services/ChatMachine.d.ts +1 -1
  23. package/dist/dts/services/ChatMachine.d.ts.map +1 -1
  24. package/dist/dts/services/EventLoop.d.ts +2 -2
  25. package/dist/dts/services/EventLoop.d.ts.map +1 -1
  26. package/dist/dts/services/LalphConfig.d.ts +1 -0
  27. package/dist/dts/services/LalphConfig.d.ts.map +1 -1
  28. package/dist/dts/services/LinearSdkClient.d.ts +1 -0
  29. package/dist/dts/services/LinearSdkClient.d.ts.map +1 -1
  30. package/dist/dts/services/PlanOverviewUploaderMap.d.ts +1 -1
  31. package/dist/dts/services/PlanSession.d.ts +10 -1
  32. package/dist/dts/services/PlanSession.d.ts.map +1 -1
  33. package/dist/dts/services/PullRequestTracker.d.ts +1 -1
  34. package/dist/dts/services/TaskTracker/GitHubIssueTracker.d.ts +2 -1
  35. package/dist/dts/services/TaskTracker/GitHubIssueTracker.d.ts.map +1 -1
  36. package/dist/dts/services/TaskTracker/LinearTracker.d.ts +2 -1
  37. package/dist/dts/services/TaskTracker/LinearTracker.d.ts.map +1 -1
  38. package/dist/dts/services/TrackerLayerMap.d.ts +1 -1
  39. package/dist/dts/shim/main.d.ts +1 -1
  40. package/dist/dts/shim/main.d.ts.map +1 -1
  41. package/dist/esm/lib/StreamJsonParser.js +1 -1
  42. package/dist/esm/lib/StreamJsonParser.js.map +1 -1
  43. package/dist/esm/services/ChatMachine.js +9 -2
  44. package/dist/esm/services/ChatMachine.js.map +1 -1
  45. package/dist/esm/services/EventLoop.js +7 -1
  46. package/dist/esm/services/EventLoop.js.map +1 -1
  47. package/dist/esm/services/LalphConfig.js +19 -2
  48. package/dist/esm/services/LalphConfig.js.map +1 -1
  49. package/dist/esm/services/LinearSdkClient.js +13 -5
  50. package/dist/esm/services/LinearSdkClient.js.map +1 -1
  51. package/dist/esm/services/PlanSession.js +37 -5
  52. package/dist/esm/services/PlanSession.js.map +1 -1
  53. package/dist/esm/services/TaskTracker/GitHubIssueTracker.js +7 -3
  54. package/dist/esm/services/TaskTracker/GitHubIssueTracker.js.map +1 -1
  55. package/dist/esm/services/TaskTracker/LinearTracker.js +5 -1
  56. package/dist/esm/services/TaskTracker/LinearTracker.js.map +1 -1
  57. package/dist/esm/shim/main.js +4 -1
  58. package/dist/esm/shim/main.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/lib/StreamJsonParser.ts +1 -1
  61. package/src/services/ChatMachine.ts +11 -3
  62. package/src/services/EventLoop.ts +5 -0
  63. package/src/services/LalphConfig.ts +37 -2
  64. package/src/services/LinearSdkClient.ts +6 -1
  65. package/src/services/PlanSession.ts +52 -7
  66. package/src/services/TaskTracker/GitHubIssueTracker.ts +10 -3
  67. package/src/services/TaskTracker/LinearTracker.ts +4 -1
  68. package/src/shim/main.ts +4 -1
@@ -5,7 +5,7 @@
5
5
  import { Command, CommandExecutor, FileSystem, Path } from "@effect/platform"
6
6
  import { Context, Data, Effect, Exit, Layer, Option, Queue, Ref, Schema, Scope, Stream } from "effect"
7
7
  import type { ContentBlock, StreamJsonMessage } from "../lib/StreamJsonParser.js"
8
- import { AskUserQuestionInput, parseNdjsonMessages } from "../lib/StreamJsonParser.js"
8
+ import { AskUserQuestionInput, decodeJsonMessage } from "../lib/StreamJsonParser.js"
9
9
  import { AppContext } from "./AppContext.js"
10
10
 
11
11
  /**
@@ -84,6 +84,12 @@ export class PlanAnalysisReady extends Data.TaggedClass("PlanAnalysisReady")<{
84
84
  */
85
85
  export class PlanAwaitingInput extends Data.TaggedClass("PlanAwaitingInput")<Record<string, never>> {}
86
86
 
87
+ /**
88
+ * @since 1.0.0
89
+ * @category events
90
+ */
91
+ export class PlanTaskCreationStarted extends Data.TaggedClass("PlanTaskCreationStarted")<Record<string, never>> {}
92
+
87
93
  /**
88
94
  * @since 1.0.0
89
95
  * @category events
@@ -97,11 +103,15 @@ export type PlanEvent =
97
103
  | PlanSpecUpdated
98
104
  | PlanAnalysisReady
99
105
  | PlanAwaitingInput
106
+ | PlanTaskCreationStarted
107
+
108
+ const StdinEOF: unique symbol = Symbol.for("StdinEOF")
109
+ type StdinItem = Uint8Array | typeof StdinEOF
100
110
 
101
111
  interface ActiveSession {
102
112
  readonly process: CommandExecutor.Process
103
113
  readonly scope: Scope.CloseableScope
104
- readonly stdinQueue: Queue.Queue<Uint8Array>
114
+ readonly stdinQueue: Queue.Queue<StdinItem>
105
115
  }
106
116
 
107
117
  /**
@@ -242,14 +252,16 @@ export const PlanSessionLive = Layer.scoped(
242
252
  )
243
253
  )
244
254
 
245
- const stdinQueue = yield* Queue.unbounded<Uint8Array>()
255
+ const stdinQueue = yield* Queue.unbounded<StdinItem>()
246
256
  yield* Ref.set(sessionRef, Option.some({ process, scope: processScope, stdinQueue }))
247
257
  yield* Effect.log("Plan session process spawned").pipe(
248
258
  Effect.annotateLogs({ tempFile })
249
259
  )
250
260
 
251
261
  yield* Stream.fromQueue(stdinQueue).pipe(
262
+ Stream.takeWhile((item): item is Uint8Array => item !== StdinEOF),
252
263
  Stream.run(process.stdin),
264
+ Effect.tap(() => Effect.log("stdin stream closed")),
253
265
  Effect.catchAll((err) => Effect.logError(`stdin write error: ${String(err)}`)),
254
266
  Effect.forkDaemon
255
267
  )
@@ -266,7 +278,7 @@ export const PlanSessionLive = Layer.scoped(
266
278
  const pendingTextRef = yield* Ref.make<Option.Option<{ messageId: string; text: string }>>(
267
279
  Option.none()
268
280
  )
269
-
281
+ const stage2Ref = yield* Ref.make(false)
270
282
  const flushPendingText = Effect.gen(function*() {
271
283
  const pending = yield* Ref.get(pendingTextRef)
272
284
  if (Option.isSome(pending)) {
@@ -380,6 +392,7 @@ export const PlanSessionLive = Layer.scoped(
380
392
  if (msg.type === "result") {
381
393
  yield* flushPendingText
382
394
  yield* Ref.set(idleRef, true)
395
+ yield* Ref.set(stage2Ref, true)
383
396
  yield* Queue.offer(eventQueue, new PlanAwaitingInput({}))
384
397
  yield* Effect.log("Planner result received")
385
398
  return
@@ -400,8 +413,33 @@ export const PlanSessionLive = Layer.scoped(
400
413
  ),
401
414
  Stream.map(stripAnsi),
402
415
  Stream.map((text) => text.replace(/\r/g, "\n")),
403
- parseNdjsonMessages,
404
- Stream.mapEffect(routeMessage),
416
+ Stream.splitLines,
417
+ Stream.filter((line) => line.trim().length > 0),
418
+ Stream.mapEffect((line) =>
419
+ Effect.gen(function*() {
420
+ const isStage2 = yield* Ref.get(stage2Ref)
421
+ const parsed = yield* decodeJsonMessage(line).pipe(
422
+ Effect.tapError((err) => {
423
+ if (isStage2) return Effect.void
424
+ return Effect.logDebug("Non-JSON stdout line, skipping").pipe(
425
+ Effect.annotateLogs({
426
+ line: line.slice(0, 300),
427
+ lineBytes: Array.from(line.slice(0, 100), (c) => c.charCodeAt(0).toString(16)).join(" "),
428
+ error: err.message
429
+ })
430
+ )
431
+ }),
432
+ Effect.option
433
+ )
434
+ if (Option.isSome(parsed)) {
435
+ yield* routeMessage(parsed.value)
436
+ } else if (isStage2) {
437
+ if (line.includes("[Session started]")) {
438
+ yield* Queue.offer(eventQueue, new PlanTaskCreationStarted({}))
439
+ }
440
+ }
441
+ })
442
+ ),
405
443
  Stream.runDrain,
406
444
  Effect.tap(() => flushPendingText),
407
445
  Effect.tap(() => Effect.log("stdout stream completed")),
@@ -439,6 +477,7 @@ export const PlanSessionLive = Layer.scoped(
439
477
  )
440
478
 
441
479
  yield* Effect.gen(function*() {
480
+ yield* Effect.log("Waiting for process exit code...")
442
481
  const exitCode = yield* process.exitCode
443
482
  yield* Effect.log("Plan session process exited").pipe(
444
483
  Effect.annotateLogs({ exitCode: String(exitCode) })
@@ -519,7 +558,13 @@ export const PlanSessionLive = Layer.scoped(
519
558
  }
520
559
  const encoder = new TextEncoder()
521
560
  yield* Ref.set(idleRef, false)
522
- yield* Queue.offer(current.value.stdinQueue, encoder.encode(JSON.stringify({ type: "shim_approve" }) + "\n"))
561
+ const payload = JSON.stringify({ type: "shim_approve" }) + "\n"
562
+ yield* Effect.log("Sending shim_approve to stdin").pipe(
563
+ Effect.annotateLogs({ payload: payload.trim() })
564
+ )
565
+ yield* Queue.offer(current.value.stdinQueue, encoder.encode(payload))
566
+ yield* Queue.offer(current.value.stdinQueue, StdinEOF)
567
+ yield* Effect.log("shim_approve + StdinEOF queued to stdin")
523
568
  })
524
569
 
525
570
  const reject = Effect.gen(function*() {
@@ -7,6 +7,7 @@ import { TaskCreated, TaskUpdated } from "../../Events.js"
7
7
  import type { TaskTrackerEvent } from "../../Events.js"
8
8
  import { TrackerIssue, TrackerIssueEvent } from "../../schemas/TrackerSchemas.js"
9
9
  import { AppRuntimeConfig } from "../AppRuntimeConfig.js"
10
+ import { LalphConfig } from "../LalphConfig.js"
10
11
  import { OctokitClient } from "../OctokitClient.js"
11
12
  import { TaskTracker, TaskTrackerError } from "./TaskTracker.js"
12
13
 
@@ -29,7 +30,9 @@ export const GitHubIssueTrackerLive = Layer.effect(
29
30
  Effect.gen(function*() {
30
31
  const octokit = yield* OctokitClient
31
32
  const config = yield* AppRuntimeConfig
33
+ const lalphConfig = yield* LalphConfig
32
34
  const interval = Duration.seconds(config.pollIntervalSeconds)
35
+ const repoFullName = lalphConfig.repoFullName
33
36
 
34
37
  const fetchRecentEvents = (since: string) =>
35
38
  Effect.gen(function*() {
@@ -42,10 +45,14 @@ export const GitHubIssueTrackerLive = Layer.effect(
42
45
  new TaskTrackerError({ message: `GitHub API request failed: ${String(err)}`, cause: err })
43
46
  )
44
47
  )
45
- return Array.map(issues, (issue) => {
46
- const repoFullName = extractRepoFullName(issue.repositoryUrl)
48
+ const filteredIssues = Array.filter(
49
+ issues,
50
+ (issue) => extractRepoFullName(issue.repositoryUrl) === repoFullName
51
+ )
52
+ return Array.map(filteredIssues, (issue) => {
53
+ const issueRepoFullName = extractRepoFullName(issue.repositoryUrl)
47
54
  const trackerIssue = new TrackerIssue({
48
- id: `${repoFullName}#${issue.number}`,
55
+ id: `${issueRepoFullName}#${issue.number}`,
49
56
  title: issue.title,
50
57
  state: issue.state,
51
58
  url: issue.htmlUrl,
@@ -7,6 +7,7 @@ import { TaskCreated, TaskUpdated } from "../../Events.js"
7
7
  import type { TaskTrackerEvent } from "../../Events.js"
8
8
  import { TrackerIssue, TrackerIssueEvent } from "../../schemas/TrackerSchemas.js"
9
9
  import { AppRuntimeConfig } from "../AppRuntimeConfig.js"
10
+ import { LalphConfig } from "../LalphConfig.js"
10
11
  import { LinearSdkClient } from "../LinearSdkClient.js"
11
12
  import { TaskTracker, TaskTrackerError } from "./TaskTracker.js"
12
13
 
@@ -15,7 +16,9 @@ export const LinearTrackerLive = Layer.effect(
15
16
  Effect.gen(function*() {
16
17
  const linearClient = yield* LinearSdkClient
17
18
  const config = yield* AppRuntimeConfig
19
+ const lalphConfig = yield* LalphConfig
18
20
  const interval = Duration.seconds(config.pollIntervalSeconds)
21
+ const projectIds = lalphConfig.linearProjectIds
19
22
  const todoStateIdRef = yield* Ref.make<string | null>(null)
20
23
 
21
24
  const resolveTodoStateId = Effect.gen(function*() {
@@ -36,7 +39,7 @@ export const LinearTrackerLive = Layer.effect(
36
39
  })
37
40
 
38
41
  const fetchRecentEvents = (since: string) =>
39
- linearClient.listIssues({ since }).pipe(
42
+ linearClient.listIssues({ since, projectIds }).pipe(
40
43
  Effect.map((issues) =>
41
44
  issues.map((node) => {
42
45
  const issue = new TrackerIssue({
package/src/shim/main.ts CHANGED
@@ -162,7 +162,10 @@ export const shimProgram = Effect.gen(function*() {
162
162
  }
163
163
  case "shim_approve": {
164
164
  yield* writeDebug("shim_approve intercepted")
165
- yield* Queue.offer(followUpQueue, FollowUpStop)
165
+ // Exit immediately — scope cleanup hangs because the stdin
166
+ // reader fiber is blocked on a Node.js pipe read that
167
+ // doesn't respond to Effect's fiber interruption.
168
+ yield* Effect.sync(() => process.exit(0))
166
169
  return
167
170
  }
168
171
  case "shim_start": {