@oh-my-pi/pi-coding-agent 16.1.14 → 16.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/dist/cli.js +3705 -4015
- package/dist/types/advisor/runtime.d.ts +3 -0
- package/dist/types/cli/args.d.ts +2 -5
- package/dist/types/cli/flag-tables.d.ts +2 -2
- package/dist/types/cli/session-picker.d.ts +4 -2
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +32 -1
- package/dist/types/eval/agent-bridge.d.ts +19 -0
- package/dist/types/eval/js/shared/helpers.d.ts +1 -13
- package/dist/types/eval/js/shared/types.d.ts +1 -1
- package/dist/types/eval/js/worker-protocol.d.ts +1 -1
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/export/share.d.ts +8 -1
- package/dist/types/internal-urls/local-protocol.d.ts +18 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/transports/stdio.d.ts +12 -1
- package/dist/types/modes/components/plugin-settings.d.ts +5 -0
- package/dist/types/modes/components/session-selector.d.ts +25 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +4 -3
- package/dist/types/modes/components/status-line/types.d.ts +1 -0
- package/dist/types/secrets/obfuscator.d.ts +3 -3
- package/dist/types/task/isolation-runner.d.ts +128 -0
- package/dist/types/task/worktree.d.ts +14 -1
- package/dist/types/thinking.d.ts +15 -0
- package/dist/types/tools/eval-render.d.ts +3 -0
- package/dist/types/tools/eval.d.ts +11 -17
- package/dist/types/tools/todo.d.ts +26 -28
- package/dist/types/tui/output-block.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/shell-snapshot.d.ts +10 -0
- package/dist/types/web/search/providers/exa.d.ts +2 -0
- package/dist/types/web/search/providers/perplexity.d.ts +17 -3
- package/package.json +12 -12
- package/scripts/build-binary.ts +18 -4
- package/src/advisor/__tests__/advisor.test.ts +114 -0
- package/src/advisor/runtime.ts +129 -1
- package/src/cli/args.ts +4 -5
- package/src/cli/flag-tables.ts +3 -3
- package/src/cli/gallery-fixtures/interaction.ts +6 -9
- package/src/cli/gallery-fixtures/shell.ts +15 -23
- package/src/cli/session-picker.ts +17 -3
- package/src/commands/launch.ts +3 -3
- package/src/config/model-registry.ts +12 -4
- package/src/config/settings-schema.ts +37 -1
- package/src/edit/renderer.ts +34 -12
- package/src/eval/__tests__/agent-bridge.test.ts +462 -3
- package/src/eval/__tests__/helpers-local-roots.test.ts +2 -5
- package/src/eval/__tests__/julia-prelude.test.ts +1 -30
- package/src/eval/__tests__/prelude-agent.test.ts +42 -8
- package/src/eval/agent-bridge.ts +301 -71
- package/src/eval/jl/prelude.jl +32 -227
- package/src/eval/jl/runner.jl +38 -12
- package/src/eval/js/shared/helpers.ts +1 -114
- package/src/eval/js/shared/prelude.txt +13 -27
- package/src/eval/js/shared/runtime.ts +0 -6
- package/src/eval/js/shared/types.ts +1 -1
- package/src/eval/js/worker-protocol.ts +1 -1
- package/src/eval/py/__tests__/prelude.test.ts +13 -0
- package/src/eval/py/executor.ts +1 -1
- package/src/eval/py/prelude.py +47 -105
- package/src/eval/py/runner.py +0 -6
- package/src/eval/rb/prelude.rb +21 -189
- package/src/eval/rb/runner.rb +116 -9
- package/src/exec/bash-executor.ts +44 -0
- package/src/export/html/tool-views.generated.js +29 -29
- package/src/export/share.ts +51 -28
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/local-protocol.ts +100 -53
- package/src/main.ts +15 -4
- package/src/mcp/transports/stdio.ts +20 -4
- package/src/modes/acp/acp-event-mapper.ts +7 -2
- package/src/modes/components/custom-editor.test.ts +22 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/footer.ts +4 -3
- package/src/modes/components/plugin-settings.ts +7 -1
- package/src/modes/components/session-selector.ts +143 -29
- package/src/modes/components/status-line/component.ts +5 -1
- package/src/modes/components/status-line/context-thresholds.ts +11 -3
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/controllers/command-controller.ts +6 -0
- package/src/modes/rpc/rpc-mode.ts +6 -0
- package/src/modes/utils/copy-targets.ts +7 -2
- package/src/prompts/system/system-prompt.md +6 -6
- package/src/prompts/system/workflow-notice.md +3 -3
- package/src/prompts/tools/bash.md +18 -2
- package/src/prompts/tools/eval.md +19 -19
- package/src/prompts/tools/search.md +1 -0
- package/src/prompts/tools/todo.md +1 -1
- package/src/secrets/obfuscator.ts +3 -9
- package/src/session/agent-session.ts +264 -52
- package/src/slash-commands/builtin-registry.ts +2 -1
- package/src/task/index.ts +61 -207
- package/src/task/isolation-runner.ts +354 -0
- package/src/task/worktree.ts +46 -9
- package/src/thinking.ts +20 -0
- package/src/tools/ask.ts +44 -38
- package/src/tools/bash.ts +9 -2
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/eval-render.ts +34 -27
- package/src/tools/eval.ts +100 -103
- package/src/tools/index.ts +8 -1
- package/src/tools/read.ts +136 -60
- package/src/tools/todo.ts +60 -64
- package/src/tui/code-cell.ts +1 -1
- package/src/tui/output-block.ts +11 -0
- package/src/utils/image-resize.ts +30 -0
- package/src/utils/shell-snapshot.ts +63 -1
- package/src/web/search/providers/exa.ts +85 -1
- package/src/web/search/providers/perplexity.ts +18 -6
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Input,
|
|
6
6
|
matchesKey,
|
|
7
7
|
padding,
|
|
8
|
+
parseSgrMouse,
|
|
8
9
|
replaceTabs,
|
|
9
10
|
ScrollView,
|
|
10
11
|
Spacer,
|
|
@@ -161,6 +162,12 @@ export function mergeSessionRanking(
|
|
|
161
162
|
class SessionList implements Component {
|
|
162
163
|
#filteredSessions: SessionInfo[] = [];
|
|
163
164
|
#selectedIndex: number = 0;
|
|
165
|
+
// Maps a 0-based line within this list's own render to a filtered-session
|
|
166
|
+
// index, or undefined for chrome rows (search line, blanks, scrollbar gap).
|
|
167
|
+
// Rebuilt every render so the picker's mouse hit-testing tracks the live
|
|
168
|
+
// scroll window. Only consulted while the picker holds the alternate screen
|
|
169
|
+
// (where the overlay enables mouse tracking and paints from screen row 0).
|
|
170
|
+
#hitRows: (number | undefined)[] = [];
|
|
164
171
|
readonly #searchInput: Input;
|
|
165
172
|
onSelect?: (session: SessionInfo) => void;
|
|
166
173
|
onCancel?: () => void;
|
|
@@ -257,12 +264,32 @@ class SessionList implements Component {
|
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
266
|
|
|
267
|
+
/** Resolve a list-local rendered-line index to a filtered-session index. */
|
|
268
|
+
hitTestSession(line: number): number | undefined {
|
|
269
|
+
return this.#hitRows[line];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Wheel notch: move the selection one step (clamped, no wrap). */
|
|
273
|
+
handleWheel(delta: -1 | 1): void {
|
|
274
|
+
if (this.#filteredSessions.length === 0) return;
|
|
275
|
+
this.#selectedIndex = Math.max(0, Math.min(this.#filteredSessions.length - 1, this.#selectedIndex + delta));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Mouse click: select the session under the pointer and resume it. */
|
|
279
|
+
selectAndConfirm(index: number): void {
|
|
280
|
+
const session = this.#filteredSessions[index];
|
|
281
|
+
if (!session) return;
|
|
282
|
+
this.#selectedIndex = index;
|
|
283
|
+
this.onSelect?.(session);
|
|
284
|
+
}
|
|
285
|
+
|
|
260
286
|
invalidate(): void {
|
|
261
287
|
// No cached state to invalidate currently
|
|
262
288
|
}
|
|
263
289
|
|
|
264
290
|
render(width: number): readonly string[] {
|
|
265
291
|
const lines: string[] = [];
|
|
292
|
+
this.#hitRows = [];
|
|
266
293
|
|
|
267
294
|
// Render search input
|
|
268
295
|
lines.push(...this.#searchInput.render(width));
|
|
@@ -311,9 +338,11 @@ class SessionList implements Component {
|
|
|
311
338
|
// Each session block is built into sessionLines, then wrapped by ScrollView
|
|
312
339
|
// so the right-edge scrollbar is proportional at the physical-line level.
|
|
313
340
|
const sessionLines: string[] = [];
|
|
341
|
+
const sessionRowIndex: number[] = [];
|
|
314
342
|
const overflow = this.#filteredSessions.length > maxVisible;
|
|
315
343
|
const rowWidth = Math.max(0, width - (overflow ? 1 : 0));
|
|
316
344
|
for (let i = startIndex; i < endIndex; i++) {
|
|
345
|
+
const blockStart = sessionLines.length;
|
|
317
346
|
const session = this.#filteredSessions[i];
|
|
318
347
|
const isSelected = i === this.#selectedIndex;
|
|
319
348
|
|
|
@@ -363,6 +392,7 @@ class SessionList implements Component {
|
|
|
363
392
|
|
|
364
393
|
sessionLines.push(metadataLine);
|
|
365
394
|
sessionLines.push(""); // Blank line between sessions
|
|
395
|
+
for (let k = blockStart; k < sessionLines.length; k++) sessionRowIndex[k] = i;
|
|
366
396
|
}
|
|
367
397
|
|
|
368
398
|
// Wrap the rendered window in a ScrollView for a proportional right-edge bar.
|
|
@@ -375,16 +405,10 @@ class SessionList implements Component {
|
|
|
375
405
|
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
376
406
|
});
|
|
377
407
|
sv.setScrollOffset(Math.round(startIndex * linesPerItem));
|
|
378
|
-
lines.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
lines.push(
|
|
382
|
-
lines.push(
|
|
383
|
-
theme.fg(
|
|
384
|
-
"muted",
|
|
385
|
-
` [Del delete · Enter select · Tab ${this.#showCwd ? "current folder" : "all projects"} · Esc cancel]`,
|
|
386
|
-
),
|
|
387
|
-
);
|
|
408
|
+
const sessionRegionStart = lines.length;
|
|
409
|
+
const svLines = sv.render(width);
|
|
410
|
+
for (let k = 0; k < svLines.length; k++) this.#hitRows[sessionRegionStart + k] = sessionRowIndex[k];
|
|
411
|
+
lines.push(...svLines);
|
|
388
412
|
|
|
389
413
|
return lines;
|
|
390
414
|
}
|
|
@@ -462,6 +486,13 @@ export interface SessionSelectorOptions {
|
|
|
462
486
|
* Omitted only in tests; defaults to a conservative 24 rows.
|
|
463
487
|
*/
|
|
464
488
|
getTerminalRows?: () => number;
|
|
489
|
+
/**
|
|
490
|
+
* Fill the whole viewport and pin the footer (hint + bottom border) to the
|
|
491
|
+
* last rows, so the footer stops drifting as the list window changes height.
|
|
492
|
+
* Set by the standalone `--resume` picker (fullscreen alternate screen); the
|
|
493
|
+
* in-editor selector leaves it off and renders compactly.
|
|
494
|
+
*/
|
|
495
|
+
fillHeight?: boolean;
|
|
465
496
|
}
|
|
466
497
|
|
|
467
498
|
/**
|
|
@@ -470,6 +501,13 @@ export interface SessionSelectorOptions {
|
|
|
470
501
|
export class SessionSelectorComponent extends Container {
|
|
471
502
|
#sessionList: SessionList;
|
|
472
503
|
#confirmationDialog: HookSelectorComponent | null = null;
|
|
504
|
+
// Hosts whichever of `#sessionList` / `#confirmationDialog` is live this
|
|
505
|
+
// frame. The delete dialog REPLACES the list in this slot rather than being
|
|
506
|
+
// appended below the picker chrome, so the picker is always
|
|
507
|
+
// `chrome + max(list, dialog) + chrome` and never overflows the viewport
|
|
508
|
+
// (issue #3283: an overflowing dialog frame committed the header into
|
|
509
|
+
// scrollback, stranding it above the viewport once the dialog closed).
|
|
510
|
+
#contentSlot: Container;
|
|
473
511
|
#messageContainer: Container;
|
|
474
512
|
#headerText: Text;
|
|
475
513
|
#onDelete?: (session: SessionInfo) => Promise<boolean>;
|
|
@@ -479,6 +517,18 @@ export class SessionSelectorComponent extends Container {
|
|
|
479
517
|
#globalSessions: SessionInfo[] | null = null;
|
|
480
518
|
#scope: "folder" | "all" = "folder";
|
|
481
519
|
#toggling = false;
|
|
520
|
+
// 0-based line where the session list begins within this component's own
|
|
521
|
+
// render, captured each frame. The fullscreen picker overlay paints from
|
|
522
|
+
// screen row 0, so a mouse row maps to `row - #listLineOffset` inside the
|
|
523
|
+
// list. Only meaningful while the picker holds the alternate screen.
|
|
524
|
+
#listLineOffset = 0;
|
|
525
|
+
// 0-based line where the pinned footer begins; clicks at or below it never
|
|
526
|
+
// hit-test the list, so a footer click on a cramped (trimmed) frame can't
|
|
527
|
+
// resume a session scrolled off-screen.
|
|
528
|
+
#footerStart = 0;
|
|
529
|
+
readonly #getTerminalRows: () => number;
|
|
530
|
+
readonly #fillHeight: boolean;
|
|
531
|
+
readonly #bottomBorder = new DynamicBorder();
|
|
482
532
|
|
|
483
533
|
constructor(
|
|
484
534
|
sessions: SessionInfo[],
|
|
@@ -494,6 +544,8 @@ export class SessionSelectorComponent extends Container {
|
|
|
494
544
|
this.#loadAllSessions = options.loadAllSessions;
|
|
495
545
|
this.#folderSessions = sessions;
|
|
496
546
|
this.#globalSessions = options.allSessions ?? null;
|
|
547
|
+
this.#getTerminalRows = options.getTerminalRows ?? (() => 24);
|
|
548
|
+
this.#fillHeight = options.fillHeight ?? false;
|
|
497
549
|
// Add header
|
|
498
550
|
this.addChild(new Spacer(1));
|
|
499
551
|
this.#headerText = new Text(this.#headerLabel(), 1, 0);
|
|
@@ -517,11 +569,9 @@ export class SessionSelectorComponent extends Container {
|
|
|
517
569
|
void this.#toggleScope();
|
|
518
570
|
};
|
|
519
571
|
}
|
|
520
|
-
this
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
this.addChild(new Spacer(1));
|
|
524
|
-
this.addChild(new DynamicBorder());
|
|
572
|
+
this.#contentSlot = new Container();
|
|
573
|
+
this.#contentSlot.addChild(this.#sessionList);
|
|
574
|
+
this.addChild(this.#contentSlot);
|
|
525
575
|
}
|
|
526
576
|
|
|
527
577
|
#headerLabel(): string {
|
|
@@ -582,6 +632,15 @@ export class SessionSelectorComponent extends Container {
|
|
|
582
632
|
|
|
583
633
|
#showDeleteConfirmation(session: SessionInfo): void {
|
|
584
634
|
const displayName = session.title || session.firstMessage.slice(0, 40) || session.id;
|
|
635
|
+
const closeDialog = () => {
|
|
636
|
+
this.#confirmationDialog = null;
|
|
637
|
+
// Restore the SessionList into the content slot so the picker is back
|
|
638
|
+
// to its normal layout on the very next render — the same frame the
|
|
639
|
+
// dialog disappears.
|
|
640
|
+
this.#contentSlot.clear();
|
|
641
|
+
this.#contentSlot.addChild(this.#sessionList);
|
|
642
|
+
this.#onRequestRender?.();
|
|
643
|
+
};
|
|
585
644
|
this.#confirmationDialog = new HookSelectorComponent(
|
|
586
645
|
`Delete session?\n${displayName}`,
|
|
587
646
|
["Yes", "No"],
|
|
@@ -597,25 +656,61 @@ export class SessionSelectorComponent extends Container {
|
|
|
597
656
|
this.#showError(err instanceof Error ? err.message : String(err));
|
|
598
657
|
}
|
|
599
658
|
}
|
|
600
|
-
|
|
601
|
-
this.removeChild(this.#confirmationDialog!);
|
|
602
|
-
this.#confirmationDialog = null;
|
|
603
|
-
// Request rerender
|
|
604
|
-
this.#onRequestRender?.();
|
|
605
|
-
},
|
|
606
|
-
() => {
|
|
607
|
-
// Cancel - close confirmation dialog
|
|
608
|
-
this.removeChild(this.#confirmationDialog!);
|
|
609
|
-
this.#confirmationDialog = null;
|
|
610
|
-
// Request rerender
|
|
611
|
-
this.#onRequestRender?.();
|
|
659
|
+
closeDialog();
|
|
612
660
|
},
|
|
661
|
+
closeDialog,
|
|
613
662
|
);
|
|
614
|
-
//
|
|
615
|
-
|
|
663
|
+
// Swap the SessionList out of the content slot and mount the dialog in its
|
|
664
|
+
// place: the dialog competes only with the SessionList's rendered budget,
|
|
665
|
+
// never the SessionList AND the picker chrome, so the picker frame stays
|
|
666
|
+
// inside the terminal viewport and the TUI never commits the header into
|
|
667
|
+
// scrollback (issue #3283).
|
|
668
|
+
this.#contentSlot.clear();
|
|
669
|
+
this.#contentSlot.addChild(this.#confirmationDialog);
|
|
670
|
+
this.#onRequestRender?.();
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Concatenate the children's renders (like {@link Container}) while recording
|
|
675
|
+
* the line where the session list begins, so the fullscreen picker can hit-
|
|
676
|
+
* test mouse rows against the live list window. SessionList rebuilds its lines
|
|
677
|
+
* every frame, so Container's reference-memoization never applied here.
|
|
678
|
+
*
|
|
679
|
+
* In fill-height mode the body is padded (or, on a cramped terminal, trimmed)
|
|
680
|
+
* to leave exactly enough room for the footer at the screen bottom, so the
|
|
681
|
+
* footer is always visible and never drifts as the list window resizes. The
|
|
682
|
+
* in-editor selector just appends the footer directly.
|
|
683
|
+
*/
|
|
684
|
+
render(width: number): readonly string[] {
|
|
685
|
+
const lines: string[] = [];
|
|
686
|
+
for (const child of this.children) {
|
|
687
|
+
const childLines = child.render(width);
|
|
688
|
+
if (child === this.#contentSlot) this.#listLineOffset = lines.length;
|
|
689
|
+
for (const line of childLines) lines.push(line);
|
|
690
|
+
}
|
|
691
|
+
const footer = this.#footerLines(width);
|
|
692
|
+
if (this.#fillHeight) {
|
|
693
|
+
const target = Math.max(0, this.#getTerminalRows() - footer.length);
|
|
694
|
+
if (lines.length > target) lines.length = target;
|
|
695
|
+
else for (let i = lines.length; i < target; i++) lines.push("");
|
|
696
|
+
}
|
|
697
|
+
this.#footerStart = lines.length;
|
|
698
|
+
for (const line of footer) lines.push(line);
|
|
699
|
+
return lines;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** Blank · keybinding hint · bottom border. Rendered by {@link render}. */
|
|
703
|
+
#footerLines(width: number): string[] {
|
|
704
|
+
const scopeHint = this.#scope === "all" ? "current folder" : "all projects";
|
|
705
|
+
const hint = theme.fg("muted", ` [Del delete · Enter select · Tab ${scopeHint} · Esc cancel]`);
|
|
706
|
+
return ["", hint, "", ...this.#bottomBorder.render(width)];
|
|
616
707
|
}
|
|
617
708
|
|
|
618
709
|
handleInput(keyData: string): void {
|
|
710
|
+
if (keyData.startsWith("\x1b[<")) {
|
|
711
|
+
this.#handleMouse(keyData);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
619
714
|
if (this.#confirmationDialog) {
|
|
620
715
|
this.#confirmationDialog.handleInput(keyData);
|
|
621
716
|
} else {
|
|
@@ -623,6 +718,25 @@ export class SessionSelectorComponent extends Container {
|
|
|
623
718
|
}
|
|
624
719
|
}
|
|
625
720
|
|
|
721
|
+
/**
|
|
722
|
+
* SGR mouse reports, delivered only while the picker holds the alternate
|
|
723
|
+
* screen (the fullscreen overlay enables tracking and paints from screen row
|
|
724
|
+
* 0). Wheel scrolls the list; a left click resumes the session under the
|
|
725
|
+
* pointer. Mouse is inert while the delete-confirmation dialog is open.
|
|
726
|
+
*/
|
|
727
|
+
#handleMouse(data: string): void {
|
|
728
|
+
if (this.#confirmationDialog) return;
|
|
729
|
+
const event = parseSgrMouse(data);
|
|
730
|
+
if (!event) return;
|
|
731
|
+
if (event.wheel !== null) {
|
|
732
|
+
this.#sessionList.handleWheel(event.wheel);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (!event.leftClick || event.row >= this.#footerStart) return;
|
|
736
|
+
const index = this.#sessionList.hitTestSession(event.row - this.#listLineOffset);
|
|
737
|
+
if (index !== undefined) this.#sessionList.selectAndConfirm(index);
|
|
738
|
+
}
|
|
739
|
+
|
|
626
740
|
getSessionList(): SessionList {
|
|
627
741
|
return this.#sessionList;
|
|
628
742
|
}
|
|
@@ -727,10 +727,12 @@ export class StatusLineComponent implements Component {
|
|
|
727
727
|
|
|
728
728
|
let contextWindow = state.model?.contextWindow ?? this.session.model?.contextWindow ?? 0;
|
|
729
729
|
let contextPercent: number | null = 0;
|
|
730
|
+
let contextTokens = 0;
|
|
730
731
|
if (includeContext) {
|
|
731
732
|
const breakdown = this.getCachedContextBreakdown();
|
|
733
|
+
contextTokens = breakdown.usedTokens;
|
|
732
734
|
contextWindow = breakdown.contextWindow || contextWindow;
|
|
733
|
-
contextPercent = contextWindow > 0 ? (breakdown.usedTokens / contextWindow) * 100 :
|
|
735
|
+
contextPercent = contextWindow > 0 ? (breakdown.usedTokens / contextWindow) * 100 : null;
|
|
734
736
|
}
|
|
735
737
|
|
|
736
738
|
// Collab guest: context comes from the host's state frames — the local
|
|
@@ -738,6 +740,7 @@ export class StatusLineComponent implements Component {
|
|
|
738
740
|
const collabState = this.#collabStatus?.stateOverride;
|
|
739
741
|
if (collabState?.contextUsage) {
|
|
740
742
|
contextWindow = collabState.contextUsage.contextWindow || contextWindow;
|
|
743
|
+
contextTokens = collabState.contextUsage.tokens ?? contextTokens;
|
|
741
744
|
contextPercent = collabState.contextUsage.percent ?? contextPercent;
|
|
742
745
|
}
|
|
743
746
|
|
|
@@ -756,6 +759,7 @@ export class StatusLineComponent implements Component {
|
|
|
756
759
|
collab: this.#collabStatus,
|
|
757
760
|
usageStats,
|
|
758
761
|
contextPercent,
|
|
762
|
+
contextTokens,
|
|
759
763
|
contextWindow,
|
|
760
764
|
autoCompactEnabled: this.#autoCompactEnabled,
|
|
761
765
|
subagentCount: this.#subagentCount,
|
|
@@ -56,10 +56,18 @@ export function getContextUsageLevel(contextPercent: number, contextWindow: numb
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Format context usage as `<percent>%/<window>`
|
|
60
|
-
*
|
|
59
|
+
* Format context usage as `<percent>%/<window>` when the model window is known.
|
|
60
|
+
* Unknown windows render as `<tokens>/?`, because `0.0%/0` suggests a real
|
|
61
|
+
* empty context instead of missing provider metadata.
|
|
61
62
|
*/
|
|
62
|
-
export function formatContextUsage(
|
|
63
|
+
export function formatContextUsage(
|
|
64
|
+
contextPercent: number | null | undefined,
|
|
65
|
+
contextWindow: number,
|
|
66
|
+
usedTokens?: number,
|
|
67
|
+
): string {
|
|
68
|
+
if (!Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
69
|
+
return `${formatNumber(usedTokens ?? 0)}/?`;
|
|
70
|
+
}
|
|
63
71
|
const pct = contextPercent === null || contextPercent === undefined ? "?" : `${contextPercent.toFixed(1)}%`;
|
|
64
72
|
return `${pct}/${formatNumber(contextWindow)}`;
|
|
65
73
|
}
|
|
@@ -375,7 +375,7 @@ const contextPctSegment: StatusLineSegment = {
|
|
|
375
375
|
const window = ctx.contextWindow;
|
|
376
376
|
|
|
377
377
|
const autoIcon = ctx.autoCompactEnabled && theme.icon.auto ? ` ${theme.icon.auto}` : "";
|
|
378
|
-
const text = `${formatContextUsage(pct, window)}${autoIcon}`;
|
|
378
|
+
const text = `${formatContextUsage(pct, window, ctx.contextTokens)}${autoIcon}`;
|
|
379
379
|
|
|
380
380
|
const color = getContextUsageThemeColor(getContextUsageLevel(pct ?? 0, window));
|
|
381
381
|
const content = withIcon(theme.icon.context, theme.fg(color, text));
|
|
@@ -73,6 +73,7 @@ export interface SegmentContext {
|
|
|
73
73
|
};
|
|
74
74
|
/** Context usage percent, or null when unknown (e.g. right after compaction). */
|
|
75
75
|
contextPercent: number | null;
|
|
76
|
+
contextTokens: number;
|
|
76
77
|
contextWindow: number;
|
|
77
78
|
autoCompactEnabled: boolean;
|
|
78
79
|
subagentCount: number;
|
|
@@ -217,6 +217,7 @@ export class CommandController {
|
|
|
217
217
|
try {
|
|
218
218
|
const result = await shareSession(this.ctx.session.sessionManager, {
|
|
219
219
|
serverUrl: this.ctx.settings.get("share.serverUrl"),
|
|
220
|
+
store: this.ctx.settings.get("share.store"),
|
|
220
221
|
state: this.ctx.session.state,
|
|
221
222
|
obfuscator: this.ctx.settings.get("share.redactSecrets") ? this.ctx.session.obfuscator : undefined,
|
|
222
223
|
});
|
|
@@ -1171,6 +1172,11 @@ export class CommandController {
|
|
|
1171
1172
|
}
|
|
1172
1173
|
|
|
1173
1174
|
async handleHandoffCommand(customInstructions?: string): Promise<void> {
|
|
1175
|
+
if (this.ctx.session.isStreaming) {
|
|
1176
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before handing off.");
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1174
1180
|
const entries = this.ctx.sessionManager.getEntries();
|
|
1175
1181
|
const messageCount = entries.filter(e => e.type === "message").length;
|
|
1176
1182
|
|
|
@@ -1008,6 +1008,12 @@ export async function runRpcMode(
|
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
1010
|
case "handoff": {
|
|
1011
|
+
// Resetting the agent mid-stream lets the live turn keep emitting into a
|
|
1012
|
+
// session that handoff has already torn down. Refuse while a prompt is in
|
|
1013
|
+
// flight (mirrors the TUI /handoff guard).
|
|
1014
|
+
if (session.isStreaming) {
|
|
1015
|
+
return error(id, "handoff", "Cannot hand off while a response is in progress");
|
|
1016
|
+
}
|
|
1011
1017
|
const result = await session.handoff(command.customInstructions);
|
|
1012
1018
|
return success(id, "handoff", result ? { savedPath: result.savedPath } : null);
|
|
1013
1019
|
}
|
|
@@ -127,8 +127,13 @@ export function extractQuoteBlocks(text: string): QuoteBlock[] {
|
|
|
127
127
|
|
|
128
128
|
function extractEvalCode(args: unknown): { code: string; language: string } | undefined {
|
|
129
129
|
if (!args || typeof args !== "object") return undefined;
|
|
130
|
-
const
|
|
131
|
-
|
|
130
|
+
const argsObj = args as { cells?: unknown; code?: unknown };
|
|
131
|
+
const cells = Array.isArray(argsObj.cells)
|
|
132
|
+
? argsObj.cells
|
|
133
|
+
: typeof argsObj.code === "string"
|
|
134
|
+
? [argsObj]
|
|
135
|
+
: undefined;
|
|
136
|
+
if (!cells) return undefined;
|
|
132
137
|
|
|
133
138
|
const codeBlocks: string[] = [];
|
|
134
139
|
let language = "python";
|
|
@@ -102,16 +102,16 @@ Use tools whenever they improve correctness, completeness, or grounding.
|
|
|
102
102
|
{{#has tools "inspect_image"}}- Image tasks: prefer `{{toolRefs.inspect_image}}` over `{{toolRefs.read}}` to spare session context.{{/has}}
|
|
103
103
|
|
|
104
104
|
# Specialized Tools
|
|
105
|
-
|
|
105
|
+
You MUST use the specialized tool over its shell equivalent:
|
|
106
106
|
{{#has tools "read"}}- File or directory reads → `{{toolRefs.read}}` (a directory path lists entries).{{/has}}
|
|
107
107
|
{{#has tools "edit"}}- Surgical edits → `{{toolRefs.edit}}`.{{/has}}
|
|
108
108
|
{{#has tools "write"}}- Create or overwrite → `{{toolRefs.write}}`.{{/has}}
|
|
109
109
|
{{#has tools "lsp"}}- Code intelligence → `{{toolRefs.lsp}}`.{{/has}}
|
|
110
|
-
{{#has tools "search"}}- Regex search → `{{toolRefs.search}}`.{{/has}}
|
|
111
|
-
{{#has tools "find"}}- Globbing → `{{toolRefs.find}}`.{{/has}}
|
|
112
|
-
{{#has tools "eval"}}-
|
|
113
|
-
{{#has tools "bash"}}-
|
|
114
|
-
- Litmus:
|
|
110
|
+
{{#has tools "search"}}- Regex search → `{{toolRefs.search}}`, not `grep`, `rg`, or `awk`.{{/has}}
|
|
111
|
+
{{#has tools "find"}}- Globbing → `{{toolRefs.find}}`, not `ls **/*.ext` or `fd`.{{/has}}
|
|
112
|
+
{{#has tools "eval"}}- Default for any compute: `{{toolRefs.eval}}` cells. Bash is the EXCEPTION — only single binary calls or short fact-computing pipelines (`wc -l`, `sort | uniq -c`, `diff`, checksums). The moment a command grows a loop, conditional, heredoc, `-e`/`-c` script, `$(…)` nesting, or >2 pipe stages, it's a program → `{{toolRefs.eval}}`. NEVER write multiline or inline-script bash.{{/has}}
|
|
113
|
+
{{#has tools "bash"}}- `{{toolRefs.bash}}`: real binaries and short fact pipelines only. Commands shadowing the specialized tools above are blocked.{{/has}}
|
|
114
|
+
{{#has tools "bash"}}- Litmus: one external-CLI call or short pipeline returning a count, frequency, set difference, or checksum → bash.{{#has tools "eval"}} Needs control flow, state, or fights shell quoting → `{{toolRefs.eval}}`.{{/has}} Merely moves, pages, or trims bytes a tool can fetch → use the tool.{{/has}}
|
|
115
115
|
|
|
116
116
|
{{#has tools "report_tool_issue"}}
|
|
117
117
|
<critical>
|
|
@@ -11,16 +11,16 @@ Worth it when the task benefits from decomposition + parallel coverage, or from
|
|
|
11
11
|
</when>
|
|
12
12
|
|
|
13
13
|
<helpers>
|
|
14
|
-
State persists across
|
|
14
|
+
State persists across eval calls, so scout in one call and fan out in the next. Every eval call has:
|
|
15
15
|
|
|
16
|
-
- `agent(prompt, *,
|
|
16
|
+
- `agent(prompt, *, agent="task", model=None, label=None, schema=None, isolated=None, apply=None, merge=None, handle=False)` — run ONE subagent; returns its final text, or the validated object when `schema` (a JSON Schema dict) is given. With `schema` the subagent is forced to emit structured output that is validated for you — branch on the object, not on parsed prose. `agent` picks a discovered agent ("explore", "reviewer", "oracle", …); `label` names the artifact. Shared background goes in a `local://` file referenced from each prompt, not a parameter. Subagents are told their final text IS the return value, so they hand back raw data. `agent()` blocks until the subagent finishes; eval-spawned agents nest at most 3 deep. Pass `isolated=True` to run the spawn in a copy-on-write worktree so parallel `agent()` calls can edit overlapping files safely — strict opt-in, mirrors the `task` tool, defaults off regardless of `task.isolation.mode`; `isolated=True` while the setting is `"none"` errors out instead of silently downgrading. With isolation, `apply=False` keeps changes in the worktree, and `merge=False` forces patch mode even when the setting is `"branch"`. Captured root patch path, branch name, nested repo patches, and apply summary reach the workflow through `handle=True` — combine it with `apply=False` (or `apply=False, schema=…`) and read `node["patch_path"]`, `node["branch_name"]`, `node["nested_patches"]`, `node["changes_applied"]`, `node["isolation_summary"]` (JS: same keys camelCased) to recover artifacts.
|
|
17
17
|
- `parallel(thunks)` — run zero-arg callables concurrently through a bounded pool, preserving input order; returns once all finish. The pool is bounded by the session's `task` concurrency — don't hand-tune it; fan out as wide as the work divides. A thunk that raises propagates — wrap risky work in `try/except` inside the thunk to keep partial results. In a loop, bind each closure's value with a default arg (`lambda d=d: …`) or every thunk captures the last one.
|
|
18
18
|
- `pipeline(items, *stages)` — map items through `stages` left-to-right. There is a BARRIER between stages: ALL items clear stage N before stage N+1 begins. Each stage is a one-arg callable; stage 1 gets the original item, later stages get the previous result. Same pool width as `parallel()`.
|
|
19
19
|
- `completion(prompt, *, model="default", system=None, schema=None)` — oneshot, stateless model call (no tools, no history). Tiers: "smol", "default", "slow". Cheap classification/scoring inside a fan-out.
|
|
20
20
|
- `log(message)` — emit a progress line above the status tree. `phase(title)` — start a phase; the status lines that follow group under it.
|
|
21
21
|
- `budget` — `budget.total` (output-token ceiling, or `None` when none is set), `budget.spent()` (tokens spent this turn — main loop + eval subagents), `budget.remaining()` (`math.inf` when total is `None`), `budget.hard` (whether it's enforced). A ceiling is set by the user: `+Nk` in their message is advisory (you self-limit via `budget.remaining()`), `+Nk!` (or Goal Mode) is hard — `agent()` refuses to spawn once spent reaches it. Gate loops on `budget.total` first, since it's `None` when the user set no budget.
|
|
22
22
|
|
|
23
|
-
Everything runs INLINE and synchronously inside the eval call — no background mode, no resume, no separate progress app. Each eval call is one well-scoped fan-out; chain several across
|
|
23
|
+
Everything runs INLINE and synchronously inside the eval call — no background mode, no resume, no separate progress app. Each eval call is one well-scoped fan-out; chain several across calls and turns for multi-phase work, reading each result before you decide the next phase.
|
|
24
24
|
</helpers>
|
|
25
25
|
|
|
26
26
|
<structure>
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
Runs bash in a shell session — terminal ops: git, bun, cargo, python.
|
|
2
2
|
|
|
3
|
+
# When to use bash — and when not to
|
|
4
|
+
|
|
5
|
+
Bash invokes **real binaries** with simple args. It is NOT a scripting surface.
|
|
6
|
+
|
|
7
|
+
Use bash ONLY for: a single binary call, or one short pipeline that COMPUTES a fact (`wc -l`, `sort | uniq -c`, `comm`, `diff`, a checksum, `git status`).
|
|
8
|
+
|
|
9
|
+
Anything below → `eval` cell, not bash:
|
|
10
|
+
- Inline interpreter scripts (`-e`/`-c`/`--eval`) when an eval runtime exists for that language
|
|
11
|
+
- Heredocs (`<<EOF`), `while`/`for`/`if`/`case` shell control flow
|
|
12
|
+
- `$(…)` command substitution nested inside another command
|
|
13
|
+
- Pipelines with more than two stages, or stages that need control flow or quote/JSON escaping
|
|
14
|
+
- Multiline commands, `&&`-chains mixing control flow
|
|
15
|
+
- Quote/JSON escaping that fights the shell
|
|
16
|
+
|
|
3
17
|
<instruction>
|
|
4
18
|
- `cwd` sets the working dir, not `cd dir && …`
|
|
5
19
|
- `env: { NAME: "…" }` for multiline / quote-heavy / untrusted values; reference `$NAME`
|
|
@@ -14,8 +28,10 @@ Runs bash in a shell session — terminal ops: git, bun, cargo, python.
|
|
|
14
28
|
</instruction>
|
|
15
29
|
|
|
16
30
|
<critical>
|
|
17
|
-
-
|
|
18
|
-
-
|
|
31
|
+
- Bash invokes real binaries with simple args; it is NOT a scripting surface. Loops, conditionals, heredocs, inline interpreter scripts (`-e`/`-c`/`--eval`) when an eval runtime exists, several piped stages, or quote/JSON escaping mean you're writing a program → use `eval` cells: restartable, stateful, and free of shell-quoting traps.
|
|
32
|
+
- NEVER shell out to search content or files: `grep/rg` → `search`.
|
|
33
|
+
- NEVER use `ls` or `find` to list or locate files — `ls` → `read` (a directory path lists entries), `find` → the `find` tool (globbing). This is non-negotiable, even for a single quick listing.
|
|
34
|
+
- Avoid head/tail/redirections: stderr already merged; long output auto-truncated, FULL capture kept at `artifact://<id>`.
|
|
19
35
|
</critical>
|
|
20
36
|
|
|
21
37
|
<output>
|
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
Run code in a persistent kernel
|
|
1
|
+
Run one step of code in a persistent kernel.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
|
|
4
|
+
**One eval call = one cell = one logical step.** State persists per language across separate eval calls, tool calls, and `task` subagents — define helpers/datasets/clients in one call, then later calls reuse them directly.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Work incrementally: imports in one call, define in the next, test, then use — each its own eval call. Re-run setup ONLY after `reset`, a kernel crash, or a `NameError`/`ReferenceError` proving the state is gone. Parallelize work *within* a cell with the `parallel(thunks)` helper, not by batching steps.
|
|
7
|
+
|
|
8
|
+
Fields:
|
|
7
9
|
|
|
8
10
|
- `language` — {{#if py}}`"py"` IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` persistent JavaScript VM{{/if}}{{#if rb}}{{#ifAny py js}}, {{/ifAny}}`"rb"` persistent Ruby kernel{{/if}}{{#if jl}}{{#ifAny py js rb}}, {{/ifAny}}`"jl"` persistent Julia kernel{{/if}}.
|
|
9
11
|
- `code` — cell body, verbatim. Newlines/quotes JSON-encoded; no fences, no headers.
|
|
10
12
|
- `title` (optional) — short transcript label (e.g. `"imports"`).
|
|
11
|
-
- `timeout` (optional) —
|
|
12
|
-
- `reset` (optional) — wipe this
|
|
13
|
+
- `timeout` (optional) — seconds. Raise only for heavy compute or long non-agent tool calls.
|
|
14
|
+
- `reset` (optional) — wipe this language's kernel first.{{#ifAll py js}} Per-language: a `py` reset never touches the JS VM.{{/ifAll}}
|
|
13
15
|
|
|
14
|
-
Work incrementally — one logical step per cell (imports, define, test, use), many small cells per call; workflow notes in the assistant message or `title`, never in cell code.
|
|
15
16
|
{{#if py}}Live event loop: use top-level `await` directly; `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}}
|
|
16
|
-
{{#if
|
|
17
|
-
{{#if
|
|
18
|
-
|
|
17
|
+
{{#if js}}JS runs under **Bun**: Bun globals/APIs are available (`Bun.file`, `Bun.write`, `Bun.$`, `fetch`, `Buffer`); top-level `await`/`return` work directly.{{/if}}
|
|
18
|
+
{{#if rb}}Ruby: synchronous; helper options are keyword args (e.g. `output("id", limit: 2)`); the last expression auto-displays unless it is `nil`, an assignment, or a definition (like IRB).{{/if}}
|
|
19
|
+
{{#if jl}}Julia: synchronous; helper options are standard keyword args (e.g. `output("id", limit=2)`); the last expression auto-displays unless it is an assignment or a definition (like the Julia REPL).{{/if}}
|
|
20
|
+
On error, fix and re-run only the failing step — prior calls' state survives.
|
|
19
21
|
</instruction>
|
|
20
22
|
|
|
21
23
|
<prelude>
|
|
@@ -29,12 +31,6 @@ read(path, offset?=1, limit?=None) → str
|
|
|
29
31
|
File as text; offset/limit 1-indexed lines. Accepts `local://…`.
|
|
30
32
|
write(path, content) → str
|
|
31
33
|
Write file (creates parents) → resolved path. `local://…` persists across turns/subagents.
|
|
32
|
-
append(path, content) → str
|
|
33
|
-
Append → resolved path. Accepts `local://…`.
|
|
34
|
-
tree(path?=".", max_depth?=3, show_hidden?=False) → str
|
|
35
|
-
Directory tree.
|
|
36
|
-
diff(a, b) → str
|
|
37
|
-
Unified diff of two files.
|
|
38
34
|
env(key?=None, value?=None) → str | None | dict
|
|
39
35
|
No args → full env dict; one → value of `key`; two → set `key=value`, return value.
|
|
40
36
|
output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | dict | list[dict]
|
|
@@ -43,9 +39,9 @@ tool.<name>(args) → unknown
|
|
|
43
39
|
Invoke any session tool; `args` = its parameter object.
|
|
44
40
|
completion(prompt, model?="default", system?=None, schema?=None) → str | dict
|
|
45
41
|
Oneshot, stateless (no history/tools). `model`: "smol" fast | "default" session | "slow" most capable. `schema` (JSON-Schema) → structured output, parsed object.
|
|
46
|
-
{{#if spawns}}agent(prompt,
|
|
47
|
-
Run a subagent → final output. `
|
|
48
|
-
{{#if js}} JS: options are ONE trailing object — agent(prompt, {
|
|
42
|
+
{{#if spawns}}agent(prompt, agent?="task", model?=None, label?=None, schema?=None, handle?=False) → str | dict
|
|
43
|
+
Run a subagent → final output. `agent` picks another discovered agent; `schema` as in completion(). Background via `local://` files named in the prompt. `handle` → DAG node dict { text, output, handle: "agent://<id>", id, agent } (parsed under `data` when `schema` set).
|
|
44
|
+
{{#if js}} JS: options are ONE trailing object — agent(prompt, { agent, schema, handle }).
|
|
49
45
|
{{/if}}
|
|
50
46
|
{{/if}}
|
|
51
47
|
parallel(thunks) → list
|
|
@@ -63,10 +59,14 @@ budget → per-turn token budget
|
|
|
63
59
|
{{#if spawns}}
|
|
64
60
|
<dag>
|
|
65
61
|
Pipe handles through stage helpers to build a dependency graph — acyclic waves:
|
|
66
|
-
- **Name nodes.** Capture each `agent(…, {{#if py}}
|
|
62
|
+
- **Name nodes.** Capture each `agent(…, {{#if py}}handle=True{{/if}}{{#if js}}{ handle: true }{{/if}}{{#if jl}}handle=true{{/if}})` result; carries `handle` (`agent://<id>`) + `output`.
|
|
67
63
|
- **Wire edges by reference.** Put an upstream node's `handle`/`output` in the dependent stage's prompt — large transcript never re-inlined. Bulk: `write("local://<name>.md", …)`, pass the URI.
|
|
68
64
|
- **`pipeline(items, *stages)` = staged waves**, barrier between stages (every item clears stage N before any enters N+1). **`parallel(thunks)` = one wave** of independent nodes.
|
|
69
65
|
- **Isolate failure.** A raising node re-raises the lowest-index error, aborts its wave; wrap risky nodes in try/except so a failure degrades only its dependent subtree, independent branches finish.
|
|
70
66
|
- **Acyclic only.** A node never waits on its own descendant.
|
|
71
67
|
</dag>
|
|
72
68
|
{{/if}}
|
|
69
|
+
|
|
70
|
+
<critical>
|
|
71
|
+
Prior top-level names (`data`, `sessions`, helpers, imports) survive into the next eval call — reuse them; NEVER re-import, re-require, or re-declare a helper. Re-read a file only if it may have changed since the last read. Re-run setup only after `reset`, a crash, or a `NameError`/`ReferenceError`.
|
|
72
|
+
</critical>
|
|
@@ -17,5 +17,6 @@ Searches files using regex.
|
|
|
17
17
|
</output>
|
|
18
18
|
|
|
19
19
|
<critical>
|
|
20
|
+
- MUST use built-in `search` for any content search. NEVER shell out to `grep`, `rg`, `ripgrep`, `ag`, `ack`, `git grep`, `awk`, `sed`-for-search, or any CLI search via Bash — not even for one match or a quick check.
|
|
20
21
|
- Open-ended search needing multiple rounds? MUST use the Task tool with the explore subagent, NOT chained `search` calls.
|
|
21
22
|
</critical>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
**Tasks referenced by verbatim content string, NEVER an auto-generated ID — no "task-1"/"task-N" exists. Pass the content text in the `task` field.**
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Next pending task auto-promotes to `in_progress` on each completion.
|
|
4
4
|
|
|
5
5
|
## Operations
|
|
6
6
|
|
|
@@ -259,9 +259,9 @@ export function deobfuscateAgentMessages(obfuscator: SecretObfuscator, messages:
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
/**
|
|
262
|
-
* Restore placeholders in assistant content: visible text
|
|
263
|
-
*
|
|
264
|
-
*
|
|
262
|
+
* Restore placeholders in assistant content: visible text and tool-call
|
|
263
|
+
* arguments/intent/rawBlock. Thinking and signatures are opaque
|
|
264
|
+
* provider-replay/hidden-reasoning data and pass through byte-identical.
|
|
265
265
|
*/
|
|
266
266
|
export function deobfuscateAssistantContent(
|
|
267
267
|
obfuscator: SecretObfuscator,
|
|
@@ -276,12 +276,6 @@ export function deobfuscateAssistantContent(
|
|
|
276
276
|
changed = true;
|
|
277
277
|
return { ...block, text };
|
|
278
278
|
}
|
|
279
|
-
if (block.type === "thinking") {
|
|
280
|
-
const thinking = obfuscator.deobfuscate(block.thinking);
|
|
281
|
-
if (thinking === block.thinking) return block;
|
|
282
|
-
changed = true;
|
|
283
|
-
return { ...block, thinking };
|
|
284
|
-
}
|
|
285
279
|
if (block.type === "toolCall") {
|
|
286
280
|
const args = deobfuscateToolArguments(obfuscator, block.arguments);
|
|
287
281
|
const intent = block.intent === undefined ? undefined : obfuscator.deobfuscate(block.intent);
|