claudish 7.46.0 → 7.48.0

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.
Files changed (2) hide show
  1. package/dist/index.js +3310 -227
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -729,7 +729,7 @@ var init_onepassword_config = __esm(() => {
729
729
  });
730
730
 
731
731
  // src/version.ts
732
- var VERSION = "7.46.0";
732
+ var VERSION = "7.48.0";
733
733
 
734
734
  // src/logger.ts
735
735
  var exports_logger = {};
@@ -28579,6 +28579,25 @@ var init_provider_definitions = __esm(() => {
28579
28579
  isDirectApi: true,
28580
28580
  description: "Qwen Plan (qc@)"
28581
28581
  },
28582
+ {
28583
+ name: "qwen-payg",
28584
+ displayName: "Qwen PAYG",
28585
+ transport: "anthropic",
28586
+ baseUrl: "https://dashscope-intl.aliyuncs.com",
28587
+ baseUrlEnvVars: ["DASHSCOPE_BASE_URL"],
28588
+ apiPath: "/apps/anthropic/v1/messages",
28589
+ apiKeyEnvVar: "DASHSCOPE_API_KEY",
28590
+ apiKeyAliases: ["QWEN_API_KEY"],
28591
+ apiKeyDescription: "Alibaba Model Studio API Key (pay-as-you-go)",
28592
+ apiKeyUrl: "https://www.alibabacloud.com/help/en/model-studio/get-api-key",
28593
+ authScheme: "bearer",
28594
+ shortcuts: ["qp", "dashscope"],
28595
+ shortestPrefix: "qp",
28596
+ legacyPrefixes: [{ prefix: "qp/", stripPrefix: true }],
28597
+ modelDiscovery: { path: "/compatible-mode/v1/models", format: "openai-models-list" },
28598
+ isDirectApi: true,
28599
+ description: "Alibaba Model Studio pay-as-you-go (qp@)"
28600
+ },
28582
28601
  {
28583
28602
  name: "qwen",
28584
28603
  displayName: "Qwen",
@@ -29217,6 +29236,11 @@ function removeUriFormat(schema) {
29217
29236
  }
29218
29237
  return result;
29219
29238
  }
29239
+ function stripBillingHeader(text) {
29240
+ if (!text || !text.includes("x-anthropic-billing-header"))
29241
+ return text;
29242
+ return text.replace(BILLING_HEADER_RE, "");
29243
+ }
29220
29244
  function transformOpenAIToClaude(claudeRequestInput) {
29221
29245
  const req = JSON.parse(JSON.stringify(claudeRequestInput));
29222
29246
  const isO3Model = typeof req.model === "string" && (req.model.includes("o3") || req.model.includes("o1"));
@@ -29240,9 +29264,11 @@ function transformOpenAIToClaude(claudeRequestInput) {
29240
29264
  }
29241
29265
  }
29242
29266
  return JSON.stringify(item);
29243
- }).filter((text) => text && text.trim() !== "").join(`
29267
+ }).map((text) => stripBillingHeader(text)).filter((text) => text && text.trim() !== "").join(`
29244
29268
 
29245
29269
  `);
29270
+ } else if (typeof req.system === "string") {
29271
+ req.system = stripBillingHeader(req.system);
29246
29272
  }
29247
29273
  if (!Array.isArray(req.messages)) {
29248
29274
  if (req.messages == null)
@@ -29264,7 +29290,10 @@ function transformOpenAIToClaude(claudeRequestInput) {
29264
29290
  isO3Model
29265
29291
  };
29266
29292
  }
29267
- var init_transform = () => {};
29293
+ var BILLING_HEADER_RE;
29294
+ var init_transform = __esm(() => {
29295
+ BILLING_HEADER_RE = /x-anthropic-billing-header:[^\n]*\n?/gi;
29296
+ });
29268
29297
 
29269
29298
  // src/handlers/shared/format/openai-tools.ts
29270
29299
  function emptyParamsSchema() {
@@ -33137,6 +33166,13 @@ function createStreamingResponseHandler(c, response, adapter, target, middleware
33137
33166
  async start(controller) {
33138
33167
  const send = (e, d) => {
33139
33168
  if (!isClosed) {
33169
+ if (e === "content_block_start" && d?.content_block?.type === "tool_use") {
33170
+ try {
33171
+ behavior?.onToolCallObserved?.(String(d.content_block.name ?? ""));
33172
+ } catch (err) {
33173
+ log(`[Streaming] onToolCallObserved threw: ${err}`);
33174
+ }
33175
+ }
33140
33176
  controller.enqueue(encoder.encode(`event: ${e}
33141
33177
  data: ${JSON.stringify(d)}
33142
33178
 
@@ -33164,54 +33200,98 @@ data: ${JSON.stringify(d)}
33164
33200
  send("ping", { type: "ping" });
33165
33201
  }
33166
33202
  }, 1000);
