@p4code/cli 0.3.15 → 0.3.17

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
@@ -238,7 +238,7 @@ const make$91 = () => {
238
238
  const layer$82 = Layer.sync(NetService, make$91);
239
239
  //#endregion
240
240
  //#region package.json
241
- var version = "0.3.15";
241
+ var version = "0.3.17";
242
242
  //#endregion
243
243
  //#region src/config.ts
244
244
  /**
@@ -2173,11 +2173,46 @@ const OrchestrationSubscribeThreadInput = Schema$1.Struct({
2173
2173
  * Requests an explicit marker after the subscription has emitted its initial
2174
2174
  * snapshot or catch-up replay and before it begins emitting live events.
2175
2175
  */
2176
- requestCompletionMarker: Schema$1.optionalKey(Schema$1.Boolean)
2176
+ requestCompletionMarker: Schema$1.optionalKey(Schema$1.Boolean),
2177
+ /**
2178
+ * When provided, the fallback snapshot frame (sent when `afterSequence` is
2179
+ * missing or the catch-up gap is too large) is windowed to the last
2180
+ * `turnLimit` user-anchored turns and carries `page` metadata. Absent means
2181
+ * the fallback snapshot is the full thread, preserving pre-pagination client
2182
+ * behavior. Live events are unaffected either way.
2183
+ */
2184
+ turnLimit: Schema$1.optionalKey(PositiveInt)
2185
+ });
2186
+ Schema$1.Struct({
2187
+ turnLimit: Schema$1.optionalKey(PositiveInt),
2188
+ beforeCursor: Schema$1.optionalKey(TrimmedNonEmptyString)
2189
+ });
2190
+ /**
2191
+ * Page metadata for a windowed thread detail read. `beforeCursor` is opaque and
2192
+ * exclusive: passing it back returns the adjacent disjoint slice of older
2193
+ * turns. `null` means the thread is fully loaded below this page. The
2194
+ * `snapshotSequence` mirrors the top-level snapshot sequence so history pages
2195
+ * can be sequence-checked against live state before merging.
2196
+ */
2197
+ const OrchestrationThreadDetailPage = Schema$1.Struct({
2198
+ beforeCursor: Schema$1.NullOr(TrimmedNonEmptyString),
2199
+ hasMore: Schema$1.Boolean,
2200
+ snapshotSequence: NonNegativeInt,
2201
+ /**
2202
+ * Highest event sequence applied to THIS thread at page read time. The
2203
+ * global `snapshotSequence` advances with every thread's events, so a
2204
+ * client cannot wait for it via its per-thread subscription; this
2205
+ * thread-scoped watermark is reachable. A client merging an older page
2206
+ * must first have applied live events up to it — otherwise a streaming
2207
+ * turn outside the loaded window could have deltas replayed on top of
2208
+ * page content that already includes them, duplicating text.
2209
+ */
2210
+ threadSequence: Schema$1.optionalKey(NonNegativeInt)
2177
2211
  });
2178
2212
  const OrchestrationThreadDetailSnapshot = Schema$1.Struct({
2179
2213
  snapshotSequence: NonNegativeInt,
2180
- thread: OrchestrationThread
2214
+ thread: OrchestrationThread,
2215
+ page: Schema$1.optional(OrchestrationThreadDetailPage)
2181
2216
  });
2182
2217
  const ProjectCreateCommand = Schema$1.Struct({
2183
2218
  type: Schema$1.Literal("project.create"),
@@ -4709,6 +4744,10 @@ var EnvironmentAuthHttpApi = class extends HttpApiGroup.make("auth").add(HttpApi
4709
4744
  error: EnvironmentScopedOperationErrors
4710
4745
  }).middleware(EnvironmentAuthenticatedAuth)) {};
4711
4746
  const EnvironmentOrchestrationThreadSnapshotParams = Schema$1.Struct({ threadId: ThreadId });
