@p4code/cli 0.3.24 → 0.3.26

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
@@ -127,7 +127,7 @@ const closeServer = (server) => {
127
127
  * NetService - Service tag for startup networking helpers.
128
128
  */
129
129
  var NetService = class extends Context.Service()("@p4code/shared/Net/NetService") {};
130
- const make$91 = () => {
130
+ const make$92 = () => {
131
131
  /**
132
132
  * Returns true when a TCP server can bind to {host, port}.
133
133
  * `EADDRNOTAVAIL` is treated as available so IPv6-absent hosts don't fail
@@ -236,10 +236,10 @@ const make$91 = () => {
236
236
  })
237
237
  };
238
238
  };
239
- const layer$82 = Layer.sync(NetService, make$91);
239
+ const layer$82 = Layer.sync(NetService, make$92);
240
240
  //#endregion
241
241
  //#region package.json
242
- var version = "0.3.24";
242
+ var version = "0.3.26";
243
243
  //#endregion
244
244
  //#region src/config.ts
245
245
  /**
@@ -260,8 +260,8 @@ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerC
260
260
  /** @deprecated Import and use `layerTest` from this module. */
261
261
  static layerTest = (cwd, baseDirOrPrefix) => layerTest$3(cwd, baseDirOrPrefix);
262
262
  };
263
- const make$90 = (config) => ServerConfig$1.of(config);
264
- const layer$81 = (config) => Layer.succeed(ServerConfig$1, make$90(config));
263
+ const make$91 = (config) => ServerConfig$1.of(config);
264
+ const layer$81 = (config) => Layer.succeed(ServerConfig$1, make$91(config));
265
265
  const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
266
266
  const { join } = yield* Path.Path;
267
267
  const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
@@ -1333,6 +1333,12 @@ const ExecutionEnvironmentCapabilities = Schema$1.Struct({
1333
1333
  /** Server understands thread.pin / thread.unpin / thread.pin.reorder
1334
1334
  commands. Same version-skew contract as threadSettlement. */
1335
1335
  threadPinning: Schema$1.optionalKey(Schema$1.Boolean),
1336
+ /** Server understands the thread.fork command. Same version-skew contract
1337
+ as threadSettlement. */
1338
+ threadFork: Schema$1.optionalKey(Schema$1.Boolean),
1339
+ /** Server understands thread.scheduled-task.create / cancel and fires
1340
+ pending tasks. Same version-skew contract as threadSettlement. */
1341
+ threadScheduledTasks: Schema$1.optionalKey(Schema$1.Boolean),
1336
1342
  /** Server exposes the pull-request list, detail, activity, diff, and mutation APIs. Absent on
1337
1343
  servers from before the pull-request workspace shipped, so clients must not probe them. */
1338
1344
  pullRequests: Schema$1.optionalKey(Schema$1.Boolean),
@@ -1595,7 +1601,7 @@ const PREFERRED_DEFAULT_CODEX_MODELS = ["gpt-5.6-sol", "gpt-5.6-terra"];
1595
1601
  const DEFAULT_TEXT_GENERATION_MODEL = "gpt-5.6-luna";
1596
1602
  const DEFAULT_MODEL_BY_PROVIDER = {
1597
1603
  [CODEX_DRIVER_KIND]: DEFAULT_MODEL,
1598
- [CLAUDE_DRIVER_KIND]: "claude-sonnet-5",
1604
+ [CLAUDE_DRIVER_KIND]: "claude-fable-5-1",
1599
1605
  [CURSOR_DRIVER_KIND]: "auto",
1600
1606
  [GROK_DRIVER_KIND$1]: "grok-build",
1601
1607
  [MUSE_DRIVER_KIND]: "muse-spark-1.2",
@@ -1877,6 +1883,31 @@ const OrchestrationProposedPlan = Schema$1.Struct({
1877
1883
  createdAt: IsoDateTime,
1878
1884
  updatedAt: IsoDateTime
1879
1885
  });
1886
+ const OrchestrationScheduledTaskId = TrimmedNonEmptyString;
1887
+ const OrchestrationScheduledTaskStatus = Schema$1.Literals([
1888
+ "pending",
1889
+ "fired",
1890
+ "failed",
1891
+ "cancelled"
1892
+ ]);
1893
+ /**
1894
+ * A prompt the server sends to a thread at a chosen time. Pending tasks are
1895
+ * the live ones: firing starts a turn with `prompt`, cancelling is the way
1896
+ * out before that. A task whose turn could not start after bounded retries
1897
+ * ends `failed` with `failure` explaining why; it can be dismissed or
1898
+ * scheduled again from the client.
1899
+ */
1900
+ const OrchestrationScheduledTask = Schema$1.Struct({
1901
+ id: OrchestrationScheduledTaskId,
1902
+ prompt: TrimmedNonEmptyString,
1903
+ runAt: IsoDateTime,
1904
+ status: OrchestrationScheduledTaskStatus,
1905
+ createdAt: IsoDateTime,
1906
+ updatedAt: IsoDateTime,
1907
+ firedAt: Schema$1.NullOr(IsoDateTime).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
1908
+ cancelledAt: Schema$1.NullOr(IsoDateTime).pipe(Schema$1.withDecodingDefault(Effect.succeed(null))),
1909
+ failure: Schema$1.NullOr(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed(null)))
1910
+ });
1880
1911
  const SourceProposedPlanReference = Schema$1.Struct({
1881
1912
  threadId: ThreadId,
1882
1913
  planId: OrchestrationProposedPlanId
@@ -1996,6 +2027,7 @@ const OrchestrationThread = Schema$1.Struct({
1996
2027
  deletedAt: Schema$1.NullOr(IsoDateTime),
1997
2028
  messages: Schema$1.Array(OrchestrationMessage),
1998
2029
  proposedPlans: Schema$1.Array(OrchestrationProposedPlan).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
2030
+ scheduledTasks: Schema$1.optional(Schema$1.Array(OrchestrationScheduledTask)),
1999
2031
  activities: Schema$1.Array(OrchestrationThreadActivity),
2000
2032
  checkpoints: Schema$1.Array(OrchestrationCheckpointSummary),
2001
2033
  session: Schema$1.NullOr(OrchestrationSession)
@@ -2266,6 +2298,44 @@ const ThreadCreateCommand = Schema$1.Struct({
2266
2298
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
2267
2299
  createdAt: IsoDateTime
2268
2300
  });
2301
+ /**
2302
+ * Creates a new thread from an existing one. The decider copies the source
2303
+ * thread's configuration and the projections copy its conversation, so the
2304
+ * fork opens with the same context while the source stays untouched.
2305
+ */
2306
+ const ThreadForkCommand = Schema$1.Struct({
2307
+ type: Schema$1.Literal("thread.fork"),
2308
+ commandId: CommandId,
2309
+ sourceThreadId: ThreadId,
2310
+ threadId: ThreadId,
2311
+ title: Schema$1.optional(TrimmedNonEmptyString),
2312
+ createdAt: IsoDateTime
2313
+ });
2314
+ const ThreadScheduledTaskCreateCommand = Schema$1.Struct({
2315
+ type: Schema$1.Literal("thread.scheduled-task.create"),
2316
+ commandId: CommandId,
2317
+ threadId: ThreadId,
2318
+ taskId: OrchestrationScheduledTaskId,
2319
+ prompt: TrimmedNonEmptyString,
2320
+ runAt: IsoDateTime,
2321
+ createdAt: IsoDateTime
2322
+ });
2323
+ const ThreadScheduledTaskCancelCommand = Schema$1.Struct({
2324
+ type: Schema$1.Literal("thread.scheduled-task.cancel"),
2325
+ commandId: CommandId,
2326
+ threadId: ThreadId,
2327
+ taskId: OrchestrationScheduledTaskId,
2328
+ cancelledAt: IsoDateTime
2329
+ });
2330
+ /** Server-only: records that the scheduler fired (or failed to fire) a task. */
2331
+ const ThreadScheduledTaskFireCommand = Schema$1.Struct({
2332
+ type: Schema$1.Literal("thread.scheduled-task.fire"),
2333
+ commandId: CommandId,
2334
+ threadId: ThreadId,
2335
+ taskId: OrchestrationScheduledTaskId,
2336
+ firedAt: IsoDateTime,
2337
+ failure: Schema$1.optional(Schema$1.String)
2338
+ });
2269
2339
  const ThreadDeleteCommand = Schema$1.Struct({
2270
2340
  type: Schema$1.Literal("thread.delete"),
2271
2341
  commandId: CommandId,
@@ -2513,6 +2583,9 @@ const DispatchableClientOrchestrationCommand = Schema$1.Union([
2513
2583
  ProjectMetaUpdateCommand,
2514
2584
  ProjectDeleteCommand,
2515
2585
  ThreadCreateCommand,
2586
+ ThreadForkCommand,
2587
+ ThreadScheduledTaskCreateCommand,
2588
+ ThreadScheduledTaskCancelCommand,
2516
2589
  ThreadDeleteCommand,
2517
2590
  ThreadArchiveCommand,
2518
2591
  ThreadUnarchiveCommand,
@@ -2544,6 +2617,9 @@ const ClientOrchestrationCommand = Schema$1.Union([
2544
2617
  ProjectMetaUpdateCommand,
2545
2618
  ProjectDeleteCommand,
2546
2619
  ThreadCreateCommand,
2620
+ ThreadForkCommand,
2621
+ ThreadScheduledTaskCreateCommand,
2622
+ ThreadScheduledTaskCancelCommand,
2547
2623
  ThreadDeleteCommand,
2548
2624
  ThreadArchiveCommand,
2549
2625
  ThreadUnarchiveCommand,
@@ -2698,7 +2774,8 @@ const InternalOrchestrationCommand = Schema$1.Union([
2698
2774
  ThreadProposedPlanUpsertCommand,
2699
2775
  ThreadTurnDiffCompleteCommand,
2700
2776
  ThreadActivityAppendCommand,
2701
- ThreadRevertCompleteCommand
2777
+ ThreadRevertCompleteCommand,
2778
+ ThreadScheduledTaskFireCommand
2702
2779
  ]);
2703
2780
  Schema$1.Union([DispatchableClientOrchestrationCommand, InternalOrchestrationCommand]);
2704
2781
  const OrchestrationEventType = Schema$1.Literals([
@@ -2732,6 +2809,9 @@ const OrchestrationEventType = Schema$1.Literals([
2732
2809
  "thread.session-force-stop-requested",
2733
2810
  "thread.session-set",
2734
2811
  "thread.proposed-plan-upserted",
2812
+ "thread.scheduled-task.created",
2813
+ "thread.scheduled-task.cancelled",
2814
+ "thread.scheduled-task.fired",
2735
2815
  "thread.turn-diff-completed",
2736
2816
  "thread.activity-appended",
2737
2817
  "thread.turn-completed",
@@ -2786,6 +2866,11 @@ const ThreadCreatedPayload$1 = Schema$1.Struct({
2786
2866
  unpromptedSubagents: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
2787
2867
  branch: Schema$1.NullOr(TrimmedNonEmptyString),
2788
2868
  worktreePath: Schema$1.NullOr(TrimmedNonEmptyString),
2869
+ /**
2870
+ * Set when the thread was forked: projections copy the source thread's
2871
+ * messages into this one. Optional so events from pre-fork servers decode.
2872
+ */
2873
+ forkedFromThreadId: Schema$1.optional(ThreadId),
2789
2874
  createdAt: IsoDateTime,
2790
2875
  updatedAt: IsoDateTime
2791
2876
  });
@@ -2998,6 +3083,21 @@ const ThreadProposedPlanUpsertedPayload$1 = Schema$1.Struct({
2998
3083
  threadId: ThreadId,
2999
3084
  proposedPlan: OrchestrationProposedPlan
3000
3085
  });
3086
+ const ThreadScheduledTaskCreatedPayload$1 = Schema$1.Struct({
3087
+ threadId: ThreadId,
3088
+ task: OrchestrationScheduledTask
3089
+ });
3090
+ const ThreadScheduledTaskCancelledPayload$1 = Schema$1.Struct({
3091
+ threadId: ThreadId,
3092
+ taskId: OrchestrationScheduledTaskId,
3093
+ cancelledAt: IsoDateTime
3094
+ });
3095
+ const ThreadScheduledTaskFiredPayload$1 = Schema$1.Struct({
3096
+ threadId: ThreadId,
3097
+ taskId: OrchestrationScheduledTaskId,
3098
+ firedAt: IsoDateTime,
3099
+ failure: Schema$1.NullOr(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed(null)))
3100
+ });
3001
3101
  const ThreadTurnDiffCompletedPayload$1 = Schema$1.Struct({
3002
3102
  threadId: ThreadId,
3003
3103
  turnId: TurnId,
@@ -3185,6 +3285,21 @@ const OrchestrationEvent = Schema$1.Union([
3185
3285
  type: Schema$1.Literal("thread.proposed-plan-upserted"),
3186
3286
  payload: ThreadProposedPlanUpsertedPayload$1
3187
3287
  }),
3288
+ Schema$1.Struct({
3289
+ ...EventBaseFields,
3290
+ type: Schema$1.Literal("thread.scheduled-task.created"),
3291
+ payload: ThreadScheduledTaskCreatedPayload$1
3292
+ }),
3293
+ Schema$1.Struct({
3294
+ ...EventBaseFields,
3295
+ type: Schema$1.Literal("thread.scheduled-task.cancelled"),
3296
+ payload: ThreadScheduledTaskCancelledPayload$1
3297
+ }),
3298
+ Schema$1.Struct({
3299
+ ...EventBaseFields,
3300
+ type: Schema$1.Literal("thread.scheduled-task.fired"),
3301
+ payload: ThreadScheduledTaskFiredPayload$1
3302
+ }),
3188
3303
  Schema$1.Struct({
3189
3304
  ...EventBaseFields,
3190
3305
  type: Schema$1.Literal("thread.turn-diff-completed"),
@@ -5163,6 +5278,11 @@ const PREVIEW_AUTOMATION_OPERATIONS = [
5163
5278
  const PreviewAutomationOperation = Schema.Literals(PREVIEW_AUTOMATION_OPERATIONS);
5164
5279
  const PreviewAutomationTabTargetFields = { tabId: Schema.optional(PreviewTabId.annotate({ description: "Exact collaborative browser tab to target. Omit to use this agent session's current tab." })).annotate({ description: "Exact collaborative browser tab to target. Omit to use this agent session's current tab." }) };
5165
5280
  const PreviewAutomationTabTargetInput = Schema.Struct(PreviewAutomationTabTargetFields);
5281
+ const INCLUDE_SCREENSHOT_DESCRIPTION = "Attach a PNG screenshot to the result. Omit it for the text-only page state, which already carries locators, visible text, and diagnostics; every attached image stays in the agent's context for the rest of the session.";
5282
+ const PreviewAutomationSnapshotInput = Schema.Struct({
5283
+ ...PreviewAutomationTabTargetFields,
5284
+ includeScreenshot: Schema.optional(Schema.Boolean.annotate({ description: INCLUDE_SCREENSHOT_DESCRIPTION })).annotate({ description: INCLUDE_SCREENSHOT_DESCRIPTION })
5285
+ });
5166
5286
  const PreviewAutomationStatus = Schema.Struct({
5167
5287
  available: Schema.Boolean,
5168
5288
  visible: Schema.Boolean,
@@ -5834,6 +5954,7 @@ const ContextMenuItemSchema = Schema$1.Struct({
5834
5954
  header: Schema$1.optionalKey(Schema$1.Boolean),
5835
5955
  separator: Schema$1.optionalKey(Schema$1.Boolean),
5836
5956
  icon: Schema$1.optionalKey(Schema$1.String),
5957
+ shortcut: Schema$1.optionalKey(Schema$1.String),
5837
5958
  children: Schema$1.optionalKey(Schema$1.Array(Schema$1.suspend(() => ContextMenuItemSchema)))
5838
5959
  });
5839
5960
  const DesktopUpdateStatusSchema = Schema$1.Literals([
@@ -8156,7 +8277,9 @@ const ClientSettingsSchema = Schema$1.Struct({
8156
8277
  })).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
8157
8278
  providerModelPreferences: Schema$1.Record(ProviderInstanceId, Schema$1.Struct({
8158
8279
  hiddenModels: Schema$1.Array(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
8159
- modelOrder: Schema$1.Array(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed([])))
8280
+ modelOrder: Schema$1.Array(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
8281
+ /** Option values a model starts with in this instance until the user picks others. */
8282
+ modelOptionDefaults: Schema$1.Record(TrimmedNonEmptyString, ProviderOptionSelections).pipe(Schema$1.withDecodingDefault(Effect.succeed({})))
8160
8283
  })).pipe(Schema$1.withDecodingDefault(Effect.succeed({}))),
8161
8284
  showBuildModeToggle: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
8162
8285
  sidebarAutoSettleAfterDays: Schema$1.NullOr(SidebarAutoSettleAfterDays).pipe(Schema$1.withDecodingDefault(Effect.succeed(3))),
@@ -8614,7 +8737,8 @@ Schema$1.Struct({
8614
8737
  }))),
8615
8738
  providerModelPreferences: Schema$1.optionalKey(Schema$1.Record(ProviderInstanceId, Schema$1.Struct({
8616
8739
  hiddenModels: Schema$1.Array(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
8617
- modelOrder: Schema$1.Array(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed([])))
8740
+ modelOrder: Schema$1.Array(Schema$1.String).pipe(Schema$1.withDecodingDefault(Effect.succeed([]))),
8741
+ modelOptionDefaults: Schema$1.Record(TrimmedNonEmptyString, ProviderOptionSelections).pipe(Schema$1.withDecodingDefault(Effect.succeed({})))
8618
8742
  }))),
8619
8743
  showBuildModeToggle: Schema$1.optionalKey(Schema$1.Boolean),
8620
8744
  sidebarAutoSettleAfterDays: Schema$1.optionalKey(Schema$1.NullOr(SidebarAutoSettleAfterDays)),
@@ -12493,7 +12617,7 @@ function deriveAuthClientMetadata(input) {
12493
12617
  //#endregion
12494
12618
  //#region src/auth/EnvironmentAuthPolicy.ts
12495
12619
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
12496
- const make$89 = Effect.gen(function* () {
12620
+ const make$90 = Effect.gen(function* () {
12497
12621
  const config = yield* ServerConfig$1;
12498
12622
  const isRemoteReachable = isRemoteReachableHost(config.host);
12499
12623
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
@@ -12511,7 +12635,7 @@ const make$89 = Effect.gen(function* () {
12511
12635
  };
12512
12636
  return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
12513
12637
  });
12514
- const layer$80 = Layer.effect(EnvironmentAuthPolicy, make$89);
12638
+ const layer$80 = Layer.effect(EnvironmentAuthPolicy, make$90);
12515
12639
  //#endregion
12516
12640
  //#region src/persistence/Errors.ts
12517
12641
  function summarizeSchemaIssue(issue) {
@@ -12692,7 +12816,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
12692
12816
  cause
12693
12817
  });
12694
12818
  }
12695
- const make$88 = Effect.gen(function* () {
12819
+ const make$89 = Effect.gen(function* () {
12696
12820
  const sql = yield* SqlClient.SqlClient;
12697
12821
  const createSessionRow = SqlSchema.void({
12698
12822
  Request: CreateAuthSessionInput,
@@ -12826,7 +12950,7 @@ const make$88 = Effect.gen(function* () {
12826
12950
  setLastConnectedAt
12827
12951
  };
12828
12952
  });
12829
- const layer$79 = Layer.effect(AuthSessionRepository, make$88);
12953
+ const layer$79 = Layer.effect(AuthSessionRepository, make$89);
12830
12954
  //#endregion
12831
12955
  //#region src/auth/ServerSecretStore.ts
12832
12956
  const secretStoreErrorContext = {
@@ -12893,7 +13017,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
12893
13017
  const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
12894
13018
  const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
12895
13019
  var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
12896
- const make$87 = Effect.gen(function* () {
13020
+ const make$88 = Effect.gen(function* () {
12897
13021
  const crypto = yield* Crypto.Crypto;
12898
13022
  const fileSystem = yield* FileSystem.FileSystem;
12899
13023
  const path = yield* Path.Path;
@@ -12969,7 +13093,7 @@ const make$87 = Effect.gen(function* () {
12969
13093
  remove
12970
13094
  });
12971
13095
  });
12972
- const layer$78 = Layer.effect(ServerSecretStore, make$87);
13096
+ const layer$78 = Layer.effect(ServerSecretStore, make$88);
12973
13097
  //#endregion
12974
13098
  //#region src/auth/SessionStore.ts
12975
13099
  var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
@@ -13207,7 +13331,7 @@ function toAuthClientSession(input) {
13207
13331
  current: false
13208
13332
  };
13209
13333
  }
13210
- const make$86 = Effect.gen(function* () {
13334
+ const make$87 = Effect.gen(function* () {
13211
13335
  const crypto = yield* Crypto.Crypto;
13212
13336
  const serverConfig = yield* ServerConfig$1;
13213
13337
  const secretStore = yield* ServerSecretStore;
@@ -13521,7 +13645,7 @@ const make$86 = Effect.gen(function* () {
13521
13645
  markDisconnected
13522
13646
  });
13523
13647
  });
13524
- const layer$77 = Layer.effect(SessionStore, make$86).pipe(Layer.provideMerge(layer$79));
13648
+ const layer$77 = Layer.effect(SessionStore, make$87).pipe(Layer.provideMerge(layer$79));
13525
13649
  //#endregion
13526
13650
  //#region src/persistence/AuthPairingLinks.ts
13527
13651
  const AuthPairingLinkRecord = Schema$1.Struct({
@@ -13582,7 +13706,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
13582
13706
  cause
13583
13707
  });
13584
13708
  }
13585
- const make$85 = Effect.gen(function* () {
13709
+ const make$86 = Effect.gen(function* () {
13586
13710
  const sql = yield* SqlClient.SqlClient;
13587
13711
  const createPairingLinkRow = SqlSchema.void({
13588
13712
  Request: CreateAuthPairingLinkInput,
@@ -13717,7 +13841,7 @@ const make$85 = Effect.gen(function* () {
13717
13841
  getByCredential
13718
13842
  };
13719
13843
  });
13720
- const layer$76 = Layer.effect(AuthPairingLinkRepository, make$85);
13844
+ const layer$76 = Layer.effect(AuthPairingLinkRepository, make$86);
13721
13845
  //#endregion
13722
13846
  //#region src/auth/PairingGrantStore.ts
13723
13847
  var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
@@ -13812,7 +13936,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
13812
13936
  const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
13813
13937
  const PAIRING_TOKEN_LENGTH = 12;
13814
13938
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
13815
- const make$84 = Effect.gen(function* () {
13939
+ const make$85 = Effect.gen(function* () {
13816
13940
  const crypto = yield* Crypto.Crypto;
13817
13941
  const config = yield* ServerConfig$1;
13818
13942
  const pairingLinks = yield* AuthPairingLinkRepository;
@@ -14010,7 +14134,7 @@ const make$84 = Effect.gen(function* () {
14010
14134
  consume
14011
14135
  });
14012
14136
  });
14013
- const layer$75 = Layer.effect(PairingGrantStore, make$84).pipe(Layer.provideMerge(layer$76));
14137
+ const layer$75 = Layer.effect(PairingGrantStore, make$85).pipe(Layer.provideMerge(layer$76));
14014
14138
  //#endregion
14015
14139
  //#region src/persistence/DatabaseSnapshot.ts
14016
14140
  /**
@@ -15929,6 +16053,33 @@ var _053_ProjectionTurnsKeysetIndex_default = Effect.gen(function* () {
15929
16053
  `;
15930
16054
  });
15931
16055
  //#endregion
16056
+ //#region src/persistence/Migrations/054_ProjectionThreadScheduledTasks.ts
16057
+ /**
16058
+ * User-created scheduled tasks: a prompt the server sends to a thread at a
16059
+ * chosen time. Pending rows are what the scheduler re-arms after a restart.
16060
+ */
16061
+ var _054_ProjectionThreadScheduledTasks_default = Effect.gen(function* () {
16062
+ const sql = yield* SqlClient.SqlClient;
16063
+ yield* sql`
16064
+ CREATE TABLE IF NOT EXISTS projection_thread_scheduled_tasks (
16065
+ task_id TEXT PRIMARY KEY,
16066
+ thread_id TEXT NOT NULL,
16067
+ prompt TEXT NOT NULL,
16068
+ run_at TEXT NOT NULL,
16069
+ status TEXT NOT NULL,
16070
+ created_at TEXT NOT NULL,
16071
+ updated_at TEXT NOT NULL,
16072
+ fired_at TEXT,
16073
+ cancelled_at TEXT,
16074
+ failure TEXT
16075
+ )
16076
+ `;
16077
+ yield* sql`
16078
+ CREATE INDEX IF NOT EXISTS idx_projection_thread_scheduled_tasks_thread_run
16079
+ ON projection_thread_scheduled_tasks(thread_id, run_at)
16080
+ `;
16081
+ });
16082
+ //#endregion
15932
16083
  //#region src/persistence/Migrations.ts
15933
16084
  /**
15934
16085
  * MigrationsLive - Migration runner with inline loader
@@ -16214,6 +16365,11 @@ const migrationEntries = [
16214
16365
  53,
16215
16366
  "ProjectionTurnsKeysetIndex",
16216
16367
  _053_ProjectionTurnsKeysetIndex_default
16368
+ ],
16369
+ [
16370
+ 54,
16371
+ "ProjectionThreadScheduledTasks",
16372
+ _054_ProjectionThreadScheduledTasks_default
16217
16373
  ]
16218
16374
  ];
16219
16375
  const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -17204,7 +17360,7 @@ function parseBearerToken(request) {
17204
17360
  const token = header.slice(7).trim();
17205
17361
  return token.length > 0 ? token : null;
17206
17362
  }
17207
- const make$83 = Effect.gen(function* () {
17363
+ const make$84 = Effect.gen(function* () {
17208
17364
  const policy = yield* EnvironmentAuthPolicy;
17209
17365
  const bootstrapCredentials = yield* PairingGrantStore;
17210
17366
  const sessions = yield* SessionStore;
@@ -17399,7 +17555,7 @@ const make$83 = Effect.gen(function* () {
17399
17555
  issueStartupPairingUrl
17400
17556
  });
17401
17557
  });
17402
- const layer$74 = Layer.effect(EnvironmentAuth, make$83).pipe(Layer.provideMerge(layer$75), Layer.provideMerge(layer$77), Layer.provideMerge(layer$80));
17558
+ const layer$74 = Layer.effect(EnvironmentAuth, make$84).pipe(Layer.provideMerge(layer$75), Layer.provideMerge(layer$77), Layer.provideMerge(layer$80));
17403
17559
  const storageLayer = Layer.mergeAll(layer$78, layerConfig);
17404
17560
  const runtimeLayer = layer$74.pipe(Layer.provideMerge(storageLayer));
17405
17561
  //#endregion
@@ -19237,7 +19393,7 @@ const DEFAULT_LIMITS = {
19237
19393
  windowMillis: FAILURE_WINDOW_MS,
19238
19394
  blockMillis: BLOCK_DURATION_MS
19239
19395
  };
19240
- const make$82 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
19396
+ const make$83 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
19241
19397
  const state = yield* Ref.make(initialThrottleState);
19242
19398
  return HubAuthThrottle.of({
19243
19399
  shouldRefuse: Effect.gen(function* () {
@@ -19251,7 +19407,7 @@ const make$82 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LI
19251
19407
  })
19252
19408
  });
19253
19409
  });
19254
- const layer$73 = Layer.effect(HubAuthThrottle, make$82());
19410
+ const layer$73 = Layer.effect(HubAuthThrottle, make$83());
19255
19411
  //#endregion
19256
19412
  //#region src/hub/HubAuth.ts
19257
19413
  /**
@@ -20971,7 +21127,7 @@ function stripDefaultServerSettings(current, defaults) {
20971
21127
  }
20972
21128
  return Object.is(current, defaults) ? void 0 : current;
20973
21129
  }
20974
- const make$81 = Effect.gen(function* () {
21130
+ const make$82 = Effect.gen(function* () {
20975
21131
  const { settingsPath } = yield* ServerConfig$1;
20976
21132
  const fs = yield* FileSystem.FileSystem;
20977
21133
  const pathService = yield* Path.Path;
@@ -21192,7 +21348,7 @@ const make$81 = Effect.gen(function* () {
21192
21348
  }
21193
21349
  };
21194
21350
  });
21195
- const layer$71 = Layer.effect(ServerSettingsService, make$81);
21351
+ const layer$71 = Layer.effect(ServerSettingsService, make$82);
21196
21352
  //#endregion
21197
21353
  //#region src/pathExpansion.ts
21198
21354
  /**
@@ -21569,7 +21725,7 @@ function claudeEntryFromRegistration(registration) {
21569
21725
  };
21570
21726
  }
21571
21727
  var ClaudeMcpFiles = class extends Context.Service()("@p4code/cli/mcp/ClaudeMcpFiles") {};
21572
- const make$80 = Effect.gen(function* () {
21728
+ const make$81 = Effect.gen(function* () {
21573
21729
  const fileSystem = yield* FileSystem.FileSystem;
21574
21730
  const path = yield* Path.Path;
21575
21731
  const services = yield* Effect.context();
@@ -21636,7 +21792,7 @@ const make$80 = Effect.gen(function* () {
21636
21792
  removeProject: (projectDir, name) => removeAt(Effect.succeed(projectFile(projectDir)))(name)
21637
21793
  };
21638
21794
  });
21639
- const layer$70 = Layer.effect(ClaudeMcpFiles, make$80);
21795
+ const layer$70 = Layer.effect(ClaudeMcpFiles, make$81);
21640
21796
  Layer.succeed(ClaudeMcpFiles, {
21641
21797
  readUser: Effect.succeed([]),
21642
21798
  readUserAt: () => Effect.succeed([]),
@@ -21774,7 +21930,7 @@ const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationRe
21774
21930
  const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
21775
21931
  var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
21776
21932
  const registryError = (detail) => new McpRegistryError({ detail });
21777
- const make$79 = Effect.gen(function* () {
21933
+ const make$80 = Effect.gen(function* () {
21778
21934
  const config = yield* ServerConfig$1;
21779
21935
  const secrets = yield* ServerSecretStore;
21780
21936
  const http = yield* HttpClient.HttpClient;
@@ -22094,7 +22250,7 @@ const make$79 = Effect.gen(function* () {
22094
22250
  accessTokenFor
22095
22251
  };
22096
22252
  });
22097
- const layer$69 = Layer.effect(McpOAuth, make$79);
22253
+ const layer$69 = Layer.effect(McpOAuth, make$80);
22098
22254
  Layer.succeed(McpOAuth, {
22099
22255
  statusFor: () => Effect.succeed(Option.none()),
22100
22256
  begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
@@ -22112,7 +22268,7 @@ const decodeRegistration$1 = Schema$1.decodeUnknownExit(RegistrationFromJson$1);
22112
22268
  const encodeRegistration = Schema$1.encodeSync(RegistrationFromJson$1);
22113
22269
  var McpRegistry = class extends Context.Service()("@p4code/cli/mcp/McpRegistry") {};
22114
22270
  const slotsOf = (registration) => registration.secrets ?? [];
22115
- const make$78 = Effect.gen(function* () {
22271
+ const make$79 = Effect.gen(function* () {
22116
22272
  const config = yield* ServerConfig$1;
22117
22273
  const secrets = yield* ServerSecretStore;
22118
22274
  const oauth = yield* McpOAuth;
@@ -22286,7 +22442,7 @@ const make$78 = Effect.gen(function* () {
22286
22442
  resolveForSessionAtClaudeUserConfigPath
22287
22443
  };
22288
22444
  });
22289
- const layer$68 = Layer.effect(McpRegistry, make$78);
22445
+ const layer$68 = Layer.effect(McpRegistry, make$79);
22290
22446
  //#endregion
22291
22447
  //#region src/sync/skillDirectory.ts
22292
22448
  /**
@@ -22665,7 +22821,7 @@ const formatHubLink = (input) => encodeStoredHubLink({
22665
22821
  shareMode: input.shareMode
22666
22822
  });
22667
22823
  const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
22668
- const make$77 = Effect.fn("HubLink.make")(function* (environment) {
22824
+ const make$78 = Effect.fn("HubLink.make")(function* (environment) {
22669
22825
  const secrets = yield* ServerSecretStore;
22670
22826
  const env = environment ?? process.env;
22671
22827
  const fromEnv = fromEnvironment(env);
@@ -22731,7 +22887,7 @@ const make$77 = Effect.fn("HubLink.make")(function* (environment) {
22731
22887
  })
22732
22888
  };
22733
22889
  });
22734
- const layer$67 = Layer.effect(HubLink, make$77());
22890
+ const layer$67 = Layer.effect(HubLink, make$78());
22735
22891
  //#endregion
22736
22892
  //#region src/sync/HubAssetClient.ts
22737
22893
  /**
@@ -22764,7 +22920,7 @@ const decodeAssetListPage = Schema$1.decodeUnknownEffect(AssetListPage);
22764
22920
  const decodeConflictBody$1 = Schema$1.decodeUnknownEffect(ConflictBody$1);
22765
22921
  const decodeAsset = Schema$1.decodeUnknownEffect(AgentAsset);
22766
22922
  var HubAssetClient = class extends Context.Service()("@p4code/cli/sync/HubAssetClient") {};
22767
- const make$76 = Effect.gen(function* () {
22923
+ const make$77 = Effect.gen(function* () {
22768
22924
  const http = yield* HttpClient.HttpClient;
22769
22925
  const link = yield* HubLink;
22770
22926
  const requireSettings = Effect.gen(function* () {
@@ -22846,7 +23002,7 @@ const make$76 = Effect.gen(function* () {
22846
23002
  remove
22847
23003
  };
22848
23004
  });
22849
- const layer$66 = Layer.effect(HubAssetClient, make$76);
23005
+ const layer$66 = Layer.effect(HubAssetClient, make$77);
22850
23006
  //#endregion
22851
23007
  //#region src/sync/mcpRegistrationFiles.ts
22852
23008
  /**
@@ -23452,7 +23608,7 @@ const EMPTY_REPORT = {
23452
23608
  unavailable: null
23453
23609
  };
23454
23610
  var AssetSync = class extends Context.Service()("@p4code/cli/sync/AssetSync") {};
23455
- const make$75 = Effect.gen(function* () {
23611
+ const make$76 = Effect.gen(function* () {
23456
23612
  const client = yield* HubAssetClient;
23457
23613
  const link = yield* HubLink;
23458
23614
  const settingsStore = yield* ServerSettingsService;
@@ -24162,7 +24318,7 @@ const make$75 = Effect.gen(function* () {
24162
24318
  removeLocal
24163
24319
  };
24164
24320
  });
24165
- const layer$65 = Layer.effect(AssetSync, make$75);
24321
+ const layer$65 = Layer.effect(AssetSync, make$76);
24166
24322
  //#endregion
24167
24323
  //#region src/provider/CompressPrompts.ts
24168
24324
  /**
@@ -25610,6 +25766,306 @@ function requireThreadAbsent(input) {
25610
25766
  return Effect.fail(invariantError(input.command.type, `Thread '${input.threadId}' already exists and cannot be created twice.`));
25611
25767
  }
25612
25768
  //#endregion
25769
+ //#region src/attachmentPaths.ts
25770
+ function normalizeAttachmentRelativePath(rawRelativePath) {
25771
+ const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");
25772
+ if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) return null;
25773
+ return normalized.replace(/\\/g, "/");
25774
+ }
25775
+ function resolveAttachmentRelativePath(input) {
25776
+ const normalizedRelativePath = normalizeAttachmentRelativePath(input.relativePath);
25777
+ if (!normalizedRelativePath) return null;
25778
+ const attachmentsRoot = NodePath.resolve(input.attachmentsDir);
25779
+ const filePath = NodePath.resolve(NodePath.join(attachmentsRoot, normalizedRelativePath));
25780
+ if (!filePath.startsWith(`${attachmentsRoot}${NodePath.sep}`)) return null;
25781
+ return filePath;
25782
+ }
25783
+ //#endregion
25784
+ //#region src/imageMime.ts
25785
+ const IMAGE_EXTENSION_BY_MIME_TYPE = {
25786
+ "image/avif": ".avif",
25787
+ "image/bmp": ".bmp",
25788
+ "image/gif": ".gif",
25789
+ "image/heic": ".heic",
25790
+ "image/heif": ".heif",
25791
+ "image/jpeg": ".jpg",
25792
+ "image/jpg": ".jpg",
25793
+ "image/png": ".png",
25794
+ "image/svg+xml": ".svg",
25795
+ "image/tiff": ".tiff",
25796
+ "image/webp": ".webp"
25797
+ };
25798
+ const SAFE_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
25799
+ ".avif",
25800
+ ".bmp",
25801
+ ".gif",
25802
+ ".heic",
25803
+ ".heif",
25804
+ ".ico",
25805
+ ".jpeg",
25806
+ ".jpg",
25807
+ ".png",
25808
+ ".svg",
25809
+ ".tiff",
25810
+ ".webp"
25811
+ ]);
25812
+ function isBase64Char(code) {
25813
+ return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 43 || code === 47 || code === 61;
25814
+ }
25815
+ function isBase64Whitespace(code) {
25816
+ return code === 13 || code === 10 || code === 32;
25817
+ }
25818
+ function parseBase64DataUrl(dataUrl) {
25819
+ const trimmed = dataUrl.trim();
25820
+ if (trimmed.slice(0, 5).toLowerCase() !== "data:") return null;
25821
+ const commaIndex = trimmed.indexOf(",");
25822
+ if (commaIndex === -1) return null;
25823
+ const header = trimmed.slice(5, commaIndex);
25824
+ if (header.length === 0) return null;
25825
+ const headerParts = [];
25826
+ for (const part of header.split(";")) {
25827
+ const partTrimmed = part.trim();
25828
+ if (partTrimmed.length > 0) headerParts.push(partTrimmed);
25829
+ }
25830
+ if (headerParts.length < 2) return null;
25831
+ if (headerParts.at(-1)?.toLowerCase() !== "base64") return null;
25832
+ const mimeType = headerParts[0]?.toLowerCase();
25833
+ if (!mimeType) return null;
25834
+ const payload = trimmed.slice(commaIndex + 1);
25835
+ const runs = [];
25836
+ let runStart = -1;
25837
+ for (let index = 0; index < payload.length; index += 1) {
25838
+ const code = payload.charCodeAt(index);
25839
+ if (isBase64Char(code)) {
25840
+ if (runStart === -1) runStart = index;
25841
+ continue;
25842
+ }
25843
+ if (!isBase64Whitespace(code)) return null;
25844
+ if (runStart !== -1) {
25845
+ runs.push(payload.slice(runStart, index));
25846
+ runStart = -1;
25847
+ }
25848
+ }
25849
+ if (runStart !== -1) runs.push(payload.slice(runStart));
25850
+ const base64 = runs.length === 1 ? runs[0] : runs.join("");
25851
+ if (base64.length === 0 || base64.length % 4 !== 0) return null;
25852
+ const firstPad = base64.indexOf("=");
25853
+ if (firstPad !== -1) {
25854
+ if (base64.length - firstPad > 2) return null;
25855
+ for (let index = firstPad; index < base64.length; index += 1) if (base64.charCodeAt(index) !== 61) return null;
25856
+ }
25857
+ return {
25858
+ mimeType,
25859
+ base64
25860
+ };
25861
+ }
25862
+ function inferImageExtension(input) {
25863
+ const key = input.mimeType.toLowerCase();
25864
+ const fromMime = Object.hasOwn(IMAGE_EXTENSION_BY_MIME_TYPE, key) ? IMAGE_EXTENSION_BY_MIME_TYPE[key] : void 0;
25865
+ if (fromMime) return fromMime;
25866
+ const fromMimeExtension = Mime.getExtension(input.mimeType);
25867
+ if (fromMimeExtension && SAFE_IMAGE_FILE_EXTENSIONS.has(fromMimeExtension)) return fromMimeExtension;
25868
+ const fileName = input.fileName?.trim() ?? "";
25869
+ const extensionMatch = /\.([a-z0-9]{1,8})$/i.exec(fileName);
25870
+ const fileNameExtension = extensionMatch ? `.${extensionMatch[1].toLowerCase()}` : "";
25871
+ if (SAFE_IMAGE_FILE_EXTENSIONS.has(fileNameExtension)) return fileNameExtension;
25872
+ return ".bin";
25873
+ }
25874
+ //#endregion
25875
+ //#region src/attachmentStore.ts
25876
+ const ATTACHMENT_FILENAME_EXTENSIONS = [
25877
+ ...SAFE_IMAGE_FILE_EXTENSIONS,
25878
+ ".pdf",
25879
+ ".bin"
25880
+ ];
25881
+ const ATTACHMENT_ID_THREAD_SEGMENT_MAX_CHARS = 80;
25882
+ const ATTACHMENT_ID_PATTERN = new RegExp(`^([a-z0-9_]+(?:-[a-z0-9_]+)*)-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$`, "i");
25883
+ function toSafeThreadAttachmentSegment(threadId) {
25884
+ const segment = threadId.trim().toLowerCase().replace(/[^a-z0-9_-]+/gi, "-").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, ATTACHMENT_ID_THREAD_SEGMENT_MAX_CHARS).replace(/[-_]+$/g, "");
25885
+ if (segment.length === 0) return null;
25886
+ return segment;
25887
+ }
25888
+ function createAttachmentId(threadId) {
25889
+ const threadSegment = toSafeThreadAttachmentSegment(threadId);
25890
+ if (!threadSegment) return null;
25891
+ return `${threadSegment}-${NodeCrypto.randomUUID()}`;
25892
+ }
25893
+ function parseThreadSegmentFromAttachmentId(attachmentId) {
25894
+ const normalizedId = normalizeAttachmentRelativePath(attachmentId);
25895
+ if (!normalizedId || normalizedId.includes("/") || normalizedId.includes(".")) return null;
25896
+ const match = normalizedId.match(ATTACHMENT_ID_PATTERN);
25897
+ if (!match) return null;
25898
+ return match[1]?.toLowerCase() ?? null;
25899
+ }
25900
+ function attachmentRelativePath(attachment) {
25901
+ switch (attachment.type) {
25902
+ case "image": {
25903
+ const extension = inferImageExtension({
25904
+ mimeType: attachment.mimeType,
25905
+ fileName: attachment.name
25906
+ });
25907
+ return `${attachment.id}${extension}`;
25908
+ }
25909
+ case "document": return `${attachment.id}.pdf`;
25910
+ }
25911
+ }
25912
+ function resolveAttachmentPath(input) {
25913
+ return resolveAttachmentRelativePath({
25914
+ attachmentsDir: input.attachmentsDir,
25915
+ relativePath: attachmentRelativePath(input.attachment)
25916
+ });
25917
+ }
25918
+ function resolveAttachmentPathById(input) {
25919
+ const normalizedId = normalizeAttachmentRelativePath(input.attachmentId);
25920
+ if (!normalizedId || normalizedId.includes("/") || normalizedId.includes(".")) return null;
25921
+ for (const extension of ATTACHMENT_FILENAME_EXTENSIONS) {
25922
+ const maybePath = resolveAttachmentRelativePath({
25923
+ attachmentsDir: input.attachmentsDir,
25924
+ relativePath: `${normalizedId}${extension}`
25925
+ });
25926
+ if (maybePath && NodeFS.existsSync(maybePath)) return maybePath;
25927
+ }
25928
+ return null;
25929
+ }
25930
+ function parseAttachmentIdFromRelativePath(relativePath) {
25931
+ const normalized = normalizeAttachmentRelativePath(relativePath);
25932
+ if (!normalized || normalized.includes("/")) return null;
25933
+ const extensionIndex = normalized.lastIndexOf(".");
25934
+ if (extensionIndex <= 0) return null;
25935
+ const id = normalized.slice(0, extensionIndex);
25936
+ return id.length > 0 && !id.includes(".") ? id : null;
25937
+ }
25938
+ //#endregion
25939
+ //#region src/orchestration/threadFork.ts
25940
+ /** Providers whose adapter can open a new session on top of a saved one. */
25941
+ const FORKABLE_PROVIDERS = /* @__PURE__ */ new Set(["claudeAgent"]);
25942
+ /**
25943
+ * Message ids are unique across threads, so a copied message needs an id
25944
+ * that ties it to the fork while staying derivable from the original.
25945
+ */
25946
+ function forkedMessageId(threadId, messageId) {
25947
+ return `${threadId}:fork:${messageId}`;
25948
+ }
25949
+ /**
25950
+ * Attachment ids must look like `<thread-segment>-<uuid>` and the file lives
25951
+ * beside the id, so a copy gets a fresh id under the fork's segment. The uuid
25952
+ * is derived from the source id: the pure read-model projector and the SQLite
25953
+ * projection must agree on it without sharing state.
25954
+ */
25955
+ function forkedAttachmentId(threadId, attachmentId) {
25956
+ const segment = toSafeThreadAttachmentSegment(threadId);
25957
+ if (!segment) return null;
25958
+ const digest = NodeCrypto.createHash("sha256").update(`${threadId} ${attachmentId}`).digest("hex");
25959
+ return `${segment}-${[
25960
+ digest.slice(0, 8),
25961
+ digest.slice(8, 12),
25962
+ digest.slice(12, 16),
25963
+ digest.slice(16, 20),
25964
+ digest.slice(20, 32)
25965
+ ].join("-")}`;
25966
+ }
25967
+ function copyForkedAttachments(threadId, attachments) {
25968
+ if (attachments === void 0) return void 0;
25969
+ const copies = [];
25970
+ for (const attachment of attachments) {
25971
+ const id = forkedAttachmentId(threadId, attachment.id);
25972
+ if (id === null) continue;
25973
+ copies.push({
25974
+ ...attachment,
25975
+ id
25976
+ });
25977
+ }
25978
+ return copies;
25979
+ }
25980
+ /**
25981
+ * Copies a source thread's conversation for the fork read model. Turn ids
25982
+ * belong to the source thread's turns, so copies carry none; attachments are
25983
+ * re-keyed to the fork so their files can be copied beside them; a streaming
25984
+ * message is frozen as-is.
25985
+ */
25986
+ function copyForkedMessages(threadId, messages) {
25987
+ return messages.map((message) => {
25988
+ const attachments = copyForkedAttachments(threadId, message.attachments);
25989
+ return {
25990
+ id: forkedMessageId(threadId, message.id),
25991
+ role: message.role,
25992
+ text: message.text,
25993
+ ...attachments !== void 0 ? { attachments } : {},
25994
+ turnId: null,
25995
+ streaming: false,
25996
+ createdAt: message.createdAt,
25997
+ updatedAt: message.updatedAt
25998
+ };
25999
+ });
26000
+ }
26001
+ /** Same copy as {@link copyForkedMessages}, for the SQLite message projection. */
26002
+ function copyForkedProjectionMessages(threadId, rows) {
26003
+ return rows.map((row) => {
26004
+ const attachments = copyForkedAttachments(threadId, row.attachments);
26005
+ return {
26006
+ messageId: forkedMessageId(threadId, row.messageId),
26007
+ threadId,
26008
+ turnId: null,
26009
+ role: row.role,
26010
+ text: row.text,
26011
+ ...attachments !== void 0 ? { attachments } : {},
26012
+ isStreaming: false,
26013
+ createdAt: row.createdAt,
26014
+ updatedAt: row.updatedAt
26015
+ };
26016
+ });
26017
+ }
26018
+ /** File copies that make the re-keyed attachment references resolve. */
26019
+ function collectForkedAttachmentCopies(threadId, rows) {
26020
+ const copies = [];
26021
+ for (const row of rows) for (const attachment of row.attachments ?? []) {
26022
+ const id = forkedAttachmentId(threadId, attachment.id);
26023
+ if (id === null) continue;
26024
+ copies.push({
26025
+ fromRelativePath: attachmentRelativePath(attachment),
26026
+ toRelativePath: attachmentRelativePath({
26027
+ ...attachment,
26028
+ id
26029
+ })
26030
+ });
26031
+ }
26032
+ return copies;
26033
+ }
26034
+ function isRecord$6(value) {
26035
+ return value !== null && typeof value === "object" && !Array.isArray(value);
26036
+ }
26037
+ /**
26038
+ * Provider binding for a fork, or the reason there is none. The resume cursor
26039
+ * is re-keyed to the fork and flagged so the adapter opens a new provider
26040
+ * session from the saved one instead of continuing the source's session.
26041
+ */
26042
+ function buildForkedProviderBinding(threadId, source) {
26043
+ if (source === void 0) return { rejection: "no-binding" };
26044
+ if (!FORKABLE_PROVIDERS.has(source.provider)) return { rejection: "provider-cannot-fork" };
26045
+ if (!isRecord$6(source.resumeCursor) || typeof source.resumeCursor.resume !== "string") return { rejection: "no-resume-cursor" };
26046
+ return { binding: {
26047
+ threadId,
26048
+ provider: source.provider,
26049
+ ...source.providerInstanceId ? { providerInstanceId: source.providerInstanceId } : {},
26050
+ ...source.adapterKey ? { adapterKey: source.adapterKey } : {},
26051
+ ...source.runtimeMode ? { runtimeMode: source.runtimeMode } : {},
26052
+ status: "stopped",
26053
+ resumeCursor: {
26054
+ ...source.resumeCursor,
26055
+ threadId,
26056
+ forkSession: true
26057
+ },
26058
+ runtimePayload: source.runtimePayload ?? null
26059
+ } };
26060
+ }
26061
+ function describeThreadForkRejection(rejection) {
26062
+ switch (rejection) {
26063
+ case "no-binding": return "This thread has no provider session to fork yet. Send a message first.";
26064
+ case "provider-cannot-fork": return "This thread's provider cannot fork a session. Fork is available for Claude threads.";
26065
+ case "no-resume-cursor": return "This thread's provider session cannot be resumed, so it cannot be forked.";
26066
+ }
26067
+ }
26068
+ //#endregion
25613
26069
  //#region src/orchestration/Schemas.ts
25614
26070
  const ProjectCreatedPayload = ProjectCreatedPayload$1;
25615
26071
  const ProjectMetaUpdatedPayload = ProjectMetaUpdatedPayload$1;
@@ -25632,6 +26088,9 @@ const ThreadSnoozedPayload = ThreadSnoozedPayload$1;
25632
26088
  const ThreadUnsnoozedPayload = ThreadUnsnoozedPayload$1;
25633
26089
  const MessageSentPayloadSchema = ThreadMessageSentPayload;
25634
26090
  const ThreadProposedPlanUpsertedPayload = ThreadProposedPlanUpsertedPayload$1;
26091
+ const ThreadScheduledTaskCreatedPayload = ThreadScheduledTaskCreatedPayload$1;
26092
+ const ThreadScheduledTaskCancelledPayload = ThreadScheduledTaskCancelledPayload$1;
26093
+ const ThreadScheduledTaskFiredPayload = ThreadScheduledTaskFiredPayload$1;
25635
26094
  const ThreadSessionSetPayload = ThreadSessionSetPayload$1;
25636
26095
  const ThreadTurnDiffCompletedPayload = ThreadTurnDiffCompletedPayload$1;
25637
26096
  const ThreadRevertedPayload = ThreadRevertedPayload$1;
@@ -25814,6 +26273,7 @@ function projectEvent(model, event) {
25814
26273
  })));
25815
26274
  case "thread.created": return Effect.gen(function* () {
25816
26275
  const payload = yield* decodeForEvent(ThreadCreatedPayload, event.payload, event.type, "payload");
26276
+ const forkSource = payload.forkedFromThreadId === void 0 ? void 0 : nextBase.threads.find((entry) => entry.id === payload.forkedFromThreadId);
25817
26277
  const thread = yield* decodeForEvent(OrchestrationThread, {
25818
26278
  id: payload.threadId,
25819
26279
  projectId: payload.projectId,
@@ -25834,7 +26294,7 @@ function projectEvent(model, event) {
25834
26294
  snoozedUntil: null,
25835
26295
  snoozedAt: null,
25836
26296
  deletedAt: null,
25837
- messages: [],
26297
+ messages: forkSource ? copyForkedMessages(payload.threadId, forkSource.messages) : [],
25838
26298
  activities: [],
25839
26299
  checkpoints: [],
25840
26300
  session: null
@@ -26016,6 +26476,42 @@ function projectEvent(model, event) {
26016
26476
  })
26017
26477
  };
26018
26478
  });
26479
+ case "thread.scheduled-task.created": return Effect.gen(function* () {
26480
+ const payload = yield* decodeForEvent(ThreadScheduledTaskCreatedPayload, event.payload, event.type, "payload");
26481
+ const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
26482
+ if (!thread) return nextBase;
26483
+ return {
26484
+ ...nextBase,
26485
+ threads: updateThread(nextBase.threads, payload.threadId, {
26486
+ scheduledTasks: [...(thread.scheduledTasks ?? []).filter((entry) => entry.id !== payload.task.id), payload.task],
26487
+ updatedAt: event.occurredAt
26488
+ })
26489
+ };
26490
+ });
26491
+ case "thread.scheduled-task.cancelled":
26492
+ case "thread.scheduled-task.fired": return Effect.gen(function* () {
26493
+ const payload = event.type === "thread.scheduled-task.cancelled" ? yield* decodeForEvent(ThreadScheduledTaskCancelledPayload, event.payload, event.type, "payload") : yield* decodeForEvent(ThreadScheduledTaskFiredPayload, event.payload, event.type, "payload");
26494
+ const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
26495
+ if (!thread) return nextBase;
26496
+ return {
26497
+ ...nextBase,
26498
+ threads: updateThread(nextBase.threads, payload.threadId, {
26499
+ scheduledTasks: (thread.scheduledTasks ?? []).map((task) => task.id !== payload.taskId ? task : "cancelledAt" in payload ? {
26500
+ ...task,
26501
+ status: "cancelled",
26502
+ cancelledAt: payload.cancelledAt,
26503
+ updatedAt: event.occurredAt
26504
+ } : {
26505
+ ...task,
26506
+ status: payload.failure === null ? "fired" : "failed",
26507
+ firedAt: payload.firedAt,
26508
+ failure: payload.failure,
26509
+ updatedAt: event.occurredAt
26510
+ }),
26511
+ updatedAt: event.occurredAt
26512
+ })
26513
+ };
26514
+ });
26019
26515
  case "thread.proposed-plan-upserted": return Effect.gen(function* () {
26020
26516
  const payload = yield* decodeForEvent(ThreadProposedPlanUpsertedPayload, event.payload, event.type, "payload");
26021
26517
  const thread = nextBase.threads.find((entry) => entry.id === payload.threadId);
@@ -26336,6 +26832,113 @@ const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand")(funct
26336
26832
  updatedAt: command.createdAt
26337
26833
  }
26338
26834
  };
26835
+ case "thread.fork": {
26836
+ const source = yield* requireThread({
26837
+ readModel,
26838
+ command,
26839
+ threadId: command.sourceThreadId
26840
+ });
26841
+ yield* requireThreadAbsent({
26842
+ readModel,
26843
+ command,
26844
+ threadId: command.threadId
26845
+ });
26846
+ return {
26847
+ ...yield* withEventBase({
26848
+ aggregateKind: "thread",
26849
+ aggregateId: command.threadId,
26850
+ occurredAt: command.createdAt,
26851
+ commandId: command.commandId
26852
+ }),
26853
+ type: "thread.created",
26854
+ payload: {
26855
+ threadId: command.threadId,
26856
+ projectId: source.projectId,
26857
+ title: command.title ?? `${source.title} (fork)`,
26858
+ modelSelection: source.modelSelection,
26859
+ runtimeMode: source.runtimeMode,
26860
+ interactionMode: source.interactionMode,
26861
+ compressMode: source.compressMode,
26862
+ unpromptedSubagents: source.unpromptedSubagents,
26863
+ branch: source.branch,
26864
+ worktreePath: source.worktreePath,
26865
+ forkedFromThreadId: source.id,
26866
+ createdAt: command.createdAt,
26867
+ updatedAt: command.createdAt
26868
+ }
26869
+ };
26870
+ }
26871
+ case "thread.scheduled-task.create":
26872
+ if (((yield* requireThread({
26873
+ readModel,
26874
+ command,
26875
+ threadId: command.threadId
26876
+ })).scheduledTasks ?? []).some((task) => task.id === command.taskId)) return yield* new OrchestrationCommandInvariantError({
26877
+ commandType: command.type,
26878
+ detail: `Scheduled task '${command.taskId}' already exists on thread '${command.threadId}'.`
26879
+ });
26880
+ return {
26881
+ ...yield* withEventBase({
26882
+ aggregateKind: "thread",
26883
+ aggregateId: command.threadId,
26884
+ occurredAt: command.createdAt,
26885
+ commandId: command.commandId
26886
+ }),
26887
+ type: "thread.scheduled-task.created",
26888
+ payload: {
26889
+ threadId: command.threadId,
26890
+ task: {
26891
+ id: command.taskId,
26892
+ prompt: command.prompt,
26893
+ runAt: command.runAt,
26894
+ status: "pending",
26895
+ createdAt: command.createdAt,
26896
+ updatedAt: command.createdAt,
26897
+ firedAt: null,
26898
+ cancelledAt: null,
26899
+ failure: null
26900
+ }
26901
+ }
26902
+ };
26903
+ case "thread.scheduled-task.cancel":
26904
+ case "thread.scheduled-task.fire": {
26905
+ const task = ((yield* requireThread({
26906
+ readModel,
26907
+ command,
26908
+ threadId: command.threadId
26909
+ })).scheduledTasks ?? []).find((entry) => entry.id === command.taskId);
26910
+ const cancellable = task?.status === "pending" || task?.status === "failed";
26911
+ if (task === void 0 || (command.type === "thread.scheduled-task.fire" ? task.status !== "pending" : !cancellable)) return yield* new OrchestrationCommandInvariantError({
26912
+ commandType: command.type,
26913
+ detail: `Scheduled task '${command.taskId}' is not ${command.type === "thread.scheduled-task.fire" ? "pending" : "pending or failed"} on thread '${command.threadId}'.`
26914
+ });
26915
+ const occurredAt = command.type === "thread.scheduled-task.cancel" ? command.cancelledAt : command.firedAt;
26916
+ const base = yield* withEventBase({
26917
+ aggregateKind: "thread",
26918
+ aggregateId: command.threadId,
26919
+ occurredAt,
26920
+ commandId: command.commandId
26921
+ });
26922
+ if (command.type === "thread.scheduled-task.cancel") return {
26923
+ ...base,
26924
+ type: "thread.scheduled-task.cancelled",
26925
+ payload: {
26926
+ threadId: command.threadId,
26927
+ taskId: command.taskId,
26928
+ cancelledAt: command.cancelledAt
26929
+ }
26930
+ };
26931
+ return {
26932
+ ...base,
26933
+ type: "thread.scheduled-task.fired",
26934
+ payload: {
26935
+ threadId: command.threadId,
26936
+ taskId: command.taskId,
26937
+ firedAt: command.firedAt,
26938
+ failure: command.failure ?? null
26939
+ }
26940
+ };
26941
+ }
26339
26942
  case "thread.delete": {
26340
26943
  yield* requireThread({
26341
26944
  readModel,
@@ -27903,6 +28506,23 @@ const ListProjectionThreadProposedPlansInput = Schema$1.Struct({ threadId: Threa
27903
28506
  const DeleteProjectionThreadProposedPlansInput = Schema$1.Struct({ threadId: ThreadId });
27904
28507
  var ProjectionThreadProposedPlanRepository = class extends Context.Service()("@p4code/cli/persistence/Services/ProjectionThreadProposedPlans/ProjectionThreadProposedPlanRepository") {};
27905
28508
  //#endregion
28509
+ //#region src/persistence/Services/ProjectionThreadScheduledTasks.ts
28510
+ const ProjectionThreadScheduledTask = Schema$1.Struct({
28511
+ taskId: OrchestrationScheduledTaskId,
28512
+ threadId: ThreadId,
28513
+ prompt: TrimmedNonEmptyString,
28514
+ runAt: IsoDateTime,
28515
+ status: OrchestrationScheduledTaskStatus,
28516
+ createdAt: IsoDateTime,
28517
+ updatedAt: IsoDateTime,
28518
+ firedAt: Schema$1.NullOr(IsoDateTime),
28519
+ cancelledAt: Schema$1.NullOr(IsoDateTime),
28520
+ failure: Schema$1.NullOr(Schema$1.String)
28521
+ });
28522
+ const GetProjectionThreadScheduledTaskInput = Schema$1.Struct({ taskId: OrchestrationScheduledTaskId });
28523
+ const ListProjectionThreadScheduledTasksInput = Schema$1.Struct({ threadId: ThreadId });
28524
+ var ProjectionThreadScheduledTaskRepository = class extends Context.Service()("@p4code/cli/persistence/Services/ProjectionThreadScheduledTasks/ProjectionThreadScheduledTaskRepository") {};
28525
+ //#endregion
27906
28526
  //#region src/persistence/Services/ProjectionThreadSessions.ts
27907
28527
  /**
27908
28528
  * ProjectionThreadSessionRepository - Repository interface for thread sessions.
@@ -28598,6 +29218,91 @@ const makeProjectionThreadProposedPlanRepository = Effect.gen(function* () {
28598
29218
  });
28599
29219
  const ProjectionThreadProposedPlanRepositoryLive = Layer.effect(ProjectionThreadProposedPlanRepository, makeProjectionThreadProposedPlanRepository);
28600
29220
  //#endregion
29221
+ //#region src/persistence/Layers/ProjectionThreadScheduledTasks.ts
29222
+ const makeProjectionThreadScheduledTaskRepository = Effect.gen(function* () {
29223
+ const sql = yield* SqlClient.SqlClient;
29224
+ const upsertRow = SqlSchema.void({
29225
+ Request: ProjectionThreadScheduledTask,
29226
+ execute: (row) => sql`
29227
+ INSERT INTO projection_thread_scheduled_tasks (
29228
+ task_id,
29229
+ thread_id,
29230
+ prompt,
29231
+ run_at,
29232
+ status,
29233
+ created_at,
29234
+ updated_at,
29235
+ fired_at,
29236
+ cancelled_at,
29237
+ failure
29238
+ )
29239
+ VALUES (
29240
+ ${row.taskId},
29241
+ ${row.threadId},
29242
+ ${row.prompt},
29243
+ ${row.runAt},
29244
+ ${row.status},
29245
+ ${row.createdAt},
29246
+ ${row.updatedAt},
29247
+ ${row.firedAt},
29248
+ ${row.cancelledAt},
29249
+ ${row.failure}
29250
+ )
29251
+ ON CONFLICT (task_id)
29252
+ DO UPDATE SET
29253
+ thread_id = excluded.thread_id,
29254
+ prompt = excluded.prompt,
29255
+ run_at = excluded.run_at,
29256
+ status = excluded.status,
29257
+ created_at = excluded.created_at,
29258
+ updated_at = excluded.updated_at,
29259
+ fired_at = excluded.fired_at,
29260
+ cancelled_at = excluded.cancelled_at,
29261
+ failure = excluded.failure
29262
+ `
29263
+ });
29264
+ const selectColumns = sql`
29265
+ task_id AS "taskId",
29266
+ thread_id AS "threadId",
29267
+ prompt,
29268
+ run_at AS "runAt",
29269
+ status,
29270
+ created_at AS "createdAt",
29271
+ updated_at AS "updatedAt",
29272
+ fired_at AS "firedAt",
29273
+ cancelled_at AS "cancelledAt",
29274
+ failure
29275
+ `;
29276
+ const getRow = SqlSchema.findOneOption({
29277
+ Request: GetProjectionThreadScheduledTaskInput,
29278
+ Result: ProjectionThreadScheduledTask,
29279
+ execute: ({ taskId }) => sql`
29280
+ SELECT ${selectColumns}
29281
+ FROM projection_thread_scheduled_tasks
29282
+ WHERE task_id = ${taskId}
29283
+ `
29284
+ });
29285
+ const listRows = SqlSchema.findAll({
29286
+ Request: ListProjectionThreadScheduledTasksInput,
29287
+ Result: ProjectionThreadScheduledTask,
29288
+ execute: ({ threadId }) => sql`
29289
+ SELECT ${selectColumns}
29290
+ FROM projection_thread_scheduled_tasks
29291
+ WHERE thread_id = ${threadId}
29292
+ ORDER BY run_at ASC, task_id ASC
29293
+ `
29294
+ });
29295
+ const upsert = (row) => upsertRow(row).pipe(Effect.mapError(toPersistenceSqlError("ProjectionThreadScheduledTaskRepository.upsert:query")));
29296
+ const getByTaskId = (input) => getRow(input).pipe(Effect.map(Option.getOrNull), Effect.mapError(toPersistenceSqlError("ProjectionThreadScheduledTaskRepository.getByTaskId:query")));
29297
+ const listByThreadId = (input) => listRows(input).pipe(Effect.mapError(toPersistenceSqlError("ProjectionThreadScheduledTaskRepository.listByThreadId:query")));
29298
+ return {
29299
+ upsert,
29300
+ getByTaskId,
29301
+ listByThreadId
29302
+ };
29303
+ });
29304
+ const ProjectionThreadScheduledTaskRepositoryLive = Layer.effect(ProjectionThreadScheduledTaskRepository, makeProjectionThreadScheduledTaskRepository);
29305
+ //#endregion
28601
29306
  //#region src/persistence/Layers/ProjectionThreadSessions.ts
28602
29307
  const makeProjectionThreadSessionRepository = Effect.gen(function* () {
28603
29308
  const sql = yield* SqlClient.SqlClient;
@@ -29093,182 +29798,13 @@ const makeProjectionThreadRepository = Effect.gen(function* () {
29093
29798
  });
29094
29799
  const ProjectionThreadRepositoryLive = Layer.effect(ProjectionThreadRepository, makeProjectionThreadRepository);
29095
29800
  //#endregion
29096
- //#region src/attachmentPaths.ts
29097
- function normalizeAttachmentRelativePath(rawRelativePath) {
29098
- const normalized = NodePath.normalize(rawRelativePath).replace(/^[/\\]+/, "");
29099
- if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) return null;
29100
- return normalized.replace(/\\/g, "/");
29101
- }
29102
- function resolveAttachmentRelativePath(input) {
29103
- const normalizedRelativePath = normalizeAttachmentRelativePath(input.relativePath);
29104
- if (!normalizedRelativePath) return null;
29105
- const attachmentsRoot = NodePath.resolve(input.attachmentsDir);
29106
- const filePath = NodePath.resolve(NodePath.join(attachmentsRoot, normalizedRelativePath));
29107
- if (!filePath.startsWith(`${attachmentsRoot}${NodePath.sep}`)) return null;
29108
- return filePath;
29109
- }
29110
- //#endregion
29111
- //#region src/imageMime.ts
29112
- const IMAGE_EXTENSION_BY_MIME_TYPE = {
29113
- "image/avif": ".avif",
29114
- "image/bmp": ".bmp",
29115
- "image/gif": ".gif",
29116
- "image/heic": ".heic",
29117
- "image/heif": ".heif",
29118
- "image/jpeg": ".jpg",
29119
- "image/jpg": ".jpg",
29120
- "image/png": ".png",
29121
- "image/svg+xml": ".svg",
29122
- "image/tiff": ".tiff",
29123
- "image/webp": ".webp"
29124
- };
29125
- const SAFE_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
29126
- ".avif",
29127
- ".bmp",
29128
- ".gif",
29129
- ".heic",
29130
- ".heif",
29131
- ".ico",
29132
- ".jpeg",
29133
- ".jpg",
29134
- ".png",
29135
- ".svg",
29136
- ".tiff",
29137
- ".webp"
29138
- ]);
29139
- function isBase64Char(code) {
29140
- return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 43 || code === 47 || code === 61;
29141
- }
29142
- function isBase64Whitespace(code) {
29143
- return code === 13 || code === 10 || code === 32;
29144
- }
29145
- function parseBase64DataUrl(dataUrl) {
29146
- const trimmed = dataUrl.trim();
29147
- if (trimmed.slice(0, 5).toLowerCase() !== "data:") return null;
29148
- const commaIndex = trimmed.indexOf(",");
29149
- if (commaIndex === -1) return null;
29150
- const header = trimmed.slice(5, commaIndex);
29151
- if (header.length === 0) return null;
29152
- const headerParts = [];
29153
- for (const part of header.split(";")) {
29154
- const partTrimmed = part.trim();
29155
- if (partTrimmed.length > 0) headerParts.push(partTrimmed);
29156
- }
29157
- if (headerParts.length < 2) return null;
29158
- if (headerParts.at(-1)?.toLowerCase() !== "base64") return null;
29159
- const mimeType = headerParts[0]?.toLowerCase();
29160
- if (!mimeType) return null;
29161
- const payload = trimmed.slice(commaIndex + 1);
29162
- const runs = [];
29163
- let runStart = -1;
29164
- for (let index = 0; index < payload.length; index += 1) {
29165
- const code = payload.charCodeAt(index);
29166
- if (isBase64Char(code)) {
29167
- if (runStart === -1) runStart = index;
29168
- continue;
29169
- }
29170
- if (!isBase64Whitespace(code)) return null;
29171
- if (runStart !== -1) {
29172
- runs.push(payload.slice(runStart, index));
29173
- runStart = -1;
29174
- }
29175
- }
29176
- if (runStart !== -1) runs.push(payload.slice(runStart));
29177
- const base64 = runs.length === 1 ? runs[0] : runs.join("");
29178
- if (base64.length === 0 || base64.length % 4 !== 0) return null;
29179
- const firstPad = base64.indexOf("=");
29180
- if (firstPad !== -1) {
29181
- if (base64.length - firstPad > 2) return null;
29182
- for (let index = firstPad; index < base64.length; index += 1) if (base64.charCodeAt(index) !== 61) return null;
29183
- }
29184
- return {
29185
- mimeType,
29186
- base64
29187
- };
29188
- }
29189
- function inferImageExtension(input) {
29190
- const key = input.mimeType.toLowerCase();
29191
- const fromMime = Object.hasOwn(IMAGE_EXTENSION_BY_MIME_TYPE, key) ? IMAGE_EXTENSION_BY_MIME_TYPE[key] : void 0;
29192
- if (fromMime) return fromMime;
29193
- const fromMimeExtension = Mime.getExtension(input.mimeType);
29194
- if (fromMimeExtension && SAFE_IMAGE_FILE_EXTENSIONS.has(fromMimeExtension)) return fromMimeExtension;
29195
- const fileName = input.fileName?.trim() ?? "";
29196
- const extensionMatch = /\.([a-z0-9]{1,8})$/i.exec(fileName);
29197
- const fileNameExtension = extensionMatch ? `.${extensionMatch[1].toLowerCase()}` : "";
29198
- if (SAFE_IMAGE_FILE_EXTENSIONS.has(fileNameExtension)) return fileNameExtension;
29199
- return ".bin";
29200
- }
29201
- //#endregion
29202
- //#region src/attachmentStore.ts
29203
- const ATTACHMENT_FILENAME_EXTENSIONS = [
29204
- ...SAFE_IMAGE_FILE_EXTENSIONS,
29205
- ".pdf",
29206
- ".bin"
29207
- ];
29208
- const ATTACHMENT_ID_THREAD_SEGMENT_MAX_CHARS = 80;
29209
- const ATTACHMENT_ID_PATTERN = new RegExp(`^([a-z0-9_]+(?:-[a-z0-9_]+)*)-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$`, "i");
29210
- function toSafeThreadAttachmentSegment(threadId) {
29211
- const segment = threadId.trim().toLowerCase().replace(/[^a-z0-9_-]+/gi, "-").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, ATTACHMENT_ID_THREAD_SEGMENT_MAX_CHARS).replace(/[-_]+$/g, "");
29212
- if (segment.length === 0) return null;
29213
- return segment;
29214
- }
29215
- function createAttachmentId(threadId) {
29216
- const threadSegment = toSafeThreadAttachmentSegment(threadId);
29217
- if (!threadSegment) return null;
29218
- return `${threadSegment}-${NodeCrypto.randomUUID()}`;
29219
- }
29220
- function parseThreadSegmentFromAttachmentId(attachmentId) {
29221
- const normalizedId = normalizeAttachmentRelativePath(attachmentId);
29222
- if (!normalizedId || normalizedId.includes("/") || normalizedId.includes(".")) return null;
29223
- const match = normalizedId.match(ATTACHMENT_ID_PATTERN);
29224
- if (!match) return null;
29225
- return match[1]?.toLowerCase() ?? null;
29226
- }
29227
- function attachmentRelativePath(attachment) {
29228
- switch (attachment.type) {
29229
- case "image": {
29230
- const extension = inferImageExtension({
29231
- mimeType: attachment.mimeType,
29232
- fileName: attachment.name
29233
- });
29234
- return `${attachment.id}${extension}`;
29235
- }
29236
- case "document": return `${attachment.id}.pdf`;
29237
- }
29238
- }
29239
- function resolveAttachmentPath(input) {
29240
- return resolveAttachmentRelativePath({
29241
- attachmentsDir: input.attachmentsDir,
29242
- relativePath: attachmentRelativePath(input.attachment)
29243
- });
29244
- }
29245
- function resolveAttachmentPathById(input) {
29246
- const normalizedId = normalizeAttachmentRelativePath(input.attachmentId);
29247
- if (!normalizedId || normalizedId.includes("/") || normalizedId.includes(".")) return null;
29248
- for (const extension of ATTACHMENT_FILENAME_EXTENSIONS) {
29249
- const maybePath = resolveAttachmentRelativePath({
29250
- attachmentsDir: input.attachmentsDir,
29251
- relativePath: `${normalizedId}${extension}`
29252
- });
29253
- if (maybePath && NodeFS.existsSync(maybePath)) return maybePath;
29254
- }
29255
- return null;
29256
- }
29257
- function parseAttachmentIdFromRelativePath(relativePath) {
29258
- const normalized = normalizeAttachmentRelativePath(relativePath);
29259
- if (!normalized || normalized.includes("/")) return null;
29260
- const extensionIndex = normalized.lastIndexOf(".");
29261
- if (extensionIndex <= 0) return null;
29262
- const id = normalized.slice(0, extensionIndex);
29263
- return id.length > 0 && !id.includes(".") ? id : null;
29264
- }
29265
- //#endregion
29266
29801
  //#region src/orchestration/Layers/ProjectionPipeline.ts
29267
29802
  const ORCHESTRATION_PROJECTOR_NAMES = {
29268
29803
  projects: "projection.projects",
29269
29804
  threads: "projection.threads",
29270
29805
  threadMessages: "projection.thread-messages",
29271
29806
  threadProposedPlans: "projection.thread-proposed-plans",
29807
+ threadScheduledTasks: "projection.thread-scheduled-tasks",
29272
29808
  threadActivities: "projection.thread-activities",
29273
29809
  threadSessions: "projection.thread-sessions",
29274
29810
  threadTurns: "projection.thread-turns",
@@ -29487,6 +30023,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
29487
30023
  const projectionThreadRepository = yield* ProjectionThreadRepository;
29488
30024
  const projectionThreadMessageRepository = yield* ProjectionThreadMessageRepository;
29489
30025
  const projectionThreadProposedPlanRepository = yield* ProjectionThreadProposedPlanRepository;
30026
+ const projectionThreadScheduledTaskRepository = yield* ProjectionThreadScheduledTaskRepository;
29490
30027
  const projectionThreadActivityRepository = yield* ProjectionThreadActivityRepository;
29491
30028
  const projectionThreadSessionRepository = yield* ProjectionThreadSessionRepository;
29492
30029
  const projectionTurnRepository = yield* ProjectionTurnRepository;
@@ -29494,6 +30031,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
29494
30031
  const fileSystem = yield* FileSystem.FileSystem;
29495
30032
  const path = yield* Path.Path;
29496
30033
  const serverConfig = yield* ServerConfig$1;
30034
+ const attachmentsRootDir = serverConfig.attachmentsDir;
29497
30035
  const applyProjectsProjection = Effect.fn("applyProjectsProjection")(function* (event, _attachmentSideEffects) {
29498
30036
  switch (event.type) {
29499
30037
  case "project.created":
@@ -29893,8 +30431,34 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
29893
30431
  default: return;
29894
30432
  }
29895
30433
  });
30434
+ const copyForkedAttachmentFile = Effect.fn("copyForkedAttachmentFile")(function* (copy) {
30435
+ const fromPath = resolveAttachmentRelativePath({
30436
+ attachmentsDir: attachmentsRootDir,
30437
+ relativePath: copy.fromRelativePath
30438
+ });
30439
+ const toPath = resolveAttachmentRelativePath({
30440
+ attachmentsDir: attachmentsRootDir,
30441
+ relativePath: copy.toRelativePath
30442
+ });
30443
+ if (!fromPath || !toPath) return yield* new PersistenceSqlError({
30444
+ operation: "ProjectionPipeline.copyForkedAttachmentFile",
30445
+ detail: `Unsafe attachment path while forking: ${copy.fromRelativePath}`
30446
+ });
30447
+ yield* fileSystem.copyFile(fromPath, toPath).pipe(Effect.catch((error) => error.reason._tag === "NotFound" ? Effect.logWarning("thread fork skipped a missing source attachment", { from: copy.fromRelativePath }) : Effect.fail(new PersistenceSqlError({
30448
+ operation: "ProjectionPipeline.copyForkedAttachmentFile",
30449
+ detail: `Could not copy attachment ${copy.fromRelativePath}: ${error.message}`,
30450
+ cause: error
30451
+ }))));
30452
+ });
29896
30453
  const applyThreadMessagesProjection = Effect.fn("applyThreadMessagesProjection")(function* (event, attachmentSideEffects) {
29897
30454
  switch (event.type) {
30455
+ case "thread.created": {
30456
+ if (event.payload.forkedFromThreadId === void 0) return;
30457
+ const sourceRows = yield* projectionThreadMessageRepository.listByThreadId({ threadId: event.payload.forkedFromThreadId });
30458
+ yield* Effect.forEach(collectForkedAttachmentCopies(event.payload.threadId, sourceRows), copyForkedAttachmentFile, { concurrency: 1 });
30459
+ yield* Effect.forEach(copyForkedProjectionMessages(event.payload.threadId, sourceRows), projectionThreadMessageRepository.upsert, { concurrency: 1 }).pipe(Effect.asVoid);
30460
+ return;
30461
+ }
29898
30462
  case "thread.message-sent": {
29899
30463
  const existingMessage = yield* projectionThreadMessageRepository.getByMessageId({ messageId: event.payload.messageId });
29900
30464
  const previousMessage = Option.getOrUndefined(existingMessage);
@@ -29933,6 +30497,43 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
29933
30497
  default: return;
29934
30498
  }
29935
30499
  });
30500
+ const applyThreadScheduledTasksProjection = Effect.fn("applyThreadScheduledTasksProjection")(function* (event) {
30501
+ switch (event.type) {
30502
+ case "thread.scheduled-task.created":
30503
+ yield* projectionThreadScheduledTaskRepository.upsert({
30504
+ taskId: event.payload.task.id,
30505
+ threadId: event.payload.threadId,
30506
+ prompt: event.payload.task.prompt,
30507
+ runAt: event.payload.task.runAt,
30508
+ status: event.payload.task.status,
30509
+ createdAt: event.payload.task.createdAt,
30510
+ updatedAt: event.payload.task.updatedAt,
30511
+ firedAt: event.payload.task.firedAt,
30512
+ cancelledAt: event.payload.task.cancelledAt,
30513
+ failure: event.payload.task.failure
30514
+ });
30515
+ return;
30516
+ case "thread.scheduled-task.cancelled":
30517
+ case "thread.scheduled-task.fired": {
30518
+ const existing = yield* projectionThreadScheduledTaskRepository.getByTaskId({ taskId: event.payload.taskId });
30519
+ if (existing === null) return;
30520
+ yield* projectionThreadScheduledTaskRepository.upsert(event.type === "thread.scheduled-task.cancelled" ? {
30521
+ ...existing,
30522
+ status: "cancelled",
30523
+ cancelledAt: event.payload.cancelledAt,
30524
+ updatedAt: event.occurredAt
30525
+ } : {
30526
+ ...existing,
30527
+ status: event.payload.failure === null ? "fired" : "failed",
30528
+ firedAt: event.payload.firedAt,
30529
+ failure: event.payload.failure,
30530
+ updatedAt: event.occurredAt
30531
+ });
30532
+ return;
30533
+ }
30534
+ default: return;
30535
+ }
30536
+ });
29936
30537
  const applyThreadProposedPlansProjection = Effect.fn("applyThreadProposedPlansProjection")(function* (event, _attachmentSideEffects) {
29937
30538
  switch (event.type) {
29938
30539
  case "thread.proposed-plan-upserted":
@@ -30292,6 +30893,10 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30292
30893
  name: ORCHESTRATION_PROJECTOR_NAMES.threadProposedPlans,
30293
30894
  apply: applyThreadProposedPlansProjection
30294
30895
  },
30896
+ {
30897
+ name: ORCHESTRATION_PROJECTOR_NAMES.threadScheduledTasks,
30898
+ apply: applyThreadScheduledTasksProjection
30899
+ },
30295
30900
  {
30296
30901
  name: ORCHESTRATION_PROJECTOR_NAMES.threadActivities,
30297
30902
  apply: applyThreadActivitiesProjection
@@ -30341,7 +30946,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti
30341
30946
  projectEvent
30342
30947
  };
30343
30948
  });
30344
- const OrchestrationProjectionPipelineLive = Layer.effect(OrchestrationProjectionPipeline, makeOrchestrationProjectionPipeline()).pipe(Layer.provideMerge(ProjectionProjectRepositoryLive), Layer.provideMerge(ProjectionThreadRepositoryLive), Layer.provideMerge(ProjectionThreadMessageRepositoryLive), Layer.provideMerge(ProjectionThreadProposedPlanRepositoryLive), Layer.provideMerge(ProjectionThreadActivityRepositoryLive), Layer.provideMerge(ProjectionThreadSessionRepositoryLive), Layer.provideMerge(ProjectionTurnRepositoryLive), Layer.provideMerge(ProjectionPendingApprovalRepositoryLive), Layer.provideMerge(ProjectionStateRepositoryLive));
30949
+ 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));
30345
30950
  //#endregion
30346
30951
  //#region src/orchestration/ThreadBackgroundLiveness.ts
30347
30952
  /**
@@ -30369,7 +30974,7 @@ const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
30369
30974
  "interrupted"
30370
30975
  ]);
30371
30976
  var ThreadBackgroundLivenessService = class extends Context.Service()("@p4code/cli/orchestration/ThreadBackgroundLiveness/ThreadBackgroundLivenessService") {};
30372
- function make$74() {
30977
+ function make$75() {
30373
30978
  const stateByThreadId = /* @__PURE__ */ new Map();
30374
30979
  const stateFor = (threadId) => {
30375
30980
  const existing = stateByThreadId.get(threadId);
@@ -30419,7 +31024,7 @@ function make$74() {
30419
31024
  }
30420
31025
  };
30421
31026
  }
30422
- const layer$64 = Layer.effect(ThreadBackgroundLivenessService, Effect.sync(make$74));
31027
+ const layer$64 = Layer.effect(ThreadBackgroundLivenessService, Effect.sync(make$75));
30423
31028
  //#endregion
30424
31029
  //#region src/persistence/Services/ProjectionCheckpoints.ts
30425
31030
  /**
@@ -30988,12 +31593,12 @@ const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spaw
30988
31593
  stderrInvalidUtf8: stderr.invalidUtf8
30989
31594
  };
30990
31595
  });
30991
- const make$73 = Effect.fn("ProcessRunner.make")(function* () {
31596
+ const make$74 = Effect.fn("ProcessRunner.make")(function* () {
30992
31597
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
30993
31598
  const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
30994
31599
  return ProcessRunner.of({ run });
30995
31600
  });
30996
- const layer$63 = Layer.effect(ProcessRunner, make$73());
31601
+ const layer$63 = Layer.effect(ProcessRunner, make$74());
30997
31602
  //#endregion
30998
31603
  //#region src/project/RepositoryIdentityResolver.ts
30999
31604
  const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
@@ -31084,7 +31689,7 @@ const resolveRepositoryIdentityFromCacheKey = Effect.fn("RepositoryIdentityResol
31084
31689
  rootPath: cacheKey
31085
31690
  }) : null;
31086
31691
  });
31087
- const make$72 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
31692
+ const make$73 = Effect.fn("RepositoryIdentityResolver.make")(function* (options = {}) {
31088
31693
  const processRunner = yield* ProcessRunner;
31089
31694
  const repositoryIdentityCache = yield* Cache.makeWith((cacheKey) => resolveRepositoryIdentityFromCacheKey(cacheKey).pipe(Effect.provideService(ProcessRunner, processRunner)), {
31090
31695
  capacity: options.cacheCapacity ?? DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY,
@@ -31099,7 +31704,7 @@ const make$72 = Effect.fn("RepositoryIdentityResolver.make")(function* (options
31099
31704
  });
31100
31705
  return RepositoryIdentityResolver.of({ resolve });
31101
31706
  });
31102
- const layer$62 = Layer.effect(RepositoryIdentityResolver, make$72()).pipe(Layer.provide(layer$63));
31707
+ const layer$62 = Layer.effect(RepositoryIdentityResolver, make$73()).pipe(Layer.provide(layer$63));
31103
31708
  //#endregion
31104
31709
  //#region src/orchestration/Layers/ProjectionSnapshotQuery.ts
31105
31710
  const decodeReadModel = Schema$1.decodeUnknownEffect(OrchestrationReadModel);
@@ -31114,6 +31719,7 @@ const ProjectionThreadMessageDbRowSchema = ProjectionThreadMessage.mapFields(Str
31114
31719
  attachments: Schema$1.NullOr(Schema$1.fromJsonString(Schema$1.Array(ChatAttachment)))
31115
31720
  }));
31116
31721
  const ProjectionThreadProposedPlanDbRowSchema = ProjectionThreadProposedPlan;
31722
+ const ProjectionThreadScheduledTaskDbRowSchema = ProjectionThreadScheduledTask;
31117
31723
  const ProjectionThreadDbRowSchema = ProjectionThread.mapFields(Struct.assign({
31118
31724
  modelSelection: Schema$1.fromJsonString(ModelSelection),
31119
31725
  workspaceLifecycle: Schema$1.NullOr(Schema$1.fromJsonString(ThreadWorkspaceLifecycle))
@@ -31314,6 +31920,28 @@ function mapProjectShellRow(row, repositoryIdentity) {
31314
31920
  updatedAt: row.updatedAt
31315
31921
  };
31316
31922
  }
31923
+ function mapScheduledTaskRow(row) {
31924
+ return {
31925
+ id: row.taskId,
31926
+ prompt: row.prompt,
31927
+ runAt: row.runAt,
31928
+ status: row.status,
31929
+ createdAt: row.createdAt,
31930
+ updatedAt: row.updatedAt,
31931
+ firedAt: row.firedAt,
31932
+ cancelledAt: row.cancelledAt,
31933
+ failure: row.failure
31934
+ };
31935
+ }
31936
+ function groupScheduledTasksByThread(rows) {
31937
+ const byThread = /* @__PURE__ */ new Map();
31938
+ for (const row of rows) {
31939
+ const tasks = byThread.get(row.threadId) ?? [];
31940
+ tasks.push(mapScheduledTaskRow(row));
31941
+ byThread.set(row.threadId, tasks);
31942
+ }
31943
+ return byThread;
31944
+ }
31317
31945
  function mapProposedPlanRow(row) {
31318
31946
  return {
31319
31947
  id: row.planId,
@@ -31542,6 +32170,45 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
31542
32170
  updated_at AS "updatedAt"
31543
32171
  FROM projection_thread_proposed_plans
31544
32172
  ORDER BY thread_id ASC, created_at ASC, plan_id ASC
32173
+ `
32174
+ });
32175
+ const listThreadScheduledTaskRows = SqlSchema.findAll({
32176
+ Request: Schema$1.Void,
32177
+ Result: ProjectionThreadScheduledTaskDbRowSchema,
32178
+ execute: () => sql`
32179
+ SELECT
32180
+ task_id AS "taskId",
32181
+ thread_id AS "threadId",
32182
+ prompt,
32183
+ run_at AS "runAt",
32184
+ status,
32185
+ created_at AS "createdAt",
32186
+ updated_at AS "updatedAt",
32187
+ fired_at AS "firedAt",
32188
+ cancelled_at AS "cancelledAt",
32189
+ failure
32190
+ FROM projection_thread_scheduled_tasks
32191
+ ORDER BY thread_id ASC, run_at ASC, task_id ASC
32192
+ `
32193
+ });
32194
+ const listThreadScheduledTaskRowsByThread = SqlSchema.findAll({
32195
+ Request: ThreadIdLookupInput,
32196
+ Result: ProjectionThreadScheduledTaskDbRowSchema,
32197
+ execute: ({ threadId }) => sql`
32198
+ SELECT
32199
+ task_id AS "taskId",
32200
+ thread_id AS "threadId",
32201
+ prompt,
32202
+ run_at AS "runAt",
32203
+ status,
32204
+ created_at AS "createdAt",
32205
+ updated_at AS "updatedAt",
32206
+ fired_at AS "firedAt",
32207
+ cancelled_at AS "cancelledAt",
32208
+ failure
32209
+ FROM projection_thread_scheduled_tasks
32210
+ WHERE thread_id = ${threadId}
32211
+ ORDER BY run_at ASC, task_id ASC
31545
32212
  `
31546
32213
  });
31547
32214
  const listThreadActivityRows = SqlSchema.findAll({
@@ -32330,8 +32997,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32330
32997
  listTurnSummaryRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listTurnSummaries:query", "ProjectionSnapshotQuery.getSnapshot:listTurnSummaries:decodeRows"))),
32331
32998
  listLatestTurnRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listLatestTurns:query", "ProjectionSnapshotQuery.getSnapshot:listLatestTurns:decodeRows"))),
32332
32999
  listProjectionStateRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listProjectionState:query", "ProjectionSnapshotQuery.getSnapshot:listProjectionState:decodeRows"))),
32333
- listThreadPairRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listThreadPairs:query", "ProjectionSnapshotQuery.getSnapshot:listThreadPairs:decodeRows")))
32334
- ])).pipe(Effect.flatMap(([projectRows, threadRows, messageRows, proposedPlanRows, activityRows, sessionRows, checkpointRows, turnRows, latestTurnRows, stateRows, threadPairRows]) => Effect.gen(function* () {
33000
+ listThreadPairRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listThreadPairs:query", "ProjectionSnapshotQuery.getSnapshot:listThreadPairs:decodeRows"))),
33001
+ listThreadScheduledTaskRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getSnapshot:listThreadScheduledTasks:query", "ProjectionSnapshotQuery.getSnapshot:listThreadScheduledTasks:decodeRows")))
33002
+ ])).pipe(Effect.flatMap(([projectRows, threadRows, messageRows, proposedPlanRows, activityRows, sessionRows, checkpointRows, turnRows, latestTurnRows, stateRows, threadPairRows, scheduledTaskRows]) => Effect.gen(function* () {
33003
+ const scheduledTasksByThread = groupScheduledTasksByThread(scheduledTaskRows);
32335
33004
  const messagesByThread = /* @__PURE__ */ new Map();
32336
33005
  const proposedPlansByThread = /* @__PURE__ */ new Map();
32337
33006
  const activitiesByThread = /* @__PURE__ */ new Map();
@@ -32465,6 +33134,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32465
33134
  deletedAt: row.deletedAt,
32466
33135
  messages: messagesByThread.get(row.threadId) ?? [],
32467
33136
  proposedPlans: proposedPlansByThread.get(row.threadId) ?? [],
33137
+ scheduledTasks: scheduledTasksByThread.get(row.threadId) ?? [],
32468
33138
  activities: activitiesByThread.get(row.threadId) ?? [],
32469
33139
  checkpoints: checkpointsByThread.get(row.threadId) ?? [],
32470
33140
  session: sessionsByThread.get(row.threadId) ?? null
@@ -32509,8 +33179,10 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32509
33179
  listThreadSessionRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadSessions:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadSessions:decodeRows"))),
32510
33180
  listLatestTurnRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listLatestTurns:query", "ProjectionSnapshotQuery.getCommandReadModel:listLatestTurns:decodeRows"))),
32511
33181
  listProjectionStateRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listProjectionState:query", "ProjectionSnapshotQuery.getCommandReadModel:listProjectionState:decodeRows"))),
32512
- listThreadPairRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadPairs:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadPairs:decodeRows")))
32513
- ])).pipe(Effect.flatMap(([projectRows, threadRows, proposedPlanRows, sessionRows, latestTurnRows, stateRows, threadPairRows]) => Effect.sync(() => {
33182
+ listThreadPairRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadPairs:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadPairs:decodeRows"))),
33183
+ listThreadScheduledTaskRows(void 0).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getCommandReadModel:listThreadScheduledTasks:query", "ProjectionSnapshotQuery.getCommandReadModel:listThreadScheduledTasks:decodeRows")))
33184
+ ])).pipe(Effect.flatMap(([projectRows, threadRows, proposedPlanRows, sessionRows, latestTurnRows, stateRows, threadPairRows, scheduledTaskRows]) => Effect.sync(() => {
33185
+ const scheduledTasksByThread = groupScheduledTasksByThread(scheduledTaskRows);
32514
33186
  let updatedAt = null;
32515
33187
  const projects = [];
32516
33188
  const threads = [];
@@ -32605,6 +33277,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32605
33277
  deletedAt: row.deletedAt,
32606
33278
  messages: [],
32607
33279
  proposedPlans: proposedPlansByThread.get(row.threadId) ?? [],
33280
+ scheduledTasks: scheduledTasksByThread.get(row.threadId) ?? [],
32608
33281
  activities: [],
32609
33282
  checkpoints: [],
32610
33283
  session: sessionByThread.get(row.threadId) ?? null
@@ -32857,7 +33530,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32857
33530
  });
32858
33531
  });
32859
33532
  const getThreadDetailByIdBounded = (threadId, bounds) => Effect.gen(function* () {
32860
- const [threadRow, messageRows, proposedPlanRows, activityRows, checkpointRows, turnRows, latestTurnRow, sessionRow] = yield* Effect.all([
33533
+ const [threadRow, messageRows, proposedPlanRows, activityRows, checkpointRows, turnRows, latestTurnRow, sessionRow, scheduledTaskRows] = yield* Effect.all([
32861
33534
  getActiveThreadRowById({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getThread:query", "ProjectionSnapshotQuery.getThreadDetailById:getThread:decodeRow"))),
32862
33535
  (bounds === void 0 ? listThreadMessageRowsByThread({ threadId }) : listThreadMessageRowsByThreadWindow({
32863
33536
  threadId,
@@ -32871,7 +33544,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32871
33544
  listCheckpointRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listCheckpoints:query", "ProjectionSnapshotQuery.getThreadDetailById:listCheckpoints:decodeRows"))),
32872
33545
  listTurnSummaryRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listTurnSummaries:query", "ProjectionSnapshotQuery.getThreadDetailById:listTurnSummaries:decodeRows"))),
32873
33546
  getLatestTurnRowByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:query", "ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:decodeRow"))),
32874
- getThreadSessionRowByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getSession:query", "ProjectionSnapshotQuery.getThreadDetailById:getSession:decodeRow")))
33547
+ getThreadSessionRowByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getSession:query", "ProjectionSnapshotQuery.getThreadDetailById:getSession:decodeRow"))),
33548
+ listThreadScheduledTaskRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listScheduledTasks:query", "ProjectionSnapshotQuery.getThreadDetailById:listScheduledTasks:decodeRows")))
32875
33549
  ]);
32876
33550
  if (Option.isNone(threadRow)) return Option.none();
32877
33551
  const thread = {
@@ -32912,6 +33586,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32912
33586
  return message;
32913
33587
  }),
32914
33588
  proposedPlans: proposedPlanRows.map(mapProposedPlanRow),
33589
+ scheduledTasks: scheduledTaskRows.map(mapScheduledTaskRow),
32915
33590
  activities: activityRows.map((row) => {
32916
33591
  const activity = {
32917
33592
  id: row.activityId,
@@ -33564,7 +34239,7 @@ function mergeWithDefaultKeybindings(custom) {
33564
34239
  * Keybindings - Service tag for keybinding configuration operations.
33565
34240
  */
33566
34241
  var Keybindings = class extends Context.Service()("@p4code/cli/keybindings") {};
33567
- const make$71 = Effect.gen(function* () {
34242
+ const make$72 = Effect.gen(function* () {
33568
34243
  const { keybindingsConfigPath } = yield* ServerConfig$1;
33569
34244
  const fs = yield* FileSystem.FileSystem;
33570
34245
  const path = yield* Path.Path;
@@ -33825,7 +34500,7 @@ const make$71 = Effect.gen(function* () {
33825
34500
  }))
33826
34501
  };
33827
34502
  });
33828
- const layer$61 = Layer.effect(Keybindings, make$71);
34503
+ const layer$61 = Layer.effect(Keybindings, make$72);
33829
34504
  //#endregion
33830
34505
  //#region src/process/externalLauncher.ts
33831
34506
  /**
@@ -34052,7 +34727,7 @@ const launchEditorProcess = Effect.fn("externalLauncher.launchEditorProcess")(fu
34052
34727
  cause
34053
34728
  }));
34054
34729
  });
34055
- const make$70 = Effect.gen(function* () {
34730
+ const make$71 = Effect.gen(function* () {
34056
34731
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
34057
34732
  const fileSystem = yield* FileSystem.FileSystem;
34058
34733
  const path = yield* Path.Path;
@@ -34063,7 +34738,7 @@ const make$70 = Effect.gen(function* () {
34063
34738
  launchEditor: (input) => provideCommandResolutionServices(Effect.flatMap(resolveEditorLaunch(input), (launch) => launchEditorProcess(launch).pipe(Effect.provideService(ChildProcessSpawner$1.ChildProcessSpawner, spawner))))
34064
34739
  });
34065
34740
  });
34066
- const layer$60 = Layer.effect(ExternalLauncher, make$70);
34741
+ const layer$60 = Layer.effect(ExternalLauncher, make$71);
34067
34742
  //#endregion
34068
34743
  //#region src/orchestration/Services/OrchestrationReactor.ts
34069
34744
  /**
@@ -34081,7 +34756,7 @@ var OrchestrationReactor = class extends Context.Service()("@p4code/cli/orchestr
34081
34756
  //#endregion
34082
34757
  //#region src/serverLifecycleEvents.ts
34083
34758
  var ServerLifecycleEvents = class extends Context.Service()("@p4code/cli/serverLifecycleEvents") {};
34084
- const make$69 = Effect.gen(function* () {
34759
+ const make$70 = Effect.gen(function* () {
34085
34760
  const pubsub = yield* PubSub.unbounded();
34086
34761
  const state = yield* Ref.make({
34087
34762
  sequence: 0,
@@ -34105,7 +34780,7 @@ const make$69 = Effect.gen(function* () {
34105
34780
  }
34106
34781
  };
34107
34782
  });
34108
- const layer$59 = Layer.effect(ServerLifecycleEvents, make$69);
34783
+ const layer$59 = Layer.effect(ServerLifecycleEvents, make$70);
34109
34784
  //#endregion
34110
34785
  //#region src/telemetry/Identify.ts
34111
34786
  const CodexAuthJsonSchema = Schema$1.Struct({ tokens: Schema$1.Struct({ account_id: Schema$1.String }) });
@@ -34278,7 +34953,7 @@ var AnalyticsService = class AnalyticsService extends Context.Service()("@p4code
34278
34953
  /** No-op layer for callers that intentionally disable telemetry. */
34279
34954
  static layerTest = Layer.succeed(AnalyticsService, inert);
34280
34955
  };
34281
- const make$68 = Effect.gen(function* () {
34956
+ const make$69 = Effect.gen(function* () {
34282
34957
  const telemetryConfig = yield* TelemetryEnvConfig;
34283
34958
  const posthogKey = telemetryConfig.posthogKey.trim();
34284
34959
  if (!telemetryConfig.enabled || posthogKey === "") return inert;
@@ -34348,7 +35023,7 @@ const make$68 = Effect.gen(function* () {
34348
35023
  flush
34349
35024
  });
34350
35025
  });
34351
- const layer$58 = Layer.effect(AnalyticsService, make$68);
35026
+ const layer$58 = Layer.effect(AnalyticsService, make$69);
34352
35027
  AnalyticsService.layerTest;
34353
35028
  //#endregion
34354
35029
  //#region src/service/pinnedRuntime.ts
@@ -34695,7 +35370,7 @@ var BootServiceInstallError = class extends Schema$1.TaggedErrorClass()("BootSer
34695
35370
  }
34696
35371
  };
34697
35372
  var BootService = class extends Context.Service()("@p4code/cli/service/bootService") {};
34698
- const make$67 = Effect.fn("cloud.boot_service.make")(function* (input) {
35373
+ const make$68 = Effect.fn("cloud.boot_service.make")(function* (input) {
34699
35374
  const hostExecPath = yield* HostProcessExecutablePath;
34700
35375
  const hostArguments = yield* HostProcessArguments;
34701
35376
  const host = input.host ?? {
@@ -34917,7 +35592,7 @@ const make$67 = Effect.fn("cloud.boot_service.make")(function* (input) {
34917
35592
  logPath
34918
35593
  });
34919
35594
  });
34920
- const layer$57 = (input) => Layer.effect(BootService, make$67(input));
35595
+ const layer$57 = (input) => Layer.effect(BootService, make$68(input));
34921
35596
  //#endregion
34922
35597
  //#region src/service/selfUpdate.ts
34923
35598
  /**
@@ -34992,7 +35667,7 @@ const resolveServerSelfUpdateCapability = Effect.fn("cloud.server_self_update.re
34992
35667
  return null;
34993
35668
  });
34994
35669
  var ServerSelfUpdate = class extends Context.Service()("@p4code/cli/service/selfUpdate/ServerSelfUpdate") {};
34995
- const make$66 = Effect.fn("cloud.server_self_update.make")(function* (options) {
35670
+ const make$67 = Effect.fn("cloud.server_self_update.make")(function* (options) {
34996
35671
  const serverConfig = yield* ServerConfig$1;
34997
35672
  const fs = yield* FileSystem.FileSystem;
34998
35673
  const path = yield* Path.Path;
@@ -35142,7 +35817,7 @@ const make$66 = Effect.fn("cloud.server_self_update.make")(function* (options) {
35142
35817
  });
35143
35818
  return ServerSelfUpdate.of({ update });
35144
35819
  });
35145
- const layer$56 = Layer.effect(ServerSelfUpdate, make$66()).pipe(Layer.provide(layer$63));
35820
+ const layer$56 = Layer.effect(ServerSelfUpdate, make$67()).pipe(Layer.provide(layer$63));
35146
35821
  //#endregion
35147
35822
  //#region src/environment/ServerEnvironmentLabel.ts
35148
35823
  const ServerEnvironmentLabelCommandProbe = Schema$1.Literals(["macos-computer-name", "linux-pretty-hostname"]);
@@ -35274,7 +35949,7 @@ function platformArch(architecture) {
35274
35949
  default: return "other";
35275
35950
  }
35276
35951
  }
35277
- const make$65 = Effect.gen(function* () {
35952
+ const make$66 = Effect.gen(function* () {
35278
35953
  const fileSystem = yield* FileSystem.FileSystem;
35279
35954
  const path = yield* Path.Path;
35280
35955
  const serverConfig = yield* ServerConfig$1;
@@ -35325,6 +36000,8 @@ const make$65 = Effect.gen(function* () {
35325
36000
  threadSettlement: true,
35326
36001
  threadSnooze: true,
35327
36002
  threadPinning: true,
36003
+ threadFork: true,
36004
+ threadScheduledTasks: true,
35328
36005
  ...serverSelfUpdate === null ? {} : { serverSelfUpdate },
35329
36006
  ...serverServiceSupervised ? { serverServiceSupervised } : {}
35330
36007
  }
@@ -35339,7 +36016,7 @@ const make$65 = Effect.gen(function* () {
35339
36016
  * state. It intentionally has no fallback Layer.succeed value: callers must
35340
36017
  * provide the external platform services and a ServerConfig.
35341
36018
  */
35342
- const layer$55 = Layer.effect(ServerEnvironment, make$65).pipe(Layer.provide(layer$63));
36019
+ const layer$55 = Layer.effect(ServerEnvironment, make$66).pipe(Layer.provide(layer$63));
35343
36020
  //#endregion
35344
36021
  //#region src/provider/Services/ProviderSessionReaper.ts
35345
36022
  var ProviderSessionReaper = class extends Context.Service()("@p4code/cli/provider/Services/ProviderSessionReaper") {};
@@ -35481,7 +36158,7 @@ const maybeOpenBrowser = (target) => Effect.gen(function* () {
35481
36158
  yield* (yield* ExternalLauncher).launchBrowser(target).pipe(Effect.catch(() => Effect.logInfo("browser auto-open unavailable", { hint: `Open ${target} in your browser.` })));
35482
36159
  });
35483
36160
  const runStartupPhase = (phase, effect) => effect.pipe(Effect.annotateSpans({ "startup.phase": phase }), Effect.withSpan(`server.startup.${phase}`));
35484
- const make$64 = Effect.gen(function* () {
36161
+ const make$65 = Effect.gen(function* () {
35485
36162
  const serverConfig = yield* ServerConfig$1;
35486
36163
  const keybindings = yield* Keybindings;
35487
36164
  const orchestrationReactor = yield* OrchestrationReactor;
@@ -35622,7 +36299,7 @@ const make$64 = Effect.gen(function* () {
35622
36299
  enqueueCommand: commandGate.enqueueCommand
35623
36300
  };
35624
36301
  });
35625
- const layer$54 = Layer.effect(ServerRuntimeStartup, make$64);
36302
+ const layer$54 = Layer.effect(ServerRuntimeStartup, make$65);
35626
36303
  //#endregion
35627
36304
  //#region src/serverRuntimeState.ts
35628
36305
  const PersistedServerRuntimeState = Schema$1.Struct({
@@ -35779,7 +36456,7 @@ function expandHomePath$2(input, path) {
35779
36456
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
35780
36457
  return input;
35781
36458
  }
35782
- const make$63 = Effect.gen(function* () {
36459
+ const make$64 = Effect.gen(function* () {
35783
36460
  const fileSystem = yield* FileSystem.FileSystem;
35784
36461
  const path = yield* Path.Path;
35785
36462
  const statWorkspaceRoot = Effect.fn("WorkspacePaths.statWorkspaceRoot")(function* (workspaceRoot, normalizedWorkspaceRoot, phase) {
@@ -35836,7 +36513,7 @@ const make$63 = Effect.gen(function* () {
35836
36513
  resolveRelativePathWithinRoot
35837
36514
  });
35838
36515
  });
35839
- const layer$53 = Layer.effect(WorkspacePaths, make$63);
36516
+ const layer$53 = Layer.effect(WorkspacePaths, make$64);
35840
36517
  //#endregion
35841
36518
  //#region src/cli/project.ts
35842
36519
  const isEnvironmentHttpCommonError = Schema$1.is(EnvironmentHttpCommonError);
@@ -37019,7 +37696,7 @@ const logP4ProjectFileLoadError = (error) => Effect.logWarning(error).pipe(Effec
37019
37696
  filePath: error.filePath,
37020
37697
  errorTag: error._tag
37021
37698
  }));
37022
- const make$62 = Effect.gen(function* () {
37699
+ const make$63 = Effect.gen(function* () {
37023
37700
  const fileSystem = yield* FileSystem.FileSystem;
37024
37701
  const path = yield* Path.Path;
37025
37702
  const load = Effect.fn("P4ProjectFileLoader.load")(function* (workspaceRoot) {
@@ -37040,7 +37717,7 @@ const make$62 = Effect.gen(function* () {
37040
37717
  });
37041
37718
  return P4ProjectFileLoader.of({ load });
37042
37719
  });
37043
- const layer$52 = Layer.effect(P4ProjectFileLoader, make$62);
37720
+ const layer$52 = Layer.effect(P4ProjectFileLoader, make$63);
37044
37721
  //#endregion
37045
37722
  //#region src/project/ProjectFaviconResolver.ts
37046
37723
  /**
@@ -37115,7 +37792,7 @@ function extractIconHref(source) {
37115
37792
  return null;
37116
37793
  }
37117
37794
  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) }));
37118
- const make$61 = Effect.gen(function* () {
37795
+ const make$62 = Effect.gen(function* () {
37119
37796
  const fileSystem = yield* FileSystem.FileSystem;
37120
37797
  const path = yield* Path.Path;
37121
37798
  const workspacePaths = yield* WorkspacePaths;
@@ -37184,7 +37861,7 @@ const make$61 = Effect.gen(function* () {
37184
37861
  });
37185
37862
  return ProjectFaviconResolver.of({ resolvePath });
37186
37863
  });
37187
- const layer$51 = Layer.effect(ProjectFaviconResolver, make$61);
37864
+ const layer$51 = Layer.effect(ProjectFaviconResolver, make$62);
37188
37865
  //#endregion
37189
37866
  //#region src/assets/AssetAccess.ts
37190
37867
  const ASSET_ROUTE_PREFIX = "/api/assets";
@@ -37595,10 +38272,10 @@ const resolveAsset = Effect.fn("AssetAccess.resolveAsset")(function* (token, rel
37595
38272
  //#endregion
37596
38273
  //#region src/observability/BrowserTraceCollector.ts
37597
38274
  var BrowserTraceCollector = class extends Context.Service()("@p4code/cli/observability/BrowserTraceCollector") {};
37598
- const make$60 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
38275
+ const make$61 = (sink) => BrowserTraceCollector.of({ record: (records) => Effect.sync(() => {
37599
38276
  for (const record of records) sink.push(record);
37600
38277
  }) });
37601
- const layer$50 = (sink) => Layer.succeed(BrowserTraceCollector, make$60(sink));
38278
+ const layer$50 = (sink) => Layer.succeed(BrowserTraceCollector, make$61(sink));
37602
38279
  //#endregion
37603
38280
  //#region src/auth/http.ts
37604
38281
  const CREDENTIAL_RESPONSE_HEADERS = {
@@ -40651,7 +41328,7 @@ const classifyNonZeroExit = (command, stderr) => {
40651
41328
  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";
40652
41329
  return "command-failed";
40653
41330
  };
40654
- const make$59 = Effect.gen(function* () {
41331
+ const make$60 = Effect.gen(function* () {
40655
41332
  const processRunner = yield* ProcessRunner;
40656
41333
  const run = Effect.fn("VcsProcess.run")(function* (input) {
40657
41334
  const baseError = {
@@ -40710,7 +41387,7 @@ const make$59 = Effect.gen(function* () {
40710
41387
  });
40711
41388
  return VcsProcess.of({ run });
40712
41389
  });
40713
- const layer$49 = Layer.effect(VcsProcess, make$59).pipe(Layer.provide(layer$63));
41390
+ const layer$49 = Layer.effect(VcsProcess, make$60).pipe(Layer.provide(layer$63));
40714
41391
  //#endregion
40715
41392
  //#region src/vcs/VcsDriver.ts
40716
41393
  var VcsDriver = class extends Context.Service()("@p4code/cli/vcs/VcsDriver") {};
@@ -41161,12 +41838,12 @@ const makeVcsDriver = Effect.gen(function* () {
41161
41838
  const driver = yield* makeVcsDriverShape();
41162
41839
  return VcsDriver.of(driver);
41163
41840
  });
41164
- const make$58 = Effect.gen(function* () {
41841
+ const make$59 = Effect.gen(function* () {
41165
41842
  const git = yield* makeGitVcsDriverCore();
41166
41843
  return GitVcsDriver.of(git);
41167
41844
  });
41168
41845
  Layer.effect(VcsDriver, makeVcsDriver);
41169
- const layer$48 = Layer.effect(GitVcsDriver, make$58);
41846
+ const layer$48 = Layer.effect(GitVcsDriver, make$59);
41170
41847
  //#endregion
41171
41848
  //#region src/vcs/VcsProjectConfig.ts
41172
41849
  const ProjectVcsConfigJson = fromLenientJson(Schema$1.Struct({
@@ -41198,7 +41875,7 @@ const logVcsProjectConfigError = (error) => Effect.logWarning(error).pipe(Effect
41198
41875
  configPath: error.configPath,
41199
41876
  errorTag: error._tag
41200
41877
  }));
41201
- const make$57 = Effect.gen(function* () {
41878
+ const make$58 = Effect.gen(function* () {
41202
41879
  const fileSystem = yield* FileSystem.FileSystem;
41203
41880
  const path = yield* Path.Path;
41204
41881
  const findConfigPath = Effect.fn("VcsProjectConfig.findConfigPath")(function* (cwd) {
@@ -41239,7 +41916,7 @@ const make$57 = Effect.gen(function* () {
41239
41916
  });
41240
41917
  return VcsProjectConfig.of({ resolveKind });
41241
41918
  });
41242
- const layer$47 = Layer.effect(VcsProjectConfig, make$57);
41919
+ const layer$47 = Layer.effect(VcsProjectConfig, make$58);
41243
41920
  //#endregion
41244
41921
  //#region src/vcs/VcsDriverRegistry.ts
41245
41922
  const DETECTION_CACHE_CAPACITY = 2048;
@@ -41259,7 +41936,7 @@ function parseDetectionCacheKey(key) {
41259
41936
  cwd: key.slice(separatorIndex + 1)
41260
41937
  };
41261
41938
  }
41262
- const make$56 = Effect.gen(function* () {
41939
+ const make$57 = Effect.gen(function* () {
41263
41940
  const projectConfig = yield* VcsProjectConfig;
41264
41941
  const git = yield* makeVcsDriver;
41265
41942
  const drivers = { git };
@@ -41316,7 +41993,7 @@ const make$56 = Effect.gen(function* () {
41316
41993
  resolve
41317
41994
  });
41318
41995
  });
41319
- const layer$46 = Layer.effect(VcsDriverRegistry, make$56).pipe(Layer.provide(layer$47));
41996
+ const layer$46 = Layer.effect(VcsDriverRegistry, make$57).pipe(Layer.provide(layer$47));
41320
41997
  //#endregion
41321
41998
  //#region src/checkpointing/CheckpointStore.ts
41322
41999
  /**
@@ -41336,7 +42013,7 @@ const layer$46 = Layer.effect(VcsDriverRegistry, make$56).pipe(Layer.provide(lay
41336
42013
  */
41337
42014
  /** Service tag for checkpoint persistence and restore operations. */
41338
42015
  var CheckpointStore = class extends Context.Service()("@p4code/cli/checkpointing/CheckpointStore") {};
41339
- const make$55 = Effect.gen(function* () {
42016
+ const make$56 = Effect.gen(function* () {
41340
42017
  const vcsRegistry = yield* VcsDriverRegistry;
41341
42018
  const resolveCheckpoints = Effect.fn("CheckpointStore.resolveCheckpoints")(function* (operation, cwd) {
41342
42019
  const handle = yield* vcsRegistry.resolve({ cwd });
@@ -41375,7 +42052,7 @@ const make$55 = Effect.gen(function* () {
41375
42052
  deleteCheckpointRefs
41376
42053
  });
41377
42054
  });
41378
- const layer$45 = Layer.effect(CheckpointStore, make$55);
42055
+ const layer$45 = Layer.effect(CheckpointStore, make$56);
41379
42056
  //#endregion
41380
42057
  //#region src/checkpointing/CheckpointDiffQuery.ts
41381
42058
  /**
@@ -41397,7 +42074,7 @@ function buildTurnDiffResult(input, diff) {
41397
42074
  diff
41398
42075
  };
41399
42076
  }
41400
- const make$54 = Effect.gen(function* () {
42077
+ const make$55 = Effect.gen(function* () {
41401
42078
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
41402
42079
  const checkpointStore = yield* CheckpointStore;
41403
42080
  const threadActivities = yield* ProjectionThreadActivityRepository;
@@ -41568,7 +42245,7 @@ const make$54 = Effect.gen(function* () {
41568
42245
  getFullThreadDiff
41569
42246
  });
41570
42247
  });
41571
- const layer$44 = Layer.effect(CheckpointDiffQuery, make$54);
42248
+ const layer$44 = Layer.effect(CheckpointDiffQuery, make$55);
41572
42249
  //#endregion
41573
42250
  //#region src/orchestration/Normalizer.ts
41574
42251
  const canonicalizeClientCommandTimestamps = (command, receivedAt) => {
@@ -41683,11 +42360,11 @@ const makeTextGenerationFromRegistry = (registry) => TextGeneration.of({
41683
42360
  detail: "This provider does not report account usage."
41684
42361
  }))))
41685
42362
  });
41686
- const make$53 = Effect.gen(function* () {
42363
+ const make$54 = Effect.gen(function* () {
41687
42364
  const registry = yield* ProviderInstanceRegistry;
41688
42365
  return makeTextGenerationFromRegistry(registry);
41689
42366
  });
41690
- const layer$43 = Layer.effect(TextGeneration, make$53);
42367
+ const layer$43 = Layer.effect(TextGeneration, make$54);
41691
42368
  //#endregion
41692
42369
  //#region src/textGeneration/TextGenerationPresets.ts
41693
42370
  const conventionalCommitsTextGenerationPolicy = {
@@ -42019,7 +42696,7 @@ const serversEqual = (left, right) => {
42019
42696
  }
42020
42697
  return true;
42021
42698
  };
42022
- const make$52 = Effect.gen(function* PortDiscoveryMake() {
42699
+ const make$53 = Effect.gen(function* PortDiscoveryMake() {
42023
42700
  const net = yield* NetService;
42024
42701
  const processRunner = yield* ProcessRunner;
42025
42702
  const hostPlatform = yield* HostProcessPlatform;
@@ -42170,7 +42847,7 @@ const make$52 = Effect.gen(function* PortDiscoveryMake() {
42170
42847
  unregisterTerminal
42171
42848
  });
42172
42849
  }).pipe(Effect.withSpan("PortDiscovery.make"));
42173
- const layer$42 = Layer.effect(PortDiscovery, make$52);
42850
+ const layer$42 = Layer.effect(PortDiscovery, make$53);
42174
42851
  //#endregion
42175
42852
  //#region src/terminal/Manager.ts
42176
42853
  /**
@@ -42848,7 +43525,7 @@ function normalizedRuntimeEnv(env) {
42848
43525
  if (entries.length === 0) return null;
42849
43526
  return Object.fromEntries(entries.toSorted(([left], [right]) => left.localeCompare(right)));
42850
43527
  }
42851
- const make$51 = Effect.fn("TerminalManager.make")(function* () {
43528
+ const make$52 = Effect.fn("TerminalManager.make")(function* () {
42852
43529
  const { terminalLogsDir } = yield* ServerConfig$1;
42853
43530
  const ptyAdapter = yield* PtyAdapter;
42854
43531
  const portDiscovery = yield* PortDiscovery;
@@ -43810,7 +44487,7 @@ const makeWithOptions$1 = Effect.fn("TerminalManager.makeWithOptions")(function*
43810
44487
  subscribeMetadata
43811
44488
  });
43812
44489
  });
43813
- const layer$41 = Layer.effect(TerminalManager, make$51()).pipe(Layer.provide(layer$63));
44490
+ const layer$41 = Layer.effect(TerminalManager, make$52()).pipe(Layer.provide(layer$63));
43814
44491
  //#endregion
43815
44492
  //#region src/project/ProjectSetupScriptRunner.ts
43816
44493
  var ProjectSetupScriptOperationError = class extends Schema$1.TaggedErrorClass()("ProjectSetupScriptOperationError", {
@@ -43841,7 +44518,7 @@ var ProjectSetupScriptProjectNotFoundError = class extends Schema$1.TaggedErrorC
43841
44518
  };
43842
44519
  Schema$1.Union([ProjectSetupScriptOperationError, ProjectSetupScriptProjectNotFoundError]);
43843
44520
  var ProjectSetupScriptRunner = class extends Context.Service()("@p4code/cli/project/ProjectSetupScriptRunner") {};
43844
- const make$50 = Effect.gen(function* () {
44521
+ const make$51 = Effect.gen(function* () {
43845
44522
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
43846
44523
  const terminalManager = yield* TerminalManager;
43847
44524
  const runForThread = Effect.fn("ProjectSetupScriptRunner.runForThread")(function* (input) {
@@ -43899,7 +44576,7 @@ const make$50 = Effect.gen(function* () {
43899
44576
  });
43900
44577
  return ProjectSetupScriptRunner.of({ runForThread });
43901
44578
  });
43902
- const layer$40 = Layer.effect(ProjectSetupScriptRunner, make$50);
44579
+ const layer$40 = Layer.effect(ProjectSetupScriptRunner, make$51);
43903
44580
  //#endregion
43904
44581
  //#region src/provider/Services/ProviderRegistry.ts
43905
44582
  var ProviderRegistry = class extends Context.Service()("@p4code/cli/provider/Services/ProviderRegistry") {};
@@ -44226,7 +44903,7 @@ function decodeAzureDevOpsJson(raw, schema, operation, cwd) {
44226
44903
  cause
44227
44904
  })));
44228
44905
  }
44229
- const make$49 = Effect.gen(function* () {
44906
+ const make$50 = Effect.gen(function* () {
44230
44907
  const process = yield* VcsProcess;
44231
44908
  const execute = (input) => process.run({
44232
44909
  operation: "AzureDevOpsCli.execute",
@@ -44368,7 +45045,7 @@ const make$49 = Effect.gen(function* () {
44368
45045
  }).pipe(Effect.asVoid)
44369
45046
  });
44370
45047
  });
44371
- const layer$39 = Layer.effect(AzureDevOpsCli, make$49);
45048
+ const layer$39 = Layer.effect(AzureDevOpsCli, make$50);
44372
45049
  //#endregion
44373
45050
  //#region src/sourceControl/SourceControlProviderDiscovery.ts
44374
45051
  function firstNonEmptyLine(text) {
@@ -44571,7 +45248,7 @@ function toChangeRequest$5(summary) {
44571
45248
  isCrossRepository: false
44572
45249
  };
44573
45250
  }
44574
- const make$48 = Effect.gen(function* () {
45251
+ const make$49 = Effect.gen(function* () {
44575
45252
  const azure = yield* AzureDevOpsCli;
44576
45253
  return SourceControlProvider.of({
44577
45254
  kind: "azure-devops",
@@ -44663,7 +45340,7 @@ const make$48 = Effect.gen(function* () {
44663
45340
  })))
44664
45341
  });
44665
45342
  });
44666
- Layer.effect(SourceControlProvider, make$48);
45343
+ Layer.effect(SourceControlProvider, make$49);
44667
45344
  //#endregion
44668
45345
  //#region src/sourceControl/bitbucketPullRequests.ts
44669
45346
  const BitbucketRepositoryRefSchema = Schema$1.Struct({
@@ -45040,7 +45717,7 @@ function responseError(operation, response) {
45040
45717
  responseBodyLength: collected.text.length
45041
45718
  }))));
45042
45719
  }
45043
- const make$47 = Effect.gen(function* () {
45720
+ const make$48 = Effect.gen(function* () {
45044
45721
  const config = yield* BitbucketApiEnvConfig;
45045
45722
  const httpClient = yield* HttpClient.HttpClient;
45046
45723
  const fileSystem = yield* FileSystem.FileSystem;
@@ -45256,7 +45933,7 @@ const make$47 = Effect.gen(function* () {
45256
45933
  })))
45257
45934
  });
45258
45935
  });
45259
- const layer$37 = Layer.effect(BitbucketApi, make$47);
45936
+ const layer$37 = Layer.effect(BitbucketApi, make$48);
45260
45937
  //#endregion
45261
45938
  //#region src/sourceControl/BitbucketSourceControlProvider.ts
45262
45939
  function toChangeRequest$4(summary) {
@@ -45274,7 +45951,7 @@ function toChangeRequest$4(summary) {
45274
45951
  ...summary.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin } : {}
45275
45952
  };
45276
45953
  }
45277
- const make$46 = Effect.gen(function* () {
45954
+ const make$47 = Effect.gen(function* () {
45278
45955
  const bitbucket = yield* BitbucketApi;
45279
45956
  return SourceControlProvider.of({
45280
45957
  kind: "bitbucket",
@@ -45365,7 +46042,7 @@ const make$46 = Effect.gen(function* () {
45365
46042
  })))
45366
46043
  });
45367
46044
  });
45368
- Layer.effect(SourceControlProvider, make$46);
46045
+ Layer.effect(SourceControlProvider, make$47);
45369
46046
  const makeDiscovery = Effect.gen(function* () {
45370
46047
  return {
45371
46048
  type: "api",
@@ -45607,7 +46284,7 @@ function deriveRepositoryCloneUrlsFromCreateOutput(stdout, repository) {
45607
46284
  sshUrl: `git@${fallbackHost}:${repository}.git`
45608
46285
  };
45609
46286
  }
45610
- const make$45 = Effect.gen(function* () {
46287
+ const make$46 = Effect.gen(function* () {
45611
46288
  const process = yield* VcsProcess;
45612
46289
  const execute = (input) => process.run({
45613
46290
  operation: "GitHubCli.execute",
@@ -45725,7 +46402,7 @@ const make$45 = Effect.gen(function* () {
45725
46402
  }).pipe(Effect.asVoid)
45726
46403
  });
45727
46404
  });
45728
- const layer$35 = Layer.effect(GitHubCli, make$45);
46405
+ const layer$35 = Layer.effect(GitHubCli, make$46);
45729
46406
  //#endregion
45730
46407
  //#region src/sourceControl/gitHubAuthStatus.ts
45731
46408
  const GitHubAuthStatusAccountSchema = Schema$1.Struct({
@@ -45826,7 +46503,7 @@ const discovery$1 = {
45826
46503
  parseAuth: parseGitHubAuth,
45827
46504
  installHint: "Install the GitHub command-line tool (`gh`) via https://cli.github.com/ or your package manager (for example `brew install gh`)."
45828
46505
  };
45829
- const make$44 = Effect.gen(function* () {
46506
+ const make$45 = Effect.gen(function* () {
45830
46507
  const github = yield* GitHubCli;
45831
46508
  const listChangeRequests = (input) => {
45832
46509
  if (input.state === "open") return github.listOpenPullRequests({
@@ -45942,7 +46619,7 @@ const make$44 = Effect.gen(function* () {
45942
46619
  })))
45943
46620
  });
45944
46621
  });
45945
- Layer.effect(SourceControlProvider, make$44);
46622
+ Layer.effect(SourceControlProvider, make$45);
45946
46623
  //#endregion
45947
46624
  //#region src/sourceControl/gitLabMergeRequests.ts
45948
46625
  const GitLabProjectReferenceSchema = Schema$1.Struct({
@@ -46254,7 +46931,7 @@ function parseRepositoryPath(repository) {
46254
46931
  projectPath
46255
46932
  };
46256
46933
  }
46257
- const make$43 = Effect.gen(function* () {
46934
+ const make$44 = Effect.gen(function* () {
46258
46935
  const process = yield* VcsProcess;
46259
46936
  const run = (input, mapError) => process.run({
46260
46937
  operation: "GitLabCli.execute",
@@ -46405,7 +47082,7 @@ const make$43 = Effect.gen(function* () {
46405
47082
  }).pipe(Effect.asVoid)
46406
47083
  });
46407
47084
  });
46408
- const layer$33 = Layer.effect(GitLabCli, make$43);
47085
+ const layer$33 = Layer.effect(GitLabCli, make$44);
46409
47086
  //#endregion
46410
47087
  //#region src/sourceControl/gitLabAuthStatus.ts
46411
47088
  const HOST_LINE_PATTERN = /^(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?|\[[a-f0-9:.]+\])(?::\d+)?$/iu;
@@ -46502,7 +47179,7 @@ const discovery = {
46502
47179
  refineUnknownRemote: refineUnknownGitLabRemote,
46503
47180
  installHint: "Install the GitLab command-line tool (`glab`) from https://gitlab.com/gitlab-org/cli or your package manager (for example `brew install glab`)."
46504
47181
  };
46505
- const make$42 = Effect.gen(function* () {
47182
+ const make$43 = Effect.gen(function* () {
46506
47183
  const gitlab = yield* GitLabCli;
46507
47184
  return SourceControlProvider.of({
46508
47185
  kind: "gitlab",
@@ -46590,7 +47267,7 @@ const make$42 = Effect.gen(function* () {
46590
47267
  })))
46591
47268
  });
46592
47269
  });
46593
- Layer.effect(SourceControlProvider, make$42);
47270
+ Layer.effect(SourceControlProvider, make$43);
46594
47271
  //#endregion
46595
47272
  //#region src/sourceControl/SourceControlProviderRegistry.ts
46596
47273
  const PROVIDER_DETECTION_CACHE_CAPACITY = 2048;
@@ -46746,12 +47423,12 @@ const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWithProvid
46746
47423
  })), { concurrency: "unbounded" })
46747
47424
  });
46748
47425
  });
46749
- const make$41 = Effect.gen(function* () {
46750
- const github = yield* make$44;
46751
- const gitlab = yield* make$42;
46752
- const bitbucket = yield* make$46;
47426
+ const make$42 = Effect.gen(function* () {
47427
+ const github = yield* make$45;
47428
+ const gitlab = yield* make$43;
47429
+ const bitbucket = yield* make$47;
46753
47430
  const bitbucketDiscovery = yield* makeDiscovery;
46754
- const azureDevOps = yield* make$48;
47431
+ const azureDevOps = yield* make$49;
46755
47432
  return yield* makeWithProviders([
46756
47433
  {
46757
47434
  kind: "github",
@@ -46775,7 +47452,7 @@ const make$41 = Effect.gen(function* () {
46775
47452
  }
46776
47453
  ]);
46777
47454
  });
46778
- const layer$31 = Layer.effect(SourceControlProviderRegistry, make$41);
47455
+ const layer$31 = Layer.effect(SourceControlProviderRegistry, make$42);
46779
47456
  //#endregion
46780
47457
  //#region src/sourceControl/PrTemplateDetection.ts
46781
47458
  const TEMPLATE_MAX_BYTES = 8e3;
@@ -47145,7 +47822,7 @@ function toPullRequestHeadRemoteInfo(pr) {
47145
47822
  ...pr.headRepositoryOwnerLogin !== void 0 ? { headRepositoryOwnerLogin: pr.headRepositoryOwnerLogin } : {}
47146
47823
  };
47147
47824
  }
47148
- const make$40 = Effect.gen(function* () {
47825
+ const make$41 = Effect.gen(function* () {
47149
47826
  const gitCore = yield* GitVcsDriver;
47150
47827
  const sourceControlProviders = yield* SourceControlProviderRegistry;
47151
47828
  const textGeneration = yield* TextGeneration;
@@ -48067,7 +48744,7 @@ const make$40 = Effect.gen(function* () {
48067
48744
  runStackedAction
48068
48745
  });
48069
48746
  });
48070
- const layer$30 = Layer.effect(GitManager, make$40);
48747
+ const layer$30 = Layer.effect(GitManager, make$41);
48071
48748
  //#endregion
48072
48749
  //#region src/git/GitWorkflowService.ts
48073
48750
  var GitWorkflowService = class extends Context.Service()("@p4code/cli/git/GitWorkflowService") {};
@@ -48104,7 +48781,7 @@ function nonRepositoryListRefs() {
48104
48781
  totalCount: 0
48105
48782
  };
48106
48783
  }
48107
- const make$39 = Effect.gen(function* () {
48784
+ const make$40 = Effect.gen(function* () {
48108
48785
  const registry = yield* VcsDriverRegistry;
48109
48786
  const git = yield* GitVcsDriver;
48110
48787
  const gitManager = yield* GitManager;
@@ -48190,7 +48867,7 @@ const make$39 = Effect.gen(function* () {
48190
48867
  renameBranch: (input) => ensureGit("GitWorkflowService.renameBranch", input.cwd).pipe(Effect.andThen(git.renameBranch(input)))
48191
48868
  });
48192
48869
  });
48193
- const layer$29 = Layer.effect(GitWorkflowService, make$39);
48870
+ const layer$29 = Layer.effect(GitWorkflowService, make$40);
48194
48871
  //#endregion
48195
48872
  //#region src/pullRequest/PullRequestProvider.ts
48196
48873
  /**
@@ -48551,7 +49228,7 @@ function isReviewerName(value) {
48551
49228
  const name = value.trim();
48552
49229
  return name.length > 0 && !name.startsWith("-");
48553
49230
  }
48554
- const make$38 = Effect.gen(function* () {
49231
+ const make$39 = Effect.gen(function* () {
48555
49232
  const azure = yield* AzureDevOpsCli;
48556
49233
  const detectArgs = ["--detect", "true"];
48557
49234
  const executeJson = (input) => azure.execute({
@@ -48751,7 +49428,7 @@ const make$38 = Effect.gen(function* () {
48751
49428
  }).pipe(Effect.asVoid)
48752
49429
  });
48753
49430
  });
48754
- const layer$28 = Layer.effect(AzureDevOpsPullRequestCli, make$38);
49431
+ const layer$28 = Layer.effect(AzureDevOpsPullRequestCli, make$39);
48755
49432
  //#endregion
48756
49433
  //#region src/pullRequest/AzureDevOpsPullRequestProvider.ts
48757
49434
  const CAPABILITIES$3 = {
@@ -48826,7 +49503,7 @@ function toChangeRequest$1(pullRequest) {
48826
49503
  labels: []
48827
49504
  };
48828
49505
  }
48829
- const make$37 = Effect.gen(function* () {
49506
+ const make$38 = Effect.gen(function* () {
48830
49507
  const cli = yield* AzureDevOpsPullRequestCli;
48831
49508
  const fail = (operation) => (error) => new PullRequestProviderError({
48832
49509
  provider: "azure-devops",
@@ -49566,7 +50243,7 @@ function mergeStrategy(method) {
49566
50243
  default: return "merge_commit";
49567
50244
  }
49568
50245
  }
49569
- const make$36 = Effect.gen(function* () {
50246
+ const make$37 = Effect.gen(function* () {
49570
50247
  const bitbucket = yield* BitbucketApi;
49571
50248
  /**
49572
50249
  * The repository's own path, and the workspace above it — which the people who may review are
@@ -49846,7 +50523,7 @@ const make$36 = Effect.gen(function* () {
49846
50523
  }).pipe(Effect.asVoid))
49847
50524
  });
49848
50525
  });
49849
- const layer$27 = Layer.effect(BitbucketPullRequestApi, make$36);
50526
+ const layer$27 = Layer.effect(BitbucketPullRequestApi, make$37);
49850
50527
  //#endregion
49851
50528
  //#region src/pullRequest/BitbucketPullRequestProvider.ts
49852
50529
  const CAPABILITIES$2 = {
@@ -49926,7 +50603,7 @@ function toChangeRequest(pullRequest) {
49926
50603
  labels: []
49927
50604
  };
49928
50605
  }
49929
- const make$35 = Effect.gen(function* () {
50606
+ const make$36 = Effect.gen(function* () {
49930
50607
  const api = yield* BitbucketPullRequestApi;
49931
50608
  const fail = (operation) => (error) => new PullRequestProviderError({
49932
50609
  provider: "bitbucket",
@@ -51813,7 +52490,7 @@ function actionArgs$1(action, mergeMethod, updateMethod) {
51813
52490
  case "reopen": return ["reopen"];
51814
52491
  }
51815
52492
  }
51816
- const make$34 = Effect.gen(function* () {
52493
+ const make$35 = Effect.gen(function* () {
51817
52494
  const github = yield* GitHubCli;
51818
52495
  /**
51819
52496
  * The pull request's own node id, which is what a mutation against the pull request itself is
@@ -52533,7 +53210,7 @@ const make$34 = Effect.gen(function* () {
52533
53210
  })))
52534
53211
  });
52535
53212
  });
52536
- const layer$26 = Layer.effect(GitHubPullRequestCli, make$34);
53213
+ const layer$26 = Layer.effect(GitHubPullRequestCli, make$35);
52537
53214
  //#endregion
52538
53215
  //#region src/pullRequest/GitHubPullRequestProvider.ts
52539
53216
  const CAPABILITIES$1 = {
@@ -52648,7 +53325,7 @@ function loginAvatarUrl(login, host) {
52648
53325
  }
52649
53326
  /** True where markdown would render nothing: whitespace, or only HTML comments. */
52650
53327
  const rendersEmpty = (body) => body.replace(/<!--[\s\S]*?-->/g, "").trim().length === 0;
52651
- const make$33 = Effect.gen(function* () {
53328
+ const make$34 = Effect.gen(function* () {
52652
53329
  const cli = yield* GitHubPullRequestCli;
52653
53330
  const fail = (operation) => (error) => new PullRequestProviderError({
52654
53331
  provider: "github",
@@ -53667,7 +54344,7 @@ function actionArgs(action, mergeMethod) {
53667
54344
  case "reopen": return ["reopen"];
53668
54345
  }
53669
54346
  }
53670
- const make$32 = Effect.gen(function* () {
54347
+ const make$33 = Effect.gen(function* () {
53671
54348
  const gitlab = yield* GitLabCli;
53672
54349
  const api = (input) => gitlab.execute({
53673
54350
  cwd: input.cwd,
@@ -54238,7 +54915,7 @@ const make$32 = Effect.gen(function* () {
54238
54915
  }).pipe(Effect.asVoid)
54239
54916
  });
54240
54917
  });
54241
- const layer$25 = Layer.effect(GitLabPullRequestCli, make$32);
54918
+ const layer$25 = Layer.effect(GitLabPullRequestCli, make$33);
54242
54919
  //#endregion
54243
54920
  //#region src/pullRequest/GitLabPullRequestProvider.ts
54244
54921
  const CAPABILITIES = {
@@ -54318,7 +54995,7 @@ function reasonFor(error) {
54318
54995
  if (error._tag === "GitLabCliAuthenticationError") return "unauthenticated";
54319
54996
  return "failed";
54320
54997
  }
54321
- const make$31 = Effect.gen(function* () {
54998
+ const make$32 = Effect.gen(function* () {
54322
54999
  const cli = yield* GitLabPullRequestCli;
54323
55000
  const fail = (operation) => (error) => new PullRequestProviderError({
54324
55001
  provider: "gitlab",
@@ -54461,13 +55138,13 @@ function fromProviders(providers) {
54461
55138
  * The hosts this build can read change requests from. A host with no entry here still shows up
54462
55139
  * in the provider list as unimplemented, so its projects are explained rather than missing.
54463
55140
  */
54464
- const make$30 = Effect.map(Effect.all([
54465
- make$33,
54466
- make$31,
54467
- make$35,
54468
- make$37
55141
+ const make$31 = Effect.map(Effect.all([
55142
+ make$34,
55143
+ make$32,
55144
+ make$36,
55145
+ make$38
54469
55146
  ]), fromProviders);
54470
- const layer$24 = Layer.effect(PullRequestProviderRegistry, make$30).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))));
55147
+ 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))));
54471
55148
  //#endregion
54472
55149
  //#region src/pullRequest/PullRequestService.ts
54473
55150
  /**
@@ -54655,7 +55332,7 @@ function repositoryIdentityOf(project) {
54655
55332
  if (identity.displayName) return identity.displayName;
54656
55333
  return identity.owner && identity.name ? `${identity.owner}/${identity.name}` : null;
54657
55334
  }
54658
- const make$29 = Effect.gen(function* () {
55335
+ const make$30 = Effect.gen(function* () {
54659
55336
  const registry = yield* PullRequestProviderRegistry;
54660
55337
  const projections = yield* ProjectionSnapshotQuery;
54661
55338
  const sourceControlProviders = yield* SourceControlProviderRegistry;
@@ -55625,7 +56302,7 @@ const make$29 = Effect.gen(function* () {
55625
56302
  invalidate
55626
56303
  });
55627
56304
  });
55628
- const layer$23 = Layer.effect(PullRequestService, make$29);
56305
+ const layer$23 = Layer.effect(PullRequestService, make$30);
55629
56306
  //#endregion
55630
56307
  //#region src/orchestration/ThreadWorkspaceLifecycle.ts
55631
56308
  var ThreadWorkspaceLifecycleError = class extends Data.TaggedError("ThreadWorkspaceLifecycleError") {};
@@ -55635,6 +56312,16 @@ function resolveWorkspaceCleanupRefusal(input) {
55635
56312
  if (!input.isMerged) return "Cleanup refused because branch is not merged into default branch.";
55636
56313
  return null;
55637
56314
  }
56315
+ /**
56316
+ * A thread on a detached HEAD has no branch to merge, so its worktree is safe
56317
+ * to drop as soon as nothing in it is unsaved and every commit it points at is
56318
+ * already on the default branch.
56319
+ */
56320
+ function resolveDetachedWorkspaceCleanupRefusal(input) {
56321
+ if (input.hasUncommittedChanges) return "Cleanup refused because worktree has uncommitted changes.";
56322
+ if (!input.headIsAncestorOfDefault) return "Cleanup refused because the detached worktree has commits that are not on the default branch.";
56323
+ return null;
56324
+ }
55638
56325
  function resolveMergedWorkspaceContext(input) {
55639
56326
  const pullRequest = input.pullRequest;
55640
56327
  if (input.branchIsAncestor && input.branchCommitSha !== null) return {
@@ -55659,7 +56346,7 @@ const mapLifecycleError = Effect.mapError((cause) => cause instanceof ThreadWork
55659
56346
  detail: "Thread workspace lifecycle operation failed.",
55660
56347
  cause
55661
56348
  }));
55662
- const make$28 = Effect.gen(function* () {
56349
+ const make$29 = Effect.gen(function* () {
55663
56350
  const snapshots = yield* ProjectionSnapshotQuery;
55664
56351
  const engine = yield* OrchestrationEngineService;
55665
56352
  const gitWorkflow = yield* GitWorkflowService;
@@ -55675,6 +56362,72 @@ const make$28 = Effect.gen(function* () {
55675
56362
  }))), { discard: true });
55676
56363
  });
55677
56364
  const record = (input) => recordRaw(input).pipe(mapLifecycleError);
56365
+ const cleanupDetachedWorktree = Effect.fn("threadWorkspaceLifecycle.cleanupDetached")(function* (input) {
56366
+ const { threadIds, project, worktreePath, refusal, now } = input;
56367
+ if ((yield* git.statusDetailsLocal(worktreePath).pipe(Effect.mapError((cause) => new ThreadWorkspaceLifecycleError({
56368
+ detail: "Could not inspect thread worktree before cleanup.",
56369
+ cause
56370
+ })))).hasWorkingTreeChanges) return yield* refusal(resolveDetachedWorkspaceCleanupRefusal({
56371
+ hasUncommittedChanges: true,
56372
+ headIsAncestorOfDefault: true
56373
+ }));
56374
+ const defaultRef = (yield* gitWorkflow.listRefs({
56375
+ cwd: project.workspaceRoot,
56376
+ includeMatchingRemoteRefs: true,
56377
+ refresh: true
56378
+ })).refs.find((ref) => ref.isRemote === true && ref.isDefault);
56379
+ if (defaultRef === void 0 || defaultRef.remoteName === void 0) return yield* refusal("Cleanup refused because the default remote branch could not be resolved.");
56380
+ yield* gitWorkflow.fetchRemote({
56381
+ cwd: project.workspaceRoot,
56382
+ remoteName: defaultRef.remoteName
56383
+ });
56384
+ const ancestorResult = yield* git.execute({
56385
+ operation: "ThreadWorkspaceLifecycle.detachedMergeBase",
56386
+ cwd: worktreePath,
56387
+ args: [
56388
+ "merge-base",
56389
+ "--is-ancestor",
56390
+ "HEAD",
56391
+ `refs/remotes/${defaultRef.name}`
56392
+ ],
56393
+ allowNonZeroExit: true
56394
+ });
56395
+ if (ancestorResult.exitCode !== 0 && ancestorResult.exitCode !== 1) return yield* refusal("Cleanup refused because worktree ancestry could not be verified.");
56396
+ const ancestryRefusal = resolveDetachedWorkspaceCleanupRefusal({
56397
+ hasUncommittedChanges: false,
56398
+ headIsAncestorOfDefault: ancestorResult.exitCode === 0
56399
+ });
56400
+ if (ancestryRefusal !== null) return yield* refusal(ancestryRefusal);
56401
+ yield* record({
56402
+ threadIds,
56403
+ lifecycle: {
56404
+ status: "cleanup-pending",
56405
+ detail: "Detached worktree verified; workspace cleanup is pending.",
56406
+ pullRequestNumber: null,
56407
+ mergeCommitSha: null,
56408
+ updatedAt: now
56409
+ }
56410
+ });
56411
+ yield* gitWorkflow.removeWorktree({
56412
+ cwd: project.workspaceRoot,
56413
+ path: worktreePath
56414
+ });
56415
+ const detail = "Detached worktree removed; thread context preserved.";
56416
+ yield* record({
56417
+ threadIds,
56418
+ lifecycle: {
56419
+ status: "cleaned",
56420
+ detail,
56421
+ pullRequestNumber: null,
56422
+ mergeCommitSha: null,
56423
+ updatedAt: DateTime.formatIso(yield* DateTime.now)
56424
+ }
56425
+ });
56426
+ return {
56427
+ outcome: "cleaned",
56428
+ detail
56429
+ };
56430
+ });
55678
56431
  const cleanupRaw = Effect.fn("threadWorkspaceLifecycle.cleanup")(function* (threadId) {
55679
56432
  const snapshot = yield* snapshots.getCommandReadModel();
55680
56433
  const threadIds = activePairThreadIds(threadId, snapshot.threadPairs ?? []);
@@ -55710,8 +56463,15 @@ const make$28 = Effect.gen(function* () {
55710
56463
  if (threads.some((thread) => thread.archivedAt === null)) return yield* refusal("Cleanup refused because thread is not archived.");
55711
56464
  const branch = requestedThread.branch;
55712
56465
  const worktreePath = requestedThread.worktreePath;
55713
- if (branch === null || worktreePath === null) return yield* refusal("Cleanup refused because thread has no worktree and branch binding.");
56466
+ if (worktreePath === null) return yield* refusal("Cleanup refused because thread has no worktree.");
55714
56467
  if (threads.some((thread) => thread.branch !== branch || thread.worktreePath !== worktreePath)) return yield* refusal("Cleanup refused because Fusion threads do not share one workspace.");
56468
+ if (branch === null) return yield* cleanupDetachedWorktree({
56469
+ threadIds,
56470
+ project,
56471
+ worktreePath,
56472
+ refusal,
56473
+ now
56474
+ });
55715
56475
  const dirtyRefusal = resolveWorkspaceCleanupRefusal({
55716
56476
  hasLiveSession: false,
55717
56477
  hasUncommittedChanges: (yield* git.statusDetailsLocal(worktreePath).pipe(Effect.mapError((cause) => new ThreadWorkspaceLifecycleError({
@@ -55840,7 +56600,7 @@ const make$28 = Effect.gen(function* () {
55840
56600
  record
55841
56601
  };
55842
56602
  });
55843
- const layer$22 = Layer.effect(ThreadWorkspaceLifecycleService, make$28);
56603
+ const layer$22 = Layer.effect(ThreadWorkspaceLifecycleService, make$29);
55844
56604
  //#endregion
55845
56605
  //#region src/textGeneration/BtwRequestCoordinator.ts
55846
56606
  const MAX_PENDING_BTW_CANCELLATIONS = 256;
@@ -57994,6 +58754,9 @@ const observeRpcStreamEffect = (method, effect, traceAttributes) => {
57994
58754
  return withRpcStreamTracing(method, instrumented, traceAttributes);
57995
58755
  };
57996
58756
  //#endregion
58757
+ //#region src/provider/Services/ProviderSessionDirectory.ts
58758
+ var ProviderSessionDirectory = class extends Context.Service()("@p4code/cli/provider/Services/ProviderSessionDirectory") {};
58759
+ //#endregion
57997
58760
  //#region src/provider/providerMaintenanceCommandCoordinator.ts
57998
58761
  const makeProviderMaintenanceCommandCoordinator = Effect.fn("makeProviderMaintenanceCommandCoordinator")(function* (input) {
57999
58762
  const runningTargetsRef = yield* Ref.make(/* @__PURE__ */ new Set());
@@ -58381,7 +59144,7 @@ function makeUpdateState(input) {
58381
59144
  output: input.output ?? null
58382
59145
  };
58383
59146
  }
58384
- const make$27 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
59147
+ const make$28 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
58385
59148
  const providerRegistry = yield* ProviderRegistry;
58386
59149
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
58387
59150
  const httpClient = yield* HttpClient.HttpClient;
@@ -58496,7 +59259,7 @@ const make$27 = Effect.fn("ProviderMaintenanceRunner.make")(function* () {
58496
59259
  });
58497
59260
  return ProviderMaintenanceRunner.of({ updateProvider });
58498
59261
  });
58499
- const layer$21 = Layer.effect(ProviderMaintenanceRunner, make$27());
59262
+ const layer$21 = Layer.effect(ProviderMaintenanceRunner, make$28());
58500
59263
  //#endregion
58501
59264
  //#region src/provider/Drivers/ClaudeHome.ts
58502
59265
  const resolveClaudeHomePath = Effect.fn("resolveClaudeHomePath")(function* (config) {
@@ -59512,7 +60275,7 @@ Layer.succeed(UsageService, UsageService.of({ readSummary: (input) => Effect.suc
59512
60275
  },
59513
60276
  scanDurationMs: 0
59514
60277
  }) }));
59515
- const make$26 = Effect.gen(function* () {
60278
+ const make$27 = Effect.gen(function* () {
59516
60279
  const fileSystem = yield* FileSystem.FileSystem;
59517
60280
  const path = yield* Path.Path;
59518
60281
  const config = yield* ServerConfig$1;
@@ -59744,7 +60507,7 @@ const make$26 = Effect.gen(function* () {
59744
60507
  };
59745
60508
  }) };
59746
60509
  });
59747
- const layer$20 = Layer.effect(UsageService, make$26);
60510
+ const layer$20 = Layer.effect(UsageService, make$27);
59748
60511
  //#endregion
59749
60512
  //#region src/feed/FeedStore.ts
59750
60513
  const storageFailure = (message) => new FeedError({
@@ -60108,7 +60871,7 @@ const jsonRequest = Effect.fn("FeedService.jsonRequest")(function* (url, token,
60108
60871
  catch: () => fail("hub_unavailable", "Hub returned invalid JSON.")
60109
60872
  });
60110
60873
  });
60111
- const make$25 = Effect.gen(function* () {
60874
+ const make$26 = Effect.gen(function* () {
60112
60875
  const hubLink = yield* HubLink;
60113
60876
  const providers = yield* ProviderInstanceRegistry;
60114
60877
  const config = yield* ServerConfig$1;
@@ -60346,7 +61109,7 @@ var FeedService = class extends Context.Reference("@p4code/cli/feed/FeedService"
60346
61109
  markRead: unavailable,
60347
61110
  cleanup: unavailable
60348
61111
  }) }) {};
60349
- const layer$19 = Layer.effect(FeedService, make$25);
61112
+ const layer$19 = Layer.effect(FeedService, make$26);
60350
61113
  const SKILL_MANIFEST_FILENAME = "SKILL.md";
60351
61114
  /**
60352
61115
  * Split a catalogue id (`owner/repo/skill-name`) into its parts.
@@ -60492,7 +61255,7 @@ const emptyFetch = (id, unavailable) => ({
60492
61255
  skipped: [],
60493
61256
  unavailable
60494
61257
  });
60495
- const make$24 = Effect.gen(function* () {
61258
+ const make$25 = Effect.gen(function* () {
60496
61259
  const http = yield* HttpClient.HttpClient;
60497
61260
  const request = Effect.fn("SkillRegistry.request")(function* (url) {
60498
61261
  return yield* http.execute(HttpClientRequest.get(url).pipe(HttpClientRequest.setHeader("accept", "application/json"), HttpClientRequest.setHeader("user-agent", "p4code"))).pipe(Effect.timeout(REQUEST_TIMEOUT_MS));
@@ -60563,7 +61326,7 @@ const make$24 = Effect.gen(function* () {
60563
61326
  fetch
60564
61327
  };
60565
61328
  });
60566
- const layer$18 = Layer.effect(SkillRegistry, make$24);
61329
+ const layer$18 = Layer.effect(SkillRegistry, make$25);
60567
61330
  //#endregion
60568
61331
  //#region src/mcp/McpInvocationContext.ts
60569
61332
  var McpInvocationContext = class extends Context.Service()("@p4code/cli/mcp/McpInvocationContext") {};
@@ -60791,7 +61554,7 @@ const classifyResponseError = (context, error) => {
60791
61554
  });
60792
61555
  }
60793
61556
  };
60794
- const make$23 = Effect.gen(function* PreviewAutomationBrokerMake() {
61557
+ const make$24 = Effect.gen(function* PreviewAutomationBrokerMake() {
60795
61558
  const crypto = yield* Crypto.Crypto;
60796
61559
  const state = yield* SynchronizedRef.make({
60797
61560
  clients: /* @__PURE__ */ new Map(),
@@ -61025,7 +61788,7 @@ const make$23 = Effect.gen(function* PreviewAutomationBrokerMake() {
61025
61788
  invoke
61026
61789
  });
61027
61790
  }).pipe(Effect.withSpan("PreviewAutomationBroker.make"));
61028
- const layer$17 = Layer.effect(PreviewAutomationBroker, make$23);
61791
+ const layer$17 = Layer.effect(PreviewAutomationBroker, make$24);
61029
61792
  //#endregion
61030
61793
  //#region src/preview/Manager.ts
61031
61794
  /**
@@ -61089,7 +61852,7 @@ const buildIdleSnapshot = (input) => ({
61089
61852
  viewport: FILL_PREVIEW_VIEWPORT,
61090
61853
  updatedAt: input.updatedAt
61091
61854
  });
61092
- const make$22 = Effect.gen(function* PreviewManagerMake() {
61855
+ const make$23 = Effect.gen(function* PreviewManagerMake() {
61093
61856
  const serverEpoch = NodeCrypto.randomUUID();
61094
61857
  const stateRef = yield* SynchronizedRef.make(initialState);
61095
61858
  const eventsPubSub = yield* PubSub.unbounded();
@@ -61320,7 +62083,7 @@ const make$22 = Effect.gen(function* PreviewManagerMake() {
61320
62083
  subscribeEvents: PubSub.subscribe(eventsPubSub)
61321
62084
  });
61322
62085
  }).pipe(Effect.withSpan("PreviewManager.make"));
61323
- const layer$16 = Layer.effect(PreviewManager, make$22);
62086
+ const layer$16 = Layer.effect(PreviewManager, make$23);
61324
62087
  //#endregion
61325
62088
  //#region src/workspace/WorkspaceSearchIndex.ts
61326
62089
  const WORKSPACE_INDEX_MAX_ENTRIES = 25e3;
@@ -61454,7 +62217,7 @@ const waitForScan = (cwd, finder, onFailure) => Effect.try({
61454
62217
  timeout: WORKSPACE_INDEX_SCAN_TIMEOUT
61455
62218
  })
61456
62219
  }), Effect.withSpan("WorkspaceSearchIndex.waitForScan"));
61457
- const make$21 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
62220
+ const make$22 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
61458
62221
  const finder = yield* Effect.acquireRelease(createFinder(cwd), (finder) => Effect.try({
61459
62222
  try: () => finder.destroy(),
61460
62223
  catch: (cause) => new WorkspaceSearchIndexDestroyFailed({
@@ -61528,7 +62291,7 @@ const make$21 = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd) {
61528
62291
  * workspace root. WorkspaceSearchIndexMap owns memoization and idle cleanup;
61529
62292
  * using a default cwd here would mix resources from different workspaces.
61530
62293
  */
61531
- const layer$15 = (cwd) => Layer.effect(WorkspaceSearchIndex, make$21(cwd));
62294
+ const layer$15 = (cwd) => Layer.effect(WorkspaceSearchIndex, make$22(cwd));
61532
62295
  var WorkspaceSearchIndexMap = class extends LayerMap.Service()("@p4code/cli/workspace/WorkspaceSearchIndexMap", {
61533
62296
  lookup: layer$15,
61534
62297
  idleTimeToLive: WORKSPACE_INDEX_IDLE_TTL
@@ -61592,7 +62355,7 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu
61592
62355
  if (!input.cwd) return yield* new WorkspaceEntriesCurrentProjectRequiredError({ partialPath: input.partialPath });
61593
62356
  return path.resolve(expandHomePath$1(input.cwd, path), input.partialPath);
61594
62357
  });
61595
- const make$20 = Effect.gen(function* () {
62358
+ const make$21 = Effect.gen(function* () {
61596
62359
  const path = yield* Path.Path;
61597
62360
  const workspacePaths = yield* WorkspacePaths;
61598
62361
  const workspaceSearchIndexes = yield* WorkspaceSearchIndexMap;
@@ -61666,7 +62429,7 @@ const make$20 = Effect.gen(function* () {
61666
62429
  search
61667
62430
  });
61668
62431
  });
61669
- const layer$14 = Layer.effect(WorkspaceEntries, make$20).pipe(Layer.provide(WorkspaceSearchIndexMap.layer));
62432
+ const layer$14 = Layer.effect(WorkspaceEntries, make$21).pipe(Layer.provide(WorkspaceSearchIndexMap.layer));
61670
62433
  //#endregion
61671
62434
  //#region src/workspace/WorkspaceFileSystem.ts
61672
62435
  /**
@@ -61725,7 +62488,7 @@ Schema$1.Union([
61725
62488
  ]);
61726
62489
  /** Service tag for workspace file operations. */
61727
62490
  var WorkspaceFileSystem = class extends Context.Service()("@p4code/cli/workspace/WorkspaceFileSystem") {};
61728
- const make$19 = Effect.gen(function* () {
62491
+ const make$20 = Effect.gen(function* () {
61729
62492
  const fileSystem = yield* FileSystem.FileSystem;
61730
62493
  const path = yield* Path.Path;
61731
62494
  const workspacePaths = yield* WorkspacePaths;
@@ -61869,7 +62632,7 @@ const make$19 = Effect.gen(function* () {
61869
62632
  writeFile
61870
62633
  });
61871
62634
  });
61872
- const layer$13 = Layer.effect(WorkspaceFileSystem, make$19);
62635
+ const layer$13 = Layer.effect(WorkspaceFileSystem, make$20);
61873
62636
  //#endregion
61874
62637
  //#region src/vcs/VcsStatusBroadcaster.ts
61875
62638
  const DEFAULT_VCS_STATUS_REFRESH_INTERVAL = Duration.seconds(30);
@@ -61941,7 +62704,7 @@ function fingerprintStatusPart(status) {
61941
62704
  return JSON.stringify(status);
61942
62705
  }
61943
62706
  const normalizeCwd = (cwd) => Effect.service(FileSystem.FileSystem).pipe(Effect.flatMap((fs) => fs.realPath(cwd)), Effect.orElseSucceed(() => cwd));
61944
- const make$18 = Effect.gen(function* () {
62707
+ const make$19 = Effect.gen(function* () {
61945
62708
  const workflow = yield* GitWorkflowService;
61946
62709
  const fs = yield* FileSystem.FileSystem;
61947
62710
  const changesPubSub = yield* Effect.acquireRelease(PubSub.unbounded(), (pubsub) => PubSub.shutdown(pubsub));
@@ -62165,7 +62928,7 @@ const make$18 = Effect.gen(function* () {
62165
62928
  streamStatus
62166
62929
  });
62167
62930
  });
62168
- const layer$12 = Layer.effect(VcsStatusBroadcaster, make$18);
62931
+ const layer$12 = Layer.effect(VcsStatusBroadcaster, make$19);
62169
62932
  //#endregion
62170
62933
  //#region src/vcs/VcsProvisioningService.ts
62171
62934
  var VcsProvisioningService = class extends Context.Service()("@p4code/cli/vcs/VcsProvisioningService") {};
@@ -62178,7 +62941,7 @@ function resolveRequestedKind(kind) {
62178
62941
  }));
62179
62942
  return Effect.succeed(kind);
62180
62943
  }
62181
- const make$17 = Effect.gen(function* () {
62944
+ const make$18 = Effect.gen(function* () {
62182
62945
  const registry = yield* VcsDriverRegistry;
62183
62946
  const initRepository = Effect.fn("VcsProvisioningService.initRepository")(function* (input) {
62184
62947
  const kind = yield* resolveRequestedKind(input.kind);
@@ -62186,11 +62949,11 @@ const make$17 = Effect.gen(function* () {
62186
62949
  });
62187
62950
  return VcsProvisioningService.of({ initRepository });
62188
62951
  });
62189
- const layer$11 = Layer.effect(VcsProvisioningService, make$17);
62952
+ const layer$11 = Layer.effect(VcsProvisioningService, make$18);
62190
62953
  //#endregion
62191
62954
  //#region src/review/ReviewService.ts
62192
62955
  var ReviewService = class extends Context.Service()("@p4code/cli/review/ReviewService") {};
62193
- const make$16 = Effect.gen(function* () {
62956
+ const make$17 = Effect.gen(function* () {
62194
62957
  const config = yield* ServerConfig$1;
62195
62958
  const fileSystem = yield* FileSystem.FileSystem;
62196
62959
  const path = yield* Path.Path;
@@ -62246,7 +63009,7 @@ const make$16 = Effect.gen(function* () {
62246
63009
  });
62247
63010
  return ReviewService.of({ getDiffPreview });
62248
63011
  });
62249
- const layer$10 = Layer.effect(ReviewService, make$16);
63012
+ const layer$10 = Layer.effect(ReviewService, make$17);
62250
63013
  //#endregion
62251
63014
  //#region src/diagnostics/ProcessDiagnostics.ts
62252
63015
  const PROCESS_QUERY_TIMEOUT_MS = 1e3;
@@ -62541,7 +63304,7 @@ function assertDescendantPid(pid) {
62541
63304
  }));
62542
63305
  }));
62543
63306
  }
62544
- const make$15 = Effect.gen(function* () {
63307
+ const make$16 = Effect.gen(function* () {
62545
63308
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
62546
63309
  const read = Effect.gen(function* () {
62547
63310
  const readAt = yield* DateTime.now;
@@ -62585,7 +63348,7 @@ const make$15 = Effect.gen(function* () {
62585
63348
  signal
62586
63349
  });
62587
63350
  });
62588
- const layer$9 = Layer.effect(ProcessDiagnostics, make$15);
63351
+ const layer$9 = Layer.effect(ProcessDiagnostics, make$16);
62589
63352
  //#endregion
62590
63353
  //#region src/diagnostics/ProcessResourceMonitor.ts
62591
63354
  const SAMPLE_INTERVAL_MS = 5e3;
@@ -62736,7 +63499,7 @@ function aggregateProcessResourceHistory(input) {
62736
63499
  }) : Option.none()
62737
63500
  };
62738
63501
  }
62739
- const make$14 = Effect.gen(function* () {
63502
+ const make$15 = Effect.gen(function* () {
62740
63503
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
62741
63504
  const state = yield* Ref.make({
62742
63505
  samples: [],
@@ -62785,7 +63548,7 @@ const make$14 = Effect.gen(function* () {
62785
63548
  });
62786
63549
  return ProcessResourceMonitor.of({ readHistory });
62787
63550
  });
62788
- const layer$8 = Layer.effect(ProcessResourceMonitor, make$14);
63551
+ const layer$8 = Layer.effect(ProcessResourceMonitor, make$15);
62789
63552
  //#endregion
62790
63553
  //#region src/diagnostics/TraceDiagnostics.ts
62791
63554
  var TraceFileReadError = class extends Schema$1.TaggedErrorClass()("TraceFileReadError", {
@@ -63033,7 +63796,7 @@ function readTraceFile(fileSystem, path) {
63033
63796
  cause
63034
63797
  })) }));
63035
63798
  }
63036
- const make$13 = Effect.gen(function* () {
63799
+ const make$14 = Effect.gen(function* () {
63037
63800
  const fileSystem = yield* FileSystem.FileSystem;
63038
63801
  const read = Effect.fn("TraceDiagnostics.read")(function* (options) {
63039
63802
  const readAt = options.readAt ?? (yield* DateTime.now);
@@ -63077,7 +63840,7 @@ const make$13 = Effect.gen(function* () {
63077
63840
  });
63078
63841
  return TraceDiagnostics.of({ read });
63079
63842
  });
63080
- const layer$7 = Layer.effect(TraceDiagnostics, make$13);
63843
+ const layer$7 = Layer.effect(TraceDiagnostics, make$14);
63081
63844
  function readTraceDiagnostics(options) {
63082
63845
  return Effect.gen(function* () {
63083
63846
  return yield* (yield* TraceDiagnostics).read(options);
@@ -63101,7 +63864,7 @@ const VCS_PROBES = [{
63101
63864
  installHint: "Install Jujutsu with `brew install jj` or from https://github.com/jj-vcs/jj."
63102
63865
  }];
63103
63866
  var SourceControlDiscovery = class extends Context.Service()("@p4code/cli/sourceControl/SourceControlDiscovery") {};
63104
- const make$12 = Effect.gen(function* () {
63867
+ const make$13 = Effect.gen(function* () {
63105
63868
  const config = yield* ServerConfig$1;
63106
63869
  const process = yield* VcsProcess;
63107
63870
  const sourceControlProviders = yield* SourceControlProviderRegistry;
@@ -63150,7 +63913,7 @@ const make$12 = Effect.gen(function* () {
63150
63913
  sourceControlProviders: sourceControlProviders.discover
63151
63914
  }) });
63152
63915
  });
63153
- const layer$6 = Layer.effect(SourceControlDiscovery, make$12);
63916
+ const layer$6 = Layer.effect(SourceControlDiscovery, make$13);
63154
63917
  //#endregion
63155
63918
  //#region src/sourceControl/SourceControlRepositoryService.ts
63156
63919
  const isSourceControlRepositoryError = Schema$1.is(SourceControlRepositoryError);
@@ -63183,7 +63946,7 @@ function expandHomePath(input, path) {
63183
63946
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(NodeOS.homedir(), input.slice(2));
63184
63947
  return input;
63185
63948
  }
63186
- const make$11 = Effect.gen(function* () {
63949
+ const make$12 = Effect.gen(function* () {
63187
63950
  const config = yield* ServerConfig$1;
63188
63951
  const fileSystem = yield* FileSystem.FileSystem;
63189
63952
  const git = yield* GitVcsDriver;
@@ -63322,7 +64085,7 @@ const make$11 = Effect.gen(function* () {
63322
64085
  publishRepository: (input) => publishRepository(input).pipe(mapRepositoryError("publishRepository", input.provider))
63323
64086
  });
63324
64087
  });
63325
- const layer$5 = Layer.effect(SourceControlRepositoryService, make$11);
64088
+ const layer$5 = Layer.effect(SourceControlRepositoryService, make$12);
63326
64089
  //#endregion
63327
64090
  //#region src/ws.ts
63328
64091
  /** Matches `p4c hub token add`, so a token minted here and one minted there are the same thing. */
@@ -63424,7 +64187,7 @@ function projectSetupScriptCompatibilityDetail(error) {
63424
64187
  }
63425
64188
  }
63426
64189
  function isThreadDetailEvent(event) {
63427
- return event.type === "thread.message-sent" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set";
64190
+ return event.type === "thread.message-sent" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.scheduled-task.created" || event.type === "thread.scheduled-task.cancelled" || event.type === "thread.scheduled-task.fired" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set";
63428
64191
  }
63429
64192
  const PROVIDER_STATUS_DEBOUNCE_MS = 200;
63430
64193
  const SHELL_RESUME_MAX_GAP = 1e3;
@@ -63622,6 +64385,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
63622
64385
  const previewManager = yield* PreviewManager;
63623
64386
  const portDiscovery = yield* PortDiscovery;
63624
64387
  const providerRegistry = yield* ProviderRegistry;
64388
+ const providerSessionDirectory = yield* ProviderSessionDirectory;
63625
64389
  const providerMaintenanceRunner = yield* ProviderMaintenanceRunner;
63626
64390
  const serverSelfUpdate = yield* ServerSelfUpdate;
63627
64391
  const textGeneration = yield* TextGeneration;
@@ -64018,7 +64782,22 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
64018
64782
  onNone: () => false,
64019
64783
  onSome: (thread) => thread.session !== null && thread.session.status !== "stopped"
64020
64784
  })), Effect.orElseSucceed(() => false)) : false;
64021
- const result = yield* dispatchNormalizedCommand(normalizedCommand);
64785
+ const result = normalizedCommand.type === "thread.fork" ? yield* Effect.uninterruptible(Effect.gen(function* () {
64786
+ if (yield* projectionSnapshotQuery.getThreadShellById(normalizedCommand.threadId).pipe(Effect.map(Option.isSome), Effect.orElseSucceed(() => false))) return yield* dispatchNormalizedCommand(normalizedCommand);
64787
+ const forkBinding = yield* providerSessionDirectory.getBinding(normalizedCommand.sourceThreadId).pipe(Effect.map((source) => buildForkedProviderBinding(normalizedCommand.threadId, Option.getOrUndefined(source))), Effect.mapError((error) => new OrchestrationDispatchCommandError({
64788
+ message: `Could not read the source thread's provider session: ${error.message}`,
64789
+ cause: error
64790
+ })));
64791
+ if (forkBinding.rejection !== void 0) return yield* new OrchestrationDispatchCommandError({ message: describeThreadForkRejection(forkBinding.rejection) });
64792
+ yield* providerSessionDirectory.upsert(forkBinding.binding).pipe(Effect.mapError((error) => new OrchestrationDispatchCommandError({
64793
+ message: `Could not save the fork's provider session: ${error.message}`,
64794
+ cause: error
64795
+ })));
64796
+ return yield* dispatchNormalizedCommand(normalizedCommand).pipe(Effect.tapError(() => providerSessionDirectory.remove(normalizedCommand.threadId).pipe(Effect.catchCause((cause) => Effect.logWarning("failed to remove fork binding after dispatch failure", {
64797
+ threadId: normalizedCommand.threadId,
64798
+ cause
64799
+ })))));
64800
+ })) : yield* dispatchNormalizedCommand(normalizedCommand);
64022
64801
  if (normalizedCommand.type === "thread.archive") {
64023
64802
  const archivedThreadIds = result.events?.filter((event) => event.type === "thread.archived").map((event) => event.payload.threadId);
64024
64803
  if (archivedThreadIds === void 0 || archivedThreadIds.length === 0) return yield* new OrchestrationDispatchCommandError({ message: "Archive command completed without authoritative archive events." });
@@ -64609,7 +65388,7 @@ function toPersistenceSqlOrDecodeError(sqlOperation, decodeOperation, correlatio
64609
65388
  cause
64610
65389
  });
64611
65390
  }
64612
- const make$10 = Effect.gen(function* () {
65391
+ const make$11 = Effect.gen(function* () {
64613
65392
  const sql = yield* SqlClient.SqlClient;
64614
65393
  const upsertRuntimeRow = SqlSchema.void({
64615
65394
  Request: ProviderSessionRuntimeDbRowSchema,
@@ -64712,7 +65491,7 @@ const make$10 = Effect.gen(function* () {
64712
65491
  deleteByThreadId
64713
65492
  };
64714
65493
  });
64715
- const layer$4 = Layer.effect(ProviderSessionRuntimeRepository, make$10);
65494
+ const layer$4 = Layer.effect(ProviderSessionRuntimeRepository, make$11);
64716
65495
  //#endregion
64717
65496
  //#region src/provider/Errors.ts
64718
65497
  /**
@@ -64837,9 +65616,6 @@ var ProviderSessionDirectoryPersistenceError = class extends Schema$1.TaggedErro
64837
65616
  }
64838
65617
  };
64839
65618
  //#endregion
64840
- //#region src/provider/Services/ProviderSessionDirectory.ts
64841
- var ProviderSessionDirectory = class extends Context.Service()("@p4code/cli/provider/Services/ProviderSessionDirectory") {};
64842
- //#endregion
64843
65619
  //#region src/provider/Layers/ProviderSessionDirectory.ts
64844
65620
  const decodeProviderDriverKindValue = Schema$1.decodeUnknownEffect(ProviderDriverKind);
64845
65621
  function toPersistenceError(operation) {
@@ -64920,12 +65696,14 @@ const makeProviderSessionDirectory = Effect.gen(function* () {
64920
65696
  detail: `No persisted provider binding found for thread '${threadId}'.`
64921
65697
  }))
64922
65698
  })));
65699
+ const remove = (threadId) => repository.deleteByThreadId({ threadId }).pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.remove:deleteByThreadId")));
64923
65700
  const listThreadIds = () => repository.list().pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.listThreadIds:list")), Effect.map((rows) => rows.map((row) => row.threadId)));
64924
65701
  const listBindings = () => repository.list().pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.listBindings:list")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => toRuntimeBinding(row, "ProviderSessionDirectory.listBindings"), { concurrency: "unbounded" })));
64925
65702
  return {
64926
65703
  upsert,
64927
65704
  getProvider,
64928
65705
  getBinding,
65706
+ remove,
64929
65707
  listThreadIds,
64930
65708
  listBindings
64931
65709
  };
@@ -65600,12 +66378,12 @@ const makeWithOptions = Effect.fn("McpSessionRegistry.make")(function* (options
65600
66378
  });
65601
66379
  });
65602
66380
  let activeMcpSessionRegistry;
65603
- const make$9 = Effect.acquireRelease(makeWithOptions().pipe(Effect.tap((registry) => Effect.sync(() => {
66381
+ const make$10 = Effect.acquireRelease(makeWithOptions().pipe(Effect.tap((registry) => Effect.sync(() => {
65604
66382
  activeMcpSessionRegistry = registry;
65605
66383
  }))), (registry) => Effect.sync(() => {
65606
66384
  if (activeMcpSessionRegistry === registry) activeMcpSessionRegistry = void 0;
65607
66385
  }));
65608
- const layer$3 = Layer.effect(McpSessionRegistry, make$9);
66386
+ const layer$3 = Layer.effect(McpSessionRegistry, make$10);
65609
66387
  const issueActiveMcpCredential = (request) => activeMcpSessionRegistry ? activeMcpSessionRegistry.revokeThread(request.threadId).pipe(Effect.andThen(activeMcpSessionRegistry.issue(request))) : Effect.sync(() => void 0);
65610
66388
  /**
65611
66389
  * Refreshes the liveness of a thread's MCP credential. Called on every provider
@@ -67229,53 +68007,84 @@ const MINIMUM_CLAUDE_OPUS_5_VERSION = "2.1.219";
67229
68007
  const MINIMUM_CLAUDE_FABLE_5_VERSION = "2.1.169";
67230
68008
  const MINIMUM_CLAUDE_OPUS_4_8_VERSION = "2.1.154";
67231
68009
  const MINIMUM_CLAUDE_OPUS_4_7_VERSION = "2.1.111";
67232
- const CLAUDE_FABLE_CAPABILITIES = createModelCapabilities({ optionDescriptors: [buildSelectOptionDescriptor({
67233
- id: "effort",
67234
- label: "Reasoning",
67235
- options: [
67236
- {
67237
- value: "low",
67238
- label: "Low"
67239
- },
67240
- {
67241
- value: "medium",
67242
- label: "Medium"
67243
- },
67244
- {
67245
- value: "high",
67246
- label: "High",
68010
+ const AUTO_COMPACT_WINDOW_OPTION_ID = "autoCompactWindow";
68011
+ const AUTO_COMPACT_WINDOW_AUTO = "auto";
68012
+ /** Token counts behind each explicit auto-compact choice. */
68013
+ const AUTO_COMPACT_WINDOW_TOKENS = {
68014
+ "200k": 2e5,
68015
+ "400k": 4e5,
68016
+ "600k": 6e5
68017
+ };
68018
+ /**
68019
+ * Where Claude Code starts summarising the conversation. "Auto" keeps Claude
68020
+ * Code's own per-model threshold, which on 1M-context models lets a session
68021
+ * grow close to 1M tokens and re-read all of it on every call.
68022
+ */
68023
+ function buildAutoCompactWindowDescriptor() {
68024
+ return buildSelectOptionDescriptor({
68025
+ id: AUTO_COMPACT_WINDOW_OPTION_ID,
68026
+ label: "Auto-compact At",
68027
+ options: [{
68028
+ value: AUTO_COMPACT_WINDOW_AUTO,
68029
+ label: "Auto",
67247
68030
  isDefault: true
67248
- },
67249
- {
67250
- value: "xhigh",
67251
- label: "Extra High"
67252
- },
67253
- {
67254
- value: "max",
67255
- label: "Max"
67256
- },
67257
- {
67258
- value: "ultracode",
67259
- label: "Ultracode"
67260
- },
67261
- {
67262
- value: "ultrathink",
67263
- label: "Ultrathink"
67264
- }
67265
- ],
67266
- promptInjectedValues: ["ultrathink"]
67267
- }), buildSelectOptionDescriptor({
67268
- id: "contextWindow",
67269
- label: "Context Window",
67270
- options: [{
67271
- value: "200k",
67272
- label: "200k"
67273
- }, {
67274
- value: "1m",
67275
- label: "1M",
67276
- isDefault: true
67277
- }]
67278
- })] });
68031
+ }, ...Object.keys(AUTO_COMPACT_WINDOW_TOKENS).map((value) => ({
68032
+ value,
68033
+ label: value
68034
+ }))]
68035
+ });
68036
+ }
68037
+ const CLAUDE_FABLE_CAPABILITIES = createModelCapabilities({ optionDescriptors: [
68038
+ buildSelectOptionDescriptor({
68039
+ id: "effort",
68040
+ label: "Reasoning",
68041
+ options: [
68042
+ {
68043
+ value: "low",
68044
+ label: "Low"
68045
+ },
68046
+ {
68047
+ value: "medium",
68048
+ label: "Medium"
68049
+ },
68050
+ {
68051
+ value: "high",
68052
+ label: "High",
68053
+ isDefault: true
68054
+ },
68055
+ {
68056
+ value: "xhigh",
68057
+ label: "Extra High"
68058
+ },
68059
+ {
68060
+ value: "max",
68061
+ label: "Max"
68062
+ },
68063
+ {
68064
+ value: "ultracode",
68065
+ label: "Ultracode"
68066
+ },
68067
+ {
68068
+ value: "ultrathink",
68069
+ label: "Ultrathink"
68070
+ }
68071
+ ],
68072
+ promptInjectedValues: ["ultrathink"]
68073
+ }),
68074
+ buildSelectOptionDescriptor({
68075
+ id: "contextWindow",
68076
+ label: "Context Window",
68077
+ options: [{
68078
+ value: "200k",
68079
+ label: "200k"
68080
+ }, {
68081
+ value: "1m",
68082
+ label: "1M",
68083
+ isDefault: true
68084
+ }]
68085
+ }),
68086
+ buildAutoCompactWindowDescriptor()
68087
+ ] });
67279
68088
  const BUILT_IN_MODELS = [
67280
68089
  {
67281
68090
  slug: "claude-fable-5-1",
@@ -67345,12 +68154,157 @@ const BUILT_IN_MODELS = [
67345
68154
  label: "1M",
67346
68155
  isDefault: true
67347
68156
  }]
67348
- })
68157
+ }),
68158
+ buildAutoCompactWindowDescriptor()
68159
+ ] })
68160
+ },
68161
+ {
68162
+ slug: "claude-opus-4-8",
68163
+ name: "Claude Opus 4.8",
68164
+ isCustom: false,
68165
+ capabilities: createModelCapabilities({ optionDescriptors: [
68166
+ buildSelectOptionDescriptor({
68167
+ id: "effort",
68168
+ label: "Reasoning",
68169
+ options: [
68170
+ {
68171
+ value: "low",
68172
+ label: "Low"
68173
+ },
68174
+ {
68175
+ value: "medium",
68176
+ label: "Medium"
68177
+ },
68178
+ {
68179
+ value: "high",
68180
+ label: "High",
68181
+ isDefault: true
68182
+ },
68183
+ {
68184
+ value: "xhigh",
68185
+ label: "Extra High"
68186
+ },
68187
+ {
68188
+ value: "max",
68189
+ label: "Max"
68190
+ },
68191
+ {
68192
+ value: "ultracode",
68193
+ label: "Ultracode"
68194
+ },
68195
+ {
68196
+ value: "ultrathink",
68197
+ label: "Ultrathink"
68198
+ }
68199
+ ],
68200
+ promptInjectedValues: ["ultrathink"]
68201
+ }),
68202
+ buildBooleanOptionDescriptor({
68203
+ id: "fastMode",
68204
+ label: "Fast Mode"
68205
+ }),
68206
+ buildAutoCompactWindowDescriptor()
68207
+ ] })
68208
+ },
68209
+ {
68210
+ slug: "claude-opus-4-7",
68211
+ name: "Claude Opus 4.7",
68212
+ isCustom: false,
68213
+ capabilities: createModelCapabilities({ optionDescriptors: [
68214
+ buildSelectOptionDescriptor({
68215
+ id: "effort",
68216
+ label: "Reasoning",
68217
+ options: [
68218
+ {
68219
+ value: "low",
68220
+ label: "Low"
68221
+ },
68222
+ {
68223
+ value: "medium",
68224
+ label: "Medium"
68225
+ },
68226
+ {
68227
+ value: "high",
68228
+ label: "High"
68229
+ },
68230
+ {
68231
+ value: "xhigh",
68232
+ label: "Extra High",
68233
+ isDefault: true
68234
+ },
68235
+ {
68236
+ value: "max",
68237
+ label: "Max"
68238
+ },
68239
+ {
68240
+ value: "ultrathink",
68241
+ label: "Ultrathink"
68242
+ }
68243
+ ],
68244
+ promptInjectedValues: ["ultrathink"]
68245
+ }),
68246
+ buildBooleanOptionDescriptor({
68247
+ id: "fastMode",
68248
+ label: "Fast Mode"
68249
+ }),
68250
+ buildAutoCompactWindowDescriptor()
68251
+ ] })
68252
+ },
68253
+ {
68254
+ slug: "claude-opus-4-6",
68255
+ name: "Claude Opus 4.6",
68256
+ isCustom: false,
68257
+ capabilities: createModelCapabilities({ optionDescriptors: [
68258
+ buildSelectOptionDescriptor({
68259
+ id: "effort",
68260
+ label: "Reasoning",
68261
+ options: [
68262
+ {
68263
+ value: "low",
68264
+ label: "Low"
68265
+ },
68266
+ {
68267
+ value: "medium",
68268
+ label: "Medium"
68269
+ },
68270
+ {
68271
+ value: "high",
68272
+ label: "High",
68273
+ isDefault: true
68274
+ },
68275
+ {
68276
+ value: "max",
68277
+ label: "Max"
68278
+ },
68279
+ {
68280
+ value: "ultrathink",
68281
+ label: "Ultrathink"
68282
+ }
68283
+ ],
68284
+ promptInjectedValues: ["ultrathink"]
68285
+ }),
68286
+ buildBooleanOptionDescriptor({
68287
+ id: "fastMode",
68288
+ label: "Fast Mode"
68289
+ }),
68290
+ buildSelectOptionDescriptor({
68291
+ id: "contextWindow",
68292
+ label: "Context Window",
68293
+ options: [{
68294
+ value: "200k",
68295
+ label: "200k"
68296
+ }, {
68297
+ value: "1m",
68298
+ label: "1M",
68299
+ isDefault: true
68300
+ }]
68301
+ }),
68302
+ buildAutoCompactWindowDescriptor()
67349
68303
  ] })
67350
68304
  },
67351
68305
  {
67352
- slug: "claude-opus-4-8",
67353
- name: "Claude Opus 4.8",
68306
+ slug: "claude-opus-4-5",
68307
+ name: "Claude Opus 4.5",
67354
68308
  isCustom: false,
67355
68309
  capabilities: createModelCapabilities({ optionDescriptors: [buildSelectOptionDescriptor({
67356
68310
  id: "effort",
@@ -67369,72 +68323,19 @@ const BUILT_IN_MODELS = [
67369
68323
  label: "High",
67370
68324
  isDefault: true
67371
68325
  },
67372
- {
67373
- value: "xhigh",
67374
- label: "Extra High"
67375
- },
67376
- {
67377
- value: "max",
67378
- label: "Max"
67379
- },
67380
- {
67381
- value: "ultracode",
67382
- label: "Ultracode"
67383
- },
67384
- {
67385
- value: "ultrathink",
67386
- label: "Ultrathink"
67387
- }
67388
- ],
67389
- promptInjectedValues: ["ultrathink"]
67390
- }), buildBooleanOptionDescriptor({
67391
- id: "fastMode",
67392
- label: "Fast Mode"
67393
- })] })
67394
- },
67395
- {
67396
- slug: "claude-opus-4-7",
67397
- name: "Claude Opus 4.7",
67398
- isCustom: false,
67399
- capabilities: createModelCapabilities({ optionDescriptors: [buildSelectOptionDescriptor({
67400
- id: "effort",
67401
- label: "Reasoning",
67402
- options: [
67403
- {
67404
- value: "low",
67405
- label: "Low"
67406
- },
67407
- {
67408
- value: "medium",
67409
- label: "Medium"
67410
- },
67411
- {
67412
- value: "high",
67413
- label: "High"
67414
- },
67415
- {
67416
- value: "xhigh",
67417
- label: "Extra High",
67418
- isDefault: true
67419
- },
67420
68326
  {
67421
68327
  value: "max",
67422
68328
  label: "Max"
67423
- },
67424
- {
67425
- value: "ultrathink",
67426
- label: "Ultrathink"
67427
68329
  }
67428
- ],
67429
- promptInjectedValues: ["ultrathink"]
68330
+ ]
67430
68331
  }), buildBooleanOptionDescriptor({
67431
68332
  id: "fastMode",
67432
68333
  label: "Fast Mode"
67433
68334
  })] })
67434
68335
  },
67435
68336
  {
67436
- slug: "claude-opus-4-6",
67437
- name: "Claude Opus 4.6",
68337
+ slug: "claude-sonnet-5",
68338
+ name: "Claude Sonnet 5",
67438
68339
  isCustom: false,
67439
68340
  capabilities: createModelCapabilities({ optionDescriptors: [
67440
68341
  buildSelectOptionDescriptor({
@@ -67454,6 +68355,10 @@ const BUILT_IN_MODELS = [
67454
68355
  label: "High",
67455
68356
  isDefault: true
67456
68357
  },
68358
+ {
68359
+ value: "xhigh",
68360
+ label: "Extra High"
68361
+ },
67457
68362
  {
67458
68363
  value: "max",
67459
68364
  label: "Max"
@@ -67465,146 +68370,68 @@ const BUILT_IN_MODELS = [
67465
68370
  ],
67466
68371
  promptInjectedValues: ["ultrathink"]
67467
68372
  }),
67468
- buildBooleanOptionDescriptor({
67469
- id: "fastMode",
67470
- label: "Fast Mode"
67471
- }),
67472
68373
  buildSelectOptionDescriptor({
67473
68374
  id: "contextWindow",
67474
68375
  label: "Context Window",
67475
68376
  options: [{
67476
68377
  value: "200k",
67477
- label: "200k"
68378
+ label: "200k",
68379
+ isDefault: true
67478
68380
  }, {
67479
68381
  value: "1m",
67480
- label: "1M",
67481
- isDefault: true
68382
+ label: "1M"
67482
68383
  }]
67483
- })
68384
+ }),
68385
+ buildAutoCompactWindowDescriptor()
67484
68386
  ] })
67485
68387
  },
67486
- {
67487
- slug: "claude-opus-4-5",
67488
- name: "Claude Opus 4.5",
67489
- isCustom: false,
67490
- capabilities: createModelCapabilities({ optionDescriptors: [buildSelectOptionDescriptor({
67491
- id: "effort",
67492
- label: "Reasoning",
67493
- options: [
67494
- {
67495
- value: "low",
67496
- label: "Low"
67497
- },
67498
- {
67499
- value: "medium",
67500
- label: "Medium"
67501
- },
67502
- {
67503
- value: "high",
67504
- label: "High",
67505
- isDefault: true
67506
- },
67507
- {
67508
- value: "max",
67509
- label: "Max"
67510
- }
67511
- ]
67512
- }), buildBooleanOptionDescriptor({
67513
- id: "fastMode",
67514
- label: "Fast Mode"
67515
- })] })
67516
- },
67517
- {
67518
- slug: "claude-sonnet-5",
67519
- name: "Claude Sonnet 5",
67520
- isCustom: false,
67521
- capabilities: createModelCapabilities({ optionDescriptors: [buildSelectOptionDescriptor({
67522
- id: "effort",
67523
- label: "Reasoning",
67524
- options: [
67525
- {
67526
- value: "low",
67527
- label: "Low"
67528
- },
67529
- {
67530
- value: "medium",
67531
- label: "Medium"
67532
- },
67533
- {
67534
- value: "high",
67535
- label: "High",
67536
- isDefault: true
67537
- },
67538
- {
67539
- value: "xhigh",
67540
- label: "Extra High"
67541
- },
67542
- {
67543
- value: "max",
67544
- label: "Max"
67545
- },
67546
- {
67547
- value: "ultrathink",
67548
- label: "Ultrathink"
67549
- }
67550
- ],
67551
- promptInjectedValues: ["ultrathink"]
67552
- }), buildSelectOptionDescriptor({
67553
- id: "contextWindow",
67554
- label: "Context Window",
67555
- options: [{
67556
- value: "200k",
67557
- label: "200k",
67558
- isDefault: true
67559
- }, {
67560
- value: "1m",
67561
- label: "1M"
67562
- }]
67563
- })] })
67564
- },
67565
68388
  {
67566
68389
  slug: "claude-sonnet-4-6",
67567
68390
  name: "Claude Sonnet 4.6",
67568
68391
  isCustom: false,
67569
- capabilities: createModelCapabilities({ optionDescriptors: [buildSelectOptionDescriptor({
67570
- id: "effort",
67571
- label: "Reasoning",
67572
- options: [
67573
- {
67574
- value: "low",
67575
- label: "Low"
67576
- },
67577
- {
67578
- value: "medium",
67579
- label: "Medium"
67580
- },
67581
- {
67582
- value: "high",
67583
- label: "High",
68392
+ capabilities: createModelCapabilities({ optionDescriptors: [
68393
+ buildSelectOptionDescriptor({
68394
+ id: "effort",
68395
+ label: "Reasoning",
68396
+ options: [
68397
+ {
68398
+ value: "low",
68399
+ label: "Low"
68400
+ },
68401
+ {
68402
+ value: "medium",
68403
+ label: "Medium"
68404
+ },
68405
+ {
68406
+ value: "high",
68407
+ label: "High",
68408
+ isDefault: true
68409
+ },
68410
+ {
68411
+ value: "max",
68412
+ label: "Max"
68413
+ },
68414
+ {
68415
+ value: "ultrathink",
68416
+ label: "Ultrathink"
68417
+ }
68418
+ ],
68419
+ promptInjectedValues: ["ultrathink"]
68420
+ }),
68421
+ buildSelectOptionDescriptor({
68422
+ id: "contextWindow",
68423
+ label: "Context Window",
68424
+ options: [{
68425
+ value: "200k",
68426
+ label: "200k",
67584
68427
  isDefault: true
67585
- },
67586
- {
67587
- value: "max",
67588
- label: "Max"
67589
- },
67590
- {
67591
- value: "ultrathink",
67592
- label: "Ultrathink"
67593
- }
67594
- ],
67595
- promptInjectedValues: ["ultrathink"]
67596
- }), buildSelectOptionDescriptor({
67597
- id: "contextWindow",
67598
- label: "Context Window",
67599
- options: [{
67600
- value: "200k",
67601
- label: "200k",
67602
- isDefault: true
67603
- }, {
67604
- value: "1m",
67605
- label: "1M"
67606
- }]
67607
- })] })
68428
+ }, {
68429
+ value: "1m",
68430
+ label: "1M"
68431
+ }]
68432
+ }),
68433
+ buildAutoCompactWindowDescriptor()
68434
+ ] })
67608
68435
  },
67609
68436
  {
67610
68437
  slug: "claude-haiku-4-5",
@@ -67702,6 +68529,11 @@ function resolveClaudeContextWindow(modelSelection) {
67702
68529
  }).find((candidate) => candidate.id === "contextWindow"));
67703
68530
  return typeof value === "string" ? value : void 0;
67704
68531
  }
68532
+ /** Tokens at which Claude Code should auto-compact, or undefined for its default. */
68533
+ function resolveClaudeAutoCompactWindow(modelSelection) {
68534
+ const raw = getModelSelectionStringOptionValue(modelSelection, AUTO_COMPACT_WINDOW_OPTION_ID);
68535
+ return raw === void 0 ? void 0 : AUTO_COMPACT_WINDOW_TOKENS[raw];
68536
+ }
67705
68537
  function resolveClaudeApiModelId(modelSelection) {
67706
68538
  switch (resolveClaudeContextWindow(modelSelection)) {
67707
68539
  case "1m": return `${modelSelection.model}[1m]`;
@@ -68433,7 +69265,7 @@ function formatAskUserQuestionAnswers(answers) {
68433
69265
  /** Fresh-evidence gate adapted from superpowers' verification skill. */
68434
69266
  const VERIFY_BEFORE_COMPLETION_PROMPT = "NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE. Before claiming complete, fixed, or passing: 1) identify proving command; 2) run it fresh and fully; 3) read full output, exit code, failure count; 4) confirm evidence matches claim; 5) state claim with evidence. Missing or failed proof: report actual status.";
68435
69267
  /** Visual-proof gate for user-visible frontend work. */
68436
- const SCREENSHOTS_AFTER_UI_WORK_PROMPT = "AFTER USER-VISIBLE FRONTEND WORK, FRESH VISUAL PROOF IS MANDATORY. Before completing: 1) run the relevant real client; 2) inspect the full changed surface; 3) when P4Code preview is available, use preview_snapshot for inspection, then call preview_save_screenshot once after verification passes so P4Code saves the final state under Settings > Screenshots; 4) otherwise capture the verified final state with the relevant approved browser, simulator, or computer tool; 5) verify the screenshot shows the requested result without visible errors; 6) include it in the final response. Ask before launching browser or computer use when approval is required. Skip only work with no user-visible frontend change.";
69268
+ const SCREENSHOTS_AFTER_UI_WORK_PROMPT = "AFTER USER-VISIBLE FRONTEND WORK, FRESH VISUAL PROOF IS MANDATORY. Before completing: 1) run the relevant real client; 2) inspect the full changed surface; 3) when P4Code preview is available, inspect with preview_snapshot (text state by default; pass includeScreenshot: true only for the final visual check), then call preview_save_screenshot once after verification passes so P4Code saves the final state under Settings > Screenshots; 4) otherwise capture the verified final state with the relevant approved browser, simulator, or computer tool; 5) verify the screenshot shows the requested result without visible errors; 6) include it in the final response. Keep screenshots out of your own context where possible: delegate repeated visual inspection to a read-only visual review subagent when one is available and only pull the final proof yourself. Ask before launching browser or computer use when approval is required. Skip only work with no user-visible frontend change.";
68437
69269
  /** Root-cause gate adapted from superpowers' systematic-debugging skill. */
68438
69270
  const ROOT_CAUSE_BEFORE_FIX_PROMPT = "NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST. For bugs or unexpected behavior: 1) read errors, reproduce, inspect recent changes, trace data to source; 2) compare working patterns; 3) state one hypothesis and test smallest change; 4) add failing regression test, implement one fix, verify. After 3 failed fixes, question architecture.";
68439
69271
  function guardrailPromptsFor(settings) {
@@ -68889,7 +69721,8 @@ function readClaudeResumeState(resumeCursor) {
68889
69721
  ...threadId ? { threadId } : {},
68890
69722
  ...resume ? { resume } : {},
68891
69723
  ...resumeSessionAt ? { resumeSessionAt } : {},
68892
- ...turnCountValue !== void 0 && Number.isInteger(turnCountValue) && turnCountValue >= 0 ? { turnCount: turnCountValue } : {}
69724
+ ...turnCountValue !== void 0 && Number.isInteger(turnCountValue) && turnCountValue >= 0 ? { turnCount: turnCountValue } : {},
69725
+ ...cursor.forkSession === true && resume ? { forkSession: true } : {}
68893
69726
  };
68894
69727
  }
68895
69728
  function classifyToolItemType(toolName) {
@@ -70853,8 +71686,9 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
70853
71686
  const resumeState = readClaudeResumeState(input.resumeCursor);
70854
71687
  const threadId = input.threadId;
70855
71688
  const existingResumeSessionId = resumeState?.resume;
70856
- const newSessionId = existingResumeSessionId === void 0 ? yield* randomUUIDv4 : void 0;
70857
- const sessionId = existingResumeSessionId ?? newSessionId;
71689
+ const forkSession = resumeState?.forkSession === true && existingResumeSessionId !== void 0;
71690
+ const newSessionId = existingResumeSessionId === void 0 || forkSession ? yield* randomUUIDv4 : void 0;
71691
+ const sessionId = forkSession ? newSessionId : existingResumeSessionId ?? newSessionId;
70858
71692
  const runtimeContext = yield* Effect.context();
70859
71693
  const runFork = Effect.runForkWith(runtimeContext);
70860
71694
  const runPromise = Effect.runPromiseWith(runtimeContext);
@@ -71059,6 +71893,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
71059
71893
  const descriptors = getProviderOptionDescriptors({ caps });
71060
71894
  const apiModelId = modelSelection ? resolveClaudeApiModelId(modelSelection) : void 0;
71061
71895
  const initialContextWindow = selectedClaudeContextWindow(modelSelection);
71896
+ const autoCompactWindow = resolveClaudeAutoCompactWindow(modelSelection);
71062
71897
  const effort = resolveClaudeEffort(caps, getModelSelectionStringOptionValue(modelSelection, "effort")) ?? null;
71063
71898
  const fastModeSupported = descriptors.some((descriptor) => descriptor.type === "boolean" && descriptor.id === "fastMode");
71064
71899
  const thinkingSupported = descriptors.some((descriptor) => descriptor.type === "boolean" && descriptor.id === "thinking");
@@ -71118,11 +71953,16 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
71118
71953
  ...permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {},
71119
71954
  ...Object.keys(settings).length > 0 ? { settings } : {},
71120
71955
  ...existingResumeSessionId ? { resume: existingResumeSessionId } : {},
71956
+ ...forkSession ? { forkSession: true } : {},
71121
71957
  ...newSessionId ? { sessionId: newSessionId } : {},
71122
71958
  includePartialMessages: true,
71123
71959
  canUseTool,
71124
71960
  hooks: { SubagentStart: [{ hooks: [compressionSubagentHook] }] },
71125
- env: claudeEnvironment,
71961
+ env: {
71962
+ ...claudeEnvironment,
71963
+ CLAUDE_CODE_ENABLE_TODO_TOOLS: claudeEnvironment.CLAUDE_CODE_ENABLE_TODO_TOOLS ?? "1",
71964
+ ...autoCompactWindow === void 0 ? {} : { CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(autoCompactWindow) }
71965
+ },
71126
71966
  ...input.cwd ? { additionalDirectories: [input.cwd] } : {},
71127
71967
  ...Object.keys(extraArgs).length > 0 ? { extraArgs } : {},
71128
71968
  ...mcpSession || Object.keys(externalMcpServers).length > 0 ? { mcpServers: {
@@ -71138,7 +71978,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
71138
71978
  "provider.kind": PROVIDER$6,
71139
71979
  "provider.thread_id": threadId,
71140
71980
  "provider.runtime_mode": input.runtimeMode,
71141
- "claude.resume.source": existingResumeSessionId !== void 0 ? "resume-session" : "generated-session",
71981
+ "claude.resume.source": forkSession ? "fork-session" : existingResumeSessionId !== void 0 ? "resume-session" : "generated-session",
71142
71982
  "claude.resume.thread_id": resumeState?.threadId ?? "",
71143
71983
  "claude.resume.session_id": existingResumeSessionId ?? "",
71144
71984
  "claude.resume.session_at": resumeState?.resumeSessionAt ?? "",
@@ -89646,7 +90486,7 @@ const makeTerminationError$1 = (handle) => Effect.match(handle.exitCode, {
89646
90486
  //#endregion
89647
90487
  //#region ../../packages/effect-codex-app-server/src/client.ts
89648
90488
  var CodexAppServerClient = class extends Context.Service()("effect-codex-app-server/client/CodexAppServerClient") {};
89649
- const make$8 = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(function* (stdio, options = {}, terminationError) {
90489
+ const make$9 = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(function* (stdio, options = {}, terminationError) {
89650
90490
  const requestHandlers = /* @__PURE__ */ new Map();
89651
90491
  const notificationHandlers = /* @__PURE__ */ new Map();
89652
90492
  let unknownRequestHandler;
@@ -89713,7 +90553,7 @@ const make$8 = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(fu
89713
90553
  const layerChildProcess$1 = (handle, options = {}) => Layer.effect(CodexAppServerClient, makeChildProcessClient(handle, options));
89714
90554
  const makeChildProcessClient = Effect.fn("effect-codex-app-server/CodexAppServerClient.makeChildProcessClient")(function* (handle, options) {
89715
90555
  yield* Stream.runDrain(handle.stderr).pipe(Effect.ignore, Effect.forkScoped);
89716
- return yield* make$8(makeChildStdio$1(handle), options, makeTerminationError$1(handle));
90556
+ return yield* make$9(makeChildStdio$1(handle), options, makeTerminationError$1(handle));
89717
90557
  });
89718
90558
  const resolveCodexLaunchArgs = (launchArgs, environment = process.env) => environment["P4CODE_CODEX_LAUNCH_ARGS"]?.trim() || launchArgs?.trim() || "";
89719
90559
  const codexLaunchArgv = (launchArgs) => tokenizeCliArgs(launchArgs);
@@ -91803,10 +92643,15 @@ function collabTaskEvents(event, canonicalThreadId, item, defaults) {
91803
92643
  }
91804
92644
  return events;
91805
92645
  }
91806
- function itemTitle(itemType, item) {
92646
+ function itemTitle(itemType, item, agentNames) {
91807
92647
  if (itemType === "mcp_tool_call" && item?.type === "mcpToolCall") return `${item.server} · ${item.tool}`;
91808
92648
  if (item?.type === "collabAgentToolCall") {
91809
92649
  const title = COLLAB_TOOL_TITLES[item.tool] ?? "Agent tool call";
92650
+ const names = item.receiverThreadIds.flatMap((threadId) => {
92651
+ const name = agentNames?.get(threadId);
92652
+ return name ? [name] : [];
92653
+ });
92654
+ if (names.length > 0) return `${title} · ${names.join(", ")}`;
91810
92655
  const model = trimText$1(item.model);
91811
92656
  return model ? `${title} · ${model}` : title;
91812
92657
  }
@@ -91958,12 +92803,13 @@ function runtimeEventBase(event, canonicalThreadId) {
91958
92803
  }
91959
92804
  };
91960
92805
  }
91961
- function mapItemLifecycle(event, canonicalThreadId, lifecycle) {
92806
+ function mapItemLifecycle(event, canonicalThreadId, lifecycle, agentNames) {
91962
92807
  const item = (readPayload(V2ItemStartedNotification, event.payload) ?? readPayload(V2ItemCompletedNotification, event.payload))?.item;
91963
92808
  if (!item) return;
91964
92809
  const itemType = toCanonicalItemType(item.type);
91965
92810
  if (itemType === "unknown" && lifecycle !== "item.updated") return;
91966
92811
  const detail = itemDetail(itemType, item);
92812
+ const title = itemTitle(itemType, item, agentNames);
91967
92813
  const status = lifecycle === "item.started" ? "inProgress" : lifecycle === "item.completed" ? "completed" : void 0;
91968
92814
  return {
91969
92815
  ...runtimeEventBase(event, canonicalThreadId),
@@ -91971,12 +92817,17 @@ function mapItemLifecycle(event, canonicalThreadId, lifecycle) {
91971
92817
  payload: {
91972
92818
  itemType,
91973
92819
  ...status ? { status } : {},
91974
- ...itemTitle(itemType, item) ? { title: itemTitle(itemType, item) } : {},
92820
+ ...title ? { title } : {},
91975
92821
  ...detail ? { detail } : {},
91976
92822
  ...event.payload !== void 0 ? { data: event.payload } : {}
91977
92823
  }
91978
92824
  };
91979
92825
  }
92826
+ function rememberAgentName(item, collabDefaults) {
92827
+ if (item.type !== "subAgentActivity" || collabDefaults === void 0) return;
92828
+ const name = agentNameFromPath(item.agentPath);
92829
+ if (name) collabDefaults.agentNames.set(item.agentThreadId, name);
92830
+ }
91980
92831
  function mapToRuntimeEvents(event, canonicalThreadId, collabDefaults) {
91981
92832
  if (event.kind === "error") {
91982
92833
  if (!event.message) return [];
@@ -92169,8 +93020,9 @@ function mapToRuntimeEvents(event, canonicalThreadId, collabDefaults) {
92169
93020
  }];
92170
93021
  }
92171
93022
  if (event.method === "item/started") {
92172
- const started = mapItemLifecycle(event, canonicalThreadId, "item.started");
92173
93023
  const item = readPayload(V2ItemStartedNotification, event.payload)?.item;
93024
+ if (item) rememberAgentName(item, collabDefaults);
93025
+ const started = mapItemLifecycle(event, canonicalThreadId, "item.started", collabDefaults?.agentNames);
92174
93026
  const taskEvents = item ? collabTaskEvents(event, canonicalThreadId, item, collabDefaults) : [];
92175
93027
  return started ? [started, ...taskEvents] : taskEvents;
92176
93028
  }
@@ -92187,7 +93039,8 @@ function mapToRuntimeEvents(event, canonicalThreadId, collabDefaults) {
92187
93039
  payload: { planMarkdown: detail }
92188
93040
  }];
92189
93041
  }
92190
- const completed = mapItemLifecycle(event, canonicalThreadId, "item.completed");
93042
+ rememberAgentName(item, collabDefaults);
93043
+ const completed = mapItemLifecycle(event, canonicalThreadId, "item.completed", collabDefaults?.agentNames);
92191
93044
  const taskEvents = collabTaskEvents(event, canonicalThreadId, item, collabDefaults);
92192
93045
  return completed ? [completed, ...taskEvents] : taskEvents;
92193
93046
  }
@@ -92561,6 +93414,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
92561
93414
  cause
92562
93415
  })));
92563
93416
  const collabDefaults = {
93417
+ agentNames: /* @__PURE__ */ new Map(),
92564
93418
  model: input.modelSelection?.instanceId === boundInstanceId ? input.modelSelection.model : void 0,
92565
93419
  reasoningEffort: input.modelSelection?.instanceId === boundInstanceId ? getModelSelectionStringOptionValue(input.modelSelection, "reasoningEffort") : void 0
92566
93420
  };
@@ -96499,7 +97353,7 @@ const makeTerminationError = (handle) => Effect.match(handle.exitCode, {
96499
97353
  //#endregion
96500
97354
  //#region ../../packages/effect-acp/src/client.ts
96501
97355
  var AcpClient = class extends Context.Service()("effect-acp/client/AcpClient") {};
96502
- const make$7 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
97356
+ const make$8 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options = {}, terminationError) {
96503
97357
  const coreHandlers = {};
96504
97358
  const notificationHandlers = {
96505
97359
  sessionUpdate: {
@@ -96657,7 +97511,7 @@ const make$7 = Effect.fn("effect-acp/AcpClient.make")(function* (stdio, options
96657
97511
  const layerChildProcess = (handle, options = {}) => {
96658
97512
  const stdio = makeChildStdio(handle);
96659
97513
  const terminationError = makeTerminationError(handle);
96660
- return Layer.effect(AcpClient, make$7(stdio, options, terminationError));
97514
+ return Layer.effect(AcpClient, make$8(stdio, options, terminationError));
96661
97515
  };
96662
97516
  //#endregion
96663
97517
  //#region ../../packages/shared/src/toolActivity.ts
@@ -97117,7 +97971,7 @@ function formatConfigOptionValue(value) {
97117
97971
  const defaultSessionLoadTimeout = Duration.seconds(90);
97118
97972
  const defaultSessionLoadReplayIdleGap = Duration.seconds(2);
97119
97973
  var AcpSessionRuntime = class extends Context.Service()("@p4code/cli/provider/acp/AcpSessionRuntime") {};
97120
- const make$6 = (options) => Effect.gen(function* () {
97974
+ const make$7 = (options) => Effect.gen(function* () {
97121
97975
  const crypto = yield* Crypto.Crypto;
97122
97976
  const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
97123
97977
  const runtimeScope = yield* Scope.Scope;
@@ -97433,7 +98287,7 @@ const make$6 = (options) => Effect.gen(function* () {
97433
98287
  notify: acp.raw.notify
97434
98288
  };
97435
98289
  });
97436
- const layer$2 = (options) => Layer.effect(AcpSessionRuntime, make$6(options));
98290
+ const layer$2 = (options) => Layer.effect(AcpSessionRuntime, make$7(options));
97437
98291
  function sessionConfigOptionsFromSetup(response) {
97438
98292
  return response?.configOptions ?? [];
97439
98293
  }
@@ -104754,8 +105608,8 @@ const PreviewSetAppearanceTool = safeBrowserTool(Tool.make("preview_set_appearan
104754
105608
  dependencies: dependencies$1
104755
105609
  }).annotate(Tool.Title, "Set preview appearance").annotate(Tool.Idempotent, true));
104756
105610
  const PreviewSnapshotTool = readonlyBrowserTool(Tool.make("preview_snapshot", {
104757
- description: "Inspect a page before interacting. Pass tabId to inspect a specific tab; omit it to use this agent session's current tab. Returns page state, semantic elements, diagnostics, action history, and a PNG screenshot.",
104758
- parameters: PreviewAutomationTabTargetInput,
105611
+ description: "Inspect a page before interacting. Pass tabId to inspect a specific tab; omit it to use this agent session's current tab. Returns page state, semantic elements, diagnostics, and action history as text. Pass includeScreenshot: true only when you need to see the rendering; the text state is enough for locators and assertions.",
105612
+ parameters: PreviewAutomationSnapshotInput,
104759
105613
  success: PreviewAutomationSnapshot,
104760
105614
  failure: PreviewAutomationError,
104761
105615
  dependencies: dependencies$1
@@ -104857,7 +105711,7 @@ const handlers$4 = {
104857
105711
  preview_navigate: (input) => invokeTargeted("navigate", input, input.timeoutMs),
104858
105712
  preview_resize: (input) => invokeTargeted("resize", input, input.timeoutMs),
104859
105713
  preview_set_appearance: (input) => invokeTargeted("setColorScheme", input),
104860
- preview_snapshot: (input) => invokeTargeted("snapshot", input ?? {}),
105714
+ preview_snapshot: (input) => invokeTargeted("snapshot", input?.tabId ? { tabId: input.tabId } : {}),
104861
105715
  preview_save_screenshot: (input) => invokeTargeted("saveScreenshot", input ?? {}),
104862
105716
  preview_click: (input) => invokeTargeted("click", input, input.timeoutMs).pipe(Effect.as(null)),
104863
105717
  preview_type: (input) => invokeTargeted("type", input, input.timeoutMs).pipe(Effect.as(null)),
@@ -104879,7 +105733,7 @@ const stringField = (record, key) => {
104879
105733
  const value = record[key];
104880
105734
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
104881
105735
  };
104882
- const make$5 = Effect.gen(function* () {
105736
+ const make$6 = Effect.gen(function* () {
104883
105737
  const linear = yield* LinearClient;
104884
105738
  return { resolve: Effect.fn("TicketResolver.resolve")(function* (reference) {
104885
105739
  const identifier = parseTicketReference(reference);
@@ -104910,7 +105764,7 @@ const make$5 = Effect.gen(function* () {
104910
105764
  };
104911
105765
  }) };
104912
105766
  });
104913
- const layer$1 = Layer.effect(TicketResolver, make$5);
105767
+ const layer$1 = Layer.effect(TicketResolver, make$6);
104914
105768
  //#endregion
104915
105769
  //#region src/mcp/toolkits/tasks/tools.ts
104916
105770
  const dependencies = [McpInvocationContext, TaskRepository];
@@ -106419,6 +107273,7 @@ const registerPreviewSnapshot = Effect.fn("McpHttpServer.registerPreviewSnapshot
106419
107273
  return built.handle("preview_snapshot", payload).pipe(Stream.unwrap, Stream.run(Sink.last()), Effect.flatMap(Effect.fromOption), Effect.provideService(PreviewAutomationBroker, broker), Effect.provideService(McpInvocationContext, invocation), Effect.matchCauseEffect({
106420
107274
  onFailure: previewSnapshotFailure,
106421
107275
  onSuccess: ({ encodedResult }) => {
107276
+ const includeScreenshot = payload?.includeScreenshot === true;
106422
107277
  const { screenshot, ...page } = encodedResult;
106423
107278
  const metadata = {
106424
107279
  ...page,
@@ -106434,11 +107289,11 @@ const registerPreviewSnapshot = Effect.fn("McpHttpServer.registerPreviewSnapshot
106434
107289
  content: [{
106435
107290
  type: "text",
106436
107291
  text: JSON.stringify(metadata)
106437
- }, {
107292
+ }, ...includeScreenshot ? [{
106438
107293
  type: "image",
106439
107294
  data: new Uint8Array(Buffer.from(screenshot.data, "base64")),
106440
107295
  mimeType: screenshot.mimeType
106441
- }]
107296
+ }] : []]
106442
107297
  }));
106443
107298
  }
106444
107299
  }));
@@ -106518,6 +107373,18 @@ var ThreadDeletionReactor = class extends Context.Service()("@p4code/cli/orchest
106518
107373
  //#region src/orchestration/Services/FusionWatcherReactor.ts
106519
107374
  var FusionWatcherReactor = class extends Context.Service()("@p4code/cli/orchestration/Services/FusionWatcherReactor") {};
106520
107375
  //#endregion
107376
+ //#region src/orchestration/Services/ScheduledTaskReactor.ts
107377
+ /**
107378
+ * ScheduledTaskReactor - fires user-created scheduled tasks.
107379
+ *
107380
+ * Arms one timer per pending task (from the read model at start and from
107381
+ * `thread.scheduled-task.created` events afterwards), starts the thread turn
107382
+ * when the time comes, and records the outcome on the task.
107383
+ *
107384
+ * @module ScheduledTaskReactor
107385
+ */
107386
+ var ScheduledTaskReactor = class extends Context.Service()("@p4code/cli/orchestration/Services/ScheduledTaskReactor") {};
107387
+ //#endregion
106521
107388
  //#region src/orchestration/Layers/OrchestrationReactor.ts
106522
107389
  const makeOrchestrationReactor = Effect.gen(function* () {
106523
107390
  const providerRuntimeIngestion = yield* ProviderRuntimeIngestionService;
@@ -106525,12 +107392,14 @@ const makeOrchestrationReactor = Effect.gen(function* () {
106525
107392
  const checkpointReactor = yield* CheckpointReactor;
106526
107393
  const threadDeletionReactor = yield* ThreadDeletionReactor;
106527
107394
  const fusionWatcherReactor = yield* FusionWatcherReactor;
107395
+ const scheduledTaskReactor = yield* ScheduledTaskReactor;
106528
107396
  return { start: Effect.fn("start")(function* () {
106529
107397
  yield* providerRuntimeIngestion.start();
106530
107398
  yield* providerCommandReactor.start();
106531
107399
  yield* checkpointReactor.start();
106532
107400
  yield* threadDeletionReactor.start();
106533
107401
  yield* fusionWatcherReactor.start();
107402
+ yield* scheduledTaskReactor.start();
106534
107403
  }) };
106535
107404
  });
106536
107405
  const OrchestrationReactorLive = Layer.effect(OrchestrationReactor, makeOrchestrationReactor);
@@ -107116,7 +107985,7 @@ function runtimeEventToActivities(event, taskTitle, compressMode) {
107116
107985
  }
107117
107986
  return [];
107118
107987
  }
107119
- const make$4 = Effect.gen(function* () {
107988
+ const make$5 = Effect.gen(function* () {
107120
107989
  const crypto = yield* Crypto.Crypto;
107121
107990
  const orchestrationEngine = yield* OrchestrationEngineService;
107122
107991
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
@@ -107746,7 +108615,7 @@ const make$4 = Effect.gen(function* () {
107746
108615
  drain: worker.drain
107747
108616
  };
107748
108617
  });
107749
- const ProviderRuntimeIngestionLive = Layer.effect(ProviderRuntimeIngestionService, make$4).pipe(Layer.provide(ProjectionTurnRepositoryLive), Layer.provide(layer$64));
108618
+ const ProviderRuntimeIngestionLive = Layer.effect(ProviderRuntimeIngestionService, make$5).pipe(Layer.provide(ProjectionTurnRepositoryLive), Layer.provide(layer$64));
107750
108619
  //#endregion
107751
108620
  //#region src/provider/userInvokedSkills.ts
107752
108621
  /**
@@ -107922,8 +108791,8 @@ const DEFAULT_RUNTIME_MODE = "full-access";
107922
108791
  const DEFAULT_THREAD_TITLE = "New thread";
107923
108792
  const NON_SYSTEM_PROVIDER_STRUCTURED_USER_QUESTIONS = structuredUserQuestionPrompt("your provider's structured user-input question tool");
107924
108793
  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.`;
107925
- 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 in the same plan/todo tool you use for ordinary step tracking, 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. 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.`;
107926
- 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; this message arrived outside such a wake, so your conversational memory of the pair may be gone. The pair metadata below 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), 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.`;
108794
+ 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. 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.`;
108795
+ 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; this message arrived outside such a wake, so your conversational memory of the pair may be gone. The pair metadata below 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.`;
107927
108796
  const isFusionWatcherWakeMessageId = (messageId) => messageId.startsWith("fusion-review:") || messageId.startsWith("fusion-gate:");
107928
108797
  const fusionPairContext = (pair, role) => {
107929
108798
  const counterpartThreadId = role === "implementer" ? pair.watcherThreadId : pair.implementerThreadId;
@@ -108011,7 +108880,7 @@ function resolvePendingWorkspaceCleanupGroups(input) {
108011
108880
  }
108012
108881
  return groups;
108013
108882
  }
108014
- const make$3 = Effect.gen(function* () {
108883
+ const make$4 = Effect.gen(function* () {
108015
108884
  const crypto = yield* Crypto.Crypto;
108016
108885
  const orchestrationEngine = yield* OrchestrationEngineService;
108017
108886
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
@@ -108890,7 +109759,7 @@ const make$3 = Effect.gen(function* () {
108890
109759
  drain: Effect.all([worker.drain, forceStopDrain], { discard: true }).pipe(Effect.asVoid)
108891
109760
  };
108892
109761
  });
108893
- const ProviderCommandReactorLive = Layer.effect(ProviderCommandReactor, make$3);
109762
+ const ProviderCommandReactorLive = Layer.effect(ProviderCommandReactor, make$4);
108894
109763
  //#endregion
108895
109764
  //#region src/checkpointing/Diffs.ts
108896
109765
  function parseTurnDiffFilesFromUnifiedDiff(diff) {
@@ -108920,7 +109789,7 @@ function checkpointStatusFromRuntime(status) {
108920
109789
  default: return "ready";
108921
109790
  }
108922
109791
  }
108923
- const make$2 = Effect.gen(function* () {
109792
+ const make$3 = Effect.gen(function* () {
108924
109793
  const randomUUID = (yield* Crypto.Crypto).randomUUIDv4;
108925
109794
  const serverEventId = randomUUID.pipe(Effect.map(EventId.make));
108926
109795
  const serverCommandId = (tag) => randomUUID.pipe(Effect.map((uuid) => CommandId.make(`server:${tag}:${uuid}`)));
@@ -109402,7 +110271,7 @@ const make$2 = Effect.gen(function* () {
109402
110271
  drain: worker.drain
109403
110272
  };
109404
110273
  });
109405
- const CheckpointReactorLive = Layer.effect(CheckpointReactor, make$2);
110274
+ const CheckpointReactorLive = Layer.effect(CheckpointReactor, make$3);
109406
110275
  //#endregion
109407
110276
  //#region src/orchestration/Layers/FusionWatcherReactor.ts
109408
110277
  const GATE_TIMEOUT_SWEEP_INTERVAL = "10 seconds";
@@ -109492,7 +110361,7 @@ Then thread_gate_respond, threadId ${input.implementerThreadId}, gateId ${input.
109492
110361
  - "object" plus message: send objection, spend round. After ${input.roundCap} objections, escalate to user.
109493
110362
 
109494
110363
  No answer within ${Math.round(input.gateTimeoutMs / 1e3)} seconds: fail open, record unwatched. Answer, briefly explain to user, end turn; never wait for builder.`;
109495
- const make$1 = Effect.gen(function* () {
110364
+ const make$2 = Effect.gen(function* () {
109496
110365
  const orchestrationEngine = yield* OrchestrationEngineService;
109497
110366
  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
109498
110367
  /**
@@ -110092,7 +110961,7 @@ const make$1 = Effect.gen(function* () {
110092
110961
  sweepGates: sweepGateTimeouts.pipe(Effect.catchCause((cause) => Effect.logWarning("fusion gate timeout sweep failed", { cause: Cause.pretty(cause) })))
110093
110962
  };
110094
110963
  });
110095
- const FusionWatcherReactorLive = Layer.effect(FusionWatcherReactor, make$1);
110964
+ const FusionWatcherReactorLive = Layer.effect(FusionWatcherReactor, make$2);
110096
110965
  //#endregion
110097
110966
  //#region src/orchestration/Layers/ThreadDeletionReactor.ts
110098
110967
  const logCleanupCauseUnlessInterrupted = ({ effect, message, threadId }) => effect.pipe(Effect.catchCause((cause) => {
@@ -110102,7 +110971,7 @@ const logCleanupCauseUnlessInterrupted = ({ effect, message, threadId }) => effe
110102
110971
  cause: Cause.pretty(cause)
110103
110972
  });
110104
110973
  }));
110105
- const make = Effect.gen(function* () {
110974
+ const make$1 = Effect.gen(function* () {
110106
110975
  const orchestrationEngine = yield* OrchestrationEngineService;
110107
110976
  const providerService = yield* ProviderService;
110108
110977
  const terminalManager = yield* TerminalManager;
@@ -110143,7 +111012,213 @@ const make = Effect.gen(function* () {
110143
111012
  drain: worker.drain
110144
111013
  };
110145
111014
  });
110146
- const ThreadDeletionReactorLive = Layer.effect(ThreadDeletionReactor, make);
111015
+ const ThreadDeletionReactorLive = Layer.effect(ThreadDeletionReactor, make$1);
111016
+ //#endregion
111017
+ //#region src/orchestration/scheduledTasks.ts
111018
+ /** Milliseconds until a task is due; 0 for anything already overdue. */
111019
+ function scheduledTaskDelayMs(runAt, nowMs) {
111020
+ const runAtMs = Date.parse(runAt);
111021
+ if (Number.isNaN(runAtMs)) return 0;
111022
+ return Math.max(0, runAtMs - nowMs);
111023
+ }
111024
+ function pendingScheduledTasks(threads) {
111025
+ return threads.flatMap((thread) => thread.deletedAt !== null ? [] : (thread.scheduledTasks ?? []).filter((task) => task.status === "pending").map((task) => ({
111026
+ threadId: thread.id,
111027
+ task
111028
+ })));
111029
+ }
111030
+ /**
111031
+ * Task ids are unique per thread only, so every key derived from a task is
111032
+ * scoped by its thread.
111033
+ */
111034
+ function scheduledTaskKey(threadId, taskId) {
111035
+ return `${threadId}:${taskId}`;
111036
+ }
111037
+ /**
111038
+ * Every turn-start attempt gets its own command id: the engine keeps a
111039
+ * rejected receipt per command id, so reusing one would replay the first
111040
+ * rejection (a busy thread) on every retry. Crash idempotence comes from the
111041
+ * deterministic message id instead: a turn that already sent
111042
+ * {@link scheduledTaskMessageId} is never started again.
111043
+ */
111044
+ function scheduledTaskTurnCommandId(threadId, taskId, attemptToken) {
111045
+ return CommandId.make(`scheduled-task:${threadId}:${taskId}:turn:${attemptToken}`);
111046
+ }
111047
+ function scheduledTaskFireCommandId(threadId, taskId) {
111048
+ return CommandId.make(`scheduled-task:${threadId}:${taskId}:fire`);
111049
+ }
111050
+ function scheduledTaskMessageId(threadId, taskId) {
111051
+ return MessageId.make(`scheduled-task:${threadId}:${taskId}`);
111052
+ }
111053
+ //#endregion
111054
+ //#region src/orchestration/Layers/ScheduledTaskReactor.ts
111055
+ /**
111056
+ * A turn that cannot start (thread busy, provider down) is retried on this
111057
+ * cadence before the task is marked failed; the count resets with the process,
111058
+ * and the periodic reconcile re-arms anything still pending.
111059
+ */
111060
+ const SCHEDULED_TASK_TURN_RETRY_DELAY = Duration.minutes(2);
111061
+ /** Re-arms pending tasks that lost their timer (fire failure, missed event). */
111062
+ const SCHEDULED_TASK_RECONCILE_INTERVAL = Duration.minutes(5);
111063
+ /** Engine-level failures while firing (e.g. persistence) back off briefly and retry. */
111064
+ const FIRE_RETRY = {
111065
+ schedule: Schedule.exponential(Duration.seconds(10)),
111066
+ times: 3
111067
+ };
111068
+ const make = Effect.gen(function* () {
111069
+ const orchestrationEngine = yield* OrchestrationEngineService;
111070
+ const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
111071
+ const threadMessages = yield* ProjectionThreadMessageRepository;
111072
+ const timers = /* @__PURE__ */ new Map();
111073
+ const turnAttempts = /* @__PURE__ */ new Map();
111074
+ const nowIso = Effect.map(DateTime.now, DateTime.formatIso);
111075
+ const disarm = (threadId, taskId) => Effect.gen(function* () {
111076
+ const key = scheduledTaskKey(threadId, taskId);
111077
+ turnAttempts.delete(key);
111078
+ const fiber = timers.get(key);
111079
+ if (fiber === void 0) return;
111080
+ timers.delete(key);
111081
+ yield* Fiber.interrupt(fiber);
111082
+ });
111083
+ const describeFailure = (failure) => failure instanceof Error ? failure.message : String(failure);
111084
+ /**
111085
+ * Starts the turn, then marks the task fired. The turn's user message has a
111086
+ * deterministic id, so a crash between the two steps re-fires on the next
111087
+ * boot without a second turn: an existing message means the turn already
111088
+ * started. Each attempt uses a fresh command id because the engine replays
111089
+ * a rejected receipt for a reused one. A turn that cannot start re-arms the
111090
+ * task for a bounded number of retries and only then records the failure.
111091
+ */
111092
+ const fire = Effect.fn("ScheduledTaskReactor.fire")(function* (threadId, task) {
111093
+ const key = scheduledTaskKey(threadId, task.id);
111094
+ timers.delete(key);
111095
+ const thread = (yield* projectionSnapshotQuery.getCommandReadModel()).threads.find((entry) => entry.id === threadId);
111096
+ const current = thread?.scheduledTasks?.find((entry) => entry.id === task.id);
111097
+ if (thread === void 0 || thread.deletedAt !== null || current?.status !== "pending") {
111098
+ turnAttempts.delete(key);
111099
+ return;
111100
+ }
111101
+ const messageId = scheduledTaskMessageId(threadId, task.id);
111102
+ const turnResult = Option.isSome(yield* threadMessages.getByMessageId({ messageId })) ? { _tag: "Success" } : yield* orchestrationEngine.dispatch({
111103
+ type: "thread.turn.start",
111104
+ commandId: scheduledTaskTurnCommandId(threadId, task.id, String(yield* Clock.currentTimeMillis)),
111105
+ threadId,
111106
+ message: {
111107
+ messageId,
111108
+ role: "user",
111109
+ text: task.prompt,
111110
+ attachments: []
111111
+ },
111112
+ runtimeMode: thread.runtimeMode,
111113
+ interactionMode: thread.interactionMode,
111114
+ compressMode: thread.compressMode,
111115
+ unpromptedSubagents: thread.unpromptedSubagents,
111116
+ createdAt: yield* nowIso
111117
+ }).pipe(Effect.result);
111118
+ if (turnResult._tag === "Failure") {
111119
+ const attempts = (turnAttempts.get(key) ?? 0) + 1;
111120
+ if (attempts < 10) {
111121
+ turnAttempts.set(key, attempts);
111122
+ yield* Effect.logInfo("scheduled task turn could not start; retrying", {
111123
+ threadId,
111124
+ taskId: task.id,
111125
+ attempt: attempts,
111126
+ failure: describeFailure(turnResult.failure)
111127
+ });
111128
+ yield* armAfter(threadId, task, SCHEDULED_TASK_TURN_RETRY_DELAY);
111129
+ return;
111130
+ }
111131
+ turnAttempts.delete(key);
111132
+ yield* orchestrationEngine.dispatch({
111133
+ type: "thread.scheduled-task.fire",
111134
+ commandId: scheduledTaskFireCommandId(threadId, task.id),
111135
+ threadId,
111136
+ taskId: task.id,
111137
+ firedAt: yield* nowIso,
111138
+ failure: `Could not start the turn after ${attempts} attempts: ${describeFailure(turnResult.failure)}`
111139
+ });
111140
+ return;
111141
+ }
111142
+ turnAttempts.delete(key);
111143
+ yield* orchestrationEngine.dispatch({
111144
+ type: "thread.scheduled-task.fire",
111145
+ commandId: scheduledTaskFireCommandId(threadId, task.id),
111146
+ threadId,
111147
+ taskId: task.id,
111148
+ firedAt: yield* nowIso
111149
+ });
111150
+ });
111151
+ const armAfter = (threadId, task, delay) => Effect.gen(function* () {
111152
+ const key = scheduledTaskKey(threadId, task.id);
111153
+ const existing = timers.get(key);
111154
+ if (existing !== void 0) {
111155
+ timers.delete(key);
111156
+ yield* Fiber.interrupt(existing);
111157
+ }
111158
+ const fiber = yield* Effect.forkDetach(Effect.sleep(delay).pipe(Effect.flatMap(() => fire(threadId, task).pipe(Effect.retry(FIRE_RETRY))), Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.void : Effect.logWarning("scheduled task failed to fire; it stays pending until reconcile re-arms it", {
111159
+ threadId,
111160
+ taskId: task.id,
111161
+ cause: Cause.pretty(cause)
111162
+ }))));
111163
+ timers.set(key, fiber);
111164
+ });
111165
+ const arm = (threadId, task) => Effect.gen(function* () {
111166
+ const delayMs = scheduledTaskDelayMs(task.runAt, yield* Clock.currentTimeMillis);
111167
+ yield* armAfter(threadId, task, Duration.millis(delayMs));
111168
+ });
111169
+ /** Arms every pending task that has no live timer; the read model is authoritative. */
111170
+ const reconcile = Effect.fn("ScheduledTaskReactor.reconcile")(function* () {
111171
+ const readModel = yield* projectionSnapshotQuery.getCommandReadModel();
111172
+ for (const pending of pendingScheduledTasks(readModel.threads)) {
111173
+ if (timers.has(scheduledTaskKey(pending.threadId, pending.task.id))) continue;
111174
+ yield* arm(pending.threadId, pending.task);
111175
+ }
111176
+ });
111177
+ const processEvent = Effect.fn("ScheduledTaskReactor.processEvent")(function* (event) {
111178
+ switch (event.type) {
111179
+ case "thread.scheduled-task.created":
111180
+ yield* arm(event.payload.threadId, event.payload.task);
111181
+ return;
111182
+ case "thread.scheduled-task.cancelled":
111183
+ case "thread.scheduled-task.fired":
111184
+ yield* disarm(event.payload.threadId, event.payload.taskId);
111185
+ return;
111186
+ case "thread.deleted":
111187
+ for (const key of Array.from(timers.keys())) {
111188
+ if (!key.startsWith(`${event.payload.threadId}:`)) continue;
111189
+ const fiber = timers.get(key);
111190
+ timers.delete(key);
111191
+ turnAttempts.delete(key);
111192
+ if (fiber !== void 0) yield* Fiber.interrupt(fiber);
111193
+ }
111194
+ return;
111195
+ }
111196
+ });
111197
+ const processEventSafely = (event) => processEvent(event).pipe(Effect.catchCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.failCause(cause) : Effect.logWarning("scheduled task reactor failed to process event", {
111198
+ eventType: event.type,
111199
+ cause: Cause.pretty(cause)
111200
+ })));
111201
+ const worker = yield* makeDrainableWorker(processEventSafely);
111202
+ const logReconcileFailure = (cause) => Effect.logWarning("scheduled task reconcile failed", { cause: Cause.pretty(cause) });
111203
+ return {
111204
+ start: Effect.fn("start")(function* () {
111205
+ yield* Effect.forkScoped(Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) => {
111206
+ switch (event.type) {
111207
+ case "thread.scheduled-task.created":
111208
+ case "thread.scheduled-task.cancelled":
111209
+ case "thread.scheduled-task.fired":
111210
+ case "thread.deleted": return worker.enqueue(event);
111211
+ default: return Effect.void;
111212
+ }
111213
+ }));
111214
+ yield* reconcile().pipe(Effect.catchCause(logReconcileFailure));
111215
+ yield* Effect.forkScoped(Effect.repeat(reconcile().pipe(Effect.catchCause(logReconcileFailure)), Schedule.spaced(SCHEDULED_TASK_RECONCILE_INTERVAL)).pipe(Effect.delay(SCHEDULED_TASK_RECONCILE_INTERVAL)));
111216
+ yield* Effect.addFinalizer(() => Effect.forEach([...timers.values()], (fiber) => Fiber.interrupt(fiber), { discard: true }).pipe(Effect.tap(() => Effect.sync(() => timers.clear()))));
111217
+ }),
111218
+ drain: worker.drain
111219
+ };
111220
+ });
111221
+ const ScheduledTaskReactorLive = Layer.effect(ScheduledTaskReactor, make);
110147
111222
  //#endregion
110148
111223
  //#region src/provider/providerStatusCache.ts
110149
111224
  const decodeProviderStatusCache = Schema$1.decodeUnknownEffect(Schema$1.fromJsonString(ServerProvider));
@@ -110924,7 +111999,7 @@ const PlatformServicesLive = Layer.unwrap(Effect.gen(function* () {
110924
111999
  return layer;
110925
112000
  }
110926
112001
  }));
110927
- 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(RuntimeReceiptBusLive));
112002
+ 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));
110928
112003
  const ProviderSessionDirectoryLayerLive = ProviderSessionDirectoryLive.pipe(Layer.provide(layer$4));
110929
112004
  const ProviderLayerLive = ProviderServiceLive.pipe(Layer.provide(ProviderAdapterRegistryLive), Layer.provideMerge(ProviderSessionDirectoryLayerLive));
110930
112005
  const PersistenceLayerLive = Layer.empty.pipe(Layer.provideMerge(layerConfig));