@qotaq/lalphgram 0.1.6 → 0.1.8
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/cjs/Main.js +7 -4
- package/dist/cjs/Main.js.map +1 -1
- package/dist/cjs/lib/StreamJsonParser.js +4 -3
- package/dist/cjs/lib/StreamJsonParser.js.map +1 -1
- package/dist/cjs/services/ChatMachine.js +9 -2
- package/dist/cjs/services/ChatMachine.js.map +1 -1
- package/dist/cjs/services/EventLoop.js +3 -1
- package/dist/cjs/services/EventLoop.js.map +1 -1
- package/dist/cjs/services/LalphConfig.js +18 -1
- package/dist/cjs/services/LalphConfig.js.map +1 -1
- package/dist/cjs/services/LinearSdkClient.js +13 -5
- package/dist/cjs/services/LinearSdkClient.js.map +1 -1
- package/dist/cjs/services/PlanSession.js +45 -6
- package/dist/cjs/services/PlanSession.js.map +1 -1
- package/dist/cjs/services/TaskTracker/GitHubIssueTracker.js +7 -3
- package/dist/cjs/services/TaskTracker/GitHubIssueTracker.js.map +1 -1
- package/dist/cjs/services/TaskTracker/LinearTracker.js +5 -1
- package/dist/cjs/services/TaskTracker/LinearTracker.js.map +1 -1
- package/dist/cjs/shim/main.js +4 -1
- package/dist/cjs/shim/main.js.map +1 -1
- package/dist/dts/lib/StreamJsonParser.d.ts +2 -1
- package/dist/dts/lib/StreamJsonParser.d.ts.map +1 -1
- package/dist/dts/services/AutoMerge.d.ts +1 -1
- package/dist/dts/services/ChatMachine.d.ts +1 -1
- package/dist/dts/services/ChatMachine.d.ts.map +1 -1
- package/dist/dts/services/EventLoop.d.ts +2 -2
- package/dist/dts/services/EventLoop.d.ts.map +1 -1
- package/dist/dts/services/LalphConfig.d.ts +1 -0
- package/dist/dts/services/LalphConfig.d.ts.map +1 -1
- package/dist/dts/services/LinearSdkClient.d.ts +1 -0
- package/dist/dts/services/LinearSdkClient.d.ts.map +1 -1
- package/dist/dts/services/PlanOverviewUploaderMap.d.ts +1 -1
- package/dist/dts/services/PlanSession.d.ts.map +1 -1
- package/dist/dts/services/PullRequestTracker.d.ts +1 -1
- package/dist/dts/services/TaskTracker/GitHubIssueTracker.d.ts +2 -1
- package/dist/dts/services/TaskTracker/GitHubIssueTracker.d.ts.map +1 -1
- package/dist/dts/services/TaskTracker/LinearTracker.d.ts +2 -1
- package/dist/dts/services/TaskTracker/LinearTracker.d.ts.map +1 -1
- package/dist/dts/services/TrackerLayerMap.d.ts +1 -1
- package/dist/dts/shim/main.d.ts +1 -1
- package/dist/dts/shim/main.d.ts.map +1 -1
- package/dist/esm/Main.js +7 -4
- package/dist/esm/Main.js.map +1 -1
- package/dist/esm/lib/StreamJsonParser.js +3 -2
- package/dist/esm/lib/StreamJsonParser.js.map +1 -1
- package/dist/esm/services/ChatMachine.js +9 -2
- package/dist/esm/services/ChatMachine.js.map +1 -1
- package/dist/esm/services/EventLoop.js +3 -1
- package/dist/esm/services/EventLoop.js.map +1 -1
- package/dist/esm/services/LalphConfig.js +19 -2
- package/dist/esm/services/LalphConfig.js.map +1 -1
- package/dist/esm/services/LinearSdkClient.js +13 -5
- package/dist/esm/services/LinearSdkClient.js.map +1 -1
- package/dist/esm/services/PlanSession.js +45 -6
- package/dist/esm/services/PlanSession.js.map +1 -1
- package/dist/esm/services/TaskTracker/GitHubIssueTracker.js +7 -3
- package/dist/esm/services/TaskTracker/GitHubIssueTracker.js.map +1 -1
- package/dist/esm/services/TaskTracker/LinearTracker.js +5 -1
- package/dist/esm/services/TaskTracker/LinearTracker.js.map +1 -1
- package/dist/esm/shim/main.js +4 -1
- package/dist/esm/shim/main.js.map +1 -1
- package/package.json +1 -1
- package/src/Main.ts +14 -10
- package/src/lib/StreamJsonParser.ts +6 -2
- package/src/services/ChatMachine.ts +11 -3
- package/src/services/EventLoop.ts +6 -4
- package/src/services/LalphConfig.ts +37 -2
- package/src/services/LinearSdkClient.ts +6 -1
- package/src/services/PlanSession.ts +60 -7
- package/src/services/TaskTracker/GitHubIssueTracker.ts +10 -3
- package/src/services/TaskTracker/LinearTracker.ts +4 -1
- package/src/shim/main.ts +4 -1
package/src/Main.ts
CHANGED
|
@@ -74,18 +74,22 @@ const lalphNotifyCommand = CliCommand.make(
|
|
|
74
74
|
Effect.map((s) => s.trim())
|
|
75
75
|
)
|
|
76
76
|
|
|
77
|
-
// Resolve
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Effect.map((s) => s.trim())
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
// Resolve the SDK-based shim entry point relative to this file
|
|
84
|
-
const shimMainTs = pathService.join(
|
|
77
|
+
// Resolve the SDK-based shim entry point relative to this file.
|
|
78
|
+
// In dev mode (tsx), the file is bin.ts; when built, it's bin.js.
|
|
79
|
+
const shimDir_ = pathService.join(
|
|
85
80
|
pathService.dirname(fileURLToPath(import.meta.url)),
|
|
86
|
-
"shim"
|
|
87
|
-
"bin.ts"
|
|
81
|
+
"shim"
|
|
88
82
|
)
|
|
83
|
+
const shimJsExists = yield* fs.exists(pathService.join(shimDir_, "bin.js"))
|
|
84
|
+
const shimMainTs = pathService.join(shimDir_, shimJsExists ? "bin.js" : "bin.ts")
|
|
85
|
+
|
|
86
|
+
// Use node for compiled JS, tsx for TypeScript source
|
|
87
|
+
const tsxPath = shimJsExists
|
|
88
|
+
? process.execPath
|
|
89
|
+
: yield* PlatformCommand.make("which", "tsx").pipe(
|
|
90
|
+
PlatformCommand.string,
|
|
91
|
+
Effect.map((s) => s.trim())
|
|
92
|
+
)
|
|
89
93
|
|
|
90
94
|
yield* Effect.log("Resolved shim paths").pipe(
|
|
91
95
|
Effect.annotateLogs({ realClaudePath, tsxPath, shimMainTs })
|
|
@@ -74,7 +74,7 @@ export class StreamJsonInput extends Schema.Class<StreamJsonInput>("StreamJsonIn
|
|
|
74
74
|
parent_tool_use_id: Schema.NullOr(Schema.String)
|
|
75
75
|
}) {}
|
|
76
76
|
|
|
77
|
-
const decodeJsonMessage = Schema.decodeUnknown(Schema.parseJson(StreamJsonMessage))
|
|
77
|
+
export const decodeJsonMessage = Schema.decodeUnknown(Schema.parseJson(StreamJsonMessage))
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Splits a string stream into lines and parses each as a StreamJsonMessage,
|
|
@@ -89,7 +89,11 @@ export const parseNdjsonMessages = flow(
|
|
|
89
89
|
decodeJsonMessage(line).pipe(
|
|
90
90
|
Effect.tapError((err) =>
|
|
91
91
|
Effect.logDebug("Non-JSON stdout line, skipping").pipe(
|
|
92
|
-
Effect.annotateLogs({
|
|
92
|
+
Effect.annotateLogs({
|
|
93
|
+
line: line.slice(0, 300),
|
|
94
|
+
lineBytes: Array.from(line.slice(0, 100), (c) => c.charCodeAt(0).toString(16)).join(" "),
|
|
95
|
+
error: err.message
|
|
96
|
+
})
|
|
93
97
|
)
|
|
94
98
|
),
|
|
95
99
|
Effect.option
|
|
@@ -302,6 +302,15 @@ export const chatMachine = Machine.make(
|
|
|
302
302
|
analysisFollowUpSent: boolean
|
|
303
303
|
): Effect.Effect<ChatState, never, never> =>
|
|
304
304
|
Effect.gen(function*() {
|
|
305
|
+
yield* Effect.log("checkAllReady").pipe(
|
|
306
|
+
Effect.annotateLogs({
|
|
307
|
+
spec: String(flags.spec),
|
|
308
|
+
analysis: String(flags.analysis),
|
|
309
|
+
idle: String(flags.idle),
|
|
310
|
+
planType,
|
|
311
|
+
analysisFollowUpSent: String(analysisFollowUpSent)
|
|
312
|
+
})
|
|
313
|
+
)
|
|
305
314
|
if (!flags.spec || !flags.analysis || !flags.idle) {
|
|
306
315
|
return ChatState.SessionRunning({
|
|
307
316
|
projectId,
|
|
@@ -660,11 +669,10 @@ export const chatMachine = Machine.make(
|
|
|
660
669
|
case "SpecReady": {
|
|
661
670
|
if (text === APPROVE_BUTTON_LABEL) {
|
|
662
671
|
yield* Effect.log("User approved task creation")
|
|
663
|
-
yield* planSession.approve
|
|
664
|
-
|
|
672
|
+
yield* planSession.approve
|
|
673
|
+
yield* notifier.sendMessage({ text: "Spec approved.", replyKeyboard: IDLE_KEYBOARD }).pipe(
|
|
665
674
|
Effect.orElseSucceed(() => undefined)
|
|
666
675
|
)
|
|
667
|
-
yield* notifier.sendMessage({ text: "Spec approved.", replyKeyboard: IDLE_KEYBOARD })
|
|
668
676
|
return reply(ChatState.Idle())
|
|
669
677
|
}
|
|
670
678
|
if (text === ABORT_BUTTON_LABEL) {
|
|
@@ -87,10 +87,12 @@ const autoMergeLayer = AutoMergeLive.pipe(
|
|
|
87
87
|
Layer.provide(lalphConfigLayer)
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
const eventSourcesLayer =
|
|
91
|
-
Layer.
|
|
92
|
-
|
|
93
|
-
)
|
|
90
|
+
const eventSourcesLayer = process.env["MOCK_GITHUB"] === "1"
|
|
91
|
+
? Layer.succeed(PullRequestTracker, { eventStream: Stream.never })
|
|
92
|
+
: PullRequestTrackerLive.pipe(
|
|
93
|
+
Layer.provide(servicesLayer),
|
|
94
|
+
Layer.provide(lalphConfigLayer)
|
|
95
|
+
)
|
|
94
96
|
|
|
95
97
|
const branchParserLayer = BranchParserLive
|
|
96
98
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @since 1.0.0
|
|
4
4
|
*/
|
|
5
5
|
import { Command, FileSystem, Path } from "@effect/platform"
|
|
6
|
-
import { Context, Data, Effect, Layer, Ref, Schema, Stream } from "effect"
|
|
6
|
+
import { Array as Arr, Context, Data, Effect, Layer, Ref, Schema, Stream } from "effect"
|
|
7
7
|
import { LalphGithubToken, LalphLinearToken } from "../schemas/CredentialSchemas.js"
|
|
8
8
|
import { AppContext } from "./AppContext.js"
|
|
9
9
|
|
|
@@ -43,6 +43,7 @@ export interface LalphConfigService {
|
|
|
43
43
|
readonly issueSource: "linear" | "github"
|
|
44
44
|
readonly specUploader: "gist" | "telegraph"
|
|
45
45
|
readonly repoFullName: string
|
|
46
|
+
readonly linearProjectIds: ReadonlyArray<string>
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/**
|
|
@@ -160,6 +161,39 @@ export const LalphConfigLive = Layer.scoped(
|
|
|
160
161
|
return parseRepoFullName(output.trim())
|
|
161
162
|
})
|
|
162
163
|
|
|
164
|
+
const ProjectEntry = Schema.Struct({ id: Schema.String, enabled: Schema.Boolean })
|
|
165
|
+
|
|
166
|
+
const linearProjectIds: ReadonlyArray<string> = issueSource === "linear"
|
|
167
|
+
? yield* Effect.gen(function*() {
|
|
168
|
+
const projectsJson = yield* readStringFile("settings.projects").pipe(
|
|
169
|
+
Effect.map((raw) => JSON.parse(raw)),
|
|
170
|
+
Effect.flatMap(Schema.decodeUnknown(Schema.Array(ProjectEntry))),
|
|
171
|
+
Effect.orElseSucceed((): ReadonlyArray<Schema.Schema.Type<typeof ProjectEntry>> => [])
|
|
172
|
+
)
|
|
173
|
+
const enabledIds = Arr.filter(projectsJson, (p) => p.enabled).map((p) => p.id)
|
|
174
|
+
const projectIds = yield* Effect.forEach(enabledIds, (id) => {
|
|
175
|
+
const filePath = pathService.join(
|
|
176
|
+
appContext.projectRoot,
|
|
177
|
+
".lalph",
|
|
178
|
+
"projects",
|
|
179
|
+
encodeURIComponent(id),
|
|
180
|
+
"linear.selectedProjectId"
|
|
181
|
+
)
|
|
182
|
+
return fs.readFileString(filePath).pipe(
|
|
183
|
+
Effect.flatMap((content) =>
|
|
184
|
+
Effect.try({
|
|
185
|
+
try: () => JSON.parse(content),
|
|
186
|
+
catch: (err) => err
|
|
187
|
+
})
|
|
188
|
+
),
|
|
189
|
+
Effect.flatMap(Schema.decodeUnknown(Schema.String)),
|
|
190
|
+
Effect.option
|
|
191
|
+
)
|
|
192
|
+
})
|
|
193
|
+
return Arr.filterMap(projectIds, (opt) => opt)
|
|
194
|
+
})
|
|
195
|
+
: []
|
|
196
|
+
|
|
163
197
|
const githubTokenRef = yield* Ref.make(githubTokenData.token)
|
|
164
198
|
const linearTokenRef = yield* Ref.make(linearAccessToken)
|
|
165
199
|
|
|
@@ -212,7 +246,8 @@ export const LalphConfigLive = Layer.scoped(
|
|
|
212
246
|
),
|
|
213
247
|
issueSource,
|
|
214
248
|
specUploader,
|
|
215
|
-
repoFullName
|
|
249
|
+
repoFullName,
|
|
250
|
+
linearProjectIds
|
|
216
251
|
})
|
|
217
252
|
})
|
|
218
253
|
)
|
|
@@ -46,6 +46,7 @@ export interface LinearSdkWorkflowState {
|
|
|
46
46
|
export interface LinearSdkClientService {
|
|
47
47
|
readonly listIssues: (params: {
|
|
48
48
|
readonly since: string
|
|
49
|
+
readonly projectIds?: ReadonlyArray<string>
|
|
49
50
|
}) => Effect.Effect<ReadonlyArray<LinearSdkIssue>, LinearSdkClientError>
|
|
50
51
|
readonly getIssue: (params: {
|
|
51
52
|
readonly id: string
|
|
@@ -99,7 +100,11 @@ export const LinearSdkClientLive = Layer.effect(
|
|
|
99
100
|
const c = yield* getClient
|
|
100
101
|
return yield* Effect.tryPromise({
|
|
101
102
|
try: async () => {
|
|
102
|
-
const
|
|
103
|
+
const filter: Record<string, unknown> = { updatedAt: { gte: new Date(params.since) } }
|
|
104
|
+
if (params.projectIds && params.projectIds.length > 0) {
|
|
105
|
+
filter.project = { id: { in: params.projectIds } }
|
|
106
|
+
}
|
|
107
|
+
const connection = await c.issues({ filter })
|
|
103
108
|
const results: Array<LinearSdkIssue> = []
|
|
104
109
|
for (const node of connection.nodes) {
|
|
105
110
|
const state = await node.state
|
|
@@ -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,
|
|
8
|
+
import { AskUserQuestionInput, decodeJsonMessage } from "../lib/StreamJsonParser.js"
|
|
9
9
|
import { AppContext } from "./AppContext.js"
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -98,10 +98,13 @@ export type PlanEvent =
|
|
|
98
98
|
| PlanAnalysisReady
|
|
99
99
|
| PlanAwaitingInput
|
|
100
100
|
|
|
101
|
+
const StdinEOF: unique symbol = Symbol.for("StdinEOF")
|
|
102
|
+
type StdinItem = Uint8Array | typeof StdinEOF
|
|
103
|
+
|
|
101
104
|
interface ActiveSession {
|
|
102
105
|
readonly process: CommandExecutor.Process
|
|
103
106
|
readonly scope: Scope.CloseableScope
|
|
104
|
-
readonly stdinQueue: Queue.Queue<
|
|
107
|
+
readonly stdinQueue: Queue.Queue<StdinItem>
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
/**
|
|
@@ -152,7 +155,7 @@ export class PlanCommandBuilder extends Context.Tag("PlanCommandBuilder")<
|
|
|
152
155
|
|
|
153
156
|
const stripAnsi = (text: string): string =>
|
|
154
157
|
// eslint-disable-next-line no-control-regex
|
|
155
|
-
text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "")
|
|
158
|
+
text.replace(/\x1b\[\??[0-9;]*[a-zA-Z]/g, "")
|
|
156
159
|
|
|
157
160
|
const WriteToolInput = Schema.Struct({ file_path: Schema.String })
|
|
158
161
|
const EditToolInput = Schema.Struct({ file_path: Schema.String })
|
|
@@ -242,14 +245,16 @@ export const PlanSessionLive = Layer.scoped(
|
|
|
242
245
|
)
|
|
243
246
|
)
|
|
244
247
|
|
|
245
|
-
const stdinQueue = yield* Queue.unbounded<
|
|
248
|
+
const stdinQueue = yield* Queue.unbounded<StdinItem>()
|
|
246
249
|
yield* Ref.set(sessionRef, Option.some({ process, scope: processScope, stdinQueue }))
|
|
247
250
|
yield* Effect.log("Plan session process spawned").pipe(
|
|
248
251
|
Effect.annotateLogs({ tempFile })
|
|
249
252
|
)
|
|
250
253
|
|
|
251
254
|
yield* Stream.fromQueue(stdinQueue).pipe(
|
|
255
|
+
Stream.takeWhile((item): item is Uint8Array => item !== StdinEOF),
|
|
252
256
|
Stream.run(process.stdin),
|
|
257
|
+
Effect.tap(() => Effect.log("stdin stream closed")),
|
|
253
258
|
Effect.catchAll((err) => Effect.logError(`stdin write error: ${String(err)}`)),
|
|
254
259
|
Effect.forkDaemon
|
|
255
260
|
)
|
|
@@ -266,6 +271,8 @@ export const PlanSessionLive = Layer.scoped(
|
|
|
266
271
|
const pendingTextRef = yield* Ref.make<Option.Option<{ messageId: string; text: string }>>(
|
|
267
272
|
Option.none()
|
|
268
273
|
)
|
|
274
|
+
const stage2Ref = yield* Ref.make(false)
|
|
275
|
+
const stage2BufferRef = yield* Ref.make<ReadonlyArray<string>>([])
|
|
269
276
|
|
|
270
277
|
const flushPendingText = Effect.gen(function*() {
|
|
271
278
|
const pending = yield* Ref.get(pendingTextRef)
|
|
@@ -278,6 +285,14 @@ export const PlanSessionLive = Layer.scoped(
|
|
|
278
285
|
}
|
|
279
286
|
})
|
|
280
287
|
|
|
288
|
+
const flushStage2Buffer = Effect.gen(function*() {
|
|
289
|
+
const buf = yield* Ref.get(stage2BufferRef)
|
|
290
|
+
if (buf.length > 0) {
|
|
291
|
+
yield* Queue.offer(eventQueue, new PlanTextOutput({ text: buf.join("\n") }))
|
|
292
|
+
yield* Ref.set(stage2BufferRef, [])
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
|
|
281
296
|
const detectFileEvent = (block: typeof ContentBlock.Type) =>
|
|
282
297
|
Effect.gen(function*() {
|
|
283
298
|
const filePathResult = yield* Effect.gen(function*() {
|
|
@@ -380,6 +395,7 @@ export const PlanSessionLive = Layer.scoped(
|
|
|
380
395
|
if (msg.type === "result") {
|
|
381
396
|
yield* flushPendingText
|
|
382
397
|
yield* Ref.set(idleRef, true)
|
|
398
|
+
yield* Ref.set(stage2Ref, true)
|
|
383
399
|
yield* Queue.offer(eventQueue, new PlanAwaitingInput({}))
|
|
384
400
|
yield* Effect.log("Planner result received")
|
|
385
401
|
return
|
|
@@ -398,9 +414,39 @@ export const PlanSessionLive = Layer.scoped(
|
|
|
398
414
|
Effect.annotateLogs({ chunkLength: String(chunk.length), preview: chunk.slice(0, 200) })
|
|
399
415
|
)
|
|
400
416
|
),
|
|
401
|
-
|
|
402
|
-
Stream.
|
|
417
|
+
Stream.map(stripAnsi),
|
|
418
|
+
Stream.map((text) => text.replace(/\r/g, "\n")),
|
|
419
|
+
Stream.splitLines,
|
|
420
|
+
Stream.filter((line) => line.trim().length > 0),
|
|
421
|
+
Stream.mapEffect((line) =>
|
|
422
|
+
Effect.gen(function*() {
|
|
423
|
+
const isStage2 = yield* Ref.get(stage2Ref)
|
|
424
|
+
const parsed = yield* decodeJsonMessage(line).pipe(
|
|
425
|
+
Effect.tapError((err) => {
|
|
426
|
+
if (isStage2) return Effect.void
|
|
427
|
+
return Effect.logDebug("Non-JSON stdout line, skipping").pipe(
|
|
428
|
+
Effect.annotateLogs({
|
|
429
|
+
line: line.slice(0, 300),
|
|
430
|
+
lineBytes: Array.from(line.slice(0, 100), (c) => c.charCodeAt(0).toString(16)).join(" "),
|
|
431
|
+
error: err.message
|
|
432
|
+
})
|
|
433
|
+
)
|
|
434
|
+
}),
|
|
435
|
+
Effect.option
|
|
436
|
+
)
|
|
437
|
+
if (Option.isSome(parsed)) {
|
|
438
|
+
yield* routeMessage(parsed.value)
|
|
439
|
+
} else if (isStage2) {
|
|
440
|
+
yield* Ref.update(stage2BufferRef, (buf) => [...buf, line])
|
|
441
|
+
const buf = yield* Ref.get(stage2BufferRef)
|
|
442
|
+
if (line.includes("\u2713") || line.includes("\u2717") || buf.length >= 20) {
|
|
443
|
+
yield* flushStage2Buffer
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
),
|
|
403
448
|
Stream.runDrain,
|
|
449
|
+
Effect.tap(() => flushStage2Buffer),
|
|
404
450
|
Effect.tap(() => flushPendingText),
|
|
405
451
|
Effect.tap(() => Effect.log("stdout stream completed")),
|
|
406
452
|
Effect.tapError((err) =>
|
|
@@ -437,6 +483,7 @@ export const PlanSessionLive = Layer.scoped(
|
|
|
437
483
|
)
|
|
438
484
|
|
|
439
485
|
yield* Effect.gen(function*() {
|
|
486
|
+
yield* Effect.log("Waiting for process exit code...")
|
|
440
487
|
const exitCode = yield* process.exitCode
|
|
441
488
|
yield* Effect.log("Plan session process exited").pipe(
|
|
442
489
|
Effect.annotateLogs({ exitCode: String(exitCode) })
|
|
@@ -517,7 +564,13 @@ export const PlanSessionLive = Layer.scoped(
|
|
|
517
564
|
}
|
|
518
565
|
const encoder = new TextEncoder()
|
|
519
566
|
yield* Ref.set(idleRef, false)
|
|
520
|
-
|
|
567
|
+
const payload = JSON.stringify({ type: "shim_approve" }) + "\n"
|
|
568
|
+
yield* Effect.log("Sending shim_approve to stdin").pipe(
|
|
569
|
+
Effect.annotateLogs({ payload: payload.trim() })
|
|
570
|
+
)
|
|
571
|
+
yield* Queue.offer(current.value.stdinQueue, encoder.encode(payload))
|
|
572
|
+
yield* Queue.offer(current.value.stdinQueue, StdinEOF)
|
|
573
|
+
yield* Effect.log("shim_approve + StdinEOF queued to stdin")
|
|
521
574
|
})
|
|
522
575
|
|
|
523
576
|
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
|
-
|
|
46
|
-
|
|
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: `${
|
|
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
|
-
|
|
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": {
|