@p4code/cli 0.6.53 → 0.6.55
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/bin.mjs +1039 -72
- package/dist/client/assets/{DiffPanel-DIwMVc8O.js → DiffPanel-BYPQGCQT.js} +3 -3
- package/dist/client/assets/{FilePreviewPanel-BKJazNKZ.js → FilePreviewPanel-6b4o__bp.js} +14 -14
- package/dist/client/assets/PreviewPanel-HrbPS-5I.js +2 -0
- package/dist/client/assets/{PullRequestCodeTab-Db-f5nCl.js → PullRequestCodeTab-DMwCNuOz.js} +4 -4
- package/dist/client/assets/{fileCommentAnnotations-DNTigixo.js → fileCommentAnnotations-ClQq9bDS.js} +2 -2
- package/dist/client/assets/{index-5L471ah2.js → index-Cb4eQ3A4.js} +255 -244
- package/dist/client/assets/{index-o7sBVQed.css → index-DRONsSry.css} +1 -1
- package/dist/client/assets/previewAssetResource-6mhfd1nG.js +48 -0
- package/dist/client/assets/renderFileChildren-duImZqo2.js +2 -0
- package/dist/client/assets/toggle-group-DKavoKRh.js +2 -0
- package/dist/client/index.html +3 -3
- package/package.json +1 -1
- package/dist/client/assets/PreviewPanel-B6V5_zrn.js +0 -2
- package/dist/client/assets/previewAssetResource-CgRbTpIo.js +0 -48
- package/dist/client/assets/renderFileChildren-COEZ-8XH.js +0 -2
- package/dist/client/assets/toggle-group-DyK1qT7a.js +0 -2
package/dist/bin.mjs
CHANGED
|
@@ -263,7 +263,7 @@ const make$123 = () => {
|
|
|
263
263
|
const layer$124 = Layer.sync(NetService, make$123);
|
|
264
264
|
//#endregion
|
|
265
265
|
//#region package.json
|
|
266
|
-
var version$1 = "0.6.
|
|
266
|
+
var version$1 = "0.6.55";
|
|
267
267
|
//#endregion
|
|
268
268
|
//#region ../../packages/shared/src/serverInstance.ts
|
|
269
269
|
/**
|
|
@@ -3015,6 +3015,10 @@ const ExecutionEnvironmentCapabilities = Schema$1.Struct({
|
|
|
3015
3015
|
/** Server understands thread.scheduled-task.create / cancel and fires
|
|
3016
3016
|
pending tasks. Same version-skew contract as threadSettlement. */
|
|
3017
3017
|
threadScheduledTasks: Schema$1.optionalKey(Schema$1.Boolean),
|
|
3018
|
+
/** Server holds thread.turn.start with queueIfBusy until the running turn
|
|
3019
|
+
ends and understands thread.queued-message.send / remove. Same
|
|
3020
|
+
version-skew contract as threadSettlement. */
|
|
3021
|
+
threadMessageQueue: Schema$1.optionalKey(Schema$1.Boolean),
|
|
3018
3022
|
/** Server exposes the pull-request list, detail, activity, diff, and mutation APIs. Absent on
|
|
3019
3023
|
servers from before the pull-request workspace shipped, so clients must not probe them. */
|
|
3020
3024
|
pullRequests: Schema$1.optionalKey(Schema$1.Boolean),
|
|
@@ -3341,6 +3345,18 @@ const OrchestrationScheduledTask = Schema$1.Struct({
|
|
|
3341
3345
|
cancelledAt: Schema$1.NullOr(IsoDateTime).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
|
|
3342
3346
|
failure: Schema$1.NullOr(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed(null)))
|
|
3343
3347
|
});
|
|
3348
|
+
/**
|
|
3349
|
+
* A user message sent while the thread was busy, held until the running turn
|
|
3350
|
+
* ends. The server sends queued messages one per turn in order; steering one
|
|
3351
|
+
* sends it into the running turn right away, and removing it drops it.
|
|
3352
|
+
*/
|
|
3353
|
+
const OrchestrationQueuedMessage = Schema$1.Struct({
|
|
3354
|
+
messageId: MessageId,
|
|
3355
|
+
text: Schema$1.String,
|
|
3356
|
+
attachments: Schema$1.Array(ChatAttachment),
|
|
3357
|
+
modelSelection: Schema$1.optional(ModelSelection),
|
|
3358
|
+
createdAt: IsoDateTime
|
|
3359
|
+
});
|
|
3344
3360
|
const SourceProposedPlanReference = Schema$1.Struct({
|
|
3345
3361
|
threadId: ThreadId,
|
|
3346
3362
|
planId: OrchestrationProposedPlanId
|
|
@@ -3599,10 +3615,12 @@ const OrchestrationThread = Schema$1.Struct({
|
|
|
3599
3615
|
snoozedAt: Schema$1.optional(Schema$1.NullOr(IsoDateTime)),
|
|
3600
3616
|
pinnedAt: Schema$1.optional(Schema$1.NullOr(IsoDateTime)),
|
|
3601
3617
|
pinOrderKey: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
|
|
3618
|
+
parentThreadId: Schema$1.optional(Schema$1.NullOr(ThreadId)),
|
|
3602
3619
|
deletedAt: Schema$1.NullOr(IsoDateTime),
|
|
3603
3620
|
messages: Schema$1.Array(OrchestrationMessage),
|
|
3604
3621
|
proposedPlans: Schema$1.Array(OrchestrationProposedPlan).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
|
|
3605
3622
|
scheduledTasks: Schema$1.optional(Schema$1.Array(OrchestrationScheduledTask)),
|
|
3623
|
+
queuedMessages: Schema$1.optional(Schema$1.Array(OrchestrationQueuedMessage)),
|
|
3606
3624
|
activities: Schema$1.Array(OrchestrationThreadActivity),
|
|
3607
3625
|
checkpoints: Schema$1.Array(OrchestrationCheckpointSummary),
|
|
3608
3626
|
session: Schema$1.NullOr(OrchestrationSession)
|
|
@@ -3766,6 +3784,7 @@ const OrchestrationThreadShell = Schema$1.Struct({
|
|
|
3766
3784
|
snoozedAt: Schema$1.optional(Schema$1.NullOr(IsoDateTime)),
|
|
3767
3785
|
pinnedAt: Schema$1.optional(Schema$1.NullOr(IsoDateTime)),
|
|
3768
3786
|
pinOrderKey: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
|
|
3787
|
+
parentThreadId: Schema$1.optional(Schema$1.NullOr(ThreadId)),
|
|
3769
3788
|
session: Schema$1.NullOr(OrchestrationSession),
|
|
3770
3789
|
latestUserMessageAt: Schema$1.NullOr(IsoDateTime),
|
|
3771
3790
|
hasPendingApprovals: Schema$1.Boolean,
|
|
@@ -3937,6 +3956,8 @@ const ThreadCreateCommand = Schema$1.Struct({
|
|
|
3937
3956
|
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
3938
3957
|
branch: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
3939
3958
|
worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
3959
|
+
/** Links a sub-thread to the thread that spawned it. */
|
|
3960
|
+
parentThreadId: Schema$1.optional(ThreadId),
|
|
3940
3961
|
createdAt: IsoDateTime
|
|
3941
3962
|
});
|
|
3942
3963
|
/**
|
|
@@ -4145,6 +4166,8 @@ const ThreadTurnStartCommand = Schema$1.Struct({
|
|
|
4145
4166
|
activityId: EventId,
|
|
4146
4167
|
outcome: Schema$1.String
|
|
4147
4168
|
})),
|
|
4169
|
+
/** Hold the message until the running turn ends instead of steering into it. */
|
|
4170
|
+
queueIfBusy: Schema$1.optional(Schema$1.Boolean),
|
|
4148
4171
|
createdAt: IsoDateTime
|
|
4149
4172
|
});
|
|
4150
4173
|
const ClientThreadTurnStartCommand = Schema$1.Struct({
|
|
@@ -4165,6 +4188,22 @@ const ClientThreadTurnStartCommand = Schema$1.Struct({
|
|
|
4165
4188
|
unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
4166
4189
|
bootstrap: Schema$1.optional(ThreadTurnStartBootstrap),
|
|
4167
4190
|
sourceProposedPlan: Schema$1.optional(SourceProposedPlanReference),
|
|
4191
|
+
queueIfBusy: Schema$1.optional(Schema$1.Boolean),
|
|
4192
|
+
createdAt: IsoDateTime
|
|
4193
|
+
});
|
|
4194
|
+
/** Sends a queued message now: it steers into the running turn, or starts one when idle. */
|
|
4195
|
+
const ThreadQueuedMessageSendCommand = Schema$1.Struct({
|
|
4196
|
+
type: Schema$1.Literal("thread.queued-message.send"),
|
|
4197
|
+
commandId: CommandId,
|
|
4198
|
+
threadId: ThreadId,
|
|
4199
|
+
messageId: MessageId,
|
|
4200
|
+
createdAt: IsoDateTime
|
|
4201
|
+
});
|
|
4202
|
+
const ThreadQueuedMessageRemoveCommand = Schema$1.Struct({
|
|
4203
|
+
type: Schema$1.Literal("thread.queued-message.remove"),
|
|
4204
|
+
commandId: CommandId,
|
|
4205
|
+
threadId: ThreadId,
|
|
4206
|
+
messageId: MessageId,
|
|
4168
4207
|
createdAt: IsoDateTime
|
|
4169
4208
|
});
|
|
4170
4209
|
const ThreadTurnInterruptCommand = Schema$1.Struct({
|
|
@@ -4273,6 +4312,8 @@ const DispatchableClientOrchestrationCommand = Schema$1.Union([
|
|
|
4273
4312
|
ThreadPullRequestUnlinkCommand,
|
|
4274
4313
|
ThreadComputerUseGrantSetCommand,
|
|
4275
4314
|
ThreadTurnStartCommand,
|
|
4315
|
+
ThreadQueuedMessageSendCommand,
|
|
4316
|
+
ThreadQueuedMessageRemoveCommand,
|
|
4276
4317
|
ThreadTurnInterruptCommand,
|
|
4277
4318
|
ThreadApprovalRespondCommand,
|
|
4278
4319
|
ThreadUserInputRespondCommand,
|
|
@@ -4308,6 +4349,8 @@ const ClientOrchestrationCommand = Schema$1.Union([
|
|
|
4308
4349
|
ThreadPullRequestUnlinkCommand,
|
|
4309
4350
|
ThreadComputerUseGrantSetCommand,
|
|
4310
4351
|
ClientThreadTurnStartCommand,
|
|
4352
|
+
ThreadQueuedMessageSendCommand,
|
|
4353
|
+
ThreadQueuedMessageRemoveCommand,
|
|
4311
4354
|
ThreadTurnInterruptCommand,
|
|
4312
4355
|
ThreadApprovalRespondCommand,
|
|
4313
4356
|
ThreadUserInputRespondCommand,
|
|
@@ -4498,6 +4541,8 @@ const OrchestrationEventType = Schema$1.Literals([
|
|
|
4498
4541
|
"thread.pull-requests-updated",
|
|
4499
4542
|
"thread.computer-use-grant-set",
|
|
4500
4543
|
"thread.message-sent",
|
|
4544
|
+
"thread.message-queued",
|
|
4545
|
+
"thread.queued-message-removed",
|
|
4501
4546
|
"thread.turn-start-requested",
|
|
4502
4547
|
"thread.turn-interrupt-requested",
|
|
4503
4548
|
"thread.approval-response-requested",
|
|
@@ -4573,6 +4618,8 @@ const ThreadCreatedPayload$1 = Schema$1.Struct({
|
|
|
4573
4618
|
* messages into this one. Optional so events from pre-fork servers decode.
|
|
4574
4619
|
*/
|
|
4575
4620
|
forkedFromThreadId: Schema$1.optional(ThreadId),
|
|
4621
|
+
/** The spawning thread for a sub-thread. Optional so older events decode. */
|
|
4622
|
+
parentThreadId: Schema$1.optional(ThreadId),
|
|
4576
4623
|
createdAt: IsoDateTime,
|
|
4577
4624
|
updatedAt: IsoDateTime
|
|
4578
4625
|
});
|
|
@@ -4685,6 +4732,17 @@ const ThreadMessageSentPayload = Schema$1.Struct({
|
|
|
4685
4732
|
createdAt: IsoDateTime,
|
|
4686
4733
|
updatedAt: IsoDateTime
|
|
4687
4734
|
});
|
|
4735
|
+
const ThreadMessageQueuedPayload$1 = Schema$1.Struct({
|
|
4736
|
+
threadId: ThreadId,
|
|
4737
|
+
message: OrchestrationQueuedMessage
|
|
4738
|
+
});
|
|
4739
|
+
const ThreadQueuedMessageRemovedPayload$1 = Schema$1.Struct({
|
|
4740
|
+
threadId: ThreadId,
|
|
4741
|
+
messageId: MessageId,
|
|
4742
|
+
/** "sent" when it left the queue as a turn or steer, "removed" when dropped. */
|
|
4743
|
+
reason: Schema$1.Literals(["sent", "removed"]),
|
|
4744
|
+
removedAt: IsoDateTime
|
|
4745
|
+
});
|
|
4688
4746
|
const ThreadTurnStartRequestedPayload = Schema$1.Struct({
|
|
4689
4747
|
threadId: ThreadId,
|
|
4690
4748
|
messageId: MessageId,
|
|
@@ -4985,6 +5043,16 @@ const OrchestrationEvent = Schema$1.Union([
|
|
|
4985
5043
|
type: Schema$1.Literal("thread.message-sent"),
|
|
4986
5044
|
payload: ThreadMessageSentPayload
|
|
4987
5045
|
}),
|
|
5046
|
+
Schema$1.Struct({
|
|
5047
|
+
...EventBaseFields,
|
|
5048
|
+
type: Schema$1.Literal("thread.message-queued"),
|
|
5049
|
+
payload: ThreadMessageQueuedPayload$1
|
|
5050
|
+
}),
|
|
5051
|
+
Schema$1.Struct({
|
|
5052
|
+
...EventBaseFields,
|
|
5053
|
+
type: Schema$1.Literal("thread.queued-message-removed"),
|
|
5054
|
+
payload: ThreadQueuedMessageRemovedPayload$1
|
|
5055
|
+
}),
|
|
4988
5056
|
Schema$1.Struct({
|
|
4989
5057
|
...EventBaseFields,
|
|
4990
5058
|
type: Schema$1.Literal("thread.turn-start-requested"),
|
|
@@ -11212,6 +11280,7 @@ const ClientSettingsSchema = Schema$1.Struct({
|
|
|
11212
11280
|
composerControlsExpanded: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
11213
11281
|
confirmThreadArchive: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
|
|
11214
11282
|
confirmThreadDelete: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
11283
|
+
queueFollowUpMessages: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
11215
11284
|
dismissedProviderUpdateNotificationKeys: Schema$1.Array(TrimmedNonEmptyString).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
|
|
11216
11285
|
diffIgnoreWhitespace: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
|
|
11217
11286
|
computerUsePermissionHosts: ComputerUsePermissionHosts.pipe(Schema$1.withDecodingDefault(Effect.succeed({}))),
|
|
@@ -12005,6 +12074,7 @@ Schema$1.Struct({
|
|
|
12005
12074
|
composerControlsExpanded: Schema$1.optionalKey(Schema$1.Boolean),
|
|
12006
12075
|
confirmThreadArchive: Schema$1.optionalKey(Schema$1.Boolean),
|
|
12007
12076
|
confirmThreadDelete: Schema$1.optionalKey(Schema$1.Boolean),
|
|
12077
|
+
queueFollowUpMessages: Schema$1.optionalKey(Schema$1.Boolean),
|
|
12008
12078
|
diffIgnoreWhitespace: Schema$1.optionalKey(Schema$1.Boolean),
|
|
12009
12079
|
computerUsePermissionHosts: Schema$1.optionalKey(ComputerUsePermissionHosts),
|
|
12010
12080
|
environmentIdentificationMode: Schema$1.optionalKey(EnvironmentIdentificationMode),
|
|
@@ -13573,7 +13643,8 @@ const ThreadSpawnInput = Schema$1.Struct({
|
|
|
13573
13643
|
compressMode: Schema$1.optional(CompressMode),
|
|
13574
13644
|
unpromptedSubagents: Schema$1.optional(Schema$1.Boolean),
|
|
13575
13645
|
fusion: Schema$1.optional(Schema$1.Boolean.annotate({ description: "Create two new threads as a separate Fusion pair, then start the supervisor with prompt. The builder stays empty until advised. Leaves the caller and its existing pair unchanged. Cannot be combined with fusionWatcher." })),
|
|
13576
|
-
fusionWatcher: Schema$1.optional(Schema$1.Boolean.annotate({ description: "Set true only when creating the watcher to pair with the current thread. Defaults to the saved Fusion supervisor model. No special user command syntax is required." }))
|
|
13646
|
+
fusionWatcher: Schema$1.optional(Schema$1.Boolean.annotate({ description: "Set true only when creating the watcher to pair with the current thread. Defaults to the saved Fusion supervisor model. No special user command syntax is required." })),
|
|
13647
|
+
workspace: Schema$1.optional(Schema$1.Literals(["worktree", "shared"]).annotate({ description: "Where the sub-thread works. 'worktree' (the default for plain spawns in a git project) gives it its own git worktree and branch started from this thread's current HEAD commit, so parallel sub-threads never edit the same files; land it later with thread_merge_child. Commit first: uncommitted edits here are not visible to it. 'shared' runs it in this thread's checkout. Fusion spawns always share." }))
|
|
13577
13648
|
});
|
|
13578
13649
|
const ThreadSpawnResult = Schema$1.Struct({
|
|
13579
13650
|
/** Watchable with `thread_watch_events`, and addressable by every tool here. */
|
|
@@ -13581,7 +13652,71 @@ const ThreadSpawnResult = Schema$1.Struct({
|
|
|
13581
13652
|
projectId: ProjectId,
|
|
13582
13653
|
title: TrimmedNonEmptyString,
|
|
13583
13654
|
pairId: Schema$1.optional(ThreadPairId),
|
|
13584
|
-
watcherThreadId: Schema$1.optional(ThreadId)
|
|
13655
|
+
watcherThreadId: Schema$1.optional(ThreadId),
|
|
13656
|
+
/** Set when the sub-thread got its own worktree. */
|
|
13657
|
+
branch: Schema$1.optional(TrimmedNonEmptyString),
|
|
13658
|
+
worktreePath: Schema$1.optional(TrimmedNonEmptyString),
|
|
13659
|
+
baseCommitSha: Schema$1.optional(TrimmedNonEmptyString),
|
|
13660
|
+
/** This thread had uncommitted changes the sub-thread cannot see. */
|
|
13661
|
+
parentDirty: Schema$1.optional(Schema$1.Boolean)
|
|
13662
|
+
});
|
|
13663
|
+
const SubThreadState = Schema$1.Literals([
|
|
13664
|
+
"running",
|
|
13665
|
+
"needs-input",
|
|
13666
|
+
"completed",
|
|
13667
|
+
"failed",
|
|
13668
|
+
"missing"
|
|
13669
|
+
]);
|
|
13670
|
+
const ThreadWaitInput = Schema$1.Struct({
|
|
13671
|
+
threadIds: Schema$1.Array(ThreadId).check(Schema$1.isMinLength(1), Schema$1.isMaxLength(16)).annotate({ description: "Sub-threads to wait on, from thread_spawn. Up to 16." }),
|
|
13672
|
+
mode: Schema$1.optional(Schema$1.Literals(["all", "any"]).annotate({ description: "'all' (default) returns once every sub-thread has stopped running; 'any' returns as soon as one stops, so you can merge or answer it while the rest keep working." })),
|
|
13673
|
+
timeoutSeconds: Schema$1.optional(Schema$1.Int.check(Schema$1.isBetween({
|
|
13674
|
+
minimum: 1,
|
|
13675
|
+
maximum: 300
|
|
13676
|
+
})).annotate({ description: "Longest wait before returning the current states with timedOut true. Default 45. Call again to keep waiting." }))
|
|
13677
|
+
});
|
|
13678
|
+
const ThreadWaitChild = Schema$1.Struct({
|
|
13679
|
+
threadId: ThreadId,
|
|
13680
|
+
title: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
13681
|
+
/** running, needs-input (a question or approval waits), completed, failed, or missing. */
|
|
13682
|
+
state: SubThreadState,
|
|
13683
|
+
branch: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
13684
|
+
worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
|
|
13685
|
+
/** The latest reply, trimmed to 1500 characters. */
|
|
13686
|
+
lastAssistantMessage: Schema$1.NullOr(Schema$1.String),
|
|
13687
|
+
lastError: Schema$1.NullOr(Schema$1.String)
|
|
13688
|
+
});
|
|
13689
|
+
const ThreadWaitResult = Schema$1.Struct({
|
|
13690
|
+
timedOut: Schema$1.Boolean,
|
|
13691
|
+
children: Schema$1.Array(ThreadWaitChild)
|
|
13692
|
+
});
|
|
13693
|
+
const ThreadMessageInput = Schema$1.Struct({
|
|
13694
|
+
threadId: ThreadId.annotate({ description: "A sub-thread this thread spawned." }),
|
|
13695
|
+
text: TrimmedNonEmptyString.annotate({ description: "Message to send. If the sub-thread is still working it is queued and sent when its turn ends." })
|
|
13696
|
+
});
|
|
13697
|
+
const ThreadMessageResult = Schema$1.Struct({
|
|
13698
|
+
threadId: ThreadId,
|
|
13699
|
+
messageId: MessageId
|
|
13700
|
+
});
|
|
13701
|
+
const ThreadMergeChildInput = Schema$1.Struct({ threadId: ThreadId.annotate({ description: "A finished sub-thread whose branch should land in this thread's worktree." }) });
|
|
13702
|
+
const ThreadMergeChildResult = Schema$1.Struct({
|
|
13703
|
+
threadId: ThreadId,
|
|
13704
|
+
/**
|
|
13705
|
+
* merged: a merge commit landed. up-to-date: nothing new. conflicted: the
|
|
13706
|
+
* merge was aborted, this worktree is unchanged, and conflictedFiles lists
|
|
13707
|
+
* what the sub-thread must resolve. refused: nothing ran; see detail.
|
|
13708
|
+
*/
|
|
13709
|
+
outcome: Schema$1.Literals([
|
|
13710
|
+
"merged",
|
|
13711
|
+
"up-to-date",
|
|
13712
|
+
"conflicted",
|
|
13713
|
+
"refused"
|
|
13714
|
+
]),
|
|
13715
|
+
childBranch: Schema$1.optional(TrimmedNonEmptyString),
|
|
13716
|
+
parentBranch: Schema$1.optional(Schema$1.NullOr(TrimmedNonEmptyString)),
|
|
13717
|
+
mergeCommitSha: Schema$1.optional(TrimmedNonEmptyString),
|
|
13718
|
+
conflictedFiles: Schema$1.optional(Schema$1.Array(Schema$1.String)),
|
|
13719
|
+
detail: Schema$1.optional(Schema$1.String)
|
|
13585
13720
|
});
|
|
13586
13721
|
const ThreadPairCreateInput = Schema$1.Struct({
|
|
13587
13722
|
watcherThreadId: Schema$1.optional(ThreadId.annotate({ description: "Legacy pairing: use an existing spawned supervisor and keep this chat as builder. Omit to keep this chat and its history as supervisor and create a new empty builder, idle until advised." })),
|
|
@@ -18632,6 +18767,46 @@ var _062_ProviderResetCreditAttempts_default = Effect.gen(function* () {
|
|
|
18632
18767
|
WHERE outcome IS NULL OR outcome = 'failed'`;
|
|
18633
18768
|
});
|
|
18634
18769
|
//#endregion
|
|
18770
|
+
//#region src/persistence/Migrations/064_ProjectionThreadQueuedMessages.ts
|
|
18771
|
+
/**
|
|
18772
|
+
* User messages sent while a thread was busy, held until its turn ends. A row
|
|
18773
|
+
* exists only while the message waits; sending or removing it deletes the row.
|
|
18774
|
+
*/
|
|
18775
|
+
var _064_ProjectionThreadQueuedMessages_default = Effect.gen(function* () {
|
|
18776
|
+
const sql = yield* SqlClient.SqlClient;
|
|
18777
|
+
yield* sql`
|
|
18778
|
+
CREATE TABLE IF NOT EXISTS projection_thread_queued_messages (
|
|
18779
|
+
message_id TEXT PRIMARY KEY,
|
|
18780
|
+
thread_id TEXT NOT NULL,
|
|
18781
|
+
text TEXT NOT NULL,
|
|
18782
|
+
attachments_json TEXT NOT NULL,
|
|
18783
|
+
model_selection_json TEXT,
|
|
18784
|
+
created_at TEXT NOT NULL
|
|
18785
|
+
)
|
|
18786
|
+
`;
|
|
18787
|
+
yield* sql`
|
|
18788
|
+
CREATE INDEX IF NOT EXISTS idx_projection_thread_queued_messages_thread_created
|
|
18789
|
+
ON projection_thread_queued_messages(thread_id, created_at)
|
|
18790
|
+
`;
|
|
18791
|
+
});
|
|
18792
|
+
//#endregion
|
|
18793
|
+
//#region src/persistence/Migrations/065_ProjectionThreadsParentThread.ts
|
|
18794
|
+
/**
|
|
18795
|
+
* Links a sub-thread to the thread that spawned it, so a parent keeps control
|
|
18796
|
+
* of its children across restarts and clients can group them under it.
|
|
18797
|
+
*/
|
|
18798
|
+
var _065_ProjectionThreadsParentThread_default = Effect.gen(function* () {
|
|
18799
|
+
const sql = yield* SqlClient.SqlClient;
|
|
18800
|
+
yield* sql`
|
|
18801
|
+
ALTER TABLE projection_threads
|
|
18802
|
+
ADD COLUMN parent_thread_id TEXT
|
|
18803
|
+
`;
|
|
18804
|
+
yield* sql`
|
|
18805
|
+
CREATE INDEX IF NOT EXISTS idx_projection_threads_parent_thread
|
|
18806
|
+
ON projection_threads(parent_thread_id)
|
|
18807
|
+
`;
|
|
18808
|
+
});
|
|
18809
|
+
//#endregion
|
|
18635
18810
|
//#region src/persistence/Migrations/001_OrchestrationEvents.ts
|
|
18636
18811
|
var _001_OrchestrationEvents_default = Effect.gen(function* () {
|
|
18637
18812
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -20828,6 +21003,16 @@ const migrationEntries = [
|
|
|
20828
21003
|
63,
|
|
20829
21004
|
"RemoveThreadSettlement",
|
|
20830
21005
|
_063_RemoveThreadSettlement_default
|
|
21006
|
+
],
|
|
21007
|
+
[
|
|
21008
|
+
64,
|
|
21009
|
+
"ProjectionThreadQueuedMessages",
|
|
21010
|
+
_064_ProjectionThreadQueuedMessages_default
|
|
21011
|
+
],
|
|
21012
|
+
[
|
|
21013
|
+
65,
|
|
21014
|
+
"ProjectionThreadsParentThread",
|
|
21015
|
+
_065_ProjectionThreadsParentThread_default
|
|
20831
21016
|
]
|
|
20832
21017
|
];
|
|
20833
21018
|
const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -37223,6 +37408,8 @@ const ThreadUnpinnedPayload = ThreadUnpinnedPayload$1;
|
|
|
37223
37408
|
const ThreadSnoozedPayload = ThreadSnoozedPayload$1;
|
|
37224
37409
|
const ThreadUnsnoozedPayload = ThreadUnsnoozedPayload$1;
|
|
37225
37410
|
const MessageSentPayloadSchema = ThreadMessageSentPayload;
|
|
37411
|
+
const ThreadMessageQueuedPayload = ThreadMessageQueuedPayload$1;
|
|
37412
|
+
const ThreadQueuedMessageRemovedPayload = ThreadQueuedMessageRemovedPayload$1;
|
|
37226
37413
|
const ThreadProposedPlanUpsertedPayload = ThreadProposedPlanUpsertedPayload$1;
|
|
37227
37414
|
const ThreadScheduledTaskCreatedPayload = ThreadScheduledTaskCreatedPayload$1;
|
|
37228
37415
|
const ThreadScheduledTaskCancelledPayload = ThreadScheduledTaskCancelledPayload$1;
|
|
@@ -37440,6 +37627,7 @@ function projectEvent(model, event) {
|
|
|
37440
37627
|
computerUseHostId: payload.computerUseHostId ?? null,
|
|
37441
37628
|
branch: payload.branch,
|
|
37442
37629
|
worktreePath: payload.worktreePath,
|
|
37630
|
+
parentThreadId: payload.parentThreadId ?? null,
|
|
37443
37631
|
latestTurn: null,
|
|
37444
37632
|
createdAt: payload.createdAt,
|
|
37445
37633
|
updatedAt: payload.updatedAt,
|
|
@@ -37641,6 +37829,24 @@ function projectEvent(model, event) {
|
|
|
37641
37829
|
})
|
|
37642
37830
|
};
|
|
37643
37831
|
});
|
|
37832
|
+
case "thread.message-queued": return Effect.gen(function* () {
|
|
37833
|
+
const payload = yield* decodeForEvent(ThreadMessageQueuedPayload, event.payload, event.type, "payload");
|
|
37834
|
+
const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
|
|
37835
|
+
if (!thread) return nextBase;
|
|
37836
|
+
return {
|
|
37837
|
+
...nextBase,
|
|
37838
|
+
threads: updateThread(nextBase.threads, payload.threadId, { queuedMessages: [...(thread.queuedMessages ?? []).filter((entry) => entry.messageId !== payload.message.messageId), payload.message] })
|
|
37839
|
+
};
|
|
37840
|
+
});
|
|
37841
|
+
case "thread.queued-message-removed": return Effect.gen(function* () {
|
|
37842
|
+
const payload = yield* decodeForEvent(ThreadQueuedMessageRemovedPayload, event.payload, event.type, "payload");
|
|
37843
|
+
const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
|
|
37844
|
+
if (!thread) return nextBase;
|
|
37845
|
+
return {
|
|
37846
|
+
...nextBase,
|
|
37847
|
+
threads: updateThread(nextBase.threads, payload.threadId, { queuedMessages: (thread.queuedMessages ?? []).filter((entry) => entry.messageId !== payload.messageId) })
|
|
37848
|
+
};
|
|
37849
|
+
});
|
|
37644
37850
|
case "thread.scheduled-task.created": return Effect.gen(function* () {
|
|
37645
37851
|
const payload = yield* decodeForEvent(ThreadScheduledTaskCreatedPayload, event.payload, event.type, "payload");
|
|
37646
37852
|
const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
|
|
@@ -37788,7 +37994,7 @@ function archiveWorkspaceLifecycle(cleanup, updatedAt) {
|
|
|
37788
37994
|
function findActiveThreadPair(readModel, threadId) {
|
|
37789
37995
|
return (readModel.threadPairs ?? []).find((pair) => pair.detachedAt === null && (pair.implementerThreadId === threadId || pair.watcherThreadId === threadId));
|
|
37790
37996
|
}
|
|
37791
|
-
const QUEUED_TURN_START_GRACE_MS = 120 * 1e3;
|
|
37997
|
+
const QUEUED_TURN_START_GRACE_MS$1 = 120 * 1e3;
|
|
37792
37998
|
/**
|
|
37793
37999
|
* Blocked-on-you work derived from the thread's retained activities: an
|
|
37794
38000
|
* approval or user-input request with no later resolution for the same
|
|
@@ -37842,7 +38048,16 @@ function threadHasQueuedTurnStart(thread, occurredAt) {
|
|
|
37842
38048
|
thread.latestTurn.completedAt
|
|
37843
38049
|
].map((candidate) => candidate == null ? Number.NEGATIVE_INFINITY : Date.parse(candidate)));
|
|
37844
38050
|
const queuedAgeMs = Date.parse(occurredAt) - latestUserMessageAtMs;
|
|
37845
|
-
return thread.session?.status !== "error" && Number.isFinite(latestUserMessageAtMs) && latestUserMessageAtMs > latestTurnAtMs && Math.abs(queuedAgeMs) <= QUEUED_TURN_START_GRACE_MS;
|
|
38051
|
+
return thread.session?.status !== "error" && Number.isFinite(latestUserMessageAtMs) && latestUserMessageAtMs > latestTurnAtMs && Math.abs(queuedAgeMs) <= QUEUED_TURN_START_GRACE_MS$1;
|
|
38052
|
+
}
|
|
38053
|
+
/**
|
|
38054
|
+
* A thread is busy while a turn runs or one is about to: the session is
|
|
38055
|
+
* starting or running, or a sent message has not been adopted by a turn yet.
|
|
38056
|
+
* Queued messages wait for this to clear.
|
|
38057
|
+
*/
|
|
38058
|
+
function isThreadBusy(thread, occurredAt) {
|
|
38059
|
+
const status = thread.session?.status;
|
|
38060
|
+
return status === "running" || status === "starting" || threadHasQueuedTurnStart(thread, occurredAt);
|
|
37846
38061
|
}
|
|
37847
38062
|
function withEventBase(input) {
|
|
37848
38063
|
return Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4.pipe(Effect.map((eventId) => ({
|
|
@@ -38017,6 +38232,15 @@ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel
|
|
|
38017
38232
|
commandType: command.type,
|
|
38018
38233
|
detail: `Sidechat parent '${sidechatParent.id}' belongs to a different project.`
|
|
38019
38234
|
});
|
|
38235
|
+
const spawningParent = command.parentThreadId === void 0 ? null : yield* requireThreadNotArchived({
|
|
38236
|
+
readModel,
|
|
38237
|
+
command,
|
|
38238
|
+
threadId: command.parentThreadId
|
|
38239
|
+
});
|
|
38240
|
+
if (spawningParent && spawningParent.projectId !== command.projectId) return yield* new OrchestrationCommandInvariantError({
|
|
38241
|
+
commandType: command.type,
|
|
38242
|
+
detail: `Parent thread '${spawningParent.id}' belongs to a different project.`
|
|
38243
|
+
});
|
|
38020
38244
|
return {
|
|
38021
38245
|
...yield* withEventBase({
|
|
38022
38246
|
aggregateKind: "thread",
|
|
@@ -38037,6 +38261,7 @@ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel
|
|
|
38037
38261
|
computerUseHostId: sidechatParent ? null : command.computerUseHostId ?? null,
|
|
38038
38262
|
branch: sidechatParent ? sidechatParent.branch : command.branch,
|
|
38039
38263
|
worktreePath: sidechatParent ? sidechatParent.worktreePath : command.worktreePath,
|
|
38264
|
+
...spawningParent ? { parentThreadId: spawningParent.id } : {},
|
|
38040
38265
|
createdAt: command.createdAt,
|
|
38041
38266
|
updatedAt: command.createdAt
|
|
38042
38267
|
}
|
|
@@ -39013,6 +39238,25 @@ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel
|
|
|
39013
39238
|
commandType: command.type,
|
|
39014
39239
|
detail: budgetBlock
|
|
39015
39240
|
});
|
|
39241
|
+
if (command.queueIfBusy === true && isThreadBusy(targetThread, command.createdAt)) return {
|
|
39242
|
+
...yield* withEventBase({
|
|
39243
|
+
aggregateKind: "thread",
|
|
39244
|
+
aggregateId: command.threadId,
|
|
39245
|
+
occurredAt: command.createdAt,
|
|
39246
|
+
commandId: command.commandId
|
|
39247
|
+
}),
|
|
39248
|
+
type: "thread.message-queued",
|
|
39249
|
+
payload: {
|
|
39250
|
+
threadId: command.threadId,
|
|
39251
|
+
message: {
|
|
39252
|
+
messageId: command.message.messageId,
|
|
39253
|
+
text: command.message.text,
|
|
39254
|
+
attachments: command.message.attachments,
|
|
39255
|
+
...command.modelSelection !== void 0 ? { modelSelection: command.modelSelection } : {},
|
|
39256
|
+
createdAt: command.createdAt
|
|
39257
|
+
}
|
|
39258
|
+
}
|
|
39259
|
+
};
|
|
39016
39260
|
if (activePair?.watcherThreadId === command.threadId && targetThread.interactionMode !== "default") return yield* decideCommandSequence({
|
|
39017
39261
|
readModel,
|
|
39018
39262
|
commands: [{
|
|
@@ -39101,6 +39345,56 @@ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel
|
|
|
39101
39345
|
turnStartRequestedEvent
|
|
39102
39346
|
];
|
|
39103
39347
|
}
|
|
39348
|
+
case "thread.queued-message.send":
|
|
39349
|
+
case "thread.queued-message.remove": {
|
|
39350
|
+
const thread = yield* requireThread({
|
|
39351
|
+
readModel,
|
|
39352
|
+
command,
|
|
39353
|
+
threadId: command.threadId
|
|
39354
|
+
});
|
|
39355
|
+
const queued = (thread.queuedMessages ?? []).find((entry) => entry.messageId === command.messageId);
|
|
39356
|
+
if (queued === void 0) return yield* new OrchestrationCommandInvariantError({
|
|
39357
|
+
commandType: command.type,
|
|
39358
|
+
detail: `Message '${command.messageId}' is not queued on thread '${command.threadId}'.`
|
|
39359
|
+
});
|
|
39360
|
+
const removedEvent = {
|
|
39361
|
+
...yield* withEventBase({
|
|
39362
|
+
aggregateKind: "thread",
|
|
39363
|
+
aggregateId: command.threadId,
|
|
39364
|
+
occurredAt: command.createdAt,
|
|
39365
|
+
commandId: command.commandId
|
|
39366
|
+
}),
|
|
39367
|
+
type: "thread.queued-message-removed",
|
|
39368
|
+
payload: {
|
|
39369
|
+
threadId: command.threadId,
|
|
39370
|
+
messageId: command.messageId,
|
|
39371
|
+
reason: command.type === "thread.queued-message.send" ? "sent" : "removed",
|
|
39372
|
+
removedAt: command.createdAt
|
|
39373
|
+
}
|
|
39374
|
+
};
|
|
39375
|
+
if (command.type === "thread.queued-message.remove") return removedEvent;
|
|
39376
|
+
const turnStart = yield* decideCommand({
|
|
39377
|
+
readModel,
|
|
39378
|
+
command: {
|
|
39379
|
+
type: "thread.turn.start",
|
|
39380
|
+
commandId: command.commandId,
|
|
39381
|
+
threadId: command.threadId,
|
|
39382
|
+
message: {
|
|
39383
|
+
messageId: queued.messageId,
|
|
39384
|
+
role: "user",
|
|
39385
|
+
text: queued.text,
|
|
39386
|
+
attachments: queued.attachments
|
|
39387
|
+
},
|
|
39388
|
+
...queued.modelSelection !== void 0 ? { modelSelection: queued.modelSelection } : {},
|
|
39389
|
+
runtimeMode: thread.runtimeMode,
|
|
39390
|
+
interactionMode: thread.interactionMode,
|
|
39391
|
+
compressMode: thread.compressMode,
|
|
39392
|
+
unpromptedSubagents: thread.unpromptedSubagents,
|
|
39393
|
+
createdAt: command.createdAt
|
|
39394
|
+
}
|
|
39395
|
+
});
|
|
39396
|
+
return [removedEvent, ...Array.isArray(turnStart) ? turnStart : [turnStart]];
|
|
39397
|
+
}
|
|
39104
39398
|
case "thread.turn.interrupt":
|
|
39105
39399
|
yield* requireThread({
|
|
39106
39400
|
readModel,
|
|
@@ -39297,13 +39591,13 @@ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel
|
|
|
39297
39591
|
});
|
|
39298
39592
|
return events;
|
|
39299
39593
|
}
|
|
39300
|
-
case "thread.session.set":
|
|
39301
|
-
yield* requireThread({
|
|
39594
|
+
case "thread.session.set": {
|
|
39595
|
+
const thread = yield* requireThread({
|
|
39302
39596
|
readModel,
|
|
39303
39597
|
command,
|
|
39304
39598
|
threadId: command.threadId
|
|
39305
39599
|
});
|
|
39306
|
-
|
|
39600
|
+
const sessionSetEvent = {
|
|
39307
39601
|
...yield* withEventBase({
|
|
39308
39602
|
aggregateKind: "thread",
|
|
39309
39603
|
aggregateId: command.threadId,
|
|
@@ -39317,6 +39611,26 @@ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel
|
|
|
39317
39611
|
session: command.session
|
|
39318
39612
|
}
|
|
39319
39613
|
};
|
|
39614
|
+
const queuedHead = thread.queuedMessages?.[0];
|
|
39615
|
+
const previousStatus = thread.session?.status;
|
|
39616
|
+
if (queuedHead === void 0 || command.session.status !== "ready" && command.session.status !== "stopped" || previousStatus !== "running" && previousStatus !== "starting") return sessionSetEvent;
|
|
39617
|
+
const settledReadModel = yield* projectEvent(readModel, {
|
|
39618
|
+
...sessionSetEvent,
|
|
39619
|
+
sequence: readModel.snapshotSequence + 1
|
|
39620
|
+
}).pipe(Effect.orDie);
|
|
39621
|
+
const settledThread = settledReadModel.threads.find((entry) => entry.id === thread.id);
|
|
39622
|
+
if (settledThread === void 0 || isThreadBusy(settledThread, command.createdAt)) return sessionSetEvent;
|
|
39623
|
+
return [sessionSetEvent, ...yield* decideCommandSequence({
|
|
39624
|
+
readModel: settledReadModel,
|
|
39625
|
+
commands: [{
|
|
39626
|
+
type: "thread.queued-message.send",
|
|
39627
|
+
commandId: CommandId.make(`queued-message:${queuedHead.messageId}`),
|
|
39628
|
+
threadId: command.threadId,
|
|
39629
|
+
messageId: queuedHead.messageId,
|
|
39630
|
+
createdAt: command.createdAt
|
|
39631
|
+
}]
|
|
39632
|
+
}).pipe(Effect.catchTag("OrchestrationCommandInvariantError", () => Effect.succeed([])))];
|
|
39633
|
+
}
|
|
39320
39634
|
case "thread.message.assistant.delta":
|
|
39321
39635
|
yield* requireThread({
|
|
39322
39636
|
readModel,
|
|
@@ -39863,6 +40177,19 @@ const GetProjectionThreadScheduledTaskInput = Schema$1.Struct({ taskId: Orchestr
|
|
|
39863
40177
|
const ListProjectionThreadScheduledTasksInput = Schema$1.Struct({ threadId: ThreadId });
|
|
39864
40178
|
var ProjectionThreadScheduledTaskRepository = class extends Context.Service()("@p4code/cli/persistence/Services/ProjectionThreadScheduledTasks/ProjectionThreadScheduledTaskRepository") {};
|
|
39865
40179
|
//#endregion
|
|
40180
|
+
//#region src/persistence/Services/ProjectionThreadQueuedMessages.ts
|
|
40181
|
+
const ProjectionThreadQueuedMessage = Schema$1.Struct({
|
|
40182
|
+
messageId: MessageId,
|
|
40183
|
+
threadId: ThreadId,
|
|
40184
|
+
text: Schema$1.String,
|
|
40185
|
+
attachments: Schema$1.fromJsonString(Schema$1.Array(ChatAttachment)),
|
|
40186
|
+
modelSelection: Schema$1.NullOr(Schema$1.fromJsonString(ModelSelection)),
|
|
40187
|
+
createdAt: IsoDateTime
|
|
40188
|
+
});
|
|
40189
|
+
const DeleteProjectionThreadQueuedMessageInput = Schema$1.Struct({ messageId: MessageId });
|
|
40190
|
+
const DeleteProjectionThreadQueuedMessagesByThreadInput = Schema$1.Struct({ threadId: ThreadId });
|
|
40191
|
+
var ProjectionThreadQueuedMessageRepository = class extends Context.Service()("@p4code/cli/persistence/Services/ProjectionThreadQueuedMessages/ProjectionThreadQueuedMessageRepository") {};
|
|
40192
|
+
//#endregion
|
|
39866
40193
|
//#region src/persistence/Services/ProjectionThreadSessions.ts
|
|
39867
40194
|
/**
|
|
39868
40195
|
* ProjectionThreadSessionRepository - Repository interface for thread sessions.
|
|
@@ -39993,6 +40320,7 @@ const ProjectionThread = Schema$1.Struct({
|
|
|
39993
40320
|
snoozedAt: Schema$1.NullOr(IsoDateTime),
|
|
39994
40321
|
pinnedAt: Schema$1.NullOr(IsoDateTime),
|
|
39995
40322
|
pinOrderKey: Schema$1.NullOr(Schema$1.String),
|
|
40323
|
+
parentThreadId: Schema$1.optional(Schema$1.NullOr(ThreadId)),
|
|
39996
40324
|
latestUserMessageAt: Schema$1.NullOr(IsoDateTime),
|
|
39997
40325
|
pendingApprovalCount: NonNegativeInt,
|
|
39998
40326
|
pendingUserInputCount: NonNegativeInt,
|
|
@@ -40646,6 +40974,62 @@ const makeProjectionThreadScheduledTaskRepository = Effect.gen(function* () {
|
|
|
40646
40974
|
});
|
|
40647
40975
|
const ProjectionThreadScheduledTaskRepositoryLive = Layer.effect(ProjectionThreadScheduledTaskRepository, makeProjectionThreadScheduledTaskRepository);
|
|
40648
40976
|
//#endregion
|
|
40977
|
+
//#region src/persistence/Layers/ProjectionThreadQueuedMessages.ts
|
|
40978
|
+
const makeProjectionThreadQueuedMessageRepository = Effect.gen(function* () {
|
|
40979
|
+
const sql = yield* SqlClient.SqlClient;
|
|
40980
|
+
const upsertRow = SqlSchema.void({
|
|
40981
|
+
Request: ProjectionThreadQueuedMessage,
|
|
40982
|
+
execute: (row) => sql`
|
|
40983
|
+
INSERT INTO projection_thread_queued_messages (
|
|
40984
|
+
message_id,
|
|
40985
|
+
thread_id,
|
|
40986
|
+
text,
|
|
40987
|
+
attachments_json,
|
|
40988
|
+
model_selection_json,
|
|
40989
|
+
created_at
|
|
40990
|
+
)
|
|
40991
|
+
VALUES (
|
|
40992
|
+
${row.messageId},
|
|
40993
|
+
${row.threadId},
|
|
40994
|
+
${row.text},
|
|
40995
|
+
${row.attachments},
|
|
40996
|
+
${row.modelSelection},
|
|
40997
|
+
${row.createdAt}
|
|
40998
|
+
)
|
|
40999
|
+
ON CONFLICT (message_id)
|
|
41000
|
+
DO UPDATE SET
|
|
41001
|
+
thread_id = excluded.thread_id,
|
|
41002
|
+
text = excluded.text,
|
|
41003
|
+
attachments_json = excluded.attachments_json,
|
|
41004
|
+
model_selection_json = excluded.model_selection_json,
|
|
41005
|
+
created_at = excluded.created_at
|
|
41006
|
+
`
|
|
41007
|
+
});
|
|
41008
|
+
const deleteRow = SqlSchema.void({
|
|
41009
|
+
Request: DeleteProjectionThreadQueuedMessageInput,
|
|
41010
|
+
execute: ({ messageId }) => sql`
|
|
41011
|
+
DELETE FROM projection_thread_queued_messages
|
|
41012
|
+
WHERE message_id = ${messageId}
|
|
41013
|
+
`
|
|
41014
|
+
});
|
|
41015
|
+
const deleteThreadRows = SqlSchema.void({
|
|
41016
|
+
Request: DeleteProjectionThreadQueuedMessagesByThreadInput,
|
|
41017
|
+
execute: ({ threadId }) => sql`
|
|
41018
|
+
DELETE FROM projection_thread_queued_messages
|
|
41019
|
+
WHERE thread_id = ${threadId}
|
|
41020
|
+
`
|
|
41021
|
+
});
|
|
41022
|
+
const upsert = (row) => upsertRow(row).pipe(Effect.mapError(toPersistenceSqlError("ProjectionThreadQueuedMessageRepository.upsert:query")));
|
|
41023
|
+
const deleteByMessageId = (input) => deleteRow(input).pipe(Effect.mapError(toPersistenceSqlError("ProjectionThreadQueuedMessageRepository.deleteByMessageId:query")));
|
|
41024
|
+
const deleteByThreadId = (input) => deleteThreadRows(input).pipe(Effect.mapError(toPersistenceSqlError("ProjectionThreadQueuedMessageRepository.deleteByThreadId:query")));
|
|
41025
|
+
return {
|
|
41026
|
+
upsert,
|
|
41027
|
+
deleteByMessageId,
|
|
41028
|
+
deleteByThreadId
|
|
41029
|
+
};
|
|
41030
|
+
});
|
|
41031
|
+
const ProjectionThreadQueuedMessageRepositoryLive = Layer.effect(ProjectionThreadQueuedMessageRepository, makeProjectionThreadQueuedMessageRepository);
|
|
41032
|
+
//#endregion
|
|
40649
41033
|
//#region src/persistence/Layers/ProjectionThreadSessions.ts
|
|
40650
41034
|
const makeProjectionThreadSessionRepository = Effect.gen(function* () {
|
|
40651
41035
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -40984,6 +41368,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
40984
41368
|
snoozed_at,
|
|
40985
41369
|
pinned_at,
|
|
40986
41370
|
pin_order_key,
|
|
41371
|
+
parent_thread_id,
|
|
40987
41372
|
latest_user_message_at,
|
|
40988
41373
|
pending_approval_count,
|
|
40989
41374
|
pending_user_input_count,
|
|
@@ -41020,6 +41405,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
41020
41405
|
${row.snoozedAt},
|
|
41021
41406
|
${row.pinnedAt},
|
|
41022
41407
|
${row.pinOrderKey},
|
|
41408
|
+
${row.parentThreadId ?? null},
|
|
41023
41409
|
${row.latestUserMessageAt},
|
|
41024
41410
|
${row.pendingApprovalCount},
|
|
41025
41411
|
${row.pendingUserInputCount},
|
|
@@ -41056,6 +41442,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
41056
41442
|
snoozed_at = excluded.snoozed_at,
|
|
41057
41443
|
pinned_at = excluded.pinned_at,
|
|
41058
41444
|
pin_order_key = excluded.pin_order_key,
|
|
41445
|
+
parent_thread_id = COALESCE(excluded.parent_thread_id, projection_threads.parent_thread_id),
|
|
41059
41446
|
latest_user_message_at = excluded.latest_user_message_at,
|
|
41060
41447
|
pending_approval_count = excluded.pending_approval_count,
|
|
41061
41448
|
pending_user_input_count = excluded.pending_user_input_count,
|
|
@@ -41097,6 +41484,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
41097
41484
|
snoozed_at AS "snoozedAt",
|
|
41098
41485
|
pinned_at AS "pinnedAt",
|
|
41099
41486
|
pin_order_key AS "pinOrderKey",
|
|
41487
|
+
parent_thread_id AS "parentThreadId",
|
|
41100
41488
|
latest_user_message_at AS "latestUserMessageAt",
|
|
41101
41489
|
pending_approval_count AS "pendingApprovalCount",
|
|
41102
41490
|
pending_user_input_count AS "pendingUserInputCount",
|
|
@@ -41140,6 +41528,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
|
|
|
41140
41528
|
snoozed_at AS "snoozedAt",
|
|
41141
41529
|
pinned_at AS "pinnedAt",
|
|
41142
41530
|
pin_order_key AS "pinOrderKey",
|
|
41531
|
+
parent_thread_id AS "parentThreadId",
|
|
41143
41532
|
latest_user_message_at AS "latestUserMessageAt",
|
|
41144
41533
|
pending_approval_count AS "pendingApprovalCount",
|
|
41145
41534
|
pending_user_input_count AS "pendingUserInputCount",
|
|
@@ -41189,6 +41578,7 @@ const ORCHESTRATION_PROJECTOR_NAMES = {
|
|
|
41189
41578
|
threadMessages: "projection.thread-messages",
|
|
41190
41579
|
threadProposedPlans: "projection.thread-proposed-plans",
|
|
41191
41580
|
threadScheduledTasks: "projection.thread-scheduled-tasks",
|
|
41581
|
+
threadQueuedMessages: "projection.thread-queued-messages",
|
|
41192
41582
|
threadActivities: "projection.thread-activities",
|
|
41193
41583
|
threadSessions: "projection.thread-sessions",
|
|
41194
41584
|
threadTurns: "projection.thread-turns",
|
|
@@ -41400,6 +41790,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
41400
41790
|
const projectionThreadMessageRepository = yield* ProjectionThreadMessageRepository;
|
|
41401
41791
|
const projectionThreadProposedPlanRepository = yield* ProjectionThreadProposedPlanRepository;
|
|
41402
41792
|
const projectionThreadScheduledTaskRepository = yield* ProjectionThreadScheduledTaskRepository;
|
|
41793
|
+
const projectionThreadQueuedMessageRepository = yield* ProjectionThreadQueuedMessageRepository;
|
|
41403
41794
|
const projectionThreadActivityRepository = yield* ProjectionThreadActivityRepository;
|
|
41404
41795
|
const projectionThreadSessionRepository = yield* ProjectionThreadSessionRepository;
|
|
41405
41796
|
const projectionTurnRepository = yield* ProjectionTurnRepository;
|
|
@@ -41619,6 +42010,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
41619
42010
|
snoozedAt: null,
|
|
41620
42011
|
pinnedAt: null,
|
|
41621
42012
|
pinOrderKey: null,
|
|
42013
|
+
parentThreadId: event.payload.parentThreadId ?? null,
|
|
41622
42014
|
latestUserMessageAt: null,
|
|
41623
42015
|
pendingApprovalCount: 0,
|
|
41624
42016
|
pendingUserInputCount: 0,
|
|
@@ -41936,6 +42328,27 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
41936
42328
|
default: return;
|
|
41937
42329
|
}
|
|
41938
42330
|
});
|
|
42331
|
+
const applyThreadQueuedMessagesProjection = Effect.fn("applyThreadQueuedMessagesProjection")(function* (event) {
|
|
42332
|
+
switch (event.type) {
|
|
42333
|
+
case "thread.message-queued":
|
|
42334
|
+
yield* projectionThreadQueuedMessageRepository.upsert({
|
|
42335
|
+
messageId: event.payload.message.messageId,
|
|
42336
|
+
threadId: event.payload.threadId,
|
|
42337
|
+
text: event.payload.message.text,
|
|
42338
|
+
attachments: event.payload.message.attachments,
|
|
42339
|
+
modelSelection: event.payload.message.modelSelection ?? null,
|
|
42340
|
+
createdAt: event.payload.message.createdAt
|
|
42341
|
+
});
|
|
42342
|
+
return;
|
|
42343
|
+
case "thread.queued-message-removed":
|
|
42344
|
+
yield* projectionThreadQueuedMessageRepository.deleteByMessageId({ messageId: event.payload.messageId });
|
|
42345
|
+
return;
|
|
42346
|
+
case "thread.deleted":
|
|
42347
|
+
yield* projectionThreadQueuedMessageRepository.deleteByThreadId({ threadId: event.payload.threadId });
|
|
42348
|
+
return;
|
|
42349
|
+
default: return;
|
|
42350
|
+
}
|
|
42351
|
+
});
|
|
41939
42352
|
const applyThreadScheduledTasksProjection = Effect.fn("applyThreadScheduledTasksProjection")(function* (event) {
|
|
41940
42353
|
switch (event.type) {
|
|
41941
42354
|
case "thread.scheduled-task.created":
|
|
@@ -42345,6 +42758,10 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
42345
42758
|
name: ORCHESTRATION_PROJECTOR_NAMES.threadScheduledTasks,
|
|
42346
42759
|
apply: applyThreadScheduledTasksProjection
|
|
42347
42760
|
},
|
|
42761
|
+
{
|
|
42762
|
+
name: ORCHESTRATION_PROJECTOR_NAMES.threadQueuedMessages,
|
|
42763
|
+
apply: applyThreadQueuedMessagesProjection
|
|
42764
|
+
},
|
|
42348
42765
|
{
|
|
42349
42766
|
name: ORCHESTRATION_PROJECTOR_NAMES.threadActivities,
|
|
42350
42767
|
apply: applyThreadActivitiesProjection
|
|
@@ -42394,7 +42811,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
|
|
|
42394
42811
|
projectEvent
|
|
42395
42812
|
};
|
|
42396
42813
|
});
|
|
42397
|
-
const OrchestrationProjectionPipelineLive = Layer.effect(OrchestrationProjectionPipeline, makeOrchestrationProjectionPipeline()).pipe(Layer.provideMerge(ProjectionProjectRepositoryLive), Layer.provideMerge(ProjectionThreadRepositoryLive), Layer.provideMerge(ProjectionThreadMessageRepositoryLive), Layer.provideMerge(ProjectionThreadProposedPlanRepositoryLive), Layer.provideMerge(ProjectionThreadScheduledTaskRepositoryLive), Layer.provideMerge(ProjectionThreadActivityRepositoryLive), Layer.provideMerge(ProjectionThreadSessionRepositoryLive), Layer.provideMerge(ProjectionTurnRepositoryLive), Layer.provideMerge(ProjectionPendingApprovalRepositoryLive), Layer.provideMerge(ProjectionStateRepositoryLive));
|
|
42814
|
+
const OrchestrationProjectionPipelineLive = Layer.effect(OrchestrationProjectionPipeline, makeOrchestrationProjectionPipeline()).pipe(Layer.provideMerge(ProjectionProjectRepositoryLive), Layer.provideMerge(ProjectionThreadRepositoryLive), Layer.provideMerge(ProjectionThreadMessageRepositoryLive), Layer.provideMerge(ProjectionThreadProposedPlanRepositoryLive), Layer.provideMerge(ProjectionThreadScheduledTaskRepositoryLive), Layer.provideMerge(ProjectionThreadQueuedMessageRepositoryLive), Layer.provideMerge(ProjectionThreadActivityRepositoryLive), Layer.provideMerge(ProjectionThreadSessionRepositoryLive), Layer.provideMerge(ProjectionTurnRepositoryLive), Layer.provideMerge(ProjectionPendingApprovalRepositoryLive), Layer.provideMerge(ProjectionStateRepositoryLive));
|
|
42398
42815
|
//#endregion
|
|
42399
42816
|
//#region src/persistence/Services/ProjectionCheckpoints.ts
|
|
42400
42817
|
/**
|
|
@@ -42496,6 +42913,10 @@ function deriveLocalBranchNameFromRemoteRef$1(branchName) {
|
|
|
42496
42913
|
if (firstSeparatorIndex <= 0 || firstSeparatorIndex === branchName.length - 1) return branchName;
|
|
42497
42914
|
return branchName.slice(firstSeparatorIndex + 1);
|
|
42498
42915
|
}
|
|
42916
|
+
function buildTemporaryWorktreeBranchName(randomHex) {
|
|
42917
|
+
const token = randomHex(4).toLowerCase().replace(/[^0-9a-f]/g, "").slice(0, 8);
|
|
42918
|
+
return `${WORKTREE_BRANCH_PREFIX}/${token}`;
|
|
42919
|
+
}
|
|
42499
42920
|
function isTemporaryWorktreeBranch(refName) {
|
|
42500
42921
|
return TEMP_WORKTREE_BRANCH_PATTERN.test(refName.trim().toLowerCase());
|
|
42501
42922
|
}
|
|
@@ -43170,6 +43591,24 @@ function mapScheduledTaskRow(row) {
|
|
|
43170
43591
|
failure: row.failure
|
|
43171
43592
|
};
|
|
43172
43593
|
}
|
|
43594
|
+
function mapQueuedMessageRow(row) {
|
|
43595
|
+
return {
|
|
43596
|
+
messageId: row.messageId,
|
|
43597
|
+
text: row.text,
|
|
43598
|
+
attachments: row.attachments,
|
|
43599
|
+
...row.modelSelection !== null ? { modelSelection: row.modelSelection } : {},
|
|
43600
|
+
createdAt: row.createdAt
|
|
43601
|
+
};
|
|
43602
|
+
}
|
|
43603
|
+
function groupQueuedMessagesByThread(rows) {
|
|
43604
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
43605
|
+
for (const row of rows) {
|
|
43606
|
+
const messages = byThread.get(row.threadId) ?? [];
|
|
43607
|
+
messages.push(mapQueuedMessageRow(row));
|
|
43608
|
+
byThread.set(row.threadId, messages);
|
|
43609
|
+
}
|
|
43610
|
+
return byThread;
|
|
43611
|
+
}
|
|
43173
43612
|
function groupScheduledTasksByThread(rows) {
|
|
43174
43613
|
const byThread = /* @__PURE__ */ new Map();
|
|
43175
43614
|
for (const row of rows) {
|
|
@@ -43516,6 +43955,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43516
43955
|
snoozed_at AS "snoozedAt",
|
|
43517
43956
|
pinned_at AS "pinnedAt",
|
|
43518
43957
|
pin_order_key AS "pinOrderKey",
|
|
43958
|
+
parent_thread_id AS "parentThreadId",
|
|
43519
43959
|
latest_user_message_at AS "latestUserMessageAt",
|
|
43520
43960
|
pending_approval_count AS "pendingApprovalCount",
|
|
43521
43961
|
pending_user_input_count AS "pendingUserInputCount",
|
|
@@ -43562,6 +44002,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43562
44002
|
snoozed_at AS "snoozedAt",
|
|
43563
44003
|
pinned_at AS "pinnedAt",
|
|
43564
44004
|
pin_order_key AS "pinOrderKey",
|
|
44005
|
+
parent_thread_id AS "parentThreadId",
|
|
43565
44006
|
latest_user_message_at AS "latestUserMessageAt",
|
|
43566
44007
|
pending_approval_count AS "pendingApprovalCount",
|
|
43567
44008
|
pending_user_input_count AS "pendingUserInputCount",
|
|
@@ -43610,6 +44051,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43610
44051
|
snoozed_at AS "snoozedAt",
|
|
43611
44052
|
pinned_at AS "pinnedAt",
|
|
43612
44053
|
pin_order_key AS "pinOrderKey",
|
|
44054
|
+
parent_thread_id AS "parentThreadId",
|
|
43613
44055
|
latest_user_message_at AS "latestUserMessageAt",
|
|
43614
44056
|
pending_approval_count AS "pendingApprovalCount",
|
|
43615
44057
|
pending_user_input_count AS "pendingUserInputCount",
|
|
@@ -43675,6 +44117,33 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
43675
44117
|
failure
|
|
43676
44118
|
FROM projection_thread_scheduled_tasks
|
|
43677
44119
|
ORDER BY thread_id ASC, run_at ASC, task_id ASC
|
|
44120
|
+
`
|
|
44121
|
+
});
|
|
44122
|
+
const queuedMessageColumns = sql`
|
|
44123
|
+
message_id AS "messageId",
|
|
44124
|
+
thread_id AS "threadId",
|
|
44125
|
+
text,
|
|
44126
|
+
attachments_json AS "attachments",
|
|
44127
|
+
model_selection_json AS "modelSelection",
|
|
44128
|
+
created_at AS "createdAt"
|
|
44129
|
+
`;
|
|
44130
|
+
const listThreadQueuedMessageRows = SqlSchema.findAll({
|
|
44131
|
+
Request: Schema$1.Void,
|
|
44132
|
+
Result: ProjectionThreadQueuedMessage,
|
|
44133
|
+
execute: () => sql`
|
|
44134
|
+
SELECT ${queuedMessageColumns}
|
|
44135
|
+
FROM projection_thread_queued_messages
|
|
44136
|
+
ORDER BY thread_id ASC, rowid ASC
|
|
44137
|
+
`
|
|
44138
|
+
});
|
|
44139
|
+
const listThreadQueuedMessageRowsByThread = SqlSchema.findAll({
|
|
44140
|
+
Request: ThreadIdLookupInput,
|
|
44141
|
+
Result: ProjectionThreadQueuedMessage,
|
|
44142
|
+
execute: ({ threadId }) => sql`
|
|
44143
|
+
SELECT ${queuedMessageColumns}
|
|
44144
|
+
FROM projection_thread_queued_messages
|
|
44145
|
+
WHERE thread_id = ${threadId}
|
|
44146
|
+
ORDER BY rowid ASC
|
|
43678
44147
|
`
|
|
43679
44148
|
});
|
|
43680
44149
|
const listThreadScheduledTaskRowsByThread = SqlSchema.findAll({
|
|
@@ -44117,6 +44586,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
44117
44586
|
snoozed_at AS "snoozedAt",
|
|
44118
44587
|
pinned_at AS "pinnedAt",
|
|
44119
44588
|
pin_order_key AS "pinOrderKey",
|
|
44589
|
+
parent_thread_id AS "parentThreadId",
|
|
44120
44590
|
latest_user_message_at AS "latestUserMessageAt",
|
|
44121
44591
|
pending_approval_count AS "pendingApprovalCount",
|
|
44122
44592
|
pending_user_input_count AS "pendingUserInputCount",
|
|
@@ -44731,9 +45201,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
44731
45201
|
listLatestTurnRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listLatestTurns:query", "ProjectionSnapshotQuery.getSnapshot:listLatestTurns:decodeRows"))),
|
|
44732
45202
|
listProjectionStateRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listProjectionState:query", "ProjectionSnapshotQuery.getSnapshot:listProjectionState:decodeRows"))),
|
|
44733
45203
|
listThreadPairRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listThreadPairs:query", "ProjectionSnapshotQuery.getSnapshot:listThreadPairs:decodeRows"))),
|
|
44734
|
-
listThreadScheduledTaskRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listThreadScheduledTasks:query", "ProjectionSnapshotQuery.getSnapshot:listThreadScheduledTasks:decodeRows")))
|
|
44735
|
-
|
|
45204
|
+
listThreadScheduledTaskRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listThreadScheduledTasks:query", "ProjectionSnapshotQuery.getSnapshot:listThreadScheduledTasks:decodeRows"))),
|
|
45205
|
+
listThreadQueuedMessageRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listThreadQueuedMessages:query", "ProjectionSnapshotQuery.getSnapshot:listThreadQueuedMessages:decodeRows")))
|
|
45206
|
+
])).pipe(Effect.flatMap(([projectRows, threadRows, messageRows, proposedPlanRows, activityRows, sessionRows, checkpointRows, turnRows, latestTurnRows, stateRows, threadPairRows, scheduledTaskRows, queuedMessageRows]) => Effect.gen(function* () {
|
|
44736
45207
|
const scheduledTasksByThread = groupScheduledTasksByThread(scheduledTaskRows);
|
|
45208
|
+
const queuedMessagesByThread = groupQueuedMessagesByThread(queuedMessageRows);
|
|
44737
45209
|
const messagesByThread = /* @__PURE__ */ new Map();
|
|
44738
45210
|
const proposedPlansByThread = /* @__PURE__ */ new Map();
|
|
44739
45211
|
const activitiesByThread = /* @__PURE__ */ new Map();
|
|
@@ -44862,10 +45334,12 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
44862
45334
|
snoozedAt: row.snoozedAt,
|
|
44863
45335
|
pinnedAt: row.pinnedAt,
|
|
44864
45336
|
pinOrderKey: row.pinOrderKey ?? null,
|
|
45337
|
+
parentThreadId: row.parentThreadId ?? null,
|
|
44865
45338
|
deletedAt: row.deletedAt,
|
|
44866
45339
|
messages: messagesByThread.get(row.threadId) ?? [],
|
|
44867
45340
|
proposedPlans: proposedPlansByThread.get(row.threadId) ?? [],
|
|
44868
45341
|
scheduledTasks: scheduledTasksByThread.get(row.threadId) ?? [],
|
|
45342
|
+
queuedMessages: queuedMessagesByThread.get(row.threadId) ?? [],
|
|
44869
45343
|
activities: activitiesByThread.get(row.threadId) ?? [],
|
|
44870
45344
|
checkpoints: checkpointsByThread.get(row.threadId) ?? [],
|
|
44871
45345
|
session: sessionsByThread.get(row.threadId) ?? null
|
|
@@ -44977,9 +45451,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
44977
45451
|
listLatestTurnRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listLatestTurns:query", "ProjectionSnapshotQuery.getCommandReadModel:listLatestTurns:decodeRows"))),
|
|
44978
45452
|
listProjectionStateRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listProjectionState:query", "ProjectionSnapshotQuery.getCommandReadModel:listProjectionState:decodeRows"))),
|
|
44979
45453
|
listThreadPairRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadPairs:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadPairs:decodeRows"))),
|
|
44980
|
-
listThreadScheduledTaskRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadScheduledTasks:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadScheduledTasks:decodeRows")))
|
|
44981
|
-
|
|
45454
|
+
listThreadScheduledTaskRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadScheduledTasks:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadScheduledTasks:decodeRows"))),
|
|
45455
|
+
listThreadQueuedMessageRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadQueuedMessages:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadQueuedMessages:decodeRows")))
|
|
45456
|
+
])).pipe(Effect.flatMap(([projectRows, threadRows, proposedPlanRows, sessionRows, latestTurnRows, stateRows, threadPairRows, scheduledTaskRows, queuedMessageRows]) => Effect.sync(() => {
|
|
44982
45457
|
const scheduledTasksByThread = groupScheduledTasksByThread(scheduledTaskRows);
|
|
45458
|
+
const queuedMessagesByThread = groupQueuedMessagesByThread(queuedMessageRows);
|
|
44983
45459
|
let updatedAt = null;
|
|
44984
45460
|
const projects = [];
|
|
44985
45461
|
const threads = [];
|
|
@@ -45078,10 +45554,12 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45078
45554
|
snoozedAt: row.snoozedAt,
|
|
45079
45555
|
pinnedAt: row.pinnedAt,
|
|
45080
45556
|
pinOrderKey: row.pinOrderKey ?? null,
|
|
45557
|
+
parentThreadId: row.parentThreadId ?? null,
|
|
45081
45558
|
deletedAt: row.deletedAt,
|
|
45082
45559
|
messages: [],
|
|
45083
45560
|
proposedPlans: proposedPlansByThread.get(row.threadId) ?? [],
|
|
45084
45561
|
scheduledTasks: scheduledTasksByThread.get(row.threadId) ?? [],
|
|
45562
|
+
queuedMessages: queuedMessagesByThread.get(row.threadId) ?? [],
|
|
45085
45563
|
activities: [],
|
|
45086
45564
|
checkpoints: [],
|
|
45087
45565
|
session: sessionByThread.get(row.threadId) ?? null
|
|
@@ -45151,6 +45629,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45151
45629
|
snoozedAt: row.snoozedAt,
|
|
45152
45630
|
pinnedAt: row.pinnedAt,
|
|
45153
45631
|
pinOrderKey: row.pinOrderKey ?? null,
|
|
45632
|
+
parentThreadId: row.parentThreadId ?? null,
|
|
45154
45633
|
session: sessionByThread.get(row.threadId) ?? null,
|
|
45155
45634
|
latestUserMessageAt: row.latestUserMessageAt,
|
|
45156
45635
|
hasPendingApprovals: row.pendingApprovalCount > 0,
|
|
@@ -45224,6 +45703,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45224
45703
|
snoozedAt: row.snoozedAt,
|
|
45225
45704
|
pinnedAt: row.pinnedAt,
|
|
45226
45705
|
pinOrderKey: row.pinOrderKey ?? null,
|
|
45706
|
+
parentThreadId: row.parentThreadId ?? null,
|
|
45227
45707
|
session: sessionByThread.get(row.threadId) ?? null,
|
|
45228
45708
|
latestUserMessageAt: row.latestUserMessageAt,
|
|
45229
45709
|
hasPendingApprovals: row.pendingApprovalCount > 0,
|
|
@@ -45367,6 +45847,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45367
45847
|
snoozedAt: threadRow.value.snoozedAt,
|
|
45368
45848
|
pinnedAt: threadRow.value.pinnedAt,
|
|
45369
45849
|
pinOrderKey: threadRow.value.pinOrderKey ?? null,
|
|
45850
|
+
parentThreadId: threadRow.value.parentThreadId ?? null,
|
|
45370
45851
|
session: Option.isSome(sessionRow) ? mapSessionRow(sessionRow.value) : null,
|
|
45371
45852
|
latestUserMessageAt: threadRow.value.latestUserMessageAt,
|
|
45372
45853
|
hasPendingApprovals: threadRow.value.pendingApprovalCount > 0,
|
|
@@ -45398,7 +45879,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45398
45879
|
})).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listActivities:query", "ProjectionSnapshotQuery.getThreadDetailById:listActivities:decodeRows")), Effect.map((rows) => rows.map(mapThreadActivityRow)));
|
|
45399
45880
|
const threadRow = yield* getThreadRowById({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getThread:query", "ProjectionSnapshotQuery.getThreadDetailById:getThread:decodeRow")));
|
|
45400
45881
|
if (Option.isNone(threadRow)) return Option.none();
|
|
45401
|
-
const [messageRows, proposedPlanRows, activities, checkpointRows, turnRows, latestTurnRow, sessionRow, scheduledTaskRows] = yield* Effect.all([
|
|
45882
|
+
const [messageRows, proposedPlanRows, activities, checkpointRows, turnRows, latestTurnRow, sessionRow, scheduledTaskRows, queuedMessageRows] = yield* Effect.all([
|
|
45402
45883
|
(bounds === void 0 ? listThreadMessageRowsByThread({ threadId }) : listThreadMessageRowsByThreadWindow({
|
|
45403
45884
|
threadId,
|
|
45404
45885
|
...bounds
|
|
@@ -45409,7 +45890,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45409
45890
|
listTurnSummaryRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listTurnSummaries:query", "ProjectionSnapshotQuery.getThreadDetailById:listTurnSummaries:decodeRows"))),
|
|
45410
45891
|
getLatestTurnRowByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:query", "ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:decodeRow"))),
|
|
45411
45892
|
getThreadSessionRowByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getSession:query", "ProjectionSnapshotQuery.getThreadDetailById:getSession:decodeRow"))),
|
|
45412
|
-
listThreadScheduledTaskRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listScheduledTasks:query", "ProjectionSnapshotQuery.getThreadDetailById:listScheduledTasks:decodeRows")))
|
|
45893
|
+
listThreadScheduledTaskRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listScheduledTasks:query", "ProjectionSnapshotQuery.getThreadDetailById:listScheduledTasks:decodeRows"))),
|
|
45894
|
+
listThreadQueuedMessageRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listQueuedMessages:query", "ProjectionSnapshotQuery.getThreadDetailById:listQueuedMessages:decodeRows")))
|
|
45413
45895
|
]);
|
|
45414
45896
|
const thread = {
|
|
45415
45897
|
id: threadRow.value.threadId,
|
|
@@ -45441,6 +45923,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45441
45923
|
snoozedAt: threadRow.value.snoozedAt,
|
|
45442
45924
|
pinnedAt: threadRow.value.pinnedAt,
|
|
45443
45925
|
pinOrderKey: threadRow.value.pinOrderKey ?? null,
|
|
45926
|
+
parentThreadId: threadRow.value.parentThreadId ?? null,
|
|
45444
45927
|
deletedAt: null,
|
|
45445
45928
|
messages: messageRows.map((row) => {
|
|
45446
45929
|
const message = {
|
|
@@ -45457,6 +45940,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
|
|
|
45457
45940
|
}),
|
|
45458
45941
|
proposedPlans: proposedPlanRows.map(mapProposedPlanRow),
|
|
45459
45942
|
scheduledTasks: scheduledTaskRows.map(mapScheduledTaskRow),
|
|
45943
|
+
queuedMessages: queuedMessageRows.map(mapQueuedMessageRow),
|
|
45460
45944
|
activities,
|
|
45461
45945
|
checkpoints: checkpointRows.map((row) => ({
|
|
45462
45946
|
turnId: row.turnId,
|
|
@@ -48273,6 +48757,7 @@ const make$96 = Effect.gen(function* () {
|
|
|
48273
48757
|
threadPinning: true,
|
|
48274
48758
|
threadFork: true,
|
|
48275
48759
|
threadScheduledTasks: true,
|
|
48760
|
+
threadMessageQueue: true,
|
|
48276
48761
|
...serverSelfUpdate === null ? {} : { serverSelfUpdate },
|
|
48277
48762
|
...serverServiceSupervised ? { serverServiceSupervised } : {}
|
|
48278
48763
|
}
|
|
@@ -58019,11 +58504,22 @@ const requireThreadControlTarget = Effect.fn("mcp.requireThreadControlTarget")(f
|
|
|
58019
58504
|
invocation,
|
|
58020
58505
|
threadId: invocation.threadId
|
|
58021
58506
|
};
|
|
58022
|
-
if (
|
|
58023
|
-
|
|
58507
|
+
if (invocation.controlThreadIds?.has(requested)) return {
|
|
58508
|
+
invocation,
|
|
58509
|
+
threadId: requested
|
|
58510
|
+
};
|
|
58511
|
+
if (yield* isChildOfInvocationThread(invocation.threadId, requested)) return {
|
|
58024
58512
|
invocation,
|
|
58025
58513
|
threadId: requested
|
|
58026
58514
|
};
|
|
58515
|
+
return yield* new ThreadControlNotPermittedError({ threadId: requested });
|
|
58516
|
+
});
|
|
58517
|
+
/** Whether `childThreadId` was spawned by `parentThreadId` as a sub-thread. */
|
|
58518
|
+
const isChildOfInvocationThread = Effect.fn("mcp.isChildOfInvocationThread")(function* (parentThreadId, childThreadId) {
|
|
58519
|
+
const query = yield* Effect.serviceOption(ProjectionSnapshotQuery);
|
|
58520
|
+
if (Option.isNone(query)) return false;
|
|
58521
|
+
const shell = yield* query.value.getThreadShellById(childThreadId).pipe(Effect.catch(() => Effect.succeed(Option.none())));
|
|
58522
|
+
return Option.isSome(shell) && shell.value.parentThreadId === parentThreadId;
|
|
58027
58523
|
});
|
|
58028
58524
|
/**
|
|
58029
58525
|
* The guard on starting a thread, which is a different question from acting on
|
|
@@ -94144,6 +94640,31 @@ const makeCodexSessionRuntime = (options) => Effect.gen(function* () {
|
|
|
94144
94640
|
...options.fusionPromptSettings ? { fusionPromptSettings: options.fusionPromptSettings } : {},
|
|
94145
94641
|
...input.unpromptedSubagents !== void 0 ? { unpromptedSubagents: input.unpromptedSubagents } : {}
|
|
94146
94642
|
});
|
|
94643
|
+
const activeSession = yield* Ref.get(sessionRef);
|
|
94644
|
+
if (activeSession.status === "running" && activeSession.activeTurnId !== void 0) {
|
|
94645
|
+
const steeredTurnId = activeSession.activeTurnId;
|
|
94646
|
+
const steerExit = yield* client.raw.request("turn/steer", {
|
|
94647
|
+
threadId: providerThreadId,
|
|
94648
|
+
expectedTurnId: steeredTurnId,
|
|
94649
|
+
input: params.input
|
|
94650
|
+
}).pipe(Effect.exit);
|
|
94651
|
+
if (Exit.isSuccess(steerExit)) {
|
|
94652
|
+
const steeredProviderThreadId = currentProviderThreadId(yield* Ref.get(sessionRef));
|
|
94653
|
+
return {
|
|
94654
|
+
threadId: options.threadId,
|
|
94655
|
+
turnId: steeredTurnId,
|
|
94656
|
+
...steeredProviderThreadId ? { resumeCursor: { threadId: steeredProviderThreadId } } : {}
|
|
94657
|
+
};
|
|
94658
|
+
}
|
|
94659
|
+
if (findActiveCodexTurnId(yield* client.request("thread/read", {
|
|
94660
|
+
threadId: providerThreadId,
|
|
94661
|
+
includeTurns: true
|
|
94662
|
+
})) === steeredTurnId) {
|
|
94663
|
+
yield* Effect.logWarning("Codex turn/steer failed while the turn is still running.", { cause: steerExit.cause });
|
|
94664
|
+
return yield* Effect.failCause(steerExit.cause);
|
|
94665
|
+
}
|
|
94666
|
+
yield* Effect.logInfo("Codex turn ended before the steer landed; starting a new turn.");
|
|
94667
|
+
}
|
|
94147
94668
|
const { collaborationMode: attachedCollaborationMode, ...paramsWithoutCollaboration } = params;
|
|
94148
94669
|
const lastCollaborationMode = yield* Ref.get(lastCollaborationModeRef);
|
|
94149
94670
|
const requestParams = attachedCollaborationMode !== void 0 && lastCollaborationMode !== null && isSameCollaborationMode(attachedCollaborationMode, lastCollaborationMode) ? paramsWithoutCollaboration : params;
|
|
@@ -125052,6 +125573,86 @@ const make$36 = Effect.gen(function* () {
|
|
|
125052
125573
|
...cleanup === void 0 ? {} : { cleanup }
|
|
125053
125574
|
});
|
|
125054
125575
|
}
|
|
125576
|
+
const finishMergedCleanup = Effect.fn("ThreadWorkspaceLifecycle.finishMergedCleanup")(function* (input) {
|
|
125577
|
+
yield* record({
|
|
125578
|
+
threadIds,
|
|
125579
|
+
lifecycle: {
|
|
125580
|
+
status: "cleanup-pending",
|
|
125581
|
+
detail: "Merged context recorded; workspace cleanup is pending.",
|
|
125582
|
+
pullRequestNumber: input.merged.pullRequestNumber,
|
|
125583
|
+
mergeCommitSha: input.merged.mergeCommitSha,
|
|
125584
|
+
updatedAt: now,
|
|
125585
|
+
...cleanup === void 0 ? {} : { cleanup }
|
|
125586
|
+
}
|
|
125587
|
+
});
|
|
125588
|
+
yield* cleanGeneratedFiles();
|
|
125589
|
+
if (cleanup === void 0 || cleanup.worktree) yield* gitWorkflow.removeWorktree({
|
|
125590
|
+
cwd: project.workspaceRoot,
|
|
125591
|
+
path: worktreePath,
|
|
125592
|
+
...cleanup?.forceWorktree ? { force: true } : {}
|
|
125593
|
+
});
|
|
125594
|
+
if (cleanup === void 0 || cleanup.localBranch) yield* git.execute({
|
|
125595
|
+
operation: "ThreadWorkspaceLifecycle.deleteBranch",
|
|
125596
|
+
cwd: project.workspaceRoot,
|
|
125597
|
+
args: [
|
|
125598
|
+
"update-ref",
|
|
125599
|
+
"-d",
|
|
125600
|
+
`refs/heads/${input.branch}`,
|
|
125601
|
+
input.branchCommitSha
|
|
125602
|
+
]
|
|
125603
|
+
}).pipe(Effect.catchCause((cause) => gitWorkflow.createWorktree({
|
|
125604
|
+
cwd: project.workspaceRoot,
|
|
125605
|
+
refName: input.branch,
|
|
125606
|
+
path: worktreePath
|
|
125607
|
+
}).pipe(Effect.andThen(Effect.failCause(cause)))));
|
|
125608
|
+
const detail = cleanup === void 0 ? "Merged worktree and branch removed; thread context preserved." : "Selected archive resources removed; thread context preserved.";
|
|
125609
|
+
yield* record({
|
|
125610
|
+
threadIds,
|
|
125611
|
+
lifecycle: {
|
|
125612
|
+
status: "cleaned",
|
|
125613
|
+
detail,
|
|
125614
|
+
pullRequestNumber: input.merged.pullRequestNumber,
|
|
125615
|
+
mergeCommitSha: input.merged.mergeCommitSha,
|
|
125616
|
+
updatedAt: DateTime.formatIso(yield* DateTime.now)
|
|
125617
|
+
}
|
|
125618
|
+
});
|
|
125619
|
+
return {
|
|
125620
|
+
outcome: "cleaned",
|
|
125621
|
+
detail
|
|
125622
|
+
};
|
|
125623
|
+
});
|
|
125624
|
+
const parentThread = requestedThread.parentThreadId == null ? void 0 : snapshot.threads.find((thread) => thread.id === requestedThread.parentThreadId);
|
|
125625
|
+
if (parentThread !== void 0 && !cleanup?.remoteBranch) {
|
|
125626
|
+
if (yield* git.execute({
|
|
125627
|
+
operation: "ThreadWorkspaceLifecycle.landedInParent",
|
|
125628
|
+
cwd: parentThread.worktreePath ?? project.workspaceRoot,
|
|
125629
|
+
args: [
|
|
125630
|
+
"merge-base",
|
|
125631
|
+
"--is-ancestor",
|
|
125632
|
+
`refs/heads/${branch}`,
|
|
125633
|
+
"HEAD"
|
|
125634
|
+
],
|
|
125635
|
+
allowNonZeroExit: true
|
|
125636
|
+
}).pipe(Effect.map((result) => result.exitCode === 0), Effect.orElseSucceed(() => false))) {
|
|
125637
|
+
const branchCommitSha = (yield* git.execute({
|
|
125638
|
+
operation: "ThreadWorkspaceLifecycle.branchCommit",
|
|
125639
|
+
cwd: project.workspaceRoot,
|
|
125640
|
+
args: [
|
|
125641
|
+
"rev-parse",
|
|
125642
|
+
"--verify",
|
|
125643
|
+
`refs/heads/${branch}^{commit}`
|
|
125644
|
+
]
|
|
125645
|
+
})).stdout.trim();
|
|
125646
|
+
return yield* finishMergedCleanup({
|
|
125647
|
+
branch,
|
|
125648
|
+
branchCommitSha,
|
|
125649
|
+
merged: {
|
|
125650
|
+
mergeCommitSha: branchCommitSha,
|
|
125651
|
+
pullRequestNumber: null
|
|
125652
|
+
}
|
|
125653
|
+
});
|
|
125654
|
+
}
|
|
125655
|
+
}
|
|
125055
125656
|
const refs = yield* gitWorkflow.listRefs({
|
|
125056
125657
|
cwd: project.workspaceRoot,
|
|
125057
125658
|
includeMatchingRemoteRefs: true,
|
|
@@ -125164,52 +125765,11 @@ const make$36 = Effect.gen(function* () {
|
|
|
125164
125765
|
}).pipe(Effect.result))._tag === "Failure") return yield* workspaceRefusal("Cleanup refused because the remote branch could not be removed.");
|
|
125165
125766
|
}
|
|
125166
125767
|
}
|
|
125167
|
-
yield*
|
|
125168
|
-
|
|
125169
|
-
|
|
125170
|
-
|
|
125171
|
-
detail: "Merged context recorded; workspace cleanup is pending.",
|
|
125172
|
-
pullRequestNumber: merged.pullRequestNumber,
|
|
125173
|
-
mergeCommitSha: merged.mergeCommitSha,
|
|
125174
|
-
updatedAt: now,
|
|
125175
|
-
...cleanup === void 0 ? {} : { cleanup }
|
|
125176
|
-
}
|
|
125177
|
-
});
|
|
125178
|
-
yield* cleanGeneratedFiles();
|
|
125179
|
-
if (cleanup === void 0 || cleanup.worktree) yield* gitWorkflow.removeWorktree({
|
|
125180
|
-
cwd: project.workspaceRoot,
|
|
125181
|
-
path: worktreePath,
|
|
125182
|
-
...cleanup?.forceWorktree ? { force: true } : {}
|
|
125183
|
-
});
|
|
125184
|
-
if (cleanup === void 0 || cleanup.localBranch) yield* git.execute({
|
|
125185
|
-
operation: "ThreadWorkspaceLifecycle.deleteBranch",
|
|
125186
|
-
cwd: project.workspaceRoot,
|
|
125187
|
-
args: [
|
|
125188
|
-
"update-ref",
|
|
125189
|
-
"-d",
|
|
125190
|
-
`refs/heads/${branch}`,
|
|
125191
|
-
branchCommitResult.stdout.trim()
|
|
125192
|
-
]
|
|
125193
|
-
}).pipe(Effect.catchCause((cause) => gitWorkflow.createWorktree({
|
|
125194
|
-
cwd: project.workspaceRoot,
|
|
125195
|
-
refName: branch,
|
|
125196
|
-
path: worktreePath
|
|
125197
|
-
}).pipe(Effect.andThen(Effect.failCause(cause)))));
|
|
125198
|
-
const detail = cleanup === void 0 ? "Merged worktree and branch removed; thread context preserved." : "Selected archive resources removed; thread context preserved.";
|
|
125199
|
-
yield* record({
|
|
125200
|
-
threadIds,
|
|
125201
|
-
lifecycle: {
|
|
125202
|
-
status: "cleaned",
|
|
125203
|
-
detail,
|
|
125204
|
-
pullRequestNumber: merged.pullRequestNumber,
|
|
125205
|
-
mergeCommitSha: merged.mergeCommitSha,
|
|
125206
|
-
updatedAt: DateTime.formatIso(yield* DateTime.now)
|
|
125207
|
-
}
|
|
125768
|
+
return yield* finishMergedCleanup({
|
|
125769
|
+
branch,
|
|
125770
|
+
branchCommitSha: branchCommitResult.stdout.trim(),
|
|
125771
|
+
merged
|
|
125208
125772
|
});
|
|
125209
|
-
return {
|
|
125210
|
-
outcome: "cleaned",
|
|
125211
|
-
detail
|
|
125212
|
-
};
|
|
125213
125773
|
});
|
|
125214
125774
|
const cleanup = (threadId) => cleanupRaw(threadId).pipe(mapLifecycleError);
|
|
125215
125775
|
return {
|
|
@@ -133212,7 +133772,7 @@ function projectSetupScriptCompatibilityDetail(error) {
|
|
|
133212
133772
|
}
|
|
133213
133773
|
}
|
|
133214
133774
|
function isThreadDetailEvent(event) {
|
|
133215
|
-
return event.type === "thread.message-sent" || event.type === "thread.archived" || event.type === "thread.unarchived" || event.type === "thread.deleted" || event.type === "thread.meta-updated" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.scheduled-task.created" || event.type === "thread.scheduled-task.cancelled" || event.type === "thread.scheduled-task.fired" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set";
|
|
133775
|
+
return event.type === "thread.message-sent" || event.type === "thread.message-queued" || event.type === "thread.queued-message-removed" || event.type === "thread.archived" || event.type === "thread.unarchived" || event.type === "thread.deleted" || event.type === "thread.meta-updated" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.scheduled-task.created" || event.type === "thread.scheduled-task.cancelled" || event.type === "thread.scheduled-task.fired" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set";
|
|
133216
133776
|
}
|
|
133217
133777
|
const PROVIDER_STATUS_DEBOUNCE_MS = 200;
|
|
133218
133778
|
const SHELL_RESUME_MAX_GAP = 1e3;
|
|
@@ -135254,7 +135814,7 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
|
|
|
135254
135814
|
const next = new Map(records);
|
|
135255
135815
|
next.set(tokenHash, {
|
|
135256
135816
|
tokenHash,
|
|
135257
|
-
scope: scopeWith(!spawnedThreads.has(threadId), spawnedThreads.get(threadId) === true),
|
|
135817
|
+
scope: scopeWith(!(spawnedThreads.has(threadId) || request.spawned === true), spawnedThreads.get(threadId) === true),
|
|
135258
135818
|
controlThreadIds: /* @__PURE__ */ new Set(),
|
|
135259
135819
|
lastAliveAt: issuedAt
|
|
135260
135820
|
});
|
|
@@ -135657,7 +136217,8 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
|
|
|
135657
136217
|
issue: "Could not read thread pairs, so the session's Fusion role is unknown. Refusing to issue an MCP credential that would carry no role.",
|
|
135658
136218
|
cause
|
|
135659
136219
|
}))) : yield* Effect.logWarning("No projection service, so an MCP credential is issued with no Fusion role. A pair whose event this process never saw will be ungated.", { threadId }).pipe(Effect.as(void 0));
|
|
135660
|
-
const
|
|
136220
|
+
const shell = Option.isSome(projectionQuery) ? yield* projectionQuery.value.getThreadShellById(threadId).pipe(Effect.map(Option.getOrUndefined), Effect.catch(() => Effect.succeed(void 0))) : void 0;
|
|
136221
|
+
const threadTitle = shell?.title;
|
|
135661
136222
|
const credential = yield* issueActiveMcpCredential({
|
|
135662
136223
|
threadId,
|
|
135663
136224
|
...threadTitle ? { threadTitle } : {},
|
|
@@ -135666,7 +136227,8 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (options)
|
|
|
135666
136227
|
computerUseAccess,
|
|
135667
136228
|
...watchThreadIds !== void 0 ? { watchThreadIds } : {},
|
|
135668
136229
|
...adviseThreadIds !== void 0 ? { adviseThreadIds } : {},
|
|
135669
|
-
...fusionRole !== void 0 ? { fusionRole } : {}
|
|
136230
|
+
...fusionRole !== void 0 ? { fusionRole } : {},
|
|
136231
|
+
...shell?.parentThreadId != null ? { spawned: true } : {}
|
|
135670
136232
|
});
|
|
135671
136233
|
if (credential) yield* Effect.sync(() => setMcpProviderSession(credential.config));
|
|
135672
136234
|
return credential;
|
|
@@ -138151,6 +138713,196 @@ const TaskToolkitHandlersLive = TaskToolkit.toLayer({
|
|
|
138151
138713
|
})
|
|
138152
138714
|
});
|
|
138153
138715
|
//#endregion
|
|
138716
|
+
//#region ../../packages/shared/src/threadPendingWork.ts
|
|
138717
|
+
/**
|
|
138718
|
+
* A user message the server has not started a turn for yet, within the window
|
|
138719
|
+
* where adoption is still plausible. Mirrors `hasQueuedTurnStart` in
|
|
138720
|
+
* client-runtime, which owns the same window for settle/snooze decisions.
|
|
138721
|
+
*/
|
|
138722
|
+
const QUEUED_TURN_START_GRACE_MS = 120 * 1e3;
|
|
138723
|
+
function hasQueuedTurn(input, nowMs) {
|
|
138724
|
+
if (input.latestUserMessageAt == null) return false;
|
|
138725
|
+
if (input.session?.status === "error") return false;
|
|
138726
|
+
const messageAtMs = Date.parse(input.latestUserMessageAt);
|
|
138727
|
+
if (Number.isNaN(messageAtMs)) return false;
|
|
138728
|
+
if (Math.abs(nowMs - messageAtMs) > QUEUED_TURN_START_GRACE_MS) return false;
|
|
138729
|
+
const turn = input.latestTurn;
|
|
138730
|
+
if (turn === null) return true;
|
|
138731
|
+
return [
|
|
138732
|
+
turn.requestedAt,
|
|
138733
|
+
turn.startedAt,
|
|
138734
|
+
turn.completedAt
|
|
138735
|
+
].every((candidate) => candidate == null || Date.parse(candidate) < messageAtMs);
|
|
138736
|
+
}
|
|
138737
|
+
/**
|
|
138738
|
+
* What the thread is waiting on, or null when it is genuinely idle. A running
|
|
138739
|
+
* session answers null: the agent is working, which callers already show.
|
|
138740
|
+
*/
|
|
138741
|
+
function resolveThreadPendingWork(input, options) {
|
|
138742
|
+
const nowMs = Date.parse(options.now);
|
|
138743
|
+
if (Number.isNaN(nowMs)) return null;
|
|
138744
|
+
if (input.session?.status === "starting" || input.session?.status === "running") return null;
|
|
138745
|
+
if (hasQueuedTurn(input, nowMs)) return "queued";
|
|
138746
|
+
if (input.session === null) return null;
|
|
138747
|
+
if (input.hasBackgroundTasks === true) return "background";
|
|
138748
|
+
if (input.scheduledWakeAt != null) {
|
|
138749
|
+
const wakeAtMs = Date.parse(input.scheduledWakeAt);
|
|
138750
|
+
if (!Number.isNaN(wakeAtMs) && wakeAtMs > nowMs) return "scheduled";
|
|
138751
|
+
}
|
|
138752
|
+
return null;
|
|
138753
|
+
}
|
|
138754
|
+
//#endregion
|
|
138755
|
+
//#region src/mcp/toolkits/threads/subThreadState.ts
|
|
138756
|
+
/**
|
|
138757
|
+
* Where a sub-thread stands, for a parent deciding what to do next. A
|
|
138758
|
+
* question or approval outranks running: the session keeps reporting running
|
|
138759
|
+
* while it waits, but nothing moves until someone answers.
|
|
138760
|
+
*/
|
|
138761
|
+
function resolveSubThreadState(shell, now) {
|
|
138762
|
+
if (shell === void 0) return "missing";
|
|
138763
|
+
if (shell.archivedAt !== null) return "completed";
|
|
138764
|
+
if (shell.hasPendingApprovals || shell.hasPendingUserInput) return "needs-input";
|
|
138765
|
+
const status = shell.session?.status;
|
|
138766
|
+
if (status === "running" || status === "starting") return "running";
|
|
138767
|
+
if (resolveThreadPendingWork(shell, { now }) !== null) return "running";
|
|
138768
|
+
if (status === "error" || shell.latestTurn?.state === "error") return "failed";
|
|
138769
|
+
return "completed";
|
|
138770
|
+
}
|
|
138771
|
+
/** Whether a wait in `mode` is over for these states. */
|
|
138772
|
+
function isSubThreadWaitSatisfied(states, mode) {
|
|
138773
|
+
const stopped = (state) => state !== "running";
|
|
138774
|
+
return mode === "any" ? states.some(stopped) : states.every(stopped);
|
|
138775
|
+
}
|
|
138776
|
+
//#endregion
|
|
138777
|
+
//#region src/orchestration/SubThreadWorkspace.ts
|
|
138778
|
+
/**
|
|
138779
|
+
* Git steps for sub-threads: a child gets its own worktree branched from the
|
|
138780
|
+
* parent's current HEAD, and lands by merging back into the parent's worktree.
|
|
138781
|
+
*/
|
|
138782
|
+
const repositoryLocks = /* @__PURE__ */ new Map();
|
|
138783
|
+
const withRepositoryLock = (repositoryRoot) => {
|
|
138784
|
+
let lock = repositoryLocks.get(repositoryRoot);
|
|
138785
|
+
if (lock === void 0) {
|
|
138786
|
+
lock = Semaphore.makeUnsafe(1);
|
|
138787
|
+
repositoryLocks.set(repositoryRoot, lock);
|
|
138788
|
+
}
|
|
138789
|
+
return lock.withPermits(1);
|
|
138790
|
+
};
|
|
138791
|
+
const runGit = (operation, cwd, args) => Effect.gen(function* () {
|
|
138792
|
+
return yield* (yield* GitVcsDriver).execute({
|
|
138793
|
+
operation: `SubThreadWorkspace.${operation}`,
|
|
138794
|
+
cwd,
|
|
138795
|
+
args,
|
|
138796
|
+
allowNonZeroExit: true
|
|
138797
|
+
});
|
|
138798
|
+
});
|
|
138799
|
+
/** The branch checked out at `cwd`, or null on a detached HEAD. */
|
|
138800
|
+
const currentBranch = (cwd) => runGit("currentBranch", cwd, [
|
|
138801
|
+
"symbolic-ref",
|
|
138802
|
+
"--quiet",
|
|
138803
|
+
"--short",
|
|
138804
|
+
"HEAD"
|
|
138805
|
+
]).pipe(Effect.map((result) => result.exitCode === 0 ? result.stdout.trim() || null : null));
|
|
138806
|
+
/** Whether `cwd` has uncommitted changes, untracked files included. */
|
|
138807
|
+
const hasUncommittedChanges = (cwd) => runGit("status", cwd, ["status", "--porcelain"]).pipe(Effect.map((result) => result.stdout.trim().length > 0));
|
|
138808
|
+
const headCommit = (cwd) => runGit("headCommit", cwd, [
|
|
138809
|
+
"rev-parse",
|
|
138810
|
+
"--verify",
|
|
138811
|
+
"HEAD^{commit}"
|
|
138812
|
+
]).pipe(Effect.map((result) => result.exitCode === 0 ? result.stdout.trim() : null));
|
|
138813
|
+
/** Creates the child's worktree on a new branch at the parent's HEAD commit. */
|
|
138814
|
+
const provisionChildWorktree = Effect.fn("SubThreadWorkspace.provisionChildWorktree")(function* (input) {
|
|
138815
|
+
const gitWorkflow = yield* GitWorkflowService;
|
|
138816
|
+
return yield* Effect.gen(function* () {
|
|
138817
|
+
const baseCommitSha = yield* headCommit(input.parentCwd);
|
|
138818
|
+
if (baseCommitSha === null) return null;
|
|
138819
|
+
const parentBranch = yield* currentBranch(input.parentCwd);
|
|
138820
|
+
const parentDirty = yield* hasUncommittedChanges(input.parentCwd);
|
|
138821
|
+
const created = yield* gitWorkflow.createWorktree({
|
|
138822
|
+
cwd: input.repositoryRoot,
|
|
138823
|
+
refName: baseCommitSha,
|
|
138824
|
+
newRefName: input.branch,
|
|
138825
|
+
...parentBranch !== null ? { baseRefName: parentBranch } : {},
|
|
138826
|
+
path: null
|
|
138827
|
+
});
|
|
138828
|
+
return {
|
|
138829
|
+
worktreePath: created.worktree.path,
|
|
138830
|
+
branch: created.worktree.refName,
|
|
138831
|
+
baseCommitSha,
|
|
138832
|
+
parentBranch,
|
|
138833
|
+
parentDirty
|
|
138834
|
+
};
|
|
138835
|
+
}).pipe(withRepositoryLock(input.repositoryRoot));
|
|
138836
|
+
});
|
|
138837
|
+
/** Undoes `provisionChildWorktree` when the thread it was made for never appeared. */
|
|
138838
|
+
const discardChildWorktree = Effect.fn("SubThreadWorkspace.discardChildWorktree")(function* (input) {
|
|
138839
|
+
yield* (yield* GitWorkflowService).removeWorktree({
|
|
138840
|
+
cwd: input.repositoryRoot,
|
|
138841
|
+
path: input.worktreePath,
|
|
138842
|
+
force: true
|
|
138843
|
+
}).pipe(Effect.ignore);
|
|
138844
|
+
yield* runGit("deleteBranch", input.repositoryRoot, [
|
|
138845
|
+
"branch",
|
|
138846
|
+
"-D",
|
|
138847
|
+
input.branch
|
|
138848
|
+
]).pipe(Effect.ignore);
|
|
138849
|
+
});
|
|
138850
|
+
/**
|
|
138851
|
+
* Merges the child's branch into the parent's worktree. A conflict is aborted
|
|
138852
|
+
* so the parent stays clean, and the conflicted files come back for the child
|
|
138853
|
+
* to resolve on its own branch.
|
|
138854
|
+
*/
|
|
138855
|
+
const mergeChildIntoParent = Effect.fn("SubThreadWorkspace.mergeChildIntoParent")(function* (input) {
|
|
138856
|
+
const childBranch = yield* currentBranch(input.childCwd);
|
|
138857
|
+
if (childBranch === null) return {
|
|
138858
|
+
outcome: "refused",
|
|
138859
|
+
detail: "The child worktree is on a detached HEAD."
|
|
138860
|
+
};
|
|
138861
|
+
if (yield* hasUncommittedChanges(input.childCwd)) return {
|
|
138862
|
+
outcome: "refused",
|
|
138863
|
+
detail: `The child has uncommitted changes on '${childBranch}'. Ask it to commit first.`
|
|
138864
|
+
};
|
|
138865
|
+
if (yield* hasUncommittedChanges(input.parentCwd)) return {
|
|
138866
|
+
outcome: "refused",
|
|
138867
|
+
detail: "The parent worktree has uncommitted changes. Commit or stash them before merging."
|
|
138868
|
+
};
|
|
138869
|
+
return yield* Effect.gen(function* () {
|
|
138870
|
+
const parentBranch = yield* currentBranch(input.parentCwd);
|
|
138871
|
+
const merge = yield* runGit("merge", input.parentCwd, [
|
|
138872
|
+
"merge",
|
|
138873
|
+
"--no-ff",
|
|
138874
|
+
"--no-edit",
|
|
138875
|
+
`refs/heads/${childBranch}`
|
|
138876
|
+
]);
|
|
138877
|
+
if (merge.exitCode === 0) {
|
|
138878
|
+
const mergeCommitSha = (yield* headCommit(input.parentCwd)) ?? "";
|
|
138879
|
+
return {
|
|
138880
|
+
outcome: /already up[ -]to[ -]date/i.test(merge.stdout) ? "up-to-date" : "merged",
|
|
138881
|
+
childBranch,
|
|
138882
|
+
parentBranch,
|
|
138883
|
+
mergeCommitSha
|
|
138884
|
+
};
|
|
138885
|
+
}
|
|
138886
|
+
const conflicted = yield* runGit("conflictedFiles", input.parentCwd, [
|
|
138887
|
+
"diff",
|
|
138888
|
+
"--name-only",
|
|
138889
|
+
"--diff-filter=U"
|
|
138890
|
+
]);
|
|
138891
|
+
yield* runGit("abortMerge", input.parentCwd, ["merge", "--abort"]);
|
|
138892
|
+
const conflictedFiles = conflicted.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
138893
|
+
if (conflictedFiles.length === 0) return {
|
|
138894
|
+
outcome: "refused",
|
|
138895
|
+
detail: `git merge failed: ${merge.stderr.trim() || merge.stdout.trim()}`
|
|
138896
|
+
};
|
|
138897
|
+
return {
|
|
138898
|
+
outcome: "conflicted",
|
|
138899
|
+
childBranch,
|
|
138900
|
+
parentBranch,
|
|
138901
|
+
conflictedFiles
|
|
138902
|
+
};
|
|
138903
|
+
}).pipe(withRepositoryLock(input.repositoryRoot));
|
|
138904
|
+
});
|
|
138905
|
+
//#endregion
|
|
138154
138906
|
//#region src/orchestration/fusionEvidenceReport.ts
|
|
138155
138907
|
/**
|
|
138156
138908
|
* Shared shape of one Fusion review's evidence rows.
|
|
@@ -138581,7 +139333,7 @@ const AskUserQuestionTool = Tool.make("ask_user_question", {
|
|
|
138581
139333
|
]
|
|
138582
139334
|
}).annotate(Tool.Title, "Ask user question").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
138583
139335
|
const ThreadSpawnTool = Tool.make("thread_spawn", {
|
|
138584
|
-
description: "Start a new agent thread and send it a first message, then return its id. Use it to hand a piece of work to a fresh thread - a subtask you just planned, a job that wants its own transcript. For activation here or a new Fusion in this chat, use thread_pair_create with no watcherThreadId so this chat becomes Supervisor and its new Builder starts empty. Set fusion true only for an explicitly separate pair to create two new threads and send prompt to its supervisor, which plans and directs the builder later, even when this thread already belongs to a pair. Set fusionWatcher true only for legacy current-chat-as-builder pairing. Both use saved Fusion role models unless overridden. Act on the user's request; no special command syntax is required. The new thread inherits this one's project, model, permission mode, interaction mode, compression and subagent policy, and every one of those can be set here instead - what you set is already in force for the first turn, so a thread never has to be corrected after it starts. Set modelSelection to run it on another model: give the model id, and leave instanceId out to keep this thread's provider instance. Once started it can be watched with thread_watch_events and changed later with thread_configure. A thread that was itself started this way cannot start another.",
|
|
139336
|
+
description: "Start a new agent thread and send it a first message, then return its id. Use it to hand a piece of work to a fresh thread - a subtask you just planned, a job that wants its own transcript. For activation here or a new Fusion in this chat, use thread_pair_create with no watcherThreadId so this chat becomes Supervisor and its new Builder starts empty. Set fusion true only for an explicitly separate pair to create two new threads and send prompt to its supervisor, which plans and directs the builder later, even when this thread already belongs to a pair. Set fusionWatcher true only for legacy current-chat-as-builder pairing. Both use saved Fusion role models unless overridden. Act on the user's request; no special command syntax is required. The new thread inherits this one's project, model, permission mode, interaction mode, compression and subagent policy, and every one of those can be set here instead - what you set is already in force for the first turn, so a thread never has to be corrected after it starts. Set modelSelection to run it on another model: give the model id, and leave instanceId out to keep this thread's provider instance. Once started it can be watched with thread_watch_events and changed later with thread_configure. A thread that was itself started this way cannot start another. A plain spawn is a sub-thread: it gets its own git worktree and branch from this thread's current HEAD commit (commit first), so several can work in parallel; wait on them with thread_wait, follow up with thread_message, land each with thread_merge_child, and remove it with thread_cleanup.",
|
|
138585
139337
|
parameters: ThreadSpawnInput,
|
|
138586
139338
|
success: ThreadSpawnResult,
|
|
138587
139339
|
failure: ThreadControlToolError,
|
|
@@ -138590,8 +139342,11 @@ const ThreadSpawnTool = Tool.make("thread_spawn", {
|
|
|
138590
139342
|
McpSessionRegistry,
|
|
138591
139343
|
OrchestrationEngineService,
|
|
138592
139344
|
ProjectionThreadRepository,
|
|
139345
|
+
ProjectionSnapshotQuery,
|
|
138593
139346
|
ServerSettingsService,
|
|
138594
139347
|
ProviderRegistry,
|
|
139348
|
+
GitWorkflowService,
|
|
139349
|
+
GitVcsDriver,
|
|
138595
139350
|
Crypto.Crypto
|
|
138596
139351
|
]
|
|
138597
139352
|
}).annotate(Tool.Title, "Start a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
@@ -138648,6 +139403,7 @@ const ThreadCleanupTool = Tool.make("thread_cleanup", {
|
|
|
138648
139403
|
ProjectionThreadRepository,
|
|
138649
139404
|
ProjectionThreadSessionRepository,
|
|
138650
139405
|
ThreadWorkspaceLifecycleService,
|
|
139406
|
+
TerminalManager,
|
|
138651
139407
|
Crypto.Crypto
|
|
138652
139408
|
]
|
|
138653
139409
|
}).annotate(Tool.Title, "Archive and clean up thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.Idempotent, true);
|
|
@@ -138736,7 +139492,44 @@ const ThreadContextUpdateTool = Tool.make("thread_context_update", {
|
|
|
138736
139492
|
Crypto.Crypto
|
|
138737
139493
|
]
|
|
138738
139494
|
}).annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
138739
|
-
const
|
|
139495
|
+
const ThreadWaitTool = Tool.make("thread_wait", {
|
|
139496
|
+
description: "Wait for sub-threads you spawned to stop working, then return each one's state (running, needs-input, completed, failed, missing), branch, worktree and latest reply. mode 'all' (default) waits for every one; 'any' returns at the first, so you can merge or answer it while the others continue. Returns timedOut true after timeoutSeconds (default 45) with the current states; call again to keep waiting. Use this instead of polling thread_watch_events.",
|
|
139497
|
+
parameters: ThreadWaitInput,
|
|
139498
|
+
success: ThreadWaitResult,
|
|
139499
|
+
failure: ThreadControlToolError,
|
|
139500
|
+
dependencies: [
|
|
139501
|
+
McpInvocationContext,
|
|
139502
|
+
OrchestrationEngineService,
|
|
139503
|
+
ProjectionSnapshotQuery,
|
|
139504
|
+
ThreadEventStreamService,
|
|
139505
|
+
ProjectionThreadMessageRepository
|
|
139506
|
+
]
|
|
139507
|
+
}).annotate(Tool.Title, "Wait for sub-threads").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
|
|
139508
|
+
const ThreadMessageTool = Tool.make("thread_message", {
|
|
139509
|
+
description: "Send a follow-up message to a sub-thread you spawned: a correction, an answer, or a request to resolve merge conflicts. If it is still working, the message waits until its current turn ends.",
|
|
139510
|
+
parameters: ThreadMessageInput,
|
|
139511
|
+
success: ThreadMessageResult,
|
|
139512
|
+
failure: ThreadControlToolError,
|
|
139513
|
+
dependencies: [
|
|
139514
|
+
McpInvocationContext,
|
|
139515
|
+
OrchestrationEngineService,
|
|
139516
|
+
ProjectionSnapshotQuery,
|
|
139517
|
+
Crypto.Crypto
|
|
139518
|
+
]
|
|
139519
|
+
}).annotate(Tool.Title, "Message a sub-thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
139520
|
+
const ThreadMergeChildTool = Tool.make("thread_merge_child", {
|
|
139521
|
+
description: "Merge a finished sub-thread's branch into this thread's worktree with a merge commit. Both worktrees must be committed and the sub-thread must have stopped. On conflict the merge is aborted, this worktree is left unchanged, and conflictedFiles lists the files: send them to the sub-thread with thread_message (ask it to merge this thread's branch, resolve, and commit), wait, then merge again. After landing, remove the sub-thread with thread_cleanup.",
|
|
139522
|
+
parameters: ThreadMergeChildInput,
|
|
139523
|
+
success: ThreadMergeChildResult,
|
|
139524
|
+
failure: ThreadControlToolError,
|
|
139525
|
+
dependencies: [
|
|
139526
|
+
McpInvocationContext,
|
|
139527
|
+
ProjectionSnapshotQuery,
|
|
139528
|
+
GitWorkflowService,
|
|
139529
|
+
GitVcsDriver
|
|
139530
|
+
]
|
|
139531
|
+
}).annotate(Tool.Title, "Merge a sub-thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
|
|
139532
|
+
const ThreadToolkit = Toolkit.make(ThreadContextReadTool, ThreadContextUpdateTool, ThreadPlanReadTool, ThreadPlanUpdateTool, AskUserQuestionTool, ThreadSpawnTool, ThreadWaitTool, ThreadMessageTool, ThreadMergeChildTool, ThreadPairCreateTool, ThreadPairDetachTool, ThreadConfigureTool, ThreadCleanupTool, ThreadSnoozeTool, ThreadRenameTool, MemoryAppendTool, AssetCompressTool);
|
|
138740
139533
|
//#endregion
|
|
138741
139534
|
//#region src/orchestration/pendingMcpUserInputs.ts
|
|
138742
139535
|
/**
|
|
@@ -138830,6 +139623,15 @@ const dispatchControl = Effect.fn("mcp.threads.dispatchControl")(function* (comm
|
|
|
138830
139623
|
detail: cause.message
|
|
138831
139624
|
})));
|
|
138832
139625
|
});
|
|
139626
|
+
/** Events that can change a sub-thread's wait state; streaming deltas cannot. */
|
|
139627
|
+
const SUB_THREAD_WAKE_EVENTS = /* @__PURE__ */ new Set([
|
|
139628
|
+
"thread.session-set",
|
|
139629
|
+
"thread.turn-completed",
|
|
139630
|
+
"thread.turn-start-requested",
|
|
139631
|
+
"thread.activity-appended",
|
|
139632
|
+
"thread.archived",
|
|
139633
|
+
"thread.deleted"
|
|
139634
|
+
]);
|
|
138833
139635
|
const newCommandId = Effect.gen(function* () {
|
|
138834
139636
|
const crypto = yield* Crypto.Crypto;
|
|
138835
139637
|
return CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
@@ -139146,6 +139948,39 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
|
139146
139948
|
const unpromptedSubagents = input.unpromptedSubagents ?? template.unpromptedSubagents === 1;
|
|
139147
139949
|
const threadId = ThreadId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
139148
139950
|
const createdAt = DateTime.formatIso(yield* DateTime.now);
|
|
139951
|
+
const isFusionSpawn = input.fusion === true || input.fusionWatcher === true;
|
|
139952
|
+
if (isFusionSpawn && input.workspace === "worktree") return yield* new ThreadSpawnNotPermittedError({
|
|
139953
|
+
threadId: invocation.threadId,
|
|
139954
|
+
detail: "Fusion spawns share this thread's checkout; omit workspace or pass 'shared'."
|
|
139955
|
+
});
|
|
139956
|
+
let childWorkspace = null;
|
|
139957
|
+
if (!isFusionSpawn && input.workspace !== "shared" && !isChatProject(projectId)) {
|
|
139958
|
+
const project = yield* (yield* ProjectionSnapshotQuery).getProjectShellById(projectId).pipe(Effect.map(Option.getOrUndefined), Effect.mapError((cause) => new ThreadSpawnNotPermittedError({
|
|
139959
|
+
threadId: invocation.threadId,
|
|
139960
|
+
detail: `the project could not be read to create a worktree: ${cause.message}`
|
|
139961
|
+
})));
|
|
139962
|
+
if (project !== void 0) {
|
|
139963
|
+
const repositoryRoot = project.workspaceRoot;
|
|
139964
|
+
const parentCwd = projectId === template.projectId ? template.worktreePath ?? repositoryRoot : repositoryRoot;
|
|
139965
|
+
const hex = (yield* crypto.randomUUIDv4.pipe(Effect.orDie)).replaceAll("-", "");
|
|
139966
|
+
const provisioned = yield* provisionChildWorktree({
|
|
139967
|
+
repositoryRoot,
|
|
139968
|
+
parentCwd,
|
|
139969
|
+
branch: buildTemporaryWorktreeBranchName((bytes) => hex.slice(0, bytes * 2))
|
|
139970
|
+
}).pipe(Effect.mapError((cause) => new ThreadSpawnNotPermittedError({
|
|
139971
|
+
threadId: invocation.threadId,
|
|
139972
|
+
detail: `the sub-thread's worktree could not be created: ${cause.message}`
|
|
139973
|
+
})));
|
|
139974
|
+
if (provisioned !== null) childWorkspace = {
|
|
139975
|
+
...provisioned,
|
|
139976
|
+
repositoryRoot
|
|
139977
|
+
};
|
|
139978
|
+
}
|
|
139979
|
+
if (childWorkspace === null && input.workspace === "worktree") return yield* new ThreadSpawnNotPermittedError({
|
|
139980
|
+
threadId: invocation.threadId,
|
|
139981
|
+
detail: "the project is not a git repository with a commit, so it cannot have worktrees"
|
|
139982
|
+
});
|
|
139983
|
+
}
|
|
139149
139984
|
yield* dispatchControl({
|
|
139150
139985
|
type: "thread.create",
|
|
139151
139986
|
commandId: yield* newCommandId,
|
|
@@ -139157,10 +139992,11 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
|
139157
139992
|
interactionMode,
|
|
139158
139993
|
compressMode,
|
|
139159
139994
|
unpromptedSubagents,
|
|
139160
|
-
branch: null,
|
|
139161
|
-
worktreePath: null,
|
|
139995
|
+
branch: childWorkspace?.branch ?? null,
|
|
139996
|
+
worktreePath: childWorkspace?.worktreePath ?? null,
|
|
139997
|
+
...isFusionSpawn || projectId !== template.projectId ? {} : { parentThreadId: invocation.threadId },
|
|
139162
139998
|
createdAt
|
|
139163
|
-
}, threadId);
|
|
139999
|
+
}, threadId).pipe(Effect.tapError(() => childWorkspace === null ? Effect.void : discardChildWorktree(childWorkspace)));
|
|
139164
140000
|
yield* registry.recordSpawnedThread({
|
|
139165
140001
|
providerSessionId: invocation.providerSessionId,
|
|
139166
140002
|
threadId,
|
|
@@ -139229,7 +140065,13 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
|
139229
140065
|
threadId,
|
|
139230
140066
|
projectId,
|
|
139231
140067
|
title: input.title,
|
|
139232
|
-
...fusionPair
|
|
140068
|
+
...fusionPair,
|
|
140069
|
+
...childWorkspace === null ? {} : {
|
|
140070
|
+
branch: childWorkspace.branch,
|
|
140071
|
+
worktreePath: childWorkspace.worktreePath,
|
|
140072
|
+
baseCommitSha: childWorkspace.baseCommitSha,
|
|
140073
|
+
parentDirty: childWorkspace.parentDirty
|
|
140074
|
+
}
|
|
139233
140075
|
};
|
|
139234
140076
|
}),
|
|
139235
140077
|
thread_pair_create: (input) => Effect.gen(function* () {
|
|
@@ -139486,6 +140328,11 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
|
139486
140328
|
})));
|
|
139487
140329
|
const archivedThreadIds = result.events?.filter((event) => event.type === "thread.archived").map((event) => event.payload.threadId) ?? [];
|
|
139488
140330
|
archivedThreadIds.forEach(queueThreadWorkspaceCleanup);
|
|
140331
|
+
const terminals = yield* TerminalManager;
|
|
140332
|
+
yield* Effect.forEach(archivedThreadIds, (archivedThreadId) => terminals.close({ threadId: archivedThreadId }).pipe(Effect.catch((error) => Effect.logWarning("failed to close thread terminals after archive", {
|
|
140333
|
+
threadId: archivedThreadId,
|
|
140334
|
+
error: error.message
|
|
140335
|
+
}))), { discard: true });
|
|
139489
140336
|
const stopRequested = new Set(result.events?.filter((event) => event.type === "thread.session-stop-requested").map((event) => event.payload.threadId) ?? []);
|
|
139490
140337
|
const session = yield* sessions.getByThreadId({ threadId }).pipe(Effect.mapError((cause) => new ThreadControlRejectedError({
|
|
139491
140338
|
threadId,
|
|
@@ -139648,6 +140495,126 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
|
|
|
139648
140495
|
estimatedTokensAfter: estimateTokens(compressed),
|
|
139649
140496
|
warnings: validation.warnings
|
|
139650
140497
|
};
|
|
140498
|
+
}),
|
|
140499
|
+
thread_wait: (input) => Effect.gen(function* () {
|
|
140500
|
+
const invocation = yield* requireThreadCapability();
|
|
140501
|
+
const threadIds = [...new Set(input.threadIds)];
|
|
140502
|
+
for (const threadId of threadIds) yield* requireThreadControlTarget(threadId);
|
|
140503
|
+
const query = yield* ProjectionSnapshotQuery;
|
|
140504
|
+
const engine = yield* OrchestrationEngineService;
|
|
140505
|
+
const eventStream = yield* ThreadEventStreamService;
|
|
140506
|
+
const messages = yield* ProjectionThreadMessageRepository;
|
|
140507
|
+
const mode = input.mode ?? "all";
|
|
140508
|
+
const rejected = (detail) => new ThreadControlRejectedError({
|
|
140509
|
+
threadId: invocation.threadId,
|
|
140510
|
+
commandType: "thread_wait",
|
|
140511
|
+
detail
|
|
140512
|
+
});
|
|
140513
|
+
const check = Effect.gen(function* () {
|
|
140514
|
+
const shells = yield* Effect.forEach(threadIds, (threadId) => query.getThreadShellById(threadId).pipe(Effect.map(Option.getOrUndefined)));
|
|
140515
|
+
const now = DateTime.formatIso(yield* DateTime.now);
|
|
140516
|
+
const states = shells.map((shell) => resolveSubThreadState(shell, now));
|
|
140517
|
+
return {
|
|
140518
|
+
shells,
|
|
140519
|
+
states,
|
|
140520
|
+
done: isSubThreadWaitSatisfied(states, mode)
|
|
140521
|
+
};
|
|
140522
|
+
}).pipe(Effect.mapError((cause) => rejected(cause.message)));
|
|
140523
|
+
const head = yield* engine.latestSequence;
|
|
140524
|
+
const initial = yield* check;
|
|
140525
|
+
const settled = initial.done ? Option.some(initial) : yield* Stream.mergeAll(threadIds.map((threadId) => eventStream.watch({
|
|
140526
|
+
threadId,
|
|
140527
|
+
afterSequence: head
|
|
140528
|
+
})), { concurrency: "unbounded" }).pipe(Stream.filter((item) => item.kind === "event" && SUB_THREAD_WAKE_EVENTS.has(item.event.type)), Stream.mapEffect(() => check), Stream.filter((result) => result.done), Stream.runHead, Effect.mapError((cause) => rejected(cause.message)), Effect.timeoutOption(Duration.seconds(input.timeoutSeconds ?? 45)), Effect.map(Option.flatten));
|
|
140529
|
+
const final = Option.isSome(settled) ? settled.value : yield* check;
|
|
140530
|
+
const children = yield* Effect.forEach(threadIds, (threadId, index) => Effect.gen(function* () {
|
|
140531
|
+
const shell = final.shells[index];
|
|
140532
|
+
const assistantMessageId = shell?.latestTurn?.assistantMessageId ?? null;
|
|
140533
|
+
const reply = assistantMessageId === null ? null : yield* messages.getByMessageId({ messageId: assistantMessageId }).pipe(Effect.map((row) => Option.isSome(row) ? row.value.text : null), Effect.catch(() => Effect.succeed(null)));
|
|
140534
|
+
return {
|
|
140535
|
+
threadId,
|
|
140536
|
+
title: shell?.title ?? null,
|
|
140537
|
+
state: final.states[index] ?? "missing",
|
|
140538
|
+
branch: shell?.branch ?? null,
|
|
140539
|
+
worktreePath: shell?.worktreePath ?? null,
|
|
140540
|
+
lastAssistantMessage: reply === null ? null : reply.length > 1500 ? `${reply.slice(0, 1500)}…` : reply,
|
|
140541
|
+
lastError: shell?.session?.lastError ?? null
|
|
140542
|
+
};
|
|
140543
|
+
}));
|
|
140544
|
+
return {
|
|
140545
|
+
timedOut: Option.isNone(settled),
|
|
140546
|
+
children
|
|
140547
|
+
};
|
|
140548
|
+
}),
|
|
140549
|
+
thread_message: (input) => Effect.gen(function* () {
|
|
140550
|
+
const { invocation, threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
140551
|
+
const rejected = (detail) => new ThreadControlRejectedError({
|
|
140552
|
+
threadId,
|
|
140553
|
+
commandType: "thread_message",
|
|
140554
|
+
detail
|
|
140555
|
+
});
|
|
140556
|
+
if (threadId === invocation.threadId) return yield* rejected("Message a sub-thread, not this thread.");
|
|
140557
|
+
const shell = yield* (yield* ProjectionSnapshotQuery).getThreadShellById(threadId).pipe(Effect.map(Option.getOrUndefined), Effect.mapError((cause) => rejected(cause.message)));
|
|
140558
|
+
if (shell === void 0) return yield* rejected("The sub-thread is archived or no longer exists.");
|
|
140559
|
+
const crypto = yield* Crypto.Crypto;
|
|
140560
|
+
const messageId = MessageId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
|
|
140561
|
+
yield* dispatchControl({
|
|
140562
|
+
type: "thread.turn.start",
|
|
140563
|
+
commandId: yield* newCommandId,
|
|
140564
|
+
threadId,
|
|
140565
|
+
message: {
|
|
140566
|
+
messageId,
|
|
140567
|
+
role: "user",
|
|
140568
|
+
text: input.text,
|
|
140569
|
+
attachments: []
|
|
140570
|
+
},
|
|
140571
|
+
modelSelection: shell.modelSelection,
|
|
140572
|
+
runtimeMode: shell.runtimeMode,
|
|
140573
|
+
interactionMode: shell.interactionMode,
|
|
140574
|
+
compressMode: shell.compressMode,
|
|
140575
|
+
unpromptedSubagents: shell.unpromptedSubagents,
|
|
140576
|
+
queueIfBusy: true,
|
|
140577
|
+
createdAt: DateTime.formatIso(yield* DateTime.now)
|
|
140578
|
+
}, threadId);
|
|
140579
|
+
return {
|
|
140580
|
+
threadId,
|
|
140581
|
+
messageId
|
|
140582
|
+
};
|
|
140583
|
+
}),
|
|
140584
|
+
thread_merge_child: (input) => Effect.gen(function* () {
|
|
140585
|
+
const { invocation, threadId } = yield* requireThreadControlTarget(input.threadId);
|
|
140586
|
+
const refused = (detail) => Effect.succeed({
|
|
140587
|
+
threadId,
|
|
140588
|
+
outcome: "refused",
|
|
140589
|
+
detail
|
|
140590
|
+
});
|
|
140591
|
+
const query = yield* ProjectionSnapshotQuery;
|
|
140592
|
+
const readShell = (id) => query.getThreadShellById(id).pipe(Effect.map(Option.getOrUndefined), Effect.mapError((cause) => new ThreadControlRejectedError({
|
|
140593
|
+
threadId,
|
|
140594
|
+
commandType: "thread_merge_child",
|
|
140595
|
+
detail: cause.message
|
|
140596
|
+
})));
|
|
140597
|
+
const child = yield* readShell(threadId);
|
|
140598
|
+
const parent = yield* readShell(invocation.threadId);
|
|
140599
|
+
if (child === void 0 || parent === void 0) return yield* refused("The sub-thread or this thread is archived or no longer exists.");
|
|
140600
|
+
if (child.parentThreadId !== invocation.threadId) return yield* refused("Only a sub-thread this thread spawned can be merged into it.");
|
|
140601
|
+
if (child.worktreePath === null) return yield* refused("The sub-thread shares this checkout, so there is nothing to merge.");
|
|
140602
|
+
const status = child.session?.status;
|
|
140603
|
+
if (status === "running" || status === "starting") return yield* refused("The sub-thread is still working. Wait for it with thread_wait.");
|
|
140604
|
+
const project = yield* query.getProjectShellById(child.projectId).pipe(Effect.map(Option.getOrUndefined), Effect.catch(() => Effect.succeed(void 0)));
|
|
140605
|
+
if (project === void 0) return yield* refused("The project could not be read.");
|
|
140606
|
+
return {
|
|
140607
|
+
threadId,
|
|
140608
|
+
...yield* mergeChildIntoParent({
|
|
140609
|
+
repositoryRoot: project.workspaceRoot,
|
|
140610
|
+
parentCwd: parent.worktreePath ?? project.workspaceRoot,
|
|
140611
|
+
childCwd: child.worktreePath
|
|
140612
|
+
}).pipe(Effect.mapError((cause) => new ThreadControlRejectedError({
|
|
140613
|
+
threadId,
|
|
140614
|
+
commandType: "thread_merge_child",
|
|
140615
|
+
detail: cause.message
|
|
140616
|
+
})))
|
|
140617
|
+
};
|
|
139651
140618
|
})
|
|
139652
140619
|
});
|
|
139653
140620
|
//#endregion
|