@getpaseo/server 0.1.87 → 0.1.88

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.
@@ -176,17 +176,28 @@ function normalizeScheduleCadenceArg(value) {
176
176
  }
177
177
  return trimmed;
178
178
  }
179
+ function normalizeScheduleTimeZoneArg(value) {
180
+ return normalizeScheduleCadenceArg(value);
181
+ }
179
182
  function resolveScheduleUpdateCadence(input) {
180
183
  const every = normalizeScheduleCadenceArg(input.every);
181
184
  const cron = normalizeScheduleCadenceArg(input.cron);
185
+ const timeZone = normalizeScheduleTimeZoneArg(input.timezone);
182
186
  if (every !== undefined && cron !== undefined) {
183
187
  throw new Error("Specify at most one of every or cron");
184
188
  }
189
+ if (timeZone !== undefined && cron === undefined) {
190
+ throw new Error("timezone can only be used with cron");
191
+ }
185
192
  if (every !== undefined) {
186
193
  return { type: "every", everyMs: parseDurationString(every) };
187
194
  }
188
195
  if (cron !== undefined) {
189
- return { type: "cron", expression: cron };
196
+ return {
197
+ type: "cron",
198
+ expression: cron,
199
+ ...(timeZone !== undefined ? { timezone: timeZone } : {}),
200
+ };
190
201
  }
191
202
  return undefined;
192
203
  }