33203
+ const teardown = () => {
33204
+ if (!isClosed) {
33205
+ try {
33206
+ controller.enqueue(encoder.encode(`data: [DONE]
33207
+
33208
+
33209
+ `));
33210
+ } catch {}
33211
+ try {
33212
+ controller.close();
33213
+ } catch {}
33214
+ isClosed = true;
33215
+ }
33216
+ if (ping) {
33217
+ clearInterval(ping);
33218
+ ping = null;
33219
+ }
33220
+ };
33167
33221
  const finalize = async (reason, err) => {
33168
- if (state.finalized)
33222
+ if (state.finalized) {
33223
+ teardown();
33169
33224
  return;
33225
+ }
33170
33226
  state.finalized = true;
33171
- if (state.accumulatedText.length > 0) {
33172
- const preview = state.accumulatedText.slice(0, 500).replace(/\n/g, "\\n");
33173
- log(`[Streaming] Accumulated text (${state.accumulatedText.length} chars): ${preview}...`);
33174
- }
33175
- const textToolCalls = extractToolCallsFromText(state.accumulatedText);
33176
- log(`[Streaming] Text-based tool calls found: ${textToolCalls.length}`);
33177
- if (textToolCalls.length > 0) {
33178
- log(`[Streaming] Found ${textToolCalls.length} text-based tool call(s), converting to structured format`);
33227
+ try {
33228
+ if (state.accumulatedText.length > 0) {
33229
+ const preview = state.accumulatedText.slice(0, 500).replace(/\n/g, "\\n");
33230
+ log(`[Streaming] Accumulated text (${state.accumulatedText.length} chars): ${preview}...`);
33231
+ }
33232
+ const textToolCalls = extractToolCallsFromText(state.accumulatedText);
33233
+ log(`[Streaming] Text-based tool calls found: ${textToolCalls.length}`);
33234
+ if (textToolCalls.length > 0) {
33235
+ log(`[Streaming] Found ${textToolCalls.length} text-based tool call(s), converting to structured format`);
33236
+ if (state.textStarted) {
33237
+ send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
33238
+ state.textStarted = false;
33239
+ }
33240
+ for (const tc of textToolCalls) {
33241
+ const toolIdx = state.curIdx++;
33242
+ const toolId = `tool_${Date.now()}_${toolIdx}`;
33243
+ send("content_block_start", {
33244
+ type: "content_block_start",
33245
+ index: toolIdx,
33246
+ content_block: { type: "tool_use", id: toolId, name: tc.name }
33247
+ });
33248
+ send("content_block_delta", {
33249
+ type: "content_block_delta",
33250
+ index: toolIdx,
33251
+ delta: {
33252
+ type: "input_json_delta",
33253
+ partial_json: repairArgs(tc.name, JSON.stringify(tc.arguments))
33254
+ }
33255
+ });
33256
+ send("content_block_stop", { type: "content_block_stop", index: toolIdx });
33257
+ }
33258
+ }
33259
+ if (state.reasoningStarted) {
33260
+ send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
33261
+ }
33179
33262
  if (state.textStarted) {
33180
33263
  send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
33181
- state.textStarted = false;
33182
33264
  }
33183
- for (const tc of textToolCalls) {
33184
- const toolIdx = state.curIdx++;
33185
- const toolId = `tool_${Date.now()}_${toolIdx}`;
33186
- send("content_block_start", {
33187
- type: "content_block_start",
33188
- index: toolIdx,
33189
- content_block: { type: "tool_use", id: toolId, name: tc.name }
33190
- });
33191
- send("content_block_delta", {
33192
- type: "content_block_delta",
33193
- index: toolIdx,
33194
- delta: {
33195
- type: "input_json_delta",
33196
- partial_json: repairArgs(tc.name, JSON.stringify(tc.arguments))
33197
- }
33198
- });
33199
- send("content_block_stop", { type: "content_block_stop", index: toolIdx });
33200
- }
33201
- }
33202
- if (state.reasoningStarted) {
33203
- send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
33204
- }
33205
- if (state.textStarted) {
33206
- send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
33207
- }
33208
- for (const t of Array.from(state.tools.values())) {
33209
- if (!t.closed && t.buffered && !t.started) {
33210
- if (toolSchemas && toolSchemas.length > 0) {
33211
- const validation = validateToolArguments(t.name, t.arguments, toolSchemas, state.accumulatedText);
33212
- if (validation.valid || validation.repaired && validation.repairedArgs) {
33213
- const argsJson = repairArgs(t.name, JSON.stringify(validation.repaired ? validation.repairedArgs : validation.parsedArgs));
33214
- log(`[Streaming] Sending buffered tool call (finish_reason!=tool_calls): ${t.name} with args: ${argsJson}`);
33265
+ for (const t of Array.from(state.tools.values())) {
33266
+ if (!t.closed && t.buffered && !t.started) {
33267
+ if (toolSchemas && toolSchemas.length > 0) {
33268
+ const validation = validateToolArguments(t.name, t.arguments, toolSchemas, state.accumulatedText);
33269
+ if (validation.valid || validation.repaired && validation.repairedArgs) {
33270
+ const argsJson = repairArgs(t.name, JSON.stringify(validation.repaired ? validation.repairedArgs : validation.parsedArgs));
33271
+ log(`[Streaming] Sending buffered tool call (finish_reason!=tool_calls): ${t.name} with args: ${argsJson}`);
33272
+ send("content_block_start", {
33273
+ type: "content_block_start",
33274
+ index: t.blockIndex,
33275
+ content_block: { type: "tool_use", id: t.id, name: t.name }
33276
+ });
33277
+ send("content_block_delta", {
33278
+ type: "content_block_delta",
33279
+ index: t.blockIndex,
33280
+ delta: { type: "input_json_delta", partial_json: argsJson }
33281
+ });
33282
+ send("content_block_stop", {
33283
+ type: "content_block_stop",
33284
+ index: t.blockIndex
33285
+ });
33286
+ t.started = true;
33287
+ t.closed = true;
33288
+ } else {
33289
+ log(`[Streaming] Buffered tool call ${t.name} failed validation, skipping: ${validation.missingParams.join(", ")}`);
33290
+ t.closed = true;
33291
+ }
33292
+ } else {
33293
+ const argsJson = repairArgs(t.name, t.arguments || "{}");
33294
+ log(`[Streaming] Sending buffered tool call (no validation): ${t.name} with args: ${argsJson}`);
33215
33295
  send("content_block_start", {
33216
33296
  type: "content_block_start",
33217
33297
  index: t.blockIndex,
@@ -33228,83 +33308,51 @@ data: ${JSON.stringify(d)}
33228
33308
  });
33229
33309
  t.started = true;
33230
33310
  t.closed = true;
33231
- } else {
33232
- log(`[Streaming] Buffered tool call ${t.name} failed validation, skipping: ${validation.missingParams.join(", ")}`);
33233
- t.closed = true;
33234
33311
  }
33235
- } else {
33236
- const argsJson = repairArgs(t.name, t.arguments || "{}");
33237
- log(`[Streaming] Sending buffered tool call (no validation): ${t.name} with args: ${argsJson}`);
33238
- send("content_block_start", {
33239
- type: "content_block_start",
33240
- index: t.blockIndex,
33241
- content_block: { type: "tool_use", id: t.id, name: t.name }
33242
- });
33243
- send("content_block_delta", {
33244
- type: "content_block_delta",
33245
- index: t.blockIndex,
33246
- delta: { type: "input_json_delta", partial_json: argsJson }
33247
- });
33248
- send("content_block_stop", {
33249
- type: "content_block_stop",
33250
- index: t.blockIndex
33251
- });
33252
- t.started = true;
33312
+ }
33313
+ }
33314
+ for (const t of Array.from(state.tools.values())) {
33315
+ if (t.started && !t.closed) {
33316
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
33253
33317
  t.closed = true;
33254
33318
  }
33255
33319
  }
33256
- }
33257
- for (const t of Array.from(state.tools.values())) {
33258
- if (t.started && !t.closed) {
33259
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
33260
- t.closed = true;
33320
+ if (middlewareManager) {
33321
+ await middlewareManager.afterStreamComplete(target, streamMetadata);
33261
33322
  }
33262
- }
33263
- if (middlewareManager) {
33264
- await middlewareManager.afterStreamComplete(target, streamMetadata);
33265
- }
33266
- if (reason === "error") {
33267
- send("error", { type: "error", error: { type: "api_error", message: err } });
33268
- } else {
33269
- const hasStructuredTools = Array.from(state.tools.values()).some((t) => t.started);
33270
- const truncated = state.finishReason === "length";
33271
- const refused = state.finishReason === "content_filter";
33272
- const stopReason = refused ? "refusal" : truncated ? "max_tokens" : textToolCalls.length > 0 || hasStructuredTools ? "tool_use" : "end_turn";
33273
- if (truncated || refused) {
33274
- log(`[Streaming] Upstream finish_reason=${state.finishReason} \u2192 stop_reason=${stopReason} (${state.accumulatedText.length} chars produced)`);
33323
+ if (reason === "error") {
33324
+ send("error", { type: "error", error: { type: "api_error", message: err } });
33325
+ } else {
33326
+ const hasStructuredTools = Array.from(state.tools.values()).some((t) => t.started);
33327
+ const truncated = state.finishReason === "length";
33328
+ const refused = state.finishReason === "content_filter";
33329
+ const stopReason = refused ? "refusal" : truncated ? "max_tokens" : textToolCalls.length > 0 || hasStructuredTools ? "tool_use" : "end_turn";
33330
+ if (truncated || refused) {
33331
+ log(`[Streaming] Upstream finish_reason=${state.finishReason} \u2192 stop_reason=${stopReason} (${state.accumulatedText.length} chars produced)`);
33332
+ }
33333
+ send("message_delta", {
33334
+ type: "message_delta",
33335
+ delta: { stop_reason: stopReason, stop_sequence: null },
33336
+ usage: {
33337
+ ...state.usage?.prompt_tokens ? { input_tokens: state.usage.prompt_tokens } : {},
33338
+ output_tokens: state.usage?.completion_tokens || 0
33339
+ }
33340
+ });
33341
+ behavior?.onTurnEnd?.();
33342
+ send("message_stop", { type: "message_stop" });
33275
33343
  }
33276
- send("message_delta", {
33277
- type: "message_delta",
33278
- delta: { stop_reason: stopReason, stop_sequence: null },
33279
- usage: {
33280
- ...state.usage?.prompt_tokens ? { input_tokens: state.usage.prompt_tokens } : {},
33281
- output_tokens: state.usage?.completion_tokens || 0
33344
+ if (onTokenUpdate) {
33345
+ if (state.usage) {
33346
+ log(`[Streaming] Final usage: prompt=${state.usage.prompt_tokens || 0}, completion=${state.usage.completion_tokens || 0}`);
33347
+ onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
33348
+ } else {
33349
+ const estimatedOutputTokens = Math.ceil(state.accumulatedText.length / 4);
33350
+ log(`[Streaming] No usage data from provider, estimating: ~${estimatedOutputTokens} output tokens`);
33351
+ onTokenUpdate(priorInputTokens || 100, estimatedOutputTokens);
33282
33352
  }
33283
- });
33284
- behavior?.onTurnEnd?.();
33285
- send("message_stop", { type: "message_stop" });
33286
- }
33287
- if (onTokenUpdate) {
33288
- if (state.usage) {
33289
- log(`[Streaming] Final usage: prompt=${state.usage.prompt_tokens || 0}, completion=${state.usage.completion_tokens || 0}`);
33290
- onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
33291
- } else {
33292
- const estimatedOutputTokens = Math.ceil(state.accumulatedText.length / 4);
33293
- log(`[Streaming] No usage data from provider, estimating: ~${estimatedOutputTokens} output tokens`);
33294
- onTokenUpdate(priorInputTokens || 100, estimatedOutputTokens);
33295
33353
  }
33296
- }
33297
- if (!isClosed) {
33298
- try {
33299
- controller.enqueue(encoder.encode(`data: [DONE]
33300
-
33301
-
33302
- `));
33303
- } catch (e) {}
33304
- controller.close();
33305
- isClosed = true;
33306
- if (ping)
33307
- clearInterval(ping);
33354
+ } finally {
33355
+ teardown();
33308
33356
  }
33309
33357
  };
33310
33358
  try {
@@ -37550,7 +37598,8 @@ var init_telemetry = __esm(() => {
37550
37598
  "minimax-coding",
37551
37599
  "kimi-coding",
37552
37600
  "glm-coding",
37553
- "qwen-cloud"
37601
+ "qwen-cloud",
37602
+ "qwen-payg"
37554
37603
  ]);
37555
37604
  });
37556
37605
 
@@ -39184,62 +39233,70 @@ data: ${JSON.stringify(data)}
39184
39233
  send("ping", { type: "ping" });
39185
39234
  }
39186
39235
  }, 1000);
39236
+ const teardown = () => {
39237
+ if (!isClosed) {
39238
+ isClosed = true;
39239
+ try {
39240
+ controller.close();
39241
+ } catch {}
39242
+ }
39243
+ if (pingInterval) {
39244
+ clearInterval(pingInterval);
39245
+ pingInterval = null;
39246
+ }
39247
+ };
39187
39248
  const finalize = async (reason, err) => {
39188
- if (finalized2)
39249
+ if (finalized2) {
39250
+ teardown();
39189
39251
  return;
39190
- finalized2 = true;
39191
- if (thinkingStarted) {
39192
- send("content_block_stop", { type: "content_block_stop", index: thinkingIdx });
39193
- }
39194
- if (textStarted) {
39195
- send("content_block_stop", { type: "content_block_stop", index: textIdx });
39196
39252
  }
39197
- for (const t of toolCalls.values()) {
39198
- if (t.started && !t.closed) {
39199
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39200
- t.closed = true;
39253
+ finalized2 = true;
39254
+ try {
39255
+ if (thinkingStarted) {
39256
+ send("content_block_stop", { type: "content_block_stop", index: thinkingIdx });
39201
39257
  }
39202
- }
39203
- if (opts.middlewareManager) {
39204
- await opts.middlewareManager.afterStreamComplete(opts.modelName, new Map);
39205
- }
39206
- const inputTokens = usage?.promptTokenCount || 0;
39207
- const outputTokens = usage?.candidatesTokenCount || 0;
39208
- if (usage) {
39209
- log(`[GeminiSSE] Usage: prompt=${inputTokens}, completion=${outputTokens}`);
39210
- }
39211
- if (opts.onTokenUpdate) {
39212
- opts.onTokenUpdate(inputTokens, outputTokens);
39213
- }
39214
- if (reason === "error") {
39215
- log(`[GeminiSSE] Stream error: ${err}`);
39216
- send("error", { type: "error", error: { type: "api_error", message: err } });
39217
- } else {
39218
- const hasToolCalls = toolCalls.size > 0;
39219
- const stopReason = truncated ? "max_tokens" : hasToolCalls ? "tool_use" : "end_turn";
39220
- if (truncated) {
39221
- log("[GeminiSSE] finishReason=MAX_TOKENS \u2192 stop_reason=max_tokens");
39258
+ if (textStarted) {
39259
+ send("content_block_stop", { type: "content_block_stop", index: textIdx });
39222
39260
  }
39223
- send("message_delta", {
39224
- type: "message_delta",
39225
- delta: { stop_reason: stopReason, stop_sequence: null },
39226
- usage: {
39227
- ...inputTokens > 0 ? { input_tokens: inputTokens } : {},
39228
- output_tokens: outputTokens
39261
+ for (const t of toolCalls.values()) {
39262
+ if (t.started && !t.closed) {
39263
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39264
+ t.closed = true;
39229
39265
  }
39230
- });
39231
- opts.onTurnEnd?.();
39232
- send("message_stop", { type: "message_stop" });
39233
- }
39234
- if (!isClosed) {
39235
- isClosed = true;
39236
- if (pingInterval) {
39237
- clearInterval(pingInterval);
39238
- pingInterval = null;
39239
39266
  }
39240
- try {
39241
- controller.close();
39242
- } catch {}
39267
+ if (opts.middlewareManager) {
39268
+ await opts.middlewareManager.afterStreamComplete(opts.modelName, new Map);
39269
+ }
39270
+ const inputTokens = usage?.promptTokenCount || 0;
39271
+ const outputTokens = usage?.candidatesTokenCount || 0;
39272
+ if (usage) {
39273
+ log(`[GeminiSSE] Usage: prompt=${inputTokens}, completion=${outputTokens}`);
39274
+ }
39275
+ if (opts.onTokenUpdate) {
39276
+ opts.onTokenUpdate(inputTokens, outputTokens);
39277
+ }
39278
+ if (reason === "error") {
39279
+ log(`[GeminiSSE] Stream error: ${err}`);
39280
+ send("error", { type: "error", error: { type: "api_error", message: err } });
39281
+ } else {
39282
+ const hasToolCalls = toolCalls.size > 0;
39283
+ const stopReason = truncated ? "max_tokens" : hasToolCalls ? "tool_use" : "end_turn";
39284
+ if (truncated) {
39285
+ log("[GeminiSSE] finishReason=MAX_TOKENS \u2192 stop_reason=max_tokens");
39286
+ }
39287
+ send("message_delta", {
39288
+ type: "message_delta",
39289
+ delta: { stop_reason: stopReason, stop_sequence: null },
39290
+ usage: {
39291
+ ...inputTokens > 0 ? { input_tokens: inputTokens } : {},
39292
+ output_tokens: outputTokens
39293
+ }
39294
+ });
39295
+ opts.onTurnEnd?.();
39296
+ send("message_stop", { type: "message_stop" });
39297
+ }
39298
+ } finally {
39299
+ teardown();
39243
39300
  }
39244
39301
  };
39245
39302
  try {
@@ -39457,38 +39514,48 @@ data: ${JSON.stringify(data)}
39457
39514
  send("ping", { type: "ping" });
39458
39515
  }
39459
39516
  }, 1000);
39460
- const finalize = (reason, err) => {
39461
- if (isClosed)
39462
- return;
39463
- if (textStarted) {
39464
- send("content_block_stop", { type: "content_block_stop", index: 0 });
39465
- }
39466
- if (reason === "error") {
39467
- send("error", { type: "error", error: { type: "api_error", message: err } });
39468
- } else {
39469
- send("message_delta", {
39470
- type: "message_delta",
39471
- delta: { stop_reason: "end_turn", stop_sequence: null },
39472
- usage: {
39473
- ...promptTokens > 0 ? { input_tokens: promptTokens } : {},
39474
- output_tokens: completionTokens
39475
- }
39476
- });
39477
- send("message_stop", { type: "message_stop" });
39478
- }
39479
- if (opts.onTokenUpdate) {
39480
- opts.onTokenUpdate(promptTokens, completionTokens);
39481
- }
39517
+ let finalized2 = false;
39518
+ const teardown = () => {
39482
39519
  if (!isClosed) {
39483
39520
  isClosed = true;
39484
- if (pingInterval) {
39485
- clearInterval(pingInterval);
39486
- pingInterval = null;
39487
- }
39488
39521
  try {
39489
39522
  controller.close();
39490
39523
  } catch {}
39491
39524
  }
39525
+ if (pingInterval) {
39526
+ clearInterval(pingInterval);
39527
+ pingInterval = null;
39528
+ }
39529
+ };
39530
+ const finalize = (reason, err) => {
39531
+ if (finalized2) {
39532
+ teardown();
39533
+ return;
39534
+ }
39535
+ finalized2 = true;
39536
+ try {
39537
+ if (textStarted) {
39538
+ send("content_block_stop", { type: "content_block_stop", index: 0 });
39539
+ }
39540
+ if (reason === "error") {
39541
+ send("error", { type: "error", error: { type: "api_error", message: err } });
39542
+ } else {
39543
+ send("message_delta", {
39544
+ type: "message_delta",
39545
+ delta: { stop_reason: "end_turn", stop_sequence: null },
39546
+ usage: {
39547
+ ...promptTokens > 0 ? { input_tokens: promptTokens } : {},
39548
+ output_tokens: completionTokens
39549
+ }
39550
+ });
39551
+ send("message_stop", { type: "message_stop" });
39552
+ }
39553
+ if (opts.onTokenUpdate) {
39554
+ opts.onTokenUpdate(promptTokens, completionTokens);
39555
+ }
39556
+ } finally {
39557
+ teardown();
39558
+ }
39492
39559
  };
39493
39560
  try {
39494
39561
  const reader = response.body.getReader();
@@ -39975,10 +40042,26 @@ class TokenTracker {
39975
40042
  modelNameOverride;
39976
40043
  planUsage;
39977
40044
  lastPlanSerialized = "";
40045
+ toolCallsByName = new Map;
40046
+ startedAt = Date.now();
40047
+ sessionBilledInputTokens = 0;
39978
40048
  constructor(port, config2) {
39979
40049
  this.port = port;
39980
40050
  this.config = config2;
39981
40051
  }
40052
+ recordToolUse(name) {
40053
+ const key = name.trim() || "unknown";
40054
+ this.toolCallsByName.set(key, (this.toolCallsByName.get(key) ?? 0) + 1);
40055
+ }
40056
+ getToolCallCount() {
40057
+ let n = 0;
40058
+ for (const v of this.toolCallsByName.values())
40059
+ n += v;
40060
+ return n;
40061
+ }
40062
+ getToolCalls() {
40063
+ return [...this.toolCallsByName].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
40064
+ }
39982
40065
  setActiveModelName(name) {
39983
40066
  this.modelNameOverride = name;
39984
40067
  }
@@ -40000,6 +40083,7 @@ class TokenTracker {
40000
40083
  this.sessionInputTokens = inputTokens;
40001
40084
  this.lastInputTokens = inputTokens;
40002
40085
  this.sessionOutputTokens += outputTokens;
40086
+ this.sessionBilledInputTokens += inputTokens;
40003
40087
  const pricing = this.getPricing();
40004
40088
  const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
40005
40089
  this.sessionTotalCost += cost;
@@ -40012,6 +40096,7 @@ class TokenTracker {
40012
40096
  const pricing = this.getPricing();
40013
40097
  const cost = this.sessionInputTokens / 1e6 * pricing.inputCostPer1M + this.sessionOutputTokens / 1e6 * pricing.outputCostPer1M;
40014
40098
  this.sessionTotalCost = cost;
40099
+ this.sessionBilledInputTokens = this.sessionInputTokens;
40015
40100
  this.writeFile(this.sessionInputTokens, this.sessionOutputTokens, pricing.isEstimate);
40016
40101
  }
40017
40102
  updateWithDelta(inputTokens, outputTokens) {
@@ -40030,6 +40115,7 @@ class TokenTracker {
40030
40115
  }
40031
40116
  this.sessionOutputTokens += outputTokens;
40032
40117
  const pricing = this.getPricing();
40118
+ this.sessionBilledInputTokens += incrementalInputTokens;
40033
40119
  const cost = incrementalInputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
40034
40120
  this.sessionTotalCost += cost;
40035
40121
  this.writeFile(inputTokens, this.sessionOutputTokens, pricing.isEstimate);
@@ -40038,6 +40124,7 @@ class TokenTracker {
40038
40124
  this.sessionInputTokens = inputTokens;
40039
40125
  this.lastInputTokens = inputTokens;
40040
40126
  this.sessionOutputTokens += outputTokens;
40127
+ this.sessionBilledInputTokens += inputTokens;
40041
40128
  if (typeof actualCost === "number" && actualCost > 0) {
40042
40129
  this.sessionTotalCost += actualCost;
40043
40130
  log(`[TokenTracker] Actual cost from API: $${actualCost.toFixed(6)}`);
@@ -40055,6 +40142,7 @@ class TokenTracker {
40055
40142
  this.lastInputTokens = inputTokens;
40056
40143
  }
40057
40144
  this.sessionOutputTokens += outputTokens;
40145
+ this.sessionBilledInputTokens += inputTokens;
40058
40146
  this.writeFile(this.sessionInputTokens, this.sessionOutputTokens);
40059
40147
  }
40060
40148
  setContextWindow(contextWindow) {
@@ -40107,7 +40195,12 @@ class TokenTracker {
40107
40195
  provider_name: this.getDisplayName(),
40108
40196
  updated_at: Date.now(),
40109
40197
  is_free: isFreeModel,
40110
- is_estimated: isEstimate || false
40198
+ is_estimated: isEstimate || false,
40199
+ started_at: this.startedAt,
40200
+ tool_calls: this.getToolCalls(),
40201
+ billed_input_tokens: this.sessionBilledInputTokens,
40202
+ input_per_m: pricing.inputCostPer1M,
40203
+ output_per_m: pricing.outputCostPer1M
40111
40204
  };
40112
40205
  const displayModel = stripProviderPrefix(this.modelNameOverride || this.config.modelName || "");
40113
40206
  if (displayModel) {
@@ -40813,14 +40906,18 @@ class ComposedHandler {
40813
40906
  };
40814
40907
  const streamFormat = this.resolveStreamFormat();
40815
40908
  const priorInputTokens = this.tokenTracker.getLastInputTokens();
40909
+ const observeToolCall = (name) => {
40910
+ this.tokenTracker.recordToolUse(name);
40911
+ behaviorSession?.observeToolCall(name);
40912
+ };
40816
40913
  switch (streamFormat) {
40817
40914
  case "openai-sse":
40818
- return createStreamingResponseHandler(c, response, adapter, this.bareModelName, this.middlewareManager, onTokenUpdate, claudeRequest.tools, toolNameMap, priorInputTokens, behaviorSession && {
40819
- shouldBufferTool: (name) => behaviorSession.interceptsTool(name),
40820
- onToolCall: (name, argsJson) => behaviorSession.repairToolCall(name, argsJson),
40821
- onAssistantText: (text, kind) => behaviorSession.observeText(text, kind),
40822
- onToolCallObserved: (name) => behaviorSession.observeToolCall(name),
40823
- onTurnEnd: () => behaviorSession.finishTurn()
40915
+ return createStreamingResponseHandler(c, response, adapter, this.bareModelName, this.middlewareManager, onTokenUpdate, claudeRequest.tools, toolNameMap, priorInputTokens, {
40916
+ shouldBufferTool: (name) => behaviorSession?.interceptsTool(name) ?? false,
40917
+ onToolCall: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
40918
+ onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
40919
+ onToolCallObserved: observeToolCall,
40920
+ onTurnEnd: () => behaviorSession?.finishTurn()
40824
40921
  });
40825
40922
  case "openai-responses-sse":
40826
40923
  return createResponsesStreamHandler(c, response, {
@@ -40834,7 +40931,7 @@ class ComposedHandler {
40834
40931
  shouldBufferTool: (name) => behaviorSession?.interceptsTool(name) ?? false,
40835
40932
  onToolCall: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
40836
40933
  onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
40837
- onToolCallObserved: (name) => behaviorSession?.observeToolCall(name),
40934
+ onToolCallObserved: observeToolCall,
40838
40935
  onTurnEnd: () => behaviorSession?.finishTurn()
40839
40936
  });
40840
40937
  case "anthropic-sse":
@@ -40845,7 +40942,7 @@ class ComposedHandler {
40845
40942
  shouldBufferTool: (name) => behaviorSession?.interceptsTool(name) ?? false,
40846
40943
  repairToolArgs: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
40847
40944
  onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
40848
- onToolCallObserved: (name) => behaviorSession?.observeToolCall(name),
40945
+ onToolCallObserved: observeToolCall,
40849
40946
  onTurnEnd: () => behaviorSession?.finishTurn()
40850
40947
  });
40851
40948
  case "gemini-sse": {
@@ -40862,7 +40959,7 @@ class ComposedHandler {
40862
40959
  onToolCall,
40863
40960
  repairToolArgs: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
40864
40961
  onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
40865
- onToolCallObserved: (name) => behaviorSession?.observeToolCall(name),
40962
+ onToolCallObserved: observeToolCall,
40866
40963
  onTurnEnd: () => behaviorSession?.finishTurn(),
40867
40964
  unwrapResponse: this.options.unwrapGeminiResponse,
40868
40965
  priorInputTokens
@@ -40882,7 +40979,7 @@ class ComposedHandler {
40882
40979
  repairToolArgs: (name, argsJson) => behaviorSession?.repairToolCall(name, argsJson) ?? null,
40883
40980
  shouldBufferTool: (name) => behaviorSession?.interceptsTool(name) ?? false,
40884
40981
  onAssistantText: (text, kind) => behaviorSession?.observeText(text, kind),
40885
- onToolCallObserved: (name) => behaviorSession?.observeToolCall(name),
40982
+ onToolCallObserved: observeToolCall,
40886
40983
  onTurnEnd: () => behaviorSession?.finishTurn()
40887
40984
  });
40888
40985
  case "ollama-jsonl":
@@ -41522,6 +41619,40 @@ function toRosterEntry(model) {
41522
41619
  const { id, ...rest } = model;
41523
41620
  return { wireId: id, ...rest };
41524
41621
  }
41622
+ function recordFailure(failure) {
41623
+ _failures.set(failure.provider, failure);
41624
+ log(`[model-discovery:${failure.provider}] ${describeDiscoveryFailure(failure)}`);
41625
+ return [];
41626
+ }
41627
+ function getDiscoveryFailure(provider) {
41628
+ return _failures.get(provider);
41629
+ }
41630
+ function hasAuthHeader(headers) {
41631
+ return Object.entries(headers).some(([name, value]) => /^(authorization|x-api-key|api-key)$/i.test(name) && (value ?? "").trim().length > 0);
41632
+ }
41633
+ function oneLine(text, max = 200) {
41634
+ const flat = text.replace(/\s+/g, " ").trim();
41635
+ return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
41636
+ }
41637
+ function describeDiscoveryFailure(failure) {
41638
+ const { kind, endpoint, status, detail } = failure;
41639
+ const at = endpoint ? ` at ${endpoint}` : "";
41640
+ const because = detail ? ` \u2014 ${detail}` : "";
41641
+ switch (kind) {
41642
+ case "no-credentials":
41643
+ return `no usable credentials${because}`;
41644
+ case "unauthorized":
41645
+ return `the API key was rejected (HTTP ${status})${at}${because}`;
41646
+ case "http-error":
41647
+ return `the model list returned HTTP ${status}${at}${because}`;
41648
+ case "unreachable":
41649
+ return `the model list was unreachable${at}${because}`;
41650
+ case "malformed":
41651
+ return `the model list was not valid JSON${at}`;
41652
+ case "empty-roster":
41653
+ return `the endpoint answered${at} but listed no models`;
41654
+ }
41655
+ }
41525
41656
  function resolveBaseUrl(catalogName) {
41526
41657
  const def = getProviderByName(catalogName);
41527
41658
  if (!def)
@@ -41585,14 +41716,14 @@ async function discoverProviderModels(providerName) {
41585
41716
  const { getServedDevinModels: getServedDevinModels2 } = await Promise.resolve().then(() => (init_devin_models(), exports_devin_models));
41586
41717
  const served = await getServedDevinModels2();
41587
41718
  if (served.length === 0) {
41588
- log(`[model-discovery:${providerName}] no models for this subscription`);
41589
- return [];
41719
+ return recordFailure({ kind: "empty-roster", provider: providerName });
41590
41720
  }
41591
41721
  const { devinRosterEntry: devinRosterEntry2 } = await Promise.resolve().then(() => (init_devin(), exports_devin));
41592
41722
  const models2 = served.map((model) => {
41593
41723
  const { wireId, ...rest } = devinRosterEntry2(model);
41594
41724
  return { id: wireId, ...rest };
41595
41725
  });
41726
+ _failures.delete(providerName);
41596
41727
  log(`[model-discovery:${providerName}] discovered ${models2.length} models`);
41597
41728
  _cache.set(providerName, { models: models2, expiresAt: Date.now() + CACHE_TTL_MS });
41598
41729
  return models2;
@@ -41600,13 +41731,15 @@ async function discoverProviderModels(providerName) {
41600
41731
  if (descriptor.format === "ollama-tags") {
41601
41732
  const { fetchOllamaModels: fetchOllamaModels2 } = await Promise.resolve().then(() => exports_ollama_discovery);
41602
41733
  const installed = await fetchOllamaModels2({ enrichCapabilities: false });
41603
- if (installed.length === 0)
41604
- return [];
41734
+ if (installed.length === 0) {
41735
+ return recordFailure({ kind: "empty-roster", provider: providerName });
41736
+ }
41605
41737
  const models2 = installed.map((model) => ({
41606
41738
  id: model.name,
41607
41739
  displayName: model.name,
41608
41740
  supportsTools: model.supportsTools
41609
41741
  }));
41742
+ _failures.delete(providerName);
41610
41743
  log(`[model-discovery:${providerName}] discovered ${models2.length} local models`);
41611
41744
  _cache.set(providerName, { models: models2, expiresAt: Date.now() + CACHE_TTL_MS });
41612
41745
  return models2;
@@ -41625,8 +41758,15 @@ async function discoverProviderModels(providerName) {
41625
41758
  const auth = await credentials.getRequestAuth(providerName, { model: "" });
41626
41759
  headers = { ...auth.headers };
41627
41760
  } catch (e) {
41628
- log(`[model-discovery:${providerName}] no credentials, skipping: ${e?.message}`);
41629
- return [];
41761
+ return recordFailure({
41762
+ kind: "no-credentials",
41763
+ provider: providerName,
41764
+ endpoint,
41765
+ detail: oneLine(e?.message ?? "")
41766
+ });
41767
+ }
41768
+ if (!hasAuthHeader(headers)) {
41769
+ return recordFailure({ kind: "no-credentials", provider: providerName, endpoint });
41630
41770
  }
41631
41771
  }
41632
41772
  let response;
@@ -41637,25 +41777,38 @@ async function discoverProviderModels(providerName) {
41637
41777
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
41638
41778
  });
41639
41779
  } catch (e) {
41640
- log(`[model-discovery:${providerName}] fetch failed: ${e?.message}`);
41641
- return [];
41780
+ return recordFailure({
41781
+ kind: "unreachable",
41782
+ provider: providerName,
41783
+ endpoint,
41784
+ detail: oneLine(e?.message ?? "")
41785
+ });
41642
41786
  }
41643
41787
  if (!response.ok) {
41644
- log(`[model-discovery:${providerName}] HTTP ${response.status} from ${endpoint}`);
41645
- return [];
41788
+ const unauthorized = response.status === 401 || response.status === 403;
41789
+ let detail = "";
41790
+ try {
41791
+ detail = oneLine(await response.text());
41792
+ } catch {}
41793
+ return recordFailure({
41794
+ kind: unauthorized ? "unauthorized" : "http-error",
41795
+ provider: providerName,
41796
+ endpoint,
41797
+ status: response.status,
41798
+ detail
41799
+ });
41646
41800
  }
41647
41801
  let body;
41648
41802
  try {
41649
41803
  body = await response.json();
41650
41804
  } catch {
41651
- log(`[model-discovery:${providerName}] response was not JSON`);
41652
- return [];
41805
+ return recordFailure({ kind: "malformed", provider: providerName, endpoint });
41653
41806
  }
41654
41807
  const models = parseOpenAIModelsList(body);
41655
41808
  if (models.length === 0) {
41656
- log(`[model-discovery:${providerName}] endpoint reachable but listed no models`);
41657
- return [];
41809
+ return recordFailure({ kind: "empty-roster", provider: providerName, endpoint });
41658
41810
  }
41811
+ _failures.delete(providerName);
41659
41812
  log(`[model-discovery:${providerName}] discovered ${models.length} models: ` + models.map((m) => `${m.id}(${m.contextWindow ?? "?"})`).join(", "));
41660
41813
  _cache.set(providerName, { models, expiresAt: Date.now() + CACHE_TTL_MS });
41661
41814
  return models;
@@ -41671,13 +41824,14 @@ function rankDiscoveredModels(models) {
41671
41824
  return diff !== 0 ? diff : a.id.localeCompare(b.id);
41672
41825
  });
41673
41826
  }
41674
- var CACHE_TTL_MS, FETCH_TIMEOUT_MS = 5000, _cache, MIN_CREATED_SECONDS = 946684800, MAX_CREATED_SECONDS = 4102444800;
41827
+ var CACHE_TTL_MS, FETCH_TIMEOUT_MS = 5000, _cache, _failures, MIN_CREATED_SECONDS = 946684800, MAX_CREATED_SECONDS = 4102444800;
41675
41828
  var init_model_discovery = __esm(() => {
41676
41829
  init_authority();
41677
41830
  init_logger();
41678
41831
  init_provider_definitions();
41679
41832
  CACHE_TTL_MS = 5 * 60 * 1000;
41680
41833
  _cache = new Map;
41834
+ _failures = new Map;
41681
41835
  });
41682
41836
 
41683
41837
  // src/providers/transport/probe-discovery.ts
@@ -42073,9 +42227,10 @@ class AnthropicProviderTransport {
42073
42227
  }
42074
42228
  const discovered = await discoverProviderModels(this.provider.name);
42075
42229
  if (discovered.length === 0) {
42230
+ const failure = getDiscoveryFailure(this.provider.name);
42076
42231
  return {
42077
42232
  model: null,
42078
- reason: `${this.displayName} listed no models at ${def.modelDiscovery.path} \u2014 check the API key and that the subscription is active`
42233
+ reason: failure ? `${this.displayName}: ${describeDiscoveryFailure(failure)}` : `${this.displayName} listed no models at ${def.modelDiscovery.path} \u2014 check the API key and that the subscription is active`
42079
42234
  };
42080
42235
  }
42081
42236
  const ranked = rankDiscoveredModels(discovered).map((m) => m.id).filter(isChatCapable);
@@ -42128,6 +42283,7 @@ class AnthropicProviderTransport {
42128
42283
  kimi: "Kimi",
42129
42284
  "kimi-coding": "Kimi Coding",
42130
42285
  "qwen-cloud": "Qwen Plan",
42286
+ "qwen-payg": "Qwen PAYG",
42131
42287
  moonshot: "Kimi",
42132
42288
  "z-ai": "Z.AI"
42133
42289
  };
@@ -42674,6 +42830,7 @@ var init_routing_hints = __esm(() => {
42674
42830
  sakana: { apiKeyEnvVar: "SAKANA_API_KEY" },
42675
42831
  "sakana-subscription": { apiKeyEnvVar: "SAKANA_SUBSCRIPTION_API_KEY" },
42676
42832
  "qwen-cloud": { apiKeyEnvVar: "QWEN_CLOUD_PLAN_API_KEY" },
42833
+ "qwen-payg": { apiKeyEnvVar: "DASHSCOPE_API_KEY" },
42677
42834
  ollamacloud: { apiKeyEnvVar: "OLLAMA_API_KEY" },
42678
42835
  "native-anthropic": { apiKeyEnvVar: "ANTHROPIC_API_KEY" },
42679
42836
  openrouter: { apiKeyEnvVar: "OPENROUTER_API_KEY" },
@@ -42722,7 +42879,7 @@ var init_default_routing_rules = __esm(() => {
42722
42879
  "k3*": ["kimi-coding", "opencode-zen-go", "kimi", "openrouter"],
42723
42880
  "minimax-*": ["minimax-coding", "opencode-zen-go", "minimax", "openrouter"],
42724
42881
  "glm-*": ["glm-coding", "opencode-zen-go", "glm", "openrouter"],
42725
- "qwen3.*": ["qwen-cloud", "opencode-zen-go", "openrouter"],
42882
+ "qwen3.*": ["qwen-cloud", "opencode-zen-go", "qwen-payg", "openrouter"],
42726
42883
  "z-ai-*": ["z-ai", "openrouter"],
42727
42884
  "deepseek-*": ["opencode-zen-go", "deepseek", "openrouter"],
42728
42885
  "mimo-*": ["opencode-zen-go", "openrouter"],
@@ -42891,6 +43048,9 @@ async function routeBare(model, nativeProvider, rules, defaultProvider, cachePat
42891
43048
  const [primary, ...fallbacks] = credentialed;
42892
43049
  return { kind: "ok", primary, fallbacks };
42893
43050
  }
43051
+ function normalizeGlmSlug(model) {
43052
+ return model.replace(/^glm-(\d+)-(\d+)(-.*)?$/i, (_m, major, minor, suffix) => `glm-${major}.${minor}${suffix ?? ""}`);
43053
+ }
42894
43054
  async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePath) {
42895
43055
  const parsed = parseModelSpec(modelSpec);
42896
43056
  if (parsed.isExplicitProvider) {
@@ -42898,7 +43058,7 @@ async function route(modelSpec, rulesOverride, defaultProviderOverride, cachePat
42898
43058
  }
42899
43059
  const rules = rulesOverride ?? loadRoutingRules();
42900
43060
  const defaultProvider = defaultProviderOverride !== undefined ? defaultProviderOverride : rulesOverride !== undefined ? undefined : loadConfig().defaultProvider;
42901
- return routeBare(parsed.model, parsed.provider, rules, defaultProvider, cachePath);
43061
+ return routeBare(normalizeGlmSlug(parsed.model), parsed.provider, rules, defaultProvider, cachePath);
42902
43062
  }
42903
43063
  var init_routing_rules = __esm(() => {
42904
43064
  init_model_catalog();
@@ -47004,6 +47164,7 @@ var init_api_key_map = __esm(() => {
47004
47164
  aliases: ["SAKANA_CODING_API_KEY"]
47005
47165
  },
47006
47166
  "qwen-cloud": { envVar: "QWEN_CLOUD_PLAN_API_KEY" },
47167
+ "qwen-payg": { envVar: "DASHSCOPE_API_KEY", aliases: ["QWEN_API_KEY"] },
47007
47168
  ollamacloud: { envVar: "OLLAMA_API_KEY" },
47008
47169
  "opencode-zen": { envVar: "OPENCODE_API_KEY" },
47009
47170
  "opencode-zen-go": { envVar: "OPENCODE_GO_API_KEY", aliases: ["OPENCODE_API_KEY"] },
@@ -48052,6 +48213,7 @@ var init_provider_profiles = __esm(() => {
48052
48213
  kimi: anthropicCompatProfile,
48053
48214
  "kimi-coding": anthropicCompatProfile,
48054
48215
  "qwen-cloud": anthropicCompatProfile,
48216
+ "qwen-payg": anthropicCompatProfile,
48055
48217
  "z-ai": anthropicCompatProfile,
48056
48218
  glm: glmProfile,
48057
48219
  "glm-coding": glmProfile,
@@ -64604,6 +64766,7 @@ var init_model_catalog2 = __esm(() => {
64604
64766
  // src/model-selector.ts
64605
64767
  var exports_model_selector = {};
64606
64768
  __export(exports_model_selector, {
64769
+ warnDiscoveryFailure: () => warnDiscoveryFailure,
64607
64770
  selectProfile: () => selectProfile,
64608
64771
  selectModelsForProfile: () => selectModelsForProfile,
64609
64772
  selectModel: () => selectModel,
@@ -65155,6 +65318,26 @@ function describeOffer(offer) {
65155
65318
  });
65156
65319
  return `FREE until ${until}`;
65157
65320
  }
65321
+ function warnDiscoveryFailure(provider, displayName, def) {
65322
+ const failure = getDiscoveryFailure(provider);
65323
+ if (!failure || failure.kind === "empty-roster")
65324
+ return;
65325
+ process.stderr.write(`
65326
+ \u26A0 ${displayName} could not list its models: ${describeDiscoveryFailure(failure)}
65327
+ `);
65328
+ if (failure.kind === "unauthorized" || failure.kind === "no-credentials") {
65329
+ if (def.apiKeyEnvVar) {
65330
+ process.stderr.write(` Check ${def.apiKeyEnvVar} (a value in your shell overrides stored credentials).
65331
+ `);
65332
+ }
65333
+ if (def.apiKeyUrl)
65334
+ process.stderr.write(` Get a key: ${def.apiKeyUrl}
65335
+ `);
65336
+ }
65337
+ process.stderr.write(` Falling back to manual model entry.
65338
+
65339
+ `);
65340
+ }
65158
65341
  async function buildDiscoveredModelRows(provider, displayName, catalog) {
65159
65342
  const discovered = rankDiscoveredModels(await discoverProviderModels(provider)).filter((m) => isChatCapable(m.id));
65160
65343
  if (discovered.length === 0)
@@ -65205,6 +65388,8 @@ async function selectModelFromProvider(provider, tierName, recommendedModels, _f
65205
65388
  const picked = await pickModelFromList(provider, displayName, tierName, discoveredModels);
65206
65389
  if (picked)
65207
65390
  return picked;
65391
+ } else {
65392
+ warnDiscoveryFailure(provider, displayName, def);
65208
65393
  }
65209
65394
  }
65210
65395
  if (isUserDeployedProvider(provider)) {
@@ -65419,6 +65604,7 @@ var init_model_selector = __esm(() => {
65419
65604
  kimi: { name: "Kimi / Moonshot", description: "Direct API" },
65420
65605
  "kimi-coding": { name: "Kimi Coding", description: "Coding subscription" },
65421
65606
  "qwen-cloud": { name: "Qwen Plan", description: "Alibaba Model Studio subscription" },
65607
+ "qwen-payg": { name: "Qwen PAYG", description: "Alibaba Model Studio pay-as-you-go" },
65422
65608
  glm: { name: "GLM / Zhipu", description: "Direct API" },
65423
65609
  "glm-coding": { name: "GLM Coding Plan", description: "Coding subscription" },
65424
65610
  "z-ai": { name: "Z.AI", description: "Direct API" },
@@ -65448,6 +65634,7 @@ var init_model_selector = __esm(() => {
65448
65634
  "kimi",
65449
65635
  "kimi-coding",
65450
65636
  "qwen-cloud",
65637
+ "qwen-payg",
65451
65638
  "glm",
65452
65639
  "glm-coding",
65453
65640
  "z-ai",
@@ -68509,6 +68696,8 @@ Usage: claudish --models --provider <slug>`);
68509
68696
  if (rest.length > 0)
68510
68697
  config3._hasPositionalPrompt = true;
68511
68698
  break;
68699
+ } else if (arg === "--resume" && (i + 1 >= args.length || args[i + 1].startsWith("-"))) {
68700
+ config3._resumePicker = true;
68512
68701
  } else if (arg.startsWith("-")) {
68513
68702
  config3.claudeArgs.push(arg);
68514
68703
  if (arg === "-p" || arg === "--print") {
@@ -69024,6 +69213,7 @@ async function probeModelRouting(models, jsonOutput, options = { live: true, tim
69024
69213
  "kimi",
69025
69214
  "kimi-coding",
69026
69215
  "qwen-cloud",
69216
+ "qwen-payg",
69027
69217
  "z-ai"
69028
69218
  ];
69029
69219
  const isMinimaxModel = modelName.toLowerCase().includes("minimax");
@@ -78411,12 +78601,2864 @@ var init_team_grid = __esm(() => {
78411
78601
  ];
78412
78602
  });
78413
78603
 
78604
+ // src/tui/viz/text.ts
78605
+ function columns(n, fn, arg = "width") {
78606
+ if (!Number.isFinite(n))
78607
+ throw new RangeError(`${fn}: ${arg} must be a finite number, got ${n}`);
78608
+ return Math.max(0, Math.floor(n));
78609
+ }
78610
+ function isWide2(cp) {
78611
+ let lo = 0;
78612
+ let hi = WIDE.length - 1;
78613
+ while (lo <= hi) {
78614
+ const mid = lo + hi >> 1;
78615
+ const [a, b] = WIDE[mid];
78616
+ if (cp < a)
78617
+ hi = mid - 1;
78618
+ else if (cp > b)
78619
+ lo = mid + 1;
78620
+ else
78621
+ return true;
78622
+ }
78623
+ return false;
78624
+ }
78625
+ function fallbackClusterWidth(cluster) {
78626
+ const cp = cluster.codePointAt(0) ?? 0;
78627
+ if (cp < 32 || cp >= 127 && cp <= 159 || LEADING_FORMAT.test(cluster))
78628
+ return 0;
78629
+ return isWide2(cp) || LEADING_EMOJI_PRESENTATION.test(cluster) || cluster.includes(VS16) ? 2 : 1;
78630
+ }
78631
+ function displayWidth(s) {
78632
+ if (NATIVE_WIDTH)
78633
+ return NATIVE_WIDTH(s);
78634
+ let w = 0;
78635
+ for (const { segment } of seg.segment(s))
78636
+ w += fallbackClusterWidth(segment);
78637
+ return w;
78638
+ }
78639
+ function truncate3(s, width) {
78640
+ const n = columns(width, "truncate");
78641
+ if (n === 0)
78642
+ return "";
78643
+ const clean = sanitize2(s);
78644
+ if (displayWidth(clean) <= n)
78645
+ return clean;
78646
+ let out = "";
78647
+ let w = 0;
78648
+ for (const { segment } of seg.segment(clean)) {
78649
+ const cw = displayWidth(segment);
78650
+ if (w + cw > n - 1)
78651
+ break;
78652
+ out += segment;
78653
+ w += cw;
78654
+ }
78655
+ return `${out}\u2026`;
78656
+ }
78657
+ function padTo(s, width) {
78658
+ const n = columns(width, "padTo");
78659
+ if (n === 0)
78660
+ return "";
78661
+ const clean = sanitize2(s);
78662
+ const clipped = displayWidth(clean) > n ? truncate3(clean, n) : clean;
78663
+ return clipped + " ".repeat(Math.max(0, n - displayWidth(clipped)));
78664
+ }
78665
+ function padStartTo(s, width) {
78666
+ const n = columns(width, "padStartTo");
78667
+ if (n === 0)
78668
+ return "";
78669
+ const clean = sanitize2(s);
78670
+ const clipped = displayWidth(clean) > n ? truncate3(clean, n) : clean;
78671
+ return " ".repeat(Math.max(0, n - displayWidth(clipped))) + clipped;
78672
+ }
78673
+ function splitCells(parts, cells) {
78674
+ const n = columns(cells, "splitCells", "cells");
78675
+ const finite = parts.map((p) => Number.isFinite(p) && p > 0 ? p : 0);
78676
+ const out = finite.map(() => 0);
78677
+ let safe = finite;
78678
+ let sum = finite.reduce((a, b) => a + b, 0);
78679
+ if (!Number.isFinite(sum)) {
78680
+ const max = finite.reduce((a, b) => b > a ? b : a, 0);
78681
+ safe = finite.map((p) => p / max);
78682
+ sum = safe.reduce((a, b) => a + b, 0);
78683
+ }
78684
+ if (n <= 0 || sum <= 0)
78685
+ return out;
78686
+ const byShare = safe.map((p, i) => ({ p, i })).sort((a, b) => b.p - a.p);
78687
+ if (n < byShare.filter(({ p }) => p > 0).length) {
78688
+ for (const { i } of byShare.slice(0, n))
78689
+ out[i] = 1;
78690
+ return out;
78691
+ }
78692
+ const exact = safe.map((p) => n * (p / sum));
78693
+ exact.forEach((e, i) => {
78694
+ out[i] = Math.floor(e);
78695
+ });
78696
+ const rank = exact.map((e, i) => ({ rem: e % 1, i })).sort((a, b) => b.rem - a.rem);
78697
+ for (let k = 0, left = n - out.reduce((a, b) => a + b, 0);left > 0; k++, left--)
78698
+ out[rank[k % rank.length].i] += 1;
78699
+ for (const { p, i } of byShare)
78700
+ if (p > 0 && out[i] === 0) {
78701
+ const d = byShare.reduce((m, c) => out[c.i] > out[m] ? c.i : m, 0);
78702
+ if (out[d] > 1) {
78703
+ out[d] -= 1;
78704
+ out[i] = 1;
78705
+ }
78706
+ }
78707
+ return out;
78708
+ }
78709
+ var NATIVE_WIDTH, WIDE, seg, VS16 = "\uFE0F", LEADING_FORMAT, LEADING_EMOJI_PRESENTATION, CONTROL, sanitize2 = (s) => s.replace(CONTROL, " ");
78710
+ var init_text = __esm(() => {
78711
+ NATIVE_WIDTH = (() => {
78712
+ const b = globalThis.Bun;
78713
+ return typeof b?.stringWidth === "function" ? b.stringWidth.bind(b) : null;
78714
+ })();
78715
+ WIDE = [
78716
+ [4352, 4447],
78717
+ [9001, 9002],
78718
+ [11904, 12350],
78719
+ [12353, 12771],
78720
+ [12774, 12871],
78721
+ [12880, 13311],
78722
+ [13312, 19903],
78723
+ [19968, 40959],
78724
+ [40960, 42191],
78725
+ [43360, 43391],
78726
+ [44032, 55203],
78727
+ [63744, 64255],
78728
+ [65040, 65049],
78729
+ [65072, 65135],
78730
+ [65280, 65376],
78731
+ [65504, 65510],
78732
+ [94176, 94180],
78733
+ [94192, 94193],
78734
+ [94208, 100343],
78735
+ [100352, 101589],
78736
+ [101632, 101640],
78737
+ [110576, 110579],
78738
+ [110581, 110587],
78739
+ [110589, 110590],
78740
+ [110592, 110882],
78741
+ [110898, 110898],
78742
+ [110928, 110930],
78743
+ [110933, 110933],
78744
+ [110948, 110951],
78745
+ [110960, 111355],
78746
+ [127488, 127490],
78747
+ [127504, 127547],
78748
+ [127552, 127560],
78749
+ [127568, 127569],
78750
+ [127584, 127589],
78751
+ [131072, 196605],
78752
+ [196608, 262141]
78753
+ ];
78754
+ seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
78755
+ LEADING_FORMAT = /^\p{Cf}/u;
78756
+ LEADING_EMOJI_PRESENTATION = /^\p{Emoji_Presentation}/u;
78757
+ CONTROL = /[\u0000-\u001f\u007f-\u009f]/g;
78758
+ });
78759
+
78760
+ // src/tui/viz/tokens.ts
78761
+ var tokens, ramps;
78762
+ var init_tokens = __esm(() => {
78763
+ init_theme2();
78764
+ tokens = {
78765
+ fatal: C.red,
78766
+ error: C.red,
78767
+ warn: C.orange,
78768
+ info: C.cyan,
78769
+ debug: C.fgMuted,
78770
+ trace: C.dim,
78771
+ success: C.green,
78772
+ running: C.blue,
78773
+ idle: C.fgMuted,
78774
+ dead: C.dim,
78775
+ border: C.border,
78776
+ subtle: C.dim,
78777
+ text: C.fg,
78778
+ accent: C.focusBorder,
78779
+ bgPanel: C.bgAlt,
78780
+ ink: C.black
78781
+ };
78782
+ ramps = {
78783
+ load: [tokens.success, C.yellow, tokens.error],
78784
+ temperature: [tokens.running, tokens.success, C.orange, tokens.error],
78785
+ network: [tokens.success, C.yellow, tokens.error],
78786
+ savings: [tokens.error, C.yellow, tokens.success],
78787
+ volume: [C.border, C.blue, C.cyan]
78788
+ };
78789
+ });
78790
+
78791
+ // src/tui/viz/color.ts
78792
+ import { RGBA, parseColor, rgbToHex } from "@opentui/core";
78793
+ function mix(a, b, t) {
78794
+ const k = clamp01(t);
78795
+ return hex3(RGBA.fromValues(a.r + (b.r - a.r) * k, a.g + (b.g - a.g) * k, a.b + (b.b - a.b) * k, a.a + (b.a - a.a) * k));
78796
+ }
78797
+ function blend1D(steps, from, to) {
78798
+ if (!Number.isInteger(steps) || steps <= 0)
78799
+ return [];
78800
+ const a = rgba(from);
78801
+ if (steps === 1)
78802
+ return [hex3(a)];
78803
+ const b = rgba(to);
78804
+ return Array.from({ length: steps }, (_, i) => mix(a, b, i / (steps - 1)));
78805
+ }
78806
+ function blendStops(steps, ...stops) {
78807
+ if (!Number.isInteger(steps) || steps <= 0 || stops.length === 0)
78808
+ return [];
78809
+ if (stops.length === 1 || steps === 1)
78810
+ return blend1D(steps, stops[0], stops.at(-1));
78811
+ const pts = stops.map(rgba);
78812
+ const segs = pts.length - 1;
78813
+ return Array.from({ length: steps }, (_, i) => {
78814
+ const p = i / (steps - 1) * segs;
78815
+ const s = Math.min(segs - 1, Math.floor(p));
78816
+ return mix(pts[s], pts[s + 1], p - s);
78817
+ });
78818
+ }
78819
+ function luminance(c) {
78820
+ const f = (v) => v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
78821
+ return 0.2126 * f(clamp01(c.r)) + 0.7152 * f(clamp01(c.g)) + 0.0722 * f(clamp01(c.b));
78822
+ }
78823
+ function contrastRatio(a, b) {
78824
+ const [x, y] = [luminance(a), luminance(b)];
78825
+ return x >= y ? (x + 0.05) / (y + 0.05) : (y + 0.05) / (x + 0.05);
78826
+ }
78827
+ function over(fg, bg) {
78828
+ const a = clamp01(fg.a);
78829
+ if (a >= 1)
78830
+ return fg;
78831
+ return RGBA.fromValues(fg.r * a + bg.r * (1 - a), fg.g * a + bg.g * (1 - a), fg.b * a + bg.b * (1 - a), 1);
78832
+ }
78833
+ function pickInk(bg, dark = tokens.ink, light = tokens.text) {
78834
+ const surface = over(rgba(bg), rgba(tokens.bgPanel));
78835
+ const inkDark = over(rgba(dark), surface);
78836
+ const inkLight = over(rgba(light), surface);
78837
+ return hex3(contrastRatio(inkDark, surface) >= contrastRatio(inkLight, surface) ? inkDark : inkLight);
78838
+ }
78839
+ var rgba = (c) => parseColor(c), hex3 = (c) => rgbToHex(c), clamp01 = (n) => Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : 0;
78840
+ var init_color = __esm(() => {
78841
+ init_tokens();
78842
+ });
78843
+
78844
+ // src/tui/viz/widgets.tsx
78845
+ import { createTextAttributes as createTextAttributes2 } from "@opentui/core";
78846
+ import { jsxDEV as jsxDEV18, Fragment as Fragment11 } from "@opentui/react/jsx-dev-runtime";
78847
+ function rampFor(width, stops) {
78848
+ const use = stops.length > 0 ? stops : ramps.load;
78849
+ const key = `${width}|${use.join(":")}`;
78850
+ const hit = RAMP_CACHE.get(key);
78851
+ if (hit)
78852
+ return hit;
78853
+ const built = blendStops(width, ...use);
78854
+ if (RAMP_CACHE.size >= RAMP_CACHE_MAX)
78855
+ RAMP_CACHE.delete(RAMP_CACHE.keys().next().value);
78856
+ RAMP_CACHE.set(key, built);
78857
+ return built;
78858
+ }
78859
+ function fillCells(pct, width) {
78860
+ const cells = Math.floor(width);
78861
+ if (!Number.isFinite(cells) || cells <= 0 || Number.isNaN(pct))
78862
+ return 0;
78863
+ return Math.round(Math.min(100, Math.max(0, pct)) / 100 * cells);
78864
+ }
78865
+ function MeterSpan({
78866
+ pct,
78867
+ width,
78868
+ ramp = ramps.load
78869
+ }) {
78870
+ const cells = Math.floor(width);
78871
+ if (!Number.isFinite(cells) || cells <= 0)
78872
+ return null;
78873
+ if (Number.isNaN(pct))
78874
+ return /* @__PURE__ */ jsxDEV18("span", {
78875
+ fg: tokens.dead,
78876
+ children: NODATA.repeat(cells)
78877
+ }, undefined, false, undefined, this);
78878
+ const cols = rampFor(cells, ramp);
78879
+ const filled = fillCells(pct, cells);
78880
+ return /* @__PURE__ */ jsxDEV18(Fragment11, {
78881
+ children: Array.from({ length: cells }, (_, i) => /* @__PURE__ */ jsxDEV18("span", {
78882
+ fg: i < filled ? cols[i] : tokens.border,
78883
+ children: i < filled ? FILL : TRACK
78884
+ }, i, false, undefined, this))
78885
+ }, undefined, false, undefined, this);
78886
+ }
78887
+ function Sparkline({
78888
+ values,
78889
+ fg = tokens.info,
78890
+ style,
78891
+ ...layout
78892
+ }) {
78893
+ const row = sparkGlyphs(values);
78894
+ if (row === null)
78895
+ return null;
78896
+ return /* @__PURE__ */ jsxDEV18("text", {
78897
+ fg,
78898
+ flexShrink: 0,
78899
+ ...layout,
78900
+ style,
78901
+ children: row
78902
+ }, undefined, false, undefined, this);
78903
+ }
78904
+ function sparkGlyphs(values) {
78905
+ if (values.length === 0)
78906
+ return null;
78907
+ let max = Number.NEGATIVE_INFINITY;
78908
+ let min = Number.POSITIVE_INFINITY;
78909
+ for (const v of values)
78910
+ if (Number.isFinite(v)) {
78911
+ max = Math.max(max, v);
78912
+ min = Math.min(min, v);
78913
+ }
78914
+ if (max === Number.NEGATIVE_INFINITY)
78915
+ return GAP.repeat(values.length);
78916
+ const half = max / 2 - min / 2;
78917
+ const mid = SPARK[Math.floor((SPARK.length - 1) / 2)];
78918
+ const top = SPARK.length - 1;
78919
+ const glyph = (v) => SPARK[Math.min(top, Math.max(0, Math.round((v / 2 - min / 2) / half * top)))];
78920
+ return values.map((v) => !Number.isFinite(v) ? GAP : half > 0 ? glyph(v) : mid).join("");
78921
+ }
78922
+ function SparklineSpan({
78923
+ values,
78924
+ fg = tokens.info
78925
+ }) {
78926
+ const row = sparkGlyphs(values);
78927
+ return row === null ? null : /* @__PURE__ */ jsxDEV18("span", {
78928
+ fg,
78929
+ children: row
78930
+ }, undefined, false, undefined, this);
78931
+ }
78932
+ function badgePad(label, width) {
78933
+ const pad2 = Math.max(0, (width ?? 0) - displayWidth(label) - 2);
78934
+ return pad2 > 0 ? /* @__PURE__ */ jsxDEV18("span", {
78935
+ children: " ".repeat(pad2)
78936
+ }, undefined, false, undefined, this) : null;
78937
+ }
78938
+ function BadgeSpan({ label, bg, width }) {
78939
+ return /* @__PURE__ */ jsxDEV18(Fragment11, {
78940
+ children: [
78941
+ /* @__PURE__ */ jsxDEV18("span", {
78942
+ fg: pickInk(bg),
78943
+ bg,
78944
+ attributes: BOLD4,
78945
+ children: ` ${label} `
78946
+ }, undefined, false, undefined, this),
78947
+ badgePad(label, width)
78948
+ ]
78949
+ }, undefined, true, undefined, this);
78950
+ }
78951
+ function Panel({
78952
+ title,
78953
+ focused = false,
78954
+ flush = false,
78955
+ children,
78956
+ style,
78957
+ ...layout
78958
+ }) {
78959
+ return /* @__PURE__ */ jsxDEV18("box", {
78960
+ border: true,
78961
+ borderStyle: "rounded",
78962
+ borderColor: focused ? tokens.accent : tokens.border,
78963
+ backgroundColor: tokens.bgPanel,
78964
+ title,
78965
+ titleAlignment: "left",
78966
+ flexDirection: "column",
78967
+ overflow: "hidden",
78968
+ paddingLeft: flush ? 0 : 1,
78969
+ paddingRight: flush ? 0 : 1,
78970
+ ...layout,
78971
+ style,
78972
+ children
78973
+ }, undefined, false, undefined, this);
78974
+ }
78975
+ var BOLD4, FILL = "\u2588", TRACK = "\u2591", SPARK, GAP = " ", NODATA = "\u254C", RAMP_CACHE, RAMP_CACHE_MAX = 64;
78976
+ var init_widgets = __esm(() => {
78977
+ init_color();
78978
+ init_text();
78979
+ init_tokens();
78980
+ BOLD4 = createTextAttributes2({ bold: true });
78981
+ SPARK = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
78982
+ RAMP_CACHE = new Map;
78983
+ });
78984
+
78985
+ // src/session/session-discovery.ts
78986
+ var exports_session_discovery = {};
78987
+ __export(exports_session_discovery, {
78988
+ slugForPath: () => slugForPath,
78989
+ sessionLabel: () => sessionLabel,
78990
+ mainConversationTurn: () => mainConversationTurn,
78991
+ isHarnessNoise: () => isHarnessNoise,
78992
+ isAgentSession: () => isAgentSession,
78993
+ isActive: () => isActive,
78994
+ hydrateSession: () => hydrateSession,
78995
+ hydrateConversation: () => hydrateConversation,
78996
+ getRepoContext: () => getRepoContext,
78997
+ findLatestSessionId: () => findLatestSessionId,
78998
+ enrichWorktreeGit: () => enrichWorktreeGit,
78999
+ discoverWorktreeGroups: () => discoverWorktreeGroups,
79000
+ PROJECTS_DIR: () => PROJECTS_DIR,
79001
+ ACTIVE_WINDOW_MS: () => ACTIVE_WINDOW_MS
79002
+ });
79003
+ import { execFile, execFileSync as execFileSync2 } from "child_process";
79004
+ import { closeSync as closeSync5, openSync as openSync5, readSync, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
79005
+ import { homedir as homedir34 } from "os";
79006
+ import { basename, join as join37 } from "path";
79007
+ function slugForPath(absPath) {
79008
+ return absPath.replace(/[/.]/g, "-");
79009
+ }
79010
+ function isAgentSession(row) {
79011
+ return row.entrypoint !== undefined && row.entrypoint !== "cli";
79012
+ }
79013
+ function getRepoContext(cwd = process.cwd()) {
79014
+ const git = (args) => {
79015
+ try {
79016
+ return execFileSync2("git", args, {
79017
+ cwd,
79018
+ encoding: "utf-8",
79019
+ stdio: ["ignore", "pipe", "ignore"]
79020
+ }).trim();
79021
+ } catch {
79022
+ return null;
79023
+ }
79024
+ };
79025
+ const current = git(["rev-parse", "--show-toplevel"]);
79026
+ const commonDir = git(["rev-parse", "--git-common-dir"]);
79027
+ if (!current || !commonDir)
79028
+ return null;
79029
+ const root = commonDir.endsWith("/.git") ? commonDir.slice(0, -"/.git".length) : current;
79030
+ const liveWorktrees = [];
79031
+ const branchByPath = new Map;
79032
+ const porcelain = git(["worktree", "list", "--porcelain"]);
79033
+ if (porcelain) {
79034
+ let currentPath = null;
79035
+ for (const line of porcelain.split(`
79036
+ `)) {
79037
+ if (line.startsWith("worktree ")) {
79038
+ currentPath = line.slice("worktree ".length);
79039
+ liveWorktrees.push(currentPath);
79040
+ } else if (line.startsWith("branch ") && currentPath) {
79041
+ branchByPath.set(currentPath, line.slice("branch ".length).replace(/^refs\/heads\//, ""));
79042
+ }
79043
+ }
79044
+ }
79045
+ return { root, current, liveWorktrees, branchByPath };
79046
+ }
79047
+ function projectDirs() {
79048
+ try {
79049
+ return readdirSync7(PROJECTS_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
79050
+ } catch {
79051
+ return [];
79052
+ }
79053
+ }
79054
+ function sessionsIn(dirName) {
79055
+ const dir = join37(PROJECTS_DIR, dirName);
79056
+ let names;
79057
+ try {
79058
+ names = readdirSync7(dir).filter((n) => n.endsWith(".jsonl"));
79059
+ } catch {
79060
+ return [];
79061
+ }
79062
+ const rows = [];
79063
+ for (const n of names) {
79064
+ const file2 = join37(dir, n);
79065
+ try {
79066
+ const st = statSync6(file2);
79067
+ if (st.size === 0)
79068
+ continue;
79069
+ const row = {
79070
+ id: basename(n, ".jsonl"),
79071
+ file: file2,
79072
+ mtimeMs: st.mtimeMs,
79073
+ sizeBytes: st.size
79074
+ };
79075
+ const head = readChunk(file2, 0, Math.min(ENTRYPOINT_BYTES, st.size));
79076
+ const m = /"entrypoint":"([a-z-]+)"/.exec(head);
79077
+ if (m)
79078
+ row.entrypoint = m[1];
79079
+ rows.push(row);
79080
+ } catch {}
79081
+ }
79082
+ return rows;
79083
+ }
79084
+ async function enrichWorktreeGit(groups, repoRoot) {
79085
+ const run = (cwd, args) => new Promise((resolve5) => {
79086
+ execFile("git", args, { cwd, encoding: "utf-8" }, (err, stdout) => resolve5(err ? "" : stdout));
79087
+ });
79088
+ const trackByBranch = new Map;
79089
+ const refs = await run(repoRoot, [
79090
+ "for-each-ref",
79091
+ "--format=%(refname:short)\t%(upstream:track)",
79092
+ "refs/heads/"
79093
+ ]);
79094
+ for (const line of refs.split(`
79095
+ `)) {
79096
+ const [name, track] = line.split("\t");
79097
+ if (!name || !track)
79098
+ continue;
79099
+ const ahead = /ahead (\d+)/.exec(track)?.[1];
79100
+ const behind = /behind (\d+)/.exec(track)?.[1];
79101
+ if (ahead || behind) {
79102
+ trackByBranch.set(name, {
79103
+ ...ahead ? { ahead: Number(ahead) } : {},
79104
+ ...behind ? { behind: Number(behind) } : {}
79105
+ });
79106
+ }
79107
+ }
79108
+ await Promise.all(groups.map(async (g) => {
79109
+ if (g.branch)
79110
+ Object.assign(g, trackByBranch.get(g.branch) ?? {});
79111
+ if (!g.path || !g.live)
79112
+ return;
79113
+ const out = await run(g.path, ["status", "--porcelain"]);
79114
+ g.dirty = out.split(`
79115
+ `).filter((l) => l.trim().length > 0).length;
79116
+ }));
79117
+ }
79118
+ function isUnder(p, root) {
79119
+ return p === root || p.startsWith(`${root}/`);
79120
+ }
79121
+ function readProjectCwd(dirName) {
79122
+ const rows = sessionsIn(dirName);
79123
+ if (rows.length === 0)
79124
+ return null;
79125
+ rows.sort((a, b) => b.mtimeMs - a.mtimeMs);
79126
+ for (const row of rows.slice(0, 2)) {
79127
+ for (const r of parseRecords(readChunk(row.file, 0, Math.min(HEAD_BYTES, row.sizeBytes)), false)) {
79128
+ if (typeof r.cwd === "string" && r.cwd)
79129
+ return r.cwd;
79130
+ }
79131
+ }
79132
+ return null;
79133
+ }
79134
+ function isActive(row, now = Date.now()) {
79135
+ return now - row.mtimeMs < ACTIVE_WINDOW_MS;
79136
+ }
79137
+ function discoverWorktreeGroups(repo) {
79138
+ const rootSlug = slugForPath(repo.root);
79139
+ const mine = projectDirs();
79140
+ const known = [...repo.liveWorktrees].map((p) => ({ path: p, slug: slugForPath(p) })).sort((a, b) => b.slug.length - a.slug.length);
79141
+ const groups = new Map;
79142
+ const groupCwd = new Map;
79143
+ const upsert = (name, path2, live) => {
79144
+ let g = groups.get(name);
79145
+ if (!g) {
79146
+ g = {
79147
+ name,
79148
+ path: path2,
79149
+ live,
79150
+ current: path2 !== null && path2 === repo.current,
79151
+ sessions: [],
79152
+ lastActiveMs: 0,
79153
+ activeNow: false,
79154
+ ...path2 ? { branch: repo.branchByPath.get(path2) } : {}
79155
+ };
79156
+ groups.set(name, g);
79157
+ }
79158
+ return g;
79159
+ };
79160
+ const WORKTREE_MARK = "--claude-worktrees-";
79161
+ const worktreeMatches = known.filter((k) => k.path !== repo.root);
79162
+ for (const dir of mine) {
79163
+ const at = dir.indexOf(WORKTREE_MARK);
79164
+ let name;
79165
+ let path2;
79166
+ let live;
79167
+ const hit = worktreeMatches.find((k) => dir === k.slug || dir.startsWith(`${k.slug}-`));
79168
+ if (hit) {
79169
+ path2 = hit.path;
79170
+ live = true;
79171
+ name = hit.path === repo.root ? "(root)" : basename(hit.path);
79172
+ } else if (at !== -1 && dir.startsWith(rootSlug)) {
79173
+ path2 = null;
79174
+ live = false;
79175
+ name = dir.slice(at + WORKTREE_MARK.length);
79176
+ } else if (dir === rootSlug) {
79177
+ name = "(root)";
79178
+ path2 = repo.root;
79179
+ live = true;
79180
+ } else if (dir.startsWith(`${rootSlug}-`) && at === -1) {
79181
+ const cwd = readProjectCwd(dir);
79182
+ if (!cwd || !isUnder(cwd, repo.root))
79183
+ continue;
79184
+ name = "(root)";
79185
+ path2 = repo.root;
79186
+ live = true;
79187
+ } else {
79188
+ continue;
79189
+ }
79190
+ const g = upsert(name, path2, live);
79191
+ if (path2)
79192
+ groupCwd.set(name, path2);
79193
+ else if (!groupCwd.has(name)) {
79194
+ const cwd = readProjectCwd(dir);
79195
+ if (cwd)
79196
+ groupCwd.set(name, cwd);
79197
+ }
79198
+ for (const s of sessionsIn(dir)) {
79199
+ g.sessions.push(s);
79200
+ if (s.mtimeMs > g.lastActiveMs)
79201
+ g.lastActiveMs = s.mtimeMs;
79202
+ }
79203
+ }
79204
+ const names = [...groups.keys()].sort((a, b) => b.length - a.length);
79205
+ for (const name of [...groups.keys()]) {
79206
+ const g = groups.get(name);
79207
+ if (!g || g.live)
79208
+ continue;
79209
+ const parent = names.find((n) => n !== name && name.startsWith(`${n}-`) && groups.has(n));
79210
+ if (!parent)
79211
+ continue;
79212
+ const into = groups.get(parent);
79213
+ const childCwd = groupCwd.get(name);
79214
+ const parentCwd = groupCwd.get(parent);
79215
+ if (!childCwd || !parentCwd || !isUnder(childCwd, parentCwd))
79216
+ continue;
79217
+ into.sessions.push(...g.sessions);
79218
+ into.lastActiveMs = Math.max(into.lastActiveMs, g.lastActiveMs);
79219
+ groups.delete(name);
79220
+ }
79221
+ for (const g of groups.values()) {
79222
+ g.sessions.sort((a, b) => b.mtimeMs - a.mtimeMs);
79223
+ g.activeNow = g.sessions.some((s) => isActive(s));
79224
+ if (g.path) {
79225
+ try {
79226
+ g.createdMs = statSync6(g.path).birthtimeMs;
79227
+ } catch {}
79228
+ }
79229
+ if (!g.createdMs && g.sessions.length > 0) {
79230
+ g.createdMs = g.sessions.reduce((m, s) => Math.min(m, s.mtimeMs), Number.POSITIVE_INFINITY);
79231
+ }
79232
+ }
79233
+ return [...groups.values()].filter((g) => g.sessions.length > 0).sort((a, b) => {
79234
+ if (a.current !== b.current)
79235
+ return a.current ? -1 : 1;
79236
+ return b.lastActiveMs - a.lastActiveMs;
79237
+ });
79238
+ }
79239
+ function readChunk(file2, pos, len) {
79240
+ if (len <= 0)
79241
+ return "";
79242
+ let fd = null;
79243
+ try {
79244
+ fd = openSync5(file2, "r");
79245
+ const buf = Buffer.allocUnsafe(len);
79246
+ const n = readSync(fd, buf, 0, len, pos);
79247
+ return buf.subarray(0, n).toString("utf-8");
79248
+ } catch {
79249
+ return "";
79250
+ } finally {
79251
+ if (fd !== null) {
79252
+ try {
79253
+ closeSync5(fd);
79254
+ } catch {}
79255
+ }
79256
+ }
79257
+ }
79258
+ function parseRecords(chunk, dropFirstPartial) {
79259
+ const lines = chunk.split(`
79260
+ `);
79261
+ if (dropFirstPartial)
79262
+ lines.shift();
79263
+ else
79264
+ lines.pop();
79265
+ const out = [];
79266
+ for (const l of lines) {
79267
+ if (!l)
79268
+ continue;
79269
+ try {
79270
+ const o = JSON.parse(l);
79271
+ if (o && typeof o === "object")
79272
+ out.push(o);
79273
+ } catch {}
79274
+ }
79275
+ return out;
79276
+ }
79277
+ function contentText(content) {
79278
+ if (typeof content === "string")
79279
+ return content;
79280
+ if (!Array.isArray(content))
79281
+ return "";
79282
+ return content.map((b) => b && typeof b === "object" && typeof b.text === "string" ? b.text : "").join(`
79283
+ `);
79284
+ }
79285
+ function isHarnessNoise(raw2) {
79286
+ const t = raw2.trimStart();
79287
+ if (t.startsWith("[Request interrupted by user"))
79288
+ return true;
79289
+ return HARNESS_ENVELOPES.some((e) => t.startsWith(e));
79290
+ }
79291
+ function mainConversationTurn(r) {
79292
+ if (r.type !== "user" && r.type !== "assistant")
79293
+ return null;
79294
+ if (r.isMeta || r.isSidechain)
79295
+ return null;
79296
+ const content = r.message?.content;
79297
+ if (Array.isArray(content) && content.some((b) => b?.type === "tool_result")) {
79298
+ return null;
79299
+ }
79300
+ const raw2 = contentText(content);
79301
+ if (!raw2.trim() || isHarnessNoise(raw2))
79302
+ return null;
79303
+ return { role: r.type === "user" ? "user" : "assistant", raw: raw2 };
79304
+ }
79305
+ function cleanPrompt(text) {
79306
+ return text.replace(/<command-[a-z-]+>[\s\S]*?<\/command-[a-z-]+>/g, " ").replace(/<local-command-[a-z-]+>[\s\S]*?<\/local-command-[a-z-]+>/g, " ").replace(/<[^>]{1,40}>/g, " ").replace(/\s+/g, " ").trim();
79307
+ }
79308
+ function hydrateSession(row) {
79309
+ if (row.hydrated)
79310
+ return row;
79311
+ row.hydrated = true;
79312
+ if (isActive(row)) {
79313
+ try {
79314
+ row.sizeBytes = statSync6(row.file).size;
79315
+ } catch {}
79316
+ }
79317
+ const head = parseRecords(readChunk(row.file, 0, Math.min(HEAD_BYTES, row.sizeBytes)), false);
79318
+ for (const r of head) {
79319
+ if (!row.gitBranch && typeof r.gitBranch === "string")
79320
+ row.gitBranch = r.gitBranch;
79321
+ if (!row.firstPrompt && r.type === "user" && !r.isMeta) {
79322
+ const raw2 = contentText(r.message?.content);
79323
+ const t = isHarnessNoise(raw2) ? "" : cleanPrompt(raw2);
79324
+ if (t)
79325
+ row.firstPrompt = t;
79326
+ }
79327
+ if (row.gitBranch && row.firstPrompt)
79328
+ break;
79329
+ }
79330
+ const tailStart = Math.max(0, row.sizeBytes - TAIL_BYTES);
79331
+ const tail = parseRecords(readChunk(row.file, tailStart, row.sizeBytes - tailStart), tailStart > 0);
79332
+ for (let i = tail.length - 1;i >= 0; i--) {
79333
+ const r = tail[i];
79334
+ if (!row.title && r.type === "ai-title" && typeof r.aiTitle === "string" && r.aiTitle.trim()) {
79335
+ row.title = r.aiTitle.trim();
79336
+ }
79337
+ if (row.lastMessageChars === undefined && r.type === "user" && !r.isMeta) {
79338
+ const raw2 = contentText(r.message?.content);
79339
+ const t = isHarnessNoise(raw2) ? "" : cleanPrompt(raw2);
79340
+ if (t)
79341
+ row.lastMessageChars = t.length;
79342
+ }
79343
+ }
79344
+ row.recentTurns = extractRecentTurns(tail);
79345
+ return row;
79346
+ }
79347
+ function extractRecentTurns(records) {
79348
+ const out = [];
79349
+ let ai = 0;
79350
+ let user = 0;
79351
+ for (let i = records.length - 1;i >= 0; i--) {
79352
+ if (ai >= RECENT_AI_TURNS && user >= RECENT_USER_TURNS)
79353
+ break;
79354
+ const r = records[i];
79355
+ if (r.type !== "user" && r.type !== "assistant")
79356
+ continue;
79357
+ const role = r.type === "user" ? "user" : "assistant";
79358
+ if (role === "assistant" ? ai >= RECENT_AI_TURNS : user >= RECENT_USER_TURNS)
79359
+ continue;
79360
+ const turn = mainConversationTurn(r);
79361
+ if (!turn)
79362
+ continue;
79363
+ const text = cleanPrompt(turn.raw);
79364
+ if (!text)
79365
+ continue;
79366
+ out.push({ role, text });
79367
+ if (role === "assistant")
79368
+ ai++;
79369
+ else
79370
+ user++;
79371
+ }
79372
+ return out.reverse();
79373
+ }
79374
+ function hydrateConversation(row) {
79375
+ if (row.conversationDeepened)
79376
+ return row;
79377
+ row.conversationDeepened = true;
79378
+ const turns = row.recentTurns;
79379
+ if (!turns || turns.some((t) => t.role === "user"))
79380
+ return row;
79381
+ const start = Math.max(0, row.sizeBytes - DEEP_TAIL_BYTES);
79382
+ const chunk = readChunk(row.file, start, row.sizeBytes - start);
79383
+ if (!chunk)
79384
+ return row;
79385
+ const lines = chunk.split(`
79386
+ `);
79387
+ if (start > 0)
79388
+ lines.shift();
79389
+ for (let i = lines.length - 1;i >= 0; i--) {
79390
+ const line = lines[i];
79391
+ if (!line.includes('"type":"user"'))
79392
+ continue;
79393
+ let r;
79394
+ try {
79395
+ r = JSON.parse(line);
79396
+ } catch {
79397
+ continue;
79398
+ }
79399
+ const turn = mainConversationTurn(r);
79400
+ if (!turn || turn.role !== "user")
79401
+ continue;
79402
+ const text = cleanPrompt(turn.raw);
79403
+ if (!text)
79404
+ continue;
79405
+ turns.unshift({ role: "user", text });
79406
+ if (row.lastMessageChars === undefined)
79407
+ row.lastMessageChars = text.length;
79408
+ return row;
79409
+ }
79410
+ return row;
79411
+ }
79412
+ function sessionLabel(row) {
79413
+ return row.title || row.firstPrompt || row.id;
79414
+ }
79415
+ function findLatestSessionId(cwd = process.cwd(), sinceMs = 0) {
79416
+ const rows = sessionsIn(slugForPath(cwd)).filter((r) => r.mtimeMs >= sinceMs);
79417
+ if (rows.length === 0)
79418
+ return null;
79419
+ return rows.reduce((a, b) => b.mtimeMs > a.mtimeMs ? b : a).id;
79420
+ }
79421
+ var ENTRYPOINT_BYTES = 8192, PROJECTS_DIR, ACTIVE_WINDOW_MS = 120000, HEAD_BYTES, TAIL_BYTES, HARNESS_ENVELOPES, DEEP_TAIL_BYTES, RECENT_AI_TURNS = 5, RECENT_USER_TURNS = 1;
79422
+ var init_session_discovery = __esm(() => {
79423
+ PROJECTS_DIR = join37(homedir34(), ".claude", "projects");
79424
+ HEAD_BYTES = 64 * 1024;
79425
+ TAIL_BYTES = 128 * 1024;
79426
+ HARNESS_ENVELOPES = [
79427
+ "<task-notification>",
79428
+ "<system-reminder>",
79429
+ "<local-command-stdout>",
79430
+ "<user-prompt-submit-hook>"
79431
+ ];
79432
+ DEEP_TAIL_BYTES = 4 * 1024 * 1024;
79433
+ });
79434
+
79435
+ // src/session/conversation.ts
79436
+ import { closeSync as closeSync6, openSync as openSync6, readSync as readSync2, statSync as statSync7 } from "fs";
79437
+ import { StringDecoder } from "string_decoder";
79438
+ function looksLikeTurn(line) {
79439
+ const assistant = line.includes('"type":"assistant"');
79440
+ if (!assistant && !line.includes('"type":"user"'))
79441
+ return false;
79442
+ if (line.includes('"isSidechain":true'))
79443
+ return false;
79444
+ if (line.includes('"isMeta":true'))
79445
+ return false;
79446
+ if (line.includes('"type":"tool_result"'))
79447
+ return false;
79448
+ if (assistant && !line.includes('"type":"text"'))
79449
+ return false;
79450
+ return true;
79451
+ }
79452
+ function cleanTurnText(raw2) {
79453
+ let t = raw2;
79454
+ for (const re of INLINE_ENVELOPES)
79455
+ t = t.replace(re, "");
79456
+ t = t.replace(/<command-name>([\s\S]*?)<\/command-name>/g, "/$1");
79457
+ return t.replace(/\t/g, " ").replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").replace(/\r\n?/g, `
79458
+ `).split(`
79459
+ `).map((l) => l.replace(/\s+$/, "")).join(`
79460
+ `).replace(/\n{3,}/g, `
79461
+
79462
+ `).trim();
79463
+ }
79464
+ function readConversation(file2, opts = {}) {
79465
+ const maxTurn = opts.maxTurnChars ?? MAX_TURN_CHARS;
79466
+ const maxTotal = opts.maxTotalChars ?? MAX_TOTAL_CHARS;
79467
+ const chunkBytes = opts.chunkBytes ?? CHUNK_BYTES;
79468
+ const started = Date.now();
79469
+ const turns = [];
79470
+ let chars = 0;
79471
+ let dropped = 0;
79472
+ let anyElided = false;
79473
+ let bytes2 = 0;
79474
+ const take = (turn) => {
79475
+ turns.push(turn);
79476
+ chars += turn.text.length;
79477
+ if (chars <= maxTotal)
79478
+ return;
79479
+ let freed = 0;
79480
+ let n = 0;
79481
+ while (n < turns.length - 1 && chars - freed > maxTotal) {
79482
+ freed += turns[n].text.length;
79483
+ n++;
79484
+ }
79485
+ turns.splice(0, n);
79486
+ chars -= freed;
79487
+ dropped += n;
79488
+ };
79489
+ let fd = null;
79490
+ try {
79491
+ const size = statSync7(file2).size;
79492
+ fd = openSync6(file2, "r");
79493
+ const buf = Buffer.allocUnsafe(chunkBytes);
79494
+ const decoder = new StringDecoder("utf-8");
79495
+ let pending = "";
79496
+ let pos = 0;
79497
+ const consume = (line) => {
79498
+ if (!line || !looksLikeTurn(line))
79499
+ return;
79500
+ let record4;
79501
+ try {
79502
+ record4 = JSON.parse(line);
79503
+ } catch {
79504
+ return;
79505
+ }
79506
+ if (!record4 || typeof record4 !== "object")
79507
+ return;
79508
+ const raw2 = mainConversationTurn(record4);
79509
+ if (!raw2)
79510
+ return;
79511
+ let text = cleanTurnText(raw2.raw);
79512
+ if (!text)
79513
+ return;
79514
+ const elided = text.length > maxTurn;
79515
+ if (elided) {
79516
+ anyElided = true;
79517
+ text = `${text.slice(0, maxTurn)}
79518
+
79519
+ \u2026 turn truncated at ${maxTurn.toLocaleString()} characters`;
79520
+ }
79521
+ take({ role: raw2.role, text, elided });
79522
+ };
79523
+ while (pos < size) {
79524
+ const n = readSync2(fd, buf, 0, Math.min(chunkBytes, size - pos), pos);
79525
+ if (n <= 0)
79526
+ break;
79527
+ pos += n;
79528
+ bytes2 += n;
79529
+ pending += decoder.write(buf.subarray(0, n));
79530
+ let from = 0;
79531
+ let nl = pending.indexOf(`
79532
+ `, from);
79533
+ while (nl !== -1) {
79534
+ consume(pending.slice(from, nl));
79535
+ from = nl + 1;
79536
+ nl = pending.indexOf(`
79537
+ `, from);
79538
+ }
79539
+ if (from > 0)
79540
+ pending = pending.slice(from);
79541
+ }
79542
+ pending += decoder.end();
79543
+ if (pending)
79544
+ consume(pending);
79545
+ } catch {} finally {
79546
+ if (fd !== null) {
79547
+ try {
79548
+ closeSync6(fd);
79549
+ } catch {}
79550
+ }
79551
+ }
79552
+ return { turns, bytes: bytes2, elapsedMs: Date.now() - started, chars, dropped, anyElided };
79553
+ }
79554
+ function cpWidth(cp) {
79555
+ if (cp >= 32 && cp < 127)
79556
+ return 1;
79557
+ return displayWidth(String.fromCodePoint(cp));
79558
+ }
79559
+ function wrapOffsets(text, width) {
79560
+ const out = [];
79561
+ const w = Math.max(1, Math.floor(width));
79562
+ const n = text.length;
79563
+ let paraStart = 0;
79564
+ for (;; ) {
79565
+ let nl = text.indexOf(`
79566
+ `, paraStart);
79567
+ if (nl === -1)
79568
+ nl = n;
79569
+ let s = paraStart;
79570
+ for (;; ) {
79571
+ if (s >= nl) {
79572
+ if (s === paraStart || paraStart === nl)
79573
+ out.push({ start: s, end: nl });
79574
+ break;
79575
+ }
79576
+ let cols = 0;
79577
+ let j = s;
79578
+ let lastSpace = -1;
79579
+ while (j < nl) {
79580
+ const cp = text.codePointAt(j);
79581
+ const size = cp > 65535 ? 2 : 1;
79582
+ const cw = cpWidth(cp);
79583
+ if (cols + cw > w)
79584
+ break;
79585
+ if (cp === 32 && j > s && text.charCodeAt(j - 1) !== 32)
79586
+ lastSpace = j;
79587
+ cols += cw;
79588
+ j += size;
79589
+ }
79590
+ if (j >= nl) {
79591
+ out.push({ start: s, end: nl });
79592
+ break;
79593
+ }
79594
+ const brk = text.charCodeAt(j) === 32 ? j : lastSpace > s ? lastSpace : j;
79595
+ out.push({ start: s, end: brk });
79596
+ s = brk === j && text.charCodeAt(j) !== 32 ? j : brk + 1;
79597
+ while (s < nl && text.charCodeAt(s) === 32)
79598
+ s++;
79599
+ }
79600
+ if (nl >= n)
79601
+ break;
79602
+ paraStart = nl + 1;
79603
+ if (paraStart > n)
79604
+ break;
79605
+ if (paraStart === n) {
79606
+ out.push({ start: n, end: n });
79607
+ break;
79608
+ }
79609
+ }
79610
+ return out;
79611
+ }
79612
+ var CHUNK_BYTES, MAX_TURN_CHARS = 32000, MAX_TOTAL_CHARS = 8000000, INLINE_ENVELOPES;
79613
+ var init_conversation = __esm(() => {
79614
+ init_text();
79615
+ init_session_discovery();
79616
+ CHUNK_BYTES = 1024 * 1024;
79617
+ INLINE_ENVELOPES = [
79618
+ /<system-reminder>[\s\S]*?<\/system-reminder>/g,
79619
+ /<command-message>[\s\S]*?<\/command-message>/g,
79620
+ /<command-args>[\s\S]*?<\/command-args>/g,
79621
+ /<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g,
79622
+ /<user-prompt-submit-hook>[\s\S]*?<\/user-prompt-submit-hook>/g
79623
+ ];
79624
+ });
79625
+
79626
+ // src/session/conversation-reader.tsx
79627
+ import { useKeyboard as useKeyboard3 } from "@opentui/react";
79628
+ import { useEffect as useEffect5, useMemo as useMemo3, useState as useState6 } from "react";
79629
+ import { jsxDEV as jsxDEV19, Fragment as Fragment12 } from "@opentui/react/jsx-dev-runtime";
79630
+ function layoutRows(turns, textWidth) {
79631
+ const rows = [];
79632
+ const turnStart = [];
79633
+ const turnEnd = [];
79634
+ for (let t = 0;t < turns.length; t++) {
79635
+ turnStart.push(rows.length);
79636
+ const slices = wrapOffsets(turns[t].text, textWidth);
79637
+ for (let i = 0;i < slices.length; i++) {
79638
+ rows.push({ turn: t, first: i === 0, start: slices[i].start, end: slices[i].end });
79639
+ }
79640
+ turnEnd.push(rows.length);
79641
+ if (t < turns.length - 1)
79642
+ rows.push({ turn: -1, first: false, start: 0, end: 0 });
79643
+ }
79644
+ return { rows, turnStart, turnEnd };
79645
+ }
79646
+ function buildSearch(turns, rows, turnStart, turnEnd, query) {
79647
+ if (!query)
79648
+ return EMPTY_SEARCH;
79649
+ const q = query.toLowerCase();
79650
+ const hits = [];
79651
+ const ranges = new Map;
79652
+ let capped = false;
79653
+ for (let t = 0;t < turns.length && !capped; t++) {
79654
+ const hay = turns[t].text.toLowerCase();
79655
+ let at = hay.indexOf(q);
79656
+ if (at === -1)
79657
+ continue;
79658
+ const lo = turnStart[t];
79659
+ const hi = turnEnd[t];
79660
+ let r = lo;
79661
+ while (at !== -1) {
79662
+ if (hits.length >= MAX_MATCHES) {
79663
+ capped = true;
79664
+ break;
79665
+ }
79666
+ const end = at + q.length;
79667
+ while (r < hi - 1 && rows[r].end <= at)
79668
+ r++;
79669
+ const hit = hits.length;
79670
+ hits.push(r);
79671
+ for (let k = r;k < hi; k++) {
79672
+ const row = rows[k];
79673
+ if (row.start >= end)
79674
+ break;
79675
+ const s = Math.max(at, row.start);
79676
+ const e = Math.min(end, row.end);
79677
+ if (e > s) {
79678
+ const list = ranges.get(k);
79679
+ if (list)
79680
+ list.push({ s: s - row.start, e: e - row.start, hit });
79681
+ else
79682
+ ranges.set(k, [{ s: s - row.start, e: e - row.start, hit }]);
79683
+ }
79684
+ }
79685
+ at = hay.indexOf(q, at + 1);
79686
+ }
79687
+ }
79688
+ return { hits, ranges, capped };
79689
+ }
79690
+ function ConversationReader({
79691
+ row,
79692
+ conv,
79693
+ width,
79694
+ height: height2,
79695
+ onClose,
79696
+ onResume,
79697
+ onCancel
79698
+ }) {
79699
+ const [top, setTop] = useState6(0);
79700
+ const [query, setQuery] = useState6("");
79701
+ const [typing, setTyping] = useState6(false);
79702
+ const [matchIdx, setMatchIdx] = useState6(0);
79703
+ const CHROME_ROWS = 5;
79704
+ const viewport = Math.max(1, height2 - CHROME_ROWS);
79705
+ const inner = Math.max(20, width - 2);
79706
+ const contentW = inner - BAR_W;
79707
+ const textW = Math.max(8, contentW - GUTTER - 1);
79708
+ const turns = useMemo3(() => conv?.turns ?? NO_TURNS, [conv]);
79709
+ const { rows, turnStart, turnEnd } = useMemo3(() => layoutRows(turns, textW), [turns, textW]);
79710
+ const search = useMemo3(() => buildSearch(turns, rows, turnStart, turnEnd, query), [turns, rows, turnStart, turnEnd, query]);
79711
+ const maxTop = Math.max(0, rows.length - viewport);
79712
+ const clampTop = (v) => Math.max(0, Math.min(v, maxTop));
79713
+ const reveal = (r) => setTop(clampTop(r - Math.floor(viewport / 3)));
79714
+ useEffect5(() => {
79715
+ setTop(Math.max(0, rows.length - viewport));
79716
+ }, [rows.length, viewport]);
79717
+ useEffect5(() => {
79718
+ if (search.hits.length === 0)
79719
+ return;
79720
+ let i = search.hits.findIndex((r) => r >= top);
79721
+ if (i === -1)
79722
+ i = 0;
79723
+ setMatchIdx(i);
79724
+ reveal(search.hits[i]);
79725
+ }, [search]);
79726
+ const step = (d) => {
79727
+ if (search.hits.length === 0)
79728
+ return;
79729
+ const i = (matchIdx + d + search.hits.length) % search.hits.length;
79730
+ setMatchIdx(i);
79731
+ reveal(search.hits[i]);
79732
+ };
79733
+ useKeyboard3((key) => {
79734
+ const name = key.name;
79735
+ if (key.ctrl && name === "c") {
79736
+ onCancel();
79737
+ return;
79738
+ }
79739
+ if (name === "escape") {
79740
+ if (typing || query) {
79741
+ setTyping(false);
79742
+ setQuery("");
79743
+ return;
79744
+ }
79745
+ onClose();
79746
+ return;
79747
+ }
79748
+ if (name === "return" || name === "enter") {
79749
+ if (typing) {
79750
+ setTyping(false);
79751
+ return;
79752
+ }
79753
+ onResume();
79754
+ return;
79755
+ }
79756
+ if (name === "up")
79757
+ return setTop((t) => clampTop(t - 1));
79758
+ if (name === "down")
79759
+ return setTop((t) => clampTop(t + 1));
79760
+ if (name === "pageup")
79761
+ return setTop((t) => clampTop(t - viewport));
79762
+ if (name === "pagedown")
79763
+ return setTop((t) => clampTop(t + viewport));
79764
+ if (name === "home")
79765
+ return setTop(0);
79766
+ if (name === "end")
79767
+ return setTop(maxTop);
79768
+ if (name === "backspace") {
79769
+ if (typing)
79770
+ setQuery((q) => q.slice(0, -1));
79771
+ return;
79772
+ }
79773
+ const ch = key.raw;
79774
+ const printable = ch && ch.length === 1 && ch >= " " && ch !== "\x7F";
79775
+ if (typing) {
79776
+ if (printable)
79777
+ setQuery((q) => q + ch);
79778
+ return;
79779
+ }
79780
+ if (name === "slash" || ch === "/") {
79781
+ setTyping(true);
79782
+ return;
79783
+ }
79784
+ if (name === "n")
79785
+ return step(key.shift ? -1 : 1);
79786
+ if (ch === "g")
79787
+ return setTop(0);
79788
+ if (ch === "G")
79789
+ return setTop(maxTop);
79790
+ if (name === "space")
79791
+ return setTop((t) => clampTop(t + viewport));
79792
+ });
79793
+ const label = sessionLabel(row);
79794
+ const mb = row.sizeBytes / 1048576;
79795
+ const size = mb >= 0.1 ? `${mb.toFixed(1)} MB` : `${Math.max(1, Math.round(row.sizeBytes / 1024))} KB`;
79796
+ const stats = conv ? `${turns.length} turns \xB7 ${rows.length} lines \xB7 ${size} transcript` : `reading ${size}\u2026`;
79797
+ const visible = rows.slice(top, top + viewport);
79798
+ const barCells = useMemo3(() => scrollbarCells(viewport, rows.length, top, search.hits), [viewport, rows.length, top, search.hits]);
79799
+ const pct = rows.length <= viewport ? 100 : top / maxTop * 100;
79800
+ const POSITION_COLS = 26;
79801
+ const matchLabel = search.hits.length ? `${matchIdx + 1} of ${search.hits.length}${search.capped ? "+" : ""} matches` : query ? "no matches" : "";
79802
+ return /* @__PURE__ */ jsxDEV19("box", {
79803
+ flexDirection: "column",
79804
+ height: height2,
79805
+ backgroundColor: C.bg,
79806
+ children: [
79807
+ /* @__PURE__ */ jsxDEV19("box", {
79808
+ flexDirection: "row",
79809
+ justifyContent: "space-between",
79810
+ height: 1,
79811
+ paddingX: 1,
79812
+ children: [
79813
+ /* @__PURE__ */ jsxDEV19("text", {
79814
+ children: [
79815
+ /* @__PURE__ */ jsxDEV19("span", {
79816
+ fg: tokens.accent,
79817
+ attributes: A.bold,
79818
+ children: "claudish"
79819
+ }, undefined, false, undefined, this),
79820
+ /* @__PURE__ */ jsxDEV19("span", {
79821
+ fg: tokens.subtle,
79822
+ children: " reader"
79823
+ }, undefined, false, undefined, this),
79824
+ conv && conv.dropped > 0 ? /* @__PURE__ */ jsxDEV19("span", {
79825
+ fg: tokens.warn,
79826
+ children: ` ${conv.dropped} older turns dropped (cap)`
79827
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV19("span", {}, undefined, false, undefined, this),
79828
+ conv?.anyElided ? /* @__PURE__ */ jsxDEV19("span", {
79829
+ fg: tokens.warn,
79830
+ children: " long turns truncated"
79831
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV19("span", {}, undefined, false, undefined, this)
79832
+ ]
79833
+ }, undefined, true, undefined, this),
79834
+ /* @__PURE__ */ jsxDEV19("text", {
79835
+ children: /* @__PURE__ */ jsxDEV19("span", {
79836
+ fg: tokens.subtle,
79837
+ children: stats
79838
+ }, undefined, false, undefined, this)
79839
+ }, undefined, false, undefined, this)
79840
+ ]
79841
+ }, undefined, true, undefined, this),
79842
+ /* @__PURE__ */ jsxDEV19(Panel, {
79843
+ title: `conversation \xB7 ${truncate3(label, Math.max(10, width - 20))}`,
79844
+ flush: true,
79845
+ flexGrow: 1,
79846
+ flexBasis: 0,
79847
+ children: !conv ? /* @__PURE__ */ jsxDEV19("text", {
79848
+ fg: tokens.subtle,
79849
+ children: " reading transcript\u2026"
79850
+ }, undefined, false, undefined, this) : rows.length === 0 ? /* @__PURE__ */ jsxDEV19("text", {
79851
+ fg: tokens.trace,
79852
+ children: " no conversation recorded \u2014 this transcript has no main-thread prose"
79853
+ }, undefined, false, undefined, this) : visible.map((r, i) => {
79854
+ const line = top + i;
79855
+ return /* @__PURE__ */ jsxDEV19(ReaderRow, {
79856
+ row: r,
79857
+ turn: r.turn >= 0 ? turns[r.turn] : undefined,
79858
+ hl: search.ranges.get(line),
79859
+ current: matchIdx,
79860
+ width: contentW,
79861
+ bar: barCells[i] ?? "track"
79862
+ }, line, false, undefined, this);
79863
+ })
79864
+ }, undefined, false, undefined, this),
79865
+ /* @__PURE__ */ jsxDEV19("box", {
79866
+ flexDirection: "row",
79867
+ justifyContent: "space-between",
79868
+ height: 1,
79869
+ paddingX: 1,
79870
+ children: [
79871
+ /* @__PURE__ */ jsxDEV19("text", {
79872
+ children: query || typing ? /* @__PURE__ */ jsxDEV19(Fragment12, {
79873
+ children: [
79874
+ /* @__PURE__ */ jsxDEV19("span", {
79875
+ fg: tokens.warn,
79876
+ attributes: A.bold,
79877
+ children: truncate3(`/${query}`, Math.max(4, width - POSITION_COLS - displayWidth(matchLabel) - 4))
79878
+ }, undefined, false, undefined, this),
79879
+ /* @__PURE__ */ jsxDEV19("span", {
79880
+ fg: typing ? tokens.warn : tokens.trace,
79881
+ children: typing ? "\u258C" : " "
79882
+ }, undefined, false, undefined, this),
79883
+ /* @__PURE__ */ jsxDEV19("span", {
79884
+ fg: search.hits.length ? tokens.subtle : tokens.trace,
79885
+ children: matchLabel ? ` ${matchLabel}` : ""
79886
+ }, undefined, false, undefined, this)
79887
+ ]
79888
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV19("span", {
79889
+ fg: tokens.trace,
79890
+ children: "/ to search"
79891
+ }, undefined, false, undefined, this)
79892
+ }, undefined, false, undefined, this),
79893
+ /* @__PURE__ */ jsxDEV19("text", {
79894
+ children: [
79895
+ /* @__PURE__ */ jsxDEV19("span", {
79896
+ fg: tokens.subtle,
79897
+ children: `${Math.min(rows.length, top + viewport)}/${rows.length} `
79898
+ }, undefined, false, undefined, this),
79899
+ /* @__PURE__ */ jsxDEV19(MeterSpan, {
79900
+ pct,
79901
+ width: 12,
79902
+ ramp: ramps.volume
79903
+ }, undefined, false, undefined, this)
79904
+ ]
79905
+ }, undefined, true, undefined, this)
79906
+ ]
79907
+ }, undefined, true, undefined, this),
79908
+ /* @__PURE__ */ jsxDEV19("box", {
79909
+ flexDirection: "row",
79910
+ height: 1,
79911
+ paddingX: 1,
79912
+ gap: 2,
79913
+ children: [
79914
+ /* @__PURE__ */ jsxDEV19("text", {
79915
+ fg: tokens.subtle,
79916
+ children: "\u2191\u2193 \u21DE\u21DF scroll"
79917
+ }, undefined, false, undefined, this),
79918
+ /* @__PURE__ */ jsxDEV19("text", {
79919
+ fg: tokens.subtle,
79920
+ children: "g/G ends"
79921
+ }, undefined, false, undefined, this),
79922
+ /* @__PURE__ */ jsxDEV19("text", {
79923
+ fg: typing ? tokens.warn : tokens.subtle,
79924
+ children: "/ search"
79925
+ }, undefined, false, undefined, this),
79926
+ /* @__PURE__ */ jsxDEV19("text", {
79927
+ fg: search.hits.length ? tokens.subtle : tokens.trace,
79928
+ children: "n/N match"
79929
+ }, undefined, false, undefined, this),
79930
+ /* @__PURE__ */ jsxDEV19("text", {
79931
+ fg: tokens.accent,
79932
+ children: "\u23CE resume"
79933
+ }, undefined, false, undefined, this),
79934
+ /* @__PURE__ */ jsxDEV19("text", {
79935
+ fg: tokens.subtle,
79936
+ children: "esc back"
79937
+ }, undefined, false, undefined, this)
79938
+ ]
79939
+ }, undefined, true, undefined, this)
79940
+ ]
79941
+ }, undefined, true, undefined, this);
79942
+ }
79943
+ function scrollbarCells(viewport, total, top, hits) {
79944
+ const cells = new Array(viewport).fill("track");
79945
+ if (total <= 0)
79946
+ return cells;
79947
+ if (total > viewport) {
79948
+ const size = Math.max(1, Math.round(viewport / total * viewport));
79949
+ const span = Math.max(1, total - viewport);
79950
+ const start = Math.min(viewport - size, Math.round(top / span * (viewport - size)));
79951
+ for (let i = 0;i < size; i++)
79952
+ cells[start + i] = "thumb";
79953
+ } else {
79954
+ cells.fill("thumb");
79955
+ }
79956
+ for (const h of hits) {
79957
+ const i = Math.min(viewport - 1, Math.floor(h / total * viewport));
79958
+ if (i >= 0)
79959
+ cells[i] = "hit";
79960
+ }
79961
+ return cells;
79962
+ }
79963
+ function ReaderRow({
79964
+ row,
79965
+ turn,
79966
+ hl,
79967
+ current,
79968
+ width,
79969
+ bar
79970
+ }) {
79971
+ if (!turn) {
79972
+ return /* @__PURE__ */ jsxDEV19("text", {
79973
+ children: [
79974
+ /* @__PURE__ */ jsxDEV19("span", {
79975
+ children: " ".repeat(Math.max(0, width))
79976
+ }, undefined, false, undefined, this),
79977
+ /* @__PURE__ */ jsxDEV19("span", {
79978
+ bg: BAR_COLOR[bar],
79979
+ children: " "
79980
+ }, undefined, false, undefined, this)
79981
+ ]
79982
+ }, undefined, true, undefined, this);
79983
+ }
79984
+ const text = turn.text.slice(row.start, row.end);
79985
+ const fg = turn.role === "user" ? tokens.text : C.fgMuted;
79986
+ const railFg = turn.role === "user" ? SPEAKER.you : SPEAKER.ai;
79987
+ const pad2 = Math.max(0, width - GUTTER - displayWidth(text));
79988
+ return /* @__PURE__ */ jsxDEV19("text", {
79989
+ children: [
79990
+ /* @__PURE__ */ jsxDEV19("span", {
79991
+ fg: railFg,
79992
+ children: RAIL
79993
+ }, undefined, false, undefined, this),
79994
+ row.first ? /* @__PURE__ */ jsxDEV19(BadgeSpan, {
79995
+ label: turn.role === "user" ? "you" : "ai",
79996
+ bg: turn.role === "user" ? SPEAKER.you : SPEAKER.ai,
79997
+ width: ROLE_W
79998
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV19("span", {
79999
+ children: " ".repeat(ROLE_W)
80000
+ }, undefined, false, undefined, this),
80001
+ hl && hl.length > 0 ? highlighted(text, hl, current, fg) : /* @__PURE__ */ jsxDEV19("span", {
80002
+ fg,
80003
+ children: text
80004
+ }, undefined, false, undefined, this),
80005
+ /* @__PURE__ */ jsxDEV19("span", {
80006
+ children: " ".repeat(pad2)
80007
+ }, undefined, false, undefined, this),
80008
+ /* @__PURE__ */ jsxDEV19("span", {
80009
+ bg: BAR_COLOR[bar],
80010
+ children: " "
80011
+ }, undefined, false, undefined, this)
80012
+ ]
80013
+ }, undefined, true, undefined, this);
80014
+ }
80015
+ function highlighted(text, hl, current, fg) {
80016
+ const out = [];
80017
+ let from = 0;
80018
+ for (let i = 0;i < hl.length; i++) {
80019
+ const { s, e, hit } = hl[i];
80020
+ if (e <= from)
80021
+ continue;
80022
+ const start = Math.max(from, s);
80023
+ if (start > from)
80024
+ out.push(/* @__PURE__ */ jsxDEV19("span", {
80025
+ fg,
80026
+ children: text.slice(from, start)
80027
+ }, `p${i}`, false, undefined, this));
80028
+ const bg = hit === current ? tokens.accent : tokens.warn;
80029
+ out.push(/* @__PURE__ */ jsxDEV19("span", {
80030
+ fg: tokens.ink,
80031
+ bg,
80032
+ attributes: A.bold,
80033
+ children: text.slice(start, e)
80034
+ }, `h${i}`, false, undefined, this));
80035
+ from = e;
80036
+ }
80037
+ if (from < text.length)
80038
+ out.push(/* @__PURE__ */ jsxDEV19("span", {
80039
+ fg,
80040
+ children: text.slice(from)
80041
+ }, "tail", false, undefined, this));
80042
+ return /* @__PURE__ */ jsxDEV19(Fragment12, {
80043
+ children: out
80044
+ }, undefined, false, undefined, this);
80045
+ }
80046
+ var RAIL = "\u258D", RAIL_W = 2, ROLE_W = 6, GUTTER, BAR_W = 1, SPEAKER, MAX_MATCHES = 5000, EMPTY_SEARCH, NO_TURNS, BAR_COLOR;
80047
+ var init_conversation_reader = __esm(() => {
80048
+ init_theme2();
80049
+ init_text();
80050
+ init_tokens();
80051
+ init_widgets();
80052
+ init_conversation();
80053
+ init_session_discovery();
80054
+ GUTTER = RAIL_W + ROLE_W;
80055
+ SPEAKER = {
80056
+ you: "#39d353",
80057
+ ai: "#39c5cf"
80058
+ };
80059
+ EMPTY_SEARCH = { hits: [], ranges: new Map, capped: false };
80060
+ NO_TURNS = [];
80061
+ BAR_COLOR = {
80062
+ track: tokens.border,
80063
+ thumb: tokens.accent,
80064
+ hit: tokens.warn
80065
+ };
80066
+ });
80067
+
80068
+ // src/session/resume-picker.tsx
80069
+ import { useKeyboard as useKeyboard4, useTerminalDimensions as useTerminalDimensions3 } from "@opentui/react";
80070
+ import { useEffect as useEffect6, useMemo as useMemo4, useState as useState7 } from "react";
80071
+ import { jsxDEV as jsxDEV20 } from "@opentui/react/jsx-dev-runtime";
80072
+ function detailTurns(height2) {
80073
+ return height2 >= 40 ? 6 : height2 >= 30 ? 4 : 2;
80074
+ }
80075
+ function fuzzy(needle, hay) {
80076
+ if (!needle)
80077
+ return true;
80078
+ const n = needle.toLowerCase();
80079
+ const h = hay.toLowerCase();
80080
+ let i = 0;
80081
+ for (let j = 0;j < h.length && i < n.length; j++)
80082
+ if (h[j] === n[i])
80083
+ i++;
80084
+ return i === n.length;
80085
+ }
80086
+ function age(ms) {
80087
+ const s = Math.max(0, Math.round((Date.now() - ms) / 1000));
80088
+ if (s < 60)
80089
+ return `${s}s`;
80090
+ const m = Math.floor(s / 60);
80091
+ if (m < 60)
80092
+ return `${m}m`;
80093
+ const h = Math.floor(m / 60);
80094
+ if (h < 24)
80095
+ return `${h}h`;
80096
+ return `${Math.floor(h / 24)}d`;
80097
+ }
80098
+ function chipW(label) {
80099
+ return label + 2;
80100
+ }
80101
+ function blockWidth(c) {
80102
+ return LIVE_W + chipW(c.age) + chipW(c.count) + (c.dirty > 0 ? chipW(c.dirty + 1) : 0) + (c.sync > 0 ? 2 * chipW(c.sync + 1) : 0);
80103
+ }
80104
+ function sizePct(bytes2, max) {
80105
+ const lo = Math.log(SIZE_FLOOR_BYTES);
80106
+ const hi = Math.log(Math.max(max, SIZE_FLOOR_BYTES * 2));
80107
+ const v = Math.log(Math.max(bytes2, SIZE_FLOOR_BYTES));
80108
+ return Math.max(0, Math.min(100, (v - lo) / (hi - lo) * 100));
80109
+ }
80110
+ function Slot({
80111
+ label,
80112
+ bg,
80113
+ labelW
80114
+ }) {
80115
+ if (!label || !bg)
80116
+ return /* @__PURE__ */ jsxDEV20("span", {
80117
+ children: " ".repeat(chipW(labelW))
80118
+ }, undefined, false, undefined, this);
80119
+ return /* @__PURE__ */ jsxDEV20(BadgeSpan, {
80120
+ label,
80121
+ bg
80122
+ }, undefined, false, undefined, this);
80123
+ }
80124
+ function WorktreeRow({
80125
+ g,
80126
+ cursor,
80127
+ width,
80128
+ count,
80129
+ cols
80130
+ }) {
80131
+ const nameColor = !g.live ? tokens.dead : g.current ? tokens.success : tokens.text;
80132
+ const stale = !g.lastActiveMs || Date.now() - g.lastActiveMs >= STALE_MS;
80133
+ const room = Math.max(0, width - blockWidth(cols));
80134
+ const series = room >= SPARK_MIN_DAYS ? activitySeries(g.sessions, room) : null;
80135
+ const spark = series && hasActivity(series) ? series : null;
80136
+ const chips = [];
80137
+ if (cols.sync > 0 && g.ahead) {
80138
+ chips.push({
80139
+ label: `\u2191${padStartTo(String(g.ahead), cols.sync)}`,
80140
+ bg: CHIP.ahead,
80141
+ labelW: cols.sync + 1
80142
+ });
80143
+ }
80144
+ if (cols.sync > 0 && g.behind) {
80145
+ chips.push({
80146
+ label: `\u2193${padStartTo(String(g.behind), cols.sync)}`,
80147
+ bg: CHIP.behind,
80148
+ labelW: cols.sync + 1
80149
+ });
80150
+ }
80151
+ if (cols.dirty > 0 && g.dirty) {
80152
+ chips.push({
80153
+ label: `${DIRTY_GLYPH}${padStartTo(String(g.dirty), cols.dirty)}`,
80154
+ bg: CHIP.dirty,
80155
+ labelW: cols.dirty + 1
80156
+ });
80157
+ }
80158
+ chips.push({ label: padStartTo(String(count), cols.count), bg: CHIP.count, labelW: cols.count });
80159
+ chips.push({
80160
+ label: padStartTo(g.lastActiveMs ? age(g.lastActiveMs) : "\u2014", cols.age),
80161
+ bg: stale ? CHIP.stale : CHIP.fresh,
80162
+ labelW: cols.age
80163
+ });
80164
+ const used = chips.reduce((w, c) => w + c.labelW + 2, 0) + LIVE_W;
80165
+ const gap = Math.max(0, width - room - used);
80166
+ return /* @__PURE__ */ jsxDEV20("box", {
80167
+ flexDirection: "column",
80168
+ height: 2,
80169
+ backgroundColor: cursor ? C.bgHighlight : undefined,
80170
+ children: [
80171
+ /* @__PURE__ */ jsxDEV20("text", {
80172
+ children: /* @__PURE__ */ jsxDEV20("span", {
80173
+ fg: nameColor,
80174
+ attributes: cursor || g.current ? A.bold : undefined,
80175
+ children: truncate3(g.name, width)
80176
+ }, undefined, false, undefined, this)
80177
+ }, undefined, false, undefined, this),
80178
+ /* @__PURE__ */ jsxDEV20("box", {
80179
+ height: 1,
80180
+ children: /* @__PURE__ */ jsxDEV20("text", {
80181
+ children: [
80182
+ spark ? /* @__PURE__ */ jsxDEV20(SparklineSpan, {
80183
+ values: spark,
80184
+ fg: SPARK_FG
80185
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV20("span", {
80186
+ children: " ".repeat(room)
80187
+ }, undefined, false, undefined, this),
80188
+ /* @__PURE__ */ jsxDEV20("span", {
80189
+ children: " ".repeat(gap)
80190
+ }, undefined, false, undefined, this),
80191
+ /* @__PURE__ */ jsxDEV20("span", {
80192
+ fg: tokens.success,
80193
+ children: g.activeNow ? "\u25CF " : " "
80194
+ }, undefined, false, undefined, this),
80195
+ chips.map((c) => /* @__PURE__ */ jsxDEV20(Slot, {
80196
+ label: c.label,
80197
+ bg: c.bg,
80198
+ labelW: c.labelW
80199
+ }, c.bg + c.label, false, undefined, this))
80200
+ ]
80201
+ }, undefined, true, undefined, this)
80202
+ }, undefined, false, undefined, this)
80203
+ ]
80204
+ }, undefined, true, undefined, this);
80205
+ }
80206
+ function dailyActivity(groups, days) {
80207
+ const day = 86400000;
80208
+ const today = Math.floor(Date.now() / day);
80209
+ const buckets = new Array(days).fill(0);
80210
+ for (const g of groups) {
80211
+ for (const s of g.sessions) {
80212
+ const idx = days - 1 - (today - Math.floor(s.mtimeMs / day));
80213
+ if (idx >= 0 && idx < days)
80214
+ buckets[idx] += 1;
80215
+ }
80216
+ }
80217
+ return buckets;
80218
+ }
80219
+ function activityLevels(days) {
80220
+ const nz = days.filter((v) => v > 0).sort((a, b) => a - b);
80221
+ if (nz.length === 0)
80222
+ return days.map(() => 0);
80223
+ const at = (p) => nz[Math.min(nz.length - 1, Math.floor(nz.length * p))];
80224
+ const q1 = at(0.25);
80225
+ const q2 = at(0.5);
80226
+ const q3 = at(0.75);
80227
+ return days.map((v) => v <= 0 ? 0 : v <= q1 ? 1 : v <= q2 ? 2 : v <= q3 ? 3 : 4);
80228
+ }
80229
+ function ActivityCalendar({
80230
+ days,
80231
+ width
80232
+ }) {
80233
+ const levels = activityLevels(days);
80234
+ const title = `activity \xB7 ${ACTIVITY_WEEKS}w `;
80235
+ const grid = Math.max(WEEK_DAYS, width - WEEK_LABEL_W);
80236
+ const base = Math.floor(grid / WEEK_DAYS);
80237
+ const extra = grid - base * WEEK_DAYS;
80238
+ return /* @__PURE__ */ jsxDEV20("box", {
80239
+ flexDirection: "column",
80240
+ flexShrink: 0,
80241
+ paddingTop: 1,
80242
+ children: [
80243
+ /* @__PURE__ */ jsxDEV20("text", {
80244
+ children: [
80245
+ /* @__PURE__ */ jsxDEV20("span", {
80246
+ fg: tokens.subtle,
80247
+ attributes: A.bold,
80248
+ children: title
80249
+ }, undefined, false, undefined, this),
80250
+ /* @__PURE__ */ jsxDEV20("span", {
80251
+ fg: tokens.border,
80252
+ children: "\u2500".repeat(Math.max(0, width - title.length))
80253
+ }, undefined, false, undefined, this)
80254
+ ]
80255
+ }, undefined, true, undefined, this),
80256
+ Array.from({ length: ACTIVITY_WEEKS }, (_, w) => {
80257
+ const ago = ACTIVITY_WEEKS - 1 - w;
80258
+ return /* @__PURE__ */ jsxDEV20("text", {
80259
+ children: [
80260
+ /* @__PURE__ */ jsxDEV20("span", {
80261
+ fg: tokens.trace,
80262
+ children: padTo(ago === 0 ? "now" : `-${ago}w`, WEEK_LABEL_W)
80263
+ }, undefined, false, undefined, this),
80264
+ Array.from({ length: WEEK_DAYS }, (_2, d) => /* @__PURE__ */ jsxDEV20("span", {
80265
+ bg: GH_LEVELS[levels[w * WEEK_DAYS + d] ?? 0],
80266
+ children: " ".repeat(base + (d < extra ? 1 : 0))
80267
+ }, d, false, undefined, this))
80268
+ ]
80269
+ }, w, true, undefined, this);
80270
+ })
80271
+ ]
80272
+ }, undefined, true, undefined, this);
80273
+ }
80274
+ function SectionHeader({ label, width }) {
80275
+ const text = `${label} `;
80276
+ return /* @__PURE__ */ jsxDEV20("box", {
80277
+ flexDirection: "column",
80278
+ height: 2,
80279
+ children: [
80280
+ /* @__PURE__ */ jsxDEV20("text", {
80281
+ children: " "
80282
+ }, undefined, false, undefined, this),
80283
+ /* @__PURE__ */ jsxDEV20("text", {
80284
+ children: [
80285
+ /* @__PURE__ */ jsxDEV20("span", {
80286
+ fg: tokens.subtle,
80287
+ attributes: A.bold,
80288
+ children: text
80289
+ }, undefined, false, undefined, this),
80290
+ /* @__PURE__ */ jsxDEV20("span", {
80291
+ fg: tokens.border,
80292
+ children: "\u2500".repeat(Math.max(0, width - text.length))
80293
+ }, undefined, false, undefined, this)
80294
+ ]
80295
+ }, undefined, true, undefined, this)
80296
+ ]
80297
+ }, undefined, true, undefined, this);
80298
+ }
80299
+ function hasActivity(series) {
80300
+ return series.some((v) => v > 0);
80301
+ }
80302
+ function activitySeries(sessions2, days = WEEK_DAYS * 2) {
80303
+ const day = 86400000;
80304
+ const today = Math.floor(Date.now() / day);
80305
+ const buckets = new Array(days).fill(0);
80306
+ for (const s of sessions2) {
80307
+ const idx = days - 1 - (today - Math.floor(s.mtimeMs / day));
80308
+ if (idx >= 0 && idx < days)
80309
+ buckets[idx] += 1;
80310
+ }
80311
+ return buckets;
80312
+ }
80313
+ function SessionRowView({
80314
+ row,
80315
+ cursor,
80316
+ width,
80317
+ even,
80318
+ indent = 0,
80319
+ maxSize,
80320
+ meterW
80321
+ }) {
80322
+ const live = isActive(row);
80323
+ const pad2 = " ".repeat(indent);
80324
+ const mb = row.sizeBytes / 1048576;
80325
+ const size = mb >= 0.1 ? `${mb.toFixed(1)} MB` : `${Math.max(1, Math.round(row.sizeBytes / 1024))} KB`;
80326
+ const titleW = Math.max(10, width - indent - 2);
80327
+ const metaPad = `${pad2} `;
80328
+ const SIZE_COL = 8;
80329
+ return /* @__PURE__ */ jsxDEV20("box", {
80330
+ flexDirection: "column",
80331
+ height: 2,
80332
+ backgroundColor: cursor ? C.bgHighlight : even ? undefined : C.bgAlt,
80333
+ children: [
80334
+ /* @__PURE__ */ jsxDEV20("text", {
80335
+ children: [
80336
+ /* @__PURE__ */ jsxDEV20("span", {
80337
+ fg: live ? tokens.success : tokens.border,
80338
+ children: `${pad2}${live ? "\u25CF" : "\xB7"} `
80339
+ }, undefined, false, undefined, this),
80340
+ /* @__PURE__ */ jsxDEV20("span", {
80341
+ fg: tokens.text,
80342
+ attributes: cursor ? A.bold : undefined,
80343
+ children: truncate3(sessionLabel(row), titleW)
80344
+ }, undefined, false, undefined, this)
80345
+ ]
80346
+ }, undefined, true, undefined, this),
80347
+ /* @__PURE__ */ jsxDEV20("text", {
80348
+ children: [
80349
+ /* @__PURE__ */ jsxDEV20("span", {
80350
+ children: metaPad
80351
+ }, undefined, false, undefined, this),
80352
+ /* @__PURE__ */ jsxDEV20(BadgeSpan, {
80353
+ label: padStartTo(age(row.mtimeMs), SESSION_AGE_W),
80354
+ bg: Date.now() - row.mtimeMs < STALE_MS ? CHIP.fresh : CHIP.stale,
80355
+ width: SESSION_AGE_COL
80356
+ }, undefined, false, undefined, this),
80357
+ /* @__PURE__ */ jsxDEV20(MeterSpan, {
80358
+ pct: sizePct(row.sizeBytes, maxSize),
80359
+ width: meterW,
80360
+ ramp: ramps.volume
80361
+ }, undefined, false, undefined, this),
80362
+ /* @__PURE__ */ jsxDEV20("span", {
80363
+ fg: MUTED,
80364
+ children: padStartTo(size, SIZE_COL)
80365
+ }, undefined, false, undefined, this),
80366
+ row.gitBranch ? /* @__PURE__ */ jsxDEV20("span", {
80367
+ fg: tokens.trace,
80368
+ children: ` ${BRANCH_ICON} ${truncate3(row.gitBranch, Math.max(6, width - indent - 2 - SESSION_AGE_COL - meterW - SIZE_COL - BRANCH_LEAD_DENSE))}`
80369
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV20("span", {}, undefined, false, undefined, this)
80370
+ ]
80371
+ }, undefined, true, undefined, this)
80372
+ ]
80373
+ }, undefined, true, undefined, this);
80374
+ }
80375
+ function AgentNode({
80376
+ count,
80377
+ open,
80378
+ cursor,
80379
+ width
80380
+ }) {
80381
+ const label = `${count} agent session${count === 1 ? "" : "s"}`;
80382
+ const hint = open ? "enter to collapse" : "enter to expand \xB7 showing 3";
80383
+ return /* @__PURE__ */ jsxDEV20("box", {
80384
+ height: 1,
80385
+ backgroundColor: cursor ? C.bgHighlight : undefined,
80386
+ children: /* @__PURE__ */ jsxDEV20("text", {
80387
+ children: [
80388
+ /* @__PURE__ */ jsxDEV20("span", {
80389
+ fg: cursor ? tokens.accent : tokens.trace,
80390
+ children: `${cursor ? "\u258D" : " "} `
80391
+ }, undefined, false, undefined, this),
80392
+ /* @__PURE__ */ jsxDEV20("span", {
80393
+ fg: tokens.warn,
80394
+ children: open ? "\u25BE " : "\u25B8 "
80395
+ }, undefined, false, undefined, this),
80396
+ /* @__PURE__ */ jsxDEV20("span", {
80397
+ fg: cursor ? tokens.text : tokens.subtle,
80398
+ children: label
80399
+ }, undefined, false, undefined, this),
80400
+ /* @__PURE__ */ jsxDEV20("span", {
80401
+ fg: tokens.trace,
80402
+ children: truncate3(` ${hint}`, Math.max(0, width - label.length - 6))
80403
+ }, undefined, false, undefined, this)
80404
+ ]
80405
+ }, undefined, true, undefined, this)
80406
+ }, undefined, false, undefined, this);
80407
+ }
80408
+ function ResumePicker({ groups, onDone }) {
80409
+ const { width, height: height2 } = useTerminalDimensions3();
80410
+ const [pane, setPane] = useState7("worktrees");
80411
+ const [wtCursor, setWtCursor] = useState7(0);
80412
+ const [sessCursor, setSessCursor] = useState7(0);
80413
+ const [filter, setFilter] = useState7("");
80414
+ const [agentsOpen, setAgentsOpen] = useState7(false);
80415
+ const [reader, setReader] = useState7(null);
80416
+ const [, setTick] = useState7(0);
80417
+ const listed = useMemo4(() => {
80418
+ const m = new Map;
80419
+ for (const g of groups)
80420
+ m.set(g.name, g.sessions.filter((s) => !isAgentSession(s)));
80421
+ return m;
80422
+ }, [groups]);
80423
+ const { fresh, stale, visibleGroups } = useMemo4(() => {
80424
+ const withSessions = groups.filter((g) => (listed.get(g.name)?.length ?? 0) > 0);
80425
+ const matching = filter ? withSessions.filter((g) => fuzzy(filter, g.name)) : withSessions;
80426
+ const now = Date.now();
80427
+ const byRecency = (a, b) => b.lastActiveMs - a.lastActiveMs;
80428
+ const f = matching.filter((g) => g.current || now - g.lastActiveMs < STALE_MS).sort((a, b) => a.current !== b.current ? a.current ? -1 : 1 : byRecency(a, b));
80429
+ const st = matching.filter((g) => !g.current && now - g.lastActiveMs >= STALE_MS).sort(byRecency);
80430
+ return { fresh: f, stale: st, visibleGroups: [...f, ...st] };
80431
+ }, [groups, filter, listed]);
80432
+ const group = visibleGroups[Math.min(wtCursor, visibleGroups.length - 1)];
80433
+ const sessions2 = useMemo4(() => {
80434
+ if (!group)
80435
+ return [];
80436
+ const base = listed.get(group.name) ?? [];
80437
+ if (!filter)
80438
+ return base;
80439
+ if (fuzzy(filter, group.name))
80440
+ return base;
80441
+ return base.filter((s) => fuzzy(filter, sessionLabel(s)));
80442
+ }, [group, filter, listed]);
80443
+ const agentRows = useMemo4(() => group ? group.sessions.filter(isAgentSession) : [], [group]);
80444
+ const items = useMemo4(() => {
80445
+ const out = sessions2.map((row) => ({ kind: "session", row }));
80446
+ if (agentRows.length > 0 && !filter) {
80447
+ out.push({ kind: "agents", count: agentRows.length });
80448
+ for (const row of agentsOpen ? agentRows : agentRows.slice(0, 3)) {
80449
+ out.push({ kind: "agent", row });
80450
+ }
80451
+ }
80452
+ return out;
80453
+ }, [sessions2, agentRows, agentsOpen, filter]);
80454
+ const cursorItem = items[Math.min(sessCursor, items.length - 1)];
80455
+ const selected = cursorItem && cursorItem.kind !== "agents" ? cursorItem.row : undefined;
80456
+ const turns = detailTurns(height2);
80457
+ const sessionDetailH = DETAIL_CHROME + turns;
80458
+ const listRows = Math.max(3, height2 - sessionDetailH - WORKTREE_DETAIL_H - 4);
80459
+ useEffect6(() => {
80460
+ const start = Math.max(0, Math.min(sessCursor - 2, items.length - listRows));
80461
+ for (const it of items.slice(start, start + listRows + 2)) {
80462
+ if (it.kind !== "agents")
80463
+ hydrateSession(it.row);
80464
+ }
80465
+ if (selected) {
80466
+ hydrateSession(selected);
80467
+ hydrateConversation(selected);
80468
+ }
80469
+ setTick((t) => t + 1);
80470
+ }, [items, sessCursor, listRows, selected]);
80471
+ useEffect6(() => {
80472
+ if (!reader || reader.conv)
80473
+ return;
80474
+ const file2 = reader.row.file;
80475
+ const timer = setTimeout(() => {
80476
+ const conv = readConversation(file2);
80477
+ setReader((r) => r && r.row.file === file2 && !r.conv ? { row: r.row, conv } : r);
80478
+ }, 0);
80479
+ return () => clearTimeout(timer);
80480
+ }, [reader]);
80481
+ const clamp = (v, len) => Math.max(0, Math.min(v, len - 1));
80482
+ useKeyboard4((key) => {
80483
+ if (reader)
80484
+ return;
80485
+ const name = key.name;
80486
+ if (name === "escape") {
80487
+ if (filter) {
80488
+ setFilter("");
80489
+ return;
80490
+ }
80491
+ onDone(null);
80492
+ return;
80493
+ }
80494
+ if (key.ctrl && name === "c") {
80495
+ onDone(null);
80496
+ return;
80497
+ }
80498
+ if (name === "return" || name === "enter") {
80499
+ if (pane === "worktrees") {
80500
+ setPane("sessions");
80501
+ setSessCursor(0);
80502
+ return;
80503
+ }
80504
+ if (cursorItem?.kind === "agents") {
80505
+ setAgentsOpen((v) => !v);
80506
+ return;
80507
+ }
80508
+ if (selected)
80509
+ onDone(selected.id);
80510
+ return;
80511
+ }
80512
+ if (name === "tab") {
80513
+ setPane((p) => p === "worktrees" ? "sessions" : "worktrees");
80514
+ return;
80515
+ }
80516
+ if (name === "left") {
80517
+ setPane("worktrees");
80518
+ return;
80519
+ }
80520
+ if (name === "right") {
80521
+ setPane("sessions");
80522
+ return;
80523
+ }
80524
+ if (name === "up" || name === "down") {
80525
+ const d = name === "up" ? -1 : 1;
80526
+ if (pane === "worktrees") {
80527
+ setWtCursor((c) => clamp(c + d, visibleGroups.length));
80528
+ setSessCursor(0);
80529
+ } else {
80530
+ setSessCursor((c) => clamp(c + d, items.length));
80531
+ }
80532
+ return;
80533
+ }
80534
+ if (name === "a" && !key.ctrl && !key.meta) {
80535
+ setAgentsOpen((v) => !v);
80536
+ return;
80537
+ }
80538
+ if (name === "v" && !key.ctrl && !key.meta) {
80539
+ if (selected)
80540
+ setReader({ row: selected, conv: null });
80541
+ return;
80542
+ }
80543
+ if (name === "backspace") {
80544
+ setFilter((f) => f.slice(0, -1));
80545
+ setWtCursor(0);
80546
+ setSessCursor(0);
80547
+ return;
80548
+ }
80549
+ const ch = key.raw;
80550
+ if (ch && ch.length === 1 && ch >= " " && ch !== "\x7F" && ch !== "a" && ch !== "v") {
80551
+ setFilter((f) => f + ch);
80552
+ setWtCursor(0);
80553
+ setSessCursor(0);
80554
+ }
80555
+ });
80556
+ const bodyW = width;
80557
+ const sidebarW = Math.max(SIDEBAR_MIN, Math.min(SIDEBAR_MAX, Math.round(width * 0.34)));
80558
+ const rightW = Math.max(30, bodyW - sidebarW);
80559
+ const sessInner = rightW - PANEL_BORDER - SCROLL_CHROME;
80560
+ const sideInner = sidebarW - PANEL_BORDER - SCROLL_CHROME - 1;
80561
+ const sessionDetailInner = rightW - PANEL_CHROME;
80562
+ const worktreeDetailInner = width - PANEL_CHROME;
80563
+ const chipCols = useMemo4(() => {
80564
+ let cols = { age: 1, count: 1, dirty: 0, sync: 0 };
80565
+ for (const g of visibleGroups) {
80566
+ cols.age = Math.max(cols.age, displayWidth(g.lastActiveMs ? age(g.lastActiveMs) : "\u2014"));
80567
+ cols.count = Math.max(cols.count, String(listed.get(g.name)?.length ?? 0).length);
80568
+ if (g.dirty)
80569
+ cols.dirty = Math.max(cols.dirty, String(g.dirty).length);
80570
+ if (g.ahead)
80571
+ cols.sync = Math.max(cols.sync, String(g.ahead).length);
80572
+ if (g.behind)
80573
+ cols.sync = Math.max(cols.sync, String(g.behind).length);
80574
+ }
80575
+ if (blockWidth(cols) > sideInner)
80576
+ cols = { ...cols, sync: 0 };
80577
+ if (blockWidth(cols) > sideInner)
80578
+ cols = { ...cols, dirty: 0 };
80579
+ return cols;
80580
+ }, [visibleGroups, listed, sideInner]);
80581
+ const maxSize = Math.max(1, ...sessions2.slice(0, 400).map((s) => s.sizeBytes));
80582
+ const meterW = Math.max(6, Math.min(14, Math.round(sessInner * 0.16)));
80583
+ const sidebarInnerH = height2 - 1 - WORKTREE_DETAIL_H - 1 - PANEL_BORDER;
80584
+ const listContentH = visibleGroups.length * 2 + (stale.length > 0 ? 2 : 0);
80585
+ const showActivity = sidebarInnerH - listContentH >= ACTIVITY_H;
80586
+ const activityDays = useMemo4(() => dailyActivity(visibleGroups, ACTIVITY_WEEKS * WEEK_DAYS), [visibleGroups]);
80587
+ const shownSessions = [...listed.values()].reduce((a, v) => a + v.length, 0);
80588
+ if (reader) {
80589
+ return /* @__PURE__ */ jsxDEV20(ConversationReader, {
80590
+ row: reader.row,
80591
+ conv: reader.conv,
80592
+ width,
80593
+ height: height2,
80594
+ onClose: () => setReader(null),
80595
+ onResume: () => onDone(reader.row.id),
80596
+ onCancel: () => onDone(null)
80597
+ }, undefined, false, undefined, this);
80598
+ }
80599
+ return /* @__PURE__ */ jsxDEV20("box", {
80600
+ flexDirection: "column",
80601
+ height: height2,
80602
+ backgroundColor: C.bg,
80603
+ children: [
80604
+ /* @__PURE__ */ jsxDEV20("box", {
80605
+ flexDirection: "row",
80606
+ justifyContent: "space-between",
80607
+ height: 1,
80608
+ paddingX: 1,
80609
+ children: [
80610
+ /* @__PURE__ */ jsxDEV20("text", {
80611
+ children: [
80612
+ /* @__PURE__ */ jsxDEV20("span", {
80613
+ fg: tokens.accent,
80614
+ attributes: 1,
80615
+ children: "claudish"
80616
+ }, undefined, false, undefined, this),
80617
+ /* @__PURE__ */ jsxDEV20("span", {
80618
+ fg: tokens.subtle,
80619
+ children: " resume"
80620
+ }, undefined, false, undefined, this)
80621
+ ]
80622
+ }, undefined, true, undefined, this),
80623
+ /* @__PURE__ */ jsxDEV20("text", {
80624
+ children: [
80625
+ /* @__PURE__ */ jsxDEV20("span", {
80626
+ fg: tokens.subtle,
80627
+ children: `${visibleGroups.length} worktrees \xB7 ${shownSessions} sessions`
80628
+ }, undefined, false, undefined, this),
80629
+ filter ? /* @__PURE__ */ jsxDEV20("span", {
80630
+ fg: tokens.warn,
80631
+ children: ` /${filter}`
80632
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV20("span", {}, undefined, false, undefined, this)
80633
+ ]
80634
+ }, undefined, true, undefined, this)
80635
+ ]
80636
+ }, undefined, true, undefined, this),
80637
+ /* @__PURE__ */ jsxDEV20("box", {
80638
+ flexDirection: "row",
80639
+ flexGrow: 1,
80640
+ minHeight: 0,
80641
+ children: [
80642
+ /* @__PURE__ */ jsxDEV20("box", {
80643
+ width: sidebarW,
80644
+ flexDirection: "column",
80645
+ minHeight: 0,
80646
+ children: /* @__PURE__ */ jsxDEV20(Panel, {
80647
+ title: "worktrees",
80648
+ focused: pane === "worktrees",
80649
+ flush: true,
80650
+ flexGrow: 1,
80651
+ flexBasis: 0,
80652
+ children: [
80653
+ /* @__PURE__ */ jsxDEV20("scrollbox", {
80654
+ focused: false,
80655
+ flexGrow: 1,
80656
+ scrollbarOptions: SCROLLBAR,
80657
+ children: [
80658
+ fresh.map((g, i) => /* @__PURE__ */ jsxDEV20(WorktreeRow, {
80659
+ g,
80660
+ cursor: i === wtCursor,
80661
+ width: sideInner,
80662
+ count: listed.get(g.name)?.length ?? 0,
80663
+ cols: chipCols
80664
+ }, g.name, false, undefined, this)),
80665
+ stale.length > 0 ? /* @__PURE__ */ jsxDEV20(SectionHeader, {
80666
+ label: `stale \xB7 ${stale.length} \xB7 idle 3d+`,
80667
+ width: sideInner
80668
+ }, undefined, false, undefined, this) : null,
80669
+ stale.map((g, i) => /* @__PURE__ */ jsxDEV20(WorktreeRow, {
80670
+ g,
80671
+ cursor: fresh.length + i === wtCursor,
80672
+ width: sideInner,
80673
+ count: listed.get(g.name)?.length ?? 0,
80674
+ cols: chipCols
80675
+ }, g.name, false, undefined, this))
80676
+ ]
80677
+ }, undefined, true, undefined, this),
80678
+ showActivity ? /* @__PURE__ */ jsxDEV20(ActivityCalendar, {
80679
+ days: activityDays,
80680
+ width: sideInner
80681
+ }, undefined, false, undefined, this) : null
80682
+ ]
80683
+ }, undefined, true, undefined, this)
80684
+ }, undefined, false, undefined, this),
80685
+ /* @__PURE__ */ jsxDEV20("box", {
80686
+ flexDirection: "column",
80687
+ flexGrow: 1,
80688
+ minWidth: 0,
80689
+ minHeight: 0,
80690
+ children: [
80691
+ /* @__PURE__ */ jsxDEV20(Panel, {
80692
+ title: group ? `sessions \xB7 ${truncate3(group.name, 28)}` : "sessions",
80693
+ focused: pane === "sessions",
80694
+ flush: true,
80695
+ flexGrow: 1,
80696
+ flexBasis: 0,
80697
+ children: /* @__PURE__ */ jsxDEV20("scrollbox", {
80698
+ focused: false,
80699
+ flexGrow: 1,
80700
+ scrollbarOptions: SCROLLBAR,
80701
+ children: items.length === 0 ? /* @__PURE__ */ jsxDEV20("text", {
80702
+ fg: tokens.subtle,
80703
+ children: " no sessions match"
80704
+ }, undefined, false, undefined, this) : items.map((it, i) => it.kind === "agents" ? /* @__PURE__ */ jsxDEV20(AgentNode, {
80705
+ count: it.count,
80706
+ open: agentsOpen,
80707
+ cursor: i === sessCursor && pane === "sessions",
80708
+ width: sessInner
80709
+ }, "agents", false, undefined, this) : /* @__PURE__ */ jsxDEV20(SessionRowView, {
80710
+ row: it.row,
80711
+ cursor: i === sessCursor && pane === "sessions",
80712
+ width: sessInner,
80713
+ even: i % 2 === 0,
80714
+ indent: it.kind === "agent" ? 2 : 0,
80715
+ maxSize,
80716
+ meterW
80717
+ }, it.row.id, false, undefined, this))
80718
+ }, undefined, false, undefined, this)
80719
+ }, undefined, false, undefined, this),
80720
+ /* @__PURE__ */ jsxDEV20("box", {
80721
+ height: sessionDetailH,
80722
+ flexShrink: 0,
80723
+ children: /* @__PURE__ */ jsxDEV20(Panel, {
80724
+ title: "session",
80725
+ flexGrow: 1,
80726
+ children: selected ? /* @__PURE__ */ jsxDEV20(SessionDetail, {
80727
+ row: selected,
80728
+ width: sessionDetailInner,
80729
+ turns
80730
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV20("text", {
80731
+ fg: tokens.subtle,
80732
+ children: cursorItem?.kind === "agents" ? "agent sessions \u2014 enter to expand" : "nothing selected"
80733
+ }, undefined, false, undefined, this)
80734
+ }, undefined, false, undefined, this)
80735
+ }, undefined, false, undefined, this)
80736
+ ]
80737
+ }, undefined, true, undefined, this)
80738
+ ]
80739
+ }, undefined, true, undefined, this),
80740
+ /* @__PURE__ */ jsxDEV20("box", {
80741
+ height: WORKTREE_DETAIL_H,
80742
+ flexShrink: 0,
80743
+ children: /* @__PURE__ */ jsxDEV20(Panel, {
80744
+ title: "worktree",
80745
+ flexGrow: 1,
80746
+ children: /* @__PURE__ */ jsxDEV20(WorktreeDetail, {
80747
+ group,
80748
+ width: worktreeDetailInner,
80749
+ count: sessions2.length
80750
+ }, undefined, false, undefined, this)
80751
+ }, undefined, false, undefined, this)
80752
+ }, undefined, false, undefined, this),
80753
+ /* @__PURE__ */ jsxDEV20("box", {
80754
+ flexDirection: "row",
80755
+ height: 1,
80756
+ paddingX: 1,
80757
+ gap: 2,
80758
+ children: [
80759
+ /* @__PURE__ */ jsxDEV20("text", {
80760
+ fg: tokens.subtle,
80761
+ children: "\u2191\u2193 move"
80762
+ }, undefined, false, undefined, this),
80763
+ /* @__PURE__ */ jsxDEV20("text", {
80764
+ fg: tokens.subtle,
80765
+ children: "\u21E5 pane"
80766
+ }, undefined, false, undefined, this),
80767
+ /* @__PURE__ */ jsxDEV20("text", {
80768
+ fg: tokens.subtle,
80769
+ children: "type to filter"
80770
+ }, undefined, false, undefined, this),
80771
+ /* @__PURE__ */ jsxDEV20("text", {
80772
+ fg: tokens.accent,
80773
+ children: "\u23CE resume"
80774
+ }, undefined, false, undefined, this),
80775
+ /* @__PURE__ */ jsxDEV20("text", {
80776
+ fg: selected ? tokens.info : tokens.trace,
80777
+ children: "v read"
80778
+ }, undefined, false, undefined, this),
80779
+ /* @__PURE__ */ jsxDEV20("text", {
80780
+ fg: agentRows.length > 0 ? tokens.subtle : tokens.trace,
80781
+ children: agentRows.length > 0 ? `a ${agentsOpen ? "collapse" : "expand"} agents` : ""
80782
+ }, undefined, false, undefined, this),
80783
+ /* @__PURE__ */ jsxDEV20("text", {
80784
+ fg: tokens.subtle,
80785
+ children: "esc cancel"
80786
+ }, undefined, false, undefined, this)
80787
+ ]
80788
+ }, undefined, true, undefined, this)
80789
+ ]
80790
+ }, undefined, true, undefined, this);
80791
+ }
80792
+ function Field({
80793
+ label,
80794
+ width,
80795
+ children
80796
+ }) {
80797
+ return /* @__PURE__ */ jsxDEV20("box", {
80798
+ flexDirection: "row",
80799
+ height: 1,
80800
+ gap: 1,
80801
+ children: [
80802
+ /* @__PURE__ */ jsxDEV20("text", {
80803
+ fg: tokens.subtle,
80804
+ children: padTo(label, width)
80805
+ }, undefined, false, undefined, this),
80806
+ children
80807
+ ]
80808
+ }, undefined, true, undefined, this);
80809
+ }
80810
+ function WorktreeDetail({
80811
+ group,
80812
+ width,
80813
+ count
80814
+ }) {
80815
+ if (!group)
80816
+ return /* @__PURE__ */ jsxDEV20("text", {
80817
+ fg: tokens.subtle,
80818
+ children: "no worktree selected"
80819
+ }, undefined, false, undefined, this);
80820
+ const L = 9;
80821
+ const badges = [];
80822
+ if (group.dirty !== undefined) {
80823
+ badges.push({
80824
+ label: group.dirty > 0 ? `${DIRTY_GLYPH}${group.dirty} uncommitted` : "clean",
80825
+ bg: group.dirty > 0 ? CHIP.dirty : CHIP.clean
80826
+ });
80827
+ }
80828
+ if (group.ahead)
80829
+ badges.push({ label: `\u2191${group.ahead}`, bg: CHIP.ahead });
80830
+ if (group.behind)
80831
+ badges.push({ label: `\u2193${group.behind}`, bg: CHIP.behind });
80832
+ const badgeW = badges.reduce((w, b) => w + displayWidth(b.label) + 2, 0);
80833
+ const marker = group.current ? " \u25B6 you are here" : !group.live ? " worktree deleted" : "";
80834
+ const branch = group.branch ?? (group.live ? "detached" : "\u2014");
80835
+ const idAvail = Math.max(12, width - L - displayWidth(marker) - (BRANCH_LEAD + 1) - badgeW - 1);
80836
+ const nameW = Math.max(8, Math.min(displayWidth(group.name), Math.floor(idAvail / 2)));
80837
+ const branchW = Math.max(6, idAvail - nameW);
80838
+ const spark = activitySeries(group.sessions);
80839
+ const summary = ` ${count} session${count === 1 ? "" : "s"} \xB7 created ${group.createdMs ? age(group.createdMs) : "?"} ago \xB7 used ${group.lastActiveMs ? age(group.lastActiveMs) : "?"} ago`;
80840
+ const locAvail = Math.max(20, width - L - spark.length - 2);
80841
+ const pathW = Math.min(displayWidth(group.path ?? "\u2014"), Math.max(16, Math.floor(locAvail * 0.45)));
80842
+ return /* @__PURE__ */ jsxDEV20("box", {
80843
+ flexDirection: "column",
80844
+ children: [
80845
+ /* @__PURE__ */ jsxDEV20("text", {
80846
+ children: [
80847
+ /* @__PURE__ */ jsxDEV20("span", {
80848
+ fg: tokens.subtle,
80849
+ children: padTo("worktree", L)
80850
+ }, undefined, false, undefined, this),
80851
+ /* @__PURE__ */ jsxDEV20("span", {
80852
+ fg: tokens.text,
80853
+ attributes: A.bold,
80854
+ children: truncate3(group.name, nameW)
80855
+ }, undefined, false, undefined, this),
80856
+ /* @__PURE__ */ jsxDEV20("span", {
80857
+ fg: group.current ? HERE_FG : tokens.dead,
80858
+ children: marker
80859
+ }, undefined, false, undefined, this),
80860
+ /* @__PURE__ */ jsxDEV20("span", {
80861
+ fg: tokens.subtle,
80862
+ children: ` ${BRANCH_ICON} `
80863
+ }, undefined, false, undefined, this),
80864
+ /* @__PURE__ */ jsxDEV20("span", {
80865
+ fg: group.live ? tokens.info : tokens.dead,
80866
+ children: `${truncate3(branch, branchW)} `
80867
+ }, undefined, false, undefined, this),
80868
+ badges.map((b) => /* @__PURE__ */ jsxDEV20(BadgeSpan, {
80869
+ label: b.label,
80870
+ bg: b.bg
80871
+ }, b.label, false, undefined, this))
80872
+ ]
80873
+ }, undefined, true, undefined, this),
80874
+ /* @__PURE__ */ jsxDEV20("box", {
80875
+ flexDirection: "row",
80876
+ height: 1,
80877
+ children: [
80878
+ /* @__PURE__ */ jsxDEV20("text", {
80879
+ fg: tokens.subtle,
80880
+ flexShrink: 0,
80881
+ children: padTo("path", L)
80882
+ }, undefined, false, undefined, this),
80883
+ /* @__PURE__ */ jsxDEV20("text", {
80884
+ fg: tokens.trace,
80885
+ flexShrink: 0,
80886
+ children: `${padTo(truncate3(group.path ?? "\u2014", pathW), pathW)} `
80887
+ }, undefined, false, undefined, this),
80888
+ /* @__PURE__ */ jsxDEV20(Sparkline, {
80889
+ values: hasActivity(spark) ? spark : [],
80890
+ fg: SPARK_FG
80891
+ }, undefined, false, undefined, this),
80892
+ /* @__PURE__ */ jsxDEV20("text", {
80893
+ fg: tokens.trace,
80894
+ flexShrink: 0,
80895
+ children: truncate3(summary, Math.max(0, locAvail - pathW))
80896
+ }, undefined, false, undefined, this)
80897
+ ]
80898
+ }, undefined, true, undefined, this)
80899
+ ]
80900
+ }, undefined, true, undefined, this);
80901
+ }
80902
+ function SessionDetail({
80903
+ row,
80904
+ width,
80905
+ turns
80906
+ }) {
80907
+ const mb = row.sizeBytes / 1048576;
80908
+ const L = 9;
80909
+ const size = mb >= 0.1 ? `${mb.toFixed(1)} MB` : `${Math.round(row.sizeBytes / 1024)} KB`;
80910
+ const full = `\xB7 ${size}${row.lastMessageChars !== undefined ? ` \xB7 last msg ${row.lastMessageChars} ch` : ""}`;
80911
+ const suffixRoom = width - L - 1 - row.id.length - 1;
80912
+ const suffix = displayWidth(full) <= suffixRoom ? full : displayWidth(`\xB7 ${size}`) <= suffixRoom ? `\xB7 ${size}` : "";
80913
+ return /* @__PURE__ */ jsxDEV20("box", {
80914
+ flexDirection: "column",
80915
+ children: [
80916
+ /* @__PURE__ */ jsxDEV20(Field, {
80917
+ label: "title",
80918
+ width: L,
80919
+ children: /* @__PURE__ */ jsxDEV20("text", {
80920
+ fg: tokens.text,
80921
+ attributes: A.bold,
80922
+ children: truncate3(sessionLabel(row), width - L)
80923
+ }, undefined, false, undefined, this)
80924
+ }, undefined, false, undefined, this),
80925
+ /* @__PURE__ */ jsxDEV20(Field, {
80926
+ label: "id",
80927
+ width: L,
80928
+ children: [
80929
+ /* @__PURE__ */ jsxDEV20("text", {
80930
+ fg: tokens.info,
80931
+ flexShrink: 0,
80932
+ children: row.id
80933
+ }, undefined, false, undefined, this),
80934
+ suffix ? /* @__PURE__ */ jsxDEV20("text", {
80935
+ fg: tokens.trace,
80936
+ flexShrink: 0,
80937
+ children: suffix
80938
+ }, undefined, false, undefined, this) : null
80939
+ ]
80940
+ }, undefined, true, undefined, this),
80941
+ /* @__PURE__ */ jsxDEV20("box", {
80942
+ height: 1
80943
+ }, undefined, false, undefined, this),
80944
+ /* @__PURE__ */ jsxDEV20(Conversation, {
80945
+ turns: row.recentTurns ?? [],
80946
+ width,
80947
+ max: turns
80948
+ }, undefined, false, undefined, this)
80949
+ ]
80950
+ }, undefined, true, undefined, this);
80951
+ }
80952
+ function Conversation({
80953
+ turns,
80954
+ width,
80955
+ max
80956
+ }) {
80957
+ if (turns.length === 0) {
80958
+ return /* @__PURE__ */ jsxDEV20("box", {
80959
+ flexDirection: "column",
80960
+ width,
80961
+ children: /* @__PURE__ */ jsxDEV20("text", {
80962
+ fg: tokens.trace,
80963
+ children: "no conversation recorded"
80964
+ }, undefined, false, undefined, this)
80965
+ }, undefined, false, undefined, this);
80966
+ }
80967
+ const ROLE_W2 = 6;
80968
+ const textW = Math.max(10, width - ROLE_W2 - 2);
80969
+ const shown = turns.slice(-max);
80970
+ return /* @__PURE__ */ jsxDEV20("box", {
80971
+ flexDirection: "column",
80972
+ width,
80973
+ flexShrink: 0,
80974
+ children: shown.map((t, i) => /* @__PURE__ */ jsxDEV20("text", {
80975
+ children: [
80976
+ /* @__PURE__ */ jsxDEV20("span", {
80977
+ fg: tokens.trace,
80978
+ children: "\u258D"
80979
+ }, undefined, false, undefined, this),
80980
+ /* @__PURE__ */ jsxDEV20(BadgeSpan, {
80981
+ label: t.role === "user" ? "you" : "ai",
80982
+ bg: t.role === "user" ? SPEAKER.you : SPEAKER.ai,
80983
+ width: ROLE_W2
80984
+ }, undefined, false, undefined, this),
80985
+ /* @__PURE__ */ jsxDEV20("span", {
80986
+ fg: t.role === "user" ? tokens.text : MUTED,
80987
+ children: truncate3(t.text, textW)
80988
+ }, undefined, false, undefined, this)
80989
+ ]
80990
+ }, `${i}-${t.text.slice(0, 12)}`, true, undefined, this))
80991
+ }, undefined, false, undefined, this);
80992
+ }
80993
+ var PANEL_CHROME = 4, PANEL_BORDER = 2, SCROLL_CHROME = 1, SIDEBAR_MIN = 28, SIDEBAR_MAX = 50, DETAIL_CHROME = 5, WORKTREE_DETAIL_H = 4, STALE_MS, GH_LEVELS, CHIP, HERE_FG, LIVE_W = 2, DIRTY_GLYPH = "+", SESSION_AGE_W = 3, SESSION_AGE_COL, SIZE_FLOOR_BYTES, MUTED, SCROLLBAR, WEEK_DAYS = 7, ACTIVITY_WEEKS = 6, BRANCH_ICON = "\u2387", BRANCH_LEAD = 5, BRANCH_LEAD_DENSE = 6, WEEK_LABEL_W = 4, ACTIVITY_H, SPARK_MIN_DAYS = 7, SPARK_FG = "#3f6f9e";
80994
+ var init_resume_picker = __esm(() => {
80995
+ init_theme2();
80996
+ init_text();
80997
+ init_tokens();
80998
+ init_widgets();
80999
+ init_conversation_reader();
81000
+ init_conversation();
81001
+ init_session_discovery();
81002
+ STALE_MS = 3 * 86400000;
81003
+ GH_LEVELS = ["#21262d", "#0e4429", "#006d32", "#26a641", "#39d353"];
81004
+ CHIP = {
81005
+ fresh: GH_LEVELS[4],
81006
+ stale: "#a1a9b3",
81007
+ count: "#58a6ff",
81008
+ dirty: "#d29922",
81009
+ clean: GH_LEVELS[3],
81010
+ ahead: "#bc8cff",
81011
+ behind: "#d2a8ff"
81012
+ };
81013
+ HERE_FG = GH_LEVELS[4];
81014
+ SESSION_AGE_COL = SESSION_AGE_W + 3;
81015
+ SIZE_FLOOR_BYTES = 16 * 1024;
81016
+ MUTED = C.fgMuted;
81017
+ SCROLLBAR = {
81018
+ showArrows: false,
81019
+ trackOptions: { backgroundColor: tokens.bgPanel, foregroundColor: tokens.border }
81020
+ };
81021
+ ACTIVITY_H = 2 + ACTIVITY_WEEKS;
81022
+ });
81023
+
81024
+ // src/session/resume-picker-run.tsx
81025
+ var exports_resume_picker_run = {};
81026
+ __export(exports_resume_picker_run, {
81027
+ runResumePicker: () => runResumePicker
81028
+ });
81029
+ import { createCliRenderer as createCliRenderer3 } from "@opentui/core";
81030
+ import { createRoot as createRoot3 } from "@opentui/react";
81031
+ import { jsxDEV as jsxDEV21 } from "@opentui/react/jsx-dev-runtime";
81032
+ async function runResumePicker(cwd = process.cwd()) {
81033
+ const repo = getRepoContext(cwd);
81034
+ if (!repo)
81035
+ return { sessionId: null, hadSessions: false };
81036
+ const groups = discoverWorktreeGroups(repo);
81037
+ if (groups.length === 0)
81038
+ return { sessionId: null, hadSessions: false };
81039
+ await enrichWorktreeGit(groups, repo.root);
81040
+ setStderrQuiet(true);
81041
+ const renderer = await createCliRenderer3({
81042
+ useAlternateScreen: true,
81043
+ exitOnCtrlC: false
81044
+ });
81045
+ const root = createRoot3(renderer);
81046
+ let chosen = null;
81047
+ try {
81048
+ await new Promise((resolve5) => {
81049
+ let settled = false;
81050
+ const done = (id) => {
81051
+ if (settled)
81052
+ return;
81053
+ settled = true;
81054
+ chosen = id;
81055
+ resolve5();
81056
+ };
81057
+ root.render(/* @__PURE__ */ jsxDEV21(ResumePicker, {
81058
+ groups,
81059
+ onDone: done
81060
+ }, undefined, false, undefined, this));
81061
+ });
81062
+ } finally {
81063
+ try {
81064
+ root.unmount();
81065
+ } catch {}
81066
+ try {
81067
+ renderer.destroy();
81068
+ } catch {}
81069
+ setStderrQuiet(false);
81070
+ }
81071
+ return { sessionId: chosen, hadSessions: true };
81072
+ }
81073
+ var init_resume_picker_run = __esm(() => {
81074
+ init_logger();
81075
+ init_resume_picker();
81076
+ init_session_discovery();
81077
+ });
81078
+
81079
+ // src/session/baseline-pricing.ts
81080
+ function resolveBaseline(alias, label) {
81081
+ const entry = findEntryByAlias(alias);
81082
+ if (!entry)
81083
+ return null;
81084
+ const firstParty = entry.aggregators?.find((a) => a.provider === FIRST_PARTY && typeof a.pricing?.input === "number");
81085
+ const input = firstParty?.pricing?.input;
81086
+ const output = firstParty?.pricing?.output;
81087
+ if (typeof input !== "number" || typeof output !== "number")
81088
+ return null;
81089
+ if (input <= 0 || output <= 0)
81090
+ return null;
81091
+ return { modelId: entry.modelId, label, inputPerM: input, outputPerM: output };
81092
+ }
81093
+ function getBaselines() {
81094
+ return BASELINE_ALIASES.map(({ alias, label }) => resolveBaseline(alias, label)).filter((b) => b !== null);
81095
+ }
81096
+ function baselineCost(b, inputTokens, outputTokens) {
81097
+ return inputTokens / 1e6 * b.inputPerM + outputTokens / 1e6 * b.outputPerM;
81098
+ }
81099
+ var BASELINE_ALIASES, FIRST_PARTY = "anthropic";
81100
+ var init_baseline_pricing = __esm(() => {
81101
+ init_catalog_query();
81102
+ BASELINE_ALIASES = [
81103
+ { alias: "~anthropic/claude-sonnet-latest", label: "Sonnet" },
81104
+ { alias: "~anthropic/claude-opus-latest", label: "Opus" }
81105
+ ];
81106
+ });
81107
+
81108
+ // src/session/session-stats.ts
81109
+ var exports_session_stats = {};
81110
+ __export(exports_session_stats, {
81111
+ tokenFilePath: () => tokenFilePath,
81112
+ readSessionStats: () => readSessionStats,
81113
+ computeSavings: () => computeSavings
81114
+ });
81115
+ import { readFileSync as readFileSync28 } from "fs";
81116
+ import { homedir as homedir35 } from "os";
81117
+ import { join as join38 } from "path";
81118
+ function tokenFilePath(port) {
81119
+ return process.env.CLAUDISH_TOKEN_FILE || join38(homedir35(), ".claudish", `tokens-${port}.json`);
81120
+ }
81121
+ function readSessionStats(port) {
81122
+ let raw2;
81123
+ try {
81124
+ raw2 = JSON.parse(readFileSync28(tokenFilePath(port), "utf-8"));
81125
+ } catch {
81126
+ return null;
81127
+ }
81128
+ if (!raw2 || typeof raw2 !== "object")
81129
+ return null;
81130
+ const d = raw2;
81131
+ const inputTokens = num(d.input_tokens);
81132
+ const outputTokens = num(d.output_tokens);
81133
+ if (inputTokens <= 0 && outputTokens <= 0)
81134
+ return null;
81135
+ const costUsd = num(d.total_cost);
81136
+ const isFree = d.is_free === true;
81137
+ const contextWindow = typeof d.context_window === "number" ? d.context_window : null;
81138
+ const contextUsed = contextWindow && contextWindow > 0 ? Math.min(1, Math.max(0, inputTokens / contextWindow)) : null;
81139
+ const toolCalls = Array.isArray(d.tool_calls) ? d.tool_calls.map((t) => t).filter((t) => typeof t?.name === "string" && num(t.count) > 0).map((t) => ({ name: String(t.name), count: num(t.count) })) : [];
81140
+ const startedAt = num(d.started_at);
81141
+ const updatedAt = num(d.updated_at);
81142
+ const durationMs = startedAt > 0 && updatedAt > startedAt ? updatedAt - startedAt : 0;
81143
+ const billedInputTokens = num(d.billed_input_tokens) || inputTokens;
81144
+ const rawIn = billedInputTokens / 1e6 * num(d.input_per_m);
81145
+ const rawOut = outputTokens / 1e6 * num(d.output_per_m);
81146
+ const rawTotal = rawIn + rawOut;
81147
+ const scale = rawTotal > 0 && costUsd > 0 ? costUsd / rawTotal : 0;
81148
+ return {
81149
+ inputTokens,
81150
+ outputTokens,
81151
+ totalTokens: num(d.total_tokens) || inputTokens + outputTokens,
81152
+ costUsd,
81153
+ isFree,
81154
+ isEstimated: d.is_estimated === true,
81155
+ providerName: typeof d.provider_name === "string" ? d.provider_name : "",
81156
+ modelName: typeof d.model_name === "string" ? d.model_name : "",
81157
+ contextWindow,
81158
+ contextUsed,
81159
+ toolCalls,
81160
+ toolCallTotal: toolCalls.reduce((a, t) => a + t.count, 0),
81161
+ durationMs,
81162
+ savings: computeSavings(billedInputTokens, outputTokens, costUsd),
81163
+ inputCostUsd: rawIn * scale,
81164
+ outputCostUsd: rawOut * scale,
81165
+ billedInputTokens
81166
+ };
81167
+ }
81168
+ function computeSavings(inputTokens, outputTokens, actualUsd) {
81169
+ return getBaselines().map((b) => {
81170
+ const baselineUsd = baselineCost(b, inputTokens, outputTokens);
81171
+ return {
81172
+ label: b.label,
81173
+ modelId: b.modelId,
81174
+ baselineUsd,
81175
+ savedUsd: baselineUsd - actualUsd
81176
+ };
81177
+ });
81178
+ }
81179
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
81180
+ var init_session_stats = __esm(() => {
81181
+ init_baseline_pricing();
81182
+ });
81183
+
81184
+ // src/session/ansi-viz.ts
81185
+ function rgb(hex4) {
81186
+ return [
81187
+ Number.parseInt(hex4.slice(1, 3), 16),
81188
+ Number.parseInt(hex4.slice(3, 5), 16),
81189
+ Number.parseInt(hex4.slice(5, 7), 16)
81190
+ ];
81191
+ }
81192
+ function fg(hex4) {
81193
+ const [r, g, b] = rgb(hex4);
81194
+ return `\x1B[38;2;${r};${g};${b}m`;
81195
+ }
81196
+ function bg(hex4) {
81197
+ const [r, g, b] = rgb(hex4);
81198
+ return `\x1B[48;2;${r};${g};${b}m`;
81199
+ }
81200
+ function paint(text, hex4, bold4 = false) {
81201
+ return `${bold4 ? BOLD5 : ""}${fg(hex4)}${text}${RESET4}`;
81202
+ }
81203
+ function meter(pct, width, ramp = ramps.load) {
81204
+ const cells = Math.floor(width);
81205
+ if (!Number.isFinite(cells) || cells <= 0)
81206
+ return "";
81207
+ if (Number.isNaN(pct))
81208
+ return paint(NODATA2.repeat(cells), tokens.dead);
81209
+ const cols = rampFor(cells, ramp);
81210
+ const filled = fillCells(pct, cells);
81211
+ let out = "";
81212
+ let last = "";
81213
+ for (let i = 0;i < cells; i++) {
81214
+ const hex4 = i < filled ? cols[i] : tokens.border;
81215
+ if (hex4 !== last) {
81216
+ out += fg(hex4);
81217
+ last = hex4;
81218
+ }
81219
+ out += i < filled ? FILL2 : TRACK2;
81220
+ }
81221
+ return out + RESET4;
81222
+ }
81223
+ function stackedBar(segments, width) {
81224
+ const cells = Math.floor(width);
81225
+ if (!Number.isFinite(cells) || cells <= 0 || segments.length === 0)
81226
+ return "";
81227
+ const split = splitCells(segments.map((s) => s.value), cells);
81228
+ if (split.reduce((a, b) => a + b, 0) === 0)
81229
+ return paint(TRACK2.repeat(cells), tokens.border);
81230
+ let out = "";
81231
+ for (let i = 0;i < split.length; i++) {
81232
+ const n = split[i];
81233
+ if (n > 0)
81234
+ out += `${bg(segments[i].color)}${" ".repeat(n)}`;
81235
+ }
81236
+ return out + RESET4;
81237
+ }
81238
+ function badge(label, hex4) {
81239
+ return `${BOLD5}${fg(pickInk(hex4))}${bg(hex4)} ${label} ${RESET4}`;
81240
+ }
81241
+ function stripAnsi4(s) {
81242
+ return s.replace(ANSI_RE3, "");
81243
+ }
81244
+ function visibleWidth(s) {
81245
+ return displayWidth(stripAnsi4(s));
81246
+ }
81247
+ function clipStyled(s, width) {
81248
+ if (width <= 0)
81249
+ return "";
81250
+ if (visibleWidth(s) <= width)
81251
+ return s;
81252
+ let out = "";
81253
+ let w = 0;
81254
+ let i = 0;
81255
+ while (i < s.length) {
81256
+ if (s[i] === "\x1B") {
81257
+ const end = s.indexOf("m", i);
81258
+ if (end === -1)
81259
+ break;
81260
+ out += s.slice(i, end + 1);
81261
+ i = end + 1;
81262
+ continue;
81263
+ }
81264
+ const ch = [...s.slice(i)][0] ?? "";
81265
+ const cw = displayWidth(ch);
81266
+ if (w + cw > width)
81267
+ break;
81268
+ out += ch;
81269
+ w += cw;
81270
+ i += ch.length;
81271
+ }
81272
+ return out + RESET4;
81273
+ }
81274
+ function padVisible2(s, width, align = "left") {
81275
+ const clipped = clipStyled(s, width);
81276
+ const pad2 = Math.max(0, width - visibleWidth(clipped));
81277
+ return align === "left" ? clipped + " ".repeat(pad2) : " ".repeat(pad2) + clipped;
81278
+ }
81279
+ function compact(n) {
81280
+ if (!Number.isFinite(n))
81281
+ return "\u2014";
81282
+ const a = Math.abs(n);
81283
+ if (a >= 1e9)
81284
+ return `${(n / 1e9).toFixed(1)}G`;
81285
+ if (a >= 1e6)
81286
+ return `${(n / 1e6).toFixed(1)}M`;
81287
+ if (a >= 1000)
81288
+ return `${(n / 1000).toFixed(1)}K`;
81289
+ return String(Math.round(n));
81290
+ }
81291
+ function duration3(ms) {
81292
+ if (!Number.isFinite(ms) || ms <= 0)
81293
+ return "\u2014";
81294
+ const s = Math.round(ms / 1000);
81295
+ if (s < 60)
81296
+ return `${s}s`;
81297
+ const m = Math.floor(s / 60);
81298
+ if (m < 60)
81299
+ return `${m}m ${String(s % 60).padStart(2, "0")}s`;
81300
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, "0")}m`;
81301
+ }
81302
+ function usd(n) {
81303
+ if (!Number.isFinite(n))
81304
+ return "\u2014";
81305
+ const a = Math.abs(n);
81306
+ if (a === 0)
81307
+ return "$0";
81308
+ if (a < 0.01)
81309
+ return `$${n.toFixed(4)}`;
81310
+ if (a < 1)
81311
+ return `$${n.toFixed(3)}`;
81312
+ return `$${n.toFixed(2)}`;
81313
+ }
81314
+ var RESET4 = "\x1B[0m", BOLD5 = "\x1B[1m", FILL2 = "\u2588", TRACK2 = "\u2591", NODATA2 = "\u254C", ANSI_RE3;
81315
+ var init_ansi_viz = __esm(() => {
81316
+ init_color();
81317
+ init_text();
81318
+ init_tokens();
81319
+ init_widgets();
81320
+ ANSI_RE3 = /\x1b\[[0-9;]*m/g;
81321
+ });
81322
+
81323
+ // src/session/session-summary.ts
81324
+ var exports_session_summary = {};
81325
+ __export(exports_session_summary, {
81326
+ renderSessionSummary: () => renderSessionSummary,
81327
+ printSessionSummary: () => printSessionSummary
81328
+ });
81329
+ function cardWidth() {
81330
+ const cols = process.stdout.columns || 80;
81331
+ return Math.max(MIN_W, Math.min(MAX_W, cols - 2));
81332
+ }
81333
+ function renderSessionSummary(input) {
81334
+ const { stats, modelSpec, resumeModelSpec, resumeId, exitCode } = input;
81335
+ const W2 = cardWidth();
81336
+ const inner = W2 - CHROME;
81337
+ const out = [];
81338
+ const dim3 = (s) => paint(s, tokens.subtle);
81339
+ const body = (s) => paint(s, tokens.text);
81340
+ const titleText = exitCode === 0 ? " session " : " session \xB7 failed ";
81341
+ const titleHex = exitCode === 0 ? tokens.accent : tokens.error;
81342
+ const rule = "\u2500".repeat(Math.max(0, W2 - 2 - visibleWidth(titleText) - 1));
81343
+ out.push(paint("\u256D\u2500", tokens.border) + paint(titleText, titleHex) + paint(rule + "\u256E", tokens.border));
81344
+ const row = (s) => {
81345
+ out.push(`${paint("\u2502", tokens.border)} ${padVisible2(s, inner)} ${paint("\u2502", tokens.border)}`);
81346
+ };
81347
+ const blank = () => row("");
81348
+ const chips = [badge(truncate3(modelSpec, 34), tokens.accent)];
81349
+ if (stats.isFree)
81350
+ chips.push(badge("FREE", C.pillKeyBg));
81351
+ else if (stats.isEstimated)
81352
+ chips.push(badge("EST", "#8a7d1e"));
81353
+ if (exitCode !== 0)
81354
+ chips.push(badge(`EXIT ${exitCode}`, "#9e2b2b"));
81355
+ const right = body(duration3(stats.durationMs));
81356
+ const left = clipStyled(chips.join(" "), Math.max(0, inner - visibleWidth(right) - 1));
81357
+ const gap = Math.max(1, inner - visibleWidth(left) - visibleWidth(right));
81358
+ row(left + " ".repeat(gap) + right);
81359
+ if (stats.providerName)
81360
+ row(dim3(truncate3(stats.providerName, inner)));
81361
+ blank();
81362
+ const VALUE_W = 24;
81363
+ const barW = Math.max(12, inner - LABEL_W - VALUE_W);
81364
+ const dataRow = (label, bar, values) => {
81365
+ row(dim3(padTo(label, LABEL_W)) + bar + padVisible2(values, VALUE_W, "right"));
81366
+ };
81367
+ if (stats.contextUsed !== null && stats.contextWindow) {
81368
+ const pct = stats.contextUsed * 100;
81369
+ dataRow("context", meter(pct, barW, ramps.load), body(padStartTo(`${Math.round(pct)}%`, 4)) + dim3(` ${compact(stats.inputTokens)}/${compact(stats.contextWindow)}`));
81370
+ }
81371
+ dataRow("tokens", stackedBar([
81372
+ { value: stats.inputTokens, color: C.blue },
81373
+ { value: stats.outputTokens, color: C.cyan }
81374
+ ], barW), dim3("in ") + body(compact(stats.inputTokens)) + dim3(" out ") + body(compact(stats.outputTokens)));
81375
+ if (!stats.isFree && stats.inputCostUsd + stats.outputCostUsd > 0) {
81376
+ dataRow("spend", stackedBar([
81377
+ { value: stats.inputCostUsd, color: C.blue },
81378
+ { value: stats.outputCostUsd, color: C.cyan }
81379
+ ], barW), dim3("in ") + body(usd(stats.inputCostUsd)) + dim3(" out ") + body(usd(stats.outputCostUsd)));
81380
+ }
81381
+ if (stats.toolCallTotal > 0) {
81382
+ const shown = stats.toolCalls.slice(0, TOOL_COLORS.length);
81383
+ const rest = stats.toolCalls.slice(TOOL_COLORS.length).reduce((a, t) => a + t.count, 0);
81384
+ const segs = shown.map((t, i) => ({ value: t.count, color: TOOL_COLORS[i] }));
81385
+ if (rest > 0)
81386
+ segs.push({ value: rest, color: TOOL_OTHER });
81387
+ dataRow("tools", stackedBar(segs, barW), body(padStartTo(String(stats.toolCallTotal), 4)) + dim3(" calls"));
81388
+ const legend = shown.map((t, i) => paint(`${t.name} ${t.count}`, TOOL_COLORS[i])).concat(rest > 0 ? [paint(`other ${rest}`, TOOL_OTHER)] : []);
81389
+ for (const line of wrapStyled(legend, dim3(" \xB7 "), inner - LABEL_W)) {
81390
+ row(" ".repeat(LABEL_W) + line);
81391
+ }
81392
+ }
81393
+ blank();
81394
+ row(dim3(padTo("cost", LABEL_W)) + paint(stats.isFree ? "free" : usd(stats.costUsd), stats.isFree ? tokens.success : tokens.text, true) + (stats.isEstimated && !stats.isFree ? dim3(" estimated") : ""));
81395
+ for (const s of stats.savings) {
81396
+ const label = padTo(`vs ${s.label}`, LABEL_W);
81397
+ if (s.savedUsd >= 0) {
81398
+ const pct = s.baselineUsd > 0 ? s.savedUsd / s.baselineUsd * 100 : 0;
81399
+ dataRow(label, meter(pct, barW, ramps.savings), paint(padStartTo(`${Math.round(pct)}%`, 4), tokens.success) + dim3(" saved ") + paint(usd(s.savedUsd), tokens.success));
81400
+ } else {
81401
+ dataRow(label, meter(0, barW, ramps.savings), dim3("over by ") + paint(usd(-s.savedUsd), tokens.error));
81402
+ }
81403
+ }
81404
+ out.push(paint(`\u2570${"\u2500".repeat(W2 - 2)}\u256F`, tokens.border));
81405
+ if (resumeId) {
81406
+ out.push("");
81407
+ out.push(dim3("Resume this session with:"));
81408
+ const modelFlag = resumeModelSpec ? `--model ${resumeModelSpec} ` : "";
81409
+ out.push(`claudish ${modelFlag}--resume ${resumeId}`);
81410
+ }
81411
+ return out;
81412
+ }
81413
+ function wrapStyled(chips, sep, width) {
81414
+ const lines = [];
81415
+ let cur = "";
81416
+ let curW = 0;
81417
+ const sepW = visibleWidth(sep);
81418
+ for (const chip of chips) {
81419
+ const w = visibleWidth(chip);
81420
+ if (cur && curW + sepW + w > width) {
81421
+ lines.push(cur);
81422
+ cur = chip;
81423
+ curW = w;
81424
+ } else {
81425
+ cur = cur ? cur + sep + chip : chip;
81426
+ curW = cur === chip ? w : curW + sepW + w;
81427
+ }
81428
+ }
81429
+ if (cur)
81430
+ lines.push(cur);
81431
+ return lines;
81432
+ }
81433
+ function printSessionSummary(input, write) {
81434
+ for (const line of renderSessionSummary(input))
81435
+ write(line);
81436
+ write(RESET4);
81437
+ }
81438
+ var TOOL_COLORS, TOOL_OTHER, MIN_W = 62, MAX_W = 96, CHROME = 4, LABEL_W = 10;
81439
+ var init_session_summary = __esm(() => {
81440
+ init_theme2();
81441
+ init_text();
81442
+ init_tokens();
81443
+ init_ansi_viz();
81444
+ TOOL_COLORS = [
81445
+ C.blue,
81446
+ C.cyan,
81447
+ "#8a7d1e",
81448
+ "#1f6d75",
81449
+ C.magenta,
81450
+ "#2d6e3e",
81451
+ C.orange
81452
+ ];
81453
+ TOOL_OTHER = C.dim;
81454
+ });
81455
+
78414
81456
  // src/index.ts
78415
81457
  init_op_source();
78416
81458
  init_startup_trace();
78417
81459
  var import_dotenv3 = __toESM(require_main(), 1);
78418
- import { existsSync as existsSync29, readFileSync as readFileSync28 } from "fs";
78419
- import { join as join37, resolve as resolve5 } from "path";
81460
+ import { existsSync as existsSync29, readFileSync as readFileSync29 } from "fs";
81461
+ import { join as join39, resolve as resolve5 } from "path";
78420
81462
  import_dotenv3.config({ quiet: true });
78421
81463
  function classifyStartupKind() {
78422
81464
  const argv = process.argv.slice(2);
@@ -78659,14 +81701,14 @@ async function runCli() {
78659
81701
  if (cliConfig.team && cliConfig.team.length > 0) {
78660
81702
  let prompt = cliConfig.claudeArgs.join(" ");
78661
81703
  if (cliConfig.inputFile) {
78662
- prompt = readFileSync28(cliConfig.inputFile, "utf-8");
81704
+ prompt = readFileSync29(cliConfig.inputFile, "utf-8");
78663
81705
  }
78664
81706
  if (!prompt.trim()) {
78665
81707
  console.error("Error: --team requires a prompt (positional args or -f <file>)");
78666
81708
  process.exit(1);
78667
81709
  }
78668
81710
  const mode = cliConfig.teamMode ?? "default";
78669
- const sessionPath = join37(process.cwd(), `.claudish-team-${Date.now()}`);
81711
+ const sessionPath = join39(process.cwd(), `.claudish-team-${Date.now()}`);
78670
81712
  if (mode === "json") {
78671
81713
  const { setupSession: setupSession2, runModels: runModels2 } = await Promise.resolve().then(() => (init_team_orchestrator(), exports_team_orchestrator));
78672
81714
  setupSession2(sessionPath, cliConfig.team, prompt);
@@ -78676,9 +81718,9 @@ async function runCli() {
78676
81718
  });
78677
81719
  const result = { ...status2, responses: {} };
78678
81720
  for (const anonId of Object.keys(status2.models)) {
78679
- const responsePath = join37(sessionPath, `response-${anonId}.md`);
81721
+ const responsePath = join39(sessionPath, `response-${anonId}.md`);
78680
81722
  try {
78681
- const raw2 = readFileSync28(responsePath, "utf-8").trim();
81723
+ const raw2 = readFileSync29(responsePath, "utf-8").trim();
78682
81724
  try {
78683
81725
  result.responses[anonId] = JSON.parse(raw2);
78684
81726
  } catch {
@@ -78703,8 +81745,8 @@ async function runCli() {
78703
81745
  Team Status`);
78704
81746
  for (const id of modelIds) {
78705
81747
  const m = status.models[id];
78706
- const duration3 = m.startedAt && m.completedAt ? `${Math.round((new Date(m.completedAt).getTime() - new Date(m.startedAt).getTime()) / 1000)}s` : "pending";
78707
- console.log(` ${id} ${m.state.padEnd(10)} ${duration3}`);
81748
+ const duration4 = m.startedAt && m.completedAt ? `${Math.round((new Date(m.completedAt).getTime() - new Date(m.startedAt).getTime()) / 1000)}s` : "pending";
81749
+ console.log(` ${id} ${m.state.padEnd(10)} ${duration4}`);
78708
81750
  }
78709
81751
  process.exit(0);
78710
81752
  }
@@ -78855,6 +81897,28 @@ Team Status`);
78855
81897
  haiku: cliConfig.modelHaiku,
78856
81898
  subagent: cliConfig.modelSubagent
78857
81899
  };
81900
+ let resumedSessionId = (() => {
81901
+ const i = cliConfig.claudeArgs.indexOf("--resume");
81902
+ const v = i !== -1 ? cliConfig.claudeArgs[i + 1] : undefined;
81903
+ return v && !v.startsWith("-") ? v : null;
81904
+ })();
81905
+ if (cliConfig._resumePicker) {
81906
+ const canDrawTui = Boolean(process.stdin.isTTY && process.stdout.isTTY);
81907
+ if (!canDrawTui || cliConfig._hasPrintFlag || !cliConfig.interactive) {
81908
+ cliConfig.claudeArgs.push("--resume");
81909
+ } else {
81910
+ const { runResumePicker: runResumePicker2 } = await Promise.resolve().then(() => (init_resume_picker_run(), exports_resume_picker_run));
81911
+ const outcome = await runResumePicker2();
81912
+ if (!outcome.hadSessions) {
81913
+ cliConfig.claudeArgs.push("--resume");
81914
+ } else if (!outcome.sessionId) {
81915
+ process.exit(0);
81916
+ } else {
81917
+ cliConfig.claudeArgs.push("--resume", outcome.sessionId);
81918
+ resumedSessionId = outcome.sessionId;
81919
+ }
81920
+ }
81921
+ }
78858
81922
  const proxy = await traceSpan("startup:proxy-start", () => createProxyServer2(port, cliConfig.monitor ? undefined : cliConfig.openrouterApiKey, cliConfig.monitor ? undefined : explicitModel, cliConfig.monitor, cliConfig.anthropicApiKey, modelMap, {
78859
81923
  summarizeTools: cliConfig.summarizeTools,
78860
81924
  quiet: cliConfig.quiet,
@@ -78888,6 +81952,25 @@ Team Status`);
78888
81952
  const write = cliConfig.interactive ? console.log : console.error;
78889
81953
  write(`[claudish] Done
78890
81954
  `);
81955
+ try {
81956
+ const [{ readSessionStats: readSessionStats2 }, { printSessionSummary: printSessionSummary2 }, { findLatestSessionId: findLatestSessionId2 }] = await Promise.all([
81957
+ Promise.resolve().then(() => (init_session_stats(), exports_session_stats)),
81958
+ Promise.resolve().then(() => (init_session_summary(), exports_session_summary)),
81959
+ Promise.resolve().then(() => (init_session_discovery(), exports_session_discovery))
81960
+ ]);
81961
+ const stats = readSessionStats2(port);
81962
+ if (stats) {
81963
+ printSessionSummary2({
81964
+ stats,
81965
+ modelSpec: explicitModel || stats.modelName || "",
81966
+ resumeModelSpec: explicitModel ?? null,
81967
+ resumeId: resumedSessionId ?? findLatestSessionId2(process.cwd(), Date.now() - stats.durationMs),
81968
+ exitCode
81969
+ }, write);
81970
+ }
81971
+ } catch (e) {
81972
+ console.error(`[claudish] session summary unavailable: ${e}`);
81973
+ }
78891
81974
  }
78892
81975
  const sessionLogPath = getAlwaysOnLogPath2();
78893
81976
  if (exitCode !== 0 && sessionLogPath && !cliConfig.quiet) {