@p4code/cli 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -52,6 +52,7 @@ import * as SqlClient from "effect/unstable/sql/SqlClient";
52
52
  import * as SqlSchema from "effect/unstable/sql/SqlSchema";
53
53
  import * as PlatformError from "effect/PlatformError";
54
54
  import * as Migrator from "effect/unstable/sql/Migrator";
55
+ import * as NodeUtil from "node:util";
55
56
  import * as Cause from "effect/Cause";
56
57
  import * as Exit from "effect/Exit";
57
58
  import * as NodeReadline from "node:readline";
@@ -127,7 +128,7 @@ const closeServer = (server) => {
127
128
  * NetService - Service tag for startup networking helpers.
128
129
  */
129
130
  var NetService = class extends Context.Service()("@p4code/shared/Net/NetService") {};
130
- const make$92 = () => {
131
+ const make$93 = () => {
131
132
  /**
132
133
  * Returns true when a TCP server can bind to {host, port}.
133
134
  * `EADDRNOTAVAIL` is treated as available so IPv6-absent hosts don't fail
@@ -236,10 +237,10 @@ const make$92 = () => {
236
237
  })
237
238
  };
238
239
  };
239
- const layer$82 = Layer.sync(NetService, make$92);
240
+ const layer$82 = Layer.sync(NetService, make$93);
240
241
  //#endregion
241
242
  //#region package.json
242
- var version = "0.4.7";
243
+ var version = "0.4.9";
243
244
  //#endregion
244
245
  //#region src/config.ts
245
246
  /**
@@ -260,8 +261,8 @@ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerC
260
261
  /** @deprecated Import and use `layerTest` from this module. */
261
262
  static layerTest = (cwd, baseDirOrPrefix) => layerTest$3(cwd, baseDirOrPrefix);
262
263
  };
263
- const make$91 = (config) => ServerConfig$1.of(config);
264
- const layer$81 = (config) => Layer.succeed(ServerConfig$1, make$91(config));
264
+ const make$92 = (config) => ServerConfig$1.of(config);
265
+ const layer$81 = (config) => Layer.succeed(ServerConfig$1, make$92(config));
265
266
  const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
266
267
  const { join } = yield* Path.Path;
267
268
  const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
@@ -1321,6 +1322,7 @@ const ServerSelfUpdateCapability = Schema$1.Literals([
1321
1322
  "desktop-managed"
1322
1323
  ]);
1323
1324
  const ExecutionEnvironmentCapabilities = Schema$1.Struct({
1325
+ threadLifecycleV2: Schema$1.optionalKey(Schema$1.Boolean),
1324
1326
  repositoryIdentity: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
1325
1327
  connectionProbe: Schema$1.optionalKey(Schema$1.Boolean),
1326
1328
  /** Server understands thread.settle / thread.unsettle commands. Absent on
@@ -2001,7 +2003,31 @@ const ThreadWorkspaceLifecycle = Schema$1.Struct({
2001
2003
  mergeCommitSha: Schema$1.NullOr(TrimmedNonEmptyString),
2002
2004
  updatedAt: IsoDateTime
2003
2005
  });
2006
+ const ThreadLifecycle = Schema$1.Literals([
2007
+ "active",
2008
+ "done",
2009
+ "archived"
2010
+ ]);
2011
+ const ThreadLifecycleReason = Schema$1.Literals([
2012
+ "created",
2013
+ "manual",
2014
+ "inactivity",
2015
+ "pull-request",
2016
+ "user",
2017
+ "activity",
2018
+ "archive",
2019
+ "restore"
2020
+ ]);
2021
+ /** Optional during mixed-version support; v2 servers populate every field. */
2022
+ const ThreadCompletionFields = {
2023
+ lifecycle: Schema$1.optional(ThreadLifecycle),
2024
+ lifecycleChangedAt: Schema$1.optional(IsoDateTime),
2025
+ lifecycleReason: Schema$1.optional(ThreadLifecycleReason),
2026
+ doneAt: Schema$1.optional(Schema$1.NullOr(IsoDateTime)),
2027
+ lastEngagedAt: Schema$1.optional(IsoDateTime)
2028
+ };
2004
2029
  const OrchestrationThread = Schema$1.Struct({
2030
+ ...ThreadCompletionFields,
2005
2031
  id: ThreadId,
2006
2032
  projectId: ProjectId,
2007
2033
  title: TrimmedNonEmptyString,
@@ -2105,6 +2131,7 @@ const OrchestrationProjectShell = Schema$1.Struct({
2105
2131
  updatedAt: IsoDateTime
2106
2132
  });
2107
2133
  const OrchestrationThreadShell = Schema$1.Struct({
2134
+ ...ThreadCompletionFields,
2108
2135
  id: ThreadId,
2109
2136
  projectId: ProjectId,
2110
2137
  title: TrimmedNonEmptyString,
@@ -2362,6 +2389,25 @@ const ThreadUnsettleCommand = Schema$1.Struct({
2362
2389
  threadId: ThreadId,
2363
2390
  reason: Schema$1.Literal("user")
2364
2391
  });
2392
+ const ThreadCompleteCommand = Schema$1.Struct({
2393
+ type: Schema$1.Literal("thread.complete"),
2394
+ commandId: CommandId,
2395
+ threadId: ThreadId
2396
+ });
2397
+ const ThreadReopenCommand = Schema$1.Struct({
2398
+ type: Schema$1.Literal("thread.reopen"),
2399
+ commandId: CommandId,
2400
+ threadId: ThreadId
2401
+ });
2402
+ /** Server effects propose completion against the engagement state they inspected. */
2403
+ const ThreadAutoCompleteCommand = Schema$1.Struct({
2404
+ type: Schema$1.Literal("thread.complete.auto"),
2405
+ commandId: CommandId,
2406
+ threadId: ThreadId,
2407
+ reason: Schema$1.Literals(["inactivity", "pull-request"]),
2408
+ expectedLastEngagedAt: IsoDateTime,
2409
+ eligibleAt: IsoDateTime
2410
+ });
2365
2411
  const ThreadSnoozeCommand = Schema$1.Struct({
2366
2412
  type: Schema$1.Literal("thread.snooze"),
2367
2413
  commandId: CommandId,
@@ -2590,6 +2636,8 @@ const DispatchableClientOrchestrationCommand = Schema$1.Union([
2590
2636
  ThreadArchiveCommand,
2591
2637
  ThreadUnarchiveCommand,
2592
2638
  ThreadSettleCommand,
2639
+ ThreadCompleteCommand,
2640
+ ThreadReopenCommand,
2593
2641
  ThreadUnsettleCommand,
2594
2642
  ThreadSnoozeCommand,
2595
2643
  ThreadUnsnoozeCommand,
@@ -2624,6 +2672,8 @@ const ClientOrchestrationCommand = Schema$1.Union([
2624
2672
  ThreadArchiveCommand,
2625
2673
  ThreadUnarchiveCommand,
2626
2674
  ThreadSettleCommand,
2675
+ ThreadCompleteCommand,
2676
+ ThreadReopenCommand,
2627
2677
  ThreadUnsettleCommand,
2628
2678
  ThreadSnoozeCommand,
2629
2679
  ThreadUnsnoozeCommand,
@@ -2763,6 +2813,7 @@ const ThreadRevertCompleteCommand = Schema$1.Struct({
2763
2813
  createdAt: IsoDateTime
2764
2814
  });
2765
2815
  const InternalOrchestrationCommand = Schema$1.Union([
2816
+ ThreadAutoCompleteCommand,
2766
2817
  ThreadSessionSetCommand,
2767
2818
  ThreadSessionForceStopConvergeCommand,
2768
2819
  ThreadTurnCompleteCommand,
@@ -2856,6 +2907,7 @@ const ProjectDeletedPayload$1 = Schema$1.Struct({
2856
2907
  deletedAt: IsoDateTime
2857
2908
  });
2858
2909
  const ThreadCreatedPayload$1 = Schema$1.Struct({
2910
+ ...ThreadCompletionFields,
2859
2911
  threadId: ThreadId,
2860
2912
  projectId: ProjectId,
2861
2913
  title: TrimmedNonEmptyString,
@@ -2879,20 +2931,24 @@ const ThreadDeletedPayload$1 = Schema$1.Struct({
2879
2931
  deletedAt: IsoDateTime
2880
2932
  });
2881
2933
  const ThreadArchivedPayload$1 = Schema$1.Struct({
2934
+ ...ThreadCompletionFields,
2882
2935
  threadId: ThreadId,
2883
2936
  archivedAt: IsoDateTime,
2884
2937
  updatedAt: IsoDateTime
2885
2938
  });
2886
2939
  const ThreadUnarchivedPayload$1 = Schema$1.Struct({
2940
+ ...ThreadCompletionFields,
2887
2941
  threadId: ThreadId,
2888
2942
  updatedAt: IsoDateTime
2889
2943
  });
2890
2944
  const ThreadSettledPayload$1 = Schema$1.Struct({
2945
+ ...ThreadCompletionFields,
2891
2946
  threadId: ThreadId,
2892
2947
  settledAt: IsoDateTime,
2893
2948
  updatedAt: IsoDateTime
2894
2949
  });
2895
2950
  const ThreadUnsettledPayload$1 = Schema$1.Struct({
2951
+ ...ThreadCompletionFields,
2896
2952
  threadId: ThreadId,
2897
2953
  reason: Schema$1.Literals(["user", "activity"]),
2898
2954
  updatedAt: IsoDateTime
@@ -3696,6 +3752,7 @@ Schema$1.Struct({
3696
3752
  headRefName: TrimmedNonEmptyString,
3697
3753
  state: ChangeRequestState,
3698
3754
  updatedAt: Schema$1.Option(Schema$1.DateTimeUtc),
3755
+ terminalAt: Schema$1.optional(Schema$1.NullOr(IsoDateTime)),
3699
3756
  isCrossRepository: Schema$1.optional(Schema$1.Boolean),
3700
3757
  headRepositoryNameWithOwner: Schema$1.optional(Schema$1.NullOr(TrimmedNonEmptyString)),
3701
3758
  headRepositoryOwnerLogin: Schema$1.optional(Schema$1.NullOr(TrimmedNonEmptyString))
@@ -7637,6 +7694,7 @@ const STATIC_KEYBINDING_COMMANDS = [
7637
7694
  "preview.zoomOut",
7638
7695
  "preview.resetZoom",
7639
7696
  "commandPalette.toggle",
7697
+ "workspace.searchContents",
7640
7698
  "composer.stash",
7641
7699
  "chat.new",
7642
7700
  "chat.newLocal",
@@ -8482,6 +8540,7 @@ const SourceControlWritingStyleSettings = Schema$1.Struct({
8482
8540
  });
8483
8541
  const DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL = Duration.seconds(30);
8484
8542
  const ServerSettings = Schema$1.Struct({
8543
+ threadAutoCompleteAfterDays: Schema$1.NullOr(SidebarAutoSettleAfterDays).pipe(Schema$1.withDecodingDefault(Effect.succeed(3))),
8485
8544
  enableAssistantStreaming: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
8486
8545
  enableProviderUpdateChecks: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
8487
8546
  /**
@@ -8699,6 +8758,7 @@ const OpenCodeSettingsPatch = Schema$1.Struct({
8699
8758
  customModels: Schema$1.optionalKey(Schema$1.Array(Schema$1.String))
8700
8759
  });
8701
8760
  const ServerSettingsPatch = Schema$1.Struct({
8761
+ threadAutoCompleteAfterDays: Schema$1.optionalKey(Schema$1.NullOr(SidebarAutoSettleAfterDays)),
8702
8762
  enableAssistantStreaming: Schema$1.optionalKey(Schema$1.Boolean),
8703
8763
  enableProviderUpdateChecks: Schema$1.optionalKey(Schema$1.Boolean),
8704
8764
  enableToolCallNarration: Schema$1.optionalKey(Schema$1.Boolean),
@@ -9611,6 +9671,23 @@ const ProjectSearchEntriesResult = Schema$1.Struct({
9611
9671
  entries: Schema$1.Array(ProjectEntry),
9612
9672
  truncated: Schema$1.Boolean
9613
9673
  });
9674
+ const ProjectSearchContentsInput = Schema$1.Struct({
9675
+ cwd: TrimmedNonEmptyString,
9676
+ query: Schema$1.String.check(Schema$1.isNonEmpty(), Schema$1.isMaxLength(256)),
9677
+ limit: PositiveInt.check(Schema$1.isLessThanOrEqualTo(500))
9678
+ });
9679
+ const ProjectSearchContentsResult = Schema$1.Struct({
9680
+ matches: Schema$1.Array(Schema$1.Struct({
9681
+ path: TrimmedNonEmptyString,
9682
+ lineNumber: PositiveInt,
9683
+ lineContent: Schema$1.String,
9684
+ matchRanges: Schema$1.Array(Schema$1.Struct({
9685
+ start: NonNegativeInt,
9686
+ end: NonNegativeInt
9687
+ }))
9688
+ })),
9689
+ truncated: Schema$1.Boolean
9690
+ });
9614
9691
  const ProjectListEntriesInput = Schema$1.Struct({ cwd: TrimmedNonEmptyString });
9615
9692
  const ProjectListEntriesResult = Schema$1.Struct({
9616
9693
  entries: Schema$1.Array(ProjectEntry),
@@ -10002,6 +10079,18 @@ const ThreadRenameResult = Schema$1.Struct({
10002
10079
  threadId: ThreadId,
10003
10080
  title: TrimmedNonEmptyString
10004
10081
  });
10082
+ const ThreadPlanUpdateInput = Schema$1.Struct({
10083
+ explanation: Schema$1.optional(TrimmedNonEmptyString),
10084
+ plan: Schema$1.Array(Schema$1.Struct({
10085
+ step: TrimmedNonEmptyString,
10086
+ status: Schema$1.Literals([
10087
+ "pending",
10088
+ "in_progress",
10089
+ "completed"
10090
+ ])
10091
+ }))
10092
+ });
10093
+ const ThreadPlanUpdateResult = Schema$1.Struct({ threadId: ThreadId });
10005
10094
  /**
10006
10095
  * Which thread a control tool acts on.
10007
10096
  *
@@ -10040,13 +10129,16 @@ const ThreadSpawnInput = Schema$1.Struct({
10040
10129
  interactionMode: Schema$1.optional(ProviderInteractionMode),
10041
10130
  compressMode: Schema$1.optional(CompressMode),
10042
10131
  unpromptedSubagents: Schema$1.optional(Schema$1.Boolean),
10043
- fusionWatcher: Schema$1.optional(Schema$1.Boolean.annotate({ description: "Set true only when creating the watcher for a Fusion pair. The server requires explicit user approval before creating the thread." }))
10132
+ fusion: Schema$1.optional(Schema$1.Boolean.annotate({ description: "Create two new threads as a separate Fusion pair, then start the builder with prompt. Leaves the caller and its existing pair unchanged. Cannot be combined with fusionWatcher." })),
10133
+ 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." }))
10044
10134
  });
10045
10135
  const ThreadSpawnResult = Schema$1.Struct({
10046
10136
  /** Watchable with `thread_watch_events`, and addressable by every tool here. */
10047
10137
  threadId: ThreadId,
10048
10138
  projectId: ProjectId,
10049
- title: TrimmedNonEmptyString
10139
+ title: TrimmedNonEmptyString,
10140
+ pairId: Schema$1.optional(ThreadPairId),
10141
+ watcherThreadId: Schema$1.optional(ThreadId)
10050
10142
  });
10051
10143
  const ThreadPairCreateInput = Schema$1.Struct({
10052
10144
  watcherThreadId: ThreadId.annotate({ description: "The supervisor thread to pair with this session's own builder thread. It must have been created by this session through thread_spawn." }),
@@ -10218,12 +10310,6 @@ var ThreadSpawnNotPermittedError = class extends Schema$1.TaggedErrorClass()("Th
10218
10310
  return `Thread ${this.threadId} cannot start another thread: ${this.detail}`;
10219
10311
  }
10220
10312
  };
10221
- /** Fusion watcher and pair creation require fresh user authorization from this exact thread. */
10222
- var ThreadPairApprovalRequiredError = class extends Schema$1.TaggedErrorClass()("ThreadPairApprovalRequiredError", { threadId: ThreadId }) {
10223
- get message() {
10224
- return `Thread ${this.threadId} cannot create a Fusion watcher or pair without explicit user approval. The latest user message must invoke /fusion or $fusion on its own line, or affirm the immediately preceding assistant proposal that names Fusion and asks for approval.`;
10225
- }
10226
- };
10227
10313
  /** The orchestration engine declined or failed a thread control command. */
10228
10314
  var ThreadControlRejectedError = class extends Schema$1.TaggedErrorClass()("ThreadControlRejectedError", {
10229
10315
  threadId: ThreadId,
@@ -10238,7 +10324,6 @@ const ThreadControlToolError = Schema$1.Union([
10238
10324
  ThreadToolUnavailableError,
10239
10325
  ThreadControlNotPermittedError,
10240
10326
  ThreadSpawnNotPermittedError,
10241
- ThreadPairApprovalRequiredError,
10242
10327
  ThreadControlRejectedError,
10243
10328
  ThreadRenameRejectedError,
10244
10329
  MemoryAppendFailedError,
@@ -10262,6 +10347,45 @@ const ThreadWatchEventsResult = Schema$1.Struct({
10262
10347
  /** True when more events for this thread exist past the returned page. */
10263
10348
  hasMore: Schema$1.Boolean
10264
10349
  });
10350
+ /** Compact review limits are server-owned, independent of raw event paging. */
10351
+ const THREAD_REVIEW_SCAN_LIMIT = 2e3;
10352
+ const THREAD_REVIEW_ITEM_MAX_BYTES = 8e3;
10353
+ const ThreadWatchReviewInput = Schema$1.Struct({
10354
+ threadId: ThreadId,
10355
+ afterSequence: Schema$1.optional(NonNegativeInt),
10356
+ throughSequence: Schema$1.optional(NonNegativeInt.annotate({ description: "Inclusive fixed review boundary. Omit on the first page to capture the head; reuse the returned boundary on every subsequent page." }))
10357
+ });
10358
+ const ReviewSourceFields = {
10359
+ firstSequence: NonNegativeInt,
10360
+ lastSequence: NonNegativeInt,
10361
+ truncated: Schema$1.Boolean
10362
+ };
10363
+ const ThreadReviewItem = Schema$1.Union([Schema$1.Struct({
10364
+ ...ReviewSourceFields,
10365
+ kind: Schema$1.Literal("message"),
10366
+ messageId: Schema$1.String,
10367
+ turnId: Schema$1.NullOr(Schema$1.String),
10368
+ role: Schema$1.String,
10369
+ /** Apply append/replace by message identity across page boundaries. */
10370
+ operation: Schema$1.Literals(["append", "replace"]),
10371
+ complete: Schema$1.Boolean,
10372
+ text: Schema$1.String
10373
+ }), Schema$1.Struct({
10374
+ ...ReviewSourceFields,
10375
+ kind: Schema$1.Literal("event"),
10376
+ type: Schema$1.String,
10377
+ /** JSON evidence excerpt. Truncated excerpts require targeted raw recovery. */
10378
+ payload: Schema$1.String
10379
+ })]);
10380
+ const ThreadWatchReviewResult = Schema$1.Struct({
10381
+ threadId: ThreadId,
10382
+ items: Schema$1.Array(ThreadReviewItem),
10383
+ throughSequence: NonNegativeInt,
10384
+ /** Resume from the source scan cursor, even when items is empty. */
10385
+ nextAfterSequence: NonNegativeInt,
10386
+ hasMore: Schema$1.Boolean,
10387
+ scannedEvents: NonNegativeInt
10388
+ });
10265
10389
  var WatchToolUnavailableError = class extends Schema$1.TaggedErrorClass()("WatchToolUnavailableError", {
10266
10390
  capability: Schema$1.Literal("watch"),
10267
10391
  environmentId: EnvironmentId,
@@ -11527,6 +11651,7 @@ const WS_METHODS = {
11527
11651
  projectsRemove: "projects.remove",
11528
11652
  projectsListEntries: "projects.listEntries",
11529
11653
  projectsReadFile: "projects.readFile",
11654
+ projectsSearchContents: "projects.searchContents",
11530
11655
  projectsSearchEntries: "projects.searchEntries",
11531
11656
  projectsWriteFile: "projects.writeFile",
11532
11657
  shellOpenInEditor: "shell.openInEditor",
@@ -12237,6 +12362,11 @@ const WsSourceControlPublishRepositoryRpc = Rpc.make(WS_METHODS.sourceControlPub
12237
12362
  success: SourceControlPublishRepositoryResult,
12238
12363
  error: Schema$1.Union([SourceControlRepositoryError, EnvironmentAuthorizationError])
12239
12364
  });
12365
+ const WsProjectsSearchContentsRpc = Rpc.make(WS_METHODS.projectsSearchContents, {
12366
+ payload: ProjectSearchContentsInput,
12367
+ success: ProjectSearchContentsResult,
12368
+ error: Schema$1.Union([ProjectSearchEntriesError, EnvironmentAuthorizationError])
12369
+ });
12240
12370
  const WsProjectsSearchEntriesRpc = Rpc.make(WS_METHODS.projectsSearchEntries, {
12241
12371
  payload: ProjectSearchEntriesInput,
12242
12372
  success: ProjectSearchEntriesResult,
@@ -12502,7 +12632,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
12502
12632
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
12503
12633
  stream: true
12504
12634
  });
12505
- const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsBtwAskRpc, WsBtwCancelRpc, WsServerGetUsageSummaryRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsFeedListRpc, WsFeedRefreshRpc, WsFeedSourcesListRpc, WsFeedSourcesUpsertRpc, WsFeedSourcesDeleteRpc, WsFeedMarkReadRpc, WsFeedCleanupRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsTrackerStatusRpc, WsTrackerSetApiKeyRpc, WsTrackerClearApiKeyRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationSearchThreadsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
12635
+ const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsBtwAskRpc, WsBtwCancelRpc, WsServerGetUsageSummaryRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchContentsRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsFeedListRpc, WsFeedRefreshRpc, WsFeedSourcesListRpc, WsFeedSourcesUpsertRpc, WsFeedSourcesDeleteRpc, WsFeedMarkReadRpc, WsFeedCleanupRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsTrackerStatusRpc, WsTrackerSetApiKeyRpc, WsTrackerClearApiKeyRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationSearchThreadsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
12506
12636
  //#endregion
12507
12637
  //#region ../../packages/shared/src/oauthScope.ts
12508
12638
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -12648,7 +12778,7 @@ function deriveAuthClientMetadata(input) {
12648
12778
  //#endregion
12649
12779
  //#region src/auth/EnvironmentAuthPolicy.ts
12650
12780
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
12651
- const make$90 = Effect.gen(function* () {
12781
+ const make$91 = Effect.gen(function* () {
12652
12782
  const config = yield* ServerConfig$1;
12653
12783
  const isRemoteReachable = isRemoteReachableHost(config.host);
12654
12784
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
@@ -12666,7 +12796,7 @@ const make$90 = Effect.gen(function* () {
12666
12796
  };
12667
12797
  return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
12668
12798
  });
12669
- const layer$80 = Layer.effect(EnvironmentAuthPolicy, make$90);
12799
+ const layer$80 = Layer.effect(EnvironmentAuthPolicy, make$91);
12670
12800
  //#endregion
12671
12801
  //#region src/persistence/Errors.ts
12672
12802
  function summarizeSchemaIssue(issue) {
@@ -12847,7 +12977,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
12847
12977
  cause
12848
12978
  });
12849
12979
  }
12850
- const make$89 = Effect.gen(function* () {
12980
+ const make$90 = Effect.gen(function* () {
12851
12981
  const sql = yield* SqlClient.SqlClient;
12852
12982
  const createSessionRow = SqlSchema.void({
12853
12983
  Request: CreateAuthSessionInput,
@@ -12981,7 +13111,7 @@ const make$89 = Effect.gen(function* () {
12981
13111
  setLastConnectedAt
12982
13112
  };
12983
13113
  });
12984
- const layer$79 = Layer.effect(AuthSessionRepository, make$89);
13114
+ const layer$79 = Layer.effect(AuthSessionRepository, make$90);
12985
13115
  //#endregion
12986
13116
  //#region src/auth/ServerSecretStore.ts
12987
13117
  const secretStoreErrorContext = {
@@ -13048,7 +13178,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
13048
13178
  const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
13049
13179
  const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
13050
13180
  var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
13051
- const make$88 = Effect.gen(function* () {
13181
+ const make$89 = Effect.gen(function* () {
13052
13182
  const crypto = yield* Crypto.Crypto;
13053
13183
  const fileSystem = yield* FileSystem.FileSystem;
13054
13184
  const path = yield* Path.Path;
@@ -13124,7 +13254,7 @@ const make$88 = Effect.gen(function* () {
13124
13254
  remove
13125
13255
  });
13126
13256
  });
13127
- const layer$78 = Layer.effect(ServerSecretStore, make$88);
13257
+ const layer$78 = Layer.effect(ServerSecretStore, make$89);
13128
13258
  //#endregion
13129
13259
  //#region src/auth/SessionStore.ts
13130
13260
  var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
@@ -13362,7 +13492,7 @@ function toAuthClientSession(input) {
13362
13492
  current: false
13363
13493
  };
13364
13494
  }
13365
- const make$87 = Effect.gen(function* () {
13495
+ const make$88 = Effect.gen(function* () {
13366
13496
  const crypto = yield* Crypto.Crypto;
13367
13497
  const serverConfig = yield* ServerConfig$1;
13368
13498
  const secretStore = yield* ServerSecretStore;
@@ -13676,7 +13806,7 @@ const make$87 = Effect.gen(function* () {
13676
13806
  markDisconnected
13677
13807
  });
13678
13808
  });
13679
- const layer$77 = Layer.effect(SessionStore, make$87).pipe(Layer.provideMerge(layer$79));
13809
+ const layer$77 = Layer.effect(SessionStore, make$88).pipe(Layer.provideMerge(layer$79));
13680
13810
  //#endregion
13681
13811
  //#region src/persistence/AuthPairingLinks.ts
13682
13812
  const AuthPairingLinkRecord = Schema$1.Struct({
@@ -13737,7 +13867,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
13737
13867
  cause
13738
13868
  });
13739
13869
  }
13740
- const make$86 = Effect.gen(function* () {
13870
+ const make$87 = Effect.gen(function* () {
13741
13871
  const sql = yield* SqlClient.SqlClient;
13742
13872
  const createPairingLinkRow = SqlSchema.void({
13743
13873
  Request: CreateAuthPairingLinkInput,
@@ -13872,7 +14002,7 @@ const make$86 = Effect.gen(function* () {
13872
14002
  getByCredential
13873
14003
  };
13874
14004
  });
13875
- const layer$76 = Layer.effect(AuthPairingLinkRepository, make$86);
14005
+ const layer$76 = Layer.effect(AuthPairingLinkRepository, make$87);
13876
14006
  //#endregion
13877
14007
  //#region src/auth/PairingGrantStore.ts
13878
14008
  var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
@@ -13967,7 +14097,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
13967
14097
  const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
13968
14098
  const PAIRING_TOKEN_LENGTH = 12;
13969
14099
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
13970
- const make$85 = Effect.gen(function* () {
14100
+ const make$86 = Effect.gen(function* () {
13971
14101
  const crypto = yield* Crypto.Crypto;
13972
14102
  const config = yield* ServerConfig$1;
13973
14103
  const pairingLinks = yield* AuthPairingLinkRepository;
@@ -14165,7 +14295,7 @@ const make$85 = Effect.gen(function* () {
14165
14295
  consume
14166
14296
  });
14167
14297
  });
14168
- const layer$75 = Layer.effect(PairingGrantStore, make$85).pipe(Layer.provideMerge(layer$76));
14298
+ const layer$75 = Layer.effect(PairingGrantStore, make$86).pipe(Layer.provideMerge(layer$76));
14169
14299
  //#endregion
14170
14300
  //#region src/persistence/DatabaseSnapshot.ts
14171
14301
  /**
@@ -16111,6 +16241,31 @@ var _054_ProjectionThreadScheduledTasks_default = Effect.gen(function* () {
16111
16241
  `;
16112
16242
  });
16113
16243
  //#endregion
16244
+ //#region src/persistence/Migrations/055_ThreadCompletionLifecycle.ts
16245
+ var _055_ThreadCompletionLifecycle_default = Effect.gen(function* () {
16246
+ const sql = yield* SqlClient.SqlClient;
16247
+ yield* sql`ALTER TABLE projection_threads ADD COLUMN lifecycle TEXT NOT NULL DEFAULT 'active'`;
16248
+ yield* sql`ALTER TABLE projection_threads ADD COLUMN lifecycle_reason TEXT NOT NULL DEFAULT 'created'`;
16249
+ yield* sql`ALTER TABLE projection_threads ADD COLUMN lifecycle_changed_at TEXT`;
16250
+ yield* sql`ALTER TABLE projection_threads ADD COLUMN last_engaged_at TEXT`;
16251
+ yield* sql`
16252
+ UPDATE projection_threads SET
16253
+ lifecycle = CASE WHEN archived_at IS NOT NULL THEN 'archived'
16254
+ WHEN settled_override = 'settled' THEN 'done' ELSE 'active' END,
16255
+ lifecycle_reason = CASE WHEN archived_at IS NOT NULL THEN 'archive'
16256
+ WHEN settled_override = 'settled' THEN 'manual' ELSE 'created' END,
16257
+ lifecycle_changed_at = COALESCE(archived_at, settled_at, created_at),
16258
+ last_engaged_at = MAX(created_at, COALESCE(latest_user_message_at, created_at),
16259
+ COALESCE((SELECT MAX(message.updated_at) FROM projection_thread_messages message
16260
+ WHERE message.thread_id = projection_threads.thread_id AND message.role IN ('user', 'assistant')), created_at)),
16261
+ settled_override = CASE WHEN settled_override = 'settled' THEN 'settled' ELSE 'active' END
16262
+ `;
16263
+ yield* sql`CREATE INDEX idx_projection_threads_lifecycle_sweep
16264
+ ON projection_threads(lifecycle, deleted_at, thread_id)`;
16265
+ yield* sql`CREATE INDEX idx_projection_threads_lifecycle_inactivity
16266
+ ON projection_threads(lifecycle, deleted_at, last_engaged_at, thread_id)`;
16267
+ });
16268
+ //#endregion
16114
16269
  //#region src/persistence/Migrations.ts
16115
16270
  /**
16116
16271
  * MigrationsLive - Migration runner with inline loader
@@ -16401,6 +16556,11 @@ const migrationEntries = [
16401
16556
  54,
16402
16557
  "ProjectionThreadScheduledTasks",
16403
16558
  _054_ProjectionThreadScheduledTasks_default
16559
+ ],
16560
+ [
16561
+ 55,
16562
+ "ThreadCompletionLifecycle",
16563
+ _055_ThreadCompletionLifecycle_default
16404
16564
  ]
16405
16565
  ];
16406
16566
  const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -16996,12 +17156,42 @@ function dropStaleContextWindowActivities(activities) {
16996
17156
  return [index === breakdownIndex ? activity : withoutContextWindowBreakdown$1(activity)];
16997
17157
  });
16998
17158
  }
17159
+ function toolLifecycleIdentity(activity) {
17160
+ return asTrimmedString$2(asRecord$6(asRecord$6(activity.payload)?.data)?.toolCallId);
17161
+ }
17162
+ /** Clients retain fields missing from a completion when folding an update. */
17163
+ function completionPreservesUpdate(update, completion) {
17164
+ const updatePayload = asRecord$6(update.payload);
17165
+ const completionPayload = asRecord$6(completion.payload);
17166
+ if (!updatePayload || !completionPayload) return false;
17167
+ return Object.entries(updatePayload).every(([key, value]) => key === "status" || NodeUtil.isDeepStrictEqual(value, completionPayload[key]));
17168
+ }
17169
+ /** Remove only updates superseded by a later completion in the same turn. */
17170
+ function dropSupersededToolUpdatedActivities(activities) {
17171
+ const lastCompletionIndexByKey = /* @__PURE__ */ new Map();
17172
+ for (let index = 0; index < activities.length; index += 1) {
17173
+ const activity = activities[index];
17174
+ if (activity.kind !== "tool.completed") continue;
17175
+ const identity = toolLifecycleIdentity(activity);
17176
+ if (!identity) continue;
17177
+ const key = `${activity.turnId ?? ""}\u0000${identity}`;
17178
+ lastCompletionIndexByKey.set(key, index);
17179
+ }
17180
+ if (lastCompletionIndexByKey.size === 0) return activities;
17181
+ return activities.filter((activity, index) => {
17182
+ if (activity.kind !== "tool.updated") return true;
17183
+ const identity = toolLifecycleIdentity(activity);
17184
+ if (!identity) return true;
17185
+ const completionIndex = lastCompletionIndexByKey.get(`${activity.turnId ?? ""}\u0000${identity}`);
17186
+ return completionIndex === void 0 || completionIndex <= index || !completionPreservesUpdate(activity, activities[completionIndex]);
17187
+ });
17188
+ }
16999
17189
  function projectThreadDetailSnapshot(snapshot) {
17000
17190
  return {
17001
17191
  ...snapshot,
17002
17192
  thread: {
17003
17193
  ...snapshot.thread,
17004
- activities: dropStaleContextWindowActivities(snapshot.thread.activities).map(projectActivityPayload)
17194
+ activities: dropSupersededToolUpdatedActivities(dropStaleContextWindowActivities(snapshot.thread.activities).map(projectActivityPayload))
17005
17195
  }
17006
17196
  };
17007
17197
  }
@@ -17399,7 +17589,7 @@ function parseBearerToken(request) {
17399
17589
  const token = header.slice(7).trim();
17400
17590
  return token.length > 0 ? token : null;
17401
17591
  }
17402
- const make$84 = Effect.gen(function* () {
17592
+ const make$85 = Effect.gen(function* () {
17403
17593
  const policy = yield* EnvironmentAuthPolicy;
17404
17594
  const bootstrapCredentials = yield* PairingGrantStore;
17405
17595
  const sessions = yield* SessionStore;
@@ -17594,7 +17784,7 @@ const make$84 = Effect.gen(function* () {
17594
17784
  issueStartupPairingUrl
17595
17785
  });
17596
17786
  });
17597
- const layer$74 = Layer.effect(EnvironmentAuth, make$84).pipe(Layer.provideMerge(layer$75), Layer.provideMerge(layer$77), Layer.provideMerge(layer$80));
17787
+ const layer$74 = Layer.effect(EnvironmentAuth, make$85).pipe(Layer.provideMerge(layer$75), Layer.provideMerge(layer$77), Layer.provideMerge(layer$80));
17598
17788
  const storageLayer = Layer.mergeAll(layer$78, layerConfig);
17599
17789
  const runtimeLayer = layer$74.pipe(Layer.provideMerge(storageLayer));
17600
17790
  //#endregion
@@ -19432,7 +19622,7 @@ const DEFAULT_LIMITS = {
19432
19622
  windowMillis: FAILURE_WINDOW_MS,
19433
19623
  blockMillis: BLOCK_DURATION_MS
19434
19624
  };
19435
- const make$83 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
19625
+ const make$84 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
19436
19626
  const state = yield* Ref.make(initialThrottleState);
19437
19627
  return HubAuthThrottle.of({
19438
19628
  shouldRefuse: Effect.gen(function* () {
@@ -19446,7 +19636,7 @@ const make$83 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LI
19446
19636
  })
19447
19637
  });
19448
19638
  });
19449
- const layer$73 = Layer.effect(HubAuthThrottle, make$83());
19639
+ const layer$73 = Layer.effect(HubAuthThrottle, make$84());
19450
19640
  //#endregion
19451
19641
  //#region src/hub/HubAuth.ts
19452
19642
  /**
@@ -21166,7 +21356,7 @@ function stripDefaultServerSettings(current, defaults) {
21166
21356
  }
21167
21357
  return Object.is(current, defaults) ? void 0 : current;
21168
21358
  }
21169
- const make$82 = Effect.gen(function* () {
21359
+ const make$83 = Effect.gen(function* () {
21170
21360
  const { settingsPath } = yield* ServerConfig$1;
21171
21361
  const fs = yield* FileSystem.FileSystem;
21172
21362
  const pathService = yield* Path.Path;
@@ -21387,7 +21577,7 @@ const make$82 = Effect.gen(function* () {
21387
21577
  }
21388
21578
  };
21389
21579
  });
21390
- const layer$71 = Layer.effect(ServerSettingsService, make$82);
21580
+ const layer$71 = Layer.effect(ServerSettingsService, make$83);
21391
21581
  //#endregion
21392
21582
  //#region src/pathExpansion.ts
21393
21583
  /**
@@ -21764,7 +21954,7 @@ function claudeEntryFromRegistration(registration) {
21764
21954
  };
21765
21955
  }
21766
21956
  var ClaudeMcpFiles = class extends Context.Service()("@p4code/cli/mcp/ClaudeMcpFiles") {};
21767
- const make$81 = Effect.gen(function* () {
21957
+ const make$82 = Effect.gen(function* () {
21768
21958
  const fileSystem = yield* FileSystem.FileSystem;
21769
21959
  const path = yield* Path.Path;
21770
21960
  const services = yield* Effect.context();
@@ -21831,7 +22021,7 @@ const make$81 = Effect.gen(function* () {
21831
22021
  removeProject: (projectDir, name) => removeAt(Effect.succeed(projectFile(projectDir)))(name)
21832
22022
  };
21833
22023
  });
21834
- const layer$70 = Layer.effect(ClaudeMcpFiles, make$81);
22024
+ const layer$70 = Layer.effect(ClaudeMcpFiles, make$82);
21835
22025
  Layer.succeed(ClaudeMcpFiles, {
21836
22026
  readUser: Effect.succeed([]),
21837
22027
  readUserAt: () => Effect.succeed([]),
@@ -21969,7 +22159,7 @@ const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationRe
21969
22159
  const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
21970
22160
  var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
21971
22161
  const registryError = (detail) => new McpRegistryError({ detail });
21972
- const make$80 = Effect.gen(function* () {
22162
+ const make$81 = Effect.gen(function* () {
21973
22163
  const config = yield* ServerConfig$1;
21974
22164
  const secrets = yield* ServerSecretStore;
21975
22165
  const http = yield* HttpClient.HttpClient;
@@ -22289,7 +22479,7 @@ const make$80 = Effect.gen(function* () {
22289
22479
  accessTokenFor
22290
22480
  };
22291
22481
  });
22292
- const layer$69 = Layer.effect(McpOAuth, make$80);
22482
+ const layer$69 = Layer.effect(McpOAuth, make$81);
22293
22483
  Layer.succeed(McpOAuth, {
22294
22484
  statusFor: () => Effect.succeed(Option.none()),
22295
22485
  begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
@@ -22307,7 +22497,7 @@ const decodeRegistration$1 = Schema$1.decodeUnknownExit(RegistrationFromJson$1);
22307
22497
  const encodeRegistration = Schema$1.encodeSync(RegistrationFromJson$1);
22308
22498
  var McpRegistry = class extends Context.Service()("@p4code/cli/mcp/McpRegistry") {};
22309
22499
  const slotsOf = (registration) => registration.secrets ?? [];
22310
- const make$79 = Effect.gen(function* () {
22500
+ const make$80 = Effect.gen(function* () {
22311
22501
  const config = yield* ServerConfig$1;
22312
22502
  const secrets = yield* ServerSecretStore;
22313
22503
  const oauth = yield* McpOAuth;
@@ -22481,7 +22671,7 @@ const make$79 = Effect.gen(function* () {
22481
22671
  resolveForSessionAtClaudeUserConfigPath
22482
22672
  };
22483
22673
  });
22484
- const layer$68 = Layer.effect(McpRegistry, make$79);
22674
+ const layer$68 = Layer.effect(McpRegistry, make$80);
22485
22675
  //#endregion
22486
22676
  //#region src/sync/skillDirectory.ts
22487
22677
  /**
@@ -22860,7 +23050,7 @@ const formatHubLink = (input) => encodeStoredHubLink({
22860
23050
  shareMode: input.shareMode
22861
23051
  });
22862
23052
  const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
22863
- const make$78 = Effect.fn("HubLink.make")(function* (environment) {
23053
+ const make$79 = Effect.fn("HubLink.make")(function* (environment) {
22864
23054
  const secrets = yield* ServerSecretStore;
22865
23055
  const env = environment ?? process.env;
22866
23056
  const fromEnv = fromEnvironment(env);
@@ -22926,7 +23116,7 @@ const make$78 = Effect.fn("HubLink.make")(function* (environment) {
22926
23116
  })
22927
23117
  };
22928
23118
  });
22929
- const layer$67 = Layer.effect(HubLink, make$78());
23119
+ const layer$67 = Layer.effect(HubLink, make$79());
22930
23120
  //#endregion
22931
23121
  //#region src/sync/HubAssetClient.ts
22932
23122
  /**
@@ -22959,7 +23149,7 @@ const decodeAssetListPage = Schema$1.decodeUnknownEffect(AssetListPage);
22959
23149
  const decodeConflictBody$1 = Schema$1.decodeUnknownEffect(ConflictBody$1);
22960
23150
  const decodeAsset = Schema$1.decodeUnknownEffect(AgentAsset);
22961
23151
  var HubAssetClient = class extends Context.Service()("@p4code/cli/sync/HubAssetClient") {};
22962
- const make$77 = Effect.gen(function* () {
23152
+ const make$78 = Effect.gen(function* () {
22963
23153
  const http = yield* HttpClient.HttpClient;
22964
23154
  const link = yield* HubLink;
22965
23155
  const requireSettings = Effect.gen(function* () {
@@ -23041,7 +23231,7 @@ const make$77 = Effect.gen(function* () {
23041
23231
  remove
23042
23232
  };
23043
23233
  });
23044
- const layer$66 = Layer.effect(HubAssetClient, make$77);
23234
+ const layer$66 = Layer.effect(HubAssetClient, make$78);
23045
23235
  //#endregion
23046
23236
  //#region src/sync/mcpRegistrationFiles.ts
23047
23237
  /**
@@ -23647,7 +23837,7 @@ const EMPTY_REPORT = {
23647
23837
  unavailable: null
23648
23838
  };
23649
23839
  var AssetSync = class extends Context.Service()("@p4code/cli/sync/AssetSync") {};
23650
- const make$76 = Effect.gen(function* () {
23840
+ const make$77 = Effect.gen(function* () {
23651
23841
  const client = yield* HubAssetClient;
23652
23842
  const link = yield* HubLink;
23653
23843
  const settingsStore = yield* ServerSettingsService;
@@ -24357,7 +24547,7 @@ const make$76 = Effect.gen(function* () {
24357
24547
  removeLocal
24358
24548
  };
24359
24549
  });
24360
- const layer$65 = Layer.effect(AssetSync, make$76);
24550
+ const layer$65 = Layer.effect(AssetSync, make$77);
24361
24551
  //#endregion
24362
24552
  //#region src/provider/CompressPrompts.ts
24363
24553
  /**
@@ -25434,6 +25624,30 @@ function toPersistenceSqlOrDecodeError$4(sqlOperation, decodeOperation) {
25434
25624
  }
25435
25625
  const makeEventStore = Effect.gen(function* () {
25436
25626
  const sql = yield* SqlClient.SqlClient;
25627
+ const readReviewRows = SqlSchema.findAll({
25628
+ Request: Schema$1.Struct({
25629
+ afterSequence: NonNegativeInt,
25630
+ throughSequence: NonNegativeInt,
25631
+ limit: NonNegativeInt
25632
+ }),
25633
+ Result: Schema$1.Struct({
25634
+ sequence: NonNegativeInt,
25635
+ type: Schema$1.String,
25636
+ aggregateKind: Schema$1.String,
25637
+ aggregateId: Schema$1.String,
25638
+ payloadJson: Schema$1.String
25639
+ }),
25640
+ execute: (request) => sql`
25641
+ SELECT sequence, event_type AS "type", aggregate_kind AS "aggregateKind",
25642
+ stream_id AS "aggregateId",
25643
+ substr(payload_json, 1, ${8001}) AS "payloadJson"
25644
+ FROM orchestration_events
25645
+ WHERE sequence > ${request.afterSequence} AND sequence <= ${request.throughSequence}
25646
+ ORDER BY sequence ASC
25647
+ LIMIT ${Math.min(request.limit, THREAD_REVIEW_SCAN_LIMIT)}
25648
+ `
25649
+ });
25650
+ const readReviewPage = (input) => readReviewRows(input).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$4("OrchestrationEventStore.readReviewPage:query", "OrchestrationEventStore.readReviewPage:decode")));
25437
25651
  const appendEventRow = SqlSchema.findOne({
25438
25652
  Request: AppendEventRequestSchema,
25439
25653
  Result: OrchestrationEventPersistedRowSchema,
@@ -25603,6 +25817,7 @@ const makeEventStore = Effect.gen(function* () {
25603
25817
  append,
25604
25818
  readByCommandId,
25605
25819
  readFromSequence,
25820
+ readReviewPage,
25606
25821
  readAll: () => readFromSequence(0, Number.MAX_SAFE_INTEGER)
25607
25822
  };
25608
25823
  });
@@ -25727,6 +25942,99 @@ function toProjectorDecodeError(eventType) {
25727
25942
  });
25728
25943
  }
25729
25944
  //#endregion
25945
+ //#region ../../packages/shared/src/taskAgentKind.ts
25946
+ /** Unknown task types remain agents; only known background types are excluded. */
25947
+ function classifyTaskAgentKind(input) {
25948
+ const taskType = input.taskType ?? input.subagentType;
25949
+ const nonAgent = taskType != null && (INERT_TASK_TYPES.has(taskType) || MONITOR_TASK_TYPES.has(taskType));
25950
+ if (input.agentId?.trim()) return taskType == null || nonAgent ? "background" : "agent";
25951
+ return nonAgent ? "background" : "agent";
25952
+ }
25953
+ //#endregion
25954
+ //#region src/orchestration/ThreadBackgroundLiveness.ts
25955
+ /**
25956
+ * ThreadBackgroundLivenessService - in-memory per-thread background liveness
25957
+ * for the sidebar status pill.
25958
+ *
25959
+ * The turn can settle while native background work runs on (subagent fleets,
25960
+ * workflow runs, Monitor watch loops); the shell previously showed nothing.
25961
+ * Ingestion records task lifecycle transitions and the shell query reads the
25962
+ * derived state at mapping time — no persistence, no migration. After a
25963
+ * server restart the registry is empty until new task events arrive, which
25964
+ * matches reality: orphaned background work is not live.
25965
+ *
25966
+ * "monitoring" is reserved for watch loops (monitor tasks and background
25967
+ * shells) when they are the ONLY live work; any agent work presents as
25968
+ * "working".
25969
+ *
25970
+ * @module ThreadBackgroundLivenessService
25971
+ */
25972
+ const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
25973
+ "completed",
25974
+ "failed",
25975
+ "stopped",
25976
+ "cancelled",
25977
+ "interrupted"
25978
+ ]);
25979
+ var ThreadBackgroundLivenessService = class extends Context.Service()("@p4code/cli/orchestration/ThreadBackgroundLiveness/ThreadBackgroundLivenessService") {};
25980
+ function make$76() {
25981
+ const stateByThreadId = /* @__PURE__ */ new Map();
25982
+ const stateFor = (threadId) => {
25983
+ const existing = stateByThreadId.get(threadId);
25984
+ if (existing) return existing;
25985
+ const created = {
25986
+ agents: /* @__PURE__ */ new Set(),
25987
+ monitors: /* @__PURE__ */ new Set(),
25988
+ tasks: /* @__PURE__ */ new Map()
25989
+ };
25990
+ stateByThreadId.set(threadId, created);
25991
+ return created;
25992
+ };
25993
+ const drop = (threadId, taskId) => {
25994
+ const state = stateByThreadId.get(threadId);
25995
+ if (!state) return;
25996
+ state.agents.delete(taskId);
25997
+ state.monitors.delete(taskId);
25998
+ if (state.tasks.size === 0) stateByThreadId.delete(threadId);
25999
+ };
26000
+ return {
26001
+ recordTaskLiveness: (input) => {
26002
+ const state = stateFor(input.threadId);
26003
+ const previous = state.tasks.get(input.taskId);
26004
+ const taskType = input.taskType ?? previous?.taskType;
26005
+ const agentId = input.agentId ?? previous?.agentId;
26006
+ if (input.kind === "completed" || input.status !== void 0 && TERMINAL_STATUSES.has(input.status)) {
26007
+ state.tasks.delete(input.taskId);
26008
+ drop(input.threadId, input.taskId);
26009
+ return;
26010
+ }
26011
+ state.tasks.set(input.taskId, {
26012
+ taskType,
26013
+ agentId
26014
+ });
26015
+ drop(input.threadId, input.taskId);
26016
+ if (input.status === "idle") return;
26017
+ if (taskType !== void 0 && INERT_TASK_TYPES.has(taskType)) return;
26018
+ if (agentId !== void 0 && classifyTaskAgentKind({
26019
+ taskType,
26020
+ agentId
26021
+ }) === "background") return;
26022
+ (taskType !== void 0 && MONITOR_TASK_TYPES.has(taskType) ? state.monitors : state.agents).add(input.taskId);
26023
+ },
26024
+ clearThreadLiveness: (threadId) => {
26025
+ stateByThreadId.delete(threadId);
26026
+ },
26027
+ getThreadBackgroundLiveness: (threadId) => {
26028
+ const state = stateByThreadId.get(threadId);
26029
+ if (!state) return null;
26030
+ if (state.agents.size > 0) return "working";
26031
+ if (state.monitors.size > 0) return "monitoring";
26032
+ return null;
26033
+ }
26034
+ };
26035
+ }
26036
+ const layer$64 = Layer.effect(ThreadBackgroundLivenessService, Effect.sync(make$76));
26037
+ //#endregion
25730
26038
  //#region ../../packages/shared/src/path.ts
25731
26039
  function isWindowsDrivePath(value) {
25732
26040
  return /^[a-zA-Z]:([/\\]|$)/.test(value);
@@ -25805,6 +26113,74 @@ function requireThreadAbsent(input) {
25805
26113
  return Effect.fail(invariantError(input.command.type, `Thread '${input.threadId}' already exists and cannot be created twice.`));
25806
26114
  }
25807
26115
  //#endregion
26116
+ //#region src/orchestration/ThreadCompletion.ts
26117
+ const ENGAGEMENT_ACTIVITY_KINDS = /* @__PURE__ */ new Set([
26118
+ "approval.requested",
26119
+ "user-input.requested",
26120
+ "tool.started",
26121
+ "tool.updated",
26122
+ "tool.completed",
26123
+ "task.started",
26124
+ "task.progress",
26125
+ "task.updated",
26126
+ "task.completed",
26127
+ "turn.plan.updated"
26128
+ ]);
26129
+ function isThreadEngagement(event) {
26130
+ switch (event.type) {
26131
+ case "thread.message-sent": return "text" in event.payload && typeof event.payload.text === "string" && event.payload.text.length > 0;
26132
+ case "thread.session-set": return "session" in event.payload && (event.payload.session.status === "starting" || event.payload.session.status === "running");
26133
+ case "thread.activity-appended": return "activity" in event.payload && ENGAGEMENT_ACTIVITY_KINDS.has(event.payload.activity.kind);
26134
+ case "thread.proposed-plan-upserted":
26135
+ case "thread.pinned": return true;
26136
+ default: return false;
26137
+ }
26138
+ }
26139
+ function completionState(thread) {
26140
+ return {
26141
+ lifecycle: thread.lifecycle ?? (thread.archivedAt !== null ? "archived" : thread.settledOverride === "settled" ? "done" : "active"),
26142
+ lifecycleReason: thread.lifecycleReason ?? (thread.settledOverride === "settled" ? "manual" : "created"),
26143
+ lifecycleChangedAt: thread.lifecycleChangedAt ?? thread.archivedAt ?? thread.settledAt ?? thread.createdAt,
26144
+ lastEngagedAt: thread.lastEngagedAt ?? thread.createdAt,
26145
+ doneAt: thread.doneAt ?? thread.settledAt
26146
+ };
26147
+ }
26148
+ /** Transition metadata is shared by in-memory and persisted projections. */
26149
+ function completionAfterEvent(thread, event) {
26150
+ const state = completionState(thread);
26151
+ if (isThreadEngagement(event)) state.lastEngagedAt = state.lastEngagedAt > event.occurredAt ? state.lastEngagedAt : event.occurredAt;
26152
+ switch (event.type) {
26153
+ case "thread.settled": return {
26154
+ ...state,
26155
+ lifecycle: "done",
26156
+ doneAt: event.payload.settledAt,
26157
+ lifecycleReason: event.payload.lifecycleReason ?? "manual",
26158
+ lifecycleChangedAt: event.payload.lifecycleChangedAt ?? event.payload.settledAt
26159
+ };
26160
+ case "thread.unsettled": return {
26161
+ ...state,
26162
+ lifecycle: "active",
26163
+ doneAt: null,
26164
+ lifecycleReason: event.payload.lifecycleReason ?? event.payload.reason,
26165
+ lifecycleChangedAt: event.payload.lifecycleChangedAt ?? event.payload.updatedAt,
26166
+ lastEngagedAt: event.payload.lastEngagedAt ?? (state.lastEngagedAt > event.payload.updatedAt ? state.lastEngagedAt : event.payload.updatedAt)
26167
+ };
26168
+ case "thread.archived": return {
26169
+ ...state,
26170
+ lifecycle: "archived",
26171
+ lifecycleReason: "archive",
26172
+ lifecycleChangedAt: event.payload.lifecycleChangedAt ?? event.payload.archivedAt
26173
+ };
26174
+ case "thread.unarchived": return {
26175
+ ...state,
26176
+ lifecycle: state.doneAt !== null ? "done" : "active",
26177
+ lifecycleReason: "restore",
26178
+ lifecycleChangedAt: event.payload.lifecycleChangedAt ?? event.payload.updatedAt
26179
+ };
26180
+ default: return state;
26181
+ }
26182
+ }
26183
+ //#endregion
25808
26184
  //#region src/attachmentPaths.ts
25809
26185
  function normalizeAttachmentRelativePath(rawRelativePath) {
25810
26186
  const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");
@@ -26220,6 +26596,10 @@ function createEmptyReadModel(nowIso) {
26220
26596
  function projectEvent(model, event) {
26221
26597
  const nextBase = {
26222
26598
  ...model,
26599
+ threads: event.aggregateKind !== "thread" ? model.threads : model.threads.map((thread) => thread.id === event.aggregateId ? {
26600
+ ...thread,
26601
+ ...completionAfterEvent(thread, event)
26602
+ } : thread),
26223
26603
  snapshotSequence: event.sequence,
26224
26604
  updatedAt: event.occurredAt
26225
26605
  };
@@ -26327,8 +26707,13 @@ function projectEvent(model, event) {
26327
26707
  latestTurn: null,
26328
26708
  createdAt: payload.createdAt,
26329
26709
  updatedAt: payload.updatedAt,
26710
+ lifecycle: "active",
26711
+ lifecycleReason: "created",
26712
+ lifecycleChangedAt: payload.createdAt,
26713
+ lastEngagedAt: payload.createdAt,
26714
+ doneAt: null,
26715
+ settledOverride: "active",
26330
26716
  archivedAt: null,
26331
- settledOverride: null,
26332
26717
  settledAt: null,
26333
26718
  snoozedUntil: null,
26334
26719
  snoozedAt: null,
@@ -26376,7 +26761,7 @@ function projectEvent(model, event) {
26376
26761
  case "thread.unsettled": return decodeForEvent(ThreadUnsettledPayload, event.payload, event.type, "payload").pipe(Effect.map((payload) => ({
26377
26762
  ...nextBase,
26378
26763
  threads: updateThread(nextBase.threads, payload.threadId, {
26379
- settledOverride: payload.reason === "user" ? "active" : null,
26764
+ settledOverride: "active",
26380
26765
  settledAt: null,
26381
26766
  updatedAt: payload.updatedAt
26382
26767
  })
@@ -26736,7 +27121,7 @@ const decideCommandSequence = Effect.fn("decideCommandSequence")(function* ({ co
26736
27121
  }
26737
27122
  return plannedEvents;
26738
27123
  });
26739
- const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(function* ({ command, readModel }) {
27124
+ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel }) {
26740
27125
  switch (command.type) {
26741
27126
  case "project.create":
26742
27127
  yield* requireProjectAbsent({
@@ -27376,12 +27761,19 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27376
27761
  }
27377
27762
  return events;
27378
27763
  }
27764
+ case "thread.complete":
27765
+ case "thread.complete.auto":
27379
27766
  case "thread.settle": {
27380
27767
  const thread = yield* requireThreadNotArchived({
27381
27768
  readModel,
27382
27769
  command,
27383
27770
  threadId: command.threadId
27384
27771
  });
27772
+ const occurredAt = yield* nowIso$8;
27773
+ if (command.type === "thread.complete.auto" && (completionState(thread).lastEngagedAt !== command.expectedLastEngagedAt || !(Date.parse(command.expectedLastEngagedAt) < Date.parse(command.eligibleAt)) || !(Date.parse(command.eligibleAt) <= Date.parse(occurredAt)) || thread.pinnedAt != null)) return yield* new OrchestrationCommandInvariantError({
27774
+ commandType: command.type,
27775
+ detail: "Thread engagement changed or automatic completion is no longer eligible"
27776
+ });
27385
27777
  if (thread.session?.status === "starting" || thread.session?.status === "running") return yield* new OrchestrationCommandInvariantError({
27386
27778
  commandType: command.type,
27387
27779
  detail: `thread ${command.threadId} has an active session and cannot be settled`
@@ -27390,7 +27782,6 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27390
27782
  commandType: command.type,
27391
27783
  detail: `thread ${command.threadId} has a pending approval or user-input request and cannot be settled`
27392
27784
  });
27393
- const occurredAt = yield* nowIso$8;
27394
27785
  if (threadHasQueuedTurnStart(thread, occurredAt)) return yield* new OrchestrationCommandInvariantError({
27395
27786
  commandType: command.type,
27396
27787
  detail: `thread ${command.threadId} has a queued turn start and cannot be settled`
@@ -27425,6 +27816,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27425
27816
  }
27426
27817
  }];
27427
27818
  }
27819
+ case "thread.reopen":
27428
27820
  case "thread.unsettle": {
27429
27821
  const thread = yield* requireThreadNotArchived({
27430
27822
  readModel,
@@ -27443,7 +27835,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27443
27835
  type: "thread.unsettled",
27444
27836
  payload: {
27445
27837
  threadId: command.threadId,
27446
- reason: command.reason,
27838
+ reason: "user",
27447
27839
  updatedAt: alreadyPinnedActive ? thread.updatedAt : occurredAt
27448
27840
  }
27449
27841
  };
@@ -27800,7 +28192,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27800
28192
  }
27801
28193
  };
27802
28194
  const lifecycleResetEvents = [];
27803
- if (targetThread.settledOverride !== null) lifecycleResetEvents.push({
28195
+ if (targetThread.settledOverride === "settled") lifecycleResetEvents.push({
27804
28196
  ...yield* withEventBase({
27805
28197
  aggregateKind: "thread",
27806
28198
  aggregateId: command.threadId,
@@ -28051,7 +28443,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
28051
28443
  }
28052
28444
  };
28053
28445
  const isSessionActivity = command.session.status === "starting" || command.session.status === "running";
28054
- if (thread.settledOverride === null || !isSessionActivity) return sessionSetEvent;
28446
+ if (thread.settledOverride !== "settled" || !isSessionActivity) return sessionSetEvent;
28055
28447
  return [{
28056
28448
  ...yield* withEventBase({
28057
28449
  aggregateKind: "thread",
@@ -28202,7 +28594,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
28202
28594
  }
28203
28595
  };
28204
28596
  const wakesSettledThread = command.activity.kind === "approval.requested" || command.activity.kind === "user-input.requested";
28205
- if (thread.settledOverride === null || !wakesSettledThread) return activityAppendedEvent;
28597
+ if (thread.settledOverride !== "settled" || !wakesSettledThread) return activityAppendedEvent;
28206
28598
  return [{
28207
28599
  ...yield* withEventBase({
28208
28600
  aggregateKind: "thread",
@@ -28227,6 +28619,74 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
28227
28619
  }
28228
28620
  }
28229
28621
  });
28622
+ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(function* (input) {
28623
+ const { command, readModel } = input;
28624
+ if (input.hasLiveBackgroundWork && (command.type === "thread.settle" || command.type === "thread.complete" || command.type === "thread.complete.auto")) return yield* new OrchestrationCommandInvariantError({
28625
+ commandType: command.type,
28626
+ detail: "Thread has live background work and cannot be completed"
28627
+ });
28628
+ const decided = yield* decideCommand(input);
28629
+ const events = Array.isArray(decided) ? [...decided] : [decided];
28630
+ const target = "threadId" in command ? readModel.threads.find((thread) => thread.id === command.threadId) : void 0;
28631
+ const engagement = events.find(isThreadEngagement);
28632
+ if (target && target.archivedAt === null && completionState(target).lifecycle === "done" && engagement && !events.some((event) => event.type === "thread.unsettled")) events.unshift({
28633
+ ...yield* withEventBase({
28634
+ aggregateKind: "thread",
28635
+ aggregateId: target.id,
28636
+ occurredAt: engagement.occurredAt,
28637
+ commandId: command.commandId
28638
+ }),
28639
+ type: "thread.unsettled",
28640
+ payload: {
28641
+ threadId: target.id,
28642
+ reason: "activity",
28643
+ updatedAt: engagement.occurredAt
28644
+ }
28645
+ });
28646
+ return events.map((event) => {
28647
+ if (event.type === "thread.created") return {
28648
+ ...event,
28649
+ payload: {
28650
+ ...event.payload,
28651
+ lifecycle: "active",
28652
+ lifecycleReason: "created",
28653
+ lifecycleChangedAt: event.payload.createdAt,
28654
+ lastEngagedAt: event.payload.createdAt,
28655
+ doneAt: null
28656
+ }
28657
+ };
28658
+ const eventThread = readModel.threads.find((thread) => thread.id === event.aggregateId);
28659
+ if (!eventThread) return event;
28660
+ switch (event.type) {
28661
+ case "thread.settled":
28662
+ case "thread.unsettled":
28663
+ case "thread.archived":
28664
+ case "thread.unarchived": {
28665
+ const previous = completionState(eventThread);
28666
+ const state = completionAfterEvent(eventThread, {
28667
+ ...event,
28668
+ sequence: readModel.snapshotSequence + 1
28669
+ });
28670
+ if (event.type === "thread.settled") state.lifecycleReason = previous.lifecycle === "done" ? previous.lifecycleReason : command.type === "thread.complete.auto" ? command.reason : "manual";
28671
+ if (event.type === "thread.settled" && previous.lifecycle === "done" || event.type === "thread.unsettled" && previous.lifecycle === "active" && (command.type === "thread.reopen" || command.type === "thread.unsettle")) return {
28672
+ ...event,
28673
+ payload: {
28674
+ ...event.payload,
28675
+ ...previous
28676
+ }
28677
+ };
28678
+ return {
28679
+ ...event,
28680
+ payload: {
28681
+ ...event.payload,
28682
+ ...state
28683
+ }
28684
+ };
28685
+ }
28686
+ default: return event;
28687
+ }
28688
+ });
28689
+ });
28230
28690
  //#endregion
28231
28691
  //#region src/orchestration/Services/ProjectionPipeline.ts
28232
28692
  /**
@@ -28267,6 +28727,7 @@ const makeOrchestrationEngine = Effect.gen(function* () {
28267
28727
  const projectionPipeline = yield* OrchestrationProjectionPipeline;
28268
28728
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
28269
28729
  const crypto = yield* Crypto.Crypto;
28730
+ const backgroundLiveness = yield* ThreadBackgroundLivenessService;
28270
28731
  const nowIso = Effect.map(DateTime.now, DateTime.formatIso);
28271
28732
  let commandReadModel = createEmptyReadModel(yield* nowIso);
28272
28733
  const commandQueue = yield* Queue.unbounded();
@@ -28314,7 +28775,8 @@ const makeOrchestrationEngine = Effect.gen(function* () {
28314
28775
  }
28315
28776
  const eventBase = yield* decideOrchestrationCommand({
28316
28777
  command: envelope.command,
28317
- readModel: commandReadModel
28778
+ readModel: commandReadModel,
28779
+ hasLiveBackgroundWork: "threadId" in envelope.command && backgroundLiveness.getThreadBackgroundLiveness(envelope.command.threadId) !== null
28318
28780
  }).pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.mapError((cause) => isOrchestrationCommandInvariantError(cause) ? cause : new OrchestrationCommandInvariantError({
28319
28781
  commandType: envelope.command.type,
28320
28782
  detail: "Failed to generate an event identifier.",
@@ -28416,7 +28878,7 @@ const makeOrchestrationEngine = Effect.gen(function* () {
28416
28878
  latestSequence: Effect.sync(() => commandReadModel.snapshotSequence)
28417
28879
  };
28418
28880
  });
28419
- const OrchestrationEngineLive = Layer.effect(OrchestrationEngineService, makeOrchestrationEngine);
28881
+ const OrchestrationEngineLive = Layer.effect(OrchestrationEngineService, makeOrchestrationEngine).pipe(Layer.provide(layer$64));
28420
28882
  //#endregion
28421
28883
  //#region src/persistence/Services/ProjectionPendingApprovals.ts
28422
28884
  /**
@@ -28682,6 +29144,7 @@ var ProjectionTurnRepository = class extends Context.Service()("@p4code/cli/pers
28682
29144
  * @module ProjectionThreadRepository
28683
29145
  */
28684
29146
  const ProjectionThread = Schema$1.Struct({
29147
+ ...ThreadCompletionFields,
28685
29148
  threadId: ThreadId,
28686
29149
  projectId: ProjectId,
28687
29150
  title: Schema$1.String,
@@ -29684,6 +30147,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29684
30147
  archived_at,
29685
30148
  settled_override,
29686
30149
  settled_at,
30150
+ lifecycle,
30151
+ lifecycle_reason,
30152
+ lifecycle_changed_at,
30153
+ last_engaged_at,
29687
30154
  snoozed_until,
29688
30155
  snoozed_at,
29689
30156
  pinned_at,
@@ -29714,6 +30181,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29714
30181
  ${row.archivedAt},
29715
30182
  ${row.settledOverride},
29716
30183
  ${row.settledAt},
30184
+ ${row.lifecycle ?? (row.archivedAt !== null ? "archived" : row.settledOverride === "settled" ? "done" : "active")},
30185
+ ${row.lifecycleReason ?? (row.settledOverride === "settled" ? "manual" : "created")},
30186
+ ${row.lifecycleChangedAt ?? row.settledAt ?? row.createdAt},
30187
+ ${row.lastEngagedAt ?? row.latestUserMessageAt ?? row.createdAt},
29717
30188
  ${row.snoozedUntil},
29718
30189
  ${row.snoozedAt},
29719
30190
  ${row.pinnedAt},
@@ -29744,6 +30215,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29744
30215
  archived_at = excluded.archived_at,
29745
30216
  settled_override = excluded.settled_override,
29746
30217
  settled_at = excluded.settled_at,
30218
+ lifecycle = excluded.lifecycle,
30219
+ lifecycle_reason = excluded.lifecycle_reason,
30220
+ lifecycle_changed_at = excluded.lifecycle_changed_at,
30221
+ last_engaged_at = excluded.last_engaged_at,
29747
30222
  snoozed_until = excluded.snoozed_until,
29748
30223
  snoozed_at = excluded.snoozed_at,
29749
30224
  pinned_at = excluded.pinned_at,
@@ -29779,6 +30254,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29779
30254
  archived_at AS "archivedAt",
29780
30255
  settled_override AS "settledOverride",
29781
30256
  settled_at AS "settledAt",
30257
+ lifecycle AS "lifecycle",
30258
+ lifecycle_reason AS "lifecycleReason",
30259
+ lifecycle_changed_at AS "lifecycleChangedAt",
30260
+ last_engaged_at AS "lastEngagedAt",
29782
30261
  snoozed_until AS "snoozedUntil",
29783
30262
  snoozed_at AS "snoozedAt",
29784
30263
  pinned_at AS "pinnedAt",
@@ -29816,6 +30295,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29816
30295
  archived_at AS "archivedAt",
29817
30296
  settled_override AS "settledOverride",
29818
30297
  settled_at AS "settledAt",
30298
+ lifecycle AS "lifecycle",
30299
+ lifecycle_reason AS "lifecycleReason",
30300
+ lifecycle_changed_at AS "lifecycleChangedAt",
30301
+ last_engaged_at AS "lastEngagedAt",
29819
30302
  snoozed_until AS "snoozedUntil",
29820
30303
  snoozed_at AS "snoozedAt",
29821
30304
  pinned_at AS "pinnedAt",
@@ -29853,6 +30336,14 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29853
30336
  const ProjectionThreadRepositoryLive = Layer.effect(ProjectionThreadRepository, makeProjectionThreadRepository);
29854
30337
  //#endregion
29855
30338
  //#region src/orchestration/Layers/ProjectionPipeline.ts
30339
+ /**
30340
+ * Tasks the agent started that never reported a terminal status. A task that
30341
+ * outlives its turn is the case worth surfacing: the session goes back to idle
30342
+ * while the task keeps running, so nothing else in the shell shows the work.
30343
+ *
30344
+ * Counted rather than flagged so a lost completion can only strand one task,
30345
+ * and read as a boolean by the shell.
30346
+ */
29856
30347
  const ORCHESTRATION_PROJECTOR_NAMES = {
29857
30348
  projects: "projection.projects",
29858
30349
  threads: "projection.threads",
@@ -29898,14 +30389,6 @@ function extractActivityTaskId(payload) {
29898
30389
  const taskId = payload.taskId;
29899
30390
  return typeof taskId === "string" && taskId.length > 0 ? taskId : null;
29900
30391
  }
29901
- /**
29902
- * Tasks the agent started that never reported a terminal status. A task that
29903
- * outlives its turn is the case worth surfacing: the session goes back to idle
29904
- * while the task keeps running, so nothing else in the shell shows the work.
29905
- *
29906
- * Counted rather than flagged so a lost completion can only strand one task,
29907
- * and read as a boolean by the shell.
29908
- */
29909
30392
  function deriveBackgroundTaskCountFromActivities(activities) {
29910
30393
  const openTaskIds = /* @__PURE__ */ new Set();
29911
30394
  const ordered = [...activities].toSorted((left, right) => left.createdAt.localeCompare(right.createdAt) || left.activityId.localeCompare(right.activityId));
@@ -30248,7 +30731,12 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30248
30731
  createdAt: event.payload.createdAt,
30249
30732
  updatedAt: event.payload.updatedAt,
30250
30733
  archivedAt: null,
30251
- settledOverride: null,
30734
+ lifecycle: "active",
30735
+ lifecycleReason: "created",
30736
+ lifecycleChangedAt: event.payload.createdAt,
30737
+ lastEngagedAt: event.payload.createdAt,
30738
+ doneAt: null,
30739
+ settledOverride: "active",
30252
30740
  settledAt: null,
30253
30741
  snoozedUntil: null,
30254
30742
  snoozedAt: null,
@@ -30268,6 +30756,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30268
30756
  if (Option.isNone(existingRow)) return;
30269
30757
  yield* projectionThreadRepository.upsert({
30270
30758
  ...existingRow.value,
30759
+ ...completionAfterEvent(existingRow.value, event),
30271
30760
  archivedAt: event.payload.archivedAt,
30272
30761
  updatedAt: event.payload.updatedAt
30273
30762
  });
@@ -30278,6 +30767,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30278
30767
  if (Option.isNone(existingRow)) return;
30279
30768
  yield* projectionThreadRepository.upsert({
30280
30769
  ...existingRow.value,
30770
+ ...completionAfterEvent(existingRow.value, event),
30281
30771
  archivedAt: null,
30282
30772
  updatedAt: event.payload.updatedAt
30283
30773
  });
@@ -30288,6 +30778,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30288
30778
  if (Option.isNone(existingRow)) return;
30289
30779
  yield* projectionThreadRepository.upsert({
30290
30780
  ...existingRow.value,
30781
+ ...completionAfterEvent(existingRow.value, event),
30291
30782
  settledOverride: "settled",
30292
30783
  settledAt: event.payload.settledAt,
30293
30784
  updatedAt: event.payload.updatedAt
@@ -30299,7 +30790,8 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30299
30790
  if (Option.isNone(existingRow)) return;
30300
30791
  yield* projectionThreadRepository.upsert({
30301
30792
  ...existingRow.value,
30302
- settledOverride: event.payload.reason === "user" ? "active" : null,
30793
+ ...completionAfterEvent(existingRow.value, event),
30794
+ settledOverride: "active",
30303
30795
  settledAt: null,
30304
30796
  updatedAt: event.payload.updatedAt
30305
30797
  });
@@ -30310,6 +30802,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30310
30802
  if (Option.isNone(existingRow)) return;
30311
30803
  yield* projectionThreadRepository.upsert({
30312
30804
  ...existingRow.value,
30805
+ ...completionAfterEvent(existingRow.value, event),
30313
30806
  snoozedUntil: event.payload.snoozedUntil,
30314
30807
  snoozedAt: event.payload.snoozedAt,
30315
30808
  updatedAt: event.payload.updatedAt
@@ -30321,6 +30814,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30321
30814
  if (Option.isNone(existingRow)) return;
30322
30815
  yield* projectionThreadRepository.upsert({
30323
30816
  ...existingRow.value,
30817
+ ...completionAfterEvent(existingRow.value, event),
30324
30818
  snoozedUntil: null,
30325
30819
  snoozedAt: null,
30326
30820
  updatedAt: event.payload.updatedAt
@@ -30332,6 +30826,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30332
30826
  if (Option.isNone(existingRow)) return;
30333
30827
  yield* projectionThreadRepository.upsert({
30334
30828
  ...existingRow.value,
30829
+ ...completionAfterEvent(existingRow.value, event),
30335
30830
  pinnedAt: event.payload.pinnedAt,
30336
30831
  ...event.payload.pinOrderKey === void 0 ? {} : { pinOrderKey: event.payload.pinOrderKey },
30337
30832
  updatedAt: event.payload.updatedAt
@@ -30343,6 +30838,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30343
30838
  if (Option.isNone(existingRow)) return;
30344
30839
  yield* projectionThreadRepository.upsert({
30345
30840
  ...existingRow.value,
30841
+ ...completionAfterEvent(existingRow.value, event),
30346
30842
  pinnedAt: null,
30347
30843
  pinOrderKey: null,
30348
30844
  updatedAt: event.payload.updatedAt
@@ -30354,6 +30850,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30354
30850
  if (Option.isNone(existingRow)) return;
30355
30851
  yield* projectionThreadRepository.upsert({
30356
30852
  ...existingRow.value,
30853
+ ...completionAfterEvent(existingRow.value, event),
30357
30854
  pinOrderKey: event.payload.pinOrderKey,
30358
30855
  updatedAt: event.payload.updatedAt
30359
30856
  });
@@ -30364,6 +30861,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30364
30861
  if (Option.isNone(existingRow)) return;
30365
30862
  yield* projectionThreadRepository.upsert({
30366
30863
  ...existingRow.value,
30864
+ ...completionAfterEvent(existingRow.value, event),
30367
30865
  ...event.payload.title !== void 0 ? { title: event.payload.title } : {},
30368
30866
  ...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
30369
30867
  ...event.payload.branch !== void 0 ? { branch: event.payload.branch } : {},
@@ -30378,6 +30876,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30378
30876
  if (Option.isNone(existingRow)) return;
30379
30877
  yield* projectionThreadRepository.upsert({
30380
30878
  ...existingRow.value,
30879
+ ...completionAfterEvent(existingRow.value, event),
30381
30880
  runtimeMode: event.payload.runtimeMode,
30382
30881
  updatedAt: event.payload.updatedAt
30383
30882
  });
@@ -30388,6 +30887,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30388
30887
  if (Option.isNone(existingRow)) return;
30389
30888
  yield* projectionThreadRepository.upsert({
30390
30889
  ...existingRow.value,
30890
+ ...completionAfterEvent(existingRow.value, event),
30391
30891
  interactionMode: event.payload.interactionMode,
30392
30892
  updatedAt: event.payload.updatedAt
30393
30893
  });
@@ -30398,6 +30898,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30398
30898
  if (Option.isNone(existingRow)) return;
30399
30899
  yield* projectionThreadRepository.upsert({
30400
30900
  ...existingRow.value,
30901
+ ...completionAfterEvent(existingRow.value, event),
30401
30902
  compressMode: event.payload.compressMode,
30402
30903
  updatedAt: event.payload.updatedAt
30403
30904
  });
@@ -30408,6 +30909,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30408
30909
  if (Option.isNone(existingRow)) return;
30409
30910
  yield* projectionThreadRepository.upsert({
30410
30911
  ...existingRow.value,
30912
+ ...completionAfterEvent(existingRow.value, event),
30411
30913
  unpromptedSubagents: event.payload.unpromptedSubagents ? 1 : 0,
30412
30914
  updatedAt: event.payload.updatedAt
30413
30915
  });
@@ -30419,6 +30921,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30419
30921
  if (Option.isNone(existingRow)) return;
30420
30922
  yield* projectionThreadRepository.upsert({
30421
30923
  ...existingRow.value,
30924
+ ...completionAfterEvent(existingRow.value, event),
30422
30925
  deletedAt: event.payload.deletedAt,
30423
30926
  updatedAt: event.payload.deletedAt
30424
30927
  });
@@ -30433,6 +30936,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30433
30936
  if (Option.isNone(existingRow)) return;
30434
30937
  yield* projectionThreadRepository.upsert({
30435
30938
  ...existingRow.value,
30939
+ ...completionAfterEvent(existingRow.value, event),
30436
30940
  updatedAt: event.occurredAt
30437
30941
  });
30438
30942
  yield* refreshThreadShellSummary(event.payload.threadId);
@@ -30443,6 +30947,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30443
30947
  if (Option.isNone(existingRow)) return;
30444
30948
  yield* projectionThreadRepository.upsert({
30445
30949
  ...existingRow.value,
30950
+ ...completionAfterEvent(existingRow.value, event),
30446
30951
  latestTurnId: event.payload.session.activeTurnId,
30447
30952
  updatedAt: event.occurredAt
30448
30953
  });
@@ -30454,6 +30959,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30454
30959
  if (Option.isNone(existingRow)) return;
30455
30960
  yield* projectionThreadRepository.upsert({
30456
30961
  ...existingRow.value,
30962
+ ...completionAfterEvent(existingRow.value, event),
30457
30963
  latestTurnId: event.payload.turnId,
30458
30964
  updatedAt: event.occurredAt
30459
30965
  });
@@ -30476,6 +30982,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30476
30982
  }
30477
30983
  yield* projectionThreadRepository.upsert({
30478
30984
  ...existingRow.value,
30985
+ ...completionAfterEvent(existingRow.value, event),
30479
30986
  latestTurnId,
30480
30987
  updatedAt: event.occurredAt
30481
30988
  });
@@ -31002,84 +31509,6 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
31002
31509
  });
31003
31510
  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));
31004
31511
  //#endregion
31005
- //#region src/orchestration/ThreadBackgroundLiveness.ts
31006
- /**
31007
- * ThreadBackgroundLivenessService - in-memory per-thread background liveness
31008
- * for the sidebar status pill.
31009
- *
31010
- * The turn can settle while native background work runs on (subagent fleets,
31011
- * workflow runs, Monitor watch loops); the shell previously showed nothing.
31012
- * Ingestion records task lifecycle transitions and the shell query reads the
31013
- * derived state at mapping time — no persistence, no migration. After a
31014
- * server restart the registry is empty until new task events arrive, which
31015
- * matches reality: orphaned background work is not live.
31016
- *
31017
- * "monitoring" is reserved for watch loops (monitor tasks and background
31018
- * shells) when they are the ONLY live work; any agent work presents as
31019
- * "working".
31020
- *
31021
- * @module ThreadBackgroundLivenessService
31022
- */
31023
- const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
31024
- "completed",
31025
- "failed",
31026
- "stopped",
31027
- "cancelled",
31028
- "interrupted"
31029
- ]);
31030
- var ThreadBackgroundLivenessService = class extends Context.Service()("@p4code/cli/orchestration/ThreadBackgroundLiveness/ThreadBackgroundLivenessService") {};
31031
- function make$75() {
31032
- const stateByThreadId = /* @__PURE__ */ new Map();
31033
- const stateFor = (threadId) => {
31034
- const existing = stateByThreadId.get(threadId);
31035
- if (existing) return existing;
31036
- const created = {
31037
- agents: /* @__PURE__ */ new Set(),
31038
- monitors: /* @__PURE__ */ new Set()
31039
- };
31040
- stateByThreadId.set(threadId, created);
31041
- return created;
31042
- };
31043
- const drop = (threadId, taskId) => {
31044
- const state = stateByThreadId.get(threadId);
31045
- if (!state) return;
31046
- state.agents.delete(taskId);
31047
- state.monitors.delete(taskId);
31048
- if (state.agents.size === 0 && state.monitors.size === 0) stateByThreadId.delete(threadId);
31049
- };
31050
- return {
31051
- recordTaskLiveness: (input) => {
31052
- const taskType = input.taskType;
31053
- if (taskType !== void 0 && INERT_TASK_TYPES.has(taskType)) {
31054
- drop(input.threadId, input.taskId);
31055
- return;
31056
- }
31057
- if (input.agentId !== void 0 && (taskType === void 0 || MONITOR_TASK_TYPES.has(taskType))) {
31058
- drop(input.threadId, input.taskId);
31059
- return;
31060
- }
31061
- if (input.kind === "completed" || input.status === "idle" || input.status !== void 0 && TERMINAL_STATUSES.has(input.status)) {
31062
- drop(input.threadId, input.taskId);
31063
- return;
31064
- }
31065
- drop(input.threadId, input.taskId);
31066
- const state = stateFor(input.threadId);
31067
- (taskType !== void 0 && MONITOR_TASK_TYPES.has(taskType) ? state.monitors : state.agents).add(input.taskId);
31068
- },
31069
- clearThreadLiveness: (threadId) => {
31070
- stateByThreadId.delete(threadId);
31071
- },
31072
- getThreadBackgroundLiveness: (threadId) => {
31073
- const state = stateByThreadId.get(threadId);
31074
- if (!state) return null;
31075
- if (state.agents.size > 0) return "working";
31076
- if (state.monitors.size > 0) return "monitoring";
31077
- return null;
31078
- }
31079
- };
31080
- }
31081
- const layer$64 = Layer.effect(ThreadBackgroundLivenessService, Effect.sync(make$75));
31082
- //#endregion
31083
31512
  //#region src/persistence/Services/ProjectionCheckpoints.ts
31084
31513
  /**
31085
31514
  * ProjectionCheckpointRepository - Projection repository interface for checkpoints.
@@ -31647,12 +32076,12 @@ const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spaw
31647
32076
  stderrInvalidUtf8: stderr.invalidUtf8
31648
32077
  };
31649
32078
  });
31650
- const make$74 = Effect.fn("ProcessRunner.make")(function* () {
32079
+ const make$75 = Effect.fn("ProcessRunner.make")(function* () {
31651
32080
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
31652
32081
  const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
31653
32082
  return ProcessRunner.of({ run });
31654
32083
  });
31655
- const layer$63 = Layer.effect(ProcessRunner, make$74());
32084
+ const layer$63 = Layer.effect(ProcessRunner, make$75());
31656
32085
  //#endregion
31657
32086
  //#region src/project/RepositoryIdentityResolver.ts
31658
32087
  const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
@@ -31743,7 +32172,7 @@ const resolveRepositoryIdentityFromCacheKey = Effect.fn("RepositoryIdentityResol
31743
32172
  rootPath: cacheKey
31744
32173
  }) : null;
31745
32174
  });
31746
- const make$73 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
32175
+ const make$74 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
31747
32176
  const processRunner = yield* ProcessRunner;
31748
32177
  const repositoryIdentityCache = yield* Cache.makeWith((cacheKey) => resolveRepositoryIdentityFromCacheKey(cacheKey).pipe(Effect.provideService(ProcessRunner, processRunner)), {
31749
32178
  capacity: options.cacheCapacity ?? DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY,
@@ -31758,7 +32187,7 @@ const make$73 = Effect.fn("RepositoryIdentityResolver.make")(function* (options
31758
32187
  });
31759
32188
  return RepositoryIdentityResolver.of({ resolve });
31760
32189
  });
31761
- const layer$62 = Layer.effect(RepositoryIdentityResolver, make$73()).pipe(Layer.provide(layer$63));
32190
+ const layer$62 = Layer.effect(RepositoryIdentityResolver, make$74()).pipe(Layer.provide(layer$63));
31762
32191
  //#endregion
31763
32192
  //#region src/orchestration/Layers/ProjectionSnapshotQuery.ts
31764
32193
  const decodeReadModel = Schema$1.decodeUnknownEffect(OrchestrationReadModel);
@@ -32114,6 +32543,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32114
32543
  archived_at AS "archivedAt",
32115
32544
  settled_override AS "settledOverride",
32116
32545
  settled_at AS "settledAt",
32546
+ lifecycle AS "lifecycle",
32547
+ lifecycle_reason AS "lifecycleReason",
32548
+ lifecycle_changed_at AS "lifecycleChangedAt",
32549
+ last_engaged_at AS "lastEngagedAt",
32117
32550
  snoozed_until AS "snoozedUntil",
32118
32551
  snoozed_at AS "snoozedAt",
32119
32552
  pinned_at AS "pinnedAt",
@@ -32151,6 +32584,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32151
32584
  archived_at AS "archivedAt",
32152
32585
  settled_override AS "settledOverride",
32153
32586
  settled_at AS "settledAt",
32587
+ lifecycle AS "lifecycle",
32588
+ lifecycle_reason AS "lifecycleReason",
32589
+ lifecycle_changed_at AS "lifecycleChangedAt",
32590
+ last_engaged_at AS "lastEngagedAt",
32154
32591
  snoozed_until AS "snoozedUntil",
32155
32592
  snoozed_at AS "snoozedAt",
32156
32593
  pinned_at AS "pinnedAt",
@@ -32190,6 +32627,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32190
32627
  archived_at AS "archivedAt",
32191
32628
  settled_override AS "settledOverride",
32192
32629
  settled_at AS "settledAt",
32630
+ lifecycle AS "lifecycle",
32631
+ lifecycle_reason AS "lifecycleReason",
32632
+ lifecycle_changed_at AS "lifecycleChangedAt",
32633
+ last_engaged_at AS "lastEngagedAt",
32193
32634
  snoozed_until AS "snoozedUntil",
32194
32635
  snoozed_at AS "snoozedAt",
32195
32636
  pinned_at AS "pinnedAt",
@@ -32654,6 +33095,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32654
33095
  archived_at AS "archivedAt",
32655
33096
  settled_override AS "settledOverride",
32656
33097
  settled_at AS "settledAt",
33098
+ lifecycle AS "lifecycle",
33099
+ lifecycle_reason AS "lifecycleReason",
33100
+ lifecycle_changed_at AS "lifecycleChangedAt",
33101
+ last_engaged_at AS "lastEngagedAt",
32657
33102
  snoozed_until AS "snoozedUntil",
32658
33103
  snoozed_at AS "snoozedAt",
32659
33104
  pinned_at AS "pinnedAt",
@@ -33346,6 +33791,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33346
33791
  archivedAt: row.archivedAt,
33347
33792
  settledOverride: row.settledOverride,
33348
33793
  settledAt: row.settledAt,
33794
+ lifecycle: row.lifecycle,
33795
+ lifecycleReason: row.lifecycleReason,
33796
+ lifecycleChangedAt: row.lifecycleChangedAt,
33797
+ lastEngagedAt: row.lastEngagedAt,
33798
+ doneAt: row.settledAt,
33349
33799
  snoozedUntil: row.snoozedUntil,
33350
33800
  snoozedAt: row.snoozedAt,
33351
33801
  pinnedAt: row.pinnedAt,
@@ -33489,6 +33939,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33489
33939
  archivedAt: row.archivedAt,
33490
33940
  settledOverride: row.settledOverride,
33491
33941
  settledAt: row.settledAt,
33942
+ lifecycle: row.lifecycle,
33943
+ lifecycleReason: row.lifecycleReason,
33944
+ lifecycleChangedAt: row.lifecycleChangedAt,
33945
+ lastEngagedAt: row.lastEngagedAt,
33946
+ doneAt: row.settledAt,
33492
33947
  snoozedUntil: row.snoozedUntil,
33493
33948
  snoozedAt: row.snoozedAt,
33494
33949
  pinnedAt: row.pinnedAt,
@@ -33555,6 +34010,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33555
34010
  archivedAt: row.archivedAt,
33556
34011
  settledOverride: row.settledOverride,
33557
34012
  settledAt: row.settledAt,
34013
+ lifecycle: row.lifecycle,
34014
+ lifecycleReason: row.lifecycleReason,
34015
+ lifecycleChangedAt: row.lifecycleChangedAt,
34016
+ lastEngagedAt: row.lastEngagedAt,
34017
+ doneAt: row.settledAt,
33558
34018
  snoozedUntil: row.snoozedUntil,
33559
34019
  snoozedAt: row.snoozedAt,
33560
34020
  pinnedAt: row.pinnedAt,
@@ -33621,6 +34081,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33621
34081
  archivedAt: row.archivedAt,
33622
34082
  settledOverride: row.settledOverride,
33623
34083
  settledAt: row.settledAt,
34084
+ lifecycle: row.lifecycle,
34085
+ lifecycleReason: row.lifecycleReason,
34086
+ lifecycleChangedAt: row.lifecycleChangedAt,
34087
+ lastEngagedAt: row.lastEngagedAt,
34088
+ doneAt: row.settledAt,
33624
34089
  snoozedUntil: row.snoozedUntil,
33625
34090
  snoozedAt: row.snoozedAt,
33626
34091
  pinnedAt: row.pinnedAt,
@@ -33735,6 +34200,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33735
34200
  archivedAt: threadRow.value.archivedAt,
33736
34201
  settledOverride: threadRow.value.settledOverride,
33737
34202
  settledAt: threadRow.value.settledAt,
34203
+ lifecycle: threadRow.value.lifecycle,
34204
+ lifecycleReason: threadRow.value.lifecycleReason,
34205
+ lifecycleChangedAt: threadRow.value.lifecycleChangedAt,
34206
+ lastEngagedAt: threadRow.value.lastEngagedAt,
34207
+ doneAt: threadRow.value.settledAt,
33738
34208
  snoozedUntil: threadRow.value.snoozedUntil,
33739
34209
  snoozedAt: threadRow.value.snoozedAt,
33740
34210
  pinnedAt: threadRow.value.pinnedAt,
@@ -33802,6 +34272,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33802
34272
  archivedAt: threadRow.value.archivedAt,
33803
34273
  settledOverride: threadRow.value.settledOverride,
33804
34274
  settledAt: threadRow.value.settledAt,
34275
+ lifecycle: threadRow.value.lifecycle,
34276
+ lifecycleReason: threadRow.value.lifecycleReason,
34277
+ lifecycleChangedAt: threadRow.value.lifecycleChangedAt,
34278
+ lastEngagedAt: threadRow.value.lastEngagedAt,
34279
+ doneAt: threadRow.value.settledAt,
33805
34280
  snoozedUntil: threadRow.value.snoozedUntil,
33806
34281
  snoozedAt: threadRow.value.snoozedAt,
33807
34282
  pinnedAt: threadRow.value.pinnedAt,
@@ -33927,9 +34402,185 @@ const OrchestrationProjectionSnapshotQueryLive = Layer.effect(ProjectionSnapshot
33927
34402
  //#region src/orchestration/Services/ThreadEventStream.ts
33928
34403
  var ThreadEventStreamService = class extends Context.Service()("@p4code/cli/orchestration/Services/ThreadEventStream/ThreadEventStreamService") {};
33929
34404
  //#endregion
34405
+ //#region src/orchestration/ThreadReviewProjection.ts
34406
+ const isMessagePayload = Schema$1.is(ThreadMessageSentPayload);
34407
+ const MAX_MESSAGE_IDENTITY_LENGTH = 1024;
34408
+ const MAX_PREVIEW_DEPTH = 12;
34409
+ const MAX_PREVIEW_NODES = 512;
34410
+ const TRUNCATION_MARKER$1 = "\n[truncated; recover source events]\n";
34411
+ function excerpt(text, length) {
34412
+ if (text.length <= length) return text;
34413
+ const sideLength = Math.max(0, Math.floor((length - 36) / 2));
34414
+ return text.slice(0, sideLength).replace(/[\uD800-\uDBFF]$/, "") + TRUNCATION_MARKER$1 + (sideLength > 0 ? text.slice(-sideLength).replace(/^[\uDC00-\uDFFF]/, "") : "");
34415
+ }
34416
+ /** Serialize only a bounded prefix, without walking or copying an oversized tool result. */
34417
+ function payloadPreview(payload) {
34418
+ let remainingNodes = MAX_PREVIEW_NODES;
34419
+ function* parts(value, depth) {
34420
+ if (--remainingNodes < 0 || depth > MAX_PREVIEW_DEPTH) {
34421
+ yield TRUNCATION_MARKER$1;
34422
+ return;
34423
+ }
34424
+ if (typeof value === "string") yield JSON.stringify(excerpt(value, THREAD_REVIEW_ITEM_MAX_BYTES));
34425
+ else if (value === null || typeof value !== "object") yield JSON.stringify(value) ?? "null";
34426
+ else {
34427
+ const array = Array.isArray(value);
34428
+ yield array ? "[" : "{";
34429
+ let first = true;
34430
+ for (const key in value) {
34431
+ if (!Object.hasOwn(value, key)) continue;
34432
+ if (remainingNodes <= 0) {
34433
+ yield TRUNCATION_MARKER$1;
34434
+ break;
34435
+ }
34436
+ if (!first) yield ",";
34437
+ first = false;
34438
+ if (!array) yield JSON.stringify(excerpt(key, THREAD_REVIEW_ITEM_MAX_BYTES)) + ":";
34439
+ yield* parts(value[key], depth + 1);
34440
+ }
34441
+ yield array ? "]" : "}";
34442
+ }
34443
+ }
34444
+ let text = "";
34445
+ let truncated = false;
34446
+ for (const part of parts(payload, 0)) {
34447
+ if (part.includes(TRUNCATION_MARKER$1) || part.includes(JSON.stringify(TRUNCATION_MARKER$1).slice(1, -1))) truncated = true;
34448
+ const remaining = THREAD_REVIEW_ITEM_MAX_BYTES - text.length;
34449
+ if (part.length > remaining) {
34450
+ text += part.slice(0, remaining);
34451
+ truncated = true;
34452
+ break;
34453
+ }
34454
+ text += part;
34455
+ }
34456
+ return {
34457
+ text,
34458
+ truncated
34459
+ };
34460
+ }
34461
+ function fitItem(item) {
34462
+ let result = item;
34463
+ let bytes = Buffer.byteLength(JSON.stringify(result));
34464
+ while (bytes > THREAD_REVIEW_ITEM_MAX_BYTES) {
34465
+ const text = result.kind === "message" ? result.text : result.payload;
34466
+ const length = Math.max(36, Math.floor(text.length / 2));
34467
+ result = result.kind === "message" ? {
34468
+ ...result,
34469
+ text: excerpt(text, length),
34470
+ truncated: true
34471
+ } : {
34472
+ ...result,
34473
+ payload: excerpt(text, length),
34474
+ truncated: true
34475
+ };
34476
+ bytes = Buffer.byteLength(JSON.stringify(result));
34477
+ }
34478
+ return result;
34479
+ }
34480
+ /** Page-local folds follow the projector: streaming appends; nonempty completion replaces. */
34481
+ function createThreadReviewPage(input) {
34482
+ const items = [];
34483
+ const messageIndexes = /* @__PURE__ */ new Map();
34484
+ let nextAfterSequence = input.afterSequence;
34485
+ let scannedEvents = 0;
34486
+ let itemBytes = 0;
34487
+ const envelopeBytes = Buffer.byteLength(JSON.stringify({
34488
+ threadId: input.threadId,
34489
+ items: [],
34490
+ throughSequence: input.throughSequence,
34491
+ nextAfterSequence: Number.MAX_SAFE_INTEGER,
34492
+ hasMore: false,
34493
+ scannedEvents: Number.MAX_SAFE_INTEGER
34494
+ }));
34495
+ function offer(event, sourceTruncated = false) {
34496
+ if (event.sequence > input.throughSequence) return false;
34497
+ if (event.aggregateKind !== "thread" || event.aggregateId !== input.threadId) {
34498
+ nextAfterSequence = event.sequence;
34499
+ scannedEvents += 1;
34500
+ return true;
34501
+ }
34502
+ let item;
34503
+ let index;
34504
+ let messageKey;
34505
+ if (event.type === "thread.message-sent" && !sourceTruncated && isMessagePayload(event.payload) && event.payload.role === "assistant" && !event.payload.attachments?.length && event.payload.messageId.length + (event.payload.turnId?.length ?? 0) <= MAX_MESSAGE_IDENTITY_LENGTH) {
34506
+ const payload = event.payload;
34507
+ messageKey = JSON.stringify([payload.turnId, payload.messageId]);
34508
+ index = messageIndexes.get(messageKey);
34509
+ const previous = index === void 0 ? void 0 : items[index];
34510
+ const previousMessage = previous?.kind === "message" ? previous : void 0;
34511
+ const replaces = !payload.streaming && payload.text.length > 0;
34512
+ const part = excerpt(payload.text, THREAD_REVIEW_ITEM_MAX_BYTES);
34513
+ item = fitItem({
34514
+ kind: "message",
34515
+ firstSequence: previousMessage?.firstSequence ?? event.sequence,
34516
+ lastSequence: event.sequence,
34517
+ messageId: payload.messageId,
34518
+ turnId: payload.turnId,
34519
+ role: payload.role,
34520
+ operation: replaces ? "replace" : previousMessage?.operation ?? "append",
34521
+ complete: !payload.streaming,
34522
+ text: replaces ? part : (previousMessage?.text ?? "") + part,
34523
+ truncated: part !== payload.text || !replaces && (previousMessage?.truncated ?? false)
34524
+ });
34525
+ } else {
34526
+ const preview = sourceTruncated && typeof event.payload === "string" ? {
34527
+ text: event.payload,
34528
+ truncated: true
34529
+ } : payloadPreview(event.payload);
34530
+ item = fitItem({
34531
+ kind: "event",
34532
+ type: event.type,
34533
+ firstSequence: event.sequence,
34534
+ lastSequence: event.sequence,
34535
+ payload: preview.text,
34536
+ truncated: preview.truncated
34537
+ });
34538
+ }
34539
+ const previousBytes = index === void 0 ? 0 : Buffer.byteLength(JSON.stringify(items[index]));
34540
+ const nextBytes = itemBytes - previousBytes + Buffer.byteLength(JSON.stringify(item));
34541
+ if (index === void 0 && items.length >= 100 || envelopeBytes + nextBytes + items.length + 1 > 48e3) return false;
34542
+ if (index === void 0) {
34543
+ if (messageKey !== void 0) messageIndexes.set(messageKey, items.length);
34544
+ items.push(item);
34545
+ } else items[index] = item;
34546
+ itemBytes = nextBytes;
34547
+ nextAfterSequence = event.sequence;
34548
+ scannedEvents += 1;
34549
+ return true;
34550
+ }
34551
+ return {
34552
+ offer,
34553
+ offerStored(row) {
34554
+ if (row.payloadJson.length <= 8e3) try {
34555
+ return offer({
34556
+ ...row,
34557
+ payload: JSON.parse(row.payloadJson)
34558
+ });
34559
+ } catch {}
34560
+ return offer({
34561
+ ...row,
34562
+ payload: row.payloadJson.slice(0, THREAD_REVIEW_ITEM_MAX_BYTES)
34563
+ }, true);
34564
+ },
34565
+ finish(exhausted) {
34566
+ if (exhausted) nextAfterSequence = Math.max(nextAfterSequence, input.throughSequence);
34567
+ return {
34568
+ threadId: input.threadId,
34569
+ items: items.toSorted((a, b) => a.lastSequence - b.lastSequence),
34570
+ throughSequence: input.throughSequence,
34571
+ nextAfterSequence,
34572
+ hasMore: nextAfterSequence < input.throughSequence,
34573
+ scannedEvents
34574
+ };
34575
+ }
34576
+ };
34577
+ }
34578
+ //#endregion
33930
34579
  //#region src/orchestration/Layers/ThreadEventStream.ts
34580
+ const REVIEW_SOURCE_PAGE_SIZE = 100;
33931
34581
  const makeThreadEventStream = Effect.gen(function* () {
33932
34582
  const engine = yield* OrchestrationEngineService;
34583
+ const eventStore = yield* OrchestrationEventStore;
33933
34584
  const isThreadEvent = (threadId) => (event) => event.aggregateKind === "thread" && event.aggregateId === threadId;
33934
34585
  const watch = (input) => Stream.unwrap(Effect.gen(function* () {
33935
34586
  const matchesThread = isThreadEvent(input.threadId);
@@ -33971,9 +34622,38 @@ const makeThreadEventStream = Effect.gen(function* () {
33971
34622
  hasMore
33972
34623
  };
33973
34624
  });
34625
+ const readReview = Effect.fn("ThreadEventStream.readReview")(function* (input) {
34626
+ const headSequence = yield* engine.latestSequence;
34627
+ const throughSequence = Math.min(input.throughSequence ?? headSequence, headSequence);
34628
+ const afterSequence = input.afterSequence ?? 0;
34629
+ const page = createThreadReviewPage({
34630
+ threadId: input.threadId,
34631
+ afterSequence,
34632
+ throughSequence
34633
+ });
34634
+ if (afterSequence >= throughSequence) return page.finish(true);
34635
+ let cursor = afterSequence;
34636
+ let scanned = 0;
34637
+ while (scanned < 2e3 && cursor < throughSequence) {
34638
+ const limit = Math.min(REVIEW_SOURCE_PAGE_SIZE, THREAD_REVIEW_SCAN_LIMIT - scanned);
34639
+ const rows = yield* eventStore.readReviewPage({
34640
+ afterSequence: cursor,
34641
+ throughSequence,
34642
+ limit
34643
+ });
34644
+ for (const row of rows) {
34645
+ if (!page.offerStored(row)) return page.finish(false);
34646
+ cursor = row.sequence;
34647
+ scanned += 1;
34648
+ }
34649
+ if (rows.length < limit) return page.finish(true);
34650
+ }
34651
+ return page.finish(false);
34652
+ });
33974
34653
  return ThreadEventStreamService.of({
33975
34654
  watch,
33976
- read
34655
+ read,
34656
+ readReview
33977
34657
  });
33978
34658
  });
33979
34659
  const ThreadEventStreamLive = Layer.effect(ThreadEventStreamService, makeThreadEventStream);
@@ -33990,7 +34670,7 @@ const OrchestrationProjectionPipelineLayerLive = OrchestrationProjectionPipeline
33990
34670
  const ThreadBackgroundLivenessLayerLive = layer$64;
33991
34671
  const OrchestrationInfrastructureLayerLive = Layer.mergeAll(OrchestrationProjectionSnapshotQueryLive, OrchestrationEventInfrastructureLayerLive, OrchestrationProjectionPipelineLayerLive, ThreadBackgroundLivenessLayerLive);
33992
34672
  const OrchestrationEngineLayerLive = OrchestrationEngineLive.pipe(Layer.provide(OrchestrationInfrastructureLayerLive));
33993
- const OrchestrationLayerLive = Layer.mergeAll(OrchestrationInfrastructureLayerLive, OrchestrationEngineLayerLive, ThreadEventStreamLive.pipe(Layer.provide(OrchestrationEngineLayerLive)));
34673
+ const OrchestrationLayerLive = Layer.mergeAll(OrchestrationInfrastructureLayerLive, OrchestrationEngineLayerLive, ThreadEventStreamLive.pipe(Layer.provide(OrchestrationEngineLayerLive), Layer.provide(OrchestrationEventStoreLive)));
33994
34674
  //#endregion
33995
34675
  //#region ../../packages/shared/src/keybindings.ts
33996
34676
  const DEFAULT_KEYBINDINGS = [
@@ -34462,7 +35142,7 @@ function mergeWithDefaultKeybindings(custom) {
34462
35142
  * Keybindings - Service tag for keybinding configuration operations.
34463
35143
  */
34464
35144
  var Keybindings = class extends Context.Service()("@p4code/cli/keybindings") {};
34465
- const make$72 = Effect.gen(function* () {
35145
+ const make$73 = Effect.gen(function* () {
34466
35146
  const { keybindingsConfigPath } = yield* ServerConfig$1;
34467
35147
  const fs = yield* FileSystem.FileSystem;
34468
35148
  const path = yield* Path.Path;
@@ -34723,7 +35403,7 @@ const make$72 = Effect.gen(function* () {
34723
35403
  }))
34724
35404
  };
34725
35405
  });
34726
- const layer$61 = Layer.effect(Keybindings, make$72);
35406
+ const layer$61 = Layer.effect(Keybindings, make$73);
34727
35407
  //#endregion
34728
35408
  //#region src/process/externalLauncher.ts
34729
35409
  /**
@@ -34950,7 +35630,7 @@ const launchEditorProcess = Effect.fn("externalLauncher.launchEditorProcess")(fu
34950
35630
  cause
34951
35631
  }));
34952
35632
  });
34953
- const make$71 = Effect.gen(function* () {
35633
+ const make$72 = Effect.gen(function* () {
34954
35634
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
34955
35635
  const fileSystem = yield* FileSystem.FileSystem;
34956
35636
  const path = yield* Path.Path;
@@ -34961,7 +35641,7 @@ const make$71 = Effect.gen(function* () {
34961
35641
  launchEditor: (input) => provideCommandResolutionServices(Effect.flatMap(resolveEditorLaunch(input), (launch) => launchEditorProcess(launch).pipe(Effect.provideService(ChildProcessSpawner$1.ChildProcessSpawner, spawner))))
34962
35642
  });
34963
35643
  });
34964
- const layer$60 = Layer.effect(ExternalLauncher, make$71);
35644
+ const layer$60 = Layer.effect(ExternalLauncher, make$72);
34965
35645
  //#endregion
34966
35646
  //#region src/orchestration/Services/OrchestrationReactor.ts
34967
35647
  /**
@@ -34979,7 +35659,7 @@ var OrchestrationReactor = class extends Context.Service()("@p4code/cli/orchestr
34979
35659
  //#endregion
34980
35660
  //#region src/serverLifecycleEvents.ts
34981
35661
  var ServerLifecycleEvents = class extends Context.Service()("@p4code/cli/serverLifecycleEvents") {};
34982
- const make$70 = Effect.gen(function* () {
35662
+ const make$71 = Effect.gen(function* () {
34983
35663
  const pubsub = yield* PubSub.unbounded();
34984
35664
  const state = yield* Ref.make({
34985
35665
  sequence: 0,
@@ -35003,7 +35683,7 @@ const make$70 = Effect.gen(function* () {
35003
35683
  }
35004
35684
  };
35005
35685
  });
35006
- const layer$59 = Layer.effect(ServerLifecycleEvents, make$70);
35686
+ const layer$59 = Layer.effect(ServerLifecycleEvents, make$71);
35007
35687
  //#endregion
35008
35688
  //#region src/telemetry/Identify.ts
35009
35689
  const CodexAuthJsonSchema = Schema$1.Struct({ tokens: Schema$1.Struct({ account_id: Schema$1.String }) });
@@ -35176,7 +35856,7 @@ var AnalyticsService = class AnalyticsService extends Context.Service()("@p4code
35176
35856
  /** No-op layer for callers that intentionally disable telemetry. */
35177
35857
  static layerTest = Layer.succeed(AnalyticsService, inert);
35178
35858
  };
35179
- const make$69 = Effect.gen(function* () {
35859
+ const make$70 = Effect.gen(function* () {
35180
35860
  const telemetryConfig = yield* TelemetryEnvConfig;
35181
35861
  const posthogKey = telemetryConfig.posthogKey.trim();
35182
35862
  if (!telemetryConfig.enabled || posthogKey === "") return inert;
@@ -35246,7 +35926,7 @@ const make$69 = Effect.gen(function* () {
35246
35926
  flush
35247
35927
  });
35248
35928
  });
35249
- const layer$58 = Layer.effect(AnalyticsService, make$69);
35929
+ const layer$58 = Layer.effect(AnalyticsService, make$70);
35250
35930
  AnalyticsService.layerTest;
35251
35931
  //#endregion
35252
35932
  //#region src/service/pinnedRuntime.ts
@@ -35593,7 +36273,7 @@ var BootServiceInstallError = class extends Schema$1.TaggedErrorClass()("BootSer
35593
36273
  }
35594
36274
  };
35595
36275
  var BootService = class extends Context.Service()("@p4code/cli/service/bootService") {};
35596
- const make$68 = Effect.fn("cloud.boot_service.make")(function* (input) {
36276
+ const make$69 = Effect.fn("cloud.boot_service.make")(function* (input) {
35597
36277
  const hostExecPath = yield* HostProcessExecutablePath;
35598
36278
  const hostArguments = yield* HostProcessArguments;
35599
36279
  const host = input.host ?? {
@@ -35815,7 +36495,7 @@ const make$68 = Effect.fn("cloud.boot_service.make")(function* (input) {
35815
36495
  logPath
35816
36496
  });
35817
36497
  });
35818
- const layer$57 = (input) => Layer.effect(BootService, make$68(input));
36498
+ const layer$57 = (input) => Layer.effect(BootService, make$69(input));
35819
36499
  //#endregion
35820
36500
  //#region src/service/selfUpdate.ts
35821
36501
  /**
@@ -35890,7 +36570,7 @@ const resolveServerSelfUpdateCapability = Effect.fn("cloud.server_self_update.re
35890
36570
  return null;
35891
36571
  });
35892
36572
  var ServerSelfUpdate = class extends Context.Service()("@p4code/cli/service/selfUpdate/ServerSelfUpdate") {};
35893
- const make$67 = Effect.fn("cloud.server_self_update.make")(function* (options) {
36573
+ const make$68 = Effect.fn("cloud.server_self_update.make")(function* (options) {
35894
36574
  const serverConfig = yield* ServerConfig$1;
35895
36575
  const fs = yield* FileSystem.FileSystem;
35896
36576
  const path = yield* Path.Path;
@@ -36040,7 +36720,7 @@ const make$67 = Effect.fn("cloud.server_self_update.make")(function* (options) {
36040
36720
  });
36041
36721
  return ServerSelfUpdate.of({ update });
36042
36722
  });
36043
- const layer$56 = Layer.effect(ServerSelfUpdate, make$67()).pipe(Layer.provide(layer$63));
36723
+ const layer$56 = Layer.effect(ServerSelfUpdate, make$68()).pipe(Layer.provide(layer$63));
36044
36724
  //#endregion
36045
36725
  //#region src/environment/ServerEnvironmentLabel.ts
36046
36726
  const ServerEnvironmentLabelCommandProbe = Schema$1.Literals(["macos-computer-name", "linux-pretty-hostname"]);
@@ -36172,7 +36852,7 @@ function platformArch(architecture) {
36172
36852
  default: return "other";
36173
36853
  }
36174
36854
  }
36175
- const make$66 = Effect.gen(function* () {
36855
+ const make$67 = Effect.gen(function* () {
36176
36856
  const fileSystem = yield* FileSystem.FileSystem;
36177
36857
  const path = yield* Path.Path;
36178
36858
  const serverConfig = yield* ServerConfig$1;
@@ -36221,6 +36901,7 @@ const make$66 = Effect.gen(function* () {
36221
36901
  connectionProbe: true,
36222
36902
  pullRequests: true,
36223
36903
  threadSettlement: true,
36904
+ threadLifecycleV2: true,
36224
36905
  threadSnooze: true,
36225
36906
  threadPinning: true,
36226
36907
  threadFork: true,
@@ -36239,7 +36920,7 @@ const make$66 = Effect.gen(function* () {
36239
36920
  * state. It intentionally has no fallback Layer.succeed value: callers must
36240
36921
  * provide the external platform services and a ServerConfig.
36241
36922
  */
36242
- const layer$55 = Layer.effect(ServerEnvironment, make$66).pipe(Layer.provide(layer$63));
36923
+ const layer$55 = Layer.effect(ServerEnvironment, make$67).pipe(Layer.provide(layer$63));
36243
36924
  //#endregion
36244
36925
  //#region src/provider/Services/ProviderSessionReaper.ts
36245
36926
  var ProviderSessionReaper = class extends Context.Service()("@p4code/cli/provider/Services/ProviderSessionReaper") {};
@@ -36381,7 +37062,7 @@ const maybeOpenBrowser = (target) => Effect.gen(function* () {
36381
37062
  yield* (yield* ExternalLauncher).launchBrowser(target).pipe(Effect.catch(() => Effect.logInfo("browser auto-open unavailable", { hint: `Open ${target} in your browser.` })));
36382
37063
  });
36383
37064
  const runStartupPhase = (phase, effect) => effect.pipe(Effect.annotateSpans({ "startup.phase": phase }), Effect.withSpan(`server.startup.${phase}`));
36384
- const make$65 = Effect.gen(function* () {
37065
+ const make$66 = Effect.gen(function* () {
36385
37066
  const serverConfig = yield* ServerConfig$1;
36386
37067
  const keybindings = yield* Keybindings;
36387
37068
  const orchestrationReactor = yield* OrchestrationReactor;
@@ -36522,7 +37203,7 @@ const make$65 = Effect.gen(function* () {
36522
37203
  enqueueCommand: commandGate.enqueueCommand
36523
37204
  };
36524
37205
  });
36525
- const layer$54 = Layer.effect(ServerRuntimeStartup, make$65);
37206
+ const layer$54 = Layer.effect(ServerRuntimeStartup, make$66);
36526
37207
  //#endregion
36527
37208
  //#region src/serverRuntimeState.ts
36528
37209
  const PersistedServerRuntimeState = Schema$1.Struct({
@@ -36679,7 +37360,7 @@ function expandHomePath$2(input, path) {
36679
37360
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
36680
37361
  return input;
36681
37362
  }
36682
- const make$64 = Effect.gen(function* () {
37363
+ const make$65 = Effect.gen(function* () {
36683
37364
  const fileSystem = yield* FileSystem.FileSystem;
36684
37365
  const path = yield* Path.Path;
36685
37366
  const statWorkspaceRoot = Effect.fn("WorkspacePaths.statWorkspaceRoot")(function* (workspaceRoot, normalizedWorkspaceRoot, phase) {
@@ -36736,7 +37417,7 @@ const make$64 = Effect.gen(function* () {
36736
37417
  resolveRelativePathWithinRoot
36737
37418
  });
36738
37419
  });
36739
- const layer$53 = Layer.effect(WorkspacePaths, make$64);
37420
+ const layer$53 = Layer.effect(WorkspacePaths, make$65);
36740
37421
  //#endregion
36741
37422
  //#region src/cli/project.ts
36742
37423
  const isEnvironmentHttpCommonError = Schema$1.is(EnvironmentHttpCommonError);
@@ -36871,7 +37552,7 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* (
36871
37552
  operation: "resolveProjectTarget",
36872
37553
  identifier: input.identifier
36873
37554
  });
36874
- const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null);
37555
+ const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt == null);
36875
37556
  const exactIdMatch = activeProjects.find((project) => project.id === trimmedIdentifier);
36876
37557
  if (exactIdMatch) return {
36877
37558
  id: exactIdMatch.id,
@@ -36895,7 +37576,7 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* (
36895
37576
  };
36896
37577
  });
36897
37578
  const fetchLiveOrchestrationSnapshot = (origin, bearerToken) => Effect.gen(function* () {
36898
- return yield* (yield* makeLiveServerClient(origin)).orchestration.snapshot({ headers: { authorization: `Bearer ${bearerToken}` } });
37579
+ return yield* (yield* makeLiveServerClient(origin)).orchestration.shellSnapshot({ headers: { authorization: `Bearer ${bearerToken}` } });
36899
37580
  }).pipe(withProjectCliLiveServerTimeout, Effect.mapError(projectCommandErrorFromLiveServerRequest));
36900
37581
  const dispatchLiveOrchestrationCommand = (origin, bearerToken, command) => Effect.gen(function* () {
36901
37582
  yield* (yield* makeLiveServerClient(origin)).orchestration.dispatch({
@@ -36904,7 +37585,7 @@ const dispatchLiveOrchestrationCommand = (origin, bearerToken, command) => Effec
36904
37585
  });
36905
37586
  }).pipe(withProjectCliLiveServerTimeout, Effect.mapError(projectCommandErrorFromLiveServerRequest));
36906
37587
  const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () {
36907
- return yield* (yield* ProjectionSnapshotQuery).getSnapshot();
37588
+ return yield* (yield* ProjectionSnapshotQuery).getCommandReadModel();
36908
37589
  });
36909
37590
  const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")(function* (environmentAuth, config) {
36910
37591
  const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);
@@ -36952,7 +37633,7 @@ const projectAddCommand = Command.make("add", {
36952
37633
  title: Flag.string("title").pipe(Flag.withDescription("Optional project title."), Flag.optional)
36953
37634
  }).pipe(Command.withDescription("Add a project."), Command.withHandler((flags) => runProjectMutation(flags, Effect.fn("projectAddMutation")(function* ({ snapshot, dispatch }) {
36954
37635
  const workspaceRoot = yield* normalizeWorkspaceRootForProjectCommand(flags.workspaceRoot);
36955
- const existingProject = snapshot.projects.find((project) => project.deletedAt === null && project.workspaceRoot === workspaceRoot);
37636
+ const existingProject = snapshot.projects.find((project) => project.deletedAt == null && project.workspaceRoot === workspaceRoot);
36956
37637
  if (existingProject) return yield* new ProjectAlreadyExistsError({
36957
37638
  operation: "addProject",
36958
37639
  projectId: existingProject.id,
@@ -37919,7 +38600,7 @@ const logP4ProjectFileLoadError = (error) => Effect.logWarning(error).pipe(Effec
37919
38600
  filePath: error.filePath,
37920
38601
  errorTag: error._tag
37921
38602
  }));
37922
- const make$63 = Effect.gen(function* () {
38603
+ const make$64 = Effect.gen(function* () {
37923
38604
  const fileSystem = yield* FileSystem.FileSystem;
37924
38605
  const path = yield* Path.Path;
37925
38606
  const load = Effect.fn("P4ProjectFileLoader.load")(function* (workspaceRoot) {
@@ -37940,7 +38621,7 @@ const make$63 = Effect.gen(function* () {
37940
38621
  });
37941
38622
  return P4ProjectFileLoader.of({ load });
37942
38623
  });
37943
- const layer$52 = Layer.effect(P4ProjectFileLoader, make$63);
38624
+ const layer$52 = Layer.effect(P4ProjectFileLoader, make$64);
37944
38625
  //#endregion
37945
38626
  //#region src/project/ProjectFaviconResolver.ts
37946
38627
  /**
@@ -38015,7 +38696,7 @@ function extractIconHref(source) {
38015
38696
  return null;
38016
38697
  }
38017
38698
  const optionOnNotFound$1 = (effect) => effect.pipe(Effect.map(Option.some), Effect.catchTags({ PlatformError: (error) => error.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(error) }));
38018
- const make$62 = Effect.gen(function* () {
38699
+ const make$63 = Effect.gen(function* () {
38019
38700
  const fileSystem = yield* FileSystem.FileSystem;
38020
38701
  const path = yield* Path.Path;
38021
38702
  const workspacePaths = yield* WorkspacePaths;
@@ -38084,7 +38765,7 @@ const make$62 = Effect.gen(function* () {
38084
38765
  });
38085
38766
  return ProjectFaviconResolver.of({ resolvePath });
38086
38767
  });
38087
- const layer$51 = Layer.effect(ProjectFaviconResolver, make$62);
38768
+ const layer$51 = Layer.effect(ProjectFaviconResolver, make$63);
38088
38769
  //#endregion
38089
38770
  //#region src/assets/AssetAccess.ts
38090
38771
  const ASSET_ROUTE_PREFIX = "/api/assets";
@@ -38495,10 +39176,10 @@ const resolveAsset = Effect.fn("AssetAccess.resolveAsset")(function* (token, rel
38495
39176
  //#endregion
38496
39177
  //#region src/observability/BrowserTraceCollector.ts
38497
39178
  var BrowserTraceCollector = class extends Context.Service()("@p4code/cli/observability/BrowserTraceCollector") {};
38498
- const make$61 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
39179
+ const make$62 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
38499
39180
  for (const record of records) sink.push(record);
38500
39181
  }) });
38501
- const layer$50 = (sink) => Layer.succeed(BrowserTraceCollector, make$61(sink));
39182
+ const layer$50 = (sink) => Layer.succeed(BrowserTraceCollector, make$62(sink));
38502
39183
  //#endregion
38503
39184
  //#region src/auth/http.ts
38504
39185
  const CREDENTIAL_RESPONSE_HEADERS = {
@@ -39820,6 +40501,9 @@ const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
39820
40501
  const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
39821
40502
  const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
39822
40503
  const LIST_REFS_REFRESH_FAILURE_COOLDOWN = Duration.seconds(30);
40504
+ const STATUS_STATIC_CACHE_CAPACITY = 2048;
40505
+ const STATUS_DEFAULT_BRANCH_CACHE_TTL = Duration.minutes(5);
40506
+ const STATUS_ORIGIN_EXISTS_CACHE_TTL = Duration.minutes(5);
39823
40507
  const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
39824
40508
  GCM_INTERACTIVE: "never",
39825
40509
  GIT_ASKPASS: "",
@@ -40031,6 +40715,9 @@ function isMissingGitCwdError(error) {
40031
40715
  function isNonRepositoryGitStderr(stderr) {
40032
40716
  return stderr.toLowerCase().includes("not a git repository");
40033
40717
  }
40718
+ function isUnbornHeadStderr(stderr) {
40719
+ return stderr.toLowerCase().includes("unknown revision") && stderr.toLowerCase().includes("path not in the working tree");
40720
+ }
40034
40721
  const nowUnixNano = DateTime.now.pipe(Effect.map((now) => BigInt(DateTime.toEpochMillis(now)) * 1000000n));
40035
40722
  const addCurrentSpanEvent = (name, attributes) => Effect.gen(function* () {
40036
40723
  const span = yield* Effect.currentSpan;
@@ -40424,6 +41111,47 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40424
41111
  const cacheKey = normalizeRepositoryPathsCacheKey(cwd);
40425
41112
  return Cache.get(refresh ? repositoryPathsRefreshCache : repositoryPathsCache, cacheKey);
40426
41113
  };
41114
+ const defaultBranchCache = yield* Cache.makeWith((gitCommonDir) => Effect.gen(function* () {
41115
+ const path = yield* Path.Path;
41116
+ const fetchCwd = path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;
41117
+ return yield* executeGit("GitVcsDriver.statusDetails.defaultBranch", fetchCwd, [
41118
+ "--git-dir",
41119
+ gitCommonDir,
41120
+ "symbolic-ref",
41121
+ "refs/remotes/origin/HEAD"
41122
+ ], { allowNonZeroExit: true }).pipe(Effect.map((result) => {
41123
+ if (result.exitCode !== 0) return null;
41124
+ return parseDefaultBranchFromRemoteHeadRef(result.stdout, "origin");
41125
+ }));
41126
+ }), {
41127
+ capacity: STATUS_STATIC_CACHE_CAPACITY,
41128
+ timeToLive: Exit.match({
41129
+ onSuccess: () => STATUS_DEFAULT_BRANCH_CACHE_TTL,
41130
+ onFailure: () => Duration.zero
41131
+ })
41132
+ });
41133
+ const originExistsCache = yield* Cache.makeWith((gitCommonDir) => Effect.gen(function* () {
41134
+ const path = yield* Path.Path;
41135
+ const fetchCwd = path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;
41136
+ return yield* executeGit("GitVcsDriver.statusDetails.originExists", fetchCwd, [
41137
+ "--git-dir",
41138
+ gitCommonDir,
41139
+ "remote",
41140
+ "get-url",
41141
+ "origin"
41142
+ ], { allowNonZeroExit: true }).pipe(Effect.map((result) => result.exitCode === 0));
41143
+ }), {
41144
+ capacity: STATUS_STATIC_CACHE_CAPACITY,
41145
+ timeToLive: Exit.match({
41146
+ onSuccess: () => STATUS_ORIGIN_EXISTS_CACHE_TTL,
41147
+ onFailure: () => Duration.zero
41148
+ })
41149
+ });
41150
+ const invalidateStatusStaticCaches = Effect.fn("invalidateStatusStaticCaches")(function* (cwd) {
41151
+ const cacheKey = (yield* resolveRepositoryPaths(cwd).pipe(Effect.catchTags({ GitCommandError: () => Effect.succeed(null) })))?.gitCommonDir ?? normalizeRepositoryPathsCacheKey(cwd);
41152
+ yield* Cache.invalidate(defaultBranchCache, cacheKey);
41153
+ yield* Cache.invalidate(originExistsCache, cacheKey);
41154
+ });
40427
41155
  const resolveGitCommonDir = Effect.fn("resolveGitCommonDir")(function* (cwd) {
40428
41156
  const repositoryPaths = yield* resolveRepositoryPaths(cwd);
40429
41157
  if (repositoryPaths !== null) return repositoryPaths.gitCommonDir;
@@ -40467,10 +41195,16 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40467
41195
  remoteName: upstream.remoteName
40468
41196
  }));
40469
41197
  });
40470
- const resolveDefaultBranchName = (cwd, remoteName) => executeGit("GitVcsDriver.resolveDefaultBranchName", cwd, ["symbolic-ref", `refs/remotes/${remoteName}/HEAD`], { allowNonZeroExit: true }).pipe(Effect.map((result) => {
40471
- if (result.exitCode !== 0) return null;
40472
- return parseDefaultBranchFromRemoteHeadRef(result.stdout, remoteName);
40473
- }));
41198
+ const resolveDefaultBranchName = Effect.fn("resolveDefaultBranchName")(function* (cwd, remoteName) {
41199
+ if (remoteName === "origin") {
41200
+ const repositoryPaths = yield* resolveRepositoryPaths(cwd);
41201
+ if (repositoryPaths) return yield* Cache.get(defaultBranchCache, repositoryPaths.gitCommonDir);
41202
+ }
41203
+ return yield* executeGit("GitVcsDriver.resolveDefaultBranchName", cwd, ["symbolic-ref", `refs/remotes/${remoteName}/HEAD`], { allowNonZeroExit: true }).pipe(Effect.map((result) => {
41204
+ if (result.exitCode !== 0) return null;
41205
+ return parseDefaultBranchFromRemoteHeadRef(result.stdout, remoteName);
41206
+ }));
41207
+ });
40474
41208
  const remoteBranchExists = (cwd, remoteName, refName) => executeGit("GitVcsDriver.remoteBranchExists", cwd, [
40475
41209
  "show-ref",
40476
41210
  "--verify",
@@ -40575,9 +41309,10 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40575
41309
  "HEAD"
40576
41310
  ], { allowNonZeroExit: true }).pipe(Effect.catchTags({ GitCommandError: (error) => isMissingGitCwdError(error) ? Effect.succeed(null) : Effect.fail(error) }));
40577
41311
  if (branchResult === null) return NON_REPOSITORY_REMOTE_STATUS_DETAILS;
41312
+ let branch;
40578
41313
  if (branchResult.exitCode !== 0) {
40579
41314
  if (isNonRepositoryGitStderr(branchResult.stderr)) return NON_REPOSITORY_REMOTE_STATUS_DETAILS;
40580
- return yield* new GitCommandError({
41315
+ if (!isUnbornHeadStderr(branchResult.stderr)) return yield* new GitCommandError({
40581
41316
  ...gitCommandContext({
40582
41317
  operation: "GitVcsDriver.statusDetailsRemote.branch",
40583
41318
  cwd,
@@ -40592,9 +41327,16 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40592
41327
  stdoutLength: branchResult.stdout.length,
40593
41328
  stderrLength: branchResult.stderr.length
40594
41329
  });
41330
+ branch = (yield* runGitStdout("GitVcsDriver.statusDetailsRemote.unbornBranch", cwd, [
41331
+ "symbolic-ref",
41332
+ "--quiet",
41333
+ "--short",
41334
+ "HEAD"
41335
+ ])).trim() || null;
41336
+ } else {
41337
+ const branchValue = branchResult.stdout.trim();
41338
+ branch = branchValue.length > 0 && branchValue !== "HEAD" ? branchValue : null;
40595
41339
  }
40596
- const branchValue = branchResult.stdout.trim();
40597
- const branch = branchValue.length > 0 && branchValue !== "HEAD" ? branchValue : null;
40598
41340
  const upstreamRef = (yield* resolveCurrentUpstream(cwd))?.upstreamRef ?? null;
40599
41341
  let aheadCount = 0;
40600
41342
  let behindCount = 0;
@@ -40652,18 +41394,53 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40652
41394
  stderrLength: statusResult.stderr.length
40653
41395
  });
40654
41396
  }
40655
- const [unstagedNumstatStdout, stagedNumstatStdout, defaultRefResult, hasPrimaryRemote] = yield* Effect.all([
40656
- runGitStdout("GitVcsDriver.statusDetails.unstagedNumstat", cwd, ["diff", "--numstat"]),
40657
- runGitStdout("GitVcsDriver.statusDetails.stagedNumstat", cwd, [
41397
+ const statusCacheKey = (yield* resolveRepositoryPaths(cwd).pipe(Effect.catchTags({ GitCommandError: () => Effect.succeed(null) })))?.gitCommonDir;
41398
+ const [numstatStdout, defaultBranch, hasPrimaryRemote] = yield* Effect.all([
41399
+ executeGitWithStableDiagnostics("GitVcsDriver.statusDetails.numstat", cwd, [
40658
41400
  "diff",
40659
- "--cached",
41401
+ "HEAD",
40660
41402
  "--numstat"
40661
- ]),
40662
- executeGit("GitVcsDriver.statusDetails.defaultRef", cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], { allowNonZeroExit: true }),
40663
- originRemoteExists(cwd).pipe(Effect.orElseSucceed(() => false))
41403
+ ], { allowNonZeroExit: true }).pipe(Effect.flatMap((result) => {
41404
+ if (result.exitCode === 0) return Effect.succeed(result.stdout);
41405
+ if (isUnbornHeadStderr(result.stderr)) return Effect.map(Effect.all([runGitStdout("GitVcsDriver.statusDetails.numstat.unborn", cwd, ["diff", "--numstat"]), runGitStdout("GitVcsDriver.statusDetails.numstat.unborn.staged", cwd, [
41406
+ "diff",
41407
+ "--cached",
41408
+ "--numstat"
41409
+ ])]), ([unstagedStdout, stagedStdout]) => {
41410
+ const staged = parseNumstatEntries(stagedStdout);
41411
+ const unstaged = parseNumstatEntries(unstagedStdout);
41412
+ const map = /* @__PURE__ */ new Map();
41413
+ for (const entry of [...staged, ...unstaged]) {
41414
+ const existing = map.get(entry.path) ?? {
41415
+ insertions: 0,
41416
+ deletions: 0
41417
+ };
41418
+ existing.insertions += entry.insertions;
41419
+ existing.deletions += entry.deletions;
41420
+ map.set(entry.path, existing);
41421
+ }
41422
+ return Array.from(map.entries()).map(([p, s]) => `${s.insertions}\t${s.deletions}\t${p}`).join("\n");
41423
+ });
41424
+ return Effect.fail(new GitCommandError({
41425
+ ...gitCommandContext({
41426
+ operation: "GitVcsDriver.statusDetails.numstat",
41427
+ cwd,
41428
+ args: [
41429
+ "diff",
41430
+ "HEAD",
41431
+ "--numstat"
41432
+ ]
41433
+ }),
41434
+ detail: "git diff HEAD --numstat failed.",
41435
+ exitCode: result.exitCode,
41436
+ stdoutLength: result.stdout.length,
41437
+ stderrLength: result.stderr.length
41438
+ }));
41439
+ })),
41440
+ statusCacheKey ? Cache.get(defaultBranchCache, statusCacheKey).pipe(Effect.orElseSucceed(() => null)) : resolveDefaultBranchName(cwd, "origin").pipe(Effect.orElseSucceed(() => null)),
41441
+ statusCacheKey ? Cache.get(originExistsCache, statusCacheKey).pipe(Effect.orElseSucceed(() => false)) : originRemoteExists(cwd).pipe(Effect.orElseSucceed(() => false))
40664
41442
  ], { concurrency: "unbounded" });
40665
41443
  const statusStdout = statusResult.stdout;
40666
- const defaultBranch = defaultRefResult.exitCode === 0 ? defaultRefResult.stdout.trim().replace(/^refs\/remotes\/origin\//, "") : null;
40667
41444
  let refName = null;
40668
41445
  let upstreamRef = null;
40669
41446
  let aheadCount = 0;
@@ -40701,18 +41478,12 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40701
41478
  }
40702
41479
  const isDefaultBranch = refName !== null && (refName === defaultBranch || defaultBranch === null && (refName === "main" || refName === "master"));
40703
41480
  if (refName && !isDefaultBranch) aheadOfDefaultCount = fallbackAheadCount !== null ? fallbackAheadCount : yield* computeAheadCountAgainstBase(cwd, refName).pipe(Effect.orElseSucceed(() => 0));
40704
- const stagedEntries = parseNumstatEntries(stagedNumstatStdout);
40705
- const unstagedEntries = parseNumstatEntries(unstagedNumstatStdout);
41481
+ const numstatEntries = parseNumstatEntries(numstatStdout);
40706
41482
  const fileStatMap = /* @__PURE__ */ new Map();
40707
- for (const entry of [...stagedEntries, ...unstagedEntries]) {
40708
- const existing = fileStatMap.get(entry.path) ?? {
40709
- insertions: 0,
40710
- deletions: 0
40711
- };
40712
- existing.insertions += entry.insertions;
40713
- existing.deletions += entry.deletions;
40714
- fileStatMap.set(entry.path, existing);
40715
- }
41483
+ for (const entry of numstatEntries) fileStatMap.set(entry.path, {
41484
+ insertions: entry.insertions,
41485
+ deletions: entry.deletions
41486
+ });
40716
41487
  let insertions = 0;
40717
41488
  let deletions = 0;
40718
41489
  const files = Array.from(fileStatMap.entries()).map(([filePath, stat]) => {
@@ -41525,7 +42296,7 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
41525
42296
  }
41526
42297
  return branchNames;
41527
42298
  }));
41528
- const withListRefsInvalidation = (cwd, effect) => effect.pipe(Effect.ensuring(invalidateListRefsSnapshot(cwd).pipe(Effect.ignore)));
42299
+ const withListRefsInvalidation = (cwd, effect) => effect.pipe(Effect.ensuring(Effect.all([invalidateListRefsSnapshot(cwd).pipe(Effect.ignore), invalidateStatusStaticCaches(cwd).pipe(Effect.ignore)])));
41529
42300
  const initRepoWithListRefsInvalidation = (input) => initRepo(input).pipe(Effect.ensuring(Effect.gen(function* () {
41530
42301
  const cacheKey = normalizeRepositoryPathsCacheKey(input.cwd);
41531
42302
  yield* Cache.invalidate(repositoryPathsRefreshCache, cacheKey);
@@ -41576,7 +42347,7 @@ const classifyNonZeroExit = (command, stderr) => {
41576
42347
  if (command === "gh" && (normalized.includes("could not resolve to a pullrequest") || normalized.includes("repository.pullrequest") || normalized.includes("no pull requests found for branch") || normalized.includes("pull request not found")) || command === "glab" && (normalized.includes("merge request not found") || normalized.includes("not found") || normalized.includes("404")) || command === "az" && normalized.includes("pull request") && (normalized.includes("not found") || normalized.includes("does not exist"))) return "not-found";
41577
42348
  return "command-failed";
41578
42349
  };
41579
- const make$60 = Effect.gen(function* () {
42350
+ const make$61 = Effect.gen(function* () {
41580
42351
  const processRunner = yield* ProcessRunner;
41581
42352
  const run = Effect.fn("VcsProcess.run")(function* (input) {
41582
42353
  const baseError = {
@@ -41635,7 +42406,7 @@ const make$60 = Effect.gen(function* () {
41635
42406
  });
41636
42407
  return VcsProcess.of({ run });
41637
42408
  });
41638
- const layer$49 = Layer.effect(VcsProcess, make$60).pipe(Layer.provide(layer$63));
42409
+ const layer$49 = Layer.effect(VcsProcess, make$61).pipe(Layer.provide(layer$63));
41639
42410
  //#endregion
41640
42411
  //#region src/vcs/VcsDriver.ts
41641
42412
  var VcsDriver = class extends Context.Service()("@p4code/cli/vcs/VcsDriver") {};
@@ -42086,12 +42857,12 @@ const makeVcsDriver = Effect.gen(function* () {
42086
42857
  const driver = yield* makeVcsDriverShape();
42087
42858
  return VcsDriver.of(driver);
42088
42859
  });
42089
- const make$59 = Effect.gen(function* () {
42860
+ const make$60 = Effect.gen(function* () {
42090
42861
  const git = yield* makeGitVcsDriverCore();
42091
42862
  return GitVcsDriver.of(git);
42092
42863
  });
42093
42864
  Layer.effect(VcsDriver, makeVcsDriver);
42094
- const layer$48 = Layer.effect(GitVcsDriver, make$59);
42865
+ const layer$48 = Layer.effect(GitVcsDriver, make$60);
42095
42866
  //#endregion
42096
42867
  //#region src/vcs/VcsProjectConfig.ts
42097
42868
  const ProjectVcsConfigJson = fromLenientJson(Schema$1.Struct({
@@ -42123,7 +42894,7 @@ const logVcsProjectConfigError = (error) => Effect.logWarning(error).pipe(Effect
42123
42894
  configPath: error.configPath,
42124
42895
  errorTag: error._tag
42125
42896
  }));
42126
- const make$58 = Effect.gen(function* () {
42897
+ const make$59 = Effect.gen(function* () {
42127
42898
  const fileSystem = yield* FileSystem.FileSystem;
42128
42899
  const path = yield* Path.Path;
42129
42900
  const findConfigPath = Effect.fn("VcsProjectConfig.findConfigPath")(function* (cwd) {
@@ -42164,7 +42935,7 @@ const make$58 = Effect.gen(function* () {
42164
42935
  });
42165
42936
  return VcsProjectConfig.of({ resolveKind });
42166
42937
  });
42167
- const layer$47 = Layer.effect(VcsProjectConfig, make$58);
42938
+ const layer$47 = Layer.effect(VcsProjectConfig, make$59);
42168
42939
  //#endregion
42169
42940
  //#region src/vcs/VcsDriverRegistry.ts
42170
42941
  const DETECTION_CACHE_CAPACITY = 2048;
@@ -42184,7 +42955,7 @@ function parseDetectionCacheKey(key) {
42184
42955
  cwd: key.slice(separatorIndex + 1)
42185
42956
  };
42186
42957
  }
42187
- const make$57 = Effect.gen(function* () {
42958
+ const make$58 = Effect.gen(function* () {
42188
42959
  const projectConfig = yield* VcsProjectConfig;
42189
42960
  const git = yield* makeVcsDriver;
42190
42961
  const drivers = { git };
@@ -42241,7 +43012,7 @@ const make$57 = Effect.gen(function* () {
42241
43012
  resolve
42242
43013
  });
42243
43014
  });
42244
- const layer$46 = Layer.effect(VcsDriverRegistry, make$57).pipe(Layer.provide(layer$47));
43015
+ const layer$46 = Layer.effect(VcsDriverRegistry, make$58).pipe(Layer.provide(layer$47));
42245
43016
  //#endregion
42246
43017
  //#region src/checkpointing/CheckpointStore.ts
42247
43018
  /**
@@ -42261,7 +43032,7 @@ const layer$46 = Layer.effect(VcsDriverRegistry, make$57).pipe(Layer.provide(lay
42261
43032
  */
42262
43033
  /** Service tag for checkpoint persistence and restore operations. */
42263
43034
  var CheckpointStore = class extends Context.Service()("@p4code/cli/checkpointing/CheckpointStore") {};
42264
- const make$56 = Effect.gen(function* () {
43035
+ const make$57 = Effect.gen(function* () {
42265
43036
  const vcsRegistry = yield* VcsDriverRegistry;
42266
43037
  const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* (operation, cwd) {
42267
43038
  const handle = yield* vcsRegistry.resolve({ cwd });
@@ -42300,7 +43071,7 @@ const make$56 = Effect.gen(function* () {
42300
43071
  deleteCheckpointRefs
42301
43072
  });
42302
43073
  });
42303
- const layer$45 = Layer.effect(CheckpointStore, make$56);
43074
+ const layer$45 = Layer.effect(CheckpointStore, make$57);
42304
43075
  //#endregion
42305
43076
  //#region src/checkpointing/CheckpointDiffQuery.ts
42306
43077
  /**
@@ -42322,7 +43093,7 @@ function buildTurnDiffResult(input, diff) {
42322
43093
  diff
42323
43094
  };
42324
43095
  }
42325
- const make$55 = Effect.gen(function* () {
43096
+ const make$56 = Effect.gen(function* () {
42326
43097
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
42327
43098
  const checkpointStore = yield* CheckpointStore;
42328
43099
  const threadActivities = yield* ProjectionThreadActivityRepository;
@@ -42493,7 +43264,7 @@ const make$55 = Effect.gen(function* () {
42493
43264
  getFullThreadDiff
42494
43265
  });
42495
43266
  });
42496
- const layer$44 = Layer.effect(CheckpointDiffQuery, make$55);
43267
+ const layer$44 = Layer.effect(CheckpointDiffQuery, make$56);
42497
43268
  //#endregion
42498
43269
  //#region src/orchestration/ThreadLiveEventCoalescer.ts
42499
43270
  const COALESCE_WINDOW = Duration.millis(50);
@@ -42728,11 +43499,11 @@ const makeTextGenerationFromRegistry = (registry) => TextGeneration.of({
42728
43499
  detail: "This provider does not report account usage."
42729
43500
  }))))
42730
43501
  });
42731
- const make$54 = Effect.gen(function* () {
43502
+ const make$55 = Effect.gen(function* () {
42732
43503
  const registry = yield* ProviderInstanceRegistry;
42733
43504
  return makeTextGenerationFromRegistry(registry);
42734
43505
  });
42735
- const layer$43 = Layer.effect(TextGeneration, make$54);
43506
+ const layer$43 = Layer.effect(TextGeneration, make$55);
42736
43507
  //#endregion
42737
43508
  //#region src/textGeneration/TextGenerationPresets.ts
42738
43509
  const conventionalCommitsTextGenerationPolicy = {
@@ -43064,7 +43835,7 @@ const serversEqual = (left, right) => {
43064
43835
  }
43065
43836
  return true;
43066
43837
  };
43067
- const make$53 = Effect.gen(function* PortDiscoveryMake() {
43838
+ const make$54 = Effect.gen(function* PortDiscoveryMake() {
43068
43839
  const net = yield* NetService;
43069
43840
  const processRunner = yield* ProcessRunner;
43070
43841
  const hostPlatform = yield* HostProcessPlatform;
@@ -43215,7 +43986,7 @@ const make$53 = Effect.gen(function* PortDiscoveryMake() {
43215
43986
  unregisterTerminal
43216
43987
  });
43217
43988
  }).pipe(Effect.withSpan("PortDiscovery.make"));
43218
- const layer$42 = Layer.effect(PortDiscovery, make$53);
43989
+ const layer$42 = Layer.effect(PortDiscovery, make$54);
43219
43990
  //#endregion
43220
43991
  //#region src/terminal/Manager.ts
43221
43992
  /**
@@ -43893,7 +44664,7 @@ function normalizedRuntimeEnv(env) {
43893
44664
  if (entries.length === 0) return null;
43894
44665
  return Object.fromEntries(entries.toSorted(([left], [right]) => left.localeCompare(right)));
43895
44666
  }
43896
- const make$52 = Effect.fn("TerminalManager.make")(function* () {
44667
+ const make$53 = Effect.fn("TerminalManager.make")(function* () {
43897
44668
  const { terminalLogsDir } = yield* ServerConfig$1;
43898
44669
  const ptyAdapter = yield* PtyAdapter;
43899
44670
  const portDiscovery = yield* PortDiscovery;
@@ -44855,7 +45626,7 @@ const makeWithOptions$1 = Effect.fn("TerminalManager.makeWithOptions")(function*
44855
45626
  subscribeMetadata
44856
45627
  });
44857
45628
  });
44858
- const layer$41 = Layer.effect(TerminalManager, make$52()).pipe(Layer.provide(layer$63));
45629
+ const layer$41 = Layer.effect(TerminalManager, make$53()).pipe(Layer.provide(layer$63));
44859
45630
  //#endregion
44860
45631
  //#region src/project/ProjectSetupScriptRunner.ts
44861
45632
  var ProjectSetupScriptOperationError = class extends Schema$1.TaggedErrorClass()("ProjectSetupScriptOperationError", {
@@ -44886,7 +45657,7 @@ var ProjectSetupScriptProjectNotFoundError = class extends Schema$1.TaggedErrorC
44886
45657
  };
44887
45658
  Schema$1.Union([ProjectSetupScriptOperationError, ProjectSetupScriptProjectNotFoundError]);
44888
45659
  var ProjectSetupScriptRunner = class extends Context.Service()("@p4code/cli/project/ProjectSetupScriptRunner") {};
44889
- const make$51 = Effect.gen(function* () {
45660
+ const make$52 = Effect.gen(function* () {
44890
45661
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
44891
45662
  const terminalManager = yield* TerminalManager;
44892
45663
  const runForThread = Effect.fn("ProjectSetupScriptRunner.runForThread")(function* (input) {
@@ -44944,7 +45715,7 @@ const make$51 = Effect.gen(function* () {
44944
45715
  });
44945
45716
  return ProjectSetupScriptRunner.of({ runForThread });
44946
45717
  });
44947
- const layer$40 = Layer.effect(ProjectSetupScriptRunner, make$51);
45718
+ const layer$40 = Layer.effect(ProjectSetupScriptRunner, make$52);
44948
45719
  //#endregion
44949
45720
  //#region src/provider/Services/ProviderRegistry.ts
44950
45721
  var ProviderRegistry = class extends Context.Service()("@p4code/cli/provider/Services/ProviderRegistry") {};
@@ -45041,19 +45812,23 @@ function normalizeAzureDevOpsPullRequestRecord(raw) {
45041
45812
  baseRefName: normalizeRefName$1(raw.targetRefName),
45042
45813
  headRefName: normalizeRefName$1(raw.sourceRefName),
45043
45814
  state: normalizeAzureDevOpsPullRequestState(raw.status),
45815
+ ...Option.isSome(raw.closedDate ?? Option.none()) ? { terminalAt: DateTime.formatIso(Option.getOrThrow(raw.closedDate ?? Option.none())) } : {},
45044
45816
  updatedAt: (raw.closedDate ?? Option.none()).pipe(Option.orElse(() => raw.creationDate ?? Option.none()))
45045
45817
  };
45046
45818
  }
45047
45819
  const decodeAzureDevOpsPullRequestList = decodeJsonResult(Schema$1.Array(Schema$1.Unknown));
45048
45820
  const decodeAzureDevOpsPullRequest = decodeJsonResult(AzureDevOpsPullRequestSchema);
45049
45821
  const decodeAzureDevOpsPullRequestEntry = Schema$1.decodeUnknownExit(AzureDevOpsPullRequestSchema);
45050
- function decodeAzureDevOpsPullRequestListJson(raw) {
45822
+ function decodeAzureDevOpsPullRequestListJson(raw, strict = false) {
45051
45823
  const result = decodeAzureDevOpsPullRequestList(raw);
45052
45824
  if (Result.isSuccess(result)) {
45053
45825
  const pullRequests = [];
45054
45826
  for (const entry of result.success) {
45055
45827
  const decodedEntry = decodeAzureDevOpsPullRequestEntry(entry);
45056
- if (Exit.isFailure(decodedEntry)) continue;
45828
+ if (Exit.isFailure(decodedEntry)) {
45829
+ if (strict) return Result.fail(decodedEntry.cause);
45830
+ continue;
45831
+ }
45057
45832
  pullRequests.push(normalizeAzureDevOpsPullRequestRecord(decodedEntry.value));
45058
45833
  }
45059
45834
  return Result.succeed(pullRequests);
@@ -45271,7 +46046,7 @@ function decodeAzureDevOpsJson(raw, schema, operation, cwd) {
45271
46046
  cause
45272
46047
  })));
45273
46048
  }
45274
- const make$50 = Effect.gen(function* () {
46049
+ const make$51 = Effect.gen(function* () {
45275
46050
  const process = yield* VcsProcess;
45276
46051
  const execute = (input) => process.run({
45277
46052
  operation: "AzureDevOpsCli.execute",
@@ -45311,7 +46086,7 @@ const make$50 = Effect.gen(function* () {
45311
46086
  "--top",
45312
46087
  String(input.limit ?? 20)
45313
46088
  ]
45314
- }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => raw.length === 0 ? Effect.succeed([]) : Effect.sync(() => decodeAzureDevOpsPullRequestListJson(raw)).pipe(Effect.flatMap((decoded) => {
46089
+ }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => raw.length === 0 && !input.strict ? Effect.succeed([]) : Effect.sync(() => decodeAzureDevOpsPullRequestListJson(raw, input.strict)).pipe(Effect.flatMap((decoded) => {
45315
46090
  if (!Result.isSuccess(decoded)) return Effect.fail(new AzureDevOpsPullRequestListDecodeError({
45316
46091
  operation: "listPullRequests",
45317
46092
  command: "az",
@@ -45413,7 +46188,7 @@ const make$50 = Effect.gen(function* () {
45413
46188
  }).pipe(Effect.asVoid)
45414
46189
  });
45415
46190
  });
45416
- const layer$39 = Layer.effect(AzureDevOpsCli, make$50);
46191
+ const layer$39 = Layer.effect(AzureDevOpsCli, make$51);
45417
46192
  //#endregion
45418
46193
  //#region src/sourceControl/SourceControlProviderDiscovery.ts
45419
46194
  function firstNonEmptyLine(text) {
@@ -45613,10 +46388,11 @@ function toChangeRequest$5(summary) {
45613
46388
  headRefName: summary.headRefName,
45614
46389
  state: summary.state,
45615
46390
  updatedAt: summary.updatedAt,
46391
+ ...summary.terminalAt == null ? {} : { terminalAt: summary.terminalAt },
45616
46392
  isCrossRepository: false
45617
46393
  };
45618
46394
  }
45619
- const make$49 = Effect.gen(function* () {
46395
+ const make$50 = Effect.gen(function* () {
45620
46396
  const azure = yield* AzureDevOpsCli;
45621
46397
  return SourceControlProvider.of({
45622
46398
  kind: "azure-devops",
@@ -45627,6 +46403,7 @@ const make$49 = Effect.gen(function* () {
45627
46403
  headSelector: input.headSelector,
45628
46404
  ...source !== void 0 ? { source } : {},
45629
46405
  state: input.state,
46406
+ ...input.strict !== void 0 ? { strict: input.strict } : {},
45630
46407
  ...input.limit !== void 0 ? { limit: input.limit } : {}
45631
46408
  }).pipe(Effect.map((items) => items.map(toChangeRequest$5)), Effect.mapError((error) => new SourceControlProviderError({
45632
46409
  provider: "azure-devops",
@@ -45708,7 +46485,7 @@ const make$49 = Effect.gen(function* () {
45708
46485
  })))
45709
46486
  });
45710
46487
  });
45711
- Layer.effect(SourceControlProvider, make$49);
46488
+ Layer.effect(SourceControlProvider, make$50);
45712
46489
  //#endregion
45713
46490
  //#region src/sourceControl/bitbucketPullRequests.ts
45714
46491
  const BitbucketRepositoryRefSchema = Schema$1.Struct({
@@ -46085,7 +46862,7 @@ function responseError(operation, response) {
46085
46862
  responseBodyLength: collected.text.length
46086
46863
  }))));
46087
46864
  }
46088
- const make$48 = Effect.gen(function* () {
46865
+ const make$49 = Effect.gen(function* () {
46089
46866
  const config = yield* BitbucketApiEnvConfig;
46090
46867
  const httpClient = yield* HttpClient.HttpClient;
46091
46868
  const fileSystem = yield* FileSystem.FileSystem;
@@ -46301,7 +47078,7 @@ const make$48 = Effect.gen(function* () {
46301
47078
  })))
46302
47079
  });
46303
47080
  });
46304
- const layer$37 = Layer.effect(BitbucketApi, make$48);
47081
+ const layer$37 = Layer.effect(BitbucketApi, make$49);
46305
47082
  //#endregion
46306
47083
  //#region src/sourceControl/BitbucketSourceControlProvider.ts
46307
47084
  function toChangeRequest$4(summary) {
@@ -46319,7 +47096,7 @@ function toChangeRequest$4(summary) {
46319
47096
  ...summary.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin } : {}
46320
47097
  };
46321
47098
  }
46322
- const make$47 = Effect.gen(function* () {
47099
+ const make$48 = Effect.gen(function* () {
46323
47100
  const bitbucket = yield* BitbucketApi;
46324
47101
  return SourceControlProvider.of({
46325
47102
  kind: "bitbucket",
@@ -46410,7 +47187,7 @@ const make$47 = Effect.gen(function* () {
46410
47187
  })))
46411
47188
  });
46412
47189
  });
46413
- Layer.effect(SourceControlProvider, make$47);
47190
+ Layer.effect(SourceControlProvider, make$48);
46414
47191
  const makeDiscovery = Effect.gen(function* () {
46415
47192
  return {
46416
47193
  type: "api",
@@ -46430,6 +47207,7 @@ const GitHubPullRequestSchema = Schema$1.Struct({
46430
47207
  headRefName: TrimmedNonEmptyString,
46431
47208
  state: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
46432
47209
  mergedAt: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
47210
+ closedAt: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
46433
47211
  updatedAt: Schema$1.optional(Schema$1.OptionFromNullOr(Schema$1.DateTimeUtcFromString)),
46434
47212
  isCrossRepository: Schema$1.optional(Schema$1.Boolean),
46435
47213
  headRepository: Schema$1.optional(Schema$1.NullOr(Schema$1.Struct({
@@ -46453,6 +47231,7 @@ function normalizeGitHubPullRequestRecord(raw) {
46453
47231
  const headRepositoryName = trimOptionalString$1(raw.headRepository?.name);
46454
47232
  const headRepositoryOwnerLogin = trimOptionalString$1(raw.headRepositoryOwner?.login) ?? (explicitNameWithOwner?.includes("/") ? explicitNameWithOwner.split("/")[0] ?? null : null);
46455
47233
  const headRepositoryNameWithOwner = explicitNameWithOwner ?? (headRepositoryOwnerLogin && headRepositoryName ? `${headRepositoryOwnerLogin}/${headRepositoryName}` : null);
47234
+ const terminalAt = normalizeGitHubPullRequestState(raw) === "merged" ? raw.mergedAt ?? null : normalizeGitHubPullRequestState(raw) === "closed" ? raw.closedAt ?? null : null;
46456
47235
  return {
46457
47236
  number: raw.number,
46458
47237
  title: raw.title,
@@ -46461,6 +47240,7 @@ function normalizeGitHubPullRequestRecord(raw) {
46461
47240
  headRefName: raw.headRefName,
46462
47241
  state: normalizeGitHubPullRequestState(raw),
46463
47242
  updatedAt: raw.updatedAt ?? Option.none(),
47243
+ ...terminalAt == null ? {} : { terminalAt },
46464
47244
  ...typeof raw.isCrossRepository === "boolean" ? { isCrossRepository: raw.isCrossRepository } : {},
46465
47245
  ...headRepositoryNameWithOwner ? { headRepositoryNameWithOwner } : {},
46466
47246
  ...headRepositoryOwnerLogin ? { headRepositoryOwnerLogin } : {}
@@ -46469,13 +47249,16 @@ function normalizeGitHubPullRequestRecord(raw) {
46469
47249
  const decodeGitHubPullRequestList = decodeJsonResult(Schema$1.Array(Schema$1.Unknown));
46470
47250
  const decodeGitHubPullRequest = decodeJsonResult(GitHubPullRequestSchema);
46471
47251
  const decodeGitHubPullRequestEntry = Schema$1.decodeUnknownExit(GitHubPullRequestSchema);
46472
- function decodeGitHubPullRequestListJson(raw) {
47252
+ function decodeGitHubPullRequestListJson(raw, strict = false) {
46473
47253
  const result = decodeGitHubPullRequestList(raw);
46474
47254
  if (Result.isSuccess(result)) {
46475
47255
  const pullRequests = [];
46476
47256
  for (const entry of result.success) {
46477
47257
  const decodedEntry = decodeGitHubPullRequestEntry(entry);
46478
- if (Exit.isFailure(decodedEntry)) continue;
47258
+ if (Exit.isFailure(decodedEntry)) {
47259
+ if (strict) return Result.fail(decodedEntry.cause);
47260
+ continue;
47261
+ }
46479
47262
  pullRequests.push(normalizeGitHubPullRequestRecord(decodedEntry.value));
46480
47263
  }
46481
47264
  return Result.succeed(pullRequests);
@@ -46652,7 +47435,7 @@ function deriveRepositoryCloneUrlsFromCreateOutput(stdout, repository) {
46652
47435
  sshUrl: `git@${fallbackHost}:${repository}.git`
46653
47436
  };
46654
47437
  }
46655
- const make$46 = Effect.gen(function* () {
47438
+ const make$47 = Effect.gen(function* () {
46656
47439
  const process = yield* VcsProcess;
46657
47440
  const execute = (input) => process.run({
46658
47441
  operation: "GitHubCli.execute",
@@ -46680,7 +47463,7 @@ const make$46 = Effect.gen(function* () {
46680
47463
  "--limit",
46681
47464
  String(input.limit ?? 1),
46682
47465
  "--json",
46683
- "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner"
47466
+ "number,title,url,baseRefName,headRefName,state,mergedAt,closedAt,isCrossRepository,headRepository,headRepositoryOwner"
46684
47467
  ]
46685
47468
  }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => raw.length === 0 ? Effect.succeed([]) : Effect.sync(() => decodeGitHubPullRequestListJson(raw)).pipe(Effect.flatMap((decoded) => {
46686
47469
  if (!Result.isSuccess(decoded)) return Effect.fail(new GitHubPullRequestListDecodeError({
@@ -46697,7 +47480,7 @@ const make$46 = Effect.gen(function* () {
46697
47480
  "view",
46698
47481
  input.reference,
46699
47482
  "--json",
46700
- "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner"
47483
+ "number,title,url,baseRefName,headRefName,state,mergedAt,closedAt,isCrossRepository,headRepository,headRepositoryOwner"
46701
47484
  ]
46702
47485
  }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => Effect.sync(() => decodeGitHubPullRequestJson(raw)).pipe(Effect.flatMap((decoded) => {
46703
47486
  if (!Result.isSuccess(decoded)) return Effect.fail(new GitHubPullRequestDecodeError({
@@ -46770,7 +47553,7 @@ const make$46 = Effect.gen(function* () {
46770
47553
  }).pipe(Effect.asVoid)
46771
47554
  });
46772
47555
  });
46773
- const layer$35 = Layer.effect(GitHubCli, make$46);
47556
+ const layer$35 = Layer.effect(GitHubCli, make$47);
46774
47557
  //#endregion
46775
47558
  //#region src/sourceControl/gitHubAuthStatus.ts
46776
47559
  const GitHubAuthStatusAccountSchema = Schema$1.Struct({
@@ -46824,6 +47607,7 @@ function toChangeRequest$3(summary) {
46824
47607
  headRefName: summary.headRefName,
46825
47608
  state: summary.state ?? "open",
46826
47609
  updatedAt: Option.none(),
47610
+ ...summary.terminalAt == null ? {} : { terminalAt: summary.terminalAt },
46827
47611
  ...summary.isCrossRepository !== void 0 ? { isCrossRepository: summary.isCrossRepository } : {},
46828
47612
  ...summary.headRepositoryNameWithOwner !== void 0 ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner } : {},
46829
47613
  ...summary.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin } : {}
@@ -46871,7 +47655,7 @@ const discovery$1 = {
46871
47655
  parseAuth: parseGitHubAuth,
46872
47656
  installHint: "Install the GitHub command-line tool (`gh`) via https://cli.github.com/ or your package manager (for example `brew install gh`)."
46873
47657
  };
46874
- const make$45 = Effect.gen(function* () {
47658
+ const make$46 = Effect.gen(function* () {
46875
47659
  const github = yield* GitHubCli;
46876
47660
  const listChangeRequests = (input) => {
46877
47661
  if (input.state === "open") return github.listOpenPullRequests({
@@ -46900,12 +47684,12 @@ const make$45 = Effect.gen(function* () {
46900
47684
  "--limit",
46901
47685
  String(input.limit ?? 20),
46902
47686
  "--json",
46903
- "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner"
47687
+ "number,title,url,baseRefName,headRefName,state,mergedAt,closedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner"
46904
47688
  ]
46905
47689
  }).pipe(Effect.flatMap((result) => {
46906
47690
  const raw = result.stdout.trim();
46907
- if (raw.length === 0) return Effect.succeed([]);
46908
- return Effect.sync(() => decodeGitHubPullRequestListJson(raw)).pipe(Effect.flatMap((decoded) => Result.isSuccess(decoded) ? Effect.succeed(decoded.success.map((item) => ({
47691
+ if (raw.length === 0 && !input.strict) return Effect.succeed([]);
47692
+ return Effect.sync(() => decodeGitHubPullRequestListJson(raw, input.strict)).pipe(Effect.flatMap((decoded) => Result.isSuccess(decoded) ? Effect.succeed(decoded.success.map((item) => ({
46909
47693
  ...toChangeRequest$3(item),
46910
47694
  updatedAt: item.updatedAt
46911
47695
  }))) : Effect.fail(new GitHubChangeRequestListDecodeError({
@@ -46987,7 +47771,7 @@ const make$45 = Effect.gen(function* () {
46987
47771
  })))
46988
47772
  });
46989
47773
  });
46990
- Layer.effect(SourceControlProvider, make$45);
47774
+ Layer.effect(SourceControlProvider, make$46);
46991
47775
  //#endregion
46992
47776
  //#region src/sourceControl/gitLabMergeRequests.ts
46993
47777
  const GitLabProjectReferenceSchema = Schema$1.Struct({
@@ -47006,6 +47790,8 @@ const GitLabMergeRequestSchema = Schema$1.Struct({
47006
47790
  source_branch: TrimmedNonEmptyString,
47007
47791
  target_branch: TrimmedNonEmptyString,
47008
47792
  state: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
47793
+ merged_at: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
47794
+ closed_at: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
47009
47795
  updated_at: Schema$1.optional(Schema$1.OptionFromNullOr(Schema$1.DateTimeUtcFromString)),
47010
47796
  source_project_id: Schema$1.optional(Schema$1.NullOr(Schema$1.Number)),
47011
47797
  target_project_id: Schema$1.optional(Schema$1.NullOr(Schema$1.Number)),
@@ -47036,6 +47822,7 @@ function normalizeGitLabMergeRequestRecord(raw) {
47036
47822
  const targetProjectPath = projectPathWithNamespace(raw.target_project);
47037
47823
  const isCrossRepository = typeof raw.source_project_id === "number" && typeof raw.target_project_id === "number" ? raw.source_project_id !== raw.target_project_id : sourceProjectPath !== null && targetProjectPath !== null ? sourceProjectPath.toLowerCase() !== targetProjectPath.toLowerCase() : void 0;
47038
47824
  const headRepositoryOwnerLogin = ownerLoginFromPathWithNamespace(sourceProjectPath);
47825
+ const terminalAt = normalizeGitLabMergeRequestState(raw.state) === "merged" ? raw.merged_at ?? null : normalizeGitLabMergeRequestState(raw.state) === "closed" ? raw.closed_at ?? null : null;
47039
47826
  return {
47040
47827
  number: raw.iid,
47041
47828
  title: raw.title,
@@ -47044,6 +47831,7 @@ function normalizeGitLabMergeRequestRecord(raw) {
47044
47831
  headRefName: raw.source_branch,
47045
47832
  state: normalizeGitLabMergeRequestState(raw.state),
47046
47833
  updatedAt: raw.updated_at ?? Option.none(),
47834
+ ...terminalAt == null ? {} : { terminalAt },
47047
47835
  ...typeof isCrossRepository === "boolean" ? { isCrossRepository } : {},
47048
47836
  ...sourceProjectPath ? { headRepositoryNameWithOwner: sourceProjectPath } : {},
47049
47837
  ...headRepositoryOwnerLogin ? { headRepositoryOwnerLogin } : {}
@@ -47052,13 +47840,16 @@ function normalizeGitLabMergeRequestRecord(raw) {
47052
47840
  const decodeGitLabMergeRequestList = decodeJsonResult(Schema$1.Array(Schema$1.Unknown));
47053
47841
  const decodeGitLabMergeRequest = decodeJsonResult(GitLabMergeRequestSchema);
47054
47842
  const decodeGitLabMergeRequestEntry = Schema$1.decodeUnknownExit(GitLabMergeRequestSchema);
47055
- function decodeGitLabMergeRequestListJson(raw) {
47843
+ function decodeGitLabMergeRequestListJson(raw, strict = false) {
47056
47844
  const result = decodeGitLabMergeRequestList(raw);
47057
47845
  if (Result.isSuccess(result)) {
47058
47846
  const mergeRequests = [];
47059
47847
  for (const entry of result.success) {
47060
47848
  const decodedEntry = decodeGitLabMergeRequestEntry(entry);
47061
- if (Exit.isFailure(decodedEntry)) continue;
47849
+ if (Exit.isFailure(decodedEntry)) {
47850
+ if (strict) return Result.fail(decodedEntry.cause);
47851
+ continue;
47852
+ }
47062
47853
  mergeRequests.push(normalizeGitLabMergeRequestRecord(decodedEntry.value));
47063
47854
  }
47064
47855
  return Result.succeed(mergeRequests);
@@ -47299,7 +48090,7 @@ function parseRepositoryPath(repository) {
47299
48090
  projectPath
47300
48091
  };
47301
48092
  }
47302
- const make$44 = Effect.gen(function* () {
48093
+ const make$45 = Effect.gen(function* () {
47303
48094
  const process = yield* VcsProcess;
47304
48095
  const run = (input, mapError) => process.run({
47305
48096
  operation: "GitLabCli.execute",
@@ -47336,7 +48127,7 @@ const make$44 = Effect.gen(function* () {
47336
48127
  "--output",
47337
48128
  "json"
47338
48129
  ]
47339
- }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => raw.length === 0 ? Effect.succeed([]) : Effect.sync(() => decodeGitLabMergeRequestListJson(raw)).pipe(Effect.flatMap((decoded) => {
48130
+ }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => raw.length === 0 && !input.strict ? Effect.succeed([]) : Effect.sync(() => decodeGitLabMergeRequestListJson(raw, input.strict)).pipe(Effect.flatMap((decoded) => {
47340
48131
  if (!Result.isSuccess(decoded)) return Effect.fail(new GitLabMergeRequestListDecodeError({
47341
48132
  operation: "listMergeRequests",
47342
48133
  command: "glab",
@@ -47450,7 +48241,7 @@ const make$44 = Effect.gen(function* () {
47450
48241
  }).pipe(Effect.asVoid)
47451
48242
  });
47452
48243
  });
47453
- const layer$33 = Layer.effect(GitLabCli, make$44);
48244
+ const layer$33 = Layer.effect(GitLabCli, make$45);
47454
48245
  //#endregion
47455
48246
  //#region src/sourceControl/gitLabAuthStatus.ts
47456
48247
  const HOST_LINE_PATTERN = /^(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?|\[[a-f0-9:.]+\])(?::\d+)?$/iu;
@@ -47497,6 +48288,7 @@ function toChangeRequest$2(summary) {
47497
48288
  headRefName: summary.headRefName,
47498
48289
  state: summary.state ?? "open",
47499
48290
  updatedAt: summary.updatedAt ?? Option.none(),
48291
+ ...summary.terminalAt == null ? {} : { terminalAt: summary.terminalAt },
47500
48292
  ...summary.isCrossRepository !== void 0 ? { isCrossRepository: summary.isCrossRepository } : {},
47501
48293
  ...summary.headRepositoryNameWithOwner !== void 0 ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner } : {},
47502
48294
  ...summary.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin } : {}
@@ -47547,7 +48339,7 @@ const discovery = {
47547
48339
  refineUnknownRemote: refineUnknownGitLabRemote,
47548
48340
  installHint: "Install the GitLab command-line tool (`glab`) from https://gitlab.com/gitlab-org/cli or your package manager (for example `brew install glab`)."
47549
48341
  };
47550
- const make$43 = Effect.gen(function* () {
48342
+ const make$44 = Effect.gen(function* () {
47551
48343
  const gitlab = yield* GitLabCli;
47552
48344
  return SourceControlProvider.of({
47553
48345
  kind: "gitlab",
@@ -47558,6 +48350,7 @@ const make$43 = Effect.gen(function* () {
47558
48350
  headSelector: input.headSelector,
47559
48351
  ...source ? { source } : {},
47560
48352
  state: input.state,
48353
+ ...input.strict !== void 0 ? { strict: input.strict } : {},
47561
48354
  ...input.limit !== void 0 ? { limit: input.limit } : {}
47562
48355
  }).pipe(Effect.map((items) => items.map(toChangeRequest$2)), Effect.mapError((error) => new SourceControlProviderError({
47563
48356
  provider: "gitlab",
@@ -47635,7 +48428,7 @@ const make$43 = Effect.gen(function* () {
47635
48428
  })))
47636
48429
  });
47637
48430
  });
47638
- Layer.effect(SourceControlProvider, make$43);
48431
+ Layer.effect(SourceControlProvider, make$44);
47639
48432
  //#endregion
47640
48433
  //#region src/sourceControl/SourceControlProviderRegistry.ts
47641
48434
  const PROVIDER_DETECTION_CACHE_CAPACITY = 2048;
@@ -47791,12 +48584,12 @@ const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWithProvid
47791
48584
  })), { concurrency: "unbounded" })
47792
48585
  });
47793
48586
  });
47794
- const make$42 = Effect.gen(function* () {
47795
- const github = yield* make$45;
47796
- const gitlab = yield* make$43;
47797
- const bitbucket = yield* make$47;
48587
+ const make$43 = Effect.gen(function* () {
48588
+ const github = yield* make$46;
48589
+ const gitlab = yield* make$44;
48590
+ const bitbucket = yield* make$48;
47798
48591
  const bitbucketDiscovery = yield* makeDiscovery;
47799
- const azureDevOps = yield* make$49;
48592
+ const azureDevOps = yield* make$50;
47800
48593
  return yield* makeWithProviders([
47801
48594
  {
47802
48595
  kind: "github",
@@ -47820,7 +48613,7 @@ const make$42 = Effect.gen(function* () {
47820
48613
  }
47821
48614
  ]);
47822
48615
  });
47823
- const layer$31 = Layer.effect(SourceControlProviderRegistry, make$42);
48616
+ const layer$31 = Layer.effect(SourceControlProviderRegistry, make$43);
47824
48617
  //#endregion
47825
48618
  //#region src/sourceControl/PrTemplateDetection.ts
47826
48619
  const TEMPLATE_MAX_BYTES = 8e3;
@@ -47942,6 +48735,7 @@ const detectPrTemplate = Effect.fn("detectPrTemplate")(function* (cwd, treeish,
47942
48735
  });
47943
48736
  //#endregion
47944
48737
  //#region src/git/GitManager.ts
48738
+ const COMPLETION_PR_LIST_LIMIT = 100;
47945
48739
  var GitManager = class extends Context.Service()("@p4code/cli/git/GitManager") {};
47946
48740
  const COMMIT_TIMEOUT_MS = 10 * 6e4;
47947
48741
  const MAX_PROGRESS_TEXT_LENGTH = 500;
@@ -48190,7 +48984,7 @@ function toPullRequestHeadRemoteInfo(pr) {
48190
48984
  ...pr.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: pr.headRepositoryOwnerLogin } : {}
48191
48985
  };
48192
48986
  }
48193
- const make$41 = Effect.gen(function* () {
48987
+ const make$42 = Effect.gen(function* () {
48194
48988
  const gitCore = yield* GitVcsDriver;
48195
48989
  const sourceControlProviders = yield* SourceControlProviderRegistry;
48196
48990
  const textGeneration = yield* TextGeneration;
@@ -48568,6 +49362,57 @@ const make$41 = Effect.gen(function* () {
48568
49362
  if (latestOpenPr) return latestOpenPr;
48569
49363
  return parsed[0] ?? null;
48570
49364
  });
49365
+ const threadCompletionPrState = Effect.fn("GitManager.threadCompletionPrState")(function* (input) {
49366
+ const details = yield* gitCore.statusDetails(input.cwd);
49367
+ if (!details.isRepo) return { state: "none" };
49368
+ if (!details.hasOriginRemote) {
49369
+ const remotes = yield* gitCore.execute({
49370
+ operation: "GitManager.threadCompletionPrState.remotes",
49371
+ cwd: input.cwd,
49372
+ args: ["remote"]
49373
+ });
49374
+ if (remotes.stdoutTruncated) return { state: "unknown" };
49375
+ if (remotes.stdout.trim().length === 0) return { state: "none" };
49376
+ }
49377
+ if (details.branch === null || input.branch !== null && details.branch !== input.branch) return { state: "unknown" };
49378
+ const context = yield* resolveBranchHeadContext(input.cwd, {
49379
+ branch: details.branch,
49380
+ upstreamRef: details.upstreamRef
49381
+ });
49382
+ const provider = yield* sourceControlProvider(input.cwd);
49383
+ if (context.headSelectors.length === 0) return { state: "unknown" };
49384
+ const terminalTimes = [];
49385
+ for (const headSelector of context.headSelectors) {
49386
+ const open = yield* provider.listChangeRequests({
49387
+ cwd: input.cwd,
49388
+ headSelector,
49389
+ state: "open",
49390
+ limit: COMPLETION_PR_LIST_LIMIT,
49391
+ strict: true
49392
+ });
49393
+ if (open.some((pr) => matchesBranchHeadContext(toPullRequestInfo(pr), context))) return { state: "open" };
49394
+ if (open.length > 0) return { state: "unknown" };
49395
+ const history = yield* provider.listChangeRequests({
49396
+ cwd: input.cwd,
49397
+ headSelector,
49398
+ state: "all",
49399
+ limit: COMPLETION_PR_LIST_LIMIT,
49400
+ strict: true
49401
+ });
49402
+ if (history.length >= COMPLETION_PR_LIST_LIMIT) return { state: "unknown" };
49403
+ for (const pr of history) {
49404
+ if (!matchesBranchHeadContext(toPullRequestInfo(pr), context)) return { state: "unknown" };
49405
+ if (pr.state === "open") return { state: "open" };
49406
+ if (pr.terminalAt == null || !Number.isFinite(Date.parse(pr.terminalAt))) return { state: "unknown" };
49407
+ terminalTimes.push(DateTime.formatIso(DateTime.makeUnsafe(pr.terminalAt)));
49408
+ }
49409
+ }
49410
+ const terminalAt = terminalTimes.toSorted().at(-1);
49411
+ return terminalAt === void 0 ? { state: "none" } : {
49412
+ state: "terminal",
49413
+ terminalAt
49414
+ };
49415
+ });
48571
49416
  const buildCompletionToast = Effect.fn("buildCompletionToast")(function* (cwd, result) {
48572
49417
  const terms = yield* sourceControlProvider(cwd).pipe(Effect.map((provider) => getChangeRequestTerminologyForKind(provider.kind)), Effect.orElseSucceed(() => getChangeRequestTerminologyForKind("unknown")));
48573
49418
  const summary = summarizeGitActionResult(result, terms);
@@ -49101,6 +49946,7 @@ const make$41 = Effect.gen(function* () {
49101
49946
  }))));
49102
49947
  });
49103
49948
  return GitManager.of({
49949
+ threadCompletionPrState,
49104
49950
  localStatus,
49105
49951
  remoteStatus,
49106
49952
  status,
@@ -49112,7 +49958,7 @@ const make$41 = Effect.gen(function* () {
49112
49958
  runStackedAction
49113
49959
  });
49114
49960
  });
49115
- const layer$30 = Layer.effect(GitManager, make$41);
49961
+ const layer$30 = Layer.effect(GitManager, make$42);
49116
49962
  //#endregion
49117
49963
  //#region src/git/GitWorkflowService.ts
49118
49964
  var GitWorkflowService = class extends Context.Service()("@p4code/cli/git/GitWorkflowService") {};
@@ -49149,7 +49995,7 @@ function nonRepositoryListRefs() {
49149
49995
  totalCount: 0
49150
49996
  };
49151
49997
  }
49152
- const make$40 = Effect.gen(function* () {
49998
+ const make$41 = Effect.gen(function* () {
49153
49999
  const registry = yield* VcsDriverRegistry;
49154
50000
  const git = yield* GitVcsDriver;
49155
50001
  const gitManager = yield* GitManager;
@@ -49235,7 +50081,7 @@ const make$40 = Effect.gen(function* () {
49235
50081
  renameBranch: (input) => ensureGit("GitWorkflowService.renameBranch", input.cwd).pipe(Effect.andThen(git.renameBranch(input)))
49236
50082
  });
49237
50083
  });
49238
- const layer$29 = Layer.effect(GitWorkflowService, make$40);
50084
+ const layer$29 = Layer.effect(GitWorkflowService, make$41);
49239
50085
  //#endregion
49240
50086
  //#region src/pullRequest/PullRequestProvider.ts
49241
50087
  /**
@@ -49596,7 +50442,7 @@ function isReviewerName(value) {
49596
50442
  const name = value.trim();
49597
50443
  return name.length > 0 && !name.startsWith("-");
49598
50444
  }
49599
- const make$39 = Effect.gen(function* () {
50445
+ const make$40 = Effect.gen(function* () {
49600
50446
  const azure = yield* AzureDevOpsCli;
49601
50447
  const detectArgs = ["--detect", "true"];
49602
50448
  const executeJson = (input) => azure.execute({
@@ -49796,7 +50642,7 @@ const make$39 = Effect.gen(function* () {
49796
50642
  }).pipe(Effect.asVoid)
49797
50643
  });
49798
50644
  });
49799
- const layer$28 = Layer.effect(AzureDevOpsPullRequestCli, make$39);
50645
+ const layer$28 = Layer.effect(AzureDevOpsPullRequestCli, make$40);
49800
50646
  //#endregion
49801
50647
  //#region src/pullRequest/AzureDevOpsPullRequestProvider.ts
49802
50648
  const CAPABILITIES$3 = {
@@ -49871,7 +50717,7 @@ function toChangeRequest$1(pullRequest) {
49871
50717
  labels: []
49872
50718
  };
49873
50719
  }
49874
- const make$38 = Effect.gen(function* () {
50720
+ const make$39 = Effect.gen(function* () {
49875
50721
  const cli = yield* AzureDevOpsPullRequestCli;
49876
50722
  const fail = (operation) => (error) => new PullRequestProviderError({
49877
50723
  provider: "azure-devops",
@@ -50611,7 +51457,7 @@ function mergeStrategy(method) {
50611
51457
  default: return "merge_commit";
50612
51458
  }
50613
51459
  }
50614
- const make$37 = Effect.gen(function* () {
51460
+ const make$38 = Effect.gen(function* () {
50615
51461
  const bitbucket = yield* BitbucketApi;
50616
51462
  /**
50617
51463
  * The repository's own path, and the workspace above it — which the people who may review are
@@ -50891,7 +51737,7 @@ const make$37 = Effect.gen(function* () {
50891
51737
  }).pipe(Effect.asVoid))
50892
51738
  });
50893
51739
  });
50894
- const layer$27 = Layer.effect(BitbucketPullRequestApi, make$37);
51740
+ const layer$27 = Layer.effect(BitbucketPullRequestApi, make$38);
50895
51741
  //#endregion
50896
51742
  //#region src/pullRequest/BitbucketPullRequestProvider.ts
50897
51743
  const CAPABILITIES$2 = {
@@ -50971,7 +51817,7 @@ function toChangeRequest(pullRequest) {
50971
51817
  labels: []
50972
51818
  };
50973
51819
  }
50974
- const make$36 = Effect.gen(function* () {
51820
+ const make$37 = Effect.gen(function* () {
50975
51821
  const api = yield* BitbucketPullRequestApi;
50976
51822
  const fail = (operation) => (error) => new PullRequestProviderError({
50977
51823
  provider: "bitbucket",
@@ -52858,7 +53704,7 @@ function actionArgs$1(action, mergeMethod, updateMethod) {
52858
53704
  case "reopen": return ["reopen"];
52859
53705
  }
52860
53706
  }
52861
- const make$35 = Effect.gen(function* () {
53707
+ const make$36 = Effect.gen(function* () {
52862
53708
  const github = yield* GitHubCli;
52863
53709
  /**
52864
53710
  * The pull request's own node id, which is what a mutation against the pull request itself is
@@ -53578,7 +54424,7 @@ const make$35 = Effect.gen(function* () {
53578
54424
  })))
53579
54425
  });
53580
54426
  });
53581
- const layer$26 = Layer.effect(GitHubPullRequestCli, make$35);
54427
+ const layer$26 = Layer.effect(GitHubPullRequestCli, make$36);
53582
54428
  //#endregion
53583
54429
  //#region src/pullRequest/GitHubPullRequestProvider.ts
53584
54430
  const CAPABILITIES$1 = {
@@ -53693,7 +54539,7 @@ function loginAvatarUrl(login, host) {
53693
54539
  }
53694
54540
  /** True where markdown would render nothing: whitespace, or only HTML comments. */
53695
54541
  const rendersEmpty = (body) => body.replace(/<!--[\s\S]*?-->/g, "").trim().length === 0;
53696
- const make$34 = Effect.gen(function* () {
54542
+ const make$35 = Effect.gen(function* () {
53697
54543
  const cli = yield* GitHubPullRequestCli;
53698
54544
  const fail = (operation) => (error) => new PullRequestProviderError({
53699
54545
  provider: "github",
@@ -54712,7 +55558,7 @@ function actionArgs(action, mergeMethod) {
54712
55558
  case "reopen": return ["reopen"];
54713
55559
  }
54714
55560
  }
54715
- const make$33 = Effect.gen(function* () {
55561
+ const make$34 = Effect.gen(function* () {
54716
55562
  const gitlab = yield* GitLabCli;
54717
55563
  const api = (input) => gitlab.execute({
54718
55564
  cwd: input.cwd,
@@ -55283,7 +56129,7 @@ const make$33 = Effect.gen(function* () {
55283
56129
  }).pipe(Effect.asVoid)
55284
56130
  });
55285
56131
  });
55286
- const layer$25 = Layer.effect(GitLabPullRequestCli, make$33);
56132
+ const layer$25 = Layer.effect(GitLabPullRequestCli, make$34);
55287
56133
  //#endregion
55288
56134
  //#region src/pullRequest/GitLabPullRequestProvider.ts
55289
56135
  const CAPABILITIES = {
@@ -55363,7 +56209,7 @@ function reasonFor(error) {
55363
56209
  if (error._tag === "GitLabCliAuthenticationError") return "unauthenticated";
55364
56210
  return "failed";
55365
56211
  }
55366
- const make$32 = Effect.gen(function* () {
56212
+ const make$33 = Effect.gen(function* () {
55367
56213
  const cli = yield* GitLabPullRequestCli;
55368
56214
  const fail = (operation) => (error) => new PullRequestProviderError({
55369
56215
  provider: "gitlab",
@@ -55506,13 +56352,13 @@ function fromProviders(providers) {
55506
56352
  * The hosts this build can read change requests from. A host with no entry here still shows up
55507
56353
  * in the provider list as unimplemented, so its projects are explained rather than missing.
55508
56354
  */
55509
- const make$31 = Effect.map(Effect.all([
55510
- make$34,
55511
- make$32,
55512
- make$36,
55513
- make$38
56355
+ const make$32 = Effect.map(Effect.all([
56356
+ make$35,
56357
+ make$33,
56358
+ make$37,
56359
+ make$39
55514
56360
  ]), fromProviders);
55515
- const layer$24 = Layer.effect(PullRequestProviderRegistry, make$31).pipe(Layer.provide(layer$26.pipe(Layer.provide(layer$35))), Layer.provide(layer$25.pipe(Layer.provide(layer$33))), Layer.provide(layer$27.pipe(Layer.provide(layer$37))), Layer.provide(layer$28.pipe(Layer.provide(layer$39))));
56361
+ const layer$24 = Layer.effect(PullRequestProviderRegistry, make$32).pipe(Layer.provide(layer$26.pipe(Layer.provide(layer$35))), Layer.provide(layer$25.pipe(Layer.provide(layer$33))), Layer.provide(layer$27.pipe(Layer.provide(layer$37))), Layer.provide(layer$28.pipe(Layer.provide(layer$39))));
55516
56362
  //#endregion
55517
56363
  //#region src/pullRequest/PullRequestService.ts
55518
56364
  /**
@@ -55700,7 +56546,7 @@ function repositoryIdentityOf(project) {
55700
56546
  if (identity.displayName) return identity.displayName;
55701
56547
  return identity.owner && identity.name ? `${identity.owner}/${identity.name}` : null;
55702
56548
  }
55703
- const make$30 = Effect.gen(function* () {
56549
+ const make$31 = Effect.gen(function* () {
55704
56550
  const registry = yield* PullRequestProviderRegistry;
55705
56551
  const projections = yield* ProjectionSnapshotQuery;
55706
56552
  const sourceControlProviders = yield* SourceControlProviderRegistry;
@@ -56670,7 +57516,7 @@ const make$30 = Effect.gen(function* () {
56670
57516
  invalidate
56671
57517
  });
56672
57518
  });
56673
- const layer$23 = Layer.effect(PullRequestService, make$30);
57519
+ const layer$23 = Layer.effect(PullRequestService, make$31);
56674
57520
  //#endregion
56675
57521
  //#region src/orchestration/ThreadWorkspaceLifecycle.ts
56676
57522
  var ThreadWorkspaceLifecycleError = class extends Data.TaggedError("ThreadWorkspaceLifecycleError") {};
@@ -56714,7 +57560,7 @@ const mapLifecycleError = Effect.mapError((cause) => cause instanceof ThreadWork
56714
57560
  detail: "Thread workspace lifecycle operation failed.",
56715
57561
  cause
56716
57562
  }));
56717
- const make$29 = Effect.gen(function* () {
57563
+ const make$30 = Effect.gen(function* () {
56718
57564
  const snapshots = yield* ProjectionSnapshotQuery;
56719
57565
  const engine = yield* OrchestrationEngineService;
56720
57566
  const gitWorkflow = yield* GitWorkflowService;
@@ -56968,7 +57814,7 @@ const make$29 = Effect.gen(function* () {
56968
57814
  record
56969
57815
  };
56970
57816
  });
56971
- const layer$22 = Layer.effect(ThreadWorkspaceLifecycleService, make$29);
57817
+ const layer$22 = Layer.effect(ThreadWorkspaceLifecycleService, make$30);
56972
57818
  //#endregion
56973
57819
  //#region src/textGeneration/BtwRequestCoordinator.ts
56974
57820
  const MAX_PENDING_BTW_CANCELLATIONS = 256;
@@ -59512,7 +60358,7 @@ function makeUpdateState(input) {
59512
60358
  output: input.output ?? null
59513
60359
  };
59514
60360
  }
59515
- const make$28 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
60361
+ const make$29 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
59516
60362
  const providerRegistry = yield* ProviderRegistry;
59517
60363
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
59518
60364
  const httpClient = yield* HttpClient.HttpClient;
@@ -59627,7 +60473,7 @@ const make$28 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
59627
60473
  });
59628
60474
  return ProviderMaintenanceRunner.of({ updateProvider });
59629
60475
  });
59630
- const layer$21 = Layer.effect(ProviderMaintenanceRunner, make$28());
60476
+ const layer$21 = Layer.effect(ProviderMaintenanceRunner, make$29());
59631
60477
  //#endregion
59632
60478
  //#region src/provider/Drivers/ClaudeHome.ts
59633
60479
  const resolveClaudeHomePath = Effect.fn("resolveClaudeHomePath")(function* (config) {
@@ -60737,7 +61583,7 @@ Layer.succeed(UsageService, UsageService.of({ readSummary: (input) => Effect.suc
60737
61583
  },
60738
61584
  scanDurationMs: 0
60739
61585
  }) }));
60740
- const make$27 = Effect.gen(function* () {
61586
+ const make$28 = Effect.gen(function* () {
60741
61587
  const fileSystem = yield* FileSystem.FileSystem;
60742
61588
  const path = yield* Path.Path;
60743
61589
  const config = yield* ServerConfig$1;
@@ -60975,7 +61821,7 @@ const make$27 = Effect.gen(function* () {
60975
61821
  };
60976
61822
  }) };
60977
61823
  });
60978
- const layer$20 = Layer.effect(UsageService, make$27);
61824
+ const layer$20 = Layer.effect(UsageService, make$28);
60979
61825
  //#endregion
60980
61826
  //#region src/feed/FeedStore.ts
60981
61827
  const storageFailure = (message) => new FeedError({
@@ -61339,7 +62185,7 @@ const jsonRequest = Effect.fn("FeedService.jsonRequest")(function* (url, token,
61339
62185
  catch: () => fail("hub_unavailable", "Hub returned invalid JSON.")
61340
62186
  });
61341
62187
  });
61342
- const make$26 = Effect.gen(function* () {
62188
+ const make$27 = Effect.gen(function* () {
61343
62189
  const hubLink = yield* HubLink;
61344
62190
  const providers = yield* ProviderInstanceRegistry;
61345
62191
  const config = yield* ServerConfig$1;
@@ -61577,7 +62423,7 @@ var FeedService = class extends Context.Reference("@p4code/cli/feed/FeedService"
61577
62423
  markRead: unavailable,
61578
62424
  cleanup: unavailable
61579
62425
  }) }) {};
61580
- const layer$19 = Layer.effect(FeedService, make$26);
62426
+ const layer$19 = Layer.effect(FeedService, make$27);
61581
62427
  const SKILL_MANIFEST_FILENAME = "SKILL.md";
61582
62428
  /**
61583
62429
  * Split a catalogue id (`owner/repo/skill-name`) into its parts.
@@ -61723,7 +62569,7 @@ const emptyFetch = (id, unavailable) => ({
61723
62569
  skipped: [],
61724
62570
  unavailable
61725
62571
  });
61726
- const make$25 = Effect.gen(function* () {
62572
+ const make$26 = Effect.gen(function* () {
61727
62573
  const http = yield* HttpClient.HttpClient;
61728
62574
  const request = Effect.fn("SkillRegistry.request")(function* (url) {
61729
62575
  return yield* http.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("user-agent", "p4code"))).pipe(Effect.timeout(REQUEST_TIMEOUT_MS));
@@ -61794,7 +62640,7 @@ const make$25 = Effect.gen(function* () {
61794
62640
  fetch
61795
62641
  };
61796
62642
  });
61797
- const layer$18 = Layer.effect(SkillRegistry, make$25);
62643
+ const layer$18 = Layer.effect(SkillRegistry, make$26);
61798
62644
  //#endregion
61799
62645
  //#region src/mcp/McpInvocationContext.ts
61800
62646
  var McpInvocationContext = class extends Context.Service()("@p4code/cli/mcp/McpInvocationContext") {};
@@ -61897,9 +62743,9 @@ const requireThreadControlTarget = Effect.fn("mcp.requireThreadControlTarget")(f
61897
62743
  * The guard on starting a thread, which is a different question from acting on
61898
62744
  * one: it is not "which thread" but "may this session make more of them".
61899
62745
  */
61900
- const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* () {
62746
+ const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* (fusion = false) {
61901
62747
  const invocation = yield* requireThreadCapability();
61902
- if (invocation.mayCreateThreads !== true) return yield* new ThreadSpawnNotPermittedError({
62748
+ if (invocation.mayCreateThreads !== true && !(fusion && invocation.mayCreateFusionPairs === true)) return yield* new ThreadSpawnNotPermittedError({
61903
62749
  threadId: invocation.threadId,
61904
62750
  detail: "this thread was itself started by an agent, and spawning goes one level deep so a runaway loop has a bound"
61905
62751
  });
@@ -62022,7 +62868,7 @@ const classifyResponseError = (context, error) => {
62022
62868
  });
62023
62869
  }
62024
62870
  };
62025
- const make$24 = Effect.gen(function* PreviewAutomationBrokerMake() {
62871
+ const make$25 = Effect.gen(function* PreviewAutomationBrokerMake() {
62026
62872
  const crypto = yield* Crypto.Crypto;
62027
62873
  const state = yield* SynchronizedRef.make({
62028
62874
  clients: /* @__PURE__ */ new Map(),
@@ -62256,7 +63102,7 @@ const make$24 = Effect.gen(function* PreviewAutomationBrokerMake() {
62256
63102
  invoke
62257
63103
  });
62258
63104
  }).pipe(Effect.withSpan("PreviewAutomationBroker.make"));
62259
- const layer$17 = Layer.effect(PreviewAutomationBroker, make$24);
63105
+ const layer$17 = Layer.effect(PreviewAutomationBroker, make$25);
62260
63106
  //#endregion
62261
63107
  //#region src/preview/Manager.ts
62262
63108
  /**
@@ -62320,7 +63166,7 @@ const buildIdleSnapshot = (input) => ({
62320
63166
  viewport: FILL_PREVIEW_VIEWPORT,
62321
63167
  updatedAt: input.updatedAt
62322
63168
  });
62323
- const make$23 = Effect.gen(function* PreviewManagerMake() {
63169
+ const make$24 = Effect.gen(function* PreviewManagerMake() {
62324
63170
  const serverEpoch = NodeCrypto.randomUUID();
62325
63171
  const stateRef = yield* SynchronizedRef.make(initialState);
62326
63172
  const eventsPubSub = yield* PubSub.unbounded();
@@ -62551,12 +63397,15 @@ const make$23 = Effect.gen(function* PreviewManagerMake() {
62551
63397
  subscribeEvents: PubSub.subscribe(eventsPubSub)
62552
63398
  });
62553
63399
  }).pipe(Effect.withSpan("PreviewManager.make"));
62554
- const layer$16 = Layer.effect(PreviewManager, make$23);
63400
+ const layer$16 = Layer.effect(PreviewManager, make$24);
62555
63401
  //#endregion
62556
63402
  //#region src/workspace/WorkspaceSearchIndex.ts
62557
63403
  const WORKSPACE_INDEX_MAX_ENTRIES = 25e3;
62558
63404
  const WORKSPACE_INDEX_PAGE_SIZE = 25002;
62559
63405
  const WORKSPACE_INDEX_SCAN_TIMEOUT = "15 seconds";
63406
+ const CONTENT_SEARCH_TIME_BUDGET_MS = 250;
63407
+ const CONTENT_SEARCH_MAX_MATCHES_PER_FILE = 100;
63408
+ const CONTENT_INDEX_READY_TIMEOUT_MS = 15e3;
62560
63409
  const WORKSPACE_INDEX_IDLE_TTL = "15 minutes";
62561
63410
  const WORKSPACE_INDEX_SCAN_POLL_INTERVAL = "50 millis";
62562
63411
  var WorkspaceSearchIndexCreateFailed = class extends Schema$1.TaggedErrorClass()("WorkspaceSearchIndexCreateFailed", {
@@ -62650,12 +63499,12 @@ function withDirectoryAncestors(entries) {
62650
63499
  }
62651
63500
  return [...entryByPath.values()];
62652
63501
  }
62653
- const createFinder = Effect.fn("WorkspaceSearchIndex.createFinder")(function* (cwd) {
63502
+ const createFinder = Effect.fn("WorkspaceSearchIndex.createFinder")(function* (cwd, contentSearch) {
62654
63503
  const result = yield* Effect.try({
62655
63504
  try: () => FileFinder.create({
62656
63505
  basePath: cwd,
62657
63506
  disableMmapCache: true,
62658
- disableContentIndexing: true,
63507
+ disableContentIndexing: !contentSearch,
62659
63508
  aiMode: false,
62660
63509
  enableFsRootScanning: true,
62661
63510
  enableHomeDirScanning: true
@@ -62685,8 +63534,8 @@ const waitForScan = (cwd, finder, onFailure) => Effect.try({
62685
63534
  timeout: WORKSPACE_INDEX_SCAN_TIMEOUT
62686
63535
  })
62687
63536
  }), Effect.withSpan("WorkspaceSearchIndex.waitForScan"));
62688
- const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62689
- const finder = yield* Effect.acquireRelease(createFinder(cwd), (finder) => Effect.try({
63537
+ const make$23 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd, contentSearch = false) {
63538
+ const finder = yield* Effect.acquireRelease(createFinder(cwd, contentSearch), (finder) => Effect.try({
62690
63539
  try: () => finder.destroy(),
62691
63540
  catch: (cause) => new WorkspaceSearchIndexDestroyFailed({
62692
63541
  cwd,
@@ -62698,6 +63547,24 @@ const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62698
63547
  reason: "FileFinder.isScanning threw while creating the index.",
62699
63548
  cause
62700
63549
  }));
63550
+ if (contentSearch) {
63551
+ const ready = yield* Effect.tryPromise({
63552
+ try: () => finder.waitForIndexReady(CONTENT_INDEX_READY_TIMEOUT_MS),
63553
+ catch: (cause) => new WorkspaceSearchIndexCreateFailed({
63554
+ cwd,
63555
+ reason: "Content index initialization failed.",
63556
+ cause
63557
+ })
63558
+ });
63559
+ if (!ready.ok) return yield* new WorkspaceSearchIndexCreateFailed({
63560
+ cwd,
63561
+ reason: ready.error
63562
+ });
63563
+ if (!ready.value) return yield* new WorkspaceSearchIndexScanTimedOut({
63564
+ cwd,
63565
+ timeout: WORKSPACE_INDEX_SCAN_TIMEOUT
63566
+ });
63567
+ }
62701
63568
  const runMixedSearch = Effect.fn("WorkspaceSearchIndex.runMixedSearch")(function* (query, pageSize) {
62702
63569
  const result = yield* Effect.try({
62703
63570
  try: () => finder.mixedSearch(query, { pageSize }),
@@ -62748,10 +63615,58 @@ const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62748
63615
  const search = Effect.fn("WorkspaceSearchIndex.search")(function* (query, limit) {
62749
63616
  return mapMixedSearchResult(yield* runMixedSearch(query, Math.max(1, limit + 1)), limit);
62750
63617
  });
63618
+ const searchContents = Effect.fn("WorkspaceSearchIndex.searchContents")(function* (input) {
63619
+ const deadline = performance.now() + CONTENT_SEARCH_TIME_BUDGET_MS;
63620
+ const matches = [];
63621
+ let cursor = null;
63622
+ do {
63623
+ const result = yield* Effect.try({
63624
+ try: () => finder.grep(input.query, {
63625
+ mode: "plain",
63626
+ smartCase: true,
63627
+ maxMatchesPerFile: Math.min(CONTENT_SEARCH_MAX_MATCHES_PER_FILE, input.limit),
63628
+ pageSize: input.limit - matches.length,
63629
+ timeBudgetMs: Math.max(1, Math.ceil(deadline - performance.now())),
63630
+ cursor
63631
+ }),
63632
+ catch: (cause) => new WorkspaceSearchIndexSearchFailed({
63633
+ cwd,
63634
+ queryLength: input.query.length,
63635
+ pageSize: input.limit,
63636
+ reason: "Content search failed.",
63637
+ cause
63638
+ })
63639
+ });
63640
+ if (!result.ok) return yield* new WorkspaceSearchIndexSearchFailed({
63641
+ cwd,
63642
+ queryLength: input.query.length,
63643
+ pageSize: input.limit,
63644
+ reason: result.error
63645
+ });
63646
+ for (const match of result.value.items) {
63647
+ const bytes = Buffer.from(match.lineContent);
63648
+ matches.push({
63649
+ path: toPosixPath(match.relativePath),
63650
+ lineNumber: match.lineNumber,
63651
+ lineContent: match.lineContent,
63652
+ matchRanges: match.matchRanges.map(([start, end]) => ({
63653
+ start: bytes.subarray(0, start).toString().length,
63654
+ end: bytes.subarray(0, end).toString().length
63655
+ }))
63656
+ });
63657
+ }
63658
+ cursor = result.value.nextCursor;
63659
+ } while (matches.length < input.limit && cursor !== null && performance.now() < deadline);
63660
+ return {
63661
+ matches: matches.slice(0, input.limit),
63662
+ truncated: matches.length > input.limit || cursor !== null
63663
+ };
63664
+ });
62751
63665
  return WorkspaceSearchIndex.of({
62752
63666
  list,
62753
63667
  refresh,
62754
- search
63668
+ search,
63669
+ searchContents
62755
63670
  });
62756
63671
  });
62757
63672
  /**
@@ -62759,11 +63674,16 @@ const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62759
63674
  * workspace root. WorkspaceSearchIndexMap owns memoization and idle cleanup;
62760
63675
  * using a default cwd here would mix resources from different workspaces.
62761
63676
  */
62762
- const layer$15 = (cwd) => Layer.effect(WorkspaceSearchIndex, make$22(cwd));
63677
+ const layer$15 = (cwd) => Layer.effect(WorkspaceSearchIndex, make$23(cwd));
62763
63678
  var WorkspaceSearchIndexMap = class extends LayerMap.Service()("@p4code/cli/workspace/WorkspaceSearchIndexMap", {
62764
63679
  lookup: layer$15,
62765
63680
  idleTimeToLive: WORKSPACE_INDEX_IDLE_TTL
62766
63681
  }) {};
63682
+ /** Content indexing is allocated only when workspace content search is used. */
63683
+ var WorkspaceContentSearchIndexMap = class extends LayerMap.Service()("@p4code/cli/workspace/WorkspaceContentSearchIndexMap", {
63684
+ lookup: (cwd) => Layer.effect(WorkspaceSearchIndex, make$23(cwd, true)),
63685
+ idleTimeToLive: WORKSPACE_INDEX_IDLE_TTL
63686
+ }) {};
62767
63687
  //#endregion
62768
63688
  //#region src/workspace/WorkspaceEntries.ts
62769
63689
  var WorkspaceEntriesWindowsPathUnsupportedError = class extends Schema$1.TaggedErrorClass()("WorkspaceEntriesWindowsPathUnsupportedError", {
@@ -62823,9 +63743,10 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu
62823
63743
  if (!input.cwd) return yield* new WorkspaceEntriesCurrentProjectRequiredError({ partialPath: input.partialPath });
62824
63744
  return path.resolve(expandHomePath$1(input.cwd, path), input.partialPath);
62825
63745
  });
62826
- const make$21 = Effect.gen(function* () {
63746
+ const make$22 = Effect.gen(function* () {
62827
63747
  const path = yield* Path.Path;
62828
63748
  const workspacePaths = yield* WorkspacePaths;
63749
+ const contentSearchIndexes = yield* WorkspaceContentSearchIndexMap;
62829
63750
  const workspaceSearchIndexes = yield* WorkspaceSearchIndexMap;
62830
63751
  const normalizeWorkspaceRoot = Effect.fn("WorkspaceEntries.normalizeWorkspaceRoot")(function* (cwd) {
62831
63752
  return yield* workspacePaths.normalizeWorkspaceRoot(cwd);
@@ -62890,14 +63811,21 @@ const make$21 = Effect.gen(function* () {
62890
63811
  return yield* (yield* WorkspaceSearchIndex).list();
62891
63812
  }).pipe(Effect.provide(workspaceSearchIndexes.get(normalizedCwd)));
62892
63813
  });
63814
+ const searchContents = Effect.fn("WorkspaceEntries.searchContents")(function* (input) {
63815
+ const cwd = yield* normalizeWorkspaceRoot(input.cwd);
63816
+ return yield* Effect.gen(function* () {
63817
+ return yield* (yield* WorkspaceSearchIndex).searchContents(input);
63818
+ }).pipe(Effect.provide(contentSearchIndexes.get(cwd)));
63819
+ });
62893
63820
  return WorkspaceEntries.of({
62894
63821
  browse,
62895
63822
  list,
62896
63823
  refresh,
62897
- search
63824
+ search,
63825
+ searchContents
62898
63826
  });
62899
63827
  });
62900
- const layer$14 = Layer.effect(WorkspaceEntries, make$21).pipe(Layer.provide(WorkspaceSearchIndexMap.layer));
63828
+ const layer$14 = Layer.effect(WorkspaceEntries, make$22).pipe(Layer.provide(WorkspaceSearchIndexMap.layer), Layer.provide(WorkspaceContentSearchIndexMap.layer));
62901
63829
  //#endregion
62902
63830
  //#region src/workspace/WorkspaceFileSystem.ts
62903
63831
  /**
@@ -62956,7 +63884,7 @@ Schema$1.Union([
62956
63884
  ]);
62957
63885
  /** Service tag for workspace file operations. */
62958
63886
  var WorkspaceFileSystem = class extends Context.Service()("@p4code/cli/workspace/WorkspaceFileSystem") {};
62959
- const make$20 = Effect.gen(function* () {
63887
+ const make$21 = Effect.gen(function* () {
62960
63888
  const fileSystem = yield* FileSystem.FileSystem;
62961
63889
  const path = yield* Path.Path;
62962
63890
  const workspacePaths = yield* WorkspacePaths;
@@ -63100,7 +64028,7 @@ const make$20 = Effect.gen(function* () {
63100
64028
  writeFile
63101
64029
  });
63102
64030
  });
63103
- const layer$13 = Layer.effect(WorkspaceFileSystem, make$20);
64031
+ const layer$13 = Layer.effect(WorkspaceFileSystem, make$21);
63104
64032
  //#endregion
63105
64033
  //#region src/vcs/VcsStatusBroadcaster.ts
63106
64034
  const DEFAULT_VCS_STATUS_REFRESH_INTERVAL = Duration.seconds(30);
@@ -63172,7 +64100,7 @@ function fingerprintStatusPart(status) {
63172
64100
  return JSON.stringify(status);
63173
64101
  }
63174
64102
  const normalizeCwd = (cwd) => Effect.service(FileSystem.FileSystem).pipe(Effect.flatMap((fs) => fs.realPath(cwd)), Effect.orElseSucceed(() => cwd));
63175
- const make$19 = Effect.gen(function* () {
64103
+ const make$20 = Effect.gen(function* () {
63176
64104
  const workflow = yield* GitWorkflowService;
63177
64105
  const fs = yield* FileSystem.FileSystem;
63178
64106
  const changesPubSub = yield* Effect.acquireRelease(PubSub.unbounded(), (pubsub) => PubSub.shutdown(pubsub));
@@ -63396,7 +64324,7 @@ const make$19 = Effect.gen(function* () {
63396
64324
  streamStatus
63397
64325
  });
63398
64326
  });
63399
- const layer$12 = Layer.effect(VcsStatusBroadcaster, make$19);
64327
+ const layer$12 = Layer.effect(VcsStatusBroadcaster, make$20);
63400
64328
  //#endregion
63401
64329
  //#region src/vcs/VcsProvisioningService.ts
63402
64330
  var VcsProvisioningService = class extends Context.Service()("@p4code/cli/vcs/VcsProvisioningService") {};
@@ -63409,7 +64337,7 @@ function resolveRequestedKind(kind) {
63409
64337
  }));
63410
64338
  return Effect.succeed(kind);
63411
64339
  }
63412
- const make$18 = Effect.gen(function* () {
64340
+ const make$19 = Effect.gen(function* () {
63413
64341
  const registry = yield* VcsDriverRegistry;
63414
64342
  const initRepository = Effect.fn("VcsProvisioningService.initRepository")(function* (input) {
63415
64343
  const kind = yield* resolveRequestedKind(input.kind);
@@ -63417,11 +64345,11 @@ const make$18 = Effect.gen(function* () {
63417
64345
  });
63418
64346
  return VcsProvisioningService.of({ initRepository });
63419
64347
  });
63420
- const layer$11 = Layer.effect(VcsProvisioningService, make$18);
64348
+ const layer$11 = Layer.effect(VcsProvisioningService, make$19);
63421
64349
  //#endregion
63422
64350
  //#region src/review/ReviewService.ts
63423
64351
  var ReviewService = class extends Context.Service()("@p4code/cli/review/ReviewService") {};
63424
- const make$17 = Effect.gen(function* () {
64352
+ const make$18 = Effect.gen(function* () {
63425
64353
  const config = yield* ServerConfig$1;
63426
64354
  const fileSystem = yield* FileSystem.FileSystem;
63427
64355
  const path = yield* Path.Path;
@@ -63477,7 +64405,7 @@ const make$17 = Effect.gen(function* () {
63477
64405
  });
63478
64406
  return ReviewService.of({ getDiffPreview });
63479
64407
  });
63480
- const layer$10 = Layer.effect(ReviewService, make$17);
64408
+ const layer$10 = Layer.effect(ReviewService, make$18);
63481
64409
  //#endregion
63482
64410
  //#region src/diagnostics/ProcessDiagnostics.ts
63483
64411
  const PROCESS_QUERY_TIMEOUT_MS = 1e3;
@@ -63772,7 +64700,7 @@ function assertDescendantPid(pid) {
63772
64700
  }));
63773
64701
  }));
63774
64702
  }
63775
- const make$16 = Effect.gen(function* () {
64703
+ const make$17 = Effect.gen(function* () {
63776
64704
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
63777
64705
  const read = Effect.gen(function* () {
63778
64706
  const readAt = yield* DateTime.now;
@@ -63816,7 +64744,7 @@ const make$16 = Effect.gen(function* () {
63816
64744
  signal
63817
64745
  });
63818
64746
  });
63819
- const layer$9 = Layer.effect(ProcessDiagnostics, make$16);
64747
+ const layer$9 = Layer.effect(ProcessDiagnostics, make$17);
63820
64748
  //#endregion
63821
64749
  //#region src/diagnostics/ProcessResourceMonitor.ts
63822
64750
  const SAMPLE_INTERVAL_MS = 5e3;
@@ -63967,7 +64895,7 @@ function aggregateProcessResourceHistory(input) {
63967
64895
  }) : Option.none()
63968
64896
  };
63969
64897
  }
63970
- const make$15 = Effect.gen(function* () {
64898
+ const make$16 = Effect.gen(function* () {
63971
64899
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
63972
64900
  const state = yield* Ref.make({
63973
64901
  samples: [],
@@ -64016,7 +64944,7 @@ const make$15 = Effect.gen(function* () {
64016
64944
  });
64017
64945
  return ProcessResourceMonitor.of({ readHistory });
64018
64946
  });
64019
- const layer$8 = Layer.effect(ProcessResourceMonitor, make$15);
64947
+ const layer$8 = Layer.effect(ProcessResourceMonitor, make$16);
64020
64948
  //#endregion
64021
64949
  //#region src/diagnostics/TraceDiagnostics.ts
64022
64950
  var TraceFileReadError = class extends Schema$1.TaggedErrorClass()("TraceFileReadError", {
@@ -64264,7 +65192,7 @@ function readTraceFile(fileSystem, path) {
64264
65192
  cause
64265
65193
  })) }));
64266
65194
  }
64267
- const make$14 = Effect.gen(function* () {
65195
+ const make$15 = Effect.gen(function* () {
64268
65196
  const fileSystem = yield* FileSystem.FileSystem;
64269
65197
  const read = Effect.fn("TraceDiagnostics.read")(function* (options) {
64270
65198
  const readAt = options.readAt ?? (yield* DateTime.now);
@@ -64308,7 +65236,7 @@ const make$14 = Effect.gen(function* () {
64308
65236
  });
64309
65237
  return TraceDiagnostics.of({ read });
64310
65238
  });
64311
- const layer$7 = Layer.effect(TraceDiagnostics, make$14);
65239
+ const layer$7 = Layer.effect(TraceDiagnostics, make$15);
64312
65240
  function readTraceDiagnostics(options) {
64313
65241
  return Effect.gen(function* () {
64314
65242
  return yield* (yield* TraceDiagnostics).read(options);
@@ -64332,7 +65260,7 @@ const VCS_PROBES = [{
64332
65260
  installHint: "Install Jujutsu with `brew install jj` or from https://github.com/jj-vcs/jj."
64333
65261
  }];
64334
65262
  var SourceControlDiscovery = class extends Context.Service()("@p4code/cli/sourceControl/SourceControlDiscovery") {};
64335
- const make$13 = Effect.gen(function* () {
65263
+ const make$14 = Effect.gen(function* () {
64336
65264
  const config = yield* ServerConfig$1;
64337
65265
  const process = yield* VcsProcess;
64338
65266
  const sourceControlProviders = yield* SourceControlProviderRegistry;
@@ -64381,7 +65309,7 @@ const make$13 = Effect.gen(function* () {
64381
65309
  sourceControlProviders: sourceControlProviders.discover
64382
65310
  }) });
64383
65311
  });
64384
- const layer$6 = Layer.effect(SourceControlDiscovery, make$13);
65312
+ const layer$6 = Layer.effect(SourceControlDiscovery, make$14);
64385
65313
  //#endregion
64386
65314
  //#region src/sourceControl/SourceControlRepositoryService.ts
64387
65315
  const isSourceControlRepositoryError = Schema$1.is(SourceControlRepositoryError);
@@ -64414,7 +65342,7 @@ function expandHomePath(input, path) {
64414
65342
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
64415
65343
  return input;
64416
65344
  }
64417
- const make$12 = Effect.gen(function* () {
65345
+ const make$13 = Effect.gen(function* () {
64418
65346
  const config = yield* ServerConfig$1;
64419
65347
  const fileSystem = yield* FileSystem.FileSystem;
64420
65348
  const git = yield* GitVcsDriver;
@@ -64553,7 +65481,7 @@ const make$12 = Effect.gen(function* () {
64553
65481
  publishRepository: (input) => publishRepository(input).pipe(mapRepositoryError("publishRepository", input.provider))
64554
65482
  });
64555
65483
  });
64556
- const layer$5 = Layer.effect(SourceControlRepositoryService, make$12);
65484
+ const layer$5 = Layer.effect(SourceControlRepositoryService, make$13);
64557
65485
  //#endregion
64558
65486
  //#region src/ws.ts
64559
65487
  /** Matches `p4c hub token add`, so a token minted here and one minted there are the same thing. */
@@ -64659,6 +65587,7 @@ function isThreadDetailEvent(event) {
64659
65587
  }
64660
65588
  const PROVIDER_STATUS_DEBOUNCE_MS = 200;
64661
65589
  const SHELL_RESUME_MAX_GAP = 1e3;
65590
+ const THREAD_RESUME_MAX_GAP = 1e3;
64662
65591
  const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
64663
65592
  [ORCHESTRATION_WS_METHODS.dispatchCommand, AuthOrchestrationOperateScope],
64664
65593
  [ORCHESTRATION_WS_METHODS.getTurnDiff, AuthOrchestrationReadScope],
@@ -64745,6 +65674,7 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
64745
65674
  [WS_METHODS.projectsListEntries, AuthOrchestrationReadScope],
64746
65675
  [WS_METHODS.projectsReadFile, AuthOrchestrationReadScope],
64747
65676
  [WS_METHODS.projectsSearchEntries, AuthOrchestrationReadScope],
65677
+ [WS_METHODS.projectsSearchContents, AuthOrchestrationReadScope],
64748
65678
  [WS_METHODS.projectsWriteFile, AuthOrchestrationOperateScope],
64749
65679
  [WS_METHODS.shellOpenInEditor, AuthOrchestrationOperateScope],
64750
65680
  [WS_METHODS.filesystemBrowse, AuthOrchestrationReadScope],
@@ -65361,15 +66291,19 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
65361
66291
  const bufferedLiveStream = liveBuffer.stream;
65362
66292
  if (input.afterSequence !== void 0) {
65363
66293
  const afterSequence = input.afterSequence;
65364
- const catchUpStream = orchestrationEngine.readEvents(afterSequence, Number.MAX_SAFE_INTEGER).pipe(Stream.filter(isThisThreadDetailEvent), Stream.map((event) => ({
65365
- kind: "event",
65366
- event: projectActivityEvent(event)
65367
- })), Stream.mapError((cause) => new OrchestrationGetSnapshotError({
65368
- message: `Failed to replay thread ${input.threadId} events`,
65369
- cause
65370
- })));
65371
- const afterCatchUp = input.requestCompletionMarker === true ? Stream.concat(Stream.fromEffect(liveBuffer.offerAndWait({ kind: "synchronized" }).pipe(Effect.andThen(liveBuffer.takeAll))).pipe(Stream.flatMap((items) => Stream.fromIterable(items))), bufferedLiveStream) : bufferedLiveStream;
65372
- return Stream.concat(catchUpStream, afterCatchUp);
66294
+ const headSequence = yield* orchestrationEngine.latestSequence;
66295
+ const replayGap = headSequence - afterSequence;
66296
+ if (replayGap >= 0 && replayGap <= THREAD_RESUME_MAX_GAP) {
66297
+ const catchUpStream = (replayGap === 0 ? Stream.empty : orchestrationEngine.readEvents(afterSequence, replayGap)).pipe(Stream.takeWhile((event) => event.sequence <= headSequence), Stream.filter(isThisThreadDetailEvent), Stream.map((event) => ({
66298
+ kind: "event",
66299
+ event: projectActivityEvent(event)
66300
+ })), Stream.mapError((cause) => new OrchestrationGetSnapshotError({
66301
+ message: `Failed to replay thread ${input.threadId} events`,
66302
+ cause
66303
+ })));
66304
+ const afterCatchUp = input.requestCompletionMarker === true ? Stream.concat(Stream.fromEffect(liveBuffer.offerAndWait({ kind: "synchronized" }).pipe(Effect.andThen(liveBuffer.takeAll))).pipe(Stream.flatMap((items) => Stream.fromIterable(items))), bufferedLiveStream) : bufferedLiveStream;
66305
+ return Stream.concat(catchUpStream, afterCatchUp);
66306
+ }
65373
66307
  }
65374
66308
  const snapshot = yield* projectionSnapshotQuery.getThreadDetailSnapshot(input.threadId, input.turnLimit === void 0 ? void 0 : { turnLimit: input.turnLimit }).pipe(Effect.mapError((cause) => new OrchestrationGetSnapshotError({
65375
66309
  message: `Failed to load thread ${input.threadId}`,
@@ -65637,6 +66571,13 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
65637
66571
  ...projectEntriesFailureContext(cause),
65638
66572
  cause
65639
66573
  }))), { "rpc.aggregate": "workspace" }),
66574
+ [WS_METHODS.projectsSearchContents]: (input) => observeRpcEffect$1(WS_METHODS.projectsSearchContents, workspaceEntries.searchContents(input).pipe(Effect.mapError((cause) => new ProjectSearchEntriesError({
66575
+ cwd: input.cwd,
66576
+ queryLength: input.query.length,
66577
+ limit: input.limit,
66578
+ ...projectEntriesFailureContext(cause),
66579
+ cause
66580
+ }))), { "rpc.aggregate": "workspace" }),
65640
66581
  [WS_METHODS.projectsListEntries]: (input) => observeRpcEffect$1(WS_METHODS.projectsListEntries, workspaceEntries.list(input).pipe(Effect.mapError((cause) => new ProjectListEntriesError({
65641
66582
  ...input,
65642
66583
  ...projectEntriesFailureContext(cause),
@@ -65857,7 +66798,7 @@ function toPersistenceSqlOrDecodeError(sqlOperation, decodeOperation, correlatio
65857
66798
  cause
65858
66799
  });
65859
66800
  }
65860
- const make$11 = Effect.gen(function* () {
66801
+ const make$12 = Effect.gen(function* () {
65861
66802
  const sql = yield* SqlClient.SqlClient;
65862
66803
  const upsertRuntimeRow = SqlSchema.void({
65863
66804
  Request: ProviderSessionRuntimeDbRowSchema,
@@ -65960,7 +66901,7 @@ const make$11 = Effect.gen(function* () {
65960
66901
  deleteByThreadId
65961
66902
  };
65962
66903
  });
65963
- const layer$4 = Layer.effect(ProviderSessionRuntimeRepository, make$11);
66904
+ const layer$4 = Layer.effect(ProviderSessionRuntimeRepository, make$12);
65964
66905
  //#endregion
65965
66906
  //#region src/provider/Errors.ts
65966
66907
  /**
@@ -66612,7 +67553,7 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66612
67553
  const httpServer = yield* HttpServer.HttpServer;
66613
67554
  const state = yield* SynchronizedRef.make({
66614
67555
  records: /* @__PURE__ */ new Map(),
66615
- spawnedThreadIds: /* @__PURE__ */ new Set()
67556
+ spawnedThreads: /* @__PURE__ */ new Map()
66616
67557
  });
66617
67558
  const currentTimeMillis = options.now ? Effect.sync(options.now) : Clock.currentTimeMillis;
66618
67559
  const livenessWindowMs = options.livenessWindowMs ?? DEFAULT_LIVENESS_WINDOW_MS;
@@ -66637,7 +67578,7 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66637
67578
  if (watchThreadIds.size > 0) capabilities.add("watch");
66638
67579
  if (adviseThreadIds.size > 0) capabilities.add("advise");
66639
67580
  const threadId = ThreadId.make(request.threadId);
66640
- const scopeWith = (mayCreateThreads) => ({
67581
+ const scopeWith = (mayCreateThreads, mayCreateFusionPairs) => ({
66641
67582
  environmentId,
66642
67583
  threadId,
66643
67584
  providerSessionId,
@@ -66646,19 +67587,20 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66646
67587
  ...watchThreadIds.size > 0 ? { watchThreadIds } : {},
66647
67588
  ...adviseThreadIds.size > 0 ? { adviseThreadIds } : {},
66648
67589
  mayCreateThreads,
67590
+ mayCreateFusionPairs,
66649
67591
  issuedAt
66650
67592
  });
66651
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67593
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66652
67594
  const next = new Map(pruneDead(records, issuedAt));
66653
67595
  next.set(tokenHash, {
66654
67596
  tokenHash,
66655
- scope: scopeWith(!spawnedThreadIds.has(threadId)),
67597
+ scope: scopeWith(!spawnedThreads.has(threadId), spawnedThreads.get(threadId) === true),
66656
67598
  controlThreadIds: /* @__PURE__ */ new Set(),
66657
67599
  lastAliveAt: issuedAt
66658
67600
  });
66659
67601
  return {
66660
67602
  records: next,
66661
- spawnedThreadIds
67603
+ spawnedThreads
66662
67604
  };
66663
67605
  });
66664
67606
  return { config: {
@@ -66674,12 +67616,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66674
67616
  if (rawToken.length === 0) return void 0;
66675
67617
  const tokenHash = yield* hashToken(rawToken);
66676
67618
  const timestamp = yield* currentTimeMillis;
66677
- return yield* SynchronizedRef.modify(state, ({ records, spawnedThreadIds }) => {
67619
+ return yield* SynchronizedRef.modify(state, ({ records, spawnedThreads }) => {
66678
67620
  const current = pruneDead(records, timestamp);
66679
67621
  const record = current.get(tokenHash);
66680
67622
  if (!record) return [void 0, {
66681
67623
  records: current,
66682
- spawnedThreadIds
67624
+ spawnedThreads
66683
67625
  }];
66684
67626
  const next = new Map(current);
66685
67627
  next.set(tokenHash, {
@@ -66693,13 +67635,13 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66693
67635
  watchThreadIds: /* @__PURE__ */ new Set([...record.scope.watchThreadIds ?? [], ...record.controlThreadIds])
66694
67636
  }, {
66695
67637
  records: next,
66696
- spawnedThreadIds
67638
+ spawnedThreads
66697
67639
  }];
66698
67640
  });
66699
67641
  });
66700
67642
  const touch = Effect.fn("McpSessionRegistry.touch")(function* (threadId) {
66701
67643
  const timestamp = yield* currentTimeMillis;
66702
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67644
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66703
67645
  const current = pruneDead(records, timestamp);
66704
67646
  const next = new Map(current);
66705
67647
  for (const [tokenHash, record] of current) if (record.scope.threadId === threadId) next.set(tokenHash, {
@@ -66708,12 +67650,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66708
67650
  });
66709
67651
  return {
66710
67652
  records: next,
66711
- spawnedThreadIds
67653
+ spawnedThreads
66712
67654
  };
66713
67655
  });
66714
67656
  });
66715
67657
  const grantWatchThread = Effect.fn("McpSessionRegistry.grantWatchThread")(function* ({ watcherThreadId, watchedThreadId }) {
66716
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67658
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66717
67659
  const next = new Map(records);
66718
67660
  for (const [tokenHash, record] of records) {
66719
67661
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66728,12 +67670,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66728
67670
  }
66729
67671
  return {
66730
67672
  records: next,
66731
- spawnedThreadIds
67673
+ spawnedThreads
66732
67674
  };
66733
67675
  });
66734
67676
  });
66735
67677
  const revokeWatchThread = Effect.fn("McpSessionRegistry.revokeWatchThread")(function* ({ watcherThreadId, watchedThreadId }) {
66736
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67678
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66737
67679
  const next = new Map(records);
66738
67680
  for (const [tokenHash, record] of records) {
66739
67681
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66756,12 +67698,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66756
67698
  }
66757
67699
  return {
66758
67700
  records: next,
66759
- spawnedThreadIds
67701
+ spawnedThreads
66760
67702
  };
66761
67703
  });
66762
67704
  });
66763
67705
  const grantAdviseThread = Effect.fn("McpSessionRegistry.grantAdviseThread")(function* ({ watcherThreadId, advisedThreadId }) {
66764
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67706
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66765
67707
  const next = new Map(records);
66766
67708
  for (const [tokenHash, record] of records) {
66767
67709
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66776,12 +67718,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66776
67718
  }
66777
67719
  return {
66778
67720
  records: next,
66779
- spawnedThreadIds
67721
+ spawnedThreads
66780
67722
  };
66781
67723
  });
66782
67724
  });
66783
67725
  const revokeAdviseThread = Effect.fn("McpSessionRegistry.revokeAdviseThread")(function* ({ watcherThreadId, advisedThreadId }) {
66784
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67726
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66785
67727
  const next = new Map(records);
66786
67728
  for (const [tokenHash, record] of records) {
66787
67729
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66804,12 +67746,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66804
67746
  }
66805
67747
  return {
66806
67748
  records: next,
66807
- spawnedThreadIds
67749
+ spawnedThreads
66808
67750
  };
66809
67751
  });
66810
67752
  });
66811
67753
  const recordSpawnedThread = Effect.fn("McpSessionRegistry.recordSpawnedThread")(function* (input) {
66812
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67754
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66813
67755
  const next = new Map(records);
66814
67756
  for (const [tokenHash, record] of records) if (record.scope.providerSessionId === input.providerSessionId) next.set(tokenHash, {
66815
67757
  ...record,
@@ -66817,13 +67759,13 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66817
67759
  });
66818
67760
  return {
66819
67761
  records: next,
66820
- spawnedThreadIds: /* @__PURE__ */ new Set([...spawnedThreadIds, input.threadId])
67762
+ spawnedThreads: new Map([...spawnedThreads, [input.threadId, input.mayCreateFusionPairs === true]])
66821
67763
  };
66822
67764
  });
66823
67765
  });
66824
- const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => ({
67766
+ const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records, spawnedThreads }) => ({
66825
67767
  records: new Map(Array.from(records).filter(([, record]) => !predicate(record))),
66826
- spawnedThreadIds
67768
+ spawnedThreads
66827
67769
  }));
66828
67770
  return McpSessionRegistry.of({
66829
67771
  issue,
@@ -66840,19 +67782,19 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66840
67782
  revokeThread: Effect.fn("McpSessionRegistry.revokeThread")(function* (threadId) {
66841
67783
  yield* revokeWhere((record) => record.scope.threadId === threadId);
66842
67784
  }),
66843
- revokeAll: SynchronizedRef.update(state, ({ spawnedThreadIds }) => ({
67785
+ revokeAll: SynchronizedRef.update(state, ({ spawnedThreads }) => ({
66844
67786
  records: /* @__PURE__ */ new Map(),
66845
- spawnedThreadIds
67787
+ spawnedThreads
66846
67788
  }))
66847
67789
  });
66848
67790
  });
66849
67791
  let activeMcpSessionRegistry;
66850
- const make$10 = Effect.acquireRelease(makeWithOptions().pipe(Effect.tap((registry) => Effect.sync(() => {
67792
+ const make$11 = Effect.acquireRelease(makeWithOptions().pipe(Effect.tap((registry) => Effect.sync(() => {
66851
67793
  activeMcpSessionRegistry = registry;
66852
67794
  }))), (registry) => Effect.sync(() => {
66853
67795
  if (activeMcpSessionRegistry === registry) activeMcpSessionRegistry = void 0;
66854
67796
  }));
66855
- const layer$3 = Layer.effect(McpSessionRegistry, make$10);
67797
+ const layer$3 = Layer.effect(McpSessionRegistry, make$11);
66856
67798
  const issueActiveMcpCredential = (request) => activeMcpSessionRegistry ? activeMcpSessionRegistry.revokeThread(request.threadId).pipe(Effect.andThen(activeMcpSessionRegistry.issue(request))) : Effect.sync(() => void 0);
66857
67799
  /**
66858
67800
  * Refreshes the liveness of a thread's MCP credential. Called on every provider
@@ -67561,6 +68503,13 @@ const makeProviderSessionReaper = (options) => Effect.gen(function* () {
67561
68503
  });
67562
68504
  continue;
67563
68505
  }
68506
+ if (thread?.backgroundLiveness != null) {
68507
+ yield* Effect.logDebug("provider.session.reaper.skipped-background-work", {
68508
+ threadId: binding.threadId,
68509
+ backgroundLiveness: thread.backgroundLiveness
68510
+ });
68511
+ continue;
68512
+ }
67564
68513
  if (yield* providerService.stopSession({ threadId: binding.threadId }).pipe(Effect.tap(() => Effect.logInfo("provider.session.reaped", {
67565
68514
  threadId: binding.threadId,
67566
68515
  provider: binding.provider,
@@ -67578,14 +68527,18 @@ const makeProviderSessionReaper = (options) => Effect.gen(function* () {
67578
68527
  totalBindings: bindings.length
67579
68528
  });
67580
68529
  });
68530
+ const sweepSafely = () => sweep.pipe(Effect.catch((error) => Effect.logWarning("provider.session.reaper.sweep-failed", { error })), Effect.catchDefect((defect) => Effect.logWarning("provider.session.reaper.sweep-defect", { defect })));
67581
68531
  const start = () => Effect.gen(function* () {
67582
- yield* Effect.forkScoped(sweep.pipe(Effect.catch((error) => Effect.logWarning("provider.session.reaper.sweep-failed", { error })), Effect.catchDefect((defect) => Effect.logWarning("provider.session.reaper.sweep-defect", { defect })), Effect.repeat(Schedule.spaced(Duration.millis(sweepIntervalMs)))));
68532
+ yield* Effect.forkScoped(sweepSafely().pipe(Effect.repeat(Schedule.spaced(Duration.millis(sweepIntervalMs)))));
67583
68533
  yield* Effect.logInfo("provider.session.reaper.started", {
67584
68534
  inactivityThresholdMs,
67585
68535
  sweepIntervalMs
67586
68536
  });
67587
68537
  });
67588
- return { start };
68538
+ return {
68539
+ start,
68540
+ sweep: sweepSafely
68541
+ };
67589
68542
  });
67590
68543
  const makeProviderSessionReaperLive = (options) => Layer.effect(ProviderSessionReaper, makeProviderSessionReaper(options));
67591
68544
  const ProviderSessionReaperLive = makeProviderSessionReaperLive();
@@ -69767,7 +70720,7 @@ function formatAskUserQuestionAnswers(answers) {
69767
70720
  * only a one-line reference and the mutable `[fusion-pair]` metadata.
69768
70721
  */
69769
70722
  const FUSION_BUILDER_INSTRUCTIONS = `You are Fusion Builder in an already-created native server pair. Server owns pairing and coordination. Do not inspect or invoke the Fusion skill, create/pair/rename threads, or announce/setup Fusion. Start the user's task directly. Before editing, create and maintain the phase list with your provider's step-tracking tool (Claude Code: TaskCreate for each phase, then TaskUpdate for status, or TodoWrite when that is the tool offered; Codex: update_plan), never the MCP task board tools - one entry per phase in order, exactly one in progress at a time, marked completed at each phase end - so phases render in the task banner. That list holds phase entries only for the whole task; keep step-level or per-file todos out of it. Name each phase in 3-6 words by its outcome, never by a command, file path, or flag, because the banner shows the title verbatim. Prose alone leaves the banner empty. Split it into the fewest substantial phases the task genuinely needs plus a final integration/whole-task phase; most tasks need one to three work phases. Each phase is a complete reviewable slice of behavior. Never split per file, per function, or per trivial step: over-splitting spends review turns instead of finishing the job. Add a phase only when a real review boundary, risky decision, or independent behavior separates the work. Complete exactly one phase per turn, and finish the whole phase in that turn rather than stopping early. Do not run tests, typecheck, lint, or builds per phase; write the tests the change needs, then run verification once in the final phase over the whole task. Exception: a phase whose own correctness is unclear may run the single narrowest check that resolves it. End every phase turn with phase completed, todo status, changed behavior/files, and remaining phases; do not start the next phase in the same turn. Server then wakes the paired Supervisor, which resumes you through ${FUSION_ADVICE_PROMPT_PREFIX}; a user message may also revise or resume the work. Final phase verifies the entire task against the original request and labels it ready for whole-task review. Supervisor is unreachable during your turn. Never spawn/use another Supervisor thread/subagent or attribute Supervisor decisions without ${FUSION_ADVICE_PROMPT_PREFIX}. Within the current phase, continue when straightforward or evidence is clear. For a concrete unresolved tradeoff, correctness risk, or design decision materially needing judgment, stop safely before the risky choice; final response states the exact question and why review is needed. Evaluate/follow Supervisor advice unless conflicting with user request or verified repo state.`;
69770
- const FUSION_WATCHER_INSTRUCTIONS = `You are Fusion Supervisor (watcher) in an already-created native server pair. Server owns pairing and coordination and wakes you with ${FUSION_REVIEW_PROMPT_PREFIX} or ${FUSION_GATE_PROMPT_PREFIX} prompts at builder turn boundaries. A plain message outside such a wake may arrive after your conversational memory of the pair is gone; its [fusion-pair] metadata block is authoritative: the builder thread exists and is the counterpart thread id. Never report that no builder thread exists. To resume supervision, read builder events with thread_watch_events from lastReviewedImplementerSequence with limit 50, paging forward with the last returned sequence rather than requesting a whole range at once, derive phase from artifacts (git log/status, PR, builder events, including its turn.plan.updated phase list), steer with thread_advise, and answer an open gate with thread_gate_respond. When a review or gate wake prompt specifies an explicit event range, that range wins over this metadata. Never poll or wait for the builder; deliver review or advice, then end the turn. Every thread_advise starts a builder turn whose completion wakes you again, so never advise a builder that is idle on an external wait or has nothing actionable; report the state in your own thread and end without advising.`;
70723
+ const FUSION_WATCHER_INSTRUCTIONS = `You are Fusion Supervisor (watcher) in an already-created native server pair. Server owns pairing and coordination and wakes you with ${FUSION_REVIEW_PROMPT_PREFIX} or ${FUSION_GATE_PROMPT_PREFIX} prompts at builder turn boundaries. A plain message outside such a wake may arrive after your conversational memory of the pair is gone; its [fusion-pair] metadata block is authoritative: the builder thread exists and is the counterpart thread id. Never report that no builder thread exists. To resume supervision, read builder evidence with thread_watch_review from lastReviewedImplementerSequence, capture its throughSequence on the first page and reuse that fixed bound while paging with nextAfterSequence until hasMore is false (including empty pages). Apply message append/replace operations by identity; recover truncated evidence through targeted raw thread_watch_events source ranges. Retain reviewed requirements and evidence for final review; recover missing context with targeted history reads rather than mandatory raw replay from zero, derive phase from artifacts (git log/status, PR, builder events, including its turn.plan.updated phase list), steer with thread_advise, and answer an open gate with thread_gate_respond. When a review or gate wake prompt specifies an explicit event range, that range wins over this metadata. Never poll or wait for the builder; deliver review or advice, then end the turn. Every thread_advise starts a builder turn whose completion wakes you again, so never advise a builder that is idle on an external wait or has nothing actionable; report the state in your own thread and end without advising.`;
69771
70724
  /**
69772
70725
  * The one-line stand-in for the full block on a message whose session already
69773
70726
  * carries the role instructions.
@@ -91028,7 +91981,7 @@ const makeTerminationError$1 = (handle) => Effect.match(handle.exitCode, {
91028
91981
  //#endregion
91029
91982
  //#region ../../packages/effect-codex-app-server/src/client.ts
91030
91983
  var CodexAppServerClient = class extends Context.Service()("effect-codex-app-server/client/CodexAppServerClient") {};
91031
- const make$9 = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(function* (stdio, options = {}, terminationError) {
91984
+ const make$10 = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(function* (stdio, options = {}, terminationError) {
91032
91985
  const requestHandlers = /* @__PURE__ */ new Map();
91033
91986
  const notificationHandlers = /* @__PURE__ */ new Map();
91034
91987
  let unknownRequestHandler;
@@ -91095,7 +92048,7 @@ const make$9 = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(fu
91095
92048
  const layerChildProcess$1 = (handle, options = {}) => Layer.effect(CodexAppServerClient, makeChildProcessClient(handle, options));
91096
92049
  const makeChildProcessClient = Effect.fn("effect-codex-app-server/CodexAppServerClient.makeChildProcessClient")(function* (handle, options) {
91097
92050
  yield* Stream.runDrain(handle.stderr).pipe(Effect.ignore, Effect.forkScoped);
91098
- return yield* make$9(makeChildStdio$1(handle), options, makeTerminationError$1(handle));
92051
+ return yield* make$10(makeChildStdio$1(handle), options, makeTerminationError$1(handle));
91099
92052
  });
91100
92053
  const resolveCodexLaunchArgs = (launchArgs, environment = process.env) => environment["P4CODE_CODEX_LAUNCH_ARGS"]?.trim() || launchArgs?.trim() || "";
91101
92054
  const codexLaunchArgv = (launchArgs) => tokenizeCliArgs(launchArgs);
@@ -92118,6 +93071,8 @@ Default mode active; prior mode instructions inactive. Only developer \`<collabo
92118
93071
 
92119
93072
  Prefer reasonable assumptions and execution. Ask only when local discovery cannot answer and a reasonable assumption is risky.
92120
93073
 
93074
+ For multi-step work, keep task phases visible with native \`update_plan\`. If that tool is unavailable, use \`mcp__p4_code__thread_plan_update\` instead: send the complete ordered \`plan\` array of \`{ step, status }\`, with statuses \`pending\`, \`in_progress\`, or \`completed\`. Update it as phases finish. Prose alone does not populate the Tasks banner. This fallback tracks this conversation, not task-board tickets.
93075
+
92121
93076
  ${CODEX_STRUCTURED_USER_QUESTIONS}
92122
93077
  ${P4_CODE_BROWSER_TOOL_INSTRUCTIONS}
92123
93078
  </collaboration_mode>`;
@@ -97898,7 +98853,7 @@ const makeTerminationError = (handle) => Effect.match(handle.exitCode, {
97898
98853
  //#endregion
97899
98854
  //#region ../../packages/effect-acp/src/client.ts
97900
98855
  var AcpClient = class extends Context.Service()("effect-acp/client/AcpClient") {};
97901
- const make$8 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
98856
+ const make$9 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
97902
98857
  const coreHandlers = {};
97903
98858
  const notificationHandlers = {
97904
98859
  sessionUpdate: {
@@ -98056,7 +99011,7 @@ const make$8 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options
98056
99011
  const layerChildProcess = (handle, options = {}) => {
98057
99012
  const stdio = makeChildStdio(handle);
98058
99013
  const terminationError = makeTerminationError(handle);
98059
- return Layer.effect(AcpClient, make$8(stdio, options, terminationError));
99014
+ return Layer.effect(AcpClient, make$9(stdio, options, terminationError));
98060
99015
  };
98061
99016
  //#endregion
98062
99017
  //#region ../../packages/shared/src/toolActivity.ts
@@ -98516,7 +99471,7 @@ function formatConfigOptionValue(value) {
98516
99471
  const defaultSessionLoadTimeout = Duration.seconds(90);
98517
99472
  const defaultSessionLoadReplayIdleGap = Duration.seconds(2);
98518
99473
  var AcpSessionRuntime = class extends Context.Service()("@p4code/cli/provider/acp/AcpSessionRuntime") {};
98519
- const make$7 = (options) => Effect.gen(function* () {
99474
+ const make$8 = (options) => Effect.gen(function* () {
98520
99475
  const crypto = yield* Crypto.Crypto;
98521
99476
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
98522
99477
  const runtimeScope = yield* Scope.Scope;
@@ -98832,7 +99787,7 @@ const make$7 = (options) => Effect.gen(function* () {
98832
99787
  notify: acp.raw.notify
98833
99788
  };
98834
99789
  });
98835
- const layer$2 = (options) => Layer.effect(AcpSessionRuntime, make$7(options));
99790
+ const layer$2 = (options) => Layer.effect(AcpSessionRuntime, make$8(options));
98836
99791
  function sessionConfigOptionsFromSetup(response) {
98837
99792
  return response?.configOptions ?? [];
98838
99793
  }
@@ -106293,7 +107248,7 @@ const stringField = (record, key) => {
106293
107248
  const value = record[key];
106294
107249
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
106295
107250
  };
106296
- const make$6 = Effect.gen(function* () {
107251
+ const make$7 = Effect.gen(function* () {
106297
107252
  const linear = yield* LinearClient;
106298
107253
  return { resolve: Effect.fn("TicketResolver.resolve")(function* (reference) {
106299
107254
  const identifier = parseTicketReference(reference);
@@ -106324,7 +107279,7 @@ const make$6 = Effect.gen(function* () {
106324
107279
  };
106325
107280
  }) };
106326
107281
  });
106327
- const layer$1 = Layer.effect(TicketResolver, make$6);
107282
+ const layer$1 = Layer.effect(TicketResolver, make$7);
106328
107283
  //#endregion
106329
107284
  //#region src/mcp/toolkits/tasks/tools.ts
106330
107285
  const dependencies = [McpInvocationContext, TaskRepository];
@@ -106863,7 +107818,7 @@ const AskUserQuestionTool = Tool.make("ask_user_question", {
106863
107818
  ]
106864
107819
  }).annotate(Tool.Title, "Ask user question").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
106865
107820
  const ThreadSpawnTool = Tool.make("thread_spawn", {
106866
- 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. Set fusionWatcher true for a Fusion watcher; the server then requires explicit user approval before creating anything. 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.",
107821
+ 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. Set fusion true to create two new threads as a separate Fusion pair and start its builder with prompt, even when this thread already belongs to a pair. Set fusionWatcher true only for a supervisor to pair with the current thread. 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.",
106867
107822
  parameters: ThreadSpawnInput,
106868
107823
  success: ThreadSpawnResult,
106869
107824
  failure: ThreadControlToolError,
@@ -106871,20 +107826,19 @@ const ThreadSpawnTool = Tool.make("thread_spawn", {
106871
107826
  McpInvocationContext,
106872
107827
  McpSessionRegistry,
106873
107828
  OrchestrationEngineService,
106874
- ProjectionSnapshotQuery,
106875
107829
  ProjectionThreadRepository,
107830
+ ServerSettingsService,
106876
107831
  Crypto.Crypto
106877
107832
  ]
106878
107833
  }).annotate(Tool.Title, "Start a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
106879
107834
  const ThreadPairCreateTool = Tool.make("thread_pair_create", {
106880
- description: "Create a persisted Fusion pair between this agent session's own thread as builder and a supervisor it previously created with thread_spawn. The server refuses unless the latest user message explicitly invokes Fusion or approves the immediately preceding Fusion proposal. Successful creation automatically continues the builder, so never ask the user to resend the task.",
107835
+ description: "Create a persisted Fusion pair between this agent session's own thread as builder and a supervisor it previously created with thread_spawn. Act on the user's request; no special command syntax is required. The current thread must not already belong to an active Fusion pair. Successful creation automatically continues the builder, so never ask the user to resend the task.",
106881
107836
  parameters: ThreadPairCreateInput,
106882
107837
  success: ThreadPairCreateResult,
106883
107838
  failure: ThreadControlToolError,
106884
107839
  dependencies: [
106885
107840
  McpInvocationContext,
106886
107841
  OrchestrationEngineService,
106887
- ProjectionSnapshotQuery,
106888
107842
  Crypto.Crypto
106889
107843
  ]
106890
107844
  }).annotate(Tool.Title, "Create a Fusion pair").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
@@ -106972,7 +107926,19 @@ const AssetCompressTool = Tool.make("asset_compress", {
106972
107926
  Path.Path
106973
107927
  ]
106974
107928
  }).annotate(Tool.Title, "Compress an asset").annotate(Tool.Readonly, false).annotate(Tool.Destructive, true).annotate(Tool.Idempotent, false);
106975
- const ThreadToolkit = Toolkit.make(AskUserQuestionTool, ThreadSpawnTool, ThreadPairCreateTool, ThreadConfigureTool, ThreadSettleTool, ThreadCleanupTool, ThreadSnoozeTool, ThreadRenameTool, MemoryAppendTool, AssetCompressTool);
107929
+ const ThreadPlanUpdateTool = Tool.make("thread_plan_update", {
107930
+ description: "Update the current thread's Tasks banner when your provider has no native update_plan or todo tool. Send the complete ordered phase list on every update, with at most one in_progress phase. Use short outcome titles. An empty plan clears the banner. This tracks conversation progress, not task-board tickets.",
107931
+ parameters: ThreadPlanUpdateInput,
107932
+ success: ThreadPlanUpdateResult,
107933
+ failure: ThreadControlToolError,
107934
+ dependencies: [
107935
+ McpInvocationContext,
107936
+ OrchestrationEngineService,
107937
+ ProjectionThreadRepository,
107938
+ Crypto.Crypto
107939
+ ]
107940
+ }).annotate(Tool.Title, "Update task phases").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
107941
+ const ThreadToolkit = Toolkit.make(ThreadPlanUpdateTool, AskUserQuestionTool, ThreadSpawnTool, ThreadPairCreateTool, ThreadConfigureTool, ThreadSettleTool, ThreadCleanupTool, ThreadSnoozeTool, ThreadRenameTool, MemoryAppendTool, AssetCompressTool);
106976
107942
  //#endregion
106977
107943
  //#region src/orchestration/pendingMcpUserInputs.ts
106978
107944
  /**
@@ -107047,29 +108013,6 @@ const newCommandId = Effect.gen(function* () {
107047
108013
  const crypto = yield* Crypto.Crypto;
107048
108014
  return CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
107049
108015
  });
107050
- const fusionInvocationLine = /^(?:\/fusion|\$fusion)(?:\s+.*)?$/i;
107051
- const fusionAffirmativeLine = /^(?:approved?|yes(?:,?\s+(?:please|do it))?|ok(?:ay)?|go ahead|do it|proceed)[.!]?$/i;
107052
- function lastNonEmptyLine(text) {
107053
- return text.split(/\r?\n/).findLast((line) => line.trim().length > 0)?.trim() ?? "";
107054
- }
107055
- function directlyInvokesFusion(text) {
107056
- return text.split(/\r?\n/).some((line) => fusionInvocationLine.test(line.trim()));
107057
- }
107058
- function asksForFusionApproval(message) {
107059
- if (message?.role !== "assistant") return false;
107060
- const text = message.text.toLowerCase();
107061
- return text.includes("fusion") && (text.includes("approve") || text.includes("approval"));
107062
- }
107063
- const requireFusionApproval = Effect.fn("mcp.threads.requireFusionApproval")(function* (threadId) {
107064
- const thread = yield* (yield* ProjectionSnapshotQuery).getThreadDetailById(threadId).pipe(Effect.mapError(() => new ThreadPairApprovalRequiredError({ threadId })));
107065
- if (Option.isNone(thread)) return yield* new ThreadPairApprovalRequiredError({ threadId });
107066
- const latestUserIndex = thread.value.messages.findLastIndex((message) => message.role === "user");
107067
- const latestUser = latestUserIndex >= 0 ? thread.value.messages[latestUserIndex] : void 0;
107068
- if (latestUser !== void 0 && directlyInvokesFusion(latestUser.text)) return;
107069
- const precedingAssistant = thread.value.messages.slice(0, latestUserIndex).findLast((message) => message.role === "assistant");
107070
- if (latestUser !== void 0 && fusionAffirmativeLine.test(lastNonEmptyLine(latestUser.text)) && asksForFusionApproval(precedingAssistant)) return;
107071
- return yield* new ThreadPairApprovalRequiredError({ threadId });
107072
- });
107073
108016
  /**
107074
108017
  * The model a spawned thread starts on.
107075
108018
  *
@@ -107087,6 +108030,42 @@ const resolveSpawnModelSelection = (requested, inherited) => requested === void
107087
108030
  ...requested.options !== void 0 ? { options: requested.options } : {}
107088
108031
  };
107089
108032
  const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
108033
+ thread_plan_update: (input) => Effect.gen(function* () {
108034
+ const { threadId } = yield* requireThreadCapability();
108035
+ const threads = yield* ProjectionThreadRepository;
108036
+ const reject = (detail) => new ThreadControlRejectedError({
108037
+ threadId,
108038
+ commandType: "thread.activity.append",
108039
+ detail
108040
+ });
108041
+ const thread = yield* threads.getById({ threadId }).pipe(Effect.mapError((cause) => reject(cause.message)));
108042
+ if (Option.isNone(thread)) return yield* reject("Current thread does not exist.");
108043
+ if (input.plan.filter((step) => step.status === "in_progress").length > 1) return yield* reject("At most one phase may be in progress.");
108044
+ const crypto = yield* Crypto.Crypto;
108045
+ const createdAt = DateTime.formatIso(yield* DateTime.now);
108046
+ yield* dispatchControl({
108047
+ type: "thread.activity.append",
108048
+ commandId: yield* newCommandId,
108049
+ threadId,
108050
+ createdAt,
108051
+ activity: {
108052
+ id: EventId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
108053
+ createdAt,
108054
+ tone: "info",
108055
+ kind: "turn.plan.updated",
108056
+ summary: "Plan updated",
108057
+ turnId: thread.value.latestTurnId,
108058
+ payload: {
108059
+ ...input,
108060
+ plan: input.plan.map((step) => ({
108061
+ step: step.step,
108062
+ status: step.status === "in_progress" ? "inProgress" : step.status
108063
+ }))
108064
+ }
108065
+ }
108066
+ }, threadId);
108067
+ return { threadId };
108068
+ }),
107090
108069
  ask_user_question: (input) => Effect.gen(function* () {
107091
108070
  const invocation = yield* requireThreadCapability();
107092
108071
  const crypto = yield* Crypto.Crypto;
@@ -107158,8 +108137,11 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107158
108137
  }).pipe(Effect.ensuring(Effect.sync(() => forgetPendingMcpUserInput(invocation.threadId, requestId))));
107159
108138
  }),
107160
108139
  thread_spawn: (input) => Effect.gen(function* () {
107161
- const invocation = yield* requireThreadSpawn();
107162
- if (input.fusionWatcher === true) yield* requireFusionApproval(invocation.threadId);
108140
+ const invocation = yield* requireThreadSpawn(input.fusion === true);
108141
+ if (input.fusion === true && input.fusionWatcher === true) return yield* new ThreadSpawnNotPermittedError({
108142
+ threadId: invocation.threadId,
108143
+ detail: "Choose a new Fusion pair or a standalone watcher, not both."
108144
+ });
107163
108145
  const registry = yield* McpSessionRegistry;
107164
108146
  const threads = yield* ProjectionThreadRepository;
107165
108147
  const crypto = yield* Crypto.Crypto;
@@ -107173,7 +108155,11 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107173
108155
  });
107174
108156
  const template = parent.value;
107175
108157
  const projectId = input.projectId ?? template.projectId;
107176
- const modelSelection = resolveSpawnModelSelection(input.modelSelection, template.modelSelection);
108158
+ const settings = input.fusion === true || input.fusionWatcher === true ? yield* (yield* ServerSettingsService).getSettings.pipe(Effect.mapError((cause) => new ThreadSpawnNotPermittedError({
108159
+ threadId: invocation.threadId,
108160
+ detail: `Fusion settings could not be read: ${cause.message}`
108161
+ }))) : void 0;
108162
+ const modelSelection = resolveSpawnModelSelection(input.modelSelection, (input.fusionWatcher === true ? settings?.fusionWatcherModelSelection : settings?.fusionBuilderModelSelection) ?? template.modelSelection);
107177
108163
  const runtimeMode = input.runtimeMode ?? template.runtimeMode;
107178
108164
  const interactionMode = input.interactionMode ?? template.interactionMode;
107179
108165
  const compressMode = input.compressMode ?? template.compressMode;
@@ -107197,8 +108183,42 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107197
108183
  }, threadId);
107198
108184
  yield* registry.recordSpawnedThread({
107199
108185
  providerSessionId: invocation.providerSessionId,
107200
- threadId
108186
+ threadId,
108187
+ ...input.fusionWatcher === true ? { mayCreateFusionPairs: true } : {}
107201
108188
  });
108189
+ const fusionPair = input.fusion === true ? {
108190
+ pairId: ThreadPairId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
108191
+ watcherThreadId: ThreadId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie))
108192
+ } : void 0;
108193
+ if (fusionPair !== void 0) {
108194
+ yield* dispatchControl({
108195
+ type: "thread.create",
108196
+ commandId: yield* newCommandId,
108197
+ threadId: fusionPair.watcherThreadId,
108198
+ projectId,
108199
+ title: `Review: ${input.title}`,
108200
+ modelSelection: settings?.fusionWatcherModelSelection ?? modelSelection,
108201
+ runtimeMode,
108202
+ interactionMode: "default",
108203
+ compressMode,
108204
+ unpromptedSubagents,
108205
+ branch: null,
108206
+ worktreePath: null,
108207
+ createdAt
108208
+ }, fusionPair.watcherThreadId);
108209
+ yield* registry.recordSpawnedThread({
108210
+ providerSessionId: invocation.providerSessionId,
108211
+ threadId: fusionPair.watcherThreadId
108212
+ });
108213
+ yield* dispatchControl({
108214
+ type: "thread-pair.create",
108215
+ commandId: yield* newCommandId,
108216
+ pairId: fusionPair.pairId,
108217
+ implementerThreadId: threadId,
108218
+ watcherThreadId: fusionPair.watcherThreadId,
108219
+ createdAt
108220
+ }, threadId);
108221
+ }
107202
108222
  yield* dispatchControl({
107203
108223
  type: "thread.turn.start",
107204
108224
  commandId: yield* newCommandId,
@@ -107220,12 +108240,12 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107220
108240
  return {
107221
108241
  threadId,
107222
108242
  projectId,
107223
- title: input.title
108243
+ title: input.title,
108244
+ ...fusionPair
107224
108245
  };
107225
108246
  }),
107226
108247
  thread_pair_create: (input) => Effect.gen(function* () {
107227
108248
  const { invocation, threadId: watcherThreadId } = yield* requireThreadControlTarget(input.watcherThreadId);
107228
- yield* requireFusionApproval(invocation.threadId);
107229
108249
  const crypto = yield* Crypto.Crypto;
107230
108250
  const pairId = ThreadPairId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
107231
108251
  const continuationMessageId = MessageId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
@@ -107763,24 +108783,40 @@ const ThreadWatchEventsTool = Tool.make("thread_watch_events", {
107763
108783
  failure: ThreadWatchToolError,
107764
108784
  dependencies: [McpInvocationContext, ThreadEventStreamService]
107765
108785
  }).annotate(Tool.Title, "Read watched thread events").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
107766
- const WatchToolkit = Toolkit.make(ThreadWatchEventsTool);
107767
- const WatchToolkitHandlersLive = WatchToolkit.toLayer({ thread_watch_events: (input) => Effect.gen(function* () {
107768
- yield* requireWatchCapability(input.threadId);
107769
- const page = yield* (yield* ThreadEventStreamService).read({
107770
- threadId: input.threadId,
107771
- ...input.afterSequence !== void 0 ? { afterSequence: input.afterSequence } : {},
107772
- limit: input.limit ?? 50
107773
- }).pipe(Effect.mapError((cause) => new ThreadWatchFailedError({
107774
- threadId: input.threadId,
107775
- detail: cause.message
107776
- })));
107777
- return {
107778
- threadId: input.threadId,
107779
- events: page.events,
107780
- headSequence: page.headSequence,
107781
- hasMore: page.hasMore
107782
- };
107783
- }) });
108786
+ const ThreadWatchReviewTool = Tool.make("thread_watch_review", {
108787
+ description: "Read bounded compact review evidence for an explicitly granted thread. Combines assistant fragments by message identity; preserves user requirements, tool evidence, plans, errors and turn boundaries. Resume with nextAfterSequence and the same throughSequence until hasMore is false, even on empty pages. Message operations append or replace by identity across pages. Truncated evidence includes source sequence ranges for targeted recovery with thread_watch_events; do not treat an excerpt as full proof.",
108788
+ parameters: ThreadWatchReviewInput,
108789
+ success: ThreadWatchReviewResult,
108790
+ failure: ThreadWatchToolError,
108791
+ dependencies: [McpInvocationContext, ThreadEventStreamService]
108792
+ }).annotate(Tool.Title, "Read compact thread review").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
108793
+ const WatchToolkit = Toolkit.make(ThreadWatchEventsTool, ThreadWatchReviewTool);
108794
+ const WatchToolkitHandlersLive = WatchToolkit.toLayer({
108795
+ thread_watch_review: (input) => Effect.gen(function* () {
108796
+ yield* requireWatchCapability(input.threadId);
108797
+ return yield* (yield* ThreadEventStreamService).readReview(input).pipe(Effect.mapError((cause) => new ThreadWatchFailedError({
108798
+ threadId: input.threadId,
108799
+ detail: cause.message
108800
+ })));
108801
+ }),
108802
+ thread_watch_events: (input) => Effect.gen(function* () {
108803
+ yield* requireWatchCapability(input.threadId);
108804
+ const page = yield* (yield* ThreadEventStreamService).read({
108805
+ threadId: input.threadId,
108806
+ ...input.afterSequence !== void 0 ? { afterSequence: input.afterSequence } : {},
108807
+ limit: input.limit ?? 50
108808
+ }).pipe(Effect.mapError((cause) => new ThreadWatchFailedError({
108809
+ threadId: input.threadId,
108810
+ detail: cause.message
108811
+ })));
108812
+ return {
108813
+ threadId: input.threadId,
108814
+ events: page.events,
108815
+ headSequence: page.headSequence,
108816
+ hasMore: page.hasMore
108817
+ };
108818
+ })
108819
+ });
107784
108820
  //#endregion
107785
108821
  //#region src/mcp/McpHttpServer.ts
107786
108822
  const unauthorized = HttpServerResponse.jsonUnsafe({
@@ -107955,6 +108991,100 @@ var ThreadDeletionReactor = class extends Context.Service()("@p4code/cli/orchest
107955
108991
  //#region src/orchestration/Services/FusionWatcherReactor.ts
107956
108992
  var FusionWatcherReactor = class extends Context.Service()("@p4code/cli/orchestration/Services/FusionWatcherReactor") {};
107957
108993
  //#endregion
108994
+ //#region src/orchestration/Layers/ThreadCompletionReactor.ts
108995
+ const COMPLETION_SWEEP_BATCH_SIZE = 25;
108996
+ const COMPLETION_SWEEP_INTERVAL = Duration.minutes(1);
108997
+ const COMPLETION_PR_TIMEOUT = Duration.seconds(10);
108998
+ const MILLISECONDS_PER_DAY = 1440 * 60 * 1e3;
108999
+ const make$6 = Effect.gen(function* () {
109000
+ const sql = yield* SqlClient.SqlClient;
109001
+ const settings = yield* ServerSettingsService;
109002
+ const git = yield* GitManager;
109003
+ const engine = yield* OrchestrationEngineService;
109004
+ const crypto = yield* Crypto.Crypto;
109005
+ const mutex = yield* Semaphore.make(1);
109006
+ let cursor = "";
109007
+ const candidates = SqlSchema.findAll({
109008
+ Request: Schema$1.String,
109009
+ Result: Schema$1.Struct({
109010
+ threadId: ThreadId,
109011
+ cwd: Schema$1.String,
109012
+ branch: Schema$1.NullOr(Schema$1.String),
109013
+ lastEngagedAt: IsoDateTime,
109014
+ pinnedAt: Schema$1.NullOr(IsoDateTime),
109015
+ projectDeletedAt: Schema$1.NullOr(IsoDateTime),
109016
+ pendingApprovalCount: Schema$1.Number,
109017
+ pendingUserInputCount: Schema$1.Number,
109018
+ sessionStatus: Schema$1.NullOr(Schema$1.String)
109019
+ }),
109020
+ execute: (afterThreadId) => sql`
109021
+ SELECT t.thread_id AS "threadId", COALESCE(t.worktree_path, p.workspace_root) AS "cwd",
109022
+ t.branch, t.last_engaged_at AS "lastEngagedAt", t.pinned_at AS "pinnedAt",
109023
+ p.deleted_at AS "projectDeletedAt", t.pending_approval_count AS "pendingApprovalCount",
109024
+ t.pending_user_input_count AS "pendingUserInputCount", s.status AS "sessionStatus"
109025
+ FROM projection_threads t
109026
+ JOIN projection_projects p ON p.project_id = t.project_id
109027
+ LEFT JOIN projection_thread_sessions s ON s.thread_id = t.thread_id
109028
+ WHERE t.lifecycle = 'active' AND t.deleted_at IS NULL
109029
+ AND t.thread_id > ${afterThreadId}
109030
+ ORDER BY t.thread_id ASC LIMIT ${COMPLETION_SWEEP_BATCH_SIZE}
109031
+ `
109032
+ });
109033
+ const sweep = Effect.fn("ThreadCompletionReactor.sweep")(function* () {
109034
+ yield* mutex.withPermits(1)(Effect.gen(function* () {
109035
+ const now = yield* Clock.currentTimeMillis;
109036
+ const days = (yield* settings.getSettings).threadAutoCompleteAfterDays;
109037
+ const cutoff = days === null ? null : now - days * MILLISECONDS_PER_DAY;
109038
+ const rows = yield* candidates(cursor);
109039
+ const branchStates = /* @__PURE__ */ new Map();
109040
+ for (const row of rows) {
109041
+ if (row.projectDeletedAt !== null || row.pinnedAt !== null || row.pendingApprovalCount > 0 || row.pendingUserInputCount > 0 || row.sessionStatus === "starting" || row.sessionStatus === "running") continue;
109042
+ const key = `${row.cwd.length}:${row.cwd}${row.branch ?? ""}`;
109043
+ let pr = branchStates.get(key);
109044
+ if (pr === void 0) {
109045
+ pr = yield* git.threadCompletionPrState({
109046
+ cwd: row.cwd,
109047
+ branch: row.branch
109048
+ }).pipe(Effect.timeout(COMPLETION_PR_TIMEOUT), Effect.orElseSucceed(() => ({ state: "unknown" })));
109049
+ branchStates.set(key, pr);
109050
+ }
109051
+ if (pr.state === "unknown" || pr.state === "open") continue;
109052
+ const engagedAt = Date.parse(row.lastEngagedAt);
109053
+ const terminalAt = pr.state === "terminal" ? Date.parse(pr.terminalAt) : null;
109054
+ if (terminalAt !== null && (!Number.isFinite(terminalAt) || terminalAt > now)) continue;
109055
+ const eligible = terminalAt !== null && terminalAt > engagedAt ? {
109056
+ reason: "pull-request",
109057
+ eligibleAt: DateTime.formatIso(DateTime.makeUnsafe(terminalAt))
109058
+ } : cutoff !== null && engagedAt < cutoff ? {
109059
+ reason: "inactivity",
109060
+ eligibleAt: DateTime.formatIso(DateTime.makeUnsafe(cutoff))
109061
+ } : null;
109062
+ if (eligible === null) continue;
109063
+ const commandId = CommandId.make(`server:thread-complete:${yield* crypto.randomUUIDv4}`);
109064
+ yield* engine.dispatch({
109065
+ type: "thread.complete.auto",
109066
+ commandId,
109067
+ threadId: row.threadId,
109068
+ expectedLastEngagedAt: row.lastEngagedAt,
109069
+ ...eligible
109070
+ }).pipe(Effect.catch((cause) => Effect.logDebug("Automatic completion did not apply", {
109071
+ threadId: row.threadId,
109072
+ cause
109073
+ })));
109074
+ }
109075
+ cursor = rows.length < COMPLETION_SWEEP_BATCH_SIZE ? "" : rows.at(-1).threadId;
109076
+ }));
109077
+ });
109078
+ return {
109079
+ sweep,
109080
+ start: Effect.fn("ThreadCompletionReactor.start")(function* () {
109081
+ yield* sweep().pipe(Effect.catchCause((cause) => Effect.logWarning("Thread completion sweep failed", { cause })), Effect.repeat(Schedule.spaced(COMPLETION_SWEEP_INTERVAL)), Effect.forkScoped);
109082
+ })
109083
+ };
109084
+ });
109085
+ var ThreadCompletionReactor = class extends Context.Service()("@p4code/cli/orchestration/Layers/ThreadCompletionReactor") {};
109086
+ const ThreadCompletionReactorLive = Layer.effect(ThreadCompletionReactor, make$6);
109087
+ //#endregion
107958
109088
  //#region src/orchestration/Services/ScheduledTaskReactor.ts
107959
109089
  /**
107960
109090
  * ScheduledTaskReactor - fires user-created scheduled tasks.
@@ -107975,6 +109105,7 @@ const makeOrchestrationReactor = Effect.gen(function* () {
107975
109105
  const threadDeletionReactor = yield* ThreadDeletionReactor;
107976
109106
  const fusionWatcherReactor = yield* FusionWatcherReactor;
107977
109107
  const scheduledTaskReactor = yield* ScheduledTaskReactor;
109108
+ const completionReactor = yield* ThreadCompletionReactor;
107978
109109
  return { start: Effect.fn("start")(function* () {
107979
109110
  yield* providerRuntimeIngestion.start();
107980
109111
  yield* providerCommandReactor.start();
@@ -107982,6 +109113,7 @@ const makeOrchestrationReactor = Effect.gen(function* () {
107982
109113
  yield* threadDeletionReactor.start();
107983
109114
  yield* fusionWatcherReactor.start();
107984
109115
  yield* scheduledTaskReactor.start();
109116
+ yield* completionReactor.start();
107985
109117
  }) };
107986
109118
  });
107987
109119
  const OrchestrationReactorLive = Layer.effect(OrchestrationReactor, makeOrchestrationReactor);
@@ -109375,7 +110507,7 @@ const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30);
109375
110507
  const DEFAULT_RUNTIME_MODE = "full-access";
109376
110508
  const DEFAULT_THREAD_TITLE = "New thread";
109377
110509
  const NON_SYSTEM_PROVIDER_STRUCTURED_USER_QUESTIONS = structuredUserQuestionPrompt("your provider's structured user-input question tool");
109378
- const FUSION_PROMOTION_INSTRUCTIONS = `Work independently in this normal thread. Fusion is a silent escalation path, not a startup procedure. Do not inspect Fusion tools/skill, mention Fusion status, or announce that Fusion was not invoked. First analyze the task normally. Only if that analysis reveals a concrete unresolved tradeoff, correctness risk, or design decision materially needing a second opinion, stop before implementation, propose Fusion, and ask the user for explicit approval. The user may approve with ordinary affirmative text such as "approved"; /fusion or $fusion also authorizes Fusion directly without a prior proposal. Do not activate, spawn, or promote until one of those authorizations arrives. UI work, complex logic, task size, unfamiliarity, or duration alone never qualifies.`;
110510
+ const FUSION_PROMOTION_INSTRUCTIONS = `Work independently in this normal thread. Fusion is a silent escalation path, not a startup procedure. Do not inspect Fusion tools/skill, mention Fusion status, or announce that Fusion was not invoked. First analyze the task normally. Only if that analysis reveals a concrete unresolved tradeoff, correctness risk, or design decision materially needing a second opinion, stop before implementation, propose Fusion, and ask the user for explicit approval. The user may approve with ordinary affirmative text such as "approved". A direct user request to use or spawn Fusion authorizes Fusion directly without a prior proposal; /fusion and $fusion are optional shortcuts. Do not activate, spawn, or promote until one of those authorizations arrives. UI work, complex logic, task size, unfamiliarity, or duration alone never qualifies.`;
109379
110511
  const isFusionWatcherWakeMessageId = (messageId) => messageId.startsWith("fusion-review:") || messageId.startsWith("fusion-gate:");
109380
110512
  const findActiveFusionPair = (pairs, threadId) => (pairs ?? []).find((pair) => pair.detachedAt === null && (pair.implementerThreadId === threadId || pair.watcherThreadId === threadId));
109381
110513
  const fusionRoleForThread = (pairs, threadId) => {
@@ -110981,7 +112113,7 @@ After restart/context loss, derive phase from workspace git log/status, PR, rece
110981
112113
  const watcherPrompt = (input) => `${FUSION_REVIEW_PROMPT_PREFIX}
110982
112114
  Review completed builder turn ${input.implementerThreadId}.
110983
112115
 
110984
- Call thread_watch_events, threadId ${input.implementerThreadId}, afterSequence ${input.afterSequence}, limit 50; repeat with the last returned event's sequence as afterSequence until you reach ${input.throughSequence}. Never request a whole range in one call: an oversized page exceeds the tool output cap and wastes the turn. Inspect repo when useful.
112116
+ Call thread_watch_review, threadId ${input.implementerThreadId}, afterSequence ${input.afterSequence}, throughSequence ${input.throughSequence}. Continue with nextAfterSequence and the same throughSequence until hasMore is false, including empty pages. Apply message append/replace operations by identity across pages. Recover truncated or uncertain evidence with targeted thread_watch_events reads using its source sequence range. Inspect repo when useful.
110985
112117
 
110986
112118
  Always report concise:
110987
112119
 
@@ -110993,7 +112125,7 @@ Determine review boundary from builder's todo status:
110993
112125
 
110994
112126
  - Remaining phases, or final status unclear, and the builder has actionable work: intermediate phase review. Call thread_advise for ${input.implementerThreadId}. With objections, send required corrections and next phase. With none, explicitly approve phase and tell builder to continue next todo phase. Never require tests, typecheck, lint, or builds before the final phase.
110995
112127
  - Builder waiting on an external process or with nothing actionable: no thread_advise. Report the state here and end.
110996
- - Final whole-task phase ready: call thread_watch_events again from afterSequence 0 through ${input.throughSequence}, same limit 50 paging, then inspect full task diff/state. Review original requirements, integration across all phases, verification, and delivery. Advise only for concrete objections or unfinished work; otherwise report no objections and end.
112128
+ - Final whole-task phase ready: combine previously reviewed evidence with this delta and inspect the full task diff/state. Review original requirements and later corrections, integration across all phases, verification, and delivery. Recover missing requirements or uncertain prior evidence with targeted compact history reads bounded by throughSequence ${input.throughSequence}, and raw source reads when necessary. Do not replay raw history from zero by default. If prior context is unavailable, recover the requirements before concluding. Advise only for concrete objections or unfinished work; otherwise report no objections and end.
110997
112129
 
110998
112130
  ${watcherPowers(input.implementerThreadId)}
110999
112131
 
@@ -111012,7 +112144,7 @@ const gateKindDescription = (gate) => {
111012
112144
  const gatePrompt = (input) => `${FUSION_GATE_PROMPT_PREFIX}
111013
112145
  Gate ${input.gate.id} is open on builder thread ${input.implementerThreadId}: ${gateKindDescription(input.gate)}. Round ${Math.min(input.gate.round + 1, input.roundCap)} of ${input.roundCap}.
111014
112146
 
111015
- First read delta: thread_watch_events, threadId ${input.implementerThreadId}, afterSequence ${input.afterSequence}, limit 50; repeat with the last returned event's sequence as afterSequence until you reach ${input.throughSequence}. Never request a whole range in one call: an oversized page exceeds the tool output cap and wastes the turn. Inspect repo when useful.
112147
+ First read delta: thread_watch_review, threadId ${input.implementerThreadId}, afterSequence ${input.afterSequence}, throughSequence ${input.throughSequence}. Continue with nextAfterSequence and the same throughSequence until hasMore is false, including empty pages. Apply message append/replace operations by identity across pages; recover truncated or uncertain evidence with targeted thread_watch_events reads using its source sequence range. Inspect repo when useful.
111016
112148
 
111017
112149
  Then thread_gate_respond, threadId ${input.implementerThreadId}, gateId ${input.gate.id}:
111018
112150
 
@@ -112745,7 +113877,7 @@ const PlatformServicesLive = Layer.unwrap(Effect.gen(function* () {
112745
113877
  return layer;
112746
113878
  }
112747
113879
  }));
112748
- const ReactorLayerLive = Layer.empty.pipe(Layer.provideMerge(OrchestrationReactorLive), Layer.provideMerge(ProviderRuntimeIngestionLive), Layer.provideMerge(ProviderCommandReactorLive), Layer.provideMerge(CheckpointReactorLive), Layer.provideMerge(ThreadDeletionReactorLive), Layer.provideMerge(FusionWatcherReactorLive), Layer.provideMerge(ScheduledTaskReactorLive), Layer.provideMerge(RuntimeReceiptBusLive));
113880
+ const ReactorLayerLive = Layer.empty.pipe(Layer.provideMerge(OrchestrationReactorLive), Layer.provideMerge(ProviderRuntimeIngestionLive), Layer.provideMerge(ProviderCommandReactorLive), Layer.provideMerge(CheckpointReactorLive), Layer.provideMerge(ThreadDeletionReactorLive), Layer.provideMerge(FusionWatcherReactorLive), Layer.provideMerge(ScheduledTaskReactorLive), Layer.provideMerge(ThreadCompletionReactorLive), Layer.provideMerge(RuntimeReceiptBusLive));
112749
113881
  const ProviderSessionDirectoryLayerLive = ProviderSessionDirectoryLive.pipe(Layer.provide(layer$4));
112750
113882
  const ProviderLayerLive = ProviderServiceLive.pipe(Layer.provide(ProviderAdapterRegistryLive), Layer.provideMerge(ProviderSessionDirectoryLayerLive));
112751
113883
  const PersistenceLayerLive = Layer.empty.pipe(Layer.provideMerge(layerConfig));