kfc-code-cli 0.0.1-alpha.21 → 0.0.1-alpha.22

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/main.mjs +707 -156
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -77020,6 +77020,44 @@ function extractMatcherValue(input) {
77020
77020
  return input;
77021
77021
  }
77022
77022
  /**
77023
+ * OAuth error classes.
77024
+ *
77025
+ * All errors derive from {@link OAuthError}. Distinguishing subclasses let
77026
+ * callers react appropriately:
77027
+ * - `OAuthUnauthorizedError`: 401/403 from token endpoint → refresh_token
77028
+ * or credentials are bad; drive user through `/login` again.
77029
+ * - `DeviceCodeExpiredError`: device_code TTL ran out before user approved;
77030
+ * restart the device flow.
77031
+ * - `DeviceCodeTimeoutError`: local 15 min wall-clock budget exhausted
77032
+ * (D2 from Slice 5.0 brief).
77033
+ * - `RetryableRefreshError`: 429 / 5xx from token endpoint; the refresh
77034
+ * helper retries with exponential backoff before surfacing this.
77035
+ */
77036
+ var OAuthError = class extends Error {
77037
+ constructor(message) {
77038
+ super(message);
77039
+ this.name = "OAuthError";
77040
+ }
77041
+ };
77042
+ var OAuthUnauthorizedError = class extends OAuthError {
77043
+ constructor(message) {
77044
+ super(message);
77045
+ this.name = "OAuthUnauthorizedError";
77046
+ }
77047
+ };
77048
+ var DeviceCodeTimeoutError = class extends OAuthError {
77049
+ constructor(message = "Device authorization timed out locally.") {
77050
+ super(message);
77051
+ this.name = "DeviceCodeTimeoutError";
77052
+ }
77053
+ };
77054
+ var RetryableRefreshError = class extends OAuthError {
77055
+ constructor(message) {
77056
+ super(message);
77057
+ this.name = "RetryableRefreshError";
77058
+ }
77059
+ };
77060
+ /**
77023
77061
  * Business-error sentinel classes — Phase 18 A.11 / A.12 / A.13
77024
77062
  * (v2 §3.1 wire error code table).
77025
77063
  *
@@ -77132,6 +77170,50 @@ function classifyBusinessError(error) {
77132
77170
  };
77133
77171
  return null;
77134
77172
  }
77173
+ function classifySessionErrorType(error) {
77174
+ if (error instanceof ContextOverflowError) return "context_overflow";
77175
+ if (isRateLimitLikeError(error)) return "rate_limit";
77176
+ if (isAuthLikeError(error)) return "auth_error";
77177
+ if (error instanceof LLMNotSetError) return "auth_error";
77178
+ if (error instanceof LLMCapabilityMismatchError || error instanceof ProviderError) return "api_error";
77179
+ if (error instanceof Error && /provider|backend|upstream/i.test(error.message)) return "api_error";
77180
+ return "internal";
77181
+ }
77182
+ function isAuthLikeError(error) {
77183
+ for (const item of errorChain(error)) {
77184
+ if (item instanceof OAuthError) return true;
77185
+ const rec = item;
77186
+ if (rec["status"] === 401 || rec["status"] === 403) return true;
77187
+ if (rec["statusCode"] === 401 || rec["statusCode"] === 403) return true;
77188
+ const code = rec["code"];
77189
+ if (typeof code === "string" && /^(unauthorized|authentication_error|auth_error|invalid_api_key|invalid_grant|permission_denied)$/i.test(code)) return true;
77190
+ if (item instanceof ProviderError || item instanceof Error) {
77191
+ const message = item.message;
77192
+ if (/(api key|authenticat|unauthori[sz]ed|forbidden|credentials|run \/login)/i.test(message)) return true;
77193
+ }
77194
+ }
77195
+ return false;
77196
+ }
77197
+ function isRateLimitLikeError(error) {
77198
+ for (const item of errorChain(error)) {
77199
+ if (item instanceof RetryableRefreshError) return true;
77200
+ const rec = item;
77201
+ if (rec["status"] === 429 || rec["statusCode"] === 429) return true;
77202
+ const code = rec["code"];
77203
+ if (typeof code === "string" && /^(rate_limit|rate_limit_exceeded|too_many_requests)$/i.test(code)) return true;
77204
+ }
77205
+ return false;
77206
+ }
77207
+ function errorChain(error) {
77208
+ const out = [];
77209
+ let current = error;
77210
+ for (let depth = 0; depth < 8 && current !== void 0 && current !== null; depth += 1) {
77211
+ out.push(current);
77212
+ if (typeof current !== "object") break;
77213
+ current = current.cause;
77214
+ }
77215
+ return out;
77216
+ }
77135
77217
  /**
77136
77218
  * SoulRegistry — per-session `Map<SoulKey, SoulHandle>` (v2 §5.2.3).
77137
77219
  *
@@ -82003,7 +82085,7 @@ var TurnManager = class {
82003
82085
  this.deps.sink.emit({
82004
82086
  type: "session.error",
82005
82087
  error: message,
82006
- error_type: "internal",
82088
+ error_type: classifySessionErrorType(error),
82007
82089
  details: {
82008
82090
  name,
82009
82091
  stack
@@ -82126,7 +82208,9 @@ var TurnManager = class {
82126
82208
  runtime,
82127
82209
  context: this.deps.contextState,
82128
82210
  sink: this.deps.sink,
82129
- emitStatusUpdate: () => this.emitStatusUpdate()
82211
+ emitStatusUpdate: () => {
82212
+ this.emitStatusUpdate();
82213
+ }
82130
82214
  });
82131
82215
  }
82132
82216
  };
@@ -92758,44 +92842,6 @@ var LocalFetchURLProvider = class {
92758
92842
  return titleText.length > 0 ? `# ${titleText}\n\n${fallbackText}` : fallbackText;
92759
92843
  }
92760
92844
  };
92761
- /**
92762
- * OAuth error classes.
92763
- *
92764
- * All errors derive from {@link OAuthError}. Distinguishing subclasses let
92765
- * callers react appropriately:
92766
- * - `OAuthUnauthorizedError`: 401/403 from token endpoint → refresh_token
92767
- * or credentials are bad; drive user through `/login` again.
92768
- * - `DeviceCodeExpiredError`: device_code TTL ran out before user approved;
92769
- * restart the device flow.
92770
- * - `DeviceCodeTimeoutError`: local 15 min wall-clock budget exhausted
92771
- * (D2 from Slice 5.0 brief).
92772
- * - `RetryableRefreshError`: 429 / 5xx from token endpoint; the refresh
92773
- * helper retries with exponential backoff before surfacing this.
92774
- */
92775
- var OAuthError = class extends Error {
92776
- constructor(message) {
92777
- super(message);
92778
- this.name = "OAuthError";
92779
- }
92780
- };
92781
- var OAuthUnauthorizedError = class extends OAuthError {
92782
- constructor(message) {
92783
- super(message);
92784
- this.name = "OAuthUnauthorizedError";
92785
- }
92786
- };
92787
- var DeviceCodeTimeoutError = class extends OAuthError {
92788
- constructor(message = "Device authorization timed out locally.") {
92789
- super(message);
92790
- this.name = "DeviceCodeTimeoutError";
92791
- }
92792
- };
92793
- var RetryableRefreshError = class extends OAuthError {
92794
- constructor(message) {
92795
- super(message);
92796
- this.name = "RetryableRefreshError";
92797
- }
92798
- };
92799
92845
  function tokenToWire(token) {
92800
92846
  return {
92801
92847
  access_token: token.accessToken,
@@ -94403,8 +94449,11 @@ var KimiWireClient = class {
94403
94449
  authStatus(providerName) {
94404
94450
  return this.request("auth.status", providerName !== void 0 ? { provider_name: providerName } : {});
94405
94451
  }
94406
- authLogin(providerName) {
94407
- return this.request("auth.login", providerName !== void 0 ? { provider_name: providerName } : {}, { timeoutMs: 1200 * 1e3 });
94452
+ authLogin(providerName, options) {
94453
+ return this.request("auth.login", providerName !== void 0 ? { provider_name: providerName } : {}, {
94454
+ timeoutMs: 1200 * 1e3,
94455
+ ...options?.signal !== void 0 ? { signal: options.signal } : {}
94456
+ });
94408
94457
  }
94409
94458
  authLogout(providerName) {
94410
94459
  return this.request("auth.logout", providerName !== void 0 ? { provider_name: providerName } : {});
@@ -95430,6 +95479,123 @@ function pathTextForVideo(att) {
95430
95479
  return att.sourcePath;
95431
95480
  }
95432
95481
  //#endregion
95482
+ //#region src/tui/components/chrome/device-code-box.ts
95483
+ var DeviceCodeBoxComponent = class {
95484
+ params;
95485
+ constructor(params) {
95486
+ this.params = params;
95487
+ }
95488
+ invalidate() {}
95489
+ render(width) {
95490
+ const { title, url, code, hint, colors } = this.params;
95491
+ const border = (s) => chalk.hex(colors.primary)(s);
95492
+ const safeWidth = Math.max(28, width);
95493
+ const innerWidth = Math.max(10, safeWidth - 4);
95494
+ const pad = " ";
95495
+ const contentLines = [
95496
+ truncateToWidth(chalk.bold.hex(colors.textStrong)(title), innerWidth, "…"),
95497
+ "",
95498
+ truncateToWidth(chalk.hex(colors.textDim)("Visit the URL below in your browser to authorize:"), innerWidth, "…"),
95499
+ truncateToWidth(chalk.hex(colors.primary)(url), innerWidth, "…"),
95500
+ "",
95501
+ truncateToWidth(`${chalk.bold.hex(colors.textDim)("Verification code: ")}${chalk.bold.hex(colors.accent)(code)}`, innerWidth, "…")
95502
+ ];
95503
+ if (hint !== void 0 && hint.length > 0) {
95504
+ contentLines.push("");
95505
+ contentLines.push(truncateToWidth(chalk.hex(colors.textDim)(hint), innerWidth, "…"));
95506
+ }
95507
+ const lines = [
95508
+ "",
95509
+ border("╭" + "─".repeat(safeWidth - 2) + "╮"),
95510
+ border("│") + " ".repeat(safeWidth - 2) + border("│")
95511
+ ];
95512
+ for (const content of contentLines) {
95513
+ const truncated = content;
95514
+ const vis = visibleWidth(truncated);
95515
+ const rightPad = Math.max(0, innerWidth - vis);
95516
+ lines.push(border("│") + pad + truncated + " ".repeat(rightPad) + border("│"));
95517
+ }
95518
+ lines.push(border("│") + " ".repeat(safeWidth - 2) + border("│"));
95519
+ lines.push(border("╰" + "─".repeat(safeWidth - 2) + "╯"));
95520
+ lines.push("");
95521
+ return lines;
95522
+ }
95523
+ };
95524
+ //#endregion
95525
+ //#region src/tui/components/chrome/moon-loader.ts
95526
+ const MOON_PHASES = [
95527
+ "🌑",
95528
+ "🌒",
95529
+ "🌓",
95530
+ "🌔",
95531
+ "🌕",
95532
+ "🌖",
95533
+ "🌗",
95534
+ "🌘"
95535
+ ];
95536
+ const MOON_INTERVAL = 120;
95537
+ const BRAILLE_FRAMES = [
95538
+ "⠋",
95539
+ "⠙",
95540
+ "⠹",
95541
+ "⠸",
95542
+ "⠼",
95543
+ "⠴",
95544
+ "⠦",
95545
+ "⠧",
95546
+ "⠇",
95547
+ "⠏"
95548
+ ];
95549
+ const BRAILLE_INTERVAL = 80;
95550
+ var MoonLoader = class extends Text {
95551
+ currentFrame = 0;
95552
+ intervalId = null;
95553
+ ui;
95554
+ frames;
95555
+ interval;
95556
+ colorFn;
95557
+ label;
95558
+ constructor(ui, style = "moon", colorFn, label = "") {
95559
+ super("", 1, 0);
95560
+ this.ui = ui;
95561
+ this.frames = style === "moon" ? MOON_PHASES : BRAILLE_FRAMES;
95562
+ this.interval = style === "moon" ? MOON_INTERVAL : BRAILLE_INTERVAL;
95563
+ this.colorFn = colorFn;
95564
+ this.label = label;
95565
+ this.start();
95566
+ }
95567
+ render(width) {
95568
+ return ["", ...super.render(width)];
95569
+ }
95570
+ start() {
95571
+ this.updateDisplay();
95572
+ this.intervalId = setInterval(() => {
95573
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length;
95574
+ this.updateDisplay();
95575
+ }, this.interval);
95576
+ }
95577
+ stop() {
95578
+ if (this.intervalId) {
95579
+ clearInterval(this.intervalId);
95580
+ this.intervalId = null;
95581
+ }
95582
+ }
95583
+ setLabel(label) {
95584
+ this.label = label;
95585
+ this.updateDisplay();
95586
+ }
95587
+ setColorFn(colorFn) {
95588
+ this.colorFn = colorFn;
95589
+ this.updateDisplay();
95590
+ }
95591
+ updateDisplay() {
95592
+ const frame = this.frames[this.currentFrame];
95593
+ const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
95594
+ this.setText(this.label ? `${coloredFrame} ${this.label}` : coloredFrame);
95595
+ this.ui.requestRender();
95596
+ }
95597
+ };
95598
+ //#endregion
95433
95599
  //#region src/tui/components/chrome/welcome.ts
95434
95600
  var WelcomeComponent = class {
95435
95601
  state;
@@ -96250,7 +96416,10 @@ function pickResultRenderer(toolName) {
96250
96416
  const MAX_ARG_LENGTH = 60;
96251
96417
  const CALL_PREVIEW_LINES = 10;
96252
96418
  const MAX_SUB_TOOL_CALLS_SHOWN = 4;
96419
+ const MAX_SINGLE_SUBAGENT_TOOL_ROWS = 4;
96253
96420
  const APPROVED_PLAN_MARKER = "## Approved Plan:";
96421
+ const STREAMING_PROGRESS_INTERVAL_MS = 1e3;
96422
+ const SUBAGENT_ELAPSED_INTERVAL_MS = 1e3;
96254
96423
  function str(v) {
96255
96424
  return typeof v === "string" ? v : "";
96256
96425
  }
@@ -96260,6 +96429,17 @@ function formatSubagentTokens(usage) {
96260
96429
  if (total <= 0) return void 0;
96261
96430
  return `${total >= 1e3 ? `${(total / 1e3).toFixed(1)}k` : String(total)} tok`;
96262
96431
  }
96432
+ function formatByteSize(bytes) {
96433
+ if (bytes < 1024) return `${String(bytes)} B`;
96434
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
96435
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
96436
+ }
96437
+ function formatElapsed(seconds) {
96438
+ if (seconds < 60) return `${String(seconds)}s`;
96439
+ const minutes = Math.floor(seconds / 60);
96440
+ const remainder = seconds % 60;
96441
+ return `${String(minutes)}m ${String(remainder)}s`;
96442
+ }
96263
96443
  function extractApprovedPlan(output) {
96264
96444
  const markerIndex = output.indexOf(APPROVED_PLAN_MARKER);
96265
96445
  if (markerIndex < 0) return "";
@@ -96419,6 +96599,30 @@ function extractKeyArgument(toolName, args) {
96419
96599
  }
96420
96600
  return null;
96421
96601
  }
96602
+ function formatSubagentLabel(agentName) {
96603
+ const raw = agentName?.trim();
96604
+ if (raw === void 0 || raw.length === 0) return "SubAgent";
96605
+ const label = raw.split(/[-_\s]+/).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
96606
+ if (/\bagent$/i.test(label)) return label;
96607
+ return `${label} Agent`;
96608
+ }
96609
+ function tailNonEmptyLines(text, maxLines) {
96610
+ if (text.length === 0) return [];
96611
+ return text.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-maxLines);
96612
+ }
96613
+ var PrefixedWrappedLine = class {
96614
+ constructor(firstPrefix, continuationPrefix, text) {
96615
+ this.firstPrefix = firstPrefix;
96616
+ this.continuationPrefix = continuationPrefix;
96617
+ this.text = text;
96618
+ }
96619
+ invalidate() {}
96620
+ render(width) {
96621
+ const prefixWidth = Math.max(visibleWidth(this.firstPrefix), visibleWidth(this.continuationPrefix));
96622
+ const contentWidth = Math.max(1, width - prefixWidth);
96623
+ return new Text(this.text, 0, 0).render(contentWidth).map((line, index) => index === 0 ? `${this.firstPrefix}${line}` : `${this.continuationPrefix}${line}`);
96624
+ }
96625
+ };
96422
96626
  var ToolCallComponent = class extends Container {
96423
96627
  expanded = false;
96424
96628
  toolCall;
@@ -96440,16 +96644,22 @@ var ToolCallComponent = class extends Container {
96440
96644
  subagentAgentName;
96441
96645
  ongoingSubCalls = /* @__PURE__ */ new Map();
96442
96646
  finishedSubCalls = [];
96647
+ subToolActivities = /* @__PURE__ */ new Map();
96648
+ subToolOrderSeq = 0;
96443
96649
  hiddenSubCallCount = 0;
96444
96650
  /**
96445
- * 子 agent 最近的若干行文本(thinking / content.delta)。渲染时只显示
96446
- * 最后几行,避免占用过多屏幕。
96651
+ * 子 agent 最近的若干行正式输出。历史 replay 只有混合 text 时也落在这里。
96447
96652
  */
96448
96653
  subagentText = "";
96654
+ subagentThinkingText = "";
96449
96655
  subagentPhase;
96450
96656
  subagentUsage;
96451
96657
  subagentResultSummary;
96452
96658
  subagentError;
96659
+ streamingProgressTimer;
96660
+ subagentElapsedTimer;
96661
+ subagentStartedAtMs;
96662
+ subagentEndedAtMs;
96453
96663
  /**
96454
96664
  * 当 ToolCallComponent 被 group 容器(`AgentGroupComponent` /
96455
96665
  * `ReadGroupComponent`)借走做"隐身状态容器"时,由 group 注册回调;任何
@@ -96474,6 +96684,8 @@ var ToolCallComponent = class extends Container {
96474
96684
  this.callPreviewEndIndex = this.children.length;
96475
96685
  this.buildContent();
96476
96686
  this.buildSubagentBlock();
96687
+ this.syncStreamingProgressTimer();
96688
+ this.syncSubagentElapsedTimer();
96477
96689
  }
96478
96690
  setExpanded(expanded) {
96479
96691
  if (this.expanded === expanded) return;
@@ -96482,16 +96694,24 @@ var ToolCallComponent = class extends Container {
96482
96694
  }
96483
96695
  setResult(result) {
96484
96696
  this.result = result;
96697
+ this.finalizeSubagentElapsedIfNeeded();
96698
+ this.syncStreamingProgressTimer();
96699
+ this.syncSubagentElapsedTimer();
96485
96700
  this.headerText.setText(this.buildHeader());
96486
96701
  this.rebuildBody();
96487
96702
  this.notifySnapshotChange();
96488
96703
  }
96489
96704
  updateToolCall(toolCall) {
96490
96705
  this.toolCall = toolCall;
96706
+ this.syncStreamingProgressTimer();
96491
96707
  this.headerText.setText(this.buildHeader());
96492
96708
  this.rebuildBody();
96493
96709
  this.ui?.requestRender();
96494
96710
  }
96711
+ dispose() {
96712
+ this.stopStreamingProgressTimer();
96713
+ this.stopSubagentElapsedTimer();
96714
+ }
96495
96715
  /**
96496
96716
  * 异步注入 plan body / path。仅 ExitPlanMode 工具卡使用:当 LLM 走
96497
96717
  * plan-file 模式时 `args.plan` 为空,stream-ops 会从 `client.getPlan()`
@@ -96523,6 +96743,7 @@ var ToolCallComponent = class extends Container {
96523
96743
  name: call.name,
96524
96744
  args: call.args
96525
96745
  });
96746
+ this.upsertSubToolActivity(call.id, call.name, call.args, "ongoing");
96526
96747
  continue;
96527
96748
  }
96528
96749
  this.finishedSubCalls.push({
@@ -96531,6 +96752,7 @@ var ToolCallComponent = class extends Container {
96531
96752
  output: call.result.output,
96532
96753
  isError: call.result.is_error ?? false
96533
96754
  });
96755
+ this.upsertSubToolActivity(call.id, call.name, call.args, call.result.is_error === true ? "failed" : "done");
96534
96756
  }
96535
96757
  while (this.finishedSubCalls.length > MAX_SUB_TOOL_CALLS_SHOWN) {
96536
96758
  this.finishedSubCalls.shift();
@@ -96541,6 +96763,7 @@ var ToolCallComponent = class extends Container {
96541
96763
  if (this.subagentAgentId === agentId && this.subagentAgentName === agentName) return;
96542
96764
  this.subagentAgentId = agentId;
96543
96765
  this.subagentAgentName = agentName;
96766
+ this.headerText.setText(this.buildHeader());
96544
96767
  this.rebuildContent();
96545
96768
  this.notifySnapshotChange();
96546
96769
  this.ui?.requestRender();
@@ -96557,7 +96780,7 @@ var ToolCallComponent = class extends Container {
96557
96780
  getSubagentSnapshot() {
96558
96781
  const finished = this.finishedSubCalls.length + this.hiddenSubCallCount;
96559
96782
  const tokens = this.subagentUsage ? this.subagentUsage.input + this.subagentUsage.output : 0;
96560
- const latestActivity = computeLatestActivity(this.ongoingSubCalls, this.finishedSubCalls, this.subagentText);
96783
+ const latestActivity = computeLatestActivity(this.ongoingSubCalls, this.finishedSubCalls, this.getCombinedSubagentText());
96561
96784
  const derivedPhase = this.result !== void 0 ? this.result.is_error ? "failed" : "done" : this.subagentPhase;
96562
96785
  return {
96563
96786
  toolCallId: this.toolCall.id,
@@ -96607,6 +96830,74 @@ var ToolCallComponent = class extends Container {
96607
96830
  notifySnapshotChange() {
96608
96831
  this.onSnapshotChange?.();
96609
96832
  }
96833
+ upsertSubToolActivity(id, name, args, phase) {
96834
+ const existing = this.subToolActivities.get(id);
96835
+ if (existing !== void 0) {
96836
+ existing.name = name;
96837
+ existing.args = args;
96838
+ existing.phase = phase;
96839
+ return;
96840
+ }
96841
+ this.subToolActivities.set(id, {
96842
+ id,
96843
+ name,
96844
+ args,
96845
+ phase,
96846
+ orderSeq: ++this.subToolOrderSeq
96847
+ });
96848
+ }
96849
+ getCombinedSubagentText() {
96850
+ return [this.subagentThinkingText, this.subagentText].filter((s) => s.length > 0).join("\n");
96851
+ }
96852
+ isStreamingEditPreview() {
96853
+ return this.toolCall.name === "Edit" && this.result === void 0 && this.toolCall.streamingArguments !== void 0;
96854
+ }
96855
+ syncStreamingProgressTimer() {
96856
+ if (!this.isStreamingEditPreview()) {
96857
+ this.stopStreamingProgressTimer();
96858
+ return;
96859
+ }
96860
+ if (this.ui === void 0 || this.streamingProgressTimer !== void 0) return;
96861
+ this.streamingProgressTimer = setInterval(() => {
96862
+ if (!this.isStreamingEditPreview()) {
96863
+ this.stopStreamingProgressTimer();
96864
+ return;
96865
+ }
96866
+ this.rebuildBody();
96867
+ this.ui?.requestRender();
96868
+ }, STREAMING_PROGRESS_INTERVAL_MS);
96869
+ }
96870
+ stopStreamingProgressTimer() {
96871
+ if (this.streamingProgressTimer === void 0) return;
96872
+ clearInterval(this.streamingProgressTimer);
96873
+ this.streamingProgressTimer = void 0;
96874
+ }
96875
+ syncSubagentElapsedTimer() {
96876
+ const phase = this.getDerivedSubagentPhase();
96877
+ if (!(this.isSingleSubagentView() && this.subagentStartedAtMs !== void 0 && (phase === "spawning" || phase === "running"))) {
96878
+ this.stopSubagentElapsedTimer();
96879
+ return;
96880
+ }
96881
+ if (this.ui === void 0 || this.subagentElapsedTimer !== void 0) return;
96882
+ this.subagentElapsedTimer = setInterval(() => {
96883
+ const latestPhase = this.getDerivedSubagentPhase();
96884
+ if (latestPhase !== "spawning" && latestPhase !== "running") {
96885
+ this.stopSubagentElapsedTimer();
96886
+ return;
96887
+ }
96888
+ this.headerText.setText(this.buildHeader());
96889
+ this.invalidate();
96890
+ this.ui?.requestRender();
96891
+ }, SUBAGENT_ELAPSED_INTERVAL_MS);
96892
+ }
96893
+ stopSubagentElapsedTimer() {
96894
+ if (this.subagentElapsedTimer === void 0) return;
96895
+ clearInterval(this.subagentElapsedTimer);
96896
+ this.subagentElapsedTimer = void 0;
96897
+ }
96898
+ finalizeSubagentElapsedIfNeeded() {
96899
+ if (this.toolCall.name === "Agent" && this.subagentStartedAtMs !== void 0 && this.subagentEndedAtMs === void 0) this.subagentEndedAtMs = Date.now();
96900
+ }
96610
96901
  /**
96611
96902
  * 来自 wire 'subagent.spawned'。子 agent 已被注册,但内部活动事件
96612
96903
  * (content.delta / tool.call) 可能尚未到达——把 UI 切到 'spawning'
@@ -96616,6 +96907,10 @@ var ToolCallComponent = class extends Container {
96616
96907
  this.subagentAgentId = meta.agentId;
96617
96908
  this.subagentAgentName = meta.agentName;
96618
96909
  this.subagentPhase = meta.runInBackground ? "backgrounded" : "spawning";
96910
+ this.subagentStartedAtMs = Date.now();
96911
+ this.subagentEndedAtMs = void 0;
96912
+ this.syncSubagentElapsedTimer();
96913
+ this.headerText.setText(this.buildHeader());
96619
96914
  this.rebuildContent();
96620
96915
  this.notifySnapshotChange();
96621
96916
  this.ui?.requestRender();
@@ -96626,8 +96921,12 @@ var ToolCallComponent = class extends Container {
96626
96921
  */
96627
96922
  onSubagentCompleted(payload) {
96628
96923
  this.subagentPhase = "done";
96924
+ this.subagentEndedAtMs ??= Date.now();
96629
96925
  this.subagentUsage = payload.usage;
96630
96926
  this.subagentResultSummary = payload.resultSummary.length > 0 ? payload.resultSummary : void 0;
96927
+ if (this.subagentText.trim().length === 0 && this.subagentResultSummary !== void 0) this.subagentText = this.subagentResultSummary;
96928
+ this.syncSubagentElapsedTimer();
96929
+ this.headerText.setText(this.buildHeader());
96631
96930
  this.rebuildContent();
96632
96931
  this.notifySnapshotChange();
96633
96932
  this.ui?.requestRender();
@@ -96637,14 +96936,19 @@ var ToolCallComponent = class extends Container {
96637
96936
  */
96638
96937
  onSubagentFailed(payload) {
96639
96938
  this.subagentPhase = "failed";
96939
+ this.subagentEndedAtMs ??= Date.now();
96640
96940
  this.subagentError = payload.error;
96941
+ this.syncSubagentElapsedTimer();
96942
+ this.headerText.setText(this.buildHeader());
96641
96943
  this.rebuildContent();
96642
96944
  this.notifySnapshotChange();
96643
96945
  this.ui?.requestRender();
96644
96946
  }
96645
- appendSubagentText(text) {
96646
- this.subagentText += text;
96947
+ appendSubagentText(text, kind = "text") {
96948
+ if (kind === "thinking") this.subagentThinkingText += text;
96949
+ else this.subagentText += text;
96647
96950
  if (this.subagentPhase === void 0 || this.subagentPhase === "spawning") this.subagentPhase = "running";
96951
+ this.headerText.setText(this.buildHeader());
96648
96952
  this.rebuildContent();
96649
96953
  this.notifySnapshotChange();
96650
96954
  this.ui?.requestRender();
@@ -96656,7 +96960,9 @@ var ToolCallComponent = class extends Container {
96656
96960
  args: call.args,
96657
96961
  ...existing?.streamingArguments !== void 0 ? { streamingArguments: existing.streamingArguments } : {}
96658
96962
  });
96963
+ this.upsertSubToolActivity(call.id, call.name, call.args, "ongoing");
96659
96964
  if (this.subagentPhase === void 0 || this.subagentPhase === "spawning") this.subagentPhase = "running";
96965
+ this.headerText.setText(this.buildHeader());
96660
96966
  this.rebuildContent();
96661
96967
  this.notifySnapshotChange();
96662
96968
  this.ui?.requestRender();
@@ -96670,6 +96976,8 @@ var ToolCallComponent = class extends Container {
96670
96976
  args: parsed,
96671
96977
  streamingArguments: nextArgsText
96672
96978
  });
96979
+ this.upsertSubToolActivity(delta.id, delta.name ?? existing?.name ?? "Tool", parsed, "ongoing");
96980
+ this.headerText.setText(this.buildHeader());
96673
96981
  this.rebuildContent();
96674
96982
  this.notifySnapshotChange();
96675
96983
  this.ui?.requestRender();
@@ -96684,10 +96992,12 @@ var ToolCallComponent = class extends Container {
96684
96992
  output: result.output,
96685
96993
  isError: result.is_error ?? false
96686
96994
  });
96995
+ this.upsertSubToolActivity(result.tool_call_id, ongoing.name, ongoing.args, result.is_error === true ? "failed" : "done");
96687
96996
  while (this.finishedSubCalls.length > MAX_SUB_TOOL_CALLS_SHOWN) {
96688
96997
  this.finishedSubCalls.shift();
96689
96998
  this.hiddenSubCallCount += 1;
96690
96999
  }
97000
+ this.headerText.setText(this.buildHeader());
96691
97001
  this.rebuildContent();
96692
97002
  this.notifySnapshotChange();
96693
97003
  this.ui?.requestRender();
@@ -96714,6 +97024,7 @@ var ToolCallComponent = class extends Container {
96714
97024
  const tone = isError ? chalk.hex(colors.error) : chalk.hex(colors.primary);
96715
97025
  return `${bullet}${tone.bold(label)}`;
96716
97026
  }
97027
+ if (this.isSingleSubagentView()) return this.buildSingleSubagentHeader();
96717
97028
  const verb = isFinished ? "Used" : toolCall.streamingArguments !== void 0 ? "Preparing" : "Using";
96718
97029
  const keyArg = extractKeyArgument(toolCall.name, toolCall.args);
96719
97030
  const toolRef = chalk.hex(colors.primary).bold(toolCall.name);
@@ -96743,6 +97054,10 @@ var ToolCallComponent = class extends Container {
96743
97054
  }
96744
97055
  buildSubagentBlock() {
96745
97056
  if (this.subagentAgentId === void 0 && this.ongoingSubCalls.size === 0 && this.finishedSubCalls.length === 0 && this.subagentText.length === 0 && this.subagentPhase === void 0) return;
97057
+ if (this.isSingleSubagentView()) {
97058
+ this.buildSingleSubagentBlock();
97059
+ return;
97060
+ }
96746
97061
  const dim = chalk.dim;
96747
97062
  const phaseChip = this.formatPhaseChip();
96748
97063
  const headerLabel = this.subagentAgentName !== void 0 ? `subagent ${this.subagentAgentName} (${this.formatAgentId()})` : `subagent (${this.formatAgentId()})`;
@@ -96817,6 +97132,78 @@ var ToolCallComponent = class extends Container {
96817
97132
  const id = this.subagentAgentId ?? "";
96818
97133
  return id.length > 10 ? id.slice(0, 10) + "…" : id;
96819
97134
  }
97135
+ hasSubagentState() {
97136
+ return this.subagentAgentId !== void 0 || this.ongoingSubCalls.size > 0 || this.finishedSubCalls.length > 0 || this.subToolActivities.size > 0 || this.subagentText.length > 0 || this.subagentThinkingText.length > 0 || this.subagentPhase !== void 0;
97137
+ }
97138
+ isSingleSubagentView() {
97139
+ return this.toolCall.name === "Agent" && this.hasSubagentState();
97140
+ }
97141
+ getDerivedSubagentPhase() {
97142
+ if (this.result !== void 0) return this.result.is_error ? "failed" : "done";
97143
+ return this.subagentPhase;
97144
+ }
97145
+ buildSingleSubagentHeader() {
97146
+ const phase = this.getDerivedSubagentPhase();
97147
+ const isFailed = phase === "failed";
97148
+ const isDone = phase === "done";
97149
+ const bullet = isFailed ? chalk.hex(this.colors.error)("✗ ") : isDone ? chalk.hex(this.colors.success)(STATUS_BULLET) : chalk.hex(this.colors.roleAssistant)(STATUS_BULLET);
97150
+ const labelText = formatSubagentLabel(this.subagentAgentName);
97151
+ const label = chalk.hex(this.colors.primary).bold(labelText);
97152
+ const status = this.formatSingleSubagentStatus(phase);
97153
+ const description = str(this.toolCall.args["description"]);
97154
+ const descriptionPlain = description.length > 0 ? ` (${description})` : "";
97155
+ const descriptionText = descriptionPlain.length > 0 ? chalk.dim(descriptionPlain) : "";
97156
+ const statsText = this.formatSingleSubagentStatsText();
97157
+ if (isDone) {
97158
+ const success = chalk.hex(this.colors.success);
97159
+ return `${bullet}${success.bold(labelText)} ${success(`Completed${descriptionPlain}${statsText}`)}`;
97160
+ }
97161
+ return `${bullet}${label} ${status}${descriptionText}${chalk.dim(statsText)}`;
97162
+ }
97163
+ formatSingleSubagentStatus(phase) {
97164
+ switch (phase) {
97165
+ case "done": return chalk.hex(this.colors.success)("Completed");
97166
+ case "failed": return chalk.hex(this.colors.error)("Failed");
97167
+ case "running": return chalk.hex(this.colors.primary)("Running");
97168
+ case "backgrounded": return "Backgrounded";
97169
+ case "spawning":
97170
+ case void 0: return chalk.hex(this.colors.primary)("Starting");
97171
+ }
97172
+ }
97173
+ formatSingleSubagentStatsText() {
97174
+ const parts = [`${String(this.subToolActivities.size)} tool${this.subToolActivities.size === 1 ? "" : "s"}`];
97175
+ const elapsed = this.getSubagentElapsedSeconds();
97176
+ if (elapsed !== void 0) parts.push(formatElapsed(elapsed));
97177
+ return ` · ${parts.join(" · ")}`;
97178
+ }
97179
+ getSubagentElapsedSeconds() {
97180
+ if (this.subagentStartedAtMs === void 0) return void 0;
97181
+ const end = this.subagentEndedAtMs ?? Date.now();
97182
+ return Math.max(0, Math.floor((end - this.subagentStartedAtMs) / 1e3));
97183
+ }
97184
+ buildSingleSubagentBlock() {
97185
+ for (const activity of this.getRecentSubToolActivities()) {
97186
+ const mark = activity.phase === "failed" ? chalk.hex(this.colors.error)("✗") : activity.phase === "done" ? chalk.hex(this.colors.success)("•") : chalk.hex(this.colors.text)("•");
97187
+ const verb = activity.phase === "ongoing" ? "Using" : "Used";
97188
+ this.addChild(new Text(` ${mark} ${this.formatSubToolActivity(verb, activity)}`, 0, 0));
97189
+ }
97190
+ if (this.getDerivedSubagentPhase() === "failed" && this.subagentError !== void 0) {
97191
+ const errorLine = tailNonEmptyLines(this.subagentError, 1).at(-1);
97192
+ if (errorLine !== void 0) this.addChild(new PrefixedWrappedLine(` ${chalk.hex(this.colors.error)("└")} `, " ", chalk.hex(this.colors.error)(errorLine)));
97193
+ return;
97194
+ }
97195
+ const outputLine = tailNonEmptyLines(this.subagentText, 1).at(-1);
97196
+ const thinkingLine = tailNonEmptyLines(this.subagentThinkingText, 1).at(-1);
97197
+ if (this.getDerivedSubagentPhase() !== "done" && thinkingLine !== void 0) this.addChild(new PrefixedWrappedLine(` ${chalk.dim("◌")} `, " ", chalk.dim(thinkingLine)));
97198
+ if (outputLine !== void 0) this.addChild(new PrefixedWrappedLine(` ${chalk.hex(this.colors.text)("└")} `, " ", chalk.hex(this.colors.text)(outputLine)));
97199
+ }
97200
+ getRecentSubToolActivities() {
97201
+ return [...this.subToolActivities.values()].toSorted((a, b) => a.orderSeq - b.orderSeq).slice(-MAX_SINGLE_SUBAGENT_TOOL_ROWS);
97202
+ }
97203
+ formatSubToolActivity(verb, activity) {
97204
+ const keyArg = extractKeyArgument(activity.name, activity.args);
97205
+ return `${verb} ${chalk.hex(this.colors.primary)(activity.name)}${keyArg ? chalk.dim(` (${keyArg})`) : ""}`;
97206
+ }
96820
97207
  buildCallPreview() {
96821
97208
  const name = this.toolCall.name;
96822
97209
  if (name === "ExitPlanMode") {
@@ -96854,11 +97241,12 @@ var ToolCallComponent = class extends Container {
96854
97241
  * Live-rendering during the `tool.call.delta` streaming window.
96855
97242
  *
96856
97243
  * For tools we recognise, we reach into the partial JSON (via
96857
- * `extractPartialStringField`) and render the high-signal field as
96858
- * it grows — Write's `content` as highlighted code, Edit's diff,
96859
- * Bash's `$ command`, etc. While args are still streaming we render
96860
- * the body in full (no 10-line cap) — once the result lands, the
96861
- * preview snaps to the collapsed cap unless the user has expanded.
97244
+ * `extractPartialStringField`) and render a stable high-signal
97245
+ * preview: Write's `content` as highlighted code, Edit's argument
97246
+ * receive progress, Bash's `$ command`, etc. While args are still
97247
+ * streaming we render the body in full (no 10-line cap) — once the
97248
+ * result lands, the preview snaps to the collapsed cap unless the user
97249
+ * has expanded.
96862
97250
  */
96863
97251
  buildStreamingPreview(streamText) {
96864
97252
  const name = this.toolCall.name;
@@ -96873,14 +97261,12 @@ var ToolCallComponent = class extends Container {
96873
97261
  return;
96874
97262
  }
96875
97263
  if (name === "Edit") {
96876
- const oldStr = extractPartialStringField(streamText, "old_string") ?? "";
96877
- const newStr = extractPartialStringField(streamText, "new_string") ?? "";
96878
- if (oldStr.length === 0 && newStr.length === 0) return;
96879
- const lines = renderDiffLinesClustered(oldStr, newStr, extractPartialStringField(streamText, "file_path") ?? extractPartialStringField(streamText, "path") ?? "", this.colors, {
96880
- contextLines: 3,
96881
- isIncomplete: true
96882
- });
96883
- for (const line of lines) this.addChild(new Text(line, 2, 0));
97264
+ const filePath = extractPartialStringField(streamText, "file_path") ?? extractPartialStringField(streamText, "path") ?? "";
97265
+ const bytes = Buffer.byteLength(streamText, "utf8");
97266
+ const startedAtMs = this.toolCall.streamingStartedAtMs;
97267
+ const elapsedSeconds = startedAtMs === void 0 ? 0 : Math.max(0, Math.floor((Date.now() - startedAtMs) / 1e3));
97268
+ const progress = `Preparing changes${filePath.length > 0 ? ` for ${filePath}` : ""}... ${formatByteSize(bytes)} · ${formatElapsed(elapsedSeconds)} elapsed`;
97269
+ this.addChild(new Text(chalk.dim(progress), 2, 0));
96884
97270
  return;
96885
97271
  }
96886
97272
  if (name === "Bash") {
@@ -96919,6 +97305,7 @@ var ToolCallComponent = class extends Container {
96919
97305
  buildContent() {
96920
97306
  const { result } = this;
96921
97307
  if (result === void 0 || !result.output) return;
97308
+ if (this.isSingleSubagentView()) return;
96922
97309
  if (this.toolCall.name === "ExitPlanMode" && !result.is_error) {
96923
97310
  const outcome = interpretExitPlanModeOutcome(result.output);
96924
97311
  if (outcome.kind === "rejected" && outcome.feedback !== void 0) {
@@ -97350,6 +97737,12 @@ function disposeActiveThinkingComponent(state) {
97350
97737
  state.activeThinkingComponent = void 0;
97351
97738
  }
97352
97739
  }
97740
+ function hasDispose(value) {
97741
+ return typeof value === "object" && value !== null && "dispose" in value && typeof value.dispose === "function";
97742
+ }
97743
+ function disposePendingToolComponents(state) {
97744
+ for (const component of state.pendingToolComponents.values()) if (hasDispose(component)) component.dispose();
97745
+ }
97353
97746
  /**
97354
97747
  * Hard reset of the transcript UI: drop every entry, tool component,
97355
97748
  * compaction block, streaming draft, todo panel, and attachment, then
@@ -97359,6 +97752,7 @@ function clearTranscriptAndRedraw(state) {
97359
97752
  state.transcriptEntries = [];
97360
97753
  disposeActiveCompactionBlock(state);
97361
97754
  disposeActiveThinkingComponent(state);
97755
+ disposePendingToolComponents(state);
97362
97756
  state.transcriptContainer.clear();
97363
97757
  state.pendingToolComponents.clear();
97364
97758
  state.streamingComponent = void 0;
@@ -97401,6 +97795,31 @@ function emitSuccess(state, message) {
97401
97795
  function emitMuted(state, message) {
97402
97796
  emitStatus(state, message, state.colors.textDim);
97403
97797
  }
97798
+ /** Append the OAuth device-code panel directly to the transcript container. */
97799
+ function emitDeviceCodeBox(state, options) {
97800
+ state.transcriptContainer.addChild(new DeviceCodeBoxComponent({
97801
+ ...options,
97802
+ colors: state.colors
97803
+ }));
97804
+ state.ui.requestRender();
97805
+ }
97806
+ /**
97807
+ * Append a braille spinner line that callers can finalize with a ✓/✗
97808
+ * static line once the awaited operation completes.
97809
+ */
97810
+ function emitLiveSpinner(state, label) {
97811
+ const tint = (s) => chalk.hex(state.colors.primary)(s);
97812
+ const spinner = new MoonLoader(state.ui, "braille", tint, label);
97813
+ state.transcriptContainer.addChild(spinner);
97814
+ state.ui.requestRender();
97815
+ return { stop({ ok, label: finalLabel }) {
97816
+ spinner.stop();
97817
+ const tone = ok ? state.colors.success : state.colors.error;
97818
+ const symbol = ok ? "✓" : "✗";
97819
+ spinner.setText(chalk.hex(tone)(`${symbol} ${finalLabel}`));
97820
+ state.ui.requestRender();
97821
+ } };
97822
+ }
97404
97823
  //#endregion
97405
97824
  //#region src/utils/persistence.ts
97406
97825
  /**
@@ -97553,8 +97972,9 @@ function handleSubagentSourceEvent(ectx, payload) {
97553
97972
  tc.setSubagentMeta(payload.source_id, payload.source_name);
97554
97973
  if (payload.method === "content.delta") {
97555
97974
  const d = payload.data;
97556
- const text = d.type === "text" && typeof d.text === "string" ? d.text : typeof d.think === "string" ? d.think : typeof d.thinking === "string" ? d.thinking : void 0;
97557
- if (text !== void 0 && typeof tc.appendSubagentText === "function") tc.appendSubagentText(text);
97975
+ const textKind = d.type === "text" && typeof d.text === "string" ? "text" : d.type === "thinking" && typeof d.thinking === "string" ? "thinking" : typeof d.think === "string" || typeof d.thinking === "string" ? "thinking" : void 0;
97976
+ const text = textKind === "text" ? d.text : textKind === "thinking" ? d.think ?? d.thinking : void 0;
97977
+ if (text !== void 0 && textKind !== void 0 && typeof tc.appendSubagentText === "function") tc.appendSubagentText(text, textKind);
97558
97978
  return;
97559
97979
  }
97560
97980
  if (payload.method === "tool.call") {
@@ -97937,28 +98357,49 @@ function createAuthCommands(deps = {}) {
97937
98357
  priority: 40,
97938
98358
  async execute(_args, ctx) {
97939
98359
  if (deps.client === void 0) return ok$2("OAuth is unavailable: no wire client is attached.");
98360
+ if ((await deps.client.authStatus(providerName)).providers.some((provider) => provider.provider_name === providerName && provider.has_token)) return ok$2("Already logged in. Run /logout first to switch accounts.");
98361
+ let spinner;
98362
+ const controller = new AbortController();
97940
98363
  const disposeDeviceCodeHandler = deps.client.onRequest("auth.device_code", (req) => {
97941
98364
  const data = req.data;
97942
98365
  const url = data.verification_uri_complete ?? "";
97943
98366
  if (url.length > 0) openUrl(url);
97944
- ctx.showStatus(`Opening browser for authorization...\n\n ${url}\n\nCode: ${data.user_code ?? ""}\n\nWaiting for authorization...`);
98367
+ ctx.showDeviceCodeBox({
98368
+ title: "Sign in to Kimi Code",
98369
+ url,
98370
+ code: data.user_code ?? "",
98371
+ hint: "Press Ctrl-C to cancel"
98372
+ });
98373
+ spinner = ctx.showLiveSpinner("Waiting for authorization…");
97945
98374
  return { ok: true };
97946
98375
  });
98376
+ const disposeCancel = ctx.registerCancellable(() => {
98377
+ controller.abort();
98378
+ });
97947
98379
  try {
97948
- await deps.client.authLogin(providerName);
98380
+ await deps.client.authLogin(providerName, { signal: controller.signal });
98381
+ spinner?.stop({
98382
+ ok: true,
98383
+ label: "Logged in."
98384
+ });
98385
+ spinner = void 0;
97949
98386
  try {
97950
98387
  await refreshConfigAfterLogin(deps.client, ctx);
97951
98388
  } catch (refreshError) {
97952
98389
  return ok$2(`Login successful, but failed to refresh config: ${refreshError instanceof Error ? refreshError.message : String(refreshError)}`);
97953
98390
  }
97954
- return {
97955
- type: "ok",
97956
- message: "✓ Login successful!",
97957
- color: "green"
97958
- };
98391
+ return { type: "ok" };
97959
98392
  } catch (error) {
98393
+ const cancelled = controller.signal.aborted;
98394
+ spinner?.stop({
98395
+ ok: false,
98396
+ label: cancelled ? "Login cancelled." : "Login failed."
98397
+ });
98398
+ spinner = void 0;
98399
+ if (cancelled) return { type: "ok" };
97960
98400
  return ok$2(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
97961
98401
  } finally {
98402
+ disposeCancel();
97962
98403
  disposeDeviceCodeHandler();
97963
98404
  }
97964
98405
  }
@@ -98279,7 +98720,9 @@ function createDefaultRegistry(authDeps) {
98279
98720
  */
98280
98721
  const BRANCH_TTL_MS = 5e3;
98281
98722
  const STATUS_TTL_MS = 15e3;
98723
+ const PULL_REQUEST_TTL_MS = 6e4;
98282
98724
  const SPAWN_TIMEOUT_MS = 500;
98725
+ const PR_SPAWN_TIMEOUT_MS = 1e3;
98283
98726
  const AHEAD_BEHIND_RE = /\[(?:ahead (\d+))?(?:, )?(?:behind (\d+))?\]/;
98284
98727
  function createGitStatusCache(workDir) {
98285
98728
  const isRepo = detectGitRepo(workDir);
@@ -98291,6 +98734,13 @@ function createGitStatusCache(workDir) {
98291
98734
  dirty: false,
98292
98735
  ahead: 0,
98293
98736
  behind: 0,
98737
+ diffAdded: 0,
98738
+ diffDeleted: 0,
98739
+ fetchedAt: 0
98740
+ };
98741
+ let pullRequest = {
98742
+ value: null,
98743
+ branch: null,
98294
98744
  fetchedAt: 0
98295
98745
  };
98296
98746
  return { getStatus: () => {
@@ -98305,11 +98755,19 @@ function createGitStatusCache(workDir) {
98305
98755
  ...readStatus(workDir),
98306
98756
  fetchedAt: now
98307
98757
  };
98758
+ if (pullRequest.branch !== branch.value || now - pullRequest.fetchedAt >= PULL_REQUEST_TTL_MS) pullRequest = {
98759
+ value: readPullRequest(workDir),
98760
+ branch: branch.value,
98761
+ fetchedAt: now
98762
+ };
98308
98763
  return {
98309
98764
  branch: branch.value,
98310
98765
  dirty: status.dirty,
98311
98766
  ahead: status.ahead,
98312
- behind: status.behind
98767
+ behind: status.behind,
98768
+ diffAdded: status.diffAdded,
98769
+ diffDeleted: status.diffDeleted,
98770
+ pullRequest: pullRequest.value
98313
98771
  };
98314
98772
  } };
98315
98773
  }
@@ -98363,7 +98821,9 @@ function readStatus(workDir) {
98363
98821
  if (result.status !== 0) return {
98364
98822
  dirty: false,
98365
98823
  ahead: 0,
98366
- behind: 0
98824
+ behind: 0,
98825
+ diffAdded: 0,
98826
+ diffDeleted: 0
98367
98827
  };
98368
98828
  let dirty = false;
98369
98829
  let ahead = 0;
@@ -98375,28 +98835,149 @@ function readStatus(workDir) {
98375
98835
  behind = Number.parseInt(m[2] ?? "0", 10) || 0;
98376
98836
  }
98377
98837
  } else if (line.trim().length > 0) dirty = true;
98838
+ const diff = dirty ? readDiffStats(workDir) : {
98839
+ added: 0,
98840
+ deleted: 0
98841
+ };
98378
98842
  return {
98379
98843
  dirty,
98380
98844
  ahead,
98381
- behind
98845
+ behind,
98846
+ diffAdded: diff.added,
98847
+ diffDeleted: diff.deleted
98382
98848
  };
98383
98849
  } catch {
98384
98850
  return {
98385
98851
  dirty: false,
98386
98852
  ahead: 0,
98387
- behind: 0
98853
+ behind: 0,
98854
+ diffAdded: 0,
98855
+ diffDeleted: 0
98856
+ };
98857
+ }
98858
+ }
98859
+ function readDiffStats(workDir) {
98860
+ try {
98861
+ const result = spawnSync("git", [
98862
+ "-C",
98863
+ workDir,
98864
+ "diff",
98865
+ "--numstat",
98866
+ "HEAD",
98867
+ "--"
98868
+ ], {
98869
+ encoding: "utf8",
98870
+ timeout: SPAWN_TIMEOUT_MS,
98871
+ maxBuffer: 4 * 1024 * 1024
98872
+ });
98873
+ if (result.status !== 0) return {
98874
+ added: 0,
98875
+ deleted: 0
98876
+ };
98877
+ let added = 0;
98878
+ let deleted = 0;
98879
+ for (const line of result.stdout.split("\n")) {
98880
+ if (!line) continue;
98881
+ const [addedText, deletedText] = line.split(" ");
98882
+ added += parseDiffNumstatCount(addedText);
98883
+ deleted += parseDiffNumstatCount(deletedText);
98884
+ }
98885
+ return {
98886
+ added,
98887
+ deleted
98888
+ };
98889
+ } catch {
98890
+ return {
98891
+ added: 0,
98892
+ deleted: 0
98388
98893
  };
98389
98894
  }
98390
98895
  }
98391
- function formatGitBadge(status) {
98896
+ function parseDiffNumstatCount(value) {
98897
+ if (value === void 0 || value === "-") return 0;
98898
+ const n = Number.parseInt(value, 10);
98899
+ return Number.isFinite(n) && n > 0 ? n : 0;
98900
+ }
98901
+ function readPullRequest(workDir) {
98902
+ try {
98903
+ const result = spawnSync("gh", [
98904
+ "pr",
98905
+ "view",
98906
+ "--json",
98907
+ "number,url"
98908
+ ], {
98909
+ cwd: workDir,
98910
+ encoding: "utf8",
98911
+ env: {
98912
+ ...process.env,
98913
+ GH_NO_UPDATE_NOTIFIER: "1",
98914
+ GH_PROMPT_DISABLED: "1"
98915
+ },
98916
+ timeout: PR_SPAWN_TIMEOUT_MS,
98917
+ maxBuffer: 256 * 1024
98918
+ });
98919
+ if (result.status !== 0) return null;
98920
+ return parsePullRequest(result.stdout);
98921
+ } catch {
98922
+ return null;
98923
+ }
98924
+ }
98925
+ function parsePullRequest(stdout) {
98926
+ try {
98927
+ const raw = JSON.parse(stdout);
98928
+ if (typeof raw !== "object" || raw === null) return null;
98929
+ const record = raw;
98930
+ const number = record["number"];
98931
+ const url = record["url"];
98932
+ if (typeof number !== "number" || !Number.isInteger(number) || number <= 0) return null;
98933
+ if (typeof url !== "string" || !isSafeHttpUrl(url)) return null;
98934
+ return {
98935
+ number,
98936
+ url
98937
+ };
98938
+ } catch {
98939
+ return null;
98940
+ }
98941
+ }
98942
+ function isSafeHttpUrl(value) {
98943
+ if (hasControlChars(value)) return false;
98944
+ try {
98945
+ const url = new URL(value);
98946
+ return url.protocol === "https:" || url.protocol === "http:";
98947
+ } catch {
98948
+ return false;
98949
+ }
98950
+ }
98951
+ function hasControlChars(value) {
98952
+ for (const char of value) {
98953
+ const code = char.codePointAt(0) ?? 0;
98954
+ if (code <= 31 || code === 127) return true;
98955
+ }
98956
+ return false;
98957
+ }
98958
+ function formatGitBadge(status, options = {}) {
98392
98959
  const parts = [];
98393
- if (status.dirty) parts.push("±");
98960
+ const diff = formatDiffStats(status);
98961
+ if (diff) parts.push(diff);
98394
98962
  let sync = "";
98395
98963
  if (status.ahead > 0) sync += `↑${status.ahead}`;
98396
98964
  if (status.behind > 0) sync += `↓${status.behind}`;
98397
98965
  if (sync) parts.push(sync);
98398
- if (parts.length === 0) return status.branch;
98399
- return `${status.branch} [${parts.join(" ")}]`;
98966
+ const base = parts.length === 0 ? status.branch : `${status.branch} [${parts.join(" ")}]`;
98967
+ if (status.pullRequest === null) return base;
98968
+ const prText = `[PR#${String(status.pullRequest.number)}]`;
98969
+ return `${base} ${options.linkPullRequest ? toTerminalHyperlink(prText, status.pullRequest.url) : prText}`;
98970
+ }
98971
+ function formatDiffStats(status) {
98972
+ const parts = [];
98973
+ if (status.diffAdded > 0) parts.push(`+${String(status.diffAdded)}`);
98974
+ if (status.diffDeleted > 0) parts.push(`-${String(status.diffDeleted)}`);
98975
+ if (parts.length > 0) return parts.join(" ");
98976
+ return status.dirty ? "±" : null;
98977
+ }
98978
+ function toTerminalHyperlink(text, url) {
98979
+ if (!isSafeHttpUrl(url)) return text;
98980
+ return `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
98400
98981
  }
98401
98982
  //#endregion
98402
98983
  //#region src/tui/components/chrome/footer.ts
@@ -98549,7 +99130,7 @@ var FooterComponent = class {
98549
99130
  const cwd = shortenCwd(state.workDir);
98550
99131
  if (cwd) left.push(chalk.hex(colors.status)(cwd));
98551
99132
  const git = this.gitCache.getStatus();
98552
- if (git !== null) left.push(chalk.hex(colors.status)(formatGitBadge(git)));
99133
+ if (git !== null) left.push(chalk.hex(colors.status)(formatGitBadge(git, { linkPullRequest: true })));
98553
99134
  const leftLine = left.join(" ");
98554
99135
  const leftWidth = visibleWidth(leftLine);
98555
99136
  const tipIndex = currentTipIndex();
@@ -99347,6 +99928,7 @@ function createTUIState(options) {
99347
99928
  streamingToolCallArguments: /* @__PURE__ */ new Map(),
99348
99929
  toastTimers: /* @__PURE__ */ new Map(),
99349
99930
  pendingExit: null,
99931
+ cancelInFlight: void 0,
99350
99932
  queuedMessages: [],
99351
99933
  queueIdCounter: 0,
99352
99934
  aborted: false,
@@ -99522,6 +100104,7 @@ function sendMessageInternal(state, addEntry, input, options) {
99522
100104
  state.streamingTranscriptEntry = void 0;
99523
100105
  state.thinkingDraft = "";
99524
100106
  disposeActiveThinkingComponent(state);
100107
+ disposePendingToolComponents(state);
99525
100108
  state.activeToolCalls.clear();
99526
100109
  state.streamingToolCallArguments.clear();
99527
100110
  state.livePane = {
@@ -99585,6 +100168,7 @@ function sendSkillActivation(state, addEntry, skillName, skillArgs, fullPrompt)
99585
100168
  state.streamingTranscriptEntry = void 0;
99586
100169
  state.thinkingDraft = "";
99587
100170
  disposeActiveThinkingComponent(state);
100171
+ disposePendingToolComponents(state);
99588
100172
  state.activeToolCalls.clear();
99589
100173
  state.streamingToolCallArguments.clear();
99590
100174
  state.livePane = {
@@ -100478,6 +101062,7 @@ function releaseSessionSideEffects(state) {
100478
101062
  state.streamingTranscriptEntry = void 0;
100479
101063
  state.thinkingDraft = "";
100480
101064
  disposeActiveThinkingComponent(state);
101065
+ disposePendingToolComponents(state);
100481
101066
  }
100482
101067
  //#endregion
100483
101068
  //#region src/tui/components/dialogs/compaction.ts
@@ -101059,7 +101644,10 @@ async function dispatchSlashCommand(input, state, buildCtx, addEntry) {
101059
101644
  appState: ctx.appState,
101060
101645
  setAppState: ctx.setAppState,
101061
101646
  showStatus: ctx.showStatus,
101062
- showNotice: ctx.showNotice
101647
+ showNotice: ctx.showNotice,
101648
+ showDeviceCodeBox: ctx.showDeviceCodeBox,
101649
+ showLiveSpinner: ctx.showLiveSpinner,
101650
+ registerCancellable: ctx.registerCancellable
101063
101651
  };
101064
101652
  let result;
101065
101653
  try {
@@ -101188,80 +101776,6 @@ function refreshQueueIfBusy(state, ctx) {
101188
101776
  state.ui.requestRender();
101189
101777
  }
101190
101778
  //#endregion
101191
- //#region src/tui/components/chrome/moon-loader.ts
101192
- const MOON_PHASES = [
101193
- "🌑",
101194
- "🌒",
101195
- "🌓",
101196
- "🌔",
101197
- "🌕",
101198
- "🌖",
101199
- "🌗",
101200
- "🌘"
101201
- ];
101202
- const MOON_INTERVAL = 120;
101203
- const BRAILLE_FRAMES = [
101204
- "⠋",
101205
- "⠙",
101206
- "⠹",
101207
- "⠸",
101208
- "⠼",
101209
- "⠴",
101210
- "⠦",
101211
- "⠧",
101212
- "⠇",
101213
- "⠏"
101214
- ];
101215
- const BRAILLE_INTERVAL = 80;
101216
- var MoonLoader = class extends Text {
101217
- currentFrame = 0;
101218
- intervalId = null;
101219
- ui;
101220
- frames;
101221
- interval;
101222
- colorFn;
101223
- label;
101224
- constructor(ui, style = "moon", colorFn, label = "") {
101225
- super("", 1, 0);
101226
- this.ui = ui;
101227
- this.frames = style === "moon" ? MOON_PHASES : BRAILLE_FRAMES;
101228
- this.interval = style === "moon" ? MOON_INTERVAL : BRAILLE_INTERVAL;
101229
- this.colorFn = colorFn;
101230
- this.label = label;
101231
- this.start();
101232
- }
101233
- render(width) {
101234
- return ["", ...super.render(width)];
101235
- }
101236
- start() {
101237
- this.updateDisplay();
101238
- this.intervalId = setInterval(() => {
101239
- this.currentFrame = (this.currentFrame + 1) % this.frames.length;
101240
- this.updateDisplay();
101241
- }, this.interval);
101242
- }
101243
- stop() {
101244
- if (this.intervalId) {
101245
- clearInterval(this.intervalId);
101246
- this.intervalId = null;
101247
- }
101248
- }
101249
- setLabel(label) {
101250
- this.label = label;
101251
- this.updateDisplay();
101252
- }
101253
- setColorFn(colorFn) {
101254
- this.colorFn = colorFn;
101255
- this.updateDisplay();
101256
- }
101257
- updateDisplay() {
101258
- const frame = this.frames[this.currentFrame];
101259
- const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
101260
- this.setText(this.label ? `${coloredFrame} ${this.label}` : coloredFrame);
101261
- this.ui.requestRender();
101262
- }
101263
- };
101264
- //#endregion
101265
101779
  //#region src/tui/components/dialogs/approval-panel.ts
101266
101780
  /**
101267
101781
  * ApprovalPanel — pi-tui version of the approval request UI.
@@ -102380,6 +102894,7 @@ var UsagePanelComponent = class {
102380
102894
  //#region src/tui/handlers/turn-lifecycle.ts
102381
102895
  function handleTurnBegin(ectx, _data) {
102382
102896
  ectx.state.streamingToolCallArguments.clear();
102897
+ disposePendingToolComponents(ectx.state);
102383
102898
  ectx.state.currentStep = 0;
102384
102899
  closePendingAgentGroup(ectx.state);
102385
102900
  closePendingReadGroup(ectx.state);
@@ -102401,12 +102916,14 @@ function handleTurnEnd(ectx, _data, sendQueued) {
102401
102916
  const todos = ectx.state.todoPanel.getTodos();
102402
102917
  if (todos.length > 0 && todos.every((t) => t.status === "done")) ectx.setTodoList([]);
102403
102918
  ectx.state.streamingToolCallArguments.clear();
102919
+ disposePendingToolComponents(ectx.state);
102404
102920
  closePendingAgentGroup(ectx.state);
102405
102921
  closePendingReadGroup(ectx.state);
102406
102922
  finalizeTurn(ectx, sendQueued);
102407
102923
  }
102408
102924
  function handleStepBegin(ectx, data) {
102409
102925
  ectx.state.currentStep = data.step;
102926
+ disposePendingToolComponents(ectx.state);
102410
102927
  closePendingAgentGroup(ectx.state);
102411
102928
  closePendingReadGroup(ectx.state);
102412
102929
  flushTurnBuffers(ectx, "waiting");
@@ -102424,6 +102941,7 @@ function handleStepBegin(ectx, data) {
102424
102941
  function handleStepInterrupted(ectx, data) {
102425
102942
  closePendingAgentGroup(ectx.state);
102426
102943
  closePendingReadGroup(ectx.state);
102944
+ disposePendingToolComponents(ectx.state);
102427
102945
  flushTurnBuffers(ectx, "idle");
102428
102946
  const reason = data.reason;
102429
102947
  if (reason === "error") return;
@@ -102541,15 +103059,18 @@ function handleToolCallDelta(ectx, data) {
102541
103059
  const existing = ectx.state.streamingToolCallArguments.get(id);
102542
103060
  const argumentsText = `${existing?.argumentsText ?? ""}${data.arguments_part ?? ""}`;
102543
103061
  const name = data.name ?? existing?.name ?? ectx.state.activeToolCalls.get(id)?.name ?? "Tool";
103062
+ const startedAtMs = existing?.startedAtMs ?? Date.now();
102544
103063
  ectx.state.streamingToolCallArguments.set(id, {
102545
103064
  name,
102546
- argumentsText
103065
+ argumentsText,
103066
+ startedAtMs
102547
103067
  });
102548
103068
  const toolCall = {
102549
103069
  id,
102550
103070
  name,
102551
103071
  args: parseStreamingArgs(argumentsText),
102552
103072
  streamingArguments: argumentsText,
103073
+ streamingStartedAtMs: startedAtMs,
102553
103074
  step: ectx.state.currentStep,
102554
103075
  turnId: ectx.state.currentTurnId
102555
103076
  };
@@ -102603,6 +103124,18 @@ function isContextUsage(value) {
102603
103124
  const rec = value;
102604
103125
  return typeof rec["used"] === "number" && typeof rec["total"] === "number" && typeof rec["percent"] === "number";
102605
103126
  }
103127
+ function sessionErrorTitle(errorType) {
103128
+ switch (errorType) {
103129
+ case "auth_error": return "Authentication error";
103130
+ case "api_error": return "API error";
103131
+ case "rate_limit": return "Rate limit";
103132
+ case "context_overflow": return "Context overflow";
103133
+ case "tool_error": return "Tool error";
103134
+ case "internal": return "Internal error";
103135
+ case void 0: return "Error";
103136
+ default: return "Error";
103137
+ }
103138
+ }
102606
103139
  function handleStatusUpdate(ectx, data) {
102607
103140
  const refined = data;
102608
103141
  const patch = {};
@@ -102630,9 +103163,10 @@ function handleSessionMetaChanged(ectx, data) {
102630
103163
  function handleSessionError(ectx, data) {
102631
103164
  closePendingAgentGroup(ectx.state);
102632
103165
  closePendingReadGroup(ectx.state);
103166
+ disposePendingToolComponents(ectx.state);
102633
103167
  flushTurnBuffers(ectx, "idle");
102634
- const detail = data.error_type !== void 0 ? ` (${data.error_type})` : "";
102635
- ectx.addTranscriptEntry(makeEntry(ectx, "status", `Error${detail}: ${data.error}`, "plain", { color: ectx.colors.error }));
103168
+ const title = sessionErrorTitle(data.error_type);
103169
+ ectx.addTranscriptEntry(makeEntry(ectx, "status", `${title}: ${data.error}`, "plain", { color: ectx.colors.error }));
102636
103170
  }
102637
103171
  //#endregion
102638
103172
  //#region src/tui/handlers/compaction.ts
@@ -103913,6 +104447,13 @@ function setupEditor(state, cb) {
103913
104447
  cb.onEditorChange(text);
103914
104448
  };
103915
104449
  editor.onCtrlC = () => {
104450
+ if (state.cancelInFlight !== void 0) {
104451
+ const cancel = state.cancelInFlight;
104452
+ state.cancelInFlight = void 0;
104453
+ clearPendingExit(state);
104454
+ cancel();
104455
+ return;
104456
+ }
103916
104457
  if (state.appState.isStreaming) {
103917
104458
  clearPendingExit(state);
103918
104459
  cb.onCancelStream();
@@ -104860,6 +105401,16 @@ var KimiTUI = class {
104860
105401
  showNotice: (title, detail) => {
104861
105402
  emitNotice(this.state, title, detail);
104862
105403
  },
105404
+ showDeviceCodeBox: (options) => {
105405
+ emitDeviceCodeBox(this.state, options);
105406
+ },
105407
+ showLiveSpinner: (label) => emitLiveSpinner(this.state, label),
105408
+ registerCancellable: (abort) => {
105409
+ this.state.cancelInFlight = abort;
105410
+ return () => {
105411
+ if (this.state.cancelInFlight === abort) this.state.cancelInFlight = void 0;
105412
+ };
105413
+ },
104863
105414
  stop: () => {
104864
105415
  this.stop();
104865
105416
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kfc-code-cli",
3
- "version": "0.0.1-alpha.21",
3
+ "version": "0.0.1-alpha.22",
4
4
  "description": "KFC crazy ",
5
5
  "license": "MIT",
6
6
  "bin": {