@p4code/cli 0.4.8 → 0.4.10

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.8";
243
+ var version = "0.4.10";
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),
@@ -10052,13 +10129,16 @@ const ThreadSpawnInput = Schema$1.Struct({
10052
10129
  interactionMode: Schema$1.optional(ProviderInteractionMode),
10053
10130
  compressMode: Schema$1.optional(CompressMode),
10054
10131
  unpromptedSubagents: Schema$1.optional(Schema$1.Boolean),
10055
- 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." }))
10056
10134
  });
10057
10135
  const ThreadSpawnResult = Schema$1.Struct({
10058
10136
  /** Watchable with `thread_watch_events`, and addressable by every tool here. */
10059
10137
  threadId: ThreadId,
10060
10138
  projectId: ProjectId,
10061
- title: TrimmedNonEmptyString
10139
+ title: TrimmedNonEmptyString,
10140
+ pairId: Schema$1.optional(ThreadPairId),
10141
+ watcherThreadId: Schema$1.optional(ThreadId)
10062
10142
  });
10063
10143
  const ThreadPairCreateInput = Schema$1.Struct({
10064
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." }),
@@ -10230,12 +10310,6 @@ var ThreadSpawnNotPermittedError = class extends Schema$1.TaggedErrorClass()("Th
10230
10310
  return `Thread ${this.threadId} cannot start another thread: ${this.detail}`;
10231
10311
  }
10232
10312
  };
10233
- /** Fusion watcher and pair creation require fresh user authorization from this exact thread. */
10234
- var ThreadPairApprovalRequiredError = class extends Schema$1.TaggedErrorClass()("ThreadPairApprovalRequiredError", { threadId: ThreadId }) {
10235
- get message() {
10236
- 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.`;
10237
- }
10238
- };
10239
10313
  /** The orchestration engine declined or failed a thread control command. */
10240
10314
  var ThreadControlRejectedError = class extends Schema$1.TaggedErrorClass()("ThreadControlRejectedError", {
10241
10315
  threadId: ThreadId,
@@ -10250,7 +10324,6 @@ const ThreadControlToolError = Schema$1.Union([
10250
10324
  ThreadToolUnavailableError,
10251
10325
  ThreadControlNotPermittedError,
10252
10326
  ThreadSpawnNotPermittedError,
10253
- ThreadPairApprovalRequiredError,
10254
10327
  ThreadControlRejectedError,
10255
10328
  ThreadRenameRejectedError,
10256
10329
  MemoryAppendFailedError,
@@ -10274,6 +10347,45 @@ const ThreadWatchEventsResult = Schema$1.Struct({
10274
10347
  /** True when more events for this thread exist past the returned page. */
10275
10348
  hasMore: Schema$1.Boolean
10276
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
+ });
10277
10389
  var WatchToolUnavailableError = class extends Schema$1.TaggedErrorClass()("WatchToolUnavailableError", {
10278
10390
  capability: Schema$1.Literal("watch"),
10279
10391
  environmentId: EnvironmentId,
@@ -11539,6 +11651,7 @@ const WS_METHODS = {
11539
11651
  projectsRemove: "projects.remove",
11540
11652
  projectsListEntries: "projects.listEntries",
11541
11653
  projectsReadFile: "projects.readFile",
11654
+ projectsSearchContents: "projects.searchContents",
11542
11655
  projectsSearchEntries: "projects.searchEntries",
11543
11656
  projectsWriteFile: "projects.writeFile",
11544
11657
  shellOpenInEditor: "shell.openInEditor",
@@ -12249,6 +12362,11 @@ const WsSourceControlPublishRepositoryRpc = Rpc.make(WS_METHODS.sourceControlPub
12249
12362
  success: SourceControlPublishRepositoryResult,
12250
12363
  error: Schema$1.Union([SourceControlRepositoryError, EnvironmentAuthorizationError])
12251
12364
  });
12365
+ const WsProjectsSearchContentsRpc = Rpc.make(WS_METHODS.projectsSearchContents, {
12366
+ payload: ProjectSearchContentsInput,
12367
+ success: ProjectSearchContentsResult,
12368
+ error: Schema$1.Union([ProjectSearchEntriesError, EnvironmentAuthorizationError])
12369
+ });
12252
12370
  const WsProjectsSearchEntriesRpc = Rpc.make(WS_METHODS.projectsSearchEntries, {
12253
12371
  payload: ProjectSearchEntriesInput,
12254
12372
  success: ProjectSearchEntriesResult,
@@ -12514,7 +12632,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
12514
12632
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
12515
12633
  stream: true
12516
12634
  });
12517
- 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);
12518
12636
  //#endregion
12519
12637
  //#region ../../packages/shared/src/oauthScope.ts
12520
12638
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -12660,7 +12778,7 @@ function deriveAuthClientMetadata(input) {
12660
12778
  //#endregion
12661
12779
  //#region src/auth/EnvironmentAuthPolicy.ts
12662
12780
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
12663
- const make$90 = Effect.gen(function* () {
12781
+ const make$91 = Effect.gen(function* () {
12664
12782
  const config = yield* ServerConfig$1;
12665
12783
  const isRemoteReachable = isRemoteReachableHost(config.host);
12666
12784
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
@@ -12678,7 +12796,7 @@ const make$90 = Effect.gen(function* () {
12678
12796
  };
12679
12797
  return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
12680
12798
  });
12681
- const layer$80 = Layer.effect(EnvironmentAuthPolicy, make$90);
12799
+ const layer$80 = Layer.effect(EnvironmentAuthPolicy, make$91);
12682
12800
  //#endregion
12683
12801
  //#region src/persistence/Errors.ts
12684
12802
  function summarizeSchemaIssue(issue) {
@@ -12859,7 +12977,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
12859
12977
  cause
12860
12978
  });
12861
12979
  }
12862
- const make$89 = Effect.gen(function* () {
12980
+ const make$90 = Effect.gen(function* () {
12863
12981
  const sql = yield* SqlClient.SqlClient;
12864
12982
  const createSessionRow = SqlSchema.void({
12865
12983
  Request: CreateAuthSessionInput,
@@ -12993,7 +13111,7 @@ const make$89 = Effect.gen(function* () {
12993
13111
  setLastConnectedAt
12994
13112
  };
12995
13113
  });
12996
- const layer$79 = Layer.effect(AuthSessionRepository, make$89);
13114
+ const layer$79 = Layer.effect(AuthSessionRepository, make$90);
12997
13115
  //#endregion
12998
13116
  //#region src/auth/ServerSecretStore.ts
12999
13117
  const secretStoreErrorContext = {
@@ -13060,7 +13178,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
13060
13178
  const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
13061
13179
  const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
13062
13180
  var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
13063
- const make$88 = Effect.gen(function* () {
13181
+ const make$89 = Effect.gen(function* () {
13064
13182
  const crypto = yield* Crypto.Crypto;
13065
13183
  const fileSystem = yield* FileSystem.FileSystem;
13066
13184
  const path = yield* Path.Path;
@@ -13136,7 +13254,7 @@ const make$88 = Effect.gen(function* () {
13136
13254
  remove
13137
13255
  });
13138
13256
  });
13139
- const layer$78 = Layer.effect(ServerSecretStore, make$88);
13257
+ const layer$78 = Layer.effect(ServerSecretStore, make$89);
13140
13258
  //#endregion
13141
13259
  //#region src/auth/SessionStore.ts
13142
13260
  var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
@@ -13374,7 +13492,7 @@ function toAuthClientSession(input) {
13374
13492
  current: false
13375
13493
  };
13376
13494
  }
13377
- const make$87 = Effect.gen(function* () {
13495
+ const make$88 = Effect.gen(function* () {
13378
13496
  const crypto = yield* Crypto.Crypto;
13379
13497
  const serverConfig = yield* ServerConfig$1;
13380
13498
  const secretStore = yield* ServerSecretStore;
@@ -13688,7 +13806,7 @@ const make$87 = Effect.gen(function* () {
13688
13806
  markDisconnected
13689
13807
  });
13690
13808
  });
13691
- 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));
13692
13810
  //#endregion
13693
13811
  //#region src/persistence/AuthPairingLinks.ts
13694
13812
  const AuthPairingLinkRecord = Schema$1.Struct({
@@ -13749,7 +13867,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
13749
13867
  cause
13750
13868
  });
13751
13869
  }
13752
- const make$86 = Effect.gen(function* () {
13870
+ const make$87 = Effect.gen(function* () {
13753
13871
  const sql = yield* SqlClient.SqlClient;
13754
13872
  const createPairingLinkRow = SqlSchema.void({
13755
13873
  Request: CreateAuthPairingLinkInput,
@@ -13884,7 +14002,7 @@ const make$86 = Effect.gen(function* () {
13884
14002
  getByCredential
13885
14003
  };
13886
14004
  });
13887
- const layer$76 = Layer.effect(AuthPairingLinkRepository, make$86);
14005
+ const layer$76 = Layer.effect(AuthPairingLinkRepository, make$87);
13888
14006
  //#endregion
13889
14007
  //#region src/auth/PairingGrantStore.ts
13890
14008
  var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
@@ -13979,7 +14097,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
13979
14097
  const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
13980
14098
  const PAIRING_TOKEN_LENGTH = 12;
13981
14099
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
13982
- const make$85 = Effect.gen(function* () {
14100
+ const make$86 = Effect.gen(function* () {
13983
14101
  const crypto = yield* Crypto.Crypto;
13984
14102
  const config = yield* ServerConfig$1;
13985
14103
  const pairingLinks = yield* AuthPairingLinkRepository;
@@ -14177,7 +14295,7 @@ const make$85 = Effect.gen(function* () {
14177
14295
  consume
14178
14296
  });
14179
14297
  });
14180
- 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));
14181
14299
  //#endregion
14182
14300
  //#region src/persistence/DatabaseSnapshot.ts
14183
14301
  /**
@@ -16123,6 +16241,31 @@ var _054_ProjectionThreadScheduledTasks_default = Effect.gen(function* () {
16123
16241
  `;
16124
16242
  });
16125
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
16126
16269
  //#region src/persistence/Migrations.ts
16127
16270
  /**
16128
16271
  * MigrationsLive - Migration runner with inline loader
@@ -16413,6 +16556,11 @@ const migrationEntries = [
16413
16556
  54,
16414
16557
  "ProjectionThreadScheduledTasks",
16415
16558
  _054_ProjectionThreadScheduledTasks_default
16559
+ ],
16560
+ [
16561
+ 55,
16562
+ "ThreadCompletionLifecycle",
16563
+ _055_ThreadCompletionLifecycle_default
16416
16564
  ]
16417
16565
  ];
16418
16566
  const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -17008,12 +17156,42 @@ function dropStaleContextWindowActivities(activities) {
17008
17156
  return [index === breakdownIndex ? activity : withoutContextWindowBreakdown$1(activity)];
17009
17157
  });
17010
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
+ }
17011
17189
  function projectThreadDetailSnapshot(snapshot) {
17012
17190
  return {
17013
17191
  ...snapshot,
17014
17192
  thread: {
17015
17193
  ...snapshot.thread,
17016
- activities: dropStaleContextWindowActivities(snapshot.thread.activities).map(projectActivityPayload)
17194
+ activities: dropSupersededToolUpdatedActivities(dropStaleContextWindowActivities(snapshot.thread.activities).map(projectActivityPayload))
17017
17195
  }
17018
17196
  };
17019
17197
  }
@@ -17411,7 +17589,7 @@ function parseBearerToken(request) {
17411
17589
  const token = header.slice(7).trim();
17412
17590
  return token.length > 0 ? token : null;
17413
17591
  }
17414
- const make$84 = Effect.gen(function* () {
17592
+ const make$85 = Effect.gen(function* () {
17415
17593
  const policy = yield* EnvironmentAuthPolicy;
17416
17594
  const bootstrapCredentials = yield* PairingGrantStore;
17417
17595
  const sessions = yield* SessionStore;
@@ -17606,7 +17784,7 @@ const make$84 = Effect.gen(function* () {
17606
17784
  issueStartupPairingUrl
17607
17785
  });
17608
17786
  });
17609
- 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));
17610
17788
  const storageLayer = Layer.mergeAll(layer$78, layerConfig);
17611
17789
  const runtimeLayer = layer$74.pipe(Layer.provideMerge(storageLayer));
17612
17790
  //#endregion
@@ -19444,7 +19622,7 @@ const DEFAULT_LIMITS = {
19444
19622
  windowMillis: FAILURE_WINDOW_MS,
19445
19623
  blockMillis: BLOCK_DURATION_MS
19446
19624
  };
19447
- const make$83 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
19625
+ const make$84 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
19448
19626
  const state = yield* Ref.make(initialThrottleState);
19449
19627
  return HubAuthThrottle.of({
19450
19628
  shouldRefuse: Effect.gen(function* () {
@@ -19458,7 +19636,7 @@ const make$83 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LI
19458
19636
  })
19459
19637
  });
19460
19638
  });
19461
- const layer$73 = Layer.effect(HubAuthThrottle, make$83());
19639
+ const layer$73 = Layer.effect(HubAuthThrottle, make$84());
19462
19640
  //#endregion
19463
19641
  //#region src/hub/HubAuth.ts
19464
19642
  /**
@@ -21178,7 +21356,7 @@ function stripDefaultServerSettings(current, defaults) {
21178
21356
  }
21179
21357
  return Object.is(current, defaults) ? void 0 : current;
21180
21358
  }
21181
- const make$82 = Effect.gen(function* () {
21359
+ const make$83 = Effect.gen(function* () {
21182
21360
  const { settingsPath } = yield* ServerConfig$1;
21183
21361
  const fs = yield* FileSystem.FileSystem;
21184
21362
  const pathService = yield* Path.Path;
@@ -21399,7 +21577,7 @@ const make$82 = Effect.gen(function* () {
21399
21577
  }
21400
21578
  };
21401
21579
  });
21402
- const layer$71 = Layer.effect(ServerSettingsService, make$82);
21580
+ const layer$71 = Layer.effect(ServerSettingsService, make$83);
21403
21581
  //#endregion
21404
21582
  //#region src/pathExpansion.ts
21405
21583
  /**
@@ -21776,7 +21954,7 @@ function claudeEntryFromRegistration(registration) {
21776
21954
  };
21777
21955
  }
21778
21956
  var ClaudeMcpFiles = class extends Context.Service()("@p4code/cli/mcp/ClaudeMcpFiles") {};
21779
- const make$81 = Effect.gen(function* () {
21957
+ const make$82 = Effect.gen(function* () {
21780
21958
  const fileSystem = yield* FileSystem.FileSystem;
21781
21959
  const path = yield* Path.Path;
21782
21960
  const services = yield* Effect.context();
@@ -21843,7 +22021,7 @@ const make$81 = Effect.gen(function* () {
21843
22021
  removeProject: (projectDir, name) => removeAt(Effect.succeed(projectFile(projectDir)))(name)
21844
22022
  };
21845
22023
  });
21846
- const layer$70 = Layer.effect(ClaudeMcpFiles, make$81);
22024
+ const layer$70 = Layer.effect(ClaudeMcpFiles, make$82);
21847
22025
  Layer.succeed(ClaudeMcpFiles, {
21848
22026
  readUser: Effect.succeed([]),
21849
22027
  readUserAt: () => Effect.succeed([]),
@@ -21981,7 +22159,7 @@ const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationRe
21981
22159
  const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
21982
22160
  var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
21983
22161
  const registryError = (detail) => new McpRegistryError({ detail });
21984
- const make$80 = Effect.gen(function* () {
22162
+ const make$81 = Effect.gen(function* () {
21985
22163
  const config = yield* ServerConfig$1;
21986
22164
  const secrets = yield* ServerSecretStore;
21987
22165
  const http = yield* HttpClient.HttpClient;
@@ -22301,7 +22479,7 @@ const make$80 = Effect.gen(function* () {
22301
22479
  accessTokenFor
22302
22480
  };
22303
22481
  });
22304
- const layer$69 = Layer.effect(McpOAuth, make$80);
22482
+ const layer$69 = Layer.effect(McpOAuth, make$81);
22305
22483
  Layer.succeed(McpOAuth, {
22306
22484
  statusFor: () => Effect.succeed(Option.none()),
22307
22485
  begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
@@ -22319,7 +22497,7 @@ const decodeRegistration$1 = Schema$1.decodeUnknownExit(RegistrationFromJson$1);
22319
22497
  const encodeRegistration = Schema$1.encodeSync(RegistrationFromJson$1);
22320
22498
  var McpRegistry = class extends Context.Service()("@p4code/cli/mcp/McpRegistry") {};
22321
22499
  const slotsOf = (registration) => registration.secrets ?? [];
22322
- const make$79 = Effect.gen(function* () {
22500
+ const make$80 = Effect.gen(function* () {
22323
22501
  const config = yield* ServerConfig$1;
22324
22502
  const secrets = yield* ServerSecretStore;
22325
22503
  const oauth = yield* McpOAuth;
@@ -22493,7 +22671,7 @@ const make$79 = Effect.gen(function* () {
22493
22671
  resolveForSessionAtClaudeUserConfigPath
22494
22672
  };
22495
22673
  });
22496
- const layer$68 = Layer.effect(McpRegistry, make$79);
22674
+ const layer$68 = Layer.effect(McpRegistry, make$80);
22497
22675
  //#endregion
22498
22676
  //#region src/sync/skillDirectory.ts
22499
22677
  /**
@@ -22872,7 +23050,7 @@ const formatHubLink = (input) => encodeStoredHubLink({
22872
23050
  shareMode: input.shareMode
22873
23051
  });
22874
23052
  const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
22875
- const make$78 = Effect.fn("HubLink.make")(function* (environment) {
23053
+ const make$79 = Effect.fn("HubLink.make")(function* (environment) {
22876
23054
  const secrets = yield* ServerSecretStore;
22877
23055
  const env = environment ?? process.env;
22878
23056
  const fromEnv = fromEnvironment(env);
@@ -22938,7 +23116,7 @@ const make$78 = Effect.fn("HubLink.make")(function* (environment) {
22938
23116
  })
22939
23117
  };
22940
23118
  });
22941
- const layer$67 = Layer.effect(HubLink, make$78());
23119
+ const layer$67 = Layer.effect(HubLink, make$79());
22942
23120
  //#endregion
22943
23121
  //#region src/sync/HubAssetClient.ts
22944
23122
  /**
@@ -22971,7 +23149,7 @@ const decodeAssetListPage = Schema$1.decodeUnknownEffect(AssetListPage);
22971
23149
  const decodeConflictBody$1 = Schema$1.decodeUnknownEffect(ConflictBody$1);
22972
23150
  const decodeAsset = Schema$1.decodeUnknownEffect(AgentAsset);
22973
23151
  var HubAssetClient = class extends Context.Service()("@p4code/cli/sync/HubAssetClient") {};
22974
- const make$77 = Effect.gen(function* () {
23152
+ const make$78 = Effect.gen(function* () {
22975
23153
  const http = yield* HttpClient.HttpClient;
22976
23154
  const link = yield* HubLink;
22977
23155
  const requireSettings = Effect.gen(function* () {
@@ -23053,7 +23231,7 @@ const make$77 = Effect.gen(function* () {
23053
23231
  remove
23054
23232
  };
23055
23233
  });
23056
- const layer$66 = Layer.effect(HubAssetClient, make$77);
23234
+ const layer$66 = Layer.effect(HubAssetClient, make$78);
23057
23235
  //#endregion
23058
23236
  //#region src/sync/mcpRegistrationFiles.ts
23059
23237
  /**
@@ -23659,7 +23837,7 @@ const EMPTY_REPORT = {
23659
23837
  unavailable: null
23660
23838
  };
23661
23839
  var AssetSync = class extends Context.Service()("@p4code/cli/sync/AssetSync") {};
23662
- const make$76 = Effect.gen(function* () {
23840
+ const make$77 = Effect.gen(function* () {
23663
23841
  const client = yield* HubAssetClient;
23664
23842
  const link = yield* HubLink;
23665
23843
  const settingsStore = yield* ServerSettingsService;
@@ -24369,7 +24547,7 @@ const make$76 = Effect.gen(function* () {
24369
24547
  removeLocal
24370
24548
  };
24371
24549
  });
24372
- const layer$65 = Layer.effect(AssetSync, make$76);
24550
+ const layer$65 = Layer.effect(AssetSync, make$77);
24373
24551
  //#endregion
24374
24552
  //#region src/provider/CompressPrompts.ts
24375
24553
  /**
@@ -25446,6 +25624,30 @@ function toPersistenceSqlOrDecodeError$4(sqlOperation, decodeOperation) {
25446
25624
  }
25447
25625
  const makeEventStore = Effect.gen(function* () {
25448
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")));
25449
25651
  const appendEventRow = SqlSchema.findOne({
25450
25652
  Request: AppendEventRequestSchema,
25451
25653
  Result: OrchestrationEventPersistedRowSchema,
@@ -25615,6 +25817,7 @@ const makeEventStore = Effect.gen(function* () {
25615
25817
  append,
25616
25818
  readByCommandId,
25617
25819
  readFromSequence,
25820
+ readReviewPage,
25618
25821
  readAll: () => readFromSequence(0, Number.MAX_SAFE_INTEGER)
25619
25822
  };
25620
25823
  });
@@ -25739,6 +25942,99 @@ function toProjectorDecodeError(eventType) {
25739
25942
  });
25740
25943
  }
25741
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
25742
26038
  //#region ../../packages/shared/src/path.ts
25743
26039
  function isWindowsDrivePath(value) {
25744
26040
  return /^[a-zA-Z]:([/\\]|$)/.test(value);
@@ -25817,6 +26113,74 @@ function requireThreadAbsent(input) {
25817
26113
  return Effect.fail(invariantError(input.command.type, `Thread '${input.threadId}' already exists and cannot be created twice.`));
25818
26114
  }
25819
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
25820
26184
  //#region src/attachmentPaths.ts
25821
26185
  function normalizeAttachmentRelativePath(rawRelativePath) {
25822
26186
  const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");
@@ -26232,6 +26596,10 @@ function createEmptyReadModel(nowIso) {
26232
26596
  function projectEvent(model, event) {
26233
26597
  const nextBase = {
26234
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),
26235
26603
  snapshotSequence: event.sequence,
26236
26604
  updatedAt: event.occurredAt
26237
26605
  };
@@ -26339,8 +26707,13 @@ function projectEvent(model, event) {
26339
26707
  latestTurn: null,
26340
26708
  createdAt: payload.createdAt,
26341
26709
  updatedAt: payload.updatedAt,
26710
+ lifecycle: "active",
26711
+ lifecycleReason: "created",
26712
+ lifecycleChangedAt: payload.createdAt,
26713
+ lastEngagedAt: payload.createdAt,
26714
+ doneAt: null,
26715
+ settledOverride: "active",
26342
26716
  archivedAt: null,
26343
- settledOverride: null,
26344
26717
  settledAt: null,
26345
26718
  snoozedUntil: null,
26346
26719
  snoozedAt: null,
@@ -26388,7 +26761,7 @@ function projectEvent(model, event) {
26388
26761
  case "thread.unsettled": return decodeForEvent(ThreadUnsettledPayload, event.payload, event.type, "payload").pipe(Effect.map((payload) => ({
26389
26762
  ...nextBase,
26390
26763
  threads: updateThread(nextBase.threads, payload.threadId, {
26391
- settledOverride: payload.reason === "user" ? "active" : null,
26764
+ settledOverride: "active",
26392
26765
  settledAt: null,
26393
26766
  updatedAt: payload.updatedAt
26394
26767
  })
@@ -26748,7 +27121,7 @@ const decideCommandSequence = Effect.fn("decideCommandSequence")(function* ({ co
26748
27121
  }
26749
27122
  return plannedEvents;
26750
27123
  });
26751
- const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(function* ({ command, readModel }) {
27124
+ const decideCommand = Effect.fn("decideCommand")(function* ({ command, readModel }) {
26752
27125
  switch (command.type) {
26753
27126
  case "project.create":
26754
27127
  yield* requireProjectAbsent({
@@ -27388,12 +27761,19 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27388
27761
  }
27389
27762
  return events;
27390
27763
  }
27764
+ case "thread.complete":
27765
+ case "thread.complete.auto":
27391
27766
  case "thread.settle": {
27392
27767
  const thread = yield* requireThreadNotArchived({
27393
27768
  readModel,
27394
27769
  command,
27395
27770
  threadId: command.threadId
27396
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
+ });
27397
27777
  if (thread.session?.status === "starting" || thread.session?.status === "running") return yield* new OrchestrationCommandInvariantError({
27398
27778
  commandType: command.type,
27399
27779
  detail: `thread ${command.threadId} has an active session and cannot be settled`
@@ -27402,7 +27782,6 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27402
27782
  commandType: command.type,
27403
27783
  detail: `thread ${command.threadId} has a pending approval or user-input request and cannot be settled`
27404
27784
  });
27405
- const occurredAt = yield* nowIso$8;
27406
27785
  if (threadHasQueuedTurnStart(thread, occurredAt)) return yield* new OrchestrationCommandInvariantError({
27407
27786
  commandType: command.type,
27408
27787
  detail: `thread ${command.threadId} has a queued turn start and cannot be settled`
@@ -27437,6 +27816,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27437
27816
  }
27438
27817
  }];
27439
27818
  }
27819
+ case "thread.reopen":
27440
27820
  case "thread.unsettle": {
27441
27821
  const thread = yield* requireThreadNotArchived({
27442
27822
  readModel,
@@ -27455,7 +27835,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27455
27835
  type: "thread.unsettled",
27456
27836
  payload: {
27457
27837
  threadId: command.threadId,
27458
- reason: command.reason,
27838
+ reason: "user",
27459
27839
  updatedAt: alreadyPinnedActive ? thread.updatedAt : occurredAt
27460
27840
  }
27461
27841
  };
@@ -27812,7 +28192,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
27812
28192
  }
27813
28193
  };
27814
28194
  const lifecycleResetEvents = [];
27815
- if (targetThread.settledOverride !== null) lifecycleResetEvents.push({
28195
+ if (targetThread.settledOverride === "settled") lifecycleResetEvents.push({
27816
28196
  ...yield* withEventBase({
27817
28197
  aggregateKind: "thread",
27818
28198
  aggregateId: command.threadId,
@@ -28063,7 +28443,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
28063
28443
  }
28064
28444
  };
28065
28445
  const isSessionActivity = command.session.status === "starting" || command.session.status === "running";
28066
- if (thread.settledOverride === null || !isSessionActivity) return sessionSetEvent;
28446
+ if (thread.settledOverride !== "settled" || !isSessionActivity) return sessionSetEvent;
28067
28447
  return [{
28068
28448
  ...yield* withEventBase({
28069
28449
  aggregateKind: "thread",
@@ -28214,7 +28594,7 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
28214
28594
  }
28215
28595
  };
28216
28596
  const wakesSettledThread = command.activity.kind === "approval.requested" || command.activity.kind === "user-input.requested";
28217
- if (thread.settledOverride === null || !wakesSettledThread) return activityAppendedEvent;
28597
+ if (thread.settledOverride !== "settled" || !wakesSettledThread) return activityAppendedEvent;
28218
28598
  return [{
28219
28599
  ...yield* withEventBase({
28220
28600
  aggregateKind: "thread",
@@ -28239,6 +28619,74 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
28239
28619
  }
28240
28620
  }
28241
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
+ });
28242
28690
  //#endregion
28243
28691
  //#region src/orchestration/Services/ProjectionPipeline.ts
28244
28692
  /**
@@ -28279,6 +28727,7 @@ const makeOrchestrationEngine = Effect.gen(function* () {
28279
28727
  const projectionPipeline = yield* OrchestrationProjectionPipeline;
28280
28728
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
28281
28729
  const crypto = yield* Crypto.Crypto;
28730
+ const backgroundLiveness = yield* ThreadBackgroundLivenessService;
28282
28731
  const nowIso = Effect.map(DateTime.now, DateTime.formatIso);
28283
28732
  let commandReadModel = createEmptyReadModel(yield* nowIso);
28284
28733
  const commandQueue = yield* Queue.unbounded();
@@ -28326,7 +28775,8 @@ const makeOrchestrationEngine = Effect.gen(function* () {
28326
28775
  }
28327
28776
  const eventBase = yield* decideOrchestrationCommand({
28328
28777
  command: envelope.command,
28329
- readModel: commandReadModel
28778
+ readModel: commandReadModel,
28779
+ hasLiveBackgroundWork: "threadId" in envelope.command && backgroundLiveness.getThreadBackgroundLiveness(envelope.command.threadId) !== null
28330
28780
  }).pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.mapError((cause) => isOrchestrationCommandInvariantError(cause) ? cause : new OrchestrationCommandInvariantError({
28331
28781
  commandType: envelope.command.type,
28332
28782
  detail: "Failed to generate an event identifier.",
@@ -28428,7 +28878,7 @@ const makeOrchestrationEngine = Effect.gen(function* () {
28428
28878
  latestSequence: Effect.sync(() => commandReadModel.snapshotSequence)
28429
28879
  };
28430
28880
  });
28431
- const OrchestrationEngineLive = Layer.effect(OrchestrationEngineService, makeOrchestrationEngine);
28881
+ const OrchestrationEngineLive = Layer.effect(OrchestrationEngineService, makeOrchestrationEngine).pipe(Layer.provide(layer$64));
28432
28882
  //#endregion
28433
28883
  //#region src/persistence/Services/ProjectionPendingApprovals.ts
28434
28884
  /**
@@ -28694,6 +29144,7 @@ var ProjectionTurnRepository = class extends Context.Service()("@p4code/cli/pers
28694
29144
  * @module ProjectionThreadRepository
28695
29145
  */
28696
29146
  const ProjectionThread = Schema$1.Struct({
29147
+ ...ThreadCompletionFields,
28697
29148
  threadId: ThreadId,
28698
29149
  projectId: ProjectId,
28699
29150
  title: Schema$1.String,
@@ -29696,6 +30147,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29696
30147
  archived_at,
29697
30148
  settled_override,
29698
30149
  settled_at,
30150
+ lifecycle,
30151
+ lifecycle_reason,
30152
+ lifecycle_changed_at,
30153
+ last_engaged_at,
29699
30154
  snoozed_until,
29700
30155
  snoozed_at,
29701
30156
  pinned_at,
@@ -29726,6 +30181,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29726
30181
  ${row.archivedAt},
29727
30182
  ${row.settledOverride},
29728
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},
29729
30188
  ${row.snoozedUntil},
29730
30189
  ${row.snoozedAt},
29731
30190
  ${row.pinnedAt},
@@ -29756,6 +30215,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29756
30215
  archived_at = excluded.archived_at,
29757
30216
  settled_override = excluded.settled_override,
29758
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,
29759
30222
  snoozed_until = excluded.snoozed_until,
29760
30223
  snoozed_at = excluded.snoozed_at,
29761
30224
  pinned_at = excluded.pinned_at,
@@ -29791,6 +30254,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29791
30254
  archived_at AS "archivedAt",
29792
30255
  settled_override AS "settledOverride",
29793
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",
29794
30261
  snoozed_until AS "snoozedUntil",
29795
30262
  snoozed_at AS "snoozedAt",
29796
30263
  pinned_at AS "pinnedAt",
@@ -29828,6 +30295,10 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29828
30295
  archived_at AS "archivedAt",
29829
30296
  settled_override AS "settledOverride",
29830
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",
29831
30302
  snoozed_until AS "snoozedUntil",
29832
30303
  snoozed_at AS "snoozedAt",
29833
30304
  pinned_at AS "pinnedAt",
@@ -29865,6 +30336,14 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29865
30336
  const ProjectionThreadRepositoryLive = Layer.effect(ProjectionThreadRepository, makeProjectionThreadRepository);
29866
30337
  //#endregion
29867
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
+ */
29868
30347
  const ORCHESTRATION_PROJECTOR_NAMES = {
29869
30348
  projects: "projection.projects",
29870
30349
  threads: "projection.threads",
@@ -29910,14 +30389,6 @@ function extractActivityTaskId(payload) {
29910
30389
  const taskId = payload.taskId;
29911
30390
  return typeof taskId === "string" && taskId.length > 0 ? taskId : null;
29912
30391
  }
29913
- /**
29914
- * Tasks the agent started that never reported a terminal status. A task that
29915
- * outlives its turn is the case worth surfacing: the session goes back to idle
29916
- * while the task keeps running, so nothing else in the shell shows the work.
29917
- *
29918
- * Counted rather than flagged so a lost completion can only strand one task,
29919
- * and read as a boolean by the shell.
29920
- */
29921
30392
  function deriveBackgroundTaskCountFromActivities(activities) {
29922
30393
  const openTaskIds = /* @__PURE__ */ new Set();
29923
30394
  const ordered = [...activities].toSorted((left, right) => left.createdAt.localeCompare(right.createdAt) || left.activityId.localeCompare(right.activityId));
@@ -30260,7 +30731,12 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30260
30731
  createdAt: event.payload.createdAt,
30261
30732
  updatedAt: event.payload.updatedAt,
30262
30733
  archivedAt: null,
30263
- settledOverride: null,
30734
+ lifecycle: "active",
30735
+ lifecycleReason: "created",
30736
+ lifecycleChangedAt: event.payload.createdAt,
30737
+ lastEngagedAt: event.payload.createdAt,
30738
+ doneAt: null,
30739
+ settledOverride: "active",
30264
30740
  settledAt: null,
30265
30741
  snoozedUntil: null,
30266
30742
  snoozedAt: null,
@@ -30280,6 +30756,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30280
30756
  if (Option.isNone(existingRow)) return;
30281
30757
  yield* projectionThreadRepository.upsert({
30282
30758
  ...existingRow.value,
30759
+ ...completionAfterEvent(existingRow.value, event),
30283
30760
  archivedAt: event.payload.archivedAt,
30284
30761
  updatedAt: event.payload.updatedAt
30285
30762
  });
@@ -30290,6 +30767,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30290
30767
  if (Option.isNone(existingRow)) return;
30291
30768
  yield* projectionThreadRepository.upsert({
30292
30769
  ...existingRow.value,
30770
+ ...completionAfterEvent(existingRow.value, event),
30293
30771
  archivedAt: null,
30294
30772
  updatedAt: event.payload.updatedAt
30295
30773
  });
@@ -30300,6 +30778,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30300
30778
  if (Option.isNone(existingRow)) return;
30301
30779
  yield* projectionThreadRepository.upsert({
30302
30780
  ...existingRow.value,
30781
+ ...completionAfterEvent(existingRow.value, event),
30303
30782
  settledOverride: "settled",
30304
30783
  settledAt: event.payload.settledAt,
30305
30784
  updatedAt: event.payload.updatedAt
@@ -30311,7 +30790,8 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30311
30790
  if (Option.isNone(existingRow)) return;
30312
30791
  yield* projectionThreadRepository.upsert({
30313
30792
  ...existingRow.value,
30314
- settledOverride: event.payload.reason === "user" ? "active" : null,
30793
+ ...completionAfterEvent(existingRow.value, event),
30794
+ settledOverride: "active",
30315
30795
  settledAt: null,
30316
30796
  updatedAt: event.payload.updatedAt
30317
30797
  });
@@ -30322,6 +30802,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30322
30802
  if (Option.isNone(existingRow)) return;
30323
30803
  yield* projectionThreadRepository.upsert({
30324
30804
  ...existingRow.value,
30805
+ ...completionAfterEvent(existingRow.value, event),
30325
30806
  snoozedUntil: event.payload.snoozedUntil,
30326
30807
  snoozedAt: event.payload.snoozedAt,
30327
30808
  updatedAt: event.payload.updatedAt
@@ -30333,6 +30814,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30333
30814
  if (Option.isNone(existingRow)) return;
30334
30815
  yield* projectionThreadRepository.upsert({
30335
30816
  ...existingRow.value,
30817
+ ...completionAfterEvent(existingRow.value, event),
30336
30818
  snoozedUntil: null,
30337
30819
  snoozedAt: null,
30338
30820
  updatedAt: event.payload.updatedAt
@@ -30344,6 +30826,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30344
30826
  if (Option.isNone(existingRow)) return;
30345
30827
  yield* projectionThreadRepository.upsert({
30346
30828
  ...existingRow.value,
30829
+ ...completionAfterEvent(existingRow.value, event),
30347
30830
  pinnedAt: event.payload.pinnedAt,
30348
30831
  ...event.payload.pinOrderKey === void 0 ? {} : { pinOrderKey: event.payload.pinOrderKey },
30349
30832
  updatedAt: event.payload.updatedAt
@@ -30355,6 +30838,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30355
30838
  if (Option.isNone(existingRow)) return;
30356
30839
  yield* projectionThreadRepository.upsert({
30357
30840
  ...existingRow.value,
30841
+ ...completionAfterEvent(existingRow.value, event),
30358
30842
  pinnedAt: null,
30359
30843
  pinOrderKey: null,
30360
30844
  updatedAt: event.payload.updatedAt
@@ -30366,6 +30850,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30366
30850
  if (Option.isNone(existingRow)) return;
30367
30851
  yield* projectionThreadRepository.upsert({
30368
30852
  ...existingRow.value,
30853
+ ...completionAfterEvent(existingRow.value, event),
30369
30854
  pinOrderKey: event.payload.pinOrderKey,
30370
30855
  updatedAt: event.payload.updatedAt
30371
30856
  });
@@ -30376,6 +30861,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30376
30861
  if (Option.isNone(existingRow)) return;
30377
30862
  yield* projectionThreadRepository.upsert({
30378
30863
  ...existingRow.value,
30864
+ ...completionAfterEvent(existingRow.value, event),
30379
30865
  ...event.payload.title !== void 0 ? { title: event.payload.title } : {},
30380
30866
  ...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
30381
30867
  ...event.payload.branch !== void 0 ? { branch: event.payload.branch } : {},
@@ -30390,6 +30876,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30390
30876
  if (Option.isNone(existingRow)) return;
30391
30877
  yield* projectionThreadRepository.upsert({
30392
30878
  ...existingRow.value,
30879
+ ...completionAfterEvent(existingRow.value, event),
30393
30880
  runtimeMode: event.payload.runtimeMode,
30394
30881
  updatedAt: event.payload.updatedAt
30395
30882
  });
@@ -30400,6 +30887,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30400
30887
  if (Option.isNone(existingRow)) return;
30401
30888
  yield* projectionThreadRepository.upsert({
30402
30889
  ...existingRow.value,
30890
+ ...completionAfterEvent(existingRow.value, event),
30403
30891
  interactionMode: event.payload.interactionMode,
30404
30892
  updatedAt: event.payload.updatedAt
30405
30893
  });
@@ -30410,6 +30898,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30410
30898
  if (Option.isNone(existingRow)) return;
30411
30899
  yield* projectionThreadRepository.upsert({
30412
30900
  ...existingRow.value,
30901
+ ...completionAfterEvent(existingRow.value, event),
30413
30902
  compressMode: event.payload.compressMode,
30414
30903
  updatedAt: event.payload.updatedAt
30415
30904
  });
@@ -30420,6 +30909,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30420
30909
  if (Option.isNone(existingRow)) return;
30421
30910
  yield* projectionThreadRepository.upsert({
30422
30911
  ...existingRow.value,
30912
+ ...completionAfterEvent(existingRow.value, event),
30423
30913
  unpromptedSubagents: event.payload.unpromptedSubagents ? 1 : 0,
30424
30914
  updatedAt: event.payload.updatedAt
30425
30915
  });
@@ -30431,6 +30921,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30431
30921
  if (Option.isNone(existingRow)) return;
30432
30922
  yield* projectionThreadRepository.upsert({
30433
30923
  ...existingRow.value,
30924
+ ...completionAfterEvent(existingRow.value, event),
30434
30925
  deletedAt: event.payload.deletedAt,
30435
30926
  updatedAt: event.payload.deletedAt
30436
30927
  });
@@ -30445,6 +30936,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30445
30936
  if (Option.isNone(existingRow)) return;
30446
30937
  yield* projectionThreadRepository.upsert({
30447
30938
  ...existingRow.value,
30939
+ ...completionAfterEvent(existingRow.value, event),
30448
30940
  updatedAt: event.occurredAt
30449
30941
  });
30450
30942
  yield* refreshThreadShellSummary(event.payload.threadId);
@@ -30455,6 +30947,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30455
30947
  if (Option.isNone(existingRow)) return;
30456
30948
  yield* projectionThreadRepository.upsert({
30457
30949
  ...existingRow.value,
30950
+ ...completionAfterEvent(existingRow.value, event),
30458
30951
  latestTurnId: event.payload.session.activeTurnId,
30459
30952
  updatedAt: event.occurredAt
30460
30953
  });
@@ -30466,6 +30959,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30466
30959
  if (Option.isNone(existingRow)) return;
30467
30960
  yield* projectionThreadRepository.upsert({
30468
30961
  ...existingRow.value,
30962
+ ...completionAfterEvent(existingRow.value, event),
30469
30963
  latestTurnId: event.payload.turnId,
30470
30964
  updatedAt: event.occurredAt
30471
30965
  });
@@ -30488,6 +30982,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30488
30982
  }
30489
30983
  yield* projectionThreadRepository.upsert({
30490
30984
  ...existingRow.value,
30985
+ ...completionAfterEvent(existingRow.value, event),
30491
30986
  latestTurnId,
30492
30987
  updatedAt: event.occurredAt
30493
30988
  });
@@ -31014,84 +31509,6 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
31014
31509
  });
31015
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));
31016
31511
  //#endregion
31017
- //#region src/orchestration/ThreadBackgroundLiveness.ts
31018
- /**
31019
- * ThreadBackgroundLivenessService - in-memory per-thread background liveness
31020
- * for the sidebar status pill.
31021
- *
31022
- * The turn can settle while native background work runs on (subagent fleets,
31023
- * workflow runs, Monitor watch loops); the shell previously showed nothing.
31024
- * Ingestion records task lifecycle transitions and the shell query reads the
31025
- * derived state at mapping time — no persistence, no migration. After a
31026
- * server restart the registry is empty until new task events arrive, which
31027
- * matches reality: orphaned background work is not live.
31028
- *
31029
- * "monitoring" is reserved for watch loops (monitor tasks and background
31030
- * shells) when they are the ONLY live work; any agent work presents as
31031
- * "working".
31032
- *
31033
- * @module ThreadBackgroundLivenessService
31034
- */
31035
- const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
31036
- "completed",
31037
- "failed",
31038
- "stopped",
31039
- "cancelled",
31040
- "interrupted"
31041
- ]);
31042
- var ThreadBackgroundLivenessService = class extends Context.Service()("@p4code/cli/orchestration/ThreadBackgroundLiveness/ThreadBackgroundLivenessService") {};
31043
- function make$75() {
31044
- const stateByThreadId = /* @__PURE__ */ new Map();
31045
- const stateFor = (threadId) => {
31046
- const existing = stateByThreadId.get(threadId);
31047
- if (existing) return existing;
31048
- const created = {
31049
- agents: /* @__PURE__ */ new Set(),
31050
- monitors: /* @__PURE__ */ new Set()
31051
- };
31052
- stateByThreadId.set(threadId, created);
31053
- return created;
31054
- };
31055
- const drop = (threadId, taskId) => {
31056
- const state = stateByThreadId.get(threadId);
31057
- if (!state) return;
31058
- state.agents.delete(taskId);
31059
- state.monitors.delete(taskId);
31060
- if (state.agents.size === 0 && state.monitors.size === 0) stateByThreadId.delete(threadId);
31061
- };
31062
- return {
31063
- recordTaskLiveness: (input) => {
31064
- const taskType = input.taskType;
31065
- if (taskType !== void 0 && INERT_TASK_TYPES.has(taskType)) {
31066
- drop(input.threadId, input.taskId);
31067
- return;
31068
- }
31069
- if (input.agentId !== void 0 && (taskType === void 0 || MONITOR_TASK_TYPES.has(taskType))) {
31070
- drop(input.threadId, input.taskId);
31071
- return;
31072
- }
31073
- if (input.kind === "completed" || input.status === "idle" || input.status !== void 0 && TERMINAL_STATUSES.has(input.status)) {
31074
- drop(input.threadId, input.taskId);
31075
- return;
31076
- }
31077
- drop(input.threadId, input.taskId);
31078
- const state = stateFor(input.threadId);
31079
- (taskType !== void 0 && MONITOR_TASK_TYPES.has(taskType) ? state.monitors : state.agents).add(input.taskId);
31080
- },
31081
- clearThreadLiveness: (threadId) => {
31082
- stateByThreadId.delete(threadId);
31083
- },
31084
- getThreadBackgroundLiveness: (threadId) => {
31085
- const state = stateByThreadId.get(threadId);
31086
- if (!state) return null;
31087
- if (state.agents.size > 0) return "working";
31088
- if (state.monitors.size > 0) return "monitoring";
31089
- return null;
31090
- }
31091
- };
31092
- }
31093
- const layer$64 = Layer.effect(ThreadBackgroundLivenessService, Effect.sync(make$75));
31094
- //#endregion
31095
31512
  //#region src/persistence/Services/ProjectionCheckpoints.ts
31096
31513
  /**
31097
31514
  * ProjectionCheckpointRepository - Projection repository interface for checkpoints.
@@ -31659,12 +32076,12 @@ const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spaw
31659
32076
  stderrInvalidUtf8: stderr.invalidUtf8
31660
32077
  };
31661
32078
  });
31662
- const make$74 = Effect.fn("ProcessRunner.make")(function* () {
32079
+ const make$75 = Effect.fn("ProcessRunner.make")(function* () {
31663
32080
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
31664
32081
  const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
31665
32082
  return ProcessRunner.of({ run });
31666
32083
  });
31667
- const layer$63 = Layer.effect(ProcessRunner, make$74());
32084
+ const layer$63 = Layer.effect(ProcessRunner, make$75());
31668
32085
  //#endregion
31669
32086
  //#region src/project/RepositoryIdentityResolver.ts
31670
32087
  const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
@@ -31755,7 +32172,7 @@ const resolveRepositoryIdentityFromCacheKey = Effect.fn("RepositoryIdentityResol
31755
32172
  rootPath: cacheKey
31756
32173
  }) : null;
31757
32174
  });
31758
- const make$73 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
32175
+ const make$74 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
31759
32176
  const processRunner = yield* ProcessRunner;
31760
32177
  const repositoryIdentityCache = yield* Cache.makeWith((cacheKey) => resolveRepositoryIdentityFromCacheKey(cacheKey).pipe(Effect.provideService(ProcessRunner, processRunner)), {
31761
32178
  capacity: options.cacheCapacity ?? DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY,
@@ -31770,7 +32187,7 @@ const make$73 = Effect.fn("RepositoryIdentityResolver.make")(function* (options
31770
32187
  });
31771
32188
  return RepositoryIdentityResolver.of({ resolve });
31772
32189
  });
31773
- 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));
31774
32191
  //#endregion
31775
32192
  //#region src/orchestration/Layers/ProjectionSnapshotQuery.ts
31776
32193
  const decodeReadModel = Schema$1.decodeUnknownEffect(OrchestrationReadModel);
@@ -32126,6 +32543,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32126
32543
  archived_at AS "archivedAt",
32127
32544
  settled_override AS "settledOverride",
32128
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",
32129
32550
  snoozed_until AS "snoozedUntil",
32130
32551
  snoozed_at AS "snoozedAt",
32131
32552
  pinned_at AS "pinnedAt",
@@ -32163,6 +32584,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32163
32584
  archived_at AS "archivedAt",
32164
32585
  settled_override AS "settledOverride",
32165
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",
32166
32591
  snoozed_until AS "snoozedUntil",
32167
32592
  snoozed_at AS "snoozedAt",
32168
32593
  pinned_at AS "pinnedAt",
@@ -32202,6 +32627,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32202
32627
  archived_at AS "archivedAt",
32203
32628
  settled_override AS "settledOverride",
32204
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",
32205
32634
  snoozed_until AS "snoozedUntil",
32206
32635
  snoozed_at AS "snoozedAt",
32207
32636
  pinned_at AS "pinnedAt",
@@ -32666,6 +33095,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32666
33095
  archived_at AS "archivedAt",
32667
33096
  settled_override AS "settledOverride",
32668
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",
32669
33102
  snoozed_until AS "snoozedUntil",
32670
33103
  snoozed_at AS "snoozedAt",
32671
33104
  pinned_at AS "pinnedAt",
@@ -33358,6 +33791,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33358
33791
  archivedAt: row.archivedAt,
33359
33792
  settledOverride: row.settledOverride,
33360
33793
  settledAt: row.settledAt,
33794
+ lifecycle: row.lifecycle,
33795
+ lifecycleReason: row.lifecycleReason,
33796
+ lifecycleChangedAt: row.lifecycleChangedAt,
33797
+ lastEngagedAt: row.lastEngagedAt,
33798
+ doneAt: row.settledAt,
33361
33799
  snoozedUntil: row.snoozedUntil,
33362
33800
  snoozedAt: row.snoozedAt,
33363
33801
  pinnedAt: row.pinnedAt,
@@ -33501,6 +33939,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33501
33939
  archivedAt: row.archivedAt,
33502
33940
  settledOverride: row.settledOverride,
33503
33941
  settledAt: row.settledAt,
33942
+ lifecycle: row.lifecycle,
33943
+ lifecycleReason: row.lifecycleReason,
33944
+ lifecycleChangedAt: row.lifecycleChangedAt,
33945
+ lastEngagedAt: row.lastEngagedAt,
33946
+ doneAt: row.settledAt,
33504
33947
  snoozedUntil: row.snoozedUntil,
33505
33948
  snoozedAt: row.snoozedAt,
33506
33949
  pinnedAt: row.pinnedAt,
@@ -33567,6 +34010,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33567
34010
  archivedAt: row.archivedAt,
33568
34011
  settledOverride: row.settledOverride,
33569
34012
  settledAt: row.settledAt,
34013
+ lifecycle: row.lifecycle,
34014
+ lifecycleReason: row.lifecycleReason,
34015
+ lifecycleChangedAt: row.lifecycleChangedAt,
34016
+ lastEngagedAt: row.lastEngagedAt,
34017
+ doneAt: row.settledAt,
33570
34018
  snoozedUntil: row.snoozedUntil,
33571
34019
  snoozedAt: row.snoozedAt,
33572
34020
  pinnedAt: row.pinnedAt,
@@ -33633,6 +34081,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33633
34081
  archivedAt: row.archivedAt,
33634
34082
  settledOverride: row.settledOverride,
33635
34083
  settledAt: row.settledAt,
34084
+ lifecycle: row.lifecycle,
34085
+ lifecycleReason: row.lifecycleReason,
34086
+ lifecycleChangedAt: row.lifecycleChangedAt,
34087
+ lastEngagedAt: row.lastEngagedAt,
34088
+ doneAt: row.settledAt,
33636
34089
  snoozedUntil: row.snoozedUntil,
33637
34090
  snoozedAt: row.snoozedAt,
33638
34091
  pinnedAt: row.pinnedAt,
@@ -33747,6 +34200,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33747
34200
  archivedAt: threadRow.value.archivedAt,
33748
34201
  settledOverride: threadRow.value.settledOverride,
33749
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,
33750
34208
  snoozedUntil: threadRow.value.snoozedUntil,
33751
34209
  snoozedAt: threadRow.value.snoozedAt,
33752
34210
  pinnedAt: threadRow.value.pinnedAt,
@@ -33814,6 +34272,11 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
33814
34272
  archivedAt: threadRow.value.archivedAt,
33815
34273
  settledOverride: threadRow.value.settledOverride,
33816
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,
33817
34280
  snoozedUntil: threadRow.value.snoozedUntil,
33818
34281
  snoozedAt: threadRow.value.snoozedAt,
33819
34282
  pinnedAt: threadRow.value.pinnedAt,
@@ -33939,9 +34402,185 @@ const OrchestrationProjectionSnapshotQueryLive = Layer.effect(ProjectionSnapshot
33939
34402
  //#region src/orchestration/Services/ThreadEventStream.ts
33940
34403
  var ThreadEventStreamService = class extends Context.Service()("@p4code/cli/orchestration/Services/ThreadEventStream/ThreadEventStreamService") {};
33941
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
33942
34579
  //#region src/orchestration/Layers/ThreadEventStream.ts
34580
+ const REVIEW_SOURCE_PAGE_SIZE = 100;
33943
34581
  const makeThreadEventStream = Effect.gen(function* () {
33944
34582
  const engine = yield* OrchestrationEngineService;
34583
+ const eventStore = yield* OrchestrationEventStore;
33945
34584
  const isThreadEvent = (threadId) => (event) => event.aggregateKind === "thread" && event.aggregateId === threadId;
33946
34585
  const watch = (input) => Stream.unwrap(Effect.gen(function* () {
33947
34586
  const matchesThread = isThreadEvent(input.threadId);
@@ -33983,9 +34622,38 @@ const makeThreadEventStream = Effect.gen(function* () {
33983
34622
  hasMore
33984
34623
  };
33985
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
+ });
33986
34653
  return ThreadEventStreamService.of({
33987
34654
  watch,
33988
- read
34655
+ read,
34656
+ readReview
33989
34657
  });
33990
34658
  });
33991
34659
  const ThreadEventStreamLive = Layer.effect(ThreadEventStreamService, makeThreadEventStream);
@@ -34002,7 +34670,7 @@ const OrchestrationProjectionPipelineLayerLive = OrchestrationProjectionPipeline
34002
34670
  const ThreadBackgroundLivenessLayerLive = layer$64;
34003
34671
  const OrchestrationInfrastructureLayerLive = Layer.mergeAll(OrchestrationProjectionSnapshotQueryLive, OrchestrationEventInfrastructureLayerLive, OrchestrationProjectionPipelineLayerLive, ThreadBackgroundLivenessLayerLive);
34004
34672
  const OrchestrationEngineLayerLive = OrchestrationEngineLive.pipe(Layer.provide(OrchestrationInfrastructureLayerLive));
34005
- 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)));
34006
34674
  //#endregion
34007
34675
  //#region ../../packages/shared/src/keybindings.ts
34008
34676
  const DEFAULT_KEYBINDINGS = [
@@ -34474,7 +35142,7 @@ function mergeWithDefaultKeybindings(custom) {
34474
35142
  * Keybindings - Service tag for keybinding configuration operations.
34475
35143
  */
34476
35144
  var Keybindings = class extends Context.Service()("@p4code/cli/keybindings") {};
34477
- const make$72 = Effect.gen(function* () {
35145
+ const make$73 = Effect.gen(function* () {
34478
35146
  const { keybindingsConfigPath } = yield* ServerConfig$1;
34479
35147
  const fs = yield* FileSystem.FileSystem;
34480
35148
  const path = yield* Path.Path;
@@ -34735,7 +35403,7 @@ const make$72 = Effect.gen(function* () {
34735
35403
  }))
34736
35404
  };
34737
35405
  });
34738
- const layer$61 = Layer.effect(Keybindings, make$72);
35406
+ const layer$61 = Layer.effect(Keybindings, make$73);
34739
35407
  //#endregion
34740
35408
  //#region src/process/externalLauncher.ts
34741
35409
  /**
@@ -34962,7 +35630,7 @@ const launchEditorProcess = Effect.fn("externalLauncher.launchEditorProcess")(fu
34962
35630
  cause
34963
35631
  }));
34964
35632
  });
34965
- const make$71 = Effect.gen(function* () {
35633
+ const make$72 = Effect.gen(function* () {
34966
35634
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
34967
35635
  const fileSystem = yield* FileSystem.FileSystem;
34968
35636
  const path = yield* Path.Path;
@@ -34973,7 +35641,7 @@ const make$71 = Effect.gen(function* () {
34973
35641
  launchEditor: (input) => provideCommandResolutionServices(Effect.flatMap(resolveEditorLaunch(input), (launch) => launchEditorProcess(launch).pipe(Effect.provideService(ChildProcessSpawner$1.ChildProcessSpawner, spawner))))
34974
35642
  });
34975
35643
  });
34976
- const layer$60 = Layer.effect(ExternalLauncher, make$71);
35644
+ const layer$60 = Layer.effect(ExternalLauncher, make$72);
34977
35645
  //#endregion
34978
35646
  //#region src/orchestration/Services/OrchestrationReactor.ts
34979
35647
  /**
@@ -34991,7 +35659,7 @@ var OrchestrationReactor = class extends Context.Service()("@p4code/cli/orchestr
34991
35659
  //#endregion
34992
35660
  //#region src/serverLifecycleEvents.ts
34993
35661
  var ServerLifecycleEvents = class extends Context.Service()("@p4code/cli/serverLifecycleEvents") {};
34994
- const make$70 = Effect.gen(function* () {
35662
+ const make$71 = Effect.gen(function* () {
34995
35663
  const pubsub = yield* PubSub.unbounded();
34996
35664
  const state = yield* Ref.make({
34997
35665
  sequence: 0,
@@ -35015,7 +35683,7 @@ const make$70 = Effect.gen(function* () {
35015
35683
  }
35016
35684
  };
35017
35685
  });
35018
- const layer$59 = Layer.effect(ServerLifecycleEvents, make$70);
35686
+ const layer$59 = Layer.effect(ServerLifecycleEvents, make$71);
35019
35687
  //#endregion
35020
35688
  //#region src/telemetry/Identify.ts
35021
35689
  const CodexAuthJsonSchema = Schema$1.Struct({ tokens: Schema$1.Struct({ account_id: Schema$1.String }) });
@@ -35188,7 +35856,7 @@ var AnalyticsService = class AnalyticsService extends Context.Service()("@p4code
35188
35856
  /** No-op layer for callers that intentionally disable telemetry. */
35189
35857
  static layerTest = Layer.succeed(AnalyticsService, inert);
35190
35858
  };
35191
- const make$69 = Effect.gen(function* () {
35859
+ const make$70 = Effect.gen(function* () {
35192
35860
  const telemetryConfig = yield* TelemetryEnvConfig;
35193
35861
  const posthogKey = telemetryConfig.posthogKey.trim();
35194
35862
  if (!telemetryConfig.enabled || posthogKey === "") return inert;
@@ -35258,7 +35926,7 @@ const make$69 = Effect.gen(function* () {
35258
35926
  flush
35259
35927
  });
35260
35928
  });
35261
- const layer$58 = Layer.effect(AnalyticsService, make$69);
35929
+ const layer$58 = Layer.effect(AnalyticsService, make$70);
35262
35930
  AnalyticsService.layerTest;
35263
35931
  //#endregion
35264
35932
  //#region src/service/pinnedRuntime.ts
@@ -35605,7 +36273,7 @@ var BootServiceInstallError = class extends Schema$1.TaggedErrorClass()("BootSer
35605
36273
  }
35606
36274
  };
35607
36275
  var BootService = class extends Context.Service()("@p4code/cli/service/bootService") {};
35608
- const make$68 = Effect.fn("cloud.boot_service.make")(function* (input) {
36276
+ const make$69 = Effect.fn("cloud.boot_service.make")(function* (input) {
35609
36277
  const hostExecPath = yield* HostProcessExecutablePath;
35610
36278
  const hostArguments = yield* HostProcessArguments;
35611
36279
  const host = input.host ?? {
@@ -35827,7 +36495,7 @@ const make$68 = Effect.fn("cloud.boot_service.make")(function* (input) {
35827
36495
  logPath
35828
36496
  });
35829
36497
  });
35830
- const layer$57 = (input) => Layer.effect(BootService, make$68(input));
36498
+ const layer$57 = (input) => Layer.effect(BootService, make$69(input));
35831
36499
  //#endregion
35832
36500
  //#region src/service/selfUpdate.ts
35833
36501
  /**
@@ -35902,7 +36570,7 @@ const resolveServerSelfUpdateCapability = Effect.fn("cloud.server_self_update.re
35902
36570
  return null;
35903
36571
  });
35904
36572
  var ServerSelfUpdate = class extends Context.Service()("@p4code/cli/service/selfUpdate/ServerSelfUpdate") {};
35905
- 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) {
35906
36574
  const serverConfig = yield* ServerConfig$1;
35907
36575
  const fs = yield* FileSystem.FileSystem;
35908
36576
  const path = yield* Path.Path;
@@ -36052,7 +36720,7 @@ const make$67 = Effect.fn("cloud.server_self_update.make")(function* (options) {
36052
36720
  });
36053
36721
  return ServerSelfUpdate.of({ update });
36054
36722
  });
36055
- 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));
36056
36724
  //#endregion
36057
36725
  //#region src/environment/ServerEnvironmentLabel.ts
36058
36726
  const ServerEnvironmentLabelCommandProbe = Schema$1.Literals(["macos-computer-name", "linux-pretty-hostname"]);
@@ -36184,7 +36852,7 @@ function platformArch(architecture) {
36184
36852
  default: return "other";
36185
36853
  }
36186
36854
  }
36187
- const make$66 = Effect.gen(function* () {
36855
+ const make$67 = Effect.gen(function* () {
36188
36856
  const fileSystem = yield* FileSystem.FileSystem;
36189
36857
  const path = yield* Path.Path;
36190
36858
  const serverConfig = yield* ServerConfig$1;
@@ -36233,6 +36901,7 @@ const make$66 = Effect.gen(function* () {
36233
36901
  connectionProbe: true,
36234
36902
  pullRequests: true,
36235
36903
  threadSettlement: true,
36904
+ threadLifecycleV2: true,
36236
36905
  threadSnooze: true,
36237
36906
  threadPinning: true,
36238
36907
  threadFork: true,
@@ -36251,7 +36920,7 @@ const make$66 = Effect.gen(function* () {
36251
36920
  * state. It intentionally has no fallback Layer.succeed value: callers must
36252
36921
  * provide the external platform services and a ServerConfig.
36253
36922
  */
36254
- 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));
36255
36924
  //#endregion
36256
36925
  //#region src/provider/Services/ProviderSessionReaper.ts
36257
36926
  var ProviderSessionReaper = class extends Context.Service()("@p4code/cli/provider/Services/ProviderSessionReaper") {};
@@ -36393,7 +37062,7 @@ const maybeOpenBrowser = (target) => Effect.gen(function* () {
36393
37062
  yield* (yield* ExternalLauncher).launchBrowser(target).pipe(Effect.catch(() => Effect.logInfo("browser auto-open unavailable", { hint: `Open ${target} in your browser.` })));
36394
37063
  });
36395
37064
  const runStartupPhase = (phase, effect) => effect.pipe(Effect.annotateSpans({ "startup.phase": phase }), Effect.withSpan(`server.startup.${phase}`));
36396
- const make$65 = Effect.gen(function* () {
37065
+ const make$66 = Effect.gen(function* () {
36397
37066
  const serverConfig = yield* ServerConfig$1;
36398
37067
  const keybindings = yield* Keybindings;
36399
37068
  const orchestrationReactor = yield* OrchestrationReactor;
@@ -36534,7 +37203,7 @@ const make$65 = Effect.gen(function* () {
36534
37203
  enqueueCommand: commandGate.enqueueCommand
36535
37204
  };
36536
37205
  });
36537
- const layer$54 = Layer.effect(ServerRuntimeStartup, make$65);
37206
+ const layer$54 = Layer.effect(ServerRuntimeStartup, make$66);
36538
37207
  //#endregion
36539
37208
  //#region src/serverRuntimeState.ts
36540
37209
  const PersistedServerRuntimeState = Schema$1.Struct({
@@ -36691,7 +37360,7 @@ function expandHomePath$2(input, path) {
36691
37360
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
36692
37361
  return input;
36693
37362
  }
36694
- const make$64 = Effect.gen(function* () {
37363
+ const make$65 = Effect.gen(function* () {
36695
37364
  const fileSystem = yield* FileSystem.FileSystem;
36696
37365
  const path = yield* Path.Path;
36697
37366
  const statWorkspaceRoot = Effect.fn("WorkspacePaths.statWorkspaceRoot")(function* (workspaceRoot, normalizedWorkspaceRoot, phase) {
@@ -36748,7 +37417,7 @@ const make$64 = Effect.gen(function* () {
36748
37417
  resolveRelativePathWithinRoot
36749
37418
  });
36750
37419
  });
36751
- const layer$53 = Layer.effect(WorkspacePaths, make$64);
37420
+ const layer$53 = Layer.effect(WorkspacePaths, make$65);
36752
37421
  //#endregion
36753
37422
  //#region src/cli/project.ts
36754
37423
  const isEnvironmentHttpCommonError = Schema$1.is(EnvironmentHttpCommonError);
@@ -36883,7 +37552,7 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* (
36883
37552
  operation: "resolveProjectTarget",
36884
37553
  identifier: input.identifier
36885
37554
  });
36886
- const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null);
37555
+ const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt == null);
36887
37556
  const exactIdMatch = activeProjects.find((project) => project.id === trimmedIdentifier);
36888
37557
  if (exactIdMatch) return {
36889
37558
  id: exactIdMatch.id,
@@ -36907,7 +37576,7 @@ const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* (
36907
37576
  };
36908
37577
  });
36909
37578
  const fetchLiveOrchestrationSnapshot = (origin, bearerToken) => Effect.gen(function* () {
36910
- return yield* (yield* makeLiveServerClient(origin)).orchestration.snapshot({ headers: { authorization: `Bearer ${bearerToken}` } });
37579
+ return yield* (yield* makeLiveServerClient(origin)).orchestration.shellSnapshot({ headers: { authorization: `Bearer ${bearerToken}` } });
36911
37580
  }).pipe(withProjectCliLiveServerTimeout, Effect.mapError(projectCommandErrorFromLiveServerRequest));
36912
37581
  const dispatchLiveOrchestrationCommand = (origin, bearerToken, command) => Effect.gen(function* () {
36913
37582
  yield* (yield* makeLiveServerClient(origin)).orchestration.dispatch({
@@ -36916,7 +37585,7 @@ const dispatchLiveOrchestrationCommand = (origin, bearerToken, command) => Effec
36916
37585
  });
36917
37586
  }).pipe(withProjectCliLiveServerTimeout, Effect.mapError(projectCommandErrorFromLiveServerRequest));
36918
37587
  const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () {
36919
- return yield* (yield* ProjectionSnapshotQuery).getSnapshot();
37588
+ return yield* (yield* ProjectionSnapshotQuery).getCommandReadModel();
36920
37589
  });
36921
37590
  const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")(function* (environmentAuth, config) {
36922
37591
  const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);
@@ -36964,7 +37633,7 @@ const projectAddCommand = Command.make("add", {
36964
37633
  title: Flag.string("title").pipe(Flag.withDescription("Optional project title."), Flag.optional)
36965
37634
  }).pipe(Command.withDescription("Add a project."), Command.withHandler((flags) => runProjectMutation(flags, Effect.fn("projectAddMutation")(function* ({ snapshot, dispatch }) {
36966
37635
  const workspaceRoot = yield* normalizeWorkspaceRootForProjectCommand(flags.workspaceRoot);
36967
- 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);
36968
37637
  if (existingProject) return yield* new ProjectAlreadyExistsError({
36969
37638
  operation: "addProject",
36970
37639
  projectId: existingProject.id,
@@ -37931,7 +38600,7 @@ const logP4ProjectFileLoadError = (error) => Effect.logWarning(error).pipe(Effec
37931
38600
  filePath: error.filePath,
37932
38601
  errorTag: error._tag
37933
38602
  }));
37934
- const make$63 = Effect.gen(function* () {
38603
+ const make$64 = Effect.gen(function* () {
37935
38604
  const fileSystem = yield* FileSystem.FileSystem;
37936
38605
  const path = yield* Path.Path;
37937
38606
  const load = Effect.fn("P4ProjectFileLoader.load")(function* (workspaceRoot) {
@@ -37952,7 +38621,7 @@ const make$63 = Effect.gen(function* () {
37952
38621
  });
37953
38622
  return P4ProjectFileLoader.of({ load });
37954
38623
  });
37955
- const layer$52 = Layer.effect(P4ProjectFileLoader, make$63);
38624
+ const layer$52 = Layer.effect(P4ProjectFileLoader, make$64);
37956
38625
  //#endregion
37957
38626
  //#region src/project/ProjectFaviconResolver.ts
37958
38627
  /**
@@ -38027,7 +38696,7 @@ function extractIconHref(source) {
38027
38696
  return null;
38028
38697
  }
38029
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) }));
38030
- const make$62 = Effect.gen(function* () {
38699
+ const make$63 = Effect.gen(function* () {
38031
38700
  const fileSystem = yield* FileSystem.FileSystem;
38032
38701
  const path = yield* Path.Path;
38033
38702
  const workspacePaths = yield* WorkspacePaths;
@@ -38096,7 +38765,7 @@ const make$62 = Effect.gen(function* () {
38096
38765
  });
38097
38766
  return ProjectFaviconResolver.of({ resolvePath });
38098
38767
  });
38099
- const layer$51 = Layer.effect(ProjectFaviconResolver, make$62);
38768
+ const layer$51 = Layer.effect(ProjectFaviconResolver, make$63);
38100
38769
  //#endregion
38101
38770
  //#region src/assets/AssetAccess.ts
38102
38771
  const ASSET_ROUTE_PREFIX = "/api/assets";
@@ -38507,10 +39176,10 @@ const resolveAsset = Effect.fn("AssetAccess.resolveAsset")(function* (token, rel
38507
39176
  //#endregion
38508
39177
  //#region src/observability/BrowserTraceCollector.ts
38509
39178
  var BrowserTraceCollector = class extends Context.Service()("@p4code/cli/observability/BrowserTraceCollector") {};
38510
- const make$61 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
39179
+ const make$62 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
38511
39180
  for (const record of records) sink.push(record);
38512
39181
  }) });
38513
- const layer$50 = (sink) => Layer.succeed(BrowserTraceCollector, make$61(sink));
39182
+ const layer$50 = (sink) => Layer.succeed(BrowserTraceCollector, make$62(sink));
38514
39183
  //#endregion
38515
39184
  //#region src/auth/http.ts
38516
39185
  const CREDENTIAL_RESPONSE_HEADERS = {
@@ -39832,6 +40501,9 @@ const LIST_REFS_SNAPSHOT_CACHE_CAPACITY = 64;
39832
40501
  const LIST_REFS_SNAPSHOT_CACHE_TTL = Duration.minutes(2);
39833
40502
  const LIST_REFS_REFRESH_COALESCE_TTL = Duration.seconds(5);
39834
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);
39835
40507
  const STATUS_UPSTREAM_REFRESH_ENV = Object.freeze({
39836
40508
  GCM_INTERACTIVE: "never",
39837
40509
  GIT_ASKPASS: "",
@@ -40043,6 +40715,9 @@ function isMissingGitCwdError(error) {
40043
40715
  function isNonRepositoryGitStderr(stderr) {
40044
40716
  return stderr.toLowerCase().includes("not a git repository");
40045
40717
  }
40718
+ function isUnbornHeadStderr(stderr) {
40719
+ return stderr.toLowerCase().includes("unknown revision") && stderr.toLowerCase().includes("path not in the working tree");
40720
+ }
40046
40721
  const nowUnixNano = DateTime.now.pipe(Effect.map((now) => BigInt(DateTime.toEpochMillis(now)) * 1000000n));
40047
40722
  const addCurrentSpanEvent = (name, attributes) => Effect.gen(function* () {
40048
40723
  const span = yield* Effect.currentSpan;
@@ -40436,6 +41111,47 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40436
41111
  const cacheKey = normalizeRepositoryPathsCacheKey(cwd);
40437
41112
  return Cache.get(refresh ? repositoryPathsRefreshCache : repositoryPathsCache, cacheKey);
40438
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
+ });
40439
41155
  const resolveGitCommonDir = Effect.fn("resolveGitCommonDir")(function* (cwd) {
40440
41156
  const repositoryPaths = yield* resolveRepositoryPaths(cwd);
40441
41157
  if (repositoryPaths !== null) return repositoryPaths.gitCommonDir;
@@ -40479,10 +41195,16 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40479
41195
  remoteName: upstream.remoteName
40480
41196
  }));
40481
41197
  });
40482
- const resolveDefaultBranchName = (cwd, remoteName) => executeGit("GitVcsDriver.resolveDefaultBranchName", cwd, ["symbolic-ref", `refs/remotes/${remoteName}/HEAD`], { allowNonZeroExit: true }).pipe(Effect.map((result) => {
40483
- if (result.exitCode !== 0) return null;
40484
- return parseDefaultBranchFromRemoteHeadRef(result.stdout, remoteName);
40485
- }));
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
+ });
40486
41208
  const remoteBranchExists = (cwd, remoteName, refName) => executeGit("GitVcsDriver.remoteBranchExists", cwd, [
40487
41209
  "show-ref",
40488
41210
  "--verify",
@@ -40587,9 +41309,10 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40587
41309
  "HEAD"
40588
41310
  ], { allowNonZeroExit: true }).pipe(Effect.catchTags({ GitCommandError: (error) => isMissingGitCwdError(error) ? Effect.succeed(null) : Effect.fail(error) }));
40589
41311
  if (branchResult === null) return NON_REPOSITORY_REMOTE_STATUS_DETAILS;
41312
+ let branch;
40590
41313
  if (branchResult.exitCode !== 0) {
40591
41314
  if (isNonRepositoryGitStderr(branchResult.stderr)) return NON_REPOSITORY_REMOTE_STATUS_DETAILS;
40592
- return yield* new GitCommandError({
41315
+ if (!isUnbornHeadStderr(branchResult.stderr)) return yield* new GitCommandError({
40593
41316
  ...gitCommandContext({
40594
41317
  operation: "GitVcsDriver.statusDetailsRemote.branch",
40595
41318
  cwd,
@@ -40604,9 +41327,16 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40604
41327
  stdoutLength: branchResult.stdout.length,
40605
41328
  stderrLength: branchResult.stderr.length
40606
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;
40607
41339
  }
40608
- const branchValue = branchResult.stdout.trim();
40609
- const branch = branchValue.length > 0 && branchValue !== "HEAD" ? branchValue : null;
40610
41340
  const upstreamRef = (yield* resolveCurrentUpstream(cwd))?.upstreamRef ?? null;
40611
41341
  let aheadCount = 0;
40612
41342
  let behindCount = 0;
@@ -40664,18 +41394,53 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40664
41394
  stderrLength: statusResult.stderr.length
40665
41395
  });
40666
41396
  }
40667
- const [unstagedNumstatStdout, stagedNumstatStdout, defaultRefResult, hasPrimaryRemote] = yield* Effect.all([
40668
- runGitStdout("GitVcsDriver.statusDetails.unstagedNumstat", cwd, ["diff", "--numstat"]),
40669
- 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, [
40670
41400
  "diff",
40671
- "--cached",
41401
+ "HEAD",
40672
41402
  "--numstat"
40673
- ]),
40674
- executeGit("GitVcsDriver.statusDetails.defaultRef", cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], { allowNonZeroExit: true }),
40675
- 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))
40676
41442
  ], { concurrency: "unbounded" });
40677
41443
  const statusStdout = statusResult.stdout;
40678
- const defaultBranch = defaultRefResult.exitCode === 0 ? defaultRefResult.stdout.trim().replace(/^refs\/remotes\/origin\//, "") : null;
40679
41444
  let refName = null;
40680
41445
  let upstreamRef = null;
40681
41446
  let aheadCount = 0;
@@ -40713,18 +41478,12 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
40713
41478
  }
40714
41479
  const isDefaultBranch = refName !== null && (refName === defaultBranch || defaultBranch === null && (refName === "main" || refName === "master"));
40715
41480
  if (refName && !isDefaultBranch) aheadOfDefaultCount = fallbackAheadCount !== null ? fallbackAheadCount : yield* computeAheadCountAgainstBase(cwd, refName).pipe(Effect.orElseSucceed(() => 0));
40716
- const stagedEntries = parseNumstatEntries(stagedNumstatStdout);
40717
- const unstagedEntries = parseNumstatEntries(unstagedNumstatStdout);
41481
+ const numstatEntries = parseNumstatEntries(numstatStdout);
40718
41482
  const fileStatMap = /* @__PURE__ */ new Map();
40719
- for (const entry of [...stagedEntries, ...unstagedEntries]) {
40720
- const existing = fileStatMap.get(entry.path) ?? {
40721
- insertions: 0,
40722
- deletions: 0
40723
- };
40724
- existing.insertions += entry.insertions;
40725
- existing.deletions += entry.deletions;
40726
- fileStatMap.set(entry.path, existing);
40727
- }
41483
+ for (const entry of numstatEntries) fileStatMap.set(entry.path, {
41484
+ insertions: entry.insertions,
41485
+ deletions: entry.deletions
41486
+ });
40728
41487
  let insertions = 0;
40729
41488
  let deletions = 0;
40730
41489
  const files = Array.from(fileStatMap.entries()).map(([filePath, stat]) => {
@@ -41537,7 +42296,7 @@ const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* () {
41537
42296
  }
41538
42297
  return branchNames;
41539
42298
  }));
41540
- 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)])));
41541
42300
  const initRepoWithListRefsInvalidation = (input) => initRepo(input).pipe(Effect.ensuring(Effect.gen(function* () {
41542
42301
  const cacheKey = normalizeRepositoryPathsCacheKey(input.cwd);
41543
42302
  yield* Cache.invalidate(repositoryPathsRefreshCache, cacheKey);
@@ -41588,7 +42347,7 @@ const classifyNonZeroExit = (command, stderr) => {
41588
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";
41589
42348
  return "command-failed";
41590
42349
  };
41591
- const make$60 = Effect.gen(function* () {
42350
+ const make$61 = Effect.gen(function* () {
41592
42351
  const processRunner = yield* ProcessRunner;
41593
42352
  const run = Effect.fn("VcsProcess.run")(function* (input) {
41594
42353
  const baseError = {
@@ -41647,7 +42406,7 @@ const make$60 = Effect.gen(function* () {
41647
42406
  });
41648
42407
  return VcsProcess.of({ run });
41649
42408
  });
41650
- 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));
41651
42410
  //#endregion
41652
42411
  //#region src/vcs/VcsDriver.ts
41653
42412
  var VcsDriver = class extends Context.Service()("@p4code/cli/vcs/VcsDriver") {};
@@ -42098,12 +42857,12 @@ const makeVcsDriver = Effect.gen(function* () {
42098
42857
  const driver = yield* makeVcsDriverShape();
42099
42858
  return VcsDriver.of(driver);
42100
42859
  });
42101
- const make$59 = Effect.gen(function* () {
42860
+ const make$60 = Effect.gen(function* () {
42102
42861
  const git = yield* makeGitVcsDriverCore();
42103
42862
  return GitVcsDriver.of(git);
42104
42863
  });
42105
42864
  Layer.effect(VcsDriver, makeVcsDriver);
42106
- const layer$48 = Layer.effect(GitVcsDriver, make$59);
42865
+ const layer$48 = Layer.effect(GitVcsDriver, make$60);
42107
42866
  //#endregion
42108
42867
  //#region src/vcs/VcsProjectConfig.ts
42109
42868
  const ProjectVcsConfigJson = fromLenientJson(Schema$1.Struct({
@@ -42135,7 +42894,7 @@ const logVcsProjectConfigError = (error) => Effect.logWarning(error).pipe(Effect
42135
42894
  configPath: error.configPath,
42136
42895
  errorTag: error._tag
42137
42896
  }));
42138
- const make$58 = Effect.gen(function* () {
42897
+ const make$59 = Effect.gen(function* () {
42139
42898
  const fileSystem = yield* FileSystem.FileSystem;
42140
42899
  const path = yield* Path.Path;
42141
42900
  const findConfigPath = Effect.fn("VcsProjectConfig.findConfigPath")(function* (cwd) {
@@ -42176,7 +42935,7 @@ const make$58 = Effect.gen(function* () {
42176
42935
  });
42177
42936
  return VcsProjectConfig.of({ resolveKind });
42178
42937
  });
42179
- const layer$47 = Layer.effect(VcsProjectConfig, make$58);
42938
+ const layer$47 = Layer.effect(VcsProjectConfig, make$59);
42180
42939
  //#endregion
42181
42940
  //#region src/vcs/VcsDriverRegistry.ts
42182
42941
  const DETECTION_CACHE_CAPACITY = 2048;
@@ -42196,7 +42955,7 @@ function parseDetectionCacheKey(key) {
42196
42955
  cwd: key.slice(separatorIndex + 1)
42197
42956
  };
42198
42957
  }
42199
- const make$57 = Effect.gen(function* () {
42958
+ const make$58 = Effect.gen(function* () {
42200
42959
  const projectConfig = yield* VcsProjectConfig;
42201
42960
  const git = yield* makeVcsDriver;
42202
42961
  const drivers = { git };
@@ -42253,7 +43012,7 @@ const make$57 = Effect.gen(function* () {
42253
43012
  resolve
42254
43013
  });
42255
43014
  });
42256
- 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));
42257
43016
  //#endregion
42258
43017
  //#region src/checkpointing/CheckpointStore.ts
42259
43018
  /**
@@ -42273,7 +43032,7 @@ const layer$46 = Layer.effect(VcsDriverRegistry, make$57).pipe(Layer.provide(lay
42273
43032
  */
42274
43033
  /** Service tag for checkpoint persistence and restore operations. */
42275
43034
  var CheckpointStore = class extends Context.Service()("@p4code/cli/checkpointing/CheckpointStore") {};
42276
- const make$56 = Effect.gen(function* () {
43035
+ const make$57 = Effect.gen(function* () {
42277
43036
  const vcsRegistry = yield* VcsDriverRegistry;
42278
43037
  const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* (operation, cwd) {
42279
43038
  const handle = yield* vcsRegistry.resolve({ cwd });
@@ -42312,7 +43071,7 @@ const make$56 = Effect.gen(function* () {
42312
43071
  deleteCheckpointRefs
42313
43072
  });
42314
43073
  });
42315
- const layer$45 = Layer.effect(CheckpointStore, make$56);
43074
+ const layer$45 = Layer.effect(CheckpointStore, make$57);
42316
43075
  //#endregion
42317
43076
  //#region src/checkpointing/CheckpointDiffQuery.ts
42318
43077
  /**
@@ -42334,7 +43093,7 @@ function buildTurnDiffResult(input, diff) {
42334
43093
  diff
42335
43094
  };
42336
43095
  }
42337
- const make$55 = Effect.gen(function* () {
43096
+ const make$56 = Effect.gen(function* () {
42338
43097
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
42339
43098
  const checkpointStore = yield* CheckpointStore;
42340
43099
  const threadActivities = yield* ProjectionThreadActivityRepository;
@@ -42505,7 +43264,7 @@ const make$55 = Effect.gen(function* () {
42505
43264
  getFullThreadDiff
42506
43265
  });
42507
43266
  });
42508
- const layer$44 = Layer.effect(CheckpointDiffQuery, make$55);
43267
+ const layer$44 = Layer.effect(CheckpointDiffQuery, make$56);
42509
43268
  //#endregion
42510
43269
  //#region src/orchestration/ThreadLiveEventCoalescer.ts
42511
43270
  const COALESCE_WINDOW = Duration.millis(50);
@@ -42740,11 +43499,11 @@ const makeTextGenerationFromRegistry = (registry) => TextGeneration.of({
42740
43499
  detail: "This provider does not report account usage."
42741
43500
  }))))
42742
43501
  });
42743
- const make$54 = Effect.gen(function* () {
43502
+ const make$55 = Effect.gen(function* () {
42744
43503
  const registry = yield* ProviderInstanceRegistry;
42745
43504
  return makeTextGenerationFromRegistry(registry);
42746
43505
  });
42747
- const layer$43 = Layer.effect(TextGeneration, make$54);
43506
+ const layer$43 = Layer.effect(TextGeneration, make$55);
42748
43507
  //#endregion
42749
43508
  //#region src/textGeneration/TextGenerationPresets.ts
42750
43509
  const conventionalCommitsTextGenerationPolicy = {
@@ -43076,7 +43835,7 @@ const serversEqual = (left, right) => {
43076
43835
  }
43077
43836
  return true;
43078
43837
  };
43079
- const make$53 = Effect.gen(function* PortDiscoveryMake() {
43838
+ const make$54 = Effect.gen(function* PortDiscoveryMake() {
43080
43839
  const net = yield* NetService;
43081
43840
  const processRunner = yield* ProcessRunner;
43082
43841
  const hostPlatform = yield* HostProcessPlatform;
@@ -43227,7 +43986,7 @@ const make$53 = Effect.gen(function* PortDiscoveryMake() {
43227
43986
  unregisterTerminal
43228
43987
  });
43229
43988
  }).pipe(Effect.withSpan("PortDiscovery.make"));
43230
- const layer$42 = Layer.effect(PortDiscovery, make$53);
43989
+ const layer$42 = Layer.effect(PortDiscovery, make$54);
43231
43990
  //#endregion
43232
43991
  //#region src/terminal/Manager.ts
43233
43992
  /**
@@ -43905,7 +44664,7 @@ function normalizedRuntimeEnv(env) {
43905
44664
  if (entries.length === 0) return null;
43906
44665
  return Object.fromEntries(entries.toSorted(([left], [right]) => left.localeCompare(right)));
43907
44666
  }
43908
- const make$52 = Effect.fn("TerminalManager.make")(function* () {
44667
+ const make$53 = Effect.fn("TerminalManager.make")(function* () {
43909
44668
  const { terminalLogsDir } = yield* ServerConfig$1;
43910
44669
  const ptyAdapter = yield* PtyAdapter;
43911
44670
  const portDiscovery = yield* PortDiscovery;
@@ -44867,7 +45626,7 @@ const makeWithOptions$1 = Effect.fn("TerminalManager.makeWithOptions")(function*
44867
45626
  subscribeMetadata
44868
45627
  });
44869
45628
  });
44870
- 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));
44871
45630
  //#endregion
44872
45631
  //#region src/project/ProjectSetupScriptRunner.ts
44873
45632
  var ProjectSetupScriptOperationError = class extends Schema$1.TaggedErrorClass()("ProjectSetupScriptOperationError", {
@@ -44898,7 +45657,7 @@ var ProjectSetupScriptProjectNotFoundError = class extends Schema$1.TaggedErrorC
44898
45657
  };
44899
45658
  Schema$1.Union([ProjectSetupScriptOperationError, ProjectSetupScriptProjectNotFoundError]);
44900
45659
  var ProjectSetupScriptRunner = class extends Context.Service()("@p4code/cli/project/ProjectSetupScriptRunner") {};
44901
- const make$51 = Effect.gen(function* () {
45660
+ const make$52 = Effect.gen(function* () {
44902
45661
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
44903
45662
  const terminalManager = yield* TerminalManager;
44904
45663
  const runForThread = Effect.fn("ProjectSetupScriptRunner.runForThread")(function* (input) {
@@ -44956,7 +45715,7 @@ const make$51 = Effect.gen(function* () {
44956
45715
  });
44957
45716
  return ProjectSetupScriptRunner.of({ runForThread });
44958
45717
  });
44959
- const layer$40 = Layer.effect(ProjectSetupScriptRunner, make$51);
45718
+ const layer$40 = Layer.effect(ProjectSetupScriptRunner, make$52);
44960
45719
  //#endregion
44961
45720
  //#region src/provider/Services/ProviderRegistry.ts
44962
45721
  var ProviderRegistry = class extends Context.Service()("@p4code/cli/provider/Services/ProviderRegistry") {};
@@ -45053,19 +45812,23 @@ function normalizeAzureDevOpsPullRequestRecord(raw) {
45053
45812
  baseRefName: normalizeRefName$1(raw.targetRefName),
45054
45813
  headRefName: normalizeRefName$1(raw.sourceRefName),
45055
45814
  state: normalizeAzureDevOpsPullRequestState(raw.status),
45815
+ ...Option.isSome(raw.closedDate ?? Option.none()) ? { terminalAt: DateTime.formatIso(Option.getOrThrow(raw.closedDate ?? Option.none())) } : {},
45056
45816
  updatedAt: (raw.closedDate ?? Option.none()).pipe(Option.orElse(() => raw.creationDate ?? Option.none()))
45057
45817
  };
45058
45818
  }
45059
45819
  const decodeAzureDevOpsPullRequestList = decodeJsonResult(Schema$1.Array(Schema$1.Unknown));
45060
45820
  const decodeAzureDevOpsPullRequest = decodeJsonResult(AzureDevOpsPullRequestSchema);
45061
45821
  const decodeAzureDevOpsPullRequestEntry = Schema$1.decodeUnknownExit(AzureDevOpsPullRequestSchema);
45062
- function decodeAzureDevOpsPullRequestListJson(raw) {
45822
+ function decodeAzureDevOpsPullRequestListJson(raw, strict = false) {
45063
45823
  const result = decodeAzureDevOpsPullRequestList(raw);
45064
45824
  if (Result.isSuccess(result)) {
45065
45825
  const pullRequests = [];
45066
45826
  for (const entry of result.success) {
45067
45827
  const decodedEntry = decodeAzureDevOpsPullRequestEntry(entry);
45068
- if (Exit.isFailure(decodedEntry)) continue;
45828
+ if (Exit.isFailure(decodedEntry)) {
45829
+ if (strict) return Result.fail(decodedEntry.cause);
45830
+ continue;
45831
+ }
45069
45832
  pullRequests.push(normalizeAzureDevOpsPullRequestRecord(decodedEntry.value));
45070
45833
  }
45071
45834
  return Result.succeed(pullRequests);
@@ -45283,7 +46046,7 @@ function decodeAzureDevOpsJson(raw, schema, operation, cwd) {
45283
46046
  cause
45284
46047
  })));
45285
46048
  }
45286
- const make$50 = Effect.gen(function* () {
46049
+ const make$51 = Effect.gen(function* () {
45287
46050
  const process = yield* VcsProcess;
45288
46051
  const execute = (input) => process.run({
45289
46052
  operation: "AzureDevOpsCli.execute",
@@ -45323,7 +46086,7 @@ const make$50 = Effect.gen(function* () {
45323
46086
  "--top",
45324
46087
  String(input.limit ?? 20)
45325
46088
  ]
45326
- }).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) => {
45327
46090
  if (!Result.isSuccess(decoded)) return Effect.fail(new AzureDevOpsPullRequestListDecodeError({
45328
46091
  operation: "listPullRequests",
45329
46092
  command: "az",
@@ -45425,7 +46188,7 @@ const make$50 = Effect.gen(function* () {
45425
46188
  }).pipe(Effect.asVoid)
45426
46189
  });
45427
46190
  });
45428
- const layer$39 = Layer.effect(AzureDevOpsCli, make$50);
46191
+ const layer$39 = Layer.effect(AzureDevOpsCli, make$51);
45429
46192
  //#endregion
45430
46193
  //#region src/sourceControl/SourceControlProviderDiscovery.ts
45431
46194
  function firstNonEmptyLine(text) {
@@ -45625,10 +46388,11 @@ function toChangeRequest$5(summary) {
45625
46388
  headRefName: summary.headRefName,
45626
46389
  state: summary.state,
45627
46390
  updatedAt: summary.updatedAt,
46391
+ ...summary.terminalAt == null ? {} : { terminalAt: summary.terminalAt },
45628
46392
  isCrossRepository: false
45629
46393
  };
45630
46394
  }
45631
- const make$49 = Effect.gen(function* () {
46395
+ const make$50 = Effect.gen(function* () {
45632
46396
  const azure = yield* AzureDevOpsCli;
45633
46397
  return SourceControlProvider.of({
45634
46398
  kind: "azure-devops",
@@ -45639,6 +46403,7 @@ const make$49 = Effect.gen(function* () {
45639
46403
  headSelector: input.headSelector,
45640
46404
  ...source !== void 0 ? { source } : {},
45641
46405
  state: input.state,
46406
+ ...input.strict !== void 0 ? { strict: input.strict } : {},
45642
46407
  ...input.limit !== void 0 ? { limit: input.limit } : {}
45643
46408
  }).pipe(Effect.map((items) => items.map(toChangeRequest$5)), Effect.mapError((error) => new SourceControlProviderError({
45644
46409
  provider: "azure-devops",
@@ -45720,7 +46485,7 @@ const make$49 = Effect.gen(function* () {
45720
46485
  })))
45721
46486
  });
45722
46487
  });
45723
- Layer.effect(SourceControlProvider, make$49);
46488
+ Layer.effect(SourceControlProvider, make$50);
45724
46489
  //#endregion
45725
46490
  //#region src/sourceControl/bitbucketPullRequests.ts
45726
46491
  const BitbucketRepositoryRefSchema = Schema$1.Struct({
@@ -46097,7 +46862,7 @@ function responseError(operation, response) {
46097
46862
  responseBodyLength: collected.text.length
46098
46863
  }))));
46099
46864
  }
46100
- const make$48 = Effect.gen(function* () {
46865
+ const make$49 = Effect.gen(function* () {
46101
46866
  const config = yield* BitbucketApiEnvConfig;
46102
46867
  const httpClient = yield* HttpClient.HttpClient;
46103
46868
  const fileSystem = yield* FileSystem.FileSystem;
@@ -46313,7 +47078,7 @@ const make$48 = Effect.gen(function* () {
46313
47078
  })))
46314
47079
  });
46315
47080
  });
46316
- const layer$37 = Layer.effect(BitbucketApi, make$48);
47081
+ const layer$37 = Layer.effect(BitbucketApi, make$49);
46317
47082
  //#endregion
46318
47083
  //#region src/sourceControl/BitbucketSourceControlProvider.ts
46319
47084
  function toChangeRequest$4(summary) {
@@ -46331,7 +47096,7 @@ function toChangeRequest$4(summary) {
46331
47096
  ...summary.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin } : {}
46332
47097
  };
46333
47098
  }
46334
- const make$47 = Effect.gen(function* () {
47099
+ const make$48 = Effect.gen(function* () {
46335
47100
  const bitbucket = yield* BitbucketApi;
46336
47101
  return SourceControlProvider.of({
46337
47102
  kind: "bitbucket",
@@ -46422,7 +47187,7 @@ const make$47 = Effect.gen(function* () {
46422
47187
  })))
46423
47188
  });
46424
47189
  });
46425
- Layer.effect(SourceControlProvider, make$47);
47190
+ Layer.effect(SourceControlProvider, make$48);
46426
47191
  const makeDiscovery = Effect.gen(function* () {
46427
47192
  return {
46428
47193
  type: "api",
@@ -46442,6 +47207,7 @@ const GitHubPullRequestSchema = Schema$1.Struct({
46442
47207
  headRefName: TrimmedNonEmptyString,
46443
47208
  state: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
46444
47209
  mergedAt: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
47210
+ closedAt: Schema$1.optional(Schema$1.NullOr(Schema$1.String)),
46445
47211
  updatedAt: Schema$1.optional(Schema$1.OptionFromNullOr(Schema$1.DateTimeUtcFromString)),
46446
47212
  isCrossRepository: Schema$1.optional(Schema$1.Boolean),
46447
47213
  headRepository: Schema$1.optional(Schema$1.NullOr(Schema$1.Struct({
@@ -46465,6 +47231,7 @@ function normalizeGitHubPullRequestRecord(raw) {
46465
47231
  const headRepositoryName = trimOptionalString$1(raw.headRepository?.name);
46466
47232
  const headRepositoryOwnerLogin = trimOptionalString$1(raw.headRepositoryOwner?.login) ?? (explicitNameWithOwner?.includes("/") ? explicitNameWithOwner.split("/")[0] ?? null : null);
46467
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;
46468
47235
  return {
46469
47236
  number: raw.number,
46470
47237
  title: raw.title,
@@ -46473,6 +47240,7 @@ function normalizeGitHubPullRequestRecord(raw) {
46473
47240
  headRefName: raw.headRefName,
46474
47241
  state: normalizeGitHubPullRequestState(raw),
46475
47242
  updatedAt: raw.updatedAt ?? Option.none(),
47243
+ ...terminalAt == null ? {} : { terminalAt },
46476
47244
  ...typeof raw.isCrossRepository === "boolean" ? { isCrossRepository: raw.isCrossRepository } : {},
46477
47245
  ...headRepositoryNameWithOwner ? { headRepositoryNameWithOwner } : {},
46478
47246
  ...headRepositoryOwnerLogin ? { headRepositoryOwnerLogin } : {}
@@ -46481,13 +47249,16 @@ function normalizeGitHubPullRequestRecord(raw) {
46481
47249
  const decodeGitHubPullRequestList = decodeJsonResult(Schema$1.Array(Schema$1.Unknown));
46482
47250
  const decodeGitHubPullRequest = decodeJsonResult(GitHubPullRequestSchema);
46483
47251
  const decodeGitHubPullRequestEntry = Schema$1.decodeUnknownExit(GitHubPullRequestSchema);
46484
- function decodeGitHubPullRequestListJson(raw) {
47252
+ function decodeGitHubPullRequestListJson(raw, strict = false) {
46485
47253
  const result = decodeGitHubPullRequestList(raw);
46486
47254
  if (Result.isSuccess(result)) {
46487
47255
  const pullRequests = [];
46488
47256
  for (const entry of result.success) {
46489
47257
  const decodedEntry = decodeGitHubPullRequestEntry(entry);
46490
- if (Exit.isFailure(decodedEntry)) continue;
47258
+ if (Exit.isFailure(decodedEntry)) {
47259
+ if (strict) return Result.fail(decodedEntry.cause);
47260
+ continue;
47261
+ }
46491
47262
  pullRequests.push(normalizeGitHubPullRequestRecord(decodedEntry.value));
46492
47263
  }
46493
47264
  return Result.succeed(pullRequests);
@@ -46664,7 +47435,7 @@ function deriveRepositoryCloneUrlsFromCreateOutput(stdout, repository) {
46664
47435
  sshUrl: `git@${fallbackHost}:${repository}.git`
46665
47436
  };
46666
47437
  }
46667
- const make$46 = Effect.gen(function* () {
47438
+ const make$47 = Effect.gen(function* () {
46668
47439
  const process = yield* VcsProcess;
46669
47440
  const execute = (input) => process.run({
46670
47441
  operation: "GitHubCli.execute",
@@ -46692,7 +47463,7 @@ const make$46 = Effect.gen(function* () {
46692
47463
  "--limit",
46693
47464
  String(input.limit ?? 1),
46694
47465
  "--json",
46695
- "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner"
47466
+ "number,title,url,baseRefName,headRefName,state,mergedAt,closedAt,isCrossRepository,headRepository,headRepositoryOwner"
46696
47467
  ]
46697
47468
  }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => raw.length === 0 ? Effect.succeed([]) : Effect.sync(() => decodeGitHubPullRequestListJson(raw)).pipe(Effect.flatMap((decoded) => {
46698
47469
  if (!Result.isSuccess(decoded)) return Effect.fail(new GitHubPullRequestListDecodeError({
@@ -46709,7 +47480,7 @@ const make$46 = Effect.gen(function* () {
46709
47480
  "view",
46710
47481
  input.reference,
46711
47482
  "--json",
46712
- "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner"
47483
+ "number,title,url,baseRefName,headRefName,state,mergedAt,closedAt,isCrossRepository,headRepository,headRepositoryOwner"
46713
47484
  ]
46714
47485
  }).pipe(Effect.map((result) => result.stdout.trim()), Effect.flatMap((raw) => Effect.sync(() => decodeGitHubPullRequestJson(raw)).pipe(Effect.flatMap((decoded) => {
46715
47486
  if (!Result.isSuccess(decoded)) return Effect.fail(new GitHubPullRequestDecodeError({
@@ -46782,7 +47553,7 @@ const make$46 = Effect.gen(function* () {
46782
47553
  }).pipe(Effect.asVoid)
46783
47554
  });
46784
47555
  });
46785
- const layer$35 = Layer.effect(GitHubCli, make$46);
47556
+ const layer$35 = Layer.effect(GitHubCli, make$47);
46786
47557
  //#endregion
46787
47558
  //#region src/sourceControl/gitHubAuthStatus.ts
46788
47559
  const GitHubAuthStatusAccountSchema = Schema$1.Struct({
@@ -46836,6 +47607,7 @@ function toChangeRequest$3(summary) {
46836
47607
  headRefName: summary.headRefName,
46837
47608
  state: summary.state ?? "open",
46838
47609
  updatedAt: Option.none(),
47610
+ ...summary.terminalAt == null ? {} : { terminalAt: summary.terminalAt },
46839
47611
  ...summary.isCrossRepository !== void 0 ? { isCrossRepository: summary.isCrossRepository } : {},
46840
47612
  ...summary.headRepositoryNameWithOwner !== void 0 ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner } : {},
46841
47613
  ...summary.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin } : {}
@@ -46883,7 +47655,7 @@ const discovery$1 = {
46883
47655
  parseAuth: parseGitHubAuth,
46884
47656
  installHint: "Install the GitHub command-line tool (`gh`) via https://cli.github.com/ or your package manager (for example `brew install gh`)."
46885
47657
  };
46886
- const make$45 = Effect.gen(function* () {
47658
+ const make$46 = Effect.gen(function* () {
46887
47659
  const github = yield* GitHubCli;
46888
47660
  const listChangeRequests = (input) => {
46889
47661
  if (input.state === "open") return github.listOpenPullRequests({
@@ -46912,12 +47684,12 @@ const make$45 = Effect.gen(function* () {
46912
47684
  "--limit",
46913
47685
  String(input.limit ?? 20),
46914
47686
  "--json",
46915
- "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner"
47687
+ "number,title,url,baseRefName,headRefName,state,mergedAt,closedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner"
46916
47688
  ]
46917
47689
  }).pipe(Effect.flatMap((result) => {
46918
47690
  const raw = result.stdout.trim();
46919
- if (raw.length === 0) return Effect.succeed([]);
46920
- 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) => ({
46921
47693
  ...toChangeRequest$3(item),
46922
47694
  updatedAt: item.updatedAt
46923
47695
  }))) : Effect.fail(new GitHubChangeRequestListDecodeError({
@@ -46999,7 +47771,7 @@ const make$45 = Effect.gen(function* () {
46999
47771
  })))
47000
47772
  });
47001
47773
  });
47002
- Layer.effect(SourceControlProvider, make$45);
47774
+ Layer.effect(SourceControlProvider, make$46);
47003
47775
  //#endregion
47004
47776
  //#region src/sourceControl/gitLabMergeRequests.ts
47005
47777
  const GitLabProjectReferenceSchema = Schema$1.Struct({
@@ -47018,6 +47790,8 @@ const GitLabMergeRequestSchema = Schema$1.Struct({
47018
47790
  source_branch: TrimmedNonEmptyString,
47019
47791
  target_branch: TrimmedNonEmptyString,
47020
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)),
47021
47795
  updated_at: Schema$1.optional(Schema$1.OptionFromNullOr(Schema$1.DateTimeUtcFromString)),
47022
47796
  source_project_id: Schema$1.optional(Schema$1.NullOr(Schema$1.Number)),
47023
47797
  target_project_id: Schema$1.optional(Schema$1.NullOr(Schema$1.Number)),
@@ -47048,6 +47822,7 @@ function normalizeGitLabMergeRequestRecord(raw) {
47048
47822
  const targetProjectPath = projectPathWithNamespace(raw.target_project);
47049
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;
47050
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;
47051
47826
  return {
47052
47827
  number: raw.iid,
47053
47828
  title: raw.title,
@@ -47056,6 +47831,7 @@ function normalizeGitLabMergeRequestRecord(raw) {
47056
47831
  headRefName: raw.source_branch,
47057
47832
  state: normalizeGitLabMergeRequestState(raw.state),
47058
47833
  updatedAt: raw.updated_at ?? Option.none(),
47834
+ ...terminalAt == null ? {} : { terminalAt },
47059
47835
  ...typeof isCrossRepository === "boolean" ? { isCrossRepository } : {},
47060
47836
  ...sourceProjectPath ? { headRepositoryNameWithOwner: sourceProjectPath } : {},
47061
47837
  ...headRepositoryOwnerLogin ? { headRepositoryOwnerLogin } : {}
@@ -47064,13 +47840,16 @@ function normalizeGitLabMergeRequestRecord(raw) {
47064
47840
  const decodeGitLabMergeRequestList = decodeJsonResult(Schema$1.Array(Schema$1.Unknown));
47065
47841
  const decodeGitLabMergeRequest = decodeJsonResult(GitLabMergeRequestSchema);
47066
47842
  const decodeGitLabMergeRequestEntry = Schema$1.decodeUnknownExit(GitLabMergeRequestSchema);
47067
- function decodeGitLabMergeRequestListJson(raw) {
47843
+ function decodeGitLabMergeRequestListJson(raw, strict = false) {
47068
47844
  const result = decodeGitLabMergeRequestList(raw);
47069
47845
  if (Result.isSuccess(result)) {
47070
47846
  const mergeRequests = [];
47071
47847
  for (const entry of result.success) {
47072
47848
  const decodedEntry = decodeGitLabMergeRequestEntry(entry);
47073
- if (Exit.isFailure(decodedEntry)) continue;
47849
+ if (Exit.isFailure(decodedEntry)) {
47850
+ if (strict) return Result.fail(decodedEntry.cause);
47851
+ continue;
47852
+ }
47074
47853
  mergeRequests.push(normalizeGitLabMergeRequestRecord(decodedEntry.value));
47075
47854
  }
47076
47855
  return Result.succeed(mergeRequests);
@@ -47311,7 +48090,7 @@ function parseRepositoryPath(repository) {
47311
48090
  projectPath
47312
48091
  };
47313
48092
  }
47314
- const make$44 = Effect.gen(function* () {
48093
+ const make$45 = Effect.gen(function* () {
47315
48094
  const process = yield* VcsProcess;
47316
48095
  const run = (input, mapError) => process.run({
47317
48096
  operation: "GitLabCli.execute",
@@ -47348,7 +48127,7 @@ const make$44 = Effect.gen(function* () {
47348
48127
  "--output",
47349
48128
  "json"
47350
48129
  ]
47351
- }).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) => {
47352
48131
  if (!Result.isSuccess(decoded)) return Effect.fail(new GitLabMergeRequestListDecodeError({
47353
48132
  operation: "listMergeRequests",
47354
48133
  command: "glab",
@@ -47462,7 +48241,7 @@ const make$44 = Effect.gen(function* () {
47462
48241
  }).pipe(Effect.asVoid)
47463
48242
  });
47464
48243
  });
47465
- const layer$33 = Layer.effect(GitLabCli, make$44);
48244
+ const layer$33 = Layer.effect(GitLabCli, make$45);
47466
48245
  //#endregion
47467
48246
  //#region src/sourceControl/gitLabAuthStatus.ts
47468
48247
  const HOST_LINE_PATTERN = /^(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?|\[[a-f0-9:.]+\])(?::\d+)?$/iu;
@@ -47509,6 +48288,7 @@ function toChangeRequest$2(summary) {
47509
48288
  headRefName: summary.headRefName,
47510
48289
  state: summary.state ?? "open",
47511
48290
  updatedAt: summary.updatedAt ?? Option.none(),
48291
+ ...summary.terminalAt == null ? {} : { terminalAt: summary.terminalAt },
47512
48292
  ...summary.isCrossRepository !== void 0 ? { isCrossRepository: summary.isCrossRepository } : {},
47513
48293
  ...summary.headRepositoryNameWithOwner !== void 0 ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner } : {},
47514
48294
  ...summary.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin } : {}
@@ -47559,7 +48339,7 @@ const discovery = {
47559
48339
  refineUnknownRemote: refineUnknownGitLabRemote,
47560
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`)."
47561
48341
  };
47562
- const make$43 = Effect.gen(function* () {
48342
+ const make$44 = Effect.gen(function* () {
47563
48343
  const gitlab = yield* GitLabCli;
47564
48344
  return SourceControlProvider.of({
47565
48345
  kind: "gitlab",
@@ -47570,6 +48350,7 @@ const make$43 = Effect.gen(function* () {
47570
48350
  headSelector: input.headSelector,
47571
48351
  ...source ? { source } : {},
47572
48352
  state: input.state,
48353
+ ...input.strict !== void 0 ? { strict: input.strict } : {},
47573
48354
  ...input.limit !== void 0 ? { limit: input.limit } : {}
47574
48355
  }).pipe(Effect.map((items) => items.map(toChangeRequest$2)), Effect.mapError((error) => new SourceControlProviderError({
47575
48356
  provider: "gitlab",
@@ -47647,7 +48428,7 @@ const make$43 = Effect.gen(function* () {
47647
48428
  })))
47648
48429
  });
47649
48430
  });
47650
- Layer.effect(SourceControlProvider, make$43);
48431
+ Layer.effect(SourceControlProvider, make$44);
47651
48432
  //#endregion
47652
48433
  //#region src/sourceControl/SourceControlProviderRegistry.ts
47653
48434
  const PROVIDER_DETECTION_CACHE_CAPACITY = 2048;
@@ -47803,12 +48584,12 @@ const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWithProvid
47803
48584
  })), { concurrency: "unbounded" })
47804
48585
  });
47805
48586
  });
47806
- const make$42 = Effect.gen(function* () {
47807
- const github = yield* make$45;
47808
- const gitlab = yield* make$43;
47809
- 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;
47810
48591
  const bitbucketDiscovery = yield* makeDiscovery;
47811
- const azureDevOps = yield* make$49;
48592
+ const azureDevOps = yield* make$50;
47812
48593
  return yield* makeWithProviders([
47813
48594
  {
47814
48595
  kind: "github",
@@ -47832,7 +48613,7 @@ const make$42 = Effect.gen(function* () {
47832
48613
  }
47833
48614
  ]);
47834
48615
  });
47835
- const layer$31 = Layer.effect(SourceControlProviderRegistry, make$42);
48616
+ const layer$31 = Layer.effect(SourceControlProviderRegistry, make$43);
47836
48617
  //#endregion
47837
48618
  //#region src/sourceControl/PrTemplateDetection.ts
47838
48619
  const TEMPLATE_MAX_BYTES = 8e3;
@@ -47954,6 +48735,7 @@ const detectPrTemplate = Effect.fn("detectPrTemplate")(function* (cwd, treeish,
47954
48735
  });
47955
48736
  //#endregion
47956
48737
  //#region src/git/GitManager.ts
48738
+ const COMPLETION_PR_LIST_LIMIT = 100;
47957
48739
  var GitManager = class extends Context.Service()("@p4code/cli/git/GitManager") {};
47958
48740
  const COMMIT_TIMEOUT_MS = 10 * 6e4;
47959
48741
  const MAX_PROGRESS_TEXT_LENGTH = 500;
@@ -48202,7 +48984,7 @@ function toPullRequestHeadRemoteInfo(pr) {
48202
48984
  ...pr.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: pr.headRepositoryOwnerLogin } : {}
48203
48985
  };
48204
48986
  }
48205
- const make$41 = Effect.gen(function* () {
48987
+ const make$42 = Effect.gen(function* () {
48206
48988
  const gitCore = yield* GitVcsDriver;
48207
48989
  const sourceControlProviders = yield* SourceControlProviderRegistry;
48208
48990
  const textGeneration = yield* TextGeneration;
@@ -48580,6 +49362,57 @@ const make$41 = Effect.gen(function* () {
48580
49362
  if (latestOpenPr) return latestOpenPr;
48581
49363
  return parsed[0] ?? null;
48582
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
+ });
48583
49416
  const buildCompletionToast = Effect.fn("buildCompletionToast")(function* (cwd, result) {
48584
49417
  const terms = yield* sourceControlProvider(cwd).pipe(Effect.map((provider) => getChangeRequestTerminologyForKind(provider.kind)), Effect.orElseSucceed(() => getChangeRequestTerminologyForKind("unknown")));
48585
49418
  const summary = summarizeGitActionResult(result, terms);
@@ -49113,6 +49946,7 @@ const make$41 = Effect.gen(function* () {
49113
49946
  }))));
49114
49947
  });
49115
49948
  return GitManager.of({
49949
+ threadCompletionPrState,
49116
49950
  localStatus,
49117
49951
  remoteStatus,
49118
49952
  status,
@@ -49124,7 +49958,7 @@ const make$41 = Effect.gen(function* () {
49124
49958
  runStackedAction
49125
49959
  });
49126
49960
  });
49127
- const layer$30 = Layer.effect(GitManager, make$41);
49961
+ const layer$30 = Layer.effect(GitManager, make$42);
49128
49962
  //#endregion
49129
49963
  //#region src/git/GitWorkflowService.ts
49130
49964
  var GitWorkflowService = class extends Context.Service()("@p4code/cli/git/GitWorkflowService") {};
@@ -49161,7 +49995,7 @@ function nonRepositoryListRefs() {
49161
49995
  totalCount: 0
49162
49996
  };
49163
49997
  }
49164
- const make$40 = Effect.gen(function* () {
49998
+ const make$41 = Effect.gen(function* () {
49165
49999
  const registry = yield* VcsDriverRegistry;
49166
50000
  const git = yield* GitVcsDriver;
49167
50001
  const gitManager = yield* GitManager;
@@ -49247,7 +50081,7 @@ const make$40 = Effect.gen(function* () {
49247
50081
  renameBranch: (input) => ensureGit("GitWorkflowService.renameBranch", input.cwd).pipe(Effect.andThen(git.renameBranch(input)))
49248
50082
  });
49249
50083
  });
49250
- const layer$29 = Layer.effect(GitWorkflowService, make$40);
50084
+ const layer$29 = Layer.effect(GitWorkflowService, make$41);
49251
50085
  //#endregion
49252
50086
  //#region src/pullRequest/PullRequestProvider.ts
49253
50087
  /**
@@ -49608,7 +50442,7 @@ function isReviewerName(value) {
49608
50442
  const name = value.trim();
49609
50443
  return name.length > 0 && !name.startsWith("-");
49610
50444
  }
49611
- const make$39 = Effect.gen(function* () {
50445
+ const make$40 = Effect.gen(function* () {
49612
50446
  const azure = yield* AzureDevOpsCli;
49613
50447
  const detectArgs = ["--detect", "true"];
49614
50448
  const executeJson = (input) => azure.execute({
@@ -49808,7 +50642,7 @@ const make$39 = Effect.gen(function* () {
49808
50642
  }).pipe(Effect.asVoid)
49809
50643
  });
49810
50644
  });
49811
- const layer$28 = Layer.effect(AzureDevOpsPullRequestCli, make$39);
50645
+ const layer$28 = Layer.effect(AzureDevOpsPullRequestCli, make$40);
49812
50646
  //#endregion
49813
50647
  //#region src/pullRequest/AzureDevOpsPullRequestProvider.ts
49814
50648
  const CAPABILITIES$3 = {
@@ -49883,7 +50717,7 @@ function toChangeRequest$1(pullRequest) {
49883
50717
  labels: []
49884
50718
  };
49885
50719
  }
49886
- const make$38 = Effect.gen(function* () {
50720
+ const make$39 = Effect.gen(function* () {
49887
50721
  const cli = yield* AzureDevOpsPullRequestCli;
49888
50722
  const fail = (operation) => (error) => new PullRequestProviderError({
49889
50723
  provider: "azure-devops",
@@ -50623,7 +51457,7 @@ function mergeStrategy(method) {
50623
51457
  default: return "merge_commit";
50624
51458
  }
50625
51459
  }
50626
- const make$37 = Effect.gen(function* () {
51460
+ const make$38 = Effect.gen(function* () {
50627
51461
  const bitbucket = yield* BitbucketApi;
50628
51462
  /**
50629
51463
  * The repository's own path, and the workspace above it — which the people who may review are
@@ -50903,7 +51737,7 @@ const make$37 = Effect.gen(function* () {
50903
51737
  }).pipe(Effect.asVoid))
50904
51738
  });
50905
51739
  });
50906
- const layer$27 = Layer.effect(BitbucketPullRequestApi, make$37);
51740
+ const layer$27 = Layer.effect(BitbucketPullRequestApi, make$38);
50907
51741
  //#endregion
50908
51742
  //#region src/pullRequest/BitbucketPullRequestProvider.ts
50909
51743
  const CAPABILITIES$2 = {
@@ -50983,7 +51817,7 @@ function toChangeRequest(pullRequest) {
50983
51817
  labels: []
50984
51818
  };
50985
51819
  }
50986
- const make$36 = Effect.gen(function* () {
51820
+ const make$37 = Effect.gen(function* () {
50987
51821
  const api = yield* BitbucketPullRequestApi;
50988
51822
  const fail = (operation) => (error) => new PullRequestProviderError({
50989
51823
  provider: "bitbucket",
@@ -52870,7 +53704,7 @@ function actionArgs$1(action, mergeMethod, updateMethod) {
52870
53704
  case "reopen": return ["reopen"];
52871
53705
  }
52872
53706
  }
52873
- const make$35 = Effect.gen(function* () {
53707
+ const make$36 = Effect.gen(function* () {
52874
53708
  const github = yield* GitHubCli;
52875
53709
  /**
52876
53710
  * The pull request's own node id, which is what a mutation against the pull request itself is
@@ -53590,7 +54424,7 @@ const make$35 = Effect.gen(function* () {
53590
54424
  })))
53591
54425
  });
53592
54426
  });
53593
- const layer$26 = Layer.effect(GitHubPullRequestCli, make$35);
54427
+ const layer$26 = Layer.effect(GitHubPullRequestCli, make$36);
53594
54428
  //#endregion
53595
54429
  //#region src/pullRequest/GitHubPullRequestProvider.ts
53596
54430
  const CAPABILITIES$1 = {
@@ -53705,7 +54539,7 @@ function loginAvatarUrl(login, host) {
53705
54539
  }
53706
54540
  /** True where markdown would render nothing: whitespace, or only HTML comments. */
53707
54541
  const rendersEmpty = (body) => body.replace(/<!--[\s\S]*?-->/g, "").trim().length === 0;
53708
- const make$34 = Effect.gen(function* () {
54542
+ const make$35 = Effect.gen(function* () {
53709
54543
  const cli = yield* GitHubPullRequestCli;
53710
54544
  const fail = (operation) => (error) => new PullRequestProviderError({
53711
54545
  provider: "github",
@@ -54724,7 +55558,7 @@ function actionArgs(action, mergeMethod) {
54724
55558
  case "reopen": return ["reopen"];
54725
55559
  }
54726
55560
  }
54727
- const make$33 = Effect.gen(function* () {
55561
+ const make$34 = Effect.gen(function* () {
54728
55562
  const gitlab = yield* GitLabCli;
54729
55563
  const api = (input) => gitlab.execute({
54730
55564
  cwd: input.cwd,
@@ -55295,7 +56129,7 @@ const make$33 = Effect.gen(function* () {
55295
56129
  }).pipe(Effect.asVoid)
55296
56130
  });
55297
56131
  });
55298
- const layer$25 = Layer.effect(GitLabPullRequestCli, make$33);
56132
+ const layer$25 = Layer.effect(GitLabPullRequestCli, make$34);
55299
56133
  //#endregion
55300
56134
  //#region src/pullRequest/GitLabPullRequestProvider.ts
55301
56135
  const CAPABILITIES = {
@@ -55375,7 +56209,7 @@ function reasonFor(error) {
55375
56209
  if (error._tag === "GitLabCliAuthenticationError") return "unauthenticated";
55376
56210
  return "failed";
55377
56211
  }
55378
- const make$32 = Effect.gen(function* () {
56212
+ const make$33 = Effect.gen(function* () {
55379
56213
  const cli = yield* GitLabPullRequestCli;
55380
56214
  const fail = (operation) => (error) => new PullRequestProviderError({
55381
56215
  provider: "gitlab",
@@ -55518,13 +56352,13 @@ function fromProviders(providers) {
55518
56352
  * The hosts this build can read change requests from. A host with no entry here still shows up
55519
56353
  * in the provider list as unimplemented, so its projects are explained rather than missing.
55520
56354
  */
55521
- const make$31 = Effect.map(Effect.all([
55522
- make$34,
55523
- make$32,
55524
- make$36,
55525
- make$38
56355
+ const make$32 = Effect.map(Effect.all([
56356
+ make$35,
56357
+ make$33,
56358
+ make$37,
56359
+ make$39
55526
56360
  ]), fromProviders);
55527
- 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))));
55528
56362
  //#endregion
55529
56363
  //#region src/pullRequest/PullRequestService.ts
55530
56364
  /**
@@ -55712,7 +56546,7 @@ function repositoryIdentityOf(project) {
55712
56546
  if (identity.displayName) return identity.displayName;
55713
56547
  return identity.owner && identity.name ? `${identity.owner}/${identity.name}` : null;
55714
56548
  }
55715
- const make$30 = Effect.gen(function* () {
56549
+ const make$31 = Effect.gen(function* () {
55716
56550
  const registry = yield* PullRequestProviderRegistry;
55717
56551
  const projections = yield* ProjectionSnapshotQuery;
55718
56552
  const sourceControlProviders = yield* SourceControlProviderRegistry;
@@ -56682,7 +57516,7 @@ const make$30 = Effect.gen(function* () {
56682
57516
  invalidate
56683
57517
  });
56684
57518
  });
56685
- const layer$23 = Layer.effect(PullRequestService, make$30);
57519
+ const layer$23 = Layer.effect(PullRequestService, make$31);
56686
57520
  //#endregion
56687
57521
  //#region src/orchestration/ThreadWorkspaceLifecycle.ts
56688
57522
  var ThreadWorkspaceLifecycleError = class extends Data.TaggedError("ThreadWorkspaceLifecycleError") {};
@@ -56726,7 +57560,7 @@ const mapLifecycleError = Effect.mapError((cause) => cause instanceof ThreadWork
56726
57560
  detail: "Thread workspace lifecycle operation failed.",
56727
57561
  cause
56728
57562
  }));
56729
- const make$29 = Effect.gen(function* () {
57563
+ const make$30 = Effect.gen(function* () {
56730
57564
  const snapshots = yield* ProjectionSnapshotQuery;
56731
57565
  const engine = yield* OrchestrationEngineService;
56732
57566
  const gitWorkflow = yield* GitWorkflowService;
@@ -56980,7 +57814,7 @@ const make$29 = Effect.gen(function* () {
56980
57814
  record
56981
57815
  };
56982
57816
  });
56983
- const layer$22 = Layer.effect(ThreadWorkspaceLifecycleService, make$29);
57817
+ const layer$22 = Layer.effect(ThreadWorkspaceLifecycleService, make$30);
56984
57818
  //#endregion
56985
57819
  //#region src/textGeneration/BtwRequestCoordinator.ts
56986
57820
  const MAX_PENDING_BTW_CANCELLATIONS = 256;
@@ -59524,7 +60358,7 @@ function makeUpdateState(input) {
59524
60358
  output: input.output ?? null
59525
60359
  };
59526
60360
  }
59527
- const make$28 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
60361
+ const make$29 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
59528
60362
  const providerRegistry = yield* ProviderRegistry;
59529
60363
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
59530
60364
  const httpClient = yield* HttpClient.HttpClient;
@@ -59639,7 +60473,7 @@ const make$28 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
59639
60473
  });
59640
60474
  return ProviderMaintenanceRunner.of({ updateProvider });
59641
60475
  });
59642
- const layer$21 = Layer.effect(ProviderMaintenanceRunner, make$28());
60476
+ const layer$21 = Layer.effect(ProviderMaintenanceRunner, make$29());
59643
60477
  //#endregion
59644
60478
  //#region src/provider/Drivers/ClaudeHome.ts
59645
60479
  const resolveClaudeHomePath = Effect.fn("resolveClaudeHomePath")(function* (config) {
@@ -60749,7 +61583,7 @@ Layer.succeed(UsageService, UsageService.of({ readSummary: (input) => Effect.suc
60749
61583
  },
60750
61584
  scanDurationMs: 0
60751
61585
  }) }));
60752
- const make$27 = Effect.gen(function* () {
61586
+ const make$28 = Effect.gen(function* () {
60753
61587
  const fileSystem = yield* FileSystem.FileSystem;
60754
61588
  const path = yield* Path.Path;
60755
61589
  const config = yield* ServerConfig$1;
@@ -60987,7 +61821,7 @@ const make$27 = Effect.gen(function* () {
60987
61821
  };
60988
61822
  }) };
60989
61823
  });
60990
- const layer$20 = Layer.effect(UsageService, make$27);
61824
+ const layer$20 = Layer.effect(UsageService, make$28);
60991
61825
  //#endregion
60992
61826
  //#region src/feed/FeedStore.ts
60993
61827
  const storageFailure = (message) => new FeedError({
@@ -61351,7 +62185,7 @@ const jsonRequest = Effect.fn("FeedService.jsonRequest")(function* (url, token,
61351
62185
  catch: () => fail("hub_unavailable", "Hub returned invalid JSON.")
61352
62186
  });
61353
62187
  });
61354
- const make$26 = Effect.gen(function* () {
62188
+ const make$27 = Effect.gen(function* () {
61355
62189
  const hubLink = yield* HubLink;
61356
62190
  const providers = yield* ProviderInstanceRegistry;
61357
62191
  const config = yield* ServerConfig$1;
@@ -61589,7 +62423,7 @@ var FeedService = class extends Context.Reference("@p4code/cli/feed/FeedService"
61589
62423
  markRead: unavailable,
61590
62424
  cleanup: unavailable
61591
62425
  }) }) {};
61592
- const layer$19 = Layer.effect(FeedService, make$26);
62426
+ const layer$19 = Layer.effect(FeedService, make$27);
61593
62427
  const SKILL_MANIFEST_FILENAME = "SKILL.md";
61594
62428
  /**
61595
62429
  * Split a catalogue id (`owner/repo/skill-name`) into its parts.
@@ -61735,7 +62569,7 @@ const emptyFetch = (id, unavailable) => ({
61735
62569
  skipped: [],
61736
62570
  unavailable
61737
62571
  });
61738
- const make$25 = Effect.gen(function* () {
62572
+ const make$26 = Effect.gen(function* () {
61739
62573
  const http = yield* HttpClient.HttpClient;
61740
62574
  const request = Effect.fn("SkillRegistry.request")(function* (url) {
61741
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));
@@ -61806,7 +62640,7 @@ const make$25 = Effect.gen(function* () {
61806
62640
  fetch
61807
62641
  };
61808
62642
  });
61809
- const layer$18 = Layer.effect(SkillRegistry, make$25);
62643
+ const layer$18 = Layer.effect(SkillRegistry, make$26);
61810
62644
  //#endregion
61811
62645
  //#region src/mcp/McpInvocationContext.ts
61812
62646
  var McpInvocationContext = class extends Context.Service()("@p4code/cli/mcp/McpInvocationContext") {};
@@ -61909,9 +62743,9 @@ const requireThreadControlTarget = Effect.fn("mcp.requireThreadControlTarget")(f
61909
62743
  * The guard on starting a thread, which is a different question from acting on
61910
62744
  * one: it is not "which thread" but "may this session make more of them".
61911
62745
  */
61912
- const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* () {
62746
+ const requireThreadSpawn = Effect.fn("mcp.requireThreadSpawn")(function* (fusion = false) {
61913
62747
  const invocation = yield* requireThreadCapability();
61914
- if (invocation.mayCreateThreads !== true) return yield* new ThreadSpawnNotPermittedError({
62748
+ if (invocation.mayCreateThreads !== true && !(fusion && invocation.mayCreateFusionPairs === true)) return yield* new ThreadSpawnNotPermittedError({
61915
62749
  threadId: invocation.threadId,
61916
62750
  detail: "this thread was itself started by an agent, and spawning goes one level deep so a runaway loop has a bound"
61917
62751
  });
@@ -62034,7 +62868,7 @@ const classifyResponseError = (context, error) => {
62034
62868
  });
62035
62869
  }
62036
62870
  };
62037
- const make$24 = Effect.gen(function* PreviewAutomationBrokerMake() {
62871
+ const make$25 = Effect.gen(function* PreviewAutomationBrokerMake() {
62038
62872
  const crypto = yield* Crypto.Crypto;
62039
62873
  const state = yield* SynchronizedRef.make({
62040
62874
  clients: /* @__PURE__ */ new Map(),
@@ -62268,7 +63102,7 @@ const make$24 = Effect.gen(function* PreviewAutomationBrokerMake() {
62268
63102
  invoke
62269
63103
  });
62270
63104
  }).pipe(Effect.withSpan("PreviewAutomationBroker.make"));
62271
- const layer$17 = Layer.effect(PreviewAutomationBroker, make$24);
63105
+ const layer$17 = Layer.effect(PreviewAutomationBroker, make$25);
62272
63106
  //#endregion
62273
63107
  //#region src/preview/Manager.ts
62274
63108
  /**
@@ -62332,7 +63166,7 @@ const buildIdleSnapshot = (input) => ({
62332
63166
  viewport: FILL_PREVIEW_VIEWPORT,
62333
63167
  updatedAt: input.updatedAt
62334
63168
  });
62335
- const make$23 = Effect.gen(function* PreviewManagerMake() {
63169
+ const make$24 = Effect.gen(function* PreviewManagerMake() {
62336
63170
  const serverEpoch = NodeCrypto.randomUUID();
62337
63171
  const stateRef = yield* SynchronizedRef.make(initialState);
62338
63172
  const eventsPubSub = yield* PubSub.unbounded();
@@ -62563,12 +63397,15 @@ const make$23 = Effect.gen(function* PreviewManagerMake() {
62563
63397
  subscribeEvents: PubSub.subscribe(eventsPubSub)
62564
63398
  });
62565
63399
  }).pipe(Effect.withSpan("PreviewManager.make"));
62566
- const layer$16 = Layer.effect(PreviewManager, make$23);
63400
+ const layer$16 = Layer.effect(PreviewManager, make$24);
62567
63401
  //#endregion
62568
63402
  //#region src/workspace/WorkspaceSearchIndex.ts
62569
63403
  const WORKSPACE_INDEX_MAX_ENTRIES = 25e3;
62570
63404
  const WORKSPACE_INDEX_PAGE_SIZE = 25002;
62571
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;
62572
63409
  const WORKSPACE_INDEX_IDLE_TTL = "15 minutes";
62573
63410
  const WORKSPACE_INDEX_SCAN_POLL_INTERVAL = "50 millis";
62574
63411
  var WorkspaceSearchIndexCreateFailed = class extends Schema$1.TaggedErrorClass()("WorkspaceSearchIndexCreateFailed", {
@@ -62662,12 +63499,12 @@ function withDirectoryAncestors(entries) {
62662
63499
  }
62663
63500
  return [...entryByPath.values()];
62664
63501
  }
62665
- const createFinder = Effect.fn("WorkspaceSearchIndex.createFinder")(function* (cwd) {
63502
+ const createFinder = Effect.fn("WorkspaceSearchIndex.createFinder")(function* (cwd, contentSearch) {
62666
63503
  const result = yield* Effect.try({
62667
63504
  try: () => FileFinder.create({
62668
63505
  basePath: cwd,
62669
63506
  disableMmapCache: true,
62670
- disableContentIndexing: true,
63507
+ disableContentIndexing: !contentSearch,
62671
63508
  aiMode: false,
62672
63509
  enableFsRootScanning: true,
62673
63510
  enableHomeDirScanning: true
@@ -62697,8 +63534,8 @@ const waitForScan = (cwd, finder, onFailure) => Effect.try({
62697
63534
  timeout: WORKSPACE_INDEX_SCAN_TIMEOUT
62698
63535
  })
62699
63536
  }), Effect.withSpan("WorkspaceSearchIndex.waitForScan"));
62700
- const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62701
- 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({
62702
63539
  try: () => finder.destroy(),
62703
63540
  catch: (cause) => new WorkspaceSearchIndexDestroyFailed({
62704
63541
  cwd,
@@ -62710,6 +63547,24 @@ const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62710
63547
  reason: "FileFinder.isScanning threw while creating the index.",
62711
63548
  cause
62712
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
+ }
62713
63568
  const runMixedSearch = Effect.fn("WorkspaceSearchIndex.runMixedSearch")(function* (query, pageSize) {
62714
63569
  const result = yield* Effect.try({
62715
63570
  try: () => finder.mixedSearch(query, { pageSize }),
@@ -62760,10 +63615,58 @@ const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62760
63615
  const search = Effect.fn("WorkspaceSearchIndex.search")(function* (query, limit) {
62761
63616
  return mapMixedSearchResult(yield* runMixedSearch(query, Math.max(1, limit + 1)), limit);
62762
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
+ });
62763
63665
  return WorkspaceSearchIndex.of({
62764
63666
  list,
62765
63667
  refresh,
62766
- search
63668
+ search,
63669
+ searchContents
62767
63670
  });
62768
63671
  });
62769
63672
  /**
@@ -62771,11 +63674,16 @@ const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62771
63674
  * workspace root. WorkspaceSearchIndexMap owns memoization and idle cleanup;
62772
63675
  * using a default cwd here would mix resources from different workspaces.
62773
63676
  */
62774
- const layer$15 = (cwd) => Layer.effect(WorkspaceSearchIndex, make$22(cwd));
63677
+ const layer$15 = (cwd) => Layer.effect(WorkspaceSearchIndex, make$23(cwd));
62775
63678
  var WorkspaceSearchIndexMap = class extends LayerMap.Service()("@p4code/cli/workspace/WorkspaceSearchIndexMap", {
62776
63679
  lookup: layer$15,
62777
63680
  idleTimeToLive: WORKSPACE_INDEX_IDLE_TTL
62778
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
+ }) {};
62779
63687
  //#endregion
62780
63688
  //#region src/workspace/WorkspaceEntries.ts
62781
63689
  var WorkspaceEntriesWindowsPathUnsupportedError = class extends Schema$1.TaggedErrorClass()("WorkspaceEntriesWindowsPathUnsupportedError", {
@@ -62835,9 +63743,10 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu
62835
63743
  if (!input.cwd) return yield* new WorkspaceEntriesCurrentProjectRequiredError({ partialPath: input.partialPath });
62836
63744
  return path.resolve(expandHomePath$1(input.cwd, path), input.partialPath);
62837
63745
  });
62838
- const make$21 = Effect.gen(function* () {
63746
+ const make$22 = Effect.gen(function* () {
62839
63747
  const path = yield* Path.Path;
62840
63748
  const workspacePaths = yield* WorkspacePaths;
63749
+ const contentSearchIndexes = yield* WorkspaceContentSearchIndexMap;
62841
63750
  const workspaceSearchIndexes = yield* WorkspaceSearchIndexMap;
62842
63751
  const normalizeWorkspaceRoot = Effect.fn("WorkspaceEntries.normalizeWorkspaceRoot")(function* (cwd) {
62843
63752
  return yield* workspacePaths.normalizeWorkspaceRoot(cwd);
@@ -62902,14 +63811,21 @@ const make$21 = Effect.gen(function* () {
62902
63811
  return yield* (yield* WorkspaceSearchIndex).list();
62903
63812
  }).pipe(Effect.provide(workspaceSearchIndexes.get(normalizedCwd)));
62904
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
+ });
62905
63820
  return WorkspaceEntries.of({
62906
63821
  browse,
62907
63822
  list,
62908
63823
  refresh,
62909
- search
63824
+ search,
63825
+ searchContents
62910
63826
  });
62911
63827
  });
62912
- 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));
62913
63829
  //#endregion
62914
63830
  //#region src/workspace/WorkspaceFileSystem.ts
62915
63831
  /**
@@ -62968,7 +63884,7 @@ Schema$1.Union([
62968
63884
  ]);
62969
63885
  /** Service tag for workspace file operations. */
62970
63886
  var WorkspaceFileSystem = class extends Context.Service()("@p4code/cli/workspace/WorkspaceFileSystem") {};
62971
- const make$20 = Effect.gen(function* () {
63887
+ const make$21 = Effect.gen(function* () {
62972
63888
  const fileSystem = yield* FileSystem.FileSystem;
62973
63889
  const path = yield* Path.Path;
62974
63890
  const workspacePaths = yield* WorkspacePaths;
@@ -63112,7 +64028,7 @@ const make$20 = Effect.gen(function* () {
63112
64028
  writeFile
63113
64029
  });
63114
64030
  });
63115
- const layer$13 = Layer.effect(WorkspaceFileSystem, make$20);
64031
+ const layer$13 = Layer.effect(WorkspaceFileSystem, make$21);
63116
64032
  //#endregion
63117
64033
  //#region src/vcs/VcsStatusBroadcaster.ts
63118
64034
  const DEFAULT_VCS_STATUS_REFRESH_INTERVAL = Duration.seconds(30);
@@ -63184,7 +64100,7 @@ function fingerprintStatusPart(status) {
63184
64100
  return JSON.stringify(status);
63185
64101
  }
63186
64102
  const normalizeCwd = (cwd) => Effect.service(FileSystem.FileSystem).pipe(Effect.flatMap((fs) => fs.realPath(cwd)), Effect.orElseSucceed(() => cwd));
63187
- const make$19 = Effect.gen(function* () {
64103
+ const make$20 = Effect.gen(function* () {
63188
64104
  const workflow = yield* GitWorkflowService;
63189
64105
  const fs = yield* FileSystem.FileSystem;
63190
64106
  const changesPubSub = yield* Effect.acquireRelease(PubSub.unbounded(), (pubsub) => PubSub.shutdown(pubsub));
@@ -63408,7 +64324,7 @@ const make$19 = Effect.gen(function* () {
63408
64324
  streamStatus
63409
64325
  });
63410
64326
  });
63411
- const layer$12 = Layer.effect(VcsStatusBroadcaster, make$19);
64327
+ const layer$12 = Layer.effect(VcsStatusBroadcaster, make$20);
63412
64328
  //#endregion
63413
64329
  //#region src/vcs/VcsProvisioningService.ts
63414
64330
  var VcsProvisioningService = class extends Context.Service()("@p4code/cli/vcs/VcsProvisioningService") {};
@@ -63421,7 +64337,7 @@ function resolveRequestedKind(kind) {
63421
64337
  }));
63422
64338
  return Effect.succeed(kind);
63423
64339
  }
63424
- const make$18 = Effect.gen(function* () {
64340
+ const make$19 = Effect.gen(function* () {
63425
64341
  const registry = yield* VcsDriverRegistry;
63426
64342
  const initRepository = Effect.fn("VcsProvisioningService.initRepository")(function* (input) {
63427
64343
  const kind = yield* resolveRequestedKind(input.kind);
@@ -63429,11 +64345,11 @@ const make$18 = Effect.gen(function* () {
63429
64345
  });
63430
64346
  return VcsProvisioningService.of({ initRepository });
63431
64347
  });
63432
- const layer$11 = Layer.effect(VcsProvisioningService, make$18);
64348
+ const layer$11 = Layer.effect(VcsProvisioningService, make$19);
63433
64349
  //#endregion
63434
64350
  //#region src/review/ReviewService.ts
63435
64351
  var ReviewService = class extends Context.Service()("@p4code/cli/review/ReviewService") {};
63436
- const make$17 = Effect.gen(function* () {
64352
+ const make$18 = Effect.gen(function* () {
63437
64353
  const config = yield* ServerConfig$1;
63438
64354
  const fileSystem = yield* FileSystem.FileSystem;
63439
64355
  const path = yield* Path.Path;
@@ -63489,7 +64405,7 @@ const make$17 = Effect.gen(function* () {
63489
64405
  });
63490
64406
  return ReviewService.of({ getDiffPreview });
63491
64407
  });
63492
- const layer$10 = Layer.effect(ReviewService, make$17);
64408
+ const layer$10 = Layer.effect(ReviewService, make$18);
63493
64409
  //#endregion
63494
64410
  //#region src/diagnostics/ProcessDiagnostics.ts
63495
64411
  const PROCESS_QUERY_TIMEOUT_MS = 1e3;
@@ -63784,7 +64700,7 @@ function assertDescendantPid(pid) {
63784
64700
  }));
63785
64701
  }));
63786
64702
  }
63787
- const make$16 = Effect.gen(function* () {
64703
+ const make$17 = Effect.gen(function* () {
63788
64704
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
63789
64705
  const read = Effect.gen(function* () {
63790
64706
  const readAt = yield* DateTime.now;
@@ -63828,7 +64744,7 @@ const make$16 = Effect.gen(function* () {
63828
64744
  signal
63829
64745
  });
63830
64746
  });
63831
- const layer$9 = Layer.effect(ProcessDiagnostics, make$16);
64747
+ const layer$9 = Layer.effect(ProcessDiagnostics, make$17);
63832
64748
  //#endregion
63833
64749
  //#region src/diagnostics/ProcessResourceMonitor.ts
63834
64750
  const SAMPLE_INTERVAL_MS = 5e3;
@@ -63979,7 +64895,7 @@ function aggregateProcessResourceHistory(input) {
63979
64895
  }) : Option.none()
63980
64896
  };
63981
64897
  }
63982
- const make$15 = Effect.gen(function* () {
64898
+ const make$16 = Effect.gen(function* () {
63983
64899
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
63984
64900
  const state = yield* Ref.make({
63985
64901
  samples: [],
@@ -64028,7 +64944,7 @@ const make$15 = Effect.gen(function* () {
64028
64944
  });
64029
64945
  return ProcessResourceMonitor.of({ readHistory });
64030
64946
  });
64031
- const layer$8 = Layer.effect(ProcessResourceMonitor, make$15);
64947
+ const layer$8 = Layer.effect(ProcessResourceMonitor, make$16);
64032
64948
  //#endregion
64033
64949
  //#region src/diagnostics/TraceDiagnostics.ts
64034
64950
  var TraceFileReadError = class extends Schema$1.TaggedErrorClass()("TraceFileReadError", {
@@ -64276,7 +65192,7 @@ function readTraceFile(fileSystem, path) {
64276
65192
  cause
64277
65193
  })) }));
64278
65194
  }
64279
- const make$14 = Effect.gen(function* () {
65195
+ const make$15 = Effect.gen(function* () {
64280
65196
  const fileSystem = yield* FileSystem.FileSystem;
64281
65197
  const read = Effect.fn("TraceDiagnostics.read")(function* (options) {
64282
65198
  const readAt = options.readAt ?? (yield* DateTime.now);
@@ -64320,7 +65236,7 @@ const make$14 = Effect.gen(function* () {
64320
65236
  });
64321
65237
  return TraceDiagnostics.of({ read });
64322
65238
  });
64323
- const layer$7 = Layer.effect(TraceDiagnostics, make$14);
65239
+ const layer$7 = Layer.effect(TraceDiagnostics, make$15);
64324
65240
  function readTraceDiagnostics(options) {
64325
65241
  return Effect.gen(function* () {
64326
65242
  return yield* (yield* TraceDiagnostics).read(options);
@@ -64344,7 +65260,7 @@ const VCS_PROBES = [{
64344
65260
  installHint: "Install Jujutsu with `brew install jj` or from https://github.com/jj-vcs/jj."
64345
65261
  }];
64346
65262
  var SourceControlDiscovery = class extends Context.Service()("@p4code/cli/sourceControl/SourceControlDiscovery") {};
64347
- const make$13 = Effect.gen(function* () {
65263
+ const make$14 = Effect.gen(function* () {
64348
65264
  const config = yield* ServerConfig$1;
64349
65265
  const process = yield* VcsProcess;
64350
65266
  const sourceControlProviders = yield* SourceControlProviderRegistry;
@@ -64393,7 +65309,7 @@ const make$13 = Effect.gen(function* () {
64393
65309
  sourceControlProviders: sourceControlProviders.discover
64394
65310
  }) });
64395
65311
  });
64396
- const layer$6 = Layer.effect(SourceControlDiscovery, make$13);
65312
+ const layer$6 = Layer.effect(SourceControlDiscovery, make$14);
64397
65313
  //#endregion
64398
65314
  //#region src/sourceControl/SourceControlRepositoryService.ts
64399
65315
  const isSourceControlRepositoryError = Schema$1.is(SourceControlRepositoryError);
@@ -64426,7 +65342,7 @@ function expandHomePath(input, path) {
64426
65342
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
64427
65343
  return input;
64428
65344
  }
64429
- const make$12 = Effect.gen(function* () {
65345
+ const make$13 = Effect.gen(function* () {
64430
65346
  const config = yield* ServerConfig$1;
64431
65347
  const fileSystem = yield* FileSystem.FileSystem;
64432
65348
  const git = yield* GitVcsDriver;
@@ -64565,7 +65481,7 @@ const make$12 = Effect.gen(function* () {
64565
65481
  publishRepository: (input) => publishRepository(input).pipe(mapRepositoryError("publishRepository", input.provider))
64566
65482
  });
64567
65483
  });
64568
- const layer$5 = Layer.effect(SourceControlRepositoryService, make$12);
65484
+ const layer$5 = Layer.effect(SourceControlRepositoryService, make$13);
64569
65485
  //#endregion
64570
65486
  //#region src/ws.ts
64571
65487
  /** Matches `p4c hub token add`, so a token minted here and one minted there are the same thing. */
@@ -64671,6 +65587,7 @@ function isThreadDetailEvent(event) {
64671
65587
  }
64672
65588
  const PROVIDER_STATUS_DEBOUNCE_MS = 200;
64673
65589
  const SHELL_RESUME_MAX_GAP = 1e3;
65590
+ const THREAD_RESUME_MAX_GAP = 1e3;
64674
65591
  const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
64675
65592
  [ORCHESTRATION_WS_METHODS.dispatchCommand, AuthOrchestrationOperateScope],
64676
65593
  [ORCHESTRATION_WS_METHODS.getTurnDiff, AuthOrchestrationReadScope],
@@ -64757,6 +65674,7 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
64757
65674
  [WS_METHODS.projectsListEntries, AuthOrchestrationReadScope],
64758
65675
  [WS_METHODS.projectsReadFile, AuthOrchestrationReadScope],
64759
65676
  [WS_METHODS.projectsSearchEntries, AuthOrchestrationReadScope],
65677
+ [WS_METHODS.projectsSearchContents, AuthOrchestrationReadScope],
64760
65678
  [WS_METHODS.projectsWriteFile, AuthOrchestrationOperateScope],
64761
65679
  [WS_METHODS.shellOpenInEditor, AuthOrchestrationOperateScope],
64762
65680
  [WS_METHODS.filesystemBrowse, AuthOrchestrationReadScope],
@@ -65373,15 +66291,19 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
65373
66291
  const bufferedLiveStream = liveBuffer.stream;
65374
66292
  if (input.afterSequence !== void 0) {
65375
66293
  const afterSequence = input.afterSequence;
65376
- const catchUpStream = orchestrationEngine.readEvents(afterSequence, Number.MAX_SAFE_INTEGER).pipe(Stream.filter(isThisThreadDetailEvent), Stream.map((event) => ({
65377
- kind: "event",
65378
- event: projectActivityEvent(event)
65379
- })), Stream.mapError((cause) => new OrchestrationGetSnapshotError({
65380
- message: `Failed to replay thread ${input.threadId} events`,
65381
- cause
65382
- })));
65383
- 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;
65384
- 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
+ }
65385
66307
  }
65386
66308
  const snapshot = yield* projectionSnapshotQuery.getThreadDetailSnapshot(input.threadId, input.turnLimit === void 0 ? void 0 : { turnLimit: input.turnLimit }).pipe(Effect.mapError((cause) => new OrchestrationGetSnapshotError({
65387
66309
  message: `Failed to load thread ${input.threadId}`,
@@ -65649,6 +66571,13 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
65649
66571
  ...projectEntriesFailureContext(cause),
65650
66572
  cause
65651
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" }),
65652
66581
  [WS_METHODS.projectsListEntries]: (input) => observeRpcEffect$1(WS_METHODS.projectsListEntries, workspaceEntries.list(input).pipe(Effect.mapError((cause) => new ProjectListEntriesError({
65653
66582
  ...input,
65654
66583
  ...projectEntriesFailureContext(cause),
@@ -65869,7 +66798,7 @@ function toPersistenceSqlOrDecodeError(sqlOperation, decodeOperation, correlatio
65869
66798
  cause
65870
66799
  });
65871
66800
  }
65872
- const make$11 = Effect.gen(function* () {
66801
+ const make$12 = Effect.gen(function* () {
65873
66802
  const sql = yield* SqlClient.SqlClient;
65874
66803
  const upsertRuntimeRow = SqlSchema.void({
65875
66804
  Request: ProviderSessionRuntimeDbRowSchema,
@@ -65972,7 +66901,7 @@ const make$11 = Effect.gen(function* () {
65972
66901
  deleteByThreadId
65973
66902
  };
65974
66903
  });
65975
- const layer$4 = Layer.effect(ProviderSessionRuntimeRepository, make$11);
66904
+ const layer$4 = Layer.effect(ProviderSessionRuntimeRepository, make$12);
65976
66905
  //#endregion
65977
66906
  //#region src/provider/Errors.ts
65978
66907
  /**
@@ -66624,7 +67553,7 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66624
67553
  const httpServer = yield* HttpServer.HttpServer;
66625
67554
  const state = yield* SynchronizedRef.make({
66626
67555
  records: /* @__PURE__ */ new Map(),
66627
- spawnedThreadIds: /* @__PURE__ */ new Set()
67556
+ spawnedThreads: /* @__PURE__ */ new Map()
66628
67557
  });
66629
67558
  const currentTimeMillis = options.now ? Effect.sync(options.now) : Clock.currentTimeMillis;
66630
67559
  const livenessWindowMs = options.livenessWindowMs ?? DEFAULT_LIVENESS_WINDOW_MS;
@@ -66649,7 +67578,7 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66649
67578
  if (watchThreadIds.size > 0) capabilities.add("watch");
66650
67579
  if (adviseThreadIds.size > 0) capabilities.add("advise");
66651
67580
  const threadId = ThreadId.make(request.threadId);
66652
- const scopeWith = (mayCreateThreads) => ({
67581
+ const scopeWith = (mayCreateThreads, mayCreateFusionPairs) => ({
66653
67582
  environmentId,
66654
67583
  threadId,
66655
67584
  providerSessionId,
@@ -66658,19 +67587,20 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66658
67587
  ...watchThreadIds.size > 0 ? { watchThreadIds } : {},
66659
67588
  ...adviseThreadIds.size > 0 ? { adviseThreadIds } : {},
66660
67589
  mayCreateThreads,
67590
+ mayCreateFusionPairs,
66661
67591
  issuedAt
66662
67592
  });
66663
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67593
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66664
67594
  const next = new Map(pruneDead(records, issuedAt));
66665
67595
  next.set(tokenHash, {
66666
67596
  tokenHash,
66667
- scope: scopeWith(!spawnedThreadIds.has(threadId)),
67597
+ scope: scopeWith(!spawnedThreads.has(threadId), spawnedThreads.get(threadId) === true),
66668
67598
  controlThreadIds: /* @__PURE__ */ new Set(),
66669
67599
  lastAliveAt: issuedAt
66670
67600
  });
66671
67601
  return {
66672
67602
  records: next,
66673
- spawnedThreadIds
67603
+ spawnedThreads
66674
67604
  };
66675
67605
  });
66676
67606
  return { config: {
@@ -66686,12 +67616,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66686
67616
  if (rawToken.length === 0) return void 0;
66687
67617
  const tokenHash = yield* hashToken(rawToken);
66688
67618
  const timestamp = yield* currentTimeMillis;
66689
- return yield* SynchronizedRef.modify(state, ({ records, spawnedThreadIds }) => {
67619
+ return yield* SynchronizedRef.modify(state, ({ records, spawnedThreads }) => {
66690
67620
  const current = pruneDead(records, timestamp);
66691
67621
  const record = current.get(tokenHash);
66692
67622
  if (!record) return [void 0, {
66693
67623
  records: current,
66694
- spawnedThreadIds
67624
+ spawnedThreads
66695
67625
  }];
66696
67626
  const next = new Map(current);
66697
67627
  next.set(tokenHash, {
@@ -66705,13 +67635,13 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66705
67635
  watchThreadIds: /* @__PURE__ */ new Set([...record.scope.watchThreadIds ?? [], ...record.controlThreadIds])
66706
67636
  }, {
66707
67637
  records: next,
66708
- spawnedThreadIds
67638
+ spawnedThreads
66709
67639
  }];
66710
67640
  });
66711
67641
  });
66712
67642
  const touch = Effect.fn("McpSessionRegistry.touch")(function* (threadId) {
66713
67643
  const timestamp = yield* currentTimeMillis;
66714
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67644
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66715
67645
  const current = pruneDead(records, timestamp);
66716
67646
  const next = new Map(current);
66717
67647
  for (const [tokenHash, record] of current) if (record.scope.threadId === threadId) next.set(tokenHash, {
@@ -66720,12 +67650,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66720
67650
  });
66721
67651
  return {
66722
67652
  records: next,
66723
- spawnedThreadIds
67653
+ spawnedThreads
66724
67654
  };
66725
67655
  });
66726
67656
  });
66727
67657
  const grantWatchThread = Effect.fn("McpSessionRegistry.grantWatchThread")(function* ({ watcherThreadId, watchedThreadId }) {
66728
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67658
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66729
67659
  const next = new Map(records);
66730
67660
  for (const [tokenHash, record] of records) {
66731
67661
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66740,12 +67670,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66740
67670
  }
66741
67671
  return {
66742
67672
  records: next,
66743
- spawnedThreadIds
67673
+ spawnedThreads
66744
67674
  };
66745
67675
  });
66746
67676
  });
66747
67677
  const revokeWatchThread = Effect.fn("McpSessionRegistry.revokeWatchThread")(function* ({ watcherThreadId, watchedThreadId }) {
66748
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67678
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66749
67679
  const next = new Map(records);
66750
67680
  for (const [tokenHash, record] of records) {
66751
67681
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66768,12 +67698,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66768
67698
  }
66769
67699
  return {
66770
67700
  records: next,
66771
- spawnedThreadIds
67701
+ spawnedThreads
66772
67702
  };
66773
67703
  });
66774
67704
  });
66775
67705
  const grantAdviseThread = Effect.fn("McpSessionRegistry.grantAdviseThread")(function* ({ watcherThreadId, advisedThreadId }) {
66776
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67706
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66777
67707
  const next = new Map(records);
66778
67708
  for (const [tokenHash, record] of records) {
66779
67709
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66788,12 +67718,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66788
67718
  }
66789
67719
  return {
66790
67720
  records: next,
66791
- spawnedThreadIds
67721
+ spawnedThreads
66792
67722
  };
66793
67723
  });
66794
67724
  });
66795
67725
  const revokeAdviseThread = Effect.fn("McpSessionRegistry.revokeAdviseThread")(function* ({ watcherThreadId, advisedThreadId }) {
66796
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67726
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66797
67727
  const next = new Map(records);
66798
67728
  for (const [tokenHash, record] of records) {
66799
67729
  if (record.scope.threadId !== watcherThreadId) continue;
@@ -66816,12 +67746,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66816
67746
  }
66817
67747
  return {
66818
67748
  records: next,
66819
- spawnedThreadIds
67749
+ spawnedThreads
66820
67750
  };
66821
67751
  });
66822
67752
  });
66823
67753
  const recordSpawnedThread = Effect.fn("McpSessionRegistry.recordSpawnedThread")(function* (input) {
66824
- yield* SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => {
67754
+ yield* SynchronizedRef.update(state, ({ records, spawnedThreads }) => {
66825
67755
  const next = new Map(records);
66826
67756
  for (const [tokenHash, record] of records) if (record.scope.providerSessionId === input.providerSessionId) next.set(tokenHash, {
66827
67757
  ...record,
@@ -66829,13 +67759,13 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66829
67759
  });
66830
67760
  return {
66831
67761
  records: next,
66832
- spawnedThreadIds: /* @__PURE__ */ new Set([...spawnedThreadIds, input.threadId])
67762
+ spawnedThreads: new Map([...spawnedThreads, [input.threadId, input.mayCreateFusionPairs === true]])
66833
67763
  };
66834
67764
  });
66835
67765
  });
66836
- const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records, spawnedThreadIds }) => ({
67766
+ const revokeWhere = (predicate) => SynchronizedRef.update(state, ({ records, spawnedThreads }) => ({
66837
67767
  records: new Map(Array.from(records).filter(([, record]) => !predicate(record))),
66838
- spawnedThreadIds
67768
+ spawnedThreads
66839
67769
  }));
66840
67770
  return McpSessionRegistry.of({
66841
67771
  issue,
@@ -66852,19 +67782,19 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
66852
67782
  revokeThread: Effect.fn("McpSessionRegistry.revokeThread")(function* (threadId) {
66853
67783
  yield* revokeWhere((record) => record.scope.threadId === threadId);
66854
67784
  }),
66855
- revokeAll: SynchronizedRef.update(state, ({ spawnedThreadIds }) => ({
67785
+ revokeAll: SynchronizedRef.update(state, ({ spawnedThreads }) => ({
66856
67786
  records: /* @__PURE__ */ new Map(),
66857
- spawnedThreadIds
67787
+ spawnedThreads
66858
67788
  }))
66859
67789
  });
66860
67790
  });
66861
67791
  let activeMcpSessionRegistry;
66862
- 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(() => {
66863
67793
  activeMcpSessionRegistry = registry;
66864
67794
  }))), (registry) => Effect.sync(() => {
66865
67795
  if (activeMcpSessionRegistry === registry) activeMcpSessionRegistry = void 0;
66866
67796
  }));
66867
- const layer$3 = Layer.effect(McpSessionRegistry, make$10);
67797
+ const layer$3 = Layer.effect(McpSessionRegistry, make$11);
66868
67798
  const issueActiveMcpCredential = (request) => activeMcpSessionRegistry ? activeMcpSessionRegistry.revokeThread(request.threadId).pipe(Effect.andThen(activeMcpSessionRegistry.issue(request))) : Effect.sync(() => void 0);
66869
67799
  /**
66870
67800
  * Refreshes the liveness of a thread's MCP credential. Called on every provider
@@ -67573,6 +68503,13 @@ const makeProviderSessionReaper = (options) => Effect.gen(function* () {
67573
68503
  });
67574
68504
  continue;
67575
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
+ }
67576
68513
  if (yield* providerService.stopSession({ threadId: binding.threadId }).pipe(Effect.tap(() => Effect.logInfo("provider.session.reaped", {
67577
68514
  threadId: binding.threadId,
67578
68515
  provider: binding.provider,
@@ -67590,14 +68527,18 @@ const makeProviderSessionReaper = (options) => Effect.gen(function* () {
67590
68527
  totalBindings: bindings.length
67591
68528
  });
67592
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 })));
67593
68531
  const start = () => Effect.gen(function* () {
67594
- 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)))));
67595
68533
  yield* Effect.logInfo("provider.session.reaper.started", {
67596
68534
  inactivityThresholdMs,
67597
68535
  sweepIntervalMs
67598
68536
  });
67599
68537
  });
67600
- return { start };
68538
+ return {
68539
+ start,
68540
+ sweep: sweepSafely
68541
+ };
67601
68542
  });
67602
68543
  const makeProviderSessionReaperLive = (options) => Layer.effect(ProviderSessionReaper, makeProviderSessionReaper(options));
67603
68544
  const ProviderSessionReaperLive = makeProviderSessionReaperLive();
@@ -69779,7 +70720,7 @@ function formatAskUserQuestionAnswers(answers) {
69779
70720
  * only a one-line reference and the mutable `[fusion-pair]` metadata.
69780
70721
  */
69781
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.`;
69782
- 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.`;
69783
70724
  /**
69784
70725
  * The one-line stand-in for the full block on a message whose session already
69785
70726
  * carries the role instructions.
@@ -91040,7 +91981,7 @@ const makeTerminationError$1 = (handle) => Effect.match(handle.exitCode, {
91040
91981
  //#endregion
91041
91982
  //#region ../../packages/effect-codex-app-server/src/client.ts
91042
91983
  var CodexAppServerClient = class extends Context.Service()("effect-codex-app-server/client/CodexAppServerClient") {};
91043
- 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) {
91044
91985
  const requestHandlers = /* @__PURE__ */ new Map();
91045
91986
  const notificationHandlers = /* @__PURE__ */ new Map();
91046
91987
  let unknownRequestHandler;
@@ -91107,7 +92048,7 @@ const make$9 = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(fu
91107
92048
  const layerChildProcess$1 = (handle, options = {}) => Layer.effect(CodexAppServerClient, makeChildProcessClient(handle, options));
91108
92049
  const makeChildProcessClient = Effect.fn("effect-codex-app-server/CodexAppServerClient.makeChildProcessClient")(function* (handle, options) {
91109
92050
  yield* Stream.runDrain(handle.stderr).pipe(Effect.ignore, Effect.forkScoped);
91110
- return yield* make$9(makeChildStdio$1(handle), options, makeTerminationError$1(handle));
92051
+ return yield* make$10(makeChildStdio$1(handle), options, makeTerminationError$1(handle));
91111
92052
  });
91112
92053
  const resolveCodexLaunchArgs = (launchArgs, environment = process.env) => environment["P4CODE_CODEX_LAUNCH_ARGS"]?.trim() || launchArgs?.trim() || "";
91113
92054
  const codexLaunchArgv = (launchArgs) => tokenizeCliArgs(launchArgs);
@@ -97912,7 +98853,7 @@ const makeTerminationError = (handle) => Effect.match(handle.exitCode, {
97912
98853
  //#endregion
97913
98854
  //#region ../../packages/effect-acp/src/client.ts
97914
98855
  var AcpClient = class extends Context.Service()("effect-acp/client/AcpClient") {};
97915
- 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) {
97916
98857
  const coreHandlers = {};
97917
98858
  const notificationHandlers = {
97918
98859
  sessionUpdate: {
@@ -98070,7 +99011,7 @@ const make$8 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options
98070
99011
  const layerChildProcess = (handle, options = {}) => {
98071
99012
  const stdio = makeChildStdio(handle);
98072
99013
  const terminationError = makeTerminationError(handle);
98073
- return Layer.effect(AcpClient, make$8(stdio, options, terminationError));
99014
+ return Layer.effect(AcpClient, make$9(stdio, options, terminationError));
98074
99015
  };
98075
99016
  //#endregion
98076
99017
  //#region ../../packages/shared/src/toolActivity.ts
@@ -98530,7 +99471,7 @@ function formatConfigOptionValue(value) {
98530
99471
  const defaultSessionLoadTimeout = Duration.seconds(90);
98531
99472
  const defaultSessionLoadReplayIdleGap = Duration.seconds(2);
98532
99473
  var AcpSessionRuntime = class extends Context.Service()("@p4code/cli/provider/acp/AcpSessionRuntime") {};
98533
- const make$7 = (options) => Effect.gen(function* () {
99474
+ const make$8 = (options) => Effect.gen(function* () {
98534
99475
  const crypto = yield* Crypto.Crypto;
98535
99476
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
98536
99477
  const runtimeScope = yield* Scope.Scope;
@@ -98846,7 +99787,7 @@ const make$7 = (options) => Effect.gen(function* () {
98846
99787
  notify: acp.raw.notify
98847
99788
  };
98848
99789
  });
98849
- const layer$2 = (options) => Layer.effect(AcpSessionRuntime, make$7(options));
99790
+ const layer$2 = (options) => Layer.effect(AcpSessionRuntime, make$8(options));
98850
99791
  function sessionConfigOptionsFromSetup(response) {
98851
99792
  return response?.configOptions ?? [];
98852
99793
  }
@@ -106307,7 +107248,7 @@ const stringField = (record, key) => {
106307
107248
  const value = record[key];
106308
107249
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
106309
107250
  };
106310
- const make$6 = Effect.gen(function* () {
107251
+ const make$7 = Effect.gen(function* () {
106311
107252
  const linear = yield* LinearClient;
106312
107253
  return { resolve: Effect.fn("TicketResolver.resolve")(function* (reference) {
106313
107254
  const identifier = parseTicketReference(reference);
@@ -106338,7 +107279,7 @@ const make$6 = Effect.gen(function* () {
106338
107279
  };
106339
107280
  }) };
106340
107281
  });
106341
- const layer$1 = Layer.effect(TicketResolver, make$6);
107282
+ const layer$1 = Layer.effect(TicketResolver, make$7);
106342
107283
  //#endregion
106343
107284
  //#region src/mcp/toolkits/tasks/tools.ts
106344
107285
  const dependencies = [McpInvocationContext, TaskRepository];
@@ -106877,7 +107818,7 @@ const AskUserQuestionTool = Tool.make("ask_user_question", {
106877
107818
  ]
106878
107819
  }).annotate(Tool.Title, "Ask user question").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
106879
107820
  const ThreadSpawnTool = Tool.make("thread_spawn", {
106880
- 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.",
106881
107822
  parameters: ThreadSpawnInput,
106882
107823
  success: ThreadSpawnResult,
106883
107824
  failure: ThreadControlToolError,
@@ -106885,20 +107826,19 @@ const ThreadSpawnTool = Tool.make("thread_spawn", {
106885
107826
  McpInvocationContext,
106886
107827
  McpSessionRegistry,
106887
107828
  OrchestrationEngineService,
106888
- ProjectionSnapshotQuery,
106889
107829
  ProjectionThreadRepository,
107830
+ ServerSettingsService,
106890
107831
  Crypto.Crypto
106891
107832
  ]
106892
107833
  }).annotate(Tool.Title, "Start a thread").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
106893
107834
  const ThreadPairCreateTool = Tool.make("thread_pair_create", {
106894
- 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.",
106895
107836
  parameters: ThreadPairCreateInput,
106896
107837
  success: ThreadPairCreateResult,
106897
107838
  failure: ThreadControlToolError,
106898
107839
  dependencies: [
106899
107840
  McpInvocationContext,
106900
107841
  OrchestrationEngineService,
106901
- ProjectionSnapshotQuery,
106902
107842
  Crypto.Crypto
106903
107843
  ]
106904
107844
  }).annotate(Tool.Title, "Create a Fusion pair").annotate(Tool.Readonly, false).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, false);
@@ -107073,29 +108013,6 @@ const newCommandId = Effect.gen(function* () {
107073
108013
  const crypto = yield* Crypto.Crypto;
107074
108014
  return CommandId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
107075
108015
  });
107076
- const fusionInvocationLine = /^(?:\/fusion|\$fusion)(?:\s+.*)?$/i;
107077
- const fusionAffirmativeLine = /^(?:approved?|yes(?:,?\s+(?:please|do it))?|ok(?:ay)?|go ahead|do it|proceed)[.!]?$/i;
107078
- function lastNonEmptyLine(text) {
107079
- return text.split(/\r?\n/).findLast((line) => line.trim().length > 0)?.trim() ?? "";
107080
- }
107081
- function directlyInvokesFusion(text) {
107082
- return text.split(/\r?\n/).some((line) => fusionInvocationLine.test(line.trim()));
107083
- }
107084
- function asksForFusionApproval(message) {
107085
- if (message?.role !== "assistant") return false;
107086
- const text = message.text.toLowerCase();
107087
- return text.includes("fusion") && (text.includes("approve") || text.includes("approval"));
107088
- }
107089
- const requireFusionApproval = Effect.fn("mcp.threads.requireFusionApproval")(function* (threadId) {
107090
- const thread = yield* (yield* ProjectionSnapshotQuery).getThreadDetailById(threadId).pipe(Effect.mapError(() => new ThreadPairApprovalRequiredError({ threadId })));
107091
- if (Option.isNone(thread)) return yield* new ThreadPairApprovalRequiredError({ threadId });
107092
- const latestUserIndex = thread.value.messages.findLastIndex((message) => message.role === "user");
107093
- const latestUser = latestUserIndex >= 0 ? thread.value.messages[latestUserIndex] : void 0;
107094
- if (latestUser !== void 0 && directlyInvokesFusion(latestUser.text)) return;
107095
- const precedingAssistant = thread.value.messages.slice(0, latestUserIndex).findLast((message) => message.role === "assistant");
107096
- if (latestUser !== void 0 && fusionAffirmativeLine.test(lastNonEmptyLine(latestUser.text)) && asksForFusionApproval(precedingAssistant)) return;
107097
- return yield* new ThreadPairApprovalRequiredError({ threadId });
107098
- });
107099
108016
  /**
107100
108017
  * The model a spawned thread starts on.
107101
108018
  *
@@ -107220,8 +108137,11 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107220
108137
  }).pipe(Effect.ensuring(Effect.sync(() => forgetPendingMcpUserInput(invocation.threadId, requestId))));
107221
108138
  }),
107222
108139
  thread_spawn: (input) => Effect.gen(function* () {
107223
- const invocation = yield* requireThreadSpawn();
107224
- 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
+ });
107225
108145
  const registry = yield* McpSessionRegistry;
107226
108146
  const threads = yield* ProjectionThreadRepository;
107227
108147
  const crypto = yield* Crypto.Crypto;
@@ -107235,7 +108155,11 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107235
108155
  });
107236
108156
  const template = parent.value;
107237
108157
  const projectId = input.projectId ?? template.projectId;
107238
- 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);
107239
108163
  const runtimeMode = input.runtimeMode ?? template.runtimeMode;
107240
108164
  const interactionMode = input.interactionMode ?? template.interactionMode;
107241
108165
  const compressMode = input.compressMode ?? template.compressMode;
@@ -107259,8 +108183,42 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107259
108183
  }, threadId);
107260
108184
  yield* registry.recordSpawnedThread({
107261
108185
  providerSessionId: invocation.providerSessionId,
107262
- threadId
108186
+ threadId,
108187
+ ...input.fusionWatcher === true ? { mayCreateFusionPairs: true } : {}
107263
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
+ }
107264
108222
  yield* dispatchControl({
107265
108223
  type: "thread.turn.start",
107266
108224
  commandId: yield* newCommandId,
@@ -107282,12 +108240,12 @@ const ThreadToolkitHandlersLive = ThreadToolkit.toLayer({
107282
108240
  return {
107283
108241
  threadId,
107284
108242
  projectId,
107285
- title: input.title
108243
+ title: input.title,
108244
+ ...fusionPair
107286
108245
  };
107287
108246
  }),
107288
108247
  thread_pair_create: (input) => Effect.gen(function* () {
107289
108248
  const { invocation, threadId: watcherThreadId } = yield* requireThreadControlTarget(input.watcherThreadId);
107290
- yield* requireFusionApproval(invocation.threadId);
107291
108249
  const crypto = yield* Crypto.Crypto;
107292
108250
  const pairId = ThreadPairId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
107293
108251
  const continuationMessageId = MessageId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
@@ -107825,24 +108783,40 @@ const ThreadWatchEventsTool = Tool.make("thread_watch_events", {
107825
108783
  failure: ThreadWatchToolError,
107826
108784
  dependencies: [McpInvocationContext, ThreadEventStreamService]
107827
108785
  }).annotate(Tool.Title, "Read watched thread events").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true);
107828
- const WatchToolkit = Toolkit.make(ThreadWatchEventsTool);
107829
- const WatchToolkitHandlersLive = WatchToolkit.toLayer({ thread_watch_events: (input) => Effect.gen(function* () {
107830
- yield* requireWatchCapability(input.threadId);
107831
- const page = yield* (yield* ThreadEventStreamService).read({
107832
- threadId: input.threadId,
107833
- ...input.afterSequence !== void 0 ? { afterSequence: input.afterSequence } : {},
107834
- limit: input.limit ?? 50
107835
- }).pipe(Effect.mapError((cause) => new ThreadWatchFailedError({
107836
- threadId: input.threadId,
107837
- detail: cause.message
107838
- })));
107839
- return {
107840
- threadId: input.threadId,
107841
- events: page.events,
107842
- headSequence: page.headSequence,
107843
- hasMore: page.hasMore
107844
- };
107845
- }) });
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
+ });
107846
108820
  //#endregion
107847
108821
  //#region src/mcp/McpHttpServer.ts
107848
108822
  const unauthorized = HttpServerResponse.jsonUnsafe({
@@ -108017,6 +108991,100 @@ var ThreadDeletionReactor = class extends Context.Service()("@p4code/cli/orchest
108017
108991
  //#region src/orchestration/Services/FusionWatcherReactor.ts
108018
108992
  var FusionWatcherReactor = class extends Context.Service()("@p4code/cli/orchestration/Services/FusionWatcherReactor") {};
108019
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
108020
109088
  //#region src/orchestration/Services/ScheduledTaskReactor.ts
108021
109089
  /**
108022
109090
  * ScheduledTaskReactor - fires user-created scheduled tasks.
@@ -108037,6 +109105,7 @@ const makeOrchestrationReactor = Effect.gen(function* () {
108037
109105
  const threadDeletionReactor = yield* ThreadDeletionReactor;
108038
109106
  const fusionWatcherReactor = yield* FusionWatcherReactor;
108039
109107
  const scheduledTaskReactor = yield* ScheduledTaskReactor;
109108
+ const completionReactor = yield* ThreadCompletionReactor;
108040
109109
  return { start: Effect.fn("start")(function* () {
108041
109110
  yield* providerRuntimeIngestion.start();
108042
109111
  yield* providerCommandReactor.start();
@@ -108044,6 +109113,7 @@ const makeOrchestrationReactor = Effect.gen(function* () {
108044
109113
  yield* threadDeletionReactor.start();
108045
109114
  yield* fusionWatcherReactor.start();
108046
109115
  yield* scheduledTaskReactor.start();
109116
+ yield* completionReactor.start();
108047
109117
  }) };
108048
109118
  });
108049
109119
  const OrchestrationReactorLive = Layer.effect(OrchestrationReactor, makeOrchestrationReactor);
@@ -109437,7 +110507,7 @@ const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30);
109437
110507
  const DEFAULT_RUNTIME_MODE = "full-access";
109438
110508
  const DEFAULT_THREAD_TITLE = "New thread";
109439
110509
  const NON_SYSTEM_PROVIDER_STRUCTURED_USER_QUESTIONS = structuredUserQuestionPrompt("your provider's structured user-input question tool");
109440
- 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.`;
109441
110511
  const isFusionWatcherWakeMessageId = (messageId) => messageId.startsWith("fusion-review:") || messageId.startsWith("fusion-gate:");
109442
110512
  const findActiveFusionPair = (pairs, threadId) => (pairs ?? []).find((pair) => pair.detachedAt === null && (pair.implementerThreadId === threadId || pair.watcherThreadId === threadId));
109443
110513
  const fusionRoleForThread = (pairs, threadId) => {
@@ -111043,7 +112113,7 @@ After restart/context loss, derive phase from workspace git log/status, PR, rece
111043
112113
  const watcherPrompt = (input) => `${FUSION_REVIEW_PROMPT_PREFIX}
111044
112114
  Review completed builder turn ${input.implementerThreadId}.
111045
112115
 
111046
- 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.
111047
112117
 
111048
112118
  Always report concise:
111049
112119
 
@@ -111055,7 +112125,7 @@ Determine review boundary from builder's todo status:
111055
112125
 
111056
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.
111057
112127
  - Builder waiting on an external process or with nothing actionable: no thread_advise. Report the state here and end.
111058
- - 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.
111059
112129
 
111060
112130
  ${watcherPowers(input.implementerThreadId)}
111061
112131
 
@@ -111074,7 +112144,7 @@ const gateKindDescription = (gate) => {
111074
112144
  const gatePrompt = (input) => `${FUSION_GATE_PROMPT_PREFIX}
111075
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}.
111076
112146
 
111077
- 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.
111078
112148
 
111079
112149
  Then thread_gate_respond, threadId ${input.implementerThreadId}, gateId ${input.gate.id}:
111080
112150
 
@@ -112807,7 +113877,7 @@ const PlatformServicesLive = Layer.unwrap(Effect.gen(function* () {
112807
113877
  return layer;
112808
113878
  }
112809
113879
  }));
112810
- 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));
112811
113881
  const ProviderSessionDirectoryLayerLive = ProviderSessionDirectoryLive.pipe(Layer.provide(layer$4));
112812
113882
  const ProviderLayerLive = ProviderServiceLive.pipe(Layer.provide(ProviderAdapterRegistryLive), Layer.provideMerge(ProviderSessionDirectoryLayerLive));
112813
113883
  const PersistenceLayerLive = Layer.empty.pipe(Layer.provideMerge(layerConfig));