@@ -1162,6 +1173,12 @@ export async function createAgentMcpServer(options) {
1162
1173
  prompt: z.string().trim().min(1, "prompt is required"),
1163
1174
  every: z.string().optional(),
1164
1175
  cron: z.string().optional(),
1176
+ timezone: z
1177
+ .string()
1178
+ .trim()
1179
+ .min(1)
1180
+ .optional()
1181
+ .describe("IANA time zone for cron cadence; requires cron. For example: America/New_York."),
1165
1182
  name: z.string().optional(),
1166
1183
  target: z.enum(["self", "new-agent"]).optional(),
1167
1184
  provider: AgentProviderEnum.optional().describe("Provider, or provider/model (for example: codex or codex/gpt-5.4)."),
@@ -1170,16 +1187,20 @@ export async function createAgentMcpServer(options) {
1170
1187
  expiresIn: z.string().optional(),
1171
1188
  },
1172
1189
  outputSchema: ScheduleSummarySchema.shape,
1173
- }, async ({ prompt, every, cron, name, target, provider, cwd, maxRuns, expiresIn }) => {
1190
+ }, async ({ prompt, every, cron, timezone, name, target, provider, cwd, maxRuns, expiresIn }) => {
1174
1191
  if (!scheduleService) {
1175
1192
  throw new Error("Schedule service is not configured");
1176
1193
  }
1177
1194
  const normalizedEvery = normalizeScheduleCadenceArg(every);
1178
1195
  const normalizedCron = normalizeScheduleCadenceArg(cron);
1196
+ const normalizedTimeZone = normalizeScheduleTimeZoneArg(timezone);
1179
1197
  const cadenceCount = Number(normalizedEvery !== undefined) + Number(normalizedCron !== undefined);
1180
1198
  if (cadenceCount !== 1) {
1181
1199
  throw new Error("Specify exactly one of every or cron");
1182
1200
  }
1201
+ if (normalizedTimeZone !== undefined && normalizedCron === undefined) {
1202
+ throw new Error("timezone can only be used with cron");
1203
+ }
1183
1204
  const scheduleTarget = target === "self"
1184
1205
  ? (() => {
1185
1206
  const callerAgent = resolveCallerAgent();
@@ -1209,7 +1230,11 @@ export async function createAgentMcpServer(options) {
1209
1230
  prompt: prompt.trim(),
1210
1231
  cadence: normalizedEvery !== undefined
1211
1232
  ? { type: "every", everyMs: parseDurationString(normalizedEvery) }
1212
- : { type: "cron", expression: normalizedCron },
1233
+ : {
1234
+ type: "cron",
1235
+ expression: normalizedCron,
1236
+ ...(normalizedTimeZone !== undefined ? { timezone: normalizedTimeZone } : {}),
1237
+ },
1213
1238
  target: scheduleTarget,
1214
1239
  ...(name?.trim() ? { name: name.trim() } : {}),
1215
1240
  ...(maxRuns === undefined ? {} : { maxRuns }),
@@ -1320,6 +1345,12 @@ export async function createAgentMcpServer(options) {
1320
1345
  id: z.string(),
1321
1346
  every: z.string().optional().describe("New interval duration string (e.g. 5m, 1h)."),
1322
1347
  cron: z.string().optional().describe("New cron expression."),
1348
+ timezone: z
1349
+ .string()
1350
+ .trim()
1351
+ .min(1)
1352
+ .optional()
1353
+ .describe("IANA time zone for cron cadence; requires cron. For example: America/New_York."),
1323
1354
  name: z.string().nullable().optional().describe("New name (null to clear)."),
1324
1355
  prompt: z.string().trim().min(1).optional().describe("New prompt text."),
1325
1356
  maxRuns: z
@@ -282,6 +282,7 @@ export declare function toScheduleSummary(schedule: z.infer<typeof StoredSchedul
282
282
  } | {
283
283
  type: "cron";
284
284
  expression: string;
285
+ timezone?: string | undefined;
285
286
  };
286
287
  target: {
287
288
  agentId: string;
@@ -21,6 +21,13 @@ interface ProjectedWindowSelection {
21
21
  minSeq: number | null;
22
22
  maxSeq: number | null;
23
23
  }
24
+ export interface ProjectedTimelinePageSelection {
25
+ entries: TimelineProjectionEntry[];
26
+ startSeq: number | null;
27
+ endSeq: number | null;
28
+ hasOlder: boolean;
29
+ hasNewer: boolean;
30
+ }
24
31
  export declare function projectTimelineRows(input: {
25
32
  rows: readonly AgentTimelineRow[];
26
33
  mode: TimelineProjectionMode;
@@ -34,8 +41,17 @@ export declare function selectTimelineWindowByProjectedLimit(input: {
34
41
  rows: readonly AgentTimelineRow[];
35
42
  direction: TimelineLimitDirection;
36
43
  limit: number;
37
- collapseToolLifecycle?: boolean;
38
44
  }): ProjectedWindowSelection;
45
+ export declare function selectProjectedTimelinePage(input: {
46
+ rows: readonly AgentTimelineRow[];
47
+ bounds?: {
48
+ minSeq: number;
49
+ maxSeq: number;
50
+ };
51
+ direction: TimelineLimitDirection;
52
+ cursorSeq?: number;
53
+ limit?: number;
54
+ }): ProjectedTimelinePageSelection;
39
55
  /**
40
56
  * Apply a projected-count limit to a flat AgentTimelineItem[] without seq metadata.
41
57
  * Used by callers that only have items in hand (e.g. MCP tools reading
@@ -188,9 +188,8 @@ export function projectTimelineRows(input) {
188
188
  export function selectTimelineWindowByProjectedLimit(input) {
189
189
  const { rows, direction } = input;
190
190
  const limit = Math.max(0, Math.floor(input.limit));
191
- const collapseTools = input.collapseToolLifecycle ?? true;
192
191
  const canonical = makeCanonicalEntries(rows);
193
- const projectedAll = mergeReasoningChunks(mergeAssistantChunks(collapseTools ? collapseToolLifecycle(canonical) : canonical));
192
+ const projectedAll = mergeReasoningChunks(mergeAssistantChunks(collapseToolLifecycle(canonical)));
194
193
  if (projectedAll.length === 0) {
195
194
  return {
196
195
  projectedEntries: [],
@@ -232,23 +231,21 @@ export function selectTimelineWindowByProjectedLimit(input) {
232
231
  };
233
232
  let { minSeq, maxSeq } = computeWindowBounds(projectedEntries);
234
233
  let expandedEntries = projectedEntries;
235
- if (collapseTools) {
236
- // Expand to include any projected entries that overlap the selected
237
- // canonical range. Tool lifecycle collapse can produce non-monotonic
238
- // seqEnd values, which would otherwise create cursor gaps.
239
- for (let iteration = 0; iteration < projectedAll.length + 1; iteration += 1) {
240
- const overlapping = projectedAll.filter((entry) => entry.seqStart <= maxSeq && entry.seqEnd >= minSeq);
241
- const nextBounds = computeWindowBounds(overlapping);
242
- if (overlapping.length === expandedEntries.length &&
243
- nextBounds.minSeq === minSeq &&
244
- nextBounds.maxSeq === maxSeq) {
245
- expandedEntries = overlapping;
246
- break;
247
- }
234
+ // Expand to include any projected entries that overlap the selected canonical
235
+ // range. Tool lifecycle collapse can produce non-monotonic seqEnd values,
236
+ // which would otherwise create cursor gaps.
237
+ for (let iteration = 0; iteration < projectedAll.length + 1; iteration += 1) {
238
+ const overlapping = projectedAll.filter((entry) => entry.seqStart <= maxSeq && entry.seqEnd >= minSeq);
239
+ const nextBounds = computeWindowBounds(overlapping);
240
+ if (overlapping.length === expandedEntries.length &&
241
+ nextBounds.minSeq === minSeq &&
242
+ nextBounds.maxSeq === maxSeq) {
248
243
  expandedEntries = overlapping;
249
- minSeq = nextBounds.minSeq;
250
- maxSeq = nextBounds.maxSeq;
244
+ break;
251
245
  }
246
+ expandedEntries = overlapping;
247
+ minSeq = nextBounds.minSeq;
248
+ maxSeq = nextBounds.maxSeq;
252
249
  }
253
250
  const selectedRows = rows.filter((row) => row.seq >= minSeq && row.seq <= maxSeq);
254
251
  return {
@@ -258,6 +255,74 @@ export function selectTimelineWindowByProjectedLimit(input) {
258
255
  maxSeq: Number.isFinite(maxSeq) ? maxSeq : null,
259
256
  };
260
257
  }
258
+ function getTimelineBounds(rows) {
259
+ const first = rows[0];
260
+ const last = rows[rows.length - 1];
261
+ if (!first || !last) {
262
+ return null;
263
+ }
264
+ return { minSeq: first.seq, maxSeq: last.seq };
265
+ }
266
+ function selectEntriesOverlappingSeqRange(input) {
267
+ return input.entries.filter((entry) => entry.seqStart <= input.endSeq && entry.seqEnd >= input.startSeq);
268
+ }
269
+ export function selectProjectedTimelinePage(input) {
270
+ const limit = input.limit === undefined ? 0 : Math.max(0, Math.floor(input.limit));
271
+ const bounds = input.bounds ?? getTimelineBounds(input.rows);
272
+ const projectedAll = projectTimelineRows({ rows: input.rows, mode: "projected" });
273
+ if (projectedAll.length === 0 || !bounds) {
274
+ return {
275
+ entries: [],
276
+ startSeq: null,
277
+ endSeq: null,
278
+ hasOlder: false,
279
+ hasNewer: false,
280
+ };
281
+ }
282
+ if (input.direction === "tail") {
283
+ const selected = selectTimelineWindowByProjectedLimit({
284
+ rows: input.rows,
285
+ direction: "tail",
286
+ limit,
287
+ });
288
+ return {
289
+ entries: selected.projectedEntries,
290
+ startSeq: selected.minSeq,
291
+ endSeq: selected.maxSeq,
292
+ hasOlder: selected.minSeq !== null && selected.minSeq > bounds.minSeq,
293
+ hasNewer: false,
294
+ };
295
+ }
296
+ let startSeq;
297
+ let endSeq;
298
+ if (input.direction === "after") {
299
+ const cursorSeq = input.cursorSeq ?? bounds.minSeq - 1;
300
+ startSeq = Math.max(bounds.minSeq, cursorSeq + 1);
301
+ endSeq = limit === 0 ? bounds.maxSeq : Math.min(bounds.maxSeq, cursorSeq + limit);
302
+ }
303
+ else {
304
+ const cursorSeq = input.cursorSeq ?? bounds.maxSeq + 1;
305
+ endSeq = Math.min(bounds.maxSeq, cursorSeq - 1);
306
+ startSeq = limit === 0 ? bounds.minSeq : Math.max(bounds.minSeq, cursorSeq - limit);
307
+ }
308
+ if (startSeq > endSeq) {
309
+ return {
310
+ entries: [],
311
+ startSeq: null,
312
+ endSeq: null,
313
+ hasOlder: startSeq > bounds.minSeq,
314
+ hasNewer: endSeq < bounds.maxSeq,
315
+ };
316
+ }
317
+ const entries = selectEntriesOverlappingSeqRange({ entries: projectedAll, startSeq, endSeq });
318
+ return {
319
+ entries,
320
+ startSeq,
321
+ endSeq,
322
+ hasOlder: startSeq > bounds.minSeq,
323
+ hasNewer: endSeq < bounds.maxSeq,
324
+ };
325
+ }
261
326
  export function selectItemsByProjectedLimit(input) {
262
327
  const rows = input.items.map((item, index) => ({
263
328
  seq: index + 1,
@@ -199,7 +199,7 @@ export declare const PersistedConfigSchema: z.ZodObject<{
199
199
  label: z.ZodString;
200
200
  description: z.ZodOptional<z.ZodString>;
201
201
  isDefault: z.ZodOptional<z.ZodBoolean>;
202
- }, "strict", z.ZodTypeAny, {
202
+ }, "strip", z.ZodTypeAny, {
203
203
  id: string;
204
204
  label: string;
205
205
  description?: string | undefined;
@@ -210,7 +210,7 @@ export declare const PersistedConfigSchema: z.ZodObject<{
210
210
  description?: string | undefined;
211
211
  isDefault?: boolean | undefined;
212
212
  }>, "many">>;
213
- }, "strict", z.ZodTypeAny, {
213
+ }, "strip", z.ZodTypeAny, {
214
214
  id: string;
215
215
  label: string;
216
216
  description?: string | undefined;
@@ -243,7 +243,7 @@ export declare const PersistedConfigSchema: z.ZodObject<{
243
243
  label: z.ZodString;
244
244
  description: z.ZodOptional<z.ZodString>;
245
245
  isDefault: z.ZodOptional<z.ZodBoolean>;
246
- }, "strict", z.ZodTypeAny, {
246
+ }, "strip", z.ZodTypeAny, {
247
247
  id: string;
248
248
  label: string;
249
249
  description?: string | undefined;
@@ -254,7 +254,7 @@ export declare const PersistedConfigSchema: z.ZodObject<{
254
254
  description?: string | undefined;
255
255
  isDefault?: boolean | undefined;
256
256
  }>, "many">>;
257
- }, "strict", z.ZodTypeAny, {
257
+ }, "strip", z.ZodTypeAny, {
258
258
  id: string;
259
259
  label: string;
260
260
  description?: string | undefined;
@@ -280,7 +280,7 @@ export declare const PersistedConfigSchema: z.ZodObject<{
280
280
  disallowedTools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
281
281
  enabled: z.ZodOptional<z.ZodBoolean>;
282
282
  order: z.ZodOptional<z.ZodNumber>;
283
- }, "strict", z.ZodTypeAny, {
283
+ }, "strip", z.ZodTypeAny, {
284
284
  description?: string | undefined;
285
285
  label?: string | undefined;
286
286
  enabled?: boolean | undefined;
@@ -71,9 +71,59 @@ function parseCronExpression(expression) {
71
71
  function startOfNextMinute(date) {
72
72
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes() + 1, 0, 0));
73
73
  }
74
+ function assertValidTimeZone(timeZone) {
75
+ try {
76
+ new Intl.DateTimeFormat("en-US", { timeZone }).format(new Date(0));
77
+ }
78
+ catch {
79
+ throw new Error(`Invalid cron time zone: ${timeZone}`);
80
+ }
81
+ }
82
+ function createCronDatePartsReader(timeZone) {
83
+ if (timeZone === undefined) {
84
+ return (date) => ({
85
+ minute: date.getUTCMinutes(),
86
+ hour: date.getUTCHours(),
87
+ dayOfMonth: date.getUTCDate(),
88
+ month: date.getUTCMonth() + 1,
89
+ dayOfWeek: date.getUTCDay(),
90
+ });
91
+ }
92
+ assertValidTimeZone(timeZone);
93
+ const formatter = new Intl.DateTimeFormat("en-US", {
94
+ timeZone,
95
+ hourCycle: "h23",
96
+ year: "numeric",
97
+ month: "2-digit",
98
+ day: "2-digit",
99
+ hour: "2-digit",
100
+ minute: "2-digit",
101
+ });
102
+ return (date) => {
103
+ const values = {};
104
+ for (const part of formatter.formatToParts(date)) {
105
+ if (part.type !== "literal") {
106
+ values[part.type] = part.value;
107
+ }
108
+ }
109
+ const year = Number.parseInt(values.year, 10);
110
+ const month = Number.parseInt(values.month, 10);
111
+ const dayOfMonth = Number.parseInt(values.day, 10);
112
+ return {
113
+ minute: Number.parseInt(values.minute, 10),
114
+ hour: Number.parseInt(values.hour, 10),
115
+ dayOfMonth,
116
+ month,
117
+ dayOfWeek: new Date(Date.UTC(year, month - 1, dayOfMonth)).getUTCDay(),
118
+ };
119
+ };
120
+ }
74
121
  export function validateScheduleCadence(cadence) {
75
122
  if (cadence.type === "cron") {
76
123
  parseCronExpression(cadence.expression);
124
+ if (cadence.timezone !== undefined) {
125
+ assertValidTimeZone(cadence.timezone);
126
+ }
77
127
  }
78
128
  }
79
129
  export function computeNextRunAt(cadence, after) {
@@ -81,14 +131,11 @@ export function computeNextRunAt(cadence, after) {
81
131
  return new Date(after.getTime() + cadence.everyMs);
82
132
  }
83
133
  const cron = parseCronExpression(cadence.expression);
134
+ const readDateParts = createCronDatePartsReader(cadence.timezone);
84
135
  const limit = 366 * 24 * 60;
85
136
  let cursor = startOfNextMinute(after);
86
137
  for (let index = 0; index < limit; index += 1) {
87
- const minute = cursor.getUTCMinutes();
88
- const hour = cursor.getUTCHours();
89
- const dayOfMonth = cursor.getUTCDate();
90
- const month = cursor.getUTCMonth() + 1;
91
- const dayOfWeek = cursor.getUTCDay();
138
+ const { minute, hour, dayOfMonth, month, dayOfWeek } = readDateParts(cursor);
92
139
  if (cron.minute.matches(minute) &&
93
140
  cron.hour.matches(hour) &&
94
141
  cron.dayOfMonth.matches(dayOfMonth) &&
@@ -489,7 +489,10 @@ export declare class Session {
489
489
  private handleWorkspaceSetupStatusRequest;
490
490
  private handleArchiveWorkspaceRequest;
491
491
  private handleFetchAgent;
492
- private loadProjectedTimelineWindow;
492
+ private shouldUseFullTimelineForProjectedPage;
493
+ private selectCanonicalTimelineProjection;
494
+ private selectProjectedTimelineProjection;
495
+ private selectTimelineProjection;
493
496
  private handleFetchAgentTimelineRequest;
494
497
  private handleSendAgentMessageRequest;
495
498
  private handleWaitForFinish;
@@ -38,7 +38,7 @@ import { createAgentCommand } from "./agent/create-agent/create.js";
38
38
  import { archiveAgentCommand, cancelAgentRunCommand, closeAgentCommand, setAgentModeCommand, updateAgentCommand, } from "./agent/lifecycle-command.js";
39
39
  import { buildStoredAgentPayload, resolveEffectiveThinkingOptionId, resolveStoredAgentPayloadUpdatedAt, toAgentPayload, } from "./agent/agent-projections.js";
40
40
  import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
41
- import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from "./agent/timeline-projection.js";
41
+ import { projectTimelineRows, selectProjectedTimelinePage, } from "./agent/timeline-projection.js";
42
42
  import { StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from "./agent/agent-response-loop.js";
43
43
  import { resolveStructuredGenerationProviders, } from "./agent/structured-generation-providers.js";
44
44
  import { getAgentStreamEventTurnId, } from "./agent/agent-sdk-types.js";
@@ -370,6 +370,7 @@ export class Session {
370
370
  hasBinaryChannel: () => this.onBinaryMessage !== null,
371
371
  isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
372
372
  sessionLogger: this.sessionLogger,
373
+ clientSupportsWrapReflow: () => this.clientCapabilities.has(CLIENT_CAPS.terminalReflowableSnapshot),
373
374
  });
374
375
  this.createAgentLifecycleDispatch = new CreateAgentLifecycleDispatch({
375
376
  paseoHome: this.paseoHome,
@@ -5652,62 +5653,63 @@ export class Session {
5652
5653
  payload: { requestId, agent, project, error: null },
5653
5654
  });
5654
5655
  }
5655
- loadProjectedTimelineWindow(params) {
5656
- const { agentId, direction, cursor, requestedLimit } = params;
5657
- let timeline = params.timeline;
5658
- const projectedLimit = Math.max(1, Math.floor(requestedLimit));
5659
- let fetchLimit = projectedLimit;
5660
- let projectedWindow = selectTimelineWindowByProjectedLimit({
5656
+ shouldUseFullTimelineForProjectedPage(input) {
5657
+ const { timeline } = input;
5658
+ if (timeline.reset || timeline.rows.length === 0 || !timeline.hasOlder) {
5659
+ return false;
5660
+ }
5661
+ const firstRow = timeline.rows[0];
5662
+ if (firstRow?.item.type === "assistant_message" ||
5663
+ firstRow?.item.type === "reasoning" ||
5664
+ firstRow?.item.type === "tool_call") {
5665
+ return true;
5666
+ }
5667
+ return timeline.rows.some((row) => row.item.type === "tool_call");
5668
+ }
5669
+ selectCanonicalTimelineProjection(input) {
5670
+ const entries = projectTimelineRows({ rows: input.timeline.rows, mode: "canonical" });
5671
+ return {
5672
+ timeline: input.timeline,
5673
+ entries,
5674
+ startSeq: entries[0]?.seqStart ?? null,
5675
+ endSeq: entries[entries.length - 1]?.seqEnd ?? null,
5676
+ hasOlder: input.timeline.hasOlder,
5677
+ hasNewer: input.timeline.hasNewer,
5678
+ };
5679
+ }
5680
+ selectProjectedTimelineProjection(input) {
5681
+ const timeline = this.shouldUseFullTimelineForProjectedPage({
5682
+ timeline: input.controlTimeline,
5683
+ })
5684
+ ? this.agentManager.fetchTimeline(input.agentId, { direction: "tail", limit: 0 })
5685
+ : input.controlTimeline;
5686
+ const page = selectProjectedTimelinePage({
5661
5687
  rows: timeline.rows,
5662
- direction,
5663
- limit: projectedLimit,
5664
- collapseToolLifecycle: false,
5688
+ bounds: timeline.window,
5689
+ direction: input.controlTimeline.reset ? "tail" : input.direction,
5690
+ ...(input.cursor ? { cursorSeq: input.cursor.seq } : {}),
5691
+ limit: input.pageLimit,
5665
5692
  });
5666
- while (timeline.hasOlder) {
5667
- const needsMoreProjectedEntries = projectedWindow.projectedEntries.length < projectedLimit;
5668
- const firstLoadedRow = timeline.rows[0];
5669
- const firstSelectedRow = projectedWindow.selectedRows[0];
5670
- const startsAtLoadedBoundary = firstLoadedRow != null &&
5671
- firstSelectedRow != null &&
5672
- firstSelectedRow.seq === firstLoadedRow.seq;
5673
- const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
5674
- if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
5675
- break;
5676
- }
5677
- const maxRows = Math.max(0, timeline.window.maxSeq - timeline.window.minSeq + 1);
5678
- const nextFetchLimit = Math.min(maxRows, fetchLimit * 2);
5679
- if (nextFetchLimit <= fetchLimit) {
5680
- break;
5681
- }
5682
- fetchLimit = nextFetchLimit;
5683
- timeline = this.agentManager.fetchTimeline(agentId, {
5684
- direction,
5685
- cursor,
5686
- limit: fetchLimit,
5687
- });
5688
- projectedWindow = selectTimelineWindowByProjectedLimit({
5689
- rows: timeline.rows,
5690
- direction,
5691
- limit: projectedLimit,
5692
- collapseToolLifecycle: false,
5693
- });
5694
- }
5695
5693
  return {
5696
5694
  timeline,
5697
- selectedRows: projectedWindow.selectedRows,
5698
- minSeq: projectedWindow.minSeq,
5699
- maxSeq: projectedWindow.maxSeq,
5695
+ entries: page.entries,
5696
+ startSeq: page.startSeq,
5697
+ endSeq: page.endSeq,
5698
+ hasOlder: page.hasOlder || (page.startSeq !== null && page.startSeq > timeline.window.minSeq),
5699
+ hasNewer: page.hasNewer,
5700
5700
  };
5701
5701
  }
5702
+ selectTimelineProjection(input) {
5703
+ if (input.projection === "canonical") {
5704
+ return this.selectCanonicalTimelineProjection({ timeline: input.controlTimeline });
5705
+ }
5706
+ return this.selectProjectedTimelineProjection(input);
5707
+ }
5702
5708
  async handleFetchAgentTimelineRequest(msg) {
5703
5709
  const direction = msg.direction ?? (msg.cursor ? "after" : "tail");
5704
5710
  const projection = msg.projection ?? "projected";
5705
5711
  const requestedLimit = msg.limit;
5706
- const limit = requestedLimit ?? (direction === "after" ? 0 : undefined);
5707
- const shouldLimitByProjectedWindow = projection === "canonical" &&
5708
- direction === "tail" &&
5709
- typeof requestedLimit === "number" &&
5710
- requestedLimit > 0;
5712
+ const pageLimit = requestedLimit ?? (direction === "after" ? 0 : 200);
5711
5713
  const cursor = msg.cursor
5712
5714
  ? {
5713
5715
  epoch: msg.cursor.epoch,
@@ -5721,42 +5723,25 @@ export class Session {
5721
5723
  logger: this.sessionLogger,
5722
5724
  });
5723
5725
  const agentPayload = await this.buildAgentPayload(snapshot);
5724
- let timeline = this.agentManager.fetchTimeline(msg.agentId, {
5726
+ const controlTimeline = this.agentManager.fetchTimeline(msg.agentId, {
5725
5727
  direction,
5726
5728
  cursor,
5727
- limit: shouldLimitByProjectedWindow && typeof requestedLimit === "number"
5728
- ? Math.max(1, Math.floor(requestedLimit))
5729
- : limit,
5730
- });
5731
- let hasOlder = timeline.hasOlder;
5732
- let hasNewer = timeline.hasNewer;
5733
- let startCursor = null;
5734
- let endCursor = null;
5735
- let entries;
5736
- if (shouldLimitByProjectedWindow) {
5737
- const projectedResult = this.loadProjectedTimelineWindow({
5738
- agentId: msg.agentId,
5739
- direction,
5740
- cursor,
5741
- requestedLimit,
5742
- timeline,
5743
- });
5744
- timeline = projectedResult.timeline;
5745
- entries = projectTimelineRows({ rows: projectedResult.selectedRows, mode: projection });
5746
- if (projectedResult.minSeq !== null && projectedResult.maxSeq !== null) {
5747
- startCursor = { epoch: timeline.epoch, seq: projectedResult.minSeq };
5748
- endCursor = { epoch: timeline.epoch, seq: projectedResult.maxSeq };
5749
- hasOlder = projectedResult.minSeq > timeline.window.minSeq;
5750
- hasNewer = false;
5751
- }
5752
- }
5753
- else {
5754
- const firstRow = timeline.rows[0];
5755
- const lastRow = timeline.rows[timeline.rows.length - 1];
5756
- startCursor = firstRow ? { epoch: timeline.epoch, seq: firstRow.seq } : null;
5757
- endCursor = lastRow ? { epoch: timeline.epoch, seq: lastRow.seq } : null;
5758
- entries = projectTimelineRows({ rows: timeline.rows, mode: projection });
5759
- }
5729
+ limit: pageLimit,
5730
+ });
5731
+ const selectedTimeline = this.selectTimelineProjection({
5732
+ agentId: msg.agentId,
5733
+ projection,
5734
+ controlTimeline,
5735
+ direction,
5736
+ ...(cursor ? { cursor } : {}),
5737
+ pageLimit,
5738
+ });
5739
+ const startCursor = selectedTimeline.startSeq !== null
5740
+ ? { epoch: selectedTimeline.timeline.epoch, seq: selectedTimeline.startSeq }
5741
+ : null;
5742
+ const endCursor = selectedTimeline.endSeq !== null
5743
+ ? { epoch: selectedTimeline.timeline.epoch, seq: selectedTimeline.endSeq }
5744
+ : null;
5760
5745
  this.emit({
5761
5746
  type: "fetch_agent_timeline_response",
5762
5747
  payload: {
@@ -5765,16 +5750,16 @@ export class Session {
5765
5750
  agent: agentPayload,
5766
5751
  direction,
5767
5752
  projection,
5768
- epoch: timeline.epoch,
5769
- reset: timeline.reset,
5770
- staleCursor: timeline.staleCursor,
5771
- gap: timeline.gap,
5772
- window: timeline.window,
5753
+ epoch: selectedTimeline.timeline.epoch,
5754
+ reset: controlTimeline.reset,
5755
+ staleCursor: controlTimeline.staleCursor,
5756
+ gap: controlTimeline.gap,
5757
+ window: selectedTimeline.timeline.window,
5773
5758
  startCursor,
5774
5759
  endCursor,
5775
- hasOlder,
5776
- hasNewer,
5777
- entries: entries.map((entry) => ({
5760
+ hasOlder: selectedTimeline.hasOlder,
5761
+ hasNewer: selectedTimeline.hasNewer,
5762
+ entries: selectedTimeline.entries.map((entry) => ({
5778
5763
  provider: snapshot.provider,
5779
5764
  item: entry.item,
5780
5765
  timestamp: entry.timestamp,
@@ -1,6 +1,7 @@
1
1
  import { createTerminal, } from "./terminal.js";
2
2
  import { captureTerminalLines } from "./terminal-capture.js";
3
3
  import { resolve, sep, win32, posix } from "node:path";
4
+ import { isSameOrDescendantPath } from "../server/path-utils.js";
4
5
  export function createTerminalManager() {
5
6
  const terminalsByCwd = new Map();
6
7
  const terminalsById = new Map();
@@ -99,7 +100,16 @@ export function createTerminalManager() {
99
100
  return {
100
101
  async getTerminals(cwd) {
101
102
  assertAbsolutePath(cwd);
102
- return terminalsByCwd.get(cwd) ?? [];
103
+ // Terminals are bucketed by exact cwd, but an agent can open a terminal in
104
+ // a subdirectory of the workspace. A query for the workspace root must
105
+ // surface those too, so aggregate every bucket at or below `cwd`.
106
+ const sessions = [];
107
+ for (const [bucketCwd, bucketSessions] of terminalsByCwd) {
108
+ if (isSameOrDescendantPath(cwd, bucketCwd)) {
109
+ sessions.push(...bucketSessions);
110
+ }
111
+ }
112
+ return sessions;
103
113
  },
104
114
  async createTerminal(options) {
105
115
  assertAbsolutePath(options.cwd);
@@ -9,6 +9,7 @@ export interface TerminalSessionControllerOptions {
9
9
  hasBinaryChannel: () => boolean;
10
10
  isPathWithinRoot: (rootPath: string, candidatePath: string) => boolean;
11
11
  sessionLogger: pino.Logger;
12
+ clientSupportsWrapReflow?: () => boolean;
12
13
  }
13
14
  export interface TerminalSessionControllerMetrics {
14
15
  directorySubscriptionCount: number;
@@ -21,6 +22,7 @@ export declare class TerminalSessionController {
21
22
  private readonly hasBinaryChannel;
22
23
  private readonly isPathWithinRoot;
23
24
  private readonly sessionLogger;
25
+ private readonly clientSupportsWrapReflow;
24
26
  private readonly subscribedDirectories;
25
27
  private unsubscribeTerminalsChanged;
26
28
  private readonly exitSubscriptions;
@@ -45,7 +47,7 @@ export declare class TerminalSessionController {
45
47
  private handleTerminalsChanged;
46
48
  private handleSubscribeTerminalsRequest;
47
49
  private handleUnsubscribeTerminalsRequest;
48
- private emitInitialTerminalsChangedSnapshot;
50
+ private emitTerminalsSnapshotForRoot;
49
51
  private handleListTerminalsRequest;
50
52
  private getAllTerminalSessions;
51
53
  private handleCreateTerminalRequest;
@@ -29,12 +29,15 @@ export class TerminalSessionController {
29
29
  this.hasBinaryChannel = options.hasBinaryChannel;
30
30
  this.isPathWithinRoot = options.isPathWithinRoot;
31
31
  this.sessionLogger = options.sessionLogger;
32
+ this.clientSupportsWrapReflow = options.clientSupportsWrapReflow ?? (() => false);
32
33
  }
33
34
  start() {
34
35
  if (!this.terminalManager) {
35
36
  return;
36
37
  }
37
- this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
38
+ this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => {
39
+ void this.handleTerminalsChanged(event);
40
+ });
38
41
  }
39
42
  getMetrics() {
40
43
  return {
@@ -173,23 +176,25 @@ export class TerminalSessionController {
173
176
  ...(title ? { title } : {}),
174
177
  };
175
178
  }
176
- handleTerminalsChanged(event) {
177
- if (!this.subscribedDirectories.has(event.cwd)) {
178
- return;
179
+ async handleTerminalsChanged(event) {
180
+ // A terminal can live in a subdirectory of a subscribed workspace root (an
181
+ // agent can open one there). Deliver the change to every subscribed root at
182
+ // or above the terminal's cwd, keyed by that root, carrying the full
183
+ // aggregated list — so the client's cache replacement doesn't drop the
184
+ // terminals that live directly at the root.
185
+ const matchingRoots = Array.from(this.subscribedDirectories).filter((root) => this.isPathWithinRoot(root, event.cwd));
186
+ for (const root of matchingRoots) {
187
+ await this.emitTerminalsSnapshotForRoot(root);
179
188
  }
180
- this.emitTerminalsChangedSnapshot({
181
- cwd: event.cwd,
182
- terminals: event.terminals.map((terminal) => Object.assign({ id: terminal.id, name: terminal.name }, terminal.title ? { title: terminal.title } : {})),
183
- });
184
189
  }
185
190
  handleSubscribeTerminalsRequest(msg) {
186
191
  this.subscribedDirectories.add(msg.cwd);
187
- void this.emitInitialTerminalsChangedSnapshot(msg.cwd);
192
+ void this.emitTerminalsSnapshotForRoot(msg.cwd);
188
193
  }
189
194
  handleUnsubscribeTerminalsRequest(msg) {
190
195
  this.subscribedDirectories.delete(msg.cwd);
191
196
  }
192
- async emitInitialTerminalsChangedSnapshot(cwd) {
197
+ async emitTerminalsSnapshotForRoot(cwd) {
193
198
  if (!this.terminalManager || !this.subscribedDirectories.has(cwd)) {
194
199
  return;
195
200
  }
@@ -618,7 +623,9 @@ export class TerminalSessionController {
618
623
  }
619
624
  }
620
625
  async emitLegacySnapshot(activeStream, terminalManager) {
621
- const snapshot = await terminalManager.getTerminalState(activeStream.terminalId);
626
+ const snapshot = await terminalManager.getTerminalState(activeStream.terminalId, {
627
+ includeWrapFlags: this.clientSupportsWrapReflow(),
628
+ });
622
629
  if (this.activeStreams.get(activeStream.slot) !== activeStream) {
623
630
  return { shouldContinue: false };
624
631
  }
@@ -637,7 +644,10 @@ export class TerminalSessionController {
637
644
  if (snapshotOptions === null) {
638
645
  return { shouldContinue: true };
639
646
  }
640
- const snapshot = await terminalManager.getTerminalState(activeStream.terminalId, snapshotOptions);
647
+ const snapshot = await terminalManager.getTerminalState(activeStream.terminalId, {
648
+ ...snapshotOptions,
649
+ includeWrapFlags: this.clientSupportsWrapReflow(),
650
+ });
641
651
  if (this.activeStreams.get(activeStream.slot) !== activeStream) {
642
652
  return { shouldContinue: false };
643
653
  }
@@ -13,6 +13,7 @@ export interface TerminalStateSnapshot {
13
13
  }
14
14
  export interface TerminalStateSnapshotOptions {
15
15
  scrollbackLines?: number;
16
+ includeWrapFlags?: boolean;
16
17
  }
17
18
  export interface TerminalSubscribeOptions {
18
19
  initialSnapshot?: "state" | "ready";
@@ -232,6 +232,32 @@ function extractScrollback(terminal, options) {
232
232
  }
233
233
  return scrollback;
234
234
  }
235
+ // xterm marks a line `isWrapped` when it is a continuation of the PREVIOUS line.
236
+ // The snapshot carries the inverse, tmux-style flag — "this row continues onto the
237
+ // next row" — so the client can rejoin and reflow logical lines. So row y's flag is
238
+ // whether line y+1 is a wrapped continuation.
239
+ function lineContinuesToNext(terminal, absoluteRow) {
240
+ return terminal.buffer.active.getLine(absoluteRow + 1)?.isWrapped === true;
241
+ }
242
+ function extractGridWrapped(terminal) {
243
+ const baseY = terminal.buffer.active.baseY;
244
+ const wrapped = [];
245
+ for (let row = 0; row < terminal.rows; row++) {
246
+ wrapped.push(lineContinuesToNext(terminal, baseY + row));
247
+ }
248
+ return wrapped;
249
+ }
250
+ function extractScrollbackWrapped(terminal, options) {
251
+ const scrollbackLines = terminal.buffer.active.baseY;
252
+ const startRow = typeof options?.scrollbackLines === "number"
253
+ ? Math.max(0, scrollbackLines - options.scrollbackLines)
254
+ : 0;
255
+ const wrapped = [];
256
+ for (let row = startRow; row < scrollbackLines; row++) {
257
+ wrapped.push(lineContinuesToNext(terminal, row));
258
+ }
259
+ return wrapped;
260
+ }
235
261
  function extractCursorState(terminal) {
236
262
  const coreService = terminal
237
263
  ._core?.coreService;
@@ -659,6 +685,14 @@ export async function createTerminal(options) {
659
685
  }),
660
686
  cursor: extractCursorState(terminal),
661
687
  ...(title ? { title } : {}),
688
+ ...(snapshotOptions?.includeWrapFlags
689
+ ? {
690
+ gridWrapped: extractGridWrapped(terminal),
691
+ scrollbackWrapped: extractScrollbackWrapped(terminal, {
692
+ scrollbackLines: snapshotOptions?.scrollbackLines,
693
+ }),
694
+ }
695
+ : {}),
662
696
  };
663
697
  }
664
698
  function getStateSnapshot(snapshotOptions) {
@@ -375,7 +375,7 @@ function buildGitDiffArgs(args) {
375
375
  return ["diff", ...(args.ignoreWhitespace ? ["-w"] : []), ...args.extra];
376
376
  }
377
377
  const TRACKED_DIFF_NUMSTAT_MAX_BYTES = 2 * 1024 * 1024; // 2MB
378
- const TRACKED_MAX_CHANGED_LINES = 40000;
378
+ const TRACKED_DIFF_PER_FILE_MAX_CHARS = 1024 * 1024;
379
379
  const EMPTY_TREE_OBJECT_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
380
380
  function isUnbornHeadDiffError(error) {
381
381
  return (error instanceof Error &&
@@ -423,11 +423,56 @@ async function getTrackedNumstatByPath(cwd, refs, ignoreWhitespace = false) {
423
423
  }
424
424
  return stats;
425
425
  }
426
- function isTrackedDiffTooLarge(stat) {
427
- if (!stat || stat.isBinary) {
428
- return false;
426
+ function extractTrackedDiffMetadataPath(section, prefix) {
427
+ const line = section.split("\n").find((candidate) => candidate.startsWith(prefix));
428
+ if (!line) {
429
+ return null;
430
+ }
431
+ const path = line.slice(prefix.length).replace(/\t.*$/, "").trimEnd();
432
+ if (path === "/dev/null") {
433
+ return null;
434
+ }
435
+ return path.startsWith("a/") || path.startsWith("b/") ? path.slice(2) : path;
436
+ }
437
+ function extractTrackedDiffSectionPath(section) {
438
+ const firstLineEnd = section.indexOf("\n");
439
+ const firstLine = firstLineEnd === -1 ? section : section.slice(0, firstLineEnd);
440
+ const header = firstLine.startsWith("diff --git ") ? firstLine.slice("diff --git ".length) : "";
441
+ const prefixedPathMatch = header.match(/^a\/(.+) b\/(.+)$/);
442
+ if (prefixedPathMatch) {
443
+ return prefixedPathMatch[2] ?? null;
444
+ }
445
+ const metadataPath = extractTrackedDiffMetadataPath(section, "+++ ") ??
446
+ extractTrackedDiffMetadataPath(section, "--- ");
447
+ if (metadataPath) {
448
+ return metadataPath;
449
+ }
450
+ const pathMatch = header.match(/^(\S+)\s+(\S+)$/);
451
+ return pathMatch?.[2] ?? null;
452
+ }
453
+ function splitTrackedDiffSections(diffText) {
454
+ const starts = [];
455
+ const diffHeaderPattern = /^diff --git /gm;
456
+ let match;
457
+ while ((match = diffHeaderPattern.exec(diffText))) {
458
+ starts.push(match.index);
459
+ }
460
+ const sections = [];
461
+ for (let index = 0; index < starts.length; index += 1) {
462
+ const start = starts[index];
463
+ const end = starts[index + 1] ?? diffText.length;
464
+ const text = diffText.slice(start, end);
465
+ const path = extractTrackedDiffSectionPath(text);
466
+ if (!path) {
467
+ continue;
468
+ }
469
+ sections.push({
470
+ path,
471
+ text,
472
+ isTooLarge: text.length > TRACKED_DIFF_PER_FILE_MAX_CHARS,
473
+ });
429
474
  }
430
- return stat.additions + stat.deletions > TRACKED_MAX_CHANGED_LINES;
475
+ return sections;
431
476
  }
432
477
  export class NotGitRepoError extends Error {
433
478
  constructor(cwd) {
@@ -1500,6 +1545,62 @@ async function processUntrackedChange(input) {
1500
1545
  status: "ok",
1501
1546
  });
1502
1547
  }
1548
+ async function processTrackedChanges(input) {
1549
+ const { cwd, refsForDiff, trackedChanges, ignoreWhitespace, appendDiff } = input;
1550
+ const trackedChangeByPath = new Map(trackedChanges.map((change) => [change.path, change]));
1551
+ const trackedNumstatByPath = trackedChanges.length > 0
1552
+ ? await getTrackedNumstatByPath(cwd, refsForDiff, ignoreWhitespace)
1553
+ : new Map();
1554
+ const trackedDiffPaths = [];
1555
+ const trackedPlaceholderByPath = new Map();
1556
+ for (const change of trackedChanges) {
1557
+ const stat = trackedNumstatByPath.get(change.path) ?? null;
1558
+ if (stat?.isBinary) {
1559
+ trackedPlaceholderByPath.set(change.path, { status: "binary", stat });
1560
+ continue;
1561
+ }
1562
+ trackedDiffPaths.push(change.path);
1563
+ }
1564
+ let trackedDiffText = "";
1565
+ let trackedDiffTruncated = false;
1566
+ if (trackedDiffPaths.length > 0) {
1567
+ const trackedDiffResult = await runGitCommand(buildGitDiffArgs({
1568
+ ignoreWhitespace,
1569
+ extra: [...getCheckoutDiffRefArgs(refsForDiff), "--", ...trackedDiffPaths],
1570
+ }), {
1571
+ cwd,
1572
+ envOverlay: READ_ONLY_GIT_ENV,
1573
+ maxOutputBytes: TOTAL_DIFF_MAX_BYTES,
1574
+ });
1575
+ trackedDiffTruncated = trackedDiffResult.truncated;
1576
+ const visibleTrackedDiffs = [];
1577
+ const sections = splitTrackedDiffSections(trackedDiffResult.stdout);
1578
+ for (let index = 0; index < sections.length; index += 1) {
1579
+ const section = sections[index];
1580
+ const isTruncatedTail = trackedDiffTruncated && index === sections.length - 1;
1581
+ if (section.isTooLarge || isTruncatedTail) {
1582
+ trackedPlaceholderByPath.set(section.path, {
1583
+ status: "too_large",
1584
+ stat: trackedNumstatByPath.get(section.path) ?? null,
1585
+ });
1586
+ continue;
1587
+ }
1588
+ visibleTrackedDiffs.push(section.text);
1589
+ }
1590
+ trackedDiffText = visibleTrackedDiffs.join("");
1591
+ appendDiff(trackedDiffText);
1592
+ if (trackedDiffTruncated) {
1593
+ appendDiff("# tracked diff truncated\n");
1594
+ }
1595
+ }
1596
+ return {
1597
+ trackedChangeByPath,
1598
+ trackedNumstatByPath,
1599
+ trackedPlaceholderByPath,
1600
+ trackedDiffText,
1601
+ trackedDiffTruncated,
1602
+ };
1603
+ }
1503
1604
  async function resolveCheckoutDiffRefs(cwd, compare, context) {
1504
1605
  if (compare.mode === "uncommitted") {
1505
1606
  return { baseRef: "HEAD", includeUntracked: true };
@@ -1565,42 +1666,13 @@ export async function getCheckoutDiff(cwd, compare, context) {
1565
1666
  };
1566
1667
  const trackedChanges = changes.filter((change) => !change.isUntracked);
1567
1668
  const untrackedChanges = changes.filter((change) => change.isUntracked === true);
1568
- const trackedChangeByPath = new Map(trackedChanges.map((change) => [change.path, change]));
1569
- const trackedNumstatByPath = trackedChanges.length > 0
1570
- ? await getTrackedNumstatByPath(cwd, effectiveRefsForDiff, ignoreWhitespace)
1571
- : new Map();
1572
- const trackedDiffPaths = [];
1573
- const trackedPlaceholderByPath = new Map();
1574
- for (const change of trackedChanges) {
1575
- const stat = trackedNumstatByPath.get(change.path) ?? null;
1576
- if (stat?.isBinary) {
1577
- trackedPlaceholderByPath.set(change.path, { status: "binary", stat });
1578
- continue;
1579
- }
1580
- if (isTrackedDiffTooLarge(stat)) {
1581
- trackedPlaceholderByPath.set(change.path, { status: "too_large", stat });
1582
- continue;
1583
- }
1584
- trackedDiffPaths.push(change.path);
1585
- }
1586
- let trackedDiffText = "";
1587
- let trackedDiffTruncated = false;
1588
- if (trackedDiffPaths.length > 0) {
1589
- const trackedDiffResult = await runGitCommand(buildGitDiffArgs({
1590
- ignoreWhitespace,
1591
- extra: [...getCheckoutDiffRefArgs(effectiveRefsForDiff), "--", ...trackedDiffPaths],
1592
- }), {
1593
- cwd,
1594
- envOverlay: READ_ONLY_GIT_ENV,
1595
- maxOutputBytes: TOTAL_DIFF_MAX_BYTES,
1596
- });
1597
- trackedDiffText = trackedDiffResult.stdout;
1598
- trackedDiffTruncated = trackedDiffResult.truncated;
1599
- appendDiff(trackedDiffText);
1600
- if (trackedDiffTruncated) {
1601
- appendDiff("# tracked diff truncated\n");
1602
- }
1603
- }
1669
+ const trackedDiff = await processTrackedChanges({
1670
+ cwd,
1671
+ refsForDiff: effectiveRefsForDiff,
1672
+ trackedChanges,
1673
+ ignoreWhitespace,
1674
+ appendDiff,
1675
+ });
1604
1676
  const appendTrackedPlaceholderComment = (change, status) => {
1605
1677
  if (status === "binary") {
1606
1678
  appendDiff(`# ${change.path}: binary diff omitted\n`);
@@ -1612,11 +1684,11 @@ export async function getCheckoutDiff(cwd, compare, context) {
1612
1684
  await appendStructuredTrackedDiffs({
1613
1685
  cwd,
1614
1686
  trackedChanges,
1615
- trackedChangeByPath,
1616
- trackedNumstatByPath,
1617
- trackedPlaceholderByPath,
1618
- trackedDiffText,
1619
- trackedDiffTruncated,
1687
+ trackedChangeByPath: trackedDiff.trackedChangeByPath,
1688
+ trackedNumstatByPath: trackedDiff.trackedNumstatByPath,
1689
+ trackedPlaceholderByPath: trackedDiff.trackedPlaceholderByPath,
1690
+ trackedDiffText: trackedDiff.trackedDiffText,
1691
+ trackedDiffTruncated: trackedDiff.trackedDiffTruncated,
1620
1692
  refsForDiff: effectiveRefsForDiff,
1621
1693
  ignoreWhitespace,
1622
1694
  structured,
@@ -1626,7 +1698,7 @@ export async function getCheckoutDiff(cwd, compare, context) {
1626
1698
  }
1627
1699
  else {
1628
1700
  for (const change of trackedChanges) {
1629
- const placeholder = trackedPlaceholderByPath.get(change.path);
1701
+ const placeholder = trackedDiff.trackedPlaceholderByPath.get(change.path);
1630
1702
  if (placeholder) {
1631
1703
  appendTrackedPlaceholderComment(change, placeholder.status);
1632
1704
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/server",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
4
4
  "description": "Paseo backend server",
5
5
  "files": [
6
6
  "dist/server",
@@ -57,10 +57,10 @@
57
57
  "dependencies": {
58
58
  "@agentclientprotocol/sdk": "^0.17.1",
59
59
  "@anthropic-ai/claude-agent-sdk": "^0.2.133",
60
- "@getpaseo/client": "0.1.87",
61
- "@getpaseo/highlight": "0.1.87",
62
- "@getpaseo/protocol": "0.1.87",
63
- "@getpaseo/relay": "0.1.87",
60
+ "@getpaseo/client": "0.1.88",
61
+ "@getpaseo/highlight": "0.1.88",
62
+ "@getpaseo/protocol": "0.1.88",
63
+ "@getpaseo/relay": "0.1.88",
64
64
  "@isaacs/ttlcache": "^2.1.4",
65
65
  "@modelcontextprotocol/sdk": "^1.20.1",
66
66
  "@opencode-ai/sdk": "1.14.46",