4747
+ const EnvironmentOrchestrationThreadSnapshotQuery = {
4748
+ turnLimit: Schema$1.optional(Schema$1.FiniteFromString.check(Schema$1.isInt(), Schema$1.isGreaterThanOrEqualTo(1))),
4749
+ beforeCursor: Schema$1.optional(TrimmedNonEmptyString)
4750
+ };
4712
4751
  var EnvironmentOrchestrationHttpApi = class extends HttpApiGroup.make("orchestration").add(HttpApiEndpoint.get("snapshot", "/api/orchestration/snapshot", {
4713
4752
  headers: OptionalBearerHeaders,
4714
4753
  success: OrchestrationReadModel,
@@ -4720,6 +4759,7 @@ var EnvironmentOrchestrationHttpApi = class extends HttpApiGroup.make("orchestra
4720
4759
  }).middleware(EnvironmentAuthenticatedAuth)).add(HttpApiEndpoint.get("threadSnapshot", "/api/orchestration/threads/:threadId", {
4721
4760
  headers: OptionalBearerHeaders,
4722
4761
  params: EnvironmentOrchestrationThreadSnapshotParams,
4762
+ payload: EnvironmentOrchestrationThreadSnapshotQuery,
4723
4763
  success: OrchestrationThreadDetailSnapshot,
4724
4764
  error: EnvironmentOrchestrationThreadSnapshotErrors
4725
4765
  }).middleware(EnvironmentAuthenticatedAuth)).add(HttpApiEndpoint.post("dispatch", "/api/orchestration/dispatch", {
@@ -8812,7 +8852,13 @@ const ServerConfig = Schema$1.Struct({
8812
8852
  /** Whether shell subscriptions can emit an opt-in catch-up completion marker. */
8813
8853
  shellResumeCompletionMarker: Schema$1.optionalKey(Schema$1.Boolean),
8814
8854
  /** Whether thread subscriptions can emit an opt-in catch-up completion marker. */
8815
- threadResumeCompletionMarker: Schema$1.optionalKey(Schema$1.Boolean)
8855
+ threadResumeCompletionMarker: Schema$1.optionalKey(Schema$1.Boolean),
8856
+ /**
8857
+ * Whether thread detail reads accept a turn window (`turnLimit`/
8858
+ * `beforeCursor`) and return `page` metadata. Clients must not send window
8859
+ * fields to servers that don't advertise this.
8860
+ */
8861
+ threadSnapshotPagination: Schema$1.optionalKey(Schema$1.Boolean)
8816
8862
  });
8817
8863
  const ServerUpsertKeybindingReplaceTarget = Schema$1.Struct({
8818
8864
  key: KeybindingValue,
@@ -15774,6 +15820,21 @@ var _052_CompactHistoricalToolActivities_default = Effect.gen(function* () {
15774
15820
  `;
15775
15821
  });
15776
15822
  //#endregion
15823
+ //#region src/persistence/Migrations/053_ProjectionTurnsKeysetIndex.ts
15824
+ /**
15825
+ * Composite index for windowed thread detail reads. Pagination orders turns by
15826
+ * the stable keyset (requested_at, turn_id); the pre-existing
15827
+ * (thread_id, requested_at) index cannot serve the tiebreak order, forcing a
15828
+ * temp B-tree over all of a thread's turns before the page LIMIT applies.
15829
+ * With this index the candidates scan is genuinely bounded by the page size.
15830
+ */
15831
+ var _053_ProjectionTurnsKeysetIndex_default = Effect.gen(function* () {
15832
+ yield* (yield* SqlClient.SqlClient)`
15833
+ CREATE INDEX IF NOT EXISTS idx_projection_turns_thread_keyset
15834
+ ON projection_turns(thread_id, requested_at, turn_id)
15835
+ `;
15836
+ });
15837
+ //#endregion
15777
15838
  //#region src/persistence/Migrations.ts
15778
15839
  /**
15779
15840
  * MigrationsLive - Migration runner with inline loader
@@ -16054,6 +16115,11 @@ const migrationEntries = [
16054
16115
  52,
16055
16116
  "CompactHistoricalToolActivities",
16056
16117
  _052_CompactHistoricalToolActivities_default
16118
+ ],
16119
+ [
16120
+ 53,
16121
+ "ProjectionTurnsKeysetIndex",
16122
+ _053_ProjectionTurnsKeysetIndex_default
16057
16123
  ]
16058
16124
  ];
16059
16125
  const makeMigrationLoader = (throughId) => Migrator.fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -16562,7 +16628,19 @@ function projectActivityPayload(activity) {
16562
16628
  if (item) projectedData.item = item;
16563
16629
  if ("command" in data) projectedData.command = data.command;
16564
16630
  const input = asRecord$6(data.input);
16565
- if (input && "command" in input) projectedData.input = { command: input.command };
16631
+ if (input) {
16632
+ const projectedInput = {};
16633
+ if ("command" in input) projectedInput.command = input.command;
16634
+ if (payload.itemType === "collab_agent_tool_call") {
16635
+ for (const field of [
16636
+ "description",
16637
+ "prompt",
16638
+ "subagent_type",
16639
+ "model"
16640
+ ]) if (field in input) projectedInput[field] = input[field];
16641
+ }
16642
+ if (Object.keys(projectedInput).length > 0) projectedData.input = projectedInput;
16643
+ }
16566
16644
  const changedFiles = [];
16567
16645
  collectChangedFiles(data, changedFiles, /* @__PURE__ */ new Set(), 0);
16568
16646
  if (changedFiles.length > 0) projectedData.files = changedFiles.map((path) => ({ path }));
@@ -30156,6 +30234,37 @@ Schema$1.Struct({
30156
30234
  Schema$1.Struct({ threadId: ThreadId });
30157
30235
  Context.Service()("@p4code/cli/persistence/Services/ProjectionCheckpoints/ProjectionCheckpointRepository");
30158
30236
  //#endregion
30237
+ //#region src/orchestration/threadDetailCursor.ts
30238
+ function encodeThreadDetailPageCursor(cursor) {
30239
+ return Buffer.from(JSON.stringify({
30240
+ t: cursor.threadId,
30241
+ a: cursor.beforeAnchorAt,
30242
+ i: cursor.beforeTurnId
30243
+ })).toString("base64url");
30244
+ }
30245
+ /**
30246
+ * Returns null for anything that is not a well-formed cursor. Callers degrade
30247
+ * a malformed or foreign-thread cursor to a first-page request.
30248
+ */
30249
+ function decodeThreadDetailPageCursor(encoded) {
30250
+ let parsed;
30251
+ try {
30252
+ parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
30253
+ } catch {
30254
+ return null;
30255
+ }
30256
+ if (parsed === null || typeof parsed !== "object") return null;
30257
+ const record = parsed;
30258
+ if (typeof record.t !== "string" || record.t.length === 0) return null;
30259
+ if (typeof record.a !== "string") return null;
30260
+ if (typeof record.i !== "string") return null;
30261
+ return {
30262
+ threadId: record.t,
30263
+ beforeAnchorAt: record.a,
30264
+ beforeTurnId: record.i
30265
+ };
30266
+ }
30267
+ //#endregion
30159
30268
  //#region ../../packages/shared/src/sourceControl.ts
30160
30269
  const GITHUB_CHANGE_REQUEST_PRESENTATION = {
30161
30270
  icon: "github",
@@ -30852,6 +30961,24 @@ const ProjectionThreadSearchRow = Schema$1.Struct({
30852
30961
  const WorkspaceRootLookupInput = Schema$1.Struct({ workspaceRoot: Schema$1.String });
30853
30962
  const ProjectIdLookupInput = Schema$1.Struct({ projectId: ProjectId });
30854
30963
  const ThreadIdLookupInput = Schema$1.Struct({ threadId: ThreadId });
30964
+ const ThreadTurnWindowLookupInput = Schema$1.Struct({
30965
+ threadId: ThreadId,
30966
+ beforeAnchorAt: Schema$1.String,
30967
+ beforeTurnKey: Schema$1.String,
30968
+ userTurnLimit: Schema$1.Number,
30969
+ maxRawTurns: Schema$1.Number
30970
+ });
30971
+ const ProjectionTurnWindowRowSchema = Schema$1.Struct({
30972
+ anchorAt: Schema$1.String,
30973
+ turnKey: Schema$1.String
30974
+ });
30975
+ const ThreadTurnRangeLookupInput = Schema$1.Struct({
30976
+ threadId: ThreadId,
30977
+ minAnchorAt: Schema$1.String,
30978
+ minTurnKey: Schema$1.String,
30979
+ beforeAnchorAt: Schema$1.String,
30980
+ beforeTurnKey: Schema$1.String
30981
+ });
30855
30982
  const ProjectionProjectLookupRowSchema = ProjectionProjectDbRowSchema;
30856
30983
  const ProjectionThreadIdLookupRowSchema = Schema$1.Struct({ threadId: ThreadId });
30857
30984
  const ProjectionThreadCheckpointContextThreadRowSchema = Schema$1.Struct({
@@ -31769,6 +31896,115 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
31769
31896
  LIMIT ${BTW_CONTEXT_MESSAGE_LIMIT}
31770
31897
  ) AS bounded
31771
31898
  ORDER BY created_at ASC, message_id ASC
31899
+ `
31900
+ });
31901
+ const getThreadEventWatermarkRow = SqlSchema.findOneOption({
31902
+ Request: Schema$1.Struct({
31903
+ threadId: ThreadId,
31904
+ maxSequence: Schema$1.Number
31905
+ }),
31906
+ Result: Schema$1.Struct({ threadSequence: Schema$1.NullOr(Schema$1.Number) }),
31907
+ execute: ({ threadId, maxSequence }) => sql`
31908
+ SELECT MAX(sequence) AS "threadSequence"
31909
+ FROM orchestration_events
31910
+ WHERE aggregate_kind = 'thread'
31911
+ AND stream_id = ${threadId}
31912
+ AND sequence <= ${maxSequence}
31913
+ AND event_type IN (
31914
+ 'thread.message-sent',
31915
+ 'thread.proposed-plan-upserted',
31916
+ 'thread.activity-appended',
31917
+ 'thread.turn-diff-completed',
31918
+ 'thread.reverted',
31919
+ 'thread.session-set'
31920
+ )
31921
+ `
31922
+ });
31923
+ const listTurnWindowRows = SqlSchema.findAll({
31924
+ Request: ThreadTurnWindowLookupInput,
31925
+ Result: ProjectionTurnWindowRowSchema,
31926
+ execute: ({ threadId, beforeAnchorAt, beforeTurnKey, userTurnLimit, maxRawTurns }) => sql`
31927
+ WITH candidates AS (
31928
+ SELECT
31929
+ turns.requested_at AS anchor_at,
31930
+ COALESCE(turns.turn_id, '') AS turn_key,
31931
+ turns.pending_message_id
31932
+ FROM projection_turns AS turns
31933
+ WHERE turns.thread_id = ${threadId}
31934
+ AND (
31935
+ turns.requested_at < ${beforeAnchorAt}
31936
+ OR (
31937
+ turns.requested_at = ${beforeAnchorAt}
31938
+ AND COALESCE(turns.turn_id, '') < ${beforeTurnKey}
31939
+ )
31940
+ )
31941
+ ORDER BY turns.requested_at DESC, turns.turn_id DESC
31942
+ LIMIT ${maxRawTurns}
31943
+ ),
31944
+ walked AS (
31945
+ SELECT
31946
+ candidates.anchor_at,
31947
+ candidates.turn_key,
31948
+ CASE WHEN messages.role = 'user' THEN 1 ELSE 0 END AS is_user_turn,
31949
+ SUM(CASE WHEN messages.role = 'user' THEN 1 ELSE 0 END) OVER (
31950
+ ORDER BY candidates.anchor_at DESC, candidates.turn_key DESC
31951
+ ) AS user_turns_seen
31952
+ FROM candidates
31953
+ LEFT JOIN projection_thread_messages AS messages
31954
+ ON messages.message_id = candidates.pending_message_id
31955
+ )
31956
+ SELECT
31957
+ anchor_at AS "anchorAt",
31958
+ turn_key AS "turnKey"
31959
+ FROM walked
31960
+ WHERE user_turns_seen < ${userTurnLimit}
31961
+ OR (user_turns_seen = ${userTurnLimit} AND is_user_turn = 1)
31962
+ ORDER BY anchor_at ASC, turn_key ASC
31963
+ `
31964
+ });
31965
+ const listThreadMessageRowsByThreadWindow = SqlSchema.findAll({
31966
+ Request: ThreadTurnRangeLookupInput,
31967
+ Result: ProjectionThreadMessageDbRowSchema,
31968
+ execute: ({ threadId, minAnchorAt, minTurnKey, beforeAnchorAt, beforeTurnKey }) => sql`
31969
+ SELECT
31970
+ message_id AS "messageId",
31971
+ thread_id AS "threadId",
31972
+ turn_id AS "turnId",
31973
+ role,
31974
+ text,
31975
+ attachments_json AS "attachments",
31976
+ is_streaming AS "isStreaming",
31977
+ created_at AS "createdAt",
31978
+ updated_at AS "updatedAt"
31979
+ FROM projection_thread_messages
31980
+ WHERE thread_id = ${threadId}
31981
+ AND (
31982
+ turn_id IN (
31983
+ SELECT turn_id FROM projection_turns
31984
+ WHERE thread_id = ${threadId}
31985
+ AND turn_id IS NOT NULL
31986
+ AND (
31987
+ requested_at > ${minAnchorAt}
31988
+ OR (
31989
+ requested_at = ${minAnchorAt}
31990
+ AND turn_id >= ${minTurnKey}
31991
+ )
31992
+ )
31993
+ AND (
31994
+ requested_at < ${beforeAnchorAt}
31995
+ OR (
31996
+ requested_at = ${beforeAnchorAt}
31997
+ AND turn_id < ${beforeTurnKey}
31998
+ )
31999
+ )
32000
+ )
32001
+ OR (
32002
+ turn_id IS NULL
32003
+ AND created_at >= ${minAnchorAt}
32004
+ AND created_at < ${beforeAnchorAt}
32005
+ )
32006
+ )
32007
+ ORDER BY created_at ASC, message_id ASC
31772
32008
  `
31773
32009
  });
31774
32010
  const getBtwThreadRow = SqlSchema.findOneOption({
@@ -31789,6 +32025,54 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
31789
32025
  OR projects.deleted_at IS NULL
31790
32026
  )
31791
32027
  LIMIT 1
32028
+ `
32029
+ });
32030
+ const listThreadActivityRowsByThreadWindow = SqlSchema.findAll({
32031
+ Request: ThreadTurnRangeLookupInput,
32032
+ Result: ProjectionThreadActivityDbRowSchema,
32033
+ execute: ({ threadId, minAnchorAt, minTurnKey, beforeAnchorAt, beforeTurnKey }) => sql`
32034
+ SELECT
32035
+ activity_id AS "activityId",
32036
+ thread_id AS "threadId",
32037
+ turn_id AS "turnId",
32038
+ tone,
32039
+ kind,
32040
+ summary,
32041
+ payload_json AS "payload",
32042
+ sequence,
32043
+ created_at AS "createdAt"
32044
+ FROM projection_thread_activities
32045
+ WHERE thread_id = ${threadId}
32046
+ AND (
32047
+ turn_id IN (
32048
+ SELECT turn_id FROM projection_turns
32049
+ WHERE thread_id = ${threadId}
32050
+ AND turn_id IS NOT NULL
32051
+ AND (
32052
+ requested_at > ${minAnchorAt}
32053
+ OR (
32054
+ requested_at = ${minAnchorAt}
32055
+ AND turn_id >= ${minTurnKey}
32056
+ )
32057
+ )
32058
+ AND (
32059
+ requested_at < ${beforeAnchorAt}
32060
+ OR (
32061
+ requested_at = ${beforeAnchorAt}
32062
+ AND turn_id < ${beforeTurnKey}
32063
+ )
32064
+ )
32065
+ )
32066
+ OR (
32067
+ turn_id IS NULL
32068
+ AND created_at >= ${minAnchorAt}
32069
+ AND created_at < ${beforeAnchorAt}
32070
+ )
32071
+ )
32072
+ ORDER BY
32073
+ sequence ASC,
32074
+ created_at ASC,
32075
+ activity_id ASC
31792
32076
  `
31793
32077
  });
31794
32078
  const getFullThreadDiffContextRow = SqlSchema.findOneOption({
@@ -32358,12 +32642,18 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32358
32642
  scheduledWakeAt: threadRow.value.scheduledWakeAt
32359
32643
  });
32360
32644
  });
32361
- const getThreadDetailById = (threadId) => Effect.gen(function* () {
32645
+ const getThreadDetailByIdBounded = (threadId, bounds) => Effect.gen(function* () {
32362
32646
  const [threadRow, messageRows, proposedPlanRows, activityRows, checkpointRows, turnRows, latestTurnRow, sessionRow] = yield* Effect.all([
32363
32647
  getActiveThreadRowById({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getThread:query", "ProjectionSnapshotQuery.getThreadDetailById:getThread:decodeRow"))),
32364
- listThreadMessageRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listMessages:query", "ProjectionSnapshotQuery.getThreadDetailById:listMessages:decodeRows"))),
32648
+ (bounds === void 0 ? listThreadMessageRowsByThread({ threadId }) : listThreadMessageRowsByThreadWindow({
32649
+ threadId,
32650
+ ...bounds
32651
+ })).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listMessages:query", "ProjectionSnapshotQuery.getThreadDetailById:listMessages:decodeRows"))),
32365
32652
  listThreadProposedPlanRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listPlans:query", "ProjectionSnapshotQuery.getThreadDetailById:listPlans:decodeRows"))),
32366
- listThreadActivityRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listActivities:query", "ProjectionSnapshotQuery.getThreadDetailById:listActivities:decodeRows"))),
32653
+ (bounds === void 0 ? listThreadActivityRowsByThread({ threadId }) : listThreadActivityRowsByThreadWindow({
32654
+ threadId,
32655
+ ...bounds
32656
+ })).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listActivities:query", "ProjectionSnapshotQuery.getThreadDetailById:listActivities:decodeRows"))),
32367
32657
  listCheckpointRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listCheckpoints:query", "ProjectionSnapshotQuery.getThreadDetailById:listCheckpoints:decodeRows"))),
32368
32658
  listTurnSummaryRowsByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:listTurnSummaries:query", "ProjectionSnapshotQuery.getThreadDetailById:listTurnSummaries:decodeRows"))),
32369
32659
  getLatestTurnRowByThread({ threadId }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:query", "ProjectionSnapshotQuery.getThreadDetailById:getLatestTurn:decodeRow"))),
@@ -32434,13 +32724,70 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
32434
32724
  };
32435
32725
  return Option.some(yield* decodeThread(thread).pipe(Effect.mapError(toPersistenceDecodeError("ProjectionSnapshotQuery.getThreadDetailById:decodeThread"))));
32436
32726
  });
32437
- const getThreadDetailSnapshot = (threadId) => sql.withTransaction(Effect.gen(function* () {
32438
- const thread = yield* getThreadDetailById(threadId);
32727
+ const getThreadDetailById = (threadId) => getThreadDetailByIdBounded(threadId, void 0);
32728
+ const THREAD_DETAIL_MAX_RAW_TURNS_PER_PAGE = 150;
32729
+ const ANCHOR_UNBOUNDED = "~";
32730
+ const getThreadDetailSnapshot = (threadId, window) => sql.withTransaction(Effect.gen(function* () {
32731
+ if (window?.turnLimit === void 0) {
32732
+ const thread = yield* getThreadDetailById(threadId);
32733
+ if (Option.isNone(thread)) return Option.none();
32734
+ const { snapshotSequence } = yield* getSnapshotSequence();
32735
+ return Option.some({
32736
+ snapshotSequence,
32737
+ thread: thread.value
32738
+ });
32739
+ }
32740
+ const decodedCursor = window.beforeCursor === void 0 ? null : decodeThreadDetailPageCursor(window.beforeCursor);
32741
+ const cursor = decodedCursor?.threadId === threadId ? decodedCursor : null;
32742
+ const oldest = (yield* listTurnWindowRows({
32743
+ threadId,
32744
+ beforeAnchorAt: cursor?.beforeAnchorAt ?? ANCHOR_UNBOUNDED,
32745
+ beforeTurnKey: cursor?.beforeTurnId ?? "",
32746
+ userTurnLimit: window.turnLimit,
32747
+ maxRawTurns: THREAD_DETAIL_MAX_RAW_TURNS_PER_PAGE
32748
+ }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailSnapshot:listTurnWindow:query", "ProjectionSnapshotQuery.getThreadDetailSnapshot:listTurnWindow:decodeRows"))))[0];
32749
+ const bounds = oldest === void 0 && cursor === null ? void 0 : {
32750
+ minAnchorAt: oldest?.anchorAt ?? "",
32751
+ minTurnKey: oldest?.turnKey ?? "",
32752
+ beforeAnchorAt: cursor?.beforeAnchorAt ?? ANCHOR_UNBOUNDED,
32753
+ beforeTurnKey: cursor?.beforeTurnId ?? ""
32754
+ };
32755
+ const thread = yield* getThreadDetailByIdBounded(threadId, (oldest === void 0 && cursor !== null ? {
32756
+ minAnchorAt: "",
32757
+ minTurnKey: "",
32758
+ beforeAnchorAt: "",
32759
+ beforeTurnKey: ""
32760
+ } : void 0) ?? bounds);
32439
32761
  if (Option.isNone(thread)) return Option.none();
32762
+ const hasMore = oldest !== void 0 && (yield* listTurnWindowRows({
32763
+ threadId,
32764
+ beforeAnchorAt: oldest.anchorAt,
32765
+ beforeTurnKey: oldest.turnKey,
32766
+ userTurnLimit: 1,
32767
+ maxRawTurns: 1
32768
+ }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailSnapshot:probeOlder:query", "ProjectionSnapshotQuery.getThreadDetailSnapshot:probeOlder:decodeRows")))).length > 0;
32440
32769
  const { snapshotSequence } = yield* getSnapshotSequence();
32770
+ const watermarkRow = yield* getThreadEventWatermarkRow({
32771
+ threadId,
32772
+ maxSequence: snapshotSequence
32773
+ }).pipe(Effect.mapError(toPersistenceSqlOrDecodeError$1("ProjectionSnapshotQuery.getThreadDetailSnapshot:threadWatermark:query", "ProjectionSnapshotQuery.getThreadDetailSnapshot:threadWatermark:decodeRow")));
32774
+ const threadSequence = Option.match(watermarkRow, {
32775
+ onNone: () => 0,
32776
+ onSome: (row) => row.threadSequence ?? 0
32777
+ });
32441
32778
  return Option.some({
32442
32779
  snapshotSequence,
32443
- thread: thread.value
32780
+ thread: thread.value,
32781
+ page: {
32782
+ beforeCursor: hasMore && oldest !== void 0 ? encodeThreadDetailPageCursor({
32783
+ threadId,
32784
+ beforeAnchorAt: oldest.anchorAt,
32785
+ beforeTurnId: oldest.turnKey
32786
+ }) : null,
32787
+ hasMore,
32788
+ snapshotSequence,
32789
+ threadSequence
32790
+ }
32444
32791
  });
32445
32792
  })).pipe(Effect.mapError((error) => isPersistenceError(error) ? error : toPersistenceSqlError("ProjectionSnapshotQuery.getThreadDetailSnapshot:transaction")(error)));
32446
32793
  return {
@@ -63445,7 +63792,8 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
63445
63792
  },
63446
63793
  settings,
63447
63794
  shellResumeCompletionMarker: true,
63448
- threadResumeCompletionMarker: true
63795
+ threadResumeCompletionMarker: true,
63796
+ threadSnapshotPagination: true
63449
63797
  };
63450
63798
  });
63451
63799
  const refreshGitStatus = (cwd) => vcsStatusBroadcaster.refreshStatus(cwd).pipe(Effect.ignoreCause({ log: true }), Effect.forkDetach, Effect.asVoid);
@@ -63561,7 +63909,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
63561
63909
  const afterCatchUp = input.requestCompletionMarker === true ? Stream.concat(Stream.fromEffect(Queue.offer(liveBuffer, { kind: "synchronized" })).pipe(Stream.drain), bufferedLiveStream) : bufferedLiveStream;
63562
63910
  return Stream.concat(catchUpStream, afterCatchUp);
63563
63911
  }
63564
- const snapshot = yield* projectionSnapshotQuery.getThreadDetailSnapshot(input.threadId).pipe(Effect.mapError((cause) => new OrchestrationGetSnapshotError({
63912
+ const snapshot = yield* projectionSnapshotQuery.getThreadDetailSnapshot(input.threadId, input.turnLimit === void 0 ? void 0 : { turnLimit: input.turnLimit }).pipe(Effect.mapError((cause) => new OrchestrationGetSnapshotError({
63565
63913
  message: `Failed to load thread ${input.threadId}`,
63566
63914
  cause
63567
63915
  })));
@@ -90956,13 +91304,19 @@ const COLLAB_TERMINAL_STATUSES = {
90956
91304
  shutdown: "stopped",
90957
91305
  notFound: "stopped"
90958
91306
  };
90959
- function collabTaskEvents(event, canonicalThreadId, item) {
91307
+ function collabTaskEventBase(base, taskId, phase) {
91308
+ return {
91309
+ ...base,
91310
+ eventId: EventId.make(`${base.eventId}:subagent:${taskId}:${phase}`)
91311
+ };
91312
+ }
91313
+ function collabTaskEvents(event, canonicalThreadId, item, defaults) {
90960
91314
  const base = runtimeEventBase(event, canonicalThreadId);
90961
91315
  if (item.type === "subAgentActivity") {
90962
91316
  const taskId = RuntimeTaskId.make(item.agentThreadId);
90963
91317
  const name = agentNameFromPath(item.agentPath);
90964
91318
  if (item.kind === "started") return [{
90965
- ...base,
91319
+ ...collabTaskEventBase(base, taskId, "started"),
90966
91320
  type: "task.started",
90967
91321
  payload: {
90968
91322
  taskId,
@@ -90973,7 +91327,7 @@ function collabTaskEvents(event, canonicalThreadId, item) {
90973
91327
  }
90974
91328
  }];
90975
91329
  if (item.kind === "interrupted") return [{
90976
- ...base,
91330
+ ...collabTaskEventBase(base, taskId, "completed"),
90977
91331
  type: "task.completed",
90978
91332
  payload: {
90979
91333
  taskId,
@@ -90981,7 +91335,7 @@ function collabTaskEvents(event, canonicalThreadId, item) {
90981
91335
  }
90982
91336
  }];
90983
91337
  return [{
90984
- ...base,
91338
+ ...collabTaskEventBase(base, taskId, "progress"),
90985
91339
  type: "task.progress",
90986
91340
  payload: {
90987
91341
  taskId,
@@ -90992,11 +91346,11 @@ function collabTaskEvents(event, canonicalThreadId, item) {
90992
91346
  if (item.type !== "collabAgentToolCall") return [];
90993
91347
  const events = [];
90994
91348
  const prompt = trimText$1(item.prompt);
90995
- const model = trimText$1(item.model);
90996
- const reasoningEffort = trimText$1(item.reasoningEffort);
91349
+ const model = trimText$1(item.model) ?? trimText$1(defaults?.model);
91350
+ const reasoningEffort = trimText$1(item.reasoningEffort) ?? trimText$1(defaults?.reasoningEffort);
90997
91351
  if (item.tool === "spawnAgent") for (const receiverThreadId of item.receiverThreadIds) {
90998
91352
  events.push({
90999
- ...base,
91353
+ ...collabTaskEventBase(base, RuntimeTaskId.make(receiverThreadId), "started"),
91000
91354
  type: "task.started",
91001
91355
  payload: {
91002
91356
  taskId: RuntimeTaskId.make(receiverThreadId),
@@ -91005,8 +91359,8 @@ function collabTaskEvents(event, canonicalThreadId, item) {
91005
91359
  ...reasoningEffort ? { reasoningEffort } : {}
91006
91360
  }
91007
91361
  });
91008
- if (!(receiverThreadId in item.agentsStates)) events.push({
91009
- ...base,
91362
+ events.push({
91363
+ ...collabTaskEventBase(base, RuntimeTaskId.make(receiverThreadId), "progress"),
91010
91364
  type: "task.progress",
91011
91365
  payload: {
91012
91366
  taskId: RuntimeTaskId.make(receiverThreadId),
@@ -91020,7 +91374,7 @@ function collabTaskEvents(event, canonicalThreadId, item) {
91020
91374
  const terminalStatus = COLLAB_TERMINAL_STATUSES[state.status];
91021
91375
  if (terminalStatus) {
91022
91376
  events.push({
91023
- ...base,
91377
+ ...collabTaskEventBase(base, taskId, "completed"),
91024
91378
  type: "task.completed",
91025
91379
  payload: {
91026
91380
  taskId,
@@ -91030,8 +91384,9 @@ function collabTaskEvents(event, canonicalThreadId, item) {
91030
91384
  });
91031
91385
  continue;
91032
91386
  }
91387
+ if (item.tool === "spawnAgent" && !message) continue;
91033
91388
  events.push({
91034
- ...base,
91389
+ ...collabTaskEventBase(base, taskId, "progress"),
91035
91390
  type: "task.progress",
91036
91391
  payload: {
91037
91392
  taskId,
@@ -91215,7 +91570,7 @@ function mapItemLifecycle(event, canonicalThreadId, lifecycle) {
91215
91570
  }
91216
91571
  };
91217
91572
  }
91218
- function mapToRuntimeEvents(event, canonicalThreadId) {
91573
+ function mapToRuntimeEvents(event, canonicalThreadId, collabDefaults) {
91219
91574
  if (event.kind === "error") {
91220
91575
  if (!event.message) return [];
91221
91576
  return [{
@@ -91409,7 +91764,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
91409
91764
  if (event.method === "item/started") {
91410
91765
  const started = mapItemLifecycle(event, canonicalThreadId, "item.started");
91411
91766
  const item = readPayload(V2ItemStartedNotification, event.payload)?.item;
91412
- const taskEvents = item ? collabTaskEvents(event, canonicalThreadId, item) : [];
91767
+ const taskEvents = item ? collabTaskEvents(event, canonicalThreadId, item, collabDefaults) : [];
91413
91768
  return started ? [started, ...taskEvents] : taskEvents;
91414
91769
  }
91415
91770
  if (event.method === "item/completed") {
@@ -91426,7 +91781,7 @@ function mapToRuntimeEvents(event, canonicalThreadId) {
91426
91781
  }];
91427
91782
  }
91428
91783
  const completed = mapItemLifecycle(event, canonicalThreadId, "item.completed");
91429
- const taskEvents = collabTaskEvents(event, canonicalThreadId, item);
91784
+ const taskEvents = collabTaskEvents(event, canonicalThreadId, item, collabDefaults);
91430
91785
  return completed ? [completed, ...taskEvents] : taskEvents;
91431
91786
  }
91432
91787
  if (event.method === "item/reasoning/summaryPartAdded" || event.method === "item/commandExecution/terminalInteraction") return [{
@@ -91798,9 +92153,13 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
91798
92153
  detail: cause.message,
91799
92154
  cause
91800
92155
  })));
92156
+ const collabDefaults = {
92157
+ model: input.modelSelection?.instanceId === boundInstanceId ? input.modelSelection.model : void 0,
92158
+ reasoningEffort: input.modelSelection?.instanceId === boundInstanceId ? getModelSelectionStringOptionValue(input.modelSelection, "reasoningEffort") : void 0
92159
+ };
91801
92160
  const eventFiber = yield* Stream.runForEach(runtime.events, (event) => Effect.gen(function* () {
91802
92161
  yield* writeNativeEvent(event);
91803
- const runtimeEvents = mapToRuntimeEvents(event, event.threadId);
92162
+ const runtimeEvents = mapToRuntimeEvents(event, event.threadId, collabDefaults);
91804
92163
  if (runtimeEvents.length === 0) {
91805
92164
  yield* Effect.logDebug("ignoring unhandled Codex provider event", {
91806
92165
  method: event.method,
@@ -91823,6 +92182,7 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
91823
92182
  scope: sessionScope,
91824
92183
  runtime,
91825
92184
  eventFiber,
92185
+ collabDefaults,
91826
92186
  stopped: false
91827
92187
  });
91828
92188
  sessionScopeTransferred = true;
@@ -91854,6 +92214,10 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (codexConfig, o
91854
92214
  const session = yield* requireSession(input.threadId);
91855
92215
  const reasoningEffort = input.modelSelection?.instanceId === boundInstanceId ? getModelSelectionStringOptionValue(input.modelSelection, "reasoningEffort") : void 0;
91856
92216
  const serviceTier = input.modelSelection?.instanceId === boundInstanceId ? getCodexServiceTierOptionValue(input.modelSelection) : void 0;
92217
+ if (input.modelSelection?.instanceId === boundInstanceId) {
92218
+ session.collabDefaults.model = input.modelSelection.model;
92219
+ session.collabDefaults.reasoningEffort = reasoningEffort;
92220
+ }
91857
92221
  return yield* session.runtime.sendTurn({
91858
92222
  ...input.input !== void 0 ? { input: input.input } : {},
91859
92223
  ...input.modelSelection?.instanceId === boundInstanceId ? { model: input.modelSelection.model } : {},
@@ -109768,7 +110132,10 @@ const orchestrationHttpApiLayer = HttpApiBuilder.group(EnvironmentHttpApi, "orch
109768
110132
  })).handle("threadSnapshot", Effect.fn("environment.orchestration.threadSnapshot")(function* (args) {
109769
110133
  yield* annotateEnvironmentRequest(args.endpoint.name);
109770
110134
  yield* requireEnvironmentScope(AuthOrchestrationReadScope);
109771
- const snapshot = yield* projectionSnapshotQuery.getThreadDetailSnapshot(args.params.threadId).pipe(Effect.catch((cause) => failEnvironmentInternal("orchestration_thread_snapshot_failed", cause)));
110135
+ const snapshot = yield* projectionSnapshotQuery.getThreadDetailSnapshot(args.params.threadId, args.payload.turnLimit === void 0 ? void 0 : {
110136
+ turnLimit: args.payload.turnLimit,
110137
+ ...args.payload.beforeCursor !== void 0 ? { beforeCursor: args.payload.beforeCursor } : {}
110138
+ }).pipe(Effect.catch((cause) => failEnvironmentInternal("orchestration_thread_snapshot_failed", cause)));
109772
110139
  if (Option.isNone(snapshot)) return yield* failEnvironmentNotFound("thread_not_found");
109773
110140
  return projectThreadDetailSnapshot(snapshot.value);
109774
110141
  })).handle("dispatch", Effect.fn("environment.orchestration.dispatch")(function* (args) {