@oh-my-pi/pi-coding-agent 16.1.13 → 16.1.14
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 +27 -0
- package/dist/cli.js +5547 -2759
- package/dist/types/config/settings-schema.d.ts +44 -14
- package/dist/types/eval/__tests__/julia-prelude.test.d.ts +1 -0
- package/dist/types/eval/backend-helpers.d.ts +23 -0
- package/dist/types/eval/executor-base.d.ts +118 -0
- package/dist/types/eval/index.d.ts +2 -0
- package/dist/types/eval/jl/executor.d.ts +44 -0
- package/dist/types/eval/jl/index.d.ts +11 -0
- package/dist/types/eval/jl/kernel.d.ts +28 -0
- package/dist/types/eval/jl/prelude.d.ts +1 -0
- package/dist/types/eval/jl/runtime.d.ts +22 -0
- package/dist/types/eval/kernel-base.d.ts +105 -0
- package/dist/types/eval/py/kernel.d.ts +3 -61
- package/dist/types/eval/rb/executor.d.ts +77 -0
- package/dist/types/eval/rb/index.d.ts +11 -0
- package/dist/types/eval/rb/kernel.d.ts +31 -0
- package/dist/types/eval/rb/prelude.d.ts +1 -0
- package/dist/types/eval/rb/runtime.d.ts +23 -0
- package/dist/types/eval/runtime-env.d.ts +24 -0
- package/dist/types/eval/types.d.ts +3 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -15
- package/dist/types/extensibility/hooks/loader.d.ts +1 -25
- package/dist/types/extensibility/session-handler-types.d.ts +23 -0
- package/dist/types/jsonrpc/message-framing.d.ts +35 -0
- package/dist/types/mnemopi/embed-client.d.ts +7 -24
- package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +11 -0
- package/dist/types/modes/components/selector-helpers.d.ts +53 -0
- package/dist/types/modes/interactive-mode.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +8 -1
- package/dist/types/modes/types.d.ts +0 -2
- package/dist/types/modes/utils/copy-targets.d.ts +1 -1
- package/dist/types/modes/utils/interactive-context-helpers.d.ts +14 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +54 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/stt/asr-client.d.ts +3 -29
- package/dist/types/subprocess/worker-client.d.ts +149 -0
- package/dist/types/subprocess/worker-runtime.d.ts +107 -0
- package/dist/types/tiny/title-client.d.ts +14 -34
- package/dist/types/tools/eval-backends.d.ts +6 -3
- package/dist/types/tools/eval-render.d.ts +6 -5
- package/dist/types/tools/eval.d.ts +13 -15
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tts/tts-client.d.ts +3 -28
- package/dist/types/tui/code-cell.d.ts +7 -0
- package/dist/types/utils/file-display-mode.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +6 -0
- package/package.json +12 -12
- package/src/config/settings-schema.ts +50 -18
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +13 -107
- package/src/eval/__tests__/julia-prelude.test.ts +77 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/executor-base.ts +425 -0
- package/src/eval/index.ts +2 -0
- package/src/eval/jl/executor.ts +540 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +235 -0
- package/src/eval/jl/prelude.jl +930 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +634 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/index.ts +3 -14
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/executor.ts +43 -252
- package/src/eval/py/index.ts +9 -20
- package/src/eval/py/kernel.ts +29 -544
- package/src/eval/rb/executor.ts +504 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +230 -0
- package/src/eval/rb/prelude.rb +721 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +474 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/types.ts +3 -3
- package/src/extensibility/extensions/runner.ts +4 -11
- package/src/extensibility/hooks/loader.ts +3 -21
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/lsp/client.ts +13 -109
- package/src/mnemopi/embed-client.ts +43 -198
- package/src/modes/components/agent-dashboard.ts +17 -40
- package/src/modes/components/chat-transcript-builder.ts +18 -102
- package/src/modes/components/custom-editor.ts +19 -0
- package/src/modes/components/extensions/extension-dashboard.ts +4 -13
- package/src/modes/components/extensions/extension-list.ts +16 -44
- package/src/modes/components/history-search.ts +4 -16
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/settings-selector.ts +7 -5
- package/src/modes/components/tree-selector.ts +13 -18
- package/src/modes/controllers/event-controller.ts +3 -9
- package/src/modes/controllers/input-controller.ts +32 -54
- package/src/modes/interactive-mode.ts +5 -7
- package/src/modes/theme/theme.ts +35 -0
- package/src/modes/types.ts +0 -2
- package/src/modes/utils/copy-targets.ts +3 -2
- package/src/modes/utils/interactive-context-helpers.ts +27 -0
- package/src/modes/utils/transcript-render-helpers.ts +157 -0
- package/src/modes/utils/ui-helpers.ts +21 -126
- package/src/prompts/system/system-prompt.md +10 -10
- package/src/prompts/tools/bash.md +0 -1
- package/src/prompts/tools/eval.md +6 -4
- package/src/prompts/tools/find.md +0 -4
- package/src/prompts/tools/read.md +1 -2
- package/src/prompts/tools/replace.md +1 -1
- package/src/prompts/tools/search.md +0 -1
- package/src/sdk.ts +13 -7
- package/src/session/agent-session.ts +9 -7
- package/src/stt/asr-client.ts +35 -215
- package/src/stt/asr-worker.ts +29 -181
- package/src/subprocess/worker-client.ts +297 -0
- package/src/subprocess/worker-runtime.ts +277 -0
- package/src/task/executor.ts +4 -4
- package/src/tiny/title-client.ts +53 -219
- package/src/tiny/worker.ts +29 -180
- package/src/tools/eval-backends.ts +10 -3
- package/src/tools/eval-render.ts +17 -8
- package/src/tools/eval.ts +187 -22
- package/src/tools/index.ts +51 -28
- package/src/tts/tts-client.ts +38 -206
- package/src/tts/tts-worker.ts +23 -97
- package/src/tui/code-cell.ts +12 -1
- package/src/utils/file-display-mode.ts +2 -3
- package/src/web/parallel.ts +43 -42
- package/src/web/search/providers/parallel.ts +10 -99
|
@@ -530,10 +530,7 @@ export class InputController {
|
|
|
530
530
|
// model continues the prior intent rather than second-guessing the interrupt.
|
|
531
531
|
if (text === "." || text === "c") {
|
|
532
532
|
if (this.ctx.onInputCallback) {
|
|
533
|
-
this.ctx.editor.
|
|
534
|
-
this.ctx.pendingImages = [];
|
|
535
|
-
this.ctx.pendingImageLinks = [];
|
|
536
|
-
this.ctx.editor.imageLinks = undefined;
|
|
533
|
+
this.ctx.editor.clearDraft();
|
|
537
534
|
this.ctx.onInputCallback({
|
|
538
535
|
text: manualContinuePrompt,
|
|
539
536
|
cancelled: false,
|
|
@@ -546,16 +543,14 @@ export class InputController {
|
|
|
546
543
|
}
|
|
547
544
|
|
|
548
545
|
const runner = this.ctx.session.extensionRunner;
|
|
549
|
-
let inputImages = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
550
|
-
let inputImageLinks =
|
|
546
|
+
let inputImages = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
|
|
547
|
+
let inputImageLinks =
|
|
548
|
+
this.ctx.editor.pendingImageLinks.length > 0 ? [...this.ctx.editor.pendingImageLinks] : undefined;
|
|
551
549
|
|
|
552
550
|
if (runner?.hasHandlers("input")) {
|
|
553
551
|
const result = await runner.emitInput(text, inputImages, "interactive");
|
|
554
552
|
if (result?.handled) {
|
|
555
|
-
this.ctx.editor.
|
|
556
|
-
this.ctx.pendingImages = [];
|
|
557
|
-
this.ctx.pendingImageLinks = [];
|
|
558
|
-
this.ctx.editor.imageLinks = undefined;
|
|
553
|
+
this.ctx.editor.clearDraft();
|
|
559
554
|
return;
|
|
560
555
|
}
|
|
561
556
|
if (result?.text !== undefined) {
|
|
@@ -603,12 +598,8 @@ export class InputController {
|
|
|
603
598
|
this.ctx.showStatus("This collab link is read-only — prompting is disabled");
|
|
604
599
|
return;
|
|
605
600
|
}
|
|
606
|
-
this.ctx.editor.addToHistory(text);
|
|
607
|
-
this.ctx.editor.setText("");
|
|
608
|
-
this.ctx.editor.imageLinks = undefined;
|
|
609
601
|
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
610
|
-
this.ctx.
|
|
611
|
-
this.ctx.pendingImageLinks = [];
|
|
602
|
+
this.ctx.editor.clearDraft(text);
|
|
612
603
|
// No local render: the prompt comes back from the host as a
|
|
613
604
|
// collab-prompt event/entry and renders with the author badge.
|
|
614
605
|
this.ctx.collabGuest.sendPrompt(text, images);
|
|
@@ -680,8 +671,8 @@ export class InputController {
|
|
|
680
671
|
this.ctx.editor.setText("");
|
|
681
672
|
this.ctx.editor.imageLinks = undefined;
|
|
682
673
|
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
683
|
-
this.ctx.pendingImages = [];
|
|
684
|
-
this.ctx.pendingImageLinks = [];
|
|
674
|
+
this.ctx.editor.pendingImages = [];
|
|
675
|
+
this.ctx.editor.pendingImageLinks = [];
|
|
685
676
|
// Record the signature so the queued message's eventual delivery
|
|
686
677
|
// (a user-role `message_start` event) leaves any draft the user has
|
|
687
678
|
// typed since queuing intact. Same protection as #783, applied to
|
|
@@ -744,8 +735,8 @@ export class InputController {
|
|
|
744
735
|
// Include any pending images from clipboard paste
|
|
745
736
|
this.ctx.editor.imageLinks = undefined;
|
|
746
737
|
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
747
|
-
this.ctx.pendingImages = [];
|
|
748
|
-
this.ctx.pendingImageLinks = [];
|
|
738
|
+
this.ctx.editor.pendingImages = [];
|
|
739
|
+
this.ctx.editor.pendingImageLinks = [];
|
|
749
740
|
|
|
750
741
|
// Render user message immediately, then let session events catch up.
|
|
751
742
|
// Tag the submission as "steer": this is a normal Enter the controller
|
|
@@ -771,8 +762,8 @@ export class InputController {
|
|
|
771
762
|
// semantics instead of throwing AgentBusyError.
|
|
772
763
|
this.ctx.editor.imageLinks = undefined;
|
|
773
764
|
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
774
|
-
this.ctx.pendingImages = [];
|
|
775
|
-
this.ctx.pendingImageLinks = [];
|
|
765
|
+
this.ctx.editor.pendingImages = [];
|
|
766
|
+
this.ctx.editor.pendingImageLinks = [];
|
|
776
767
|
try {
|
|
777
768
|
await this.ctx.withLocalSubmission(
|
|
778
769
|
text,
|
|
@@ -787,9 +778,11 @@ export class InputController {
|
|
|
787
778
|
// extension command).
|
|
788
779
|
this.ctx.editor.setText(text);
|
|
789
780
|
if (images && images.length > 0) {
|
|
790
|
-
this.ctx.pendingImages = [...images];
|
|
791
|
-
this.ctx.pendingImageLinks = inputImageLinks
|
|
792
|
-
|
|
781
|
+
this.ctx.editor.pendingImages = [...images];
|
|
782
|
+
this.ctx.editor.pendingImageLinks = inputImageLinks
|
|
783
|
+
? [...inputImageLinks]
|
|
784
|
+
: images.map(() => undefined);
|
|
785
|
+
this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
|
|
793
786
|
}
|
|
794
787
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
795
788
|
}
|
|
@@ -816,12 +809,8 @@ export class InputController {
|
|
|
816
809
|
this.ctx.showStatus("Commands run in the main session — press ←← to return first");
|
|
817
810
|
return; // editor text not cleared: Editor does not auto-clear on submit
|
|
818
811
|
}
|
|
819
|
-
const images = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
820
|
-
this.ctx.editor.
|
|
821
|
-
this.ctx.editor.setText("");
|
|
822
|
-
this.ctx.editor.imageLinks = undefined;
|
|
823
|
-
this.ctx.pendingImages = [];
|
|
824
|
-
this.ctx.pendingImageLinks = [];
|
|
812
|
+
const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
|
|
813
|
+
this.ctx.editor.clearDraft(text);
|
|
825
814
|
try {
|
|
826
815
|
// prompt() handles idle (new turn) and streaming (queues per streamingBehavior).
|
|
827
816
|
await this.ctx.withLocalSubmission(text, () => target.prompt(text, { streamingBehavior, images }), {
|
|
@@ -989,10 +978,7 @@ export class InputController {
|
|
|
989
978
|
}
|
|
990
979
|
const didRetry = await this.ctx.viewSession.retry();
|
|
991
980
|
if (didRetry) {
|
|
992
|
-
this.ctx.editor.
|
|
993
|
-
this.ctx.pendingImages = [];
|
|
994
|
-
this.ctx.pendingImageLinks = [];
|
|
995
|
-
this.ctx.editor.imageLinks = undefined;
|
|
981
|
+
this.ctx.editor.clearDraft();
|
|
996
982
|
} else {
|
|
997
983
|
this.ctx.showStatus("Nothing to retry");
|
|
998
984
|
}
|
|
@@ -1016,7 +1002,7 @@ export class InputController {
|
|
|
1016
1002
|
// the queued entry is later re-parsed into a skill invocation is a
|
|
1017
1003
|
// separate concern owned by the compaction-resume path.
|
|
1018
1004
|
if (this.ctx.session.isCompacting) {
|
|
1019
|
-
const images = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
1005
|
+
const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
|
|
1020
1006
|
this.ctx.queueCompactionMessage(text, "followUp", images);
|
|
1021
1007
|
return;
|
|
1022
1008
|
}
|
|
@@ -1040,14 +1026,10 @@ export class InputController {
|
|
|
1040
1026
|
|
|
1041
1027
|
// Forward any pending clipboard-pasted images alongside the queued text;
|
|
1042
1028
|
// otherwise the follow-up would drop the image (mirrors the Enter/steer path).
|
|
1043
|
-
const images = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
1029
|
+
const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
|
|
1044
1030
|
|
|
1045
1031
|
if (this.ctx.session.isStreaming) {
|
|
1046
|
-
this.ctx.editor.
|
|
1047
|
-
this.ctx.editor.setText("");
|
|
1048
|
-
this.ctx.editor.imageLinks = undefined;
|
|
1049
|
-
this.ctx.pendingImages = [];
|
|
1050
|
-
this.ctx.pendingImageLinks = [];
|
|
1032
|
+
this.ctx.editor.clearDraft(text);
|
|
1051
1033
|
await this.ctx.withLocalSubmission(
|
|
1052
1034
|
text,
|
|
1053
1035
|
() => this.ctx.session.prompt(text, { streamingBehavior: "followUp", images }),
|
|
@@ -1059,11 +1041,7 @@ export class InputController {
|
|
|
1059
1041
|
}
|
|
1060
1042
|
|
|
1061
1043
|
// Not streaming — just submit normally
|
|
1062
|
-
this.ctx.editor.
|
|
1063
|
-
this.ctx.editor.setText("");
|
|
1064
|
-
this.ctx.editor.imageLinks = undefined;
|
|
1065
|
-
this.ctx.pendingImages = [];
|
|
1066
|
-
this.ctx.pendingImageLinks = [];
|
|
1044
|
+
this.ctx.editor.clearDraft(text);
|
|
1067
1045
|
await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, { images }), {
|
|
1068
1046
|
imageCount: images?.length ?? 0,
|
|
1069
1047
|
});
|
|
@@ -1108,7 +1086,7 @@ export class InputController {
|
|
|
1108
1086
|
let queuedText: string;
|
|
1109
1087
|
if (queuedImages.length > 0) {
|
|
1110
1088
|
const parts: string[] = [];
|
|
1111
|
-
let imageOffset = this.ctx.pendingImages.length;
|
|
1089
|
+
let imageOffset = this.ctx.editor.pendingImages.length;
|
|
1112
1090
|
for (const entry of allQueued) {
|
|
1113
1091
|
parts.push(shiftImageMarkers(entry.text, imageOffset));
|
|
1114
1092
|
if (entry.images && entry.images.length > 0) imageOffset += entry.images.length;
|
|
@@ -1124,9 +1102,9 @@ export class InputController {
|
|
|
1124
1102
|
// re-materialized lazily; the restored text already carries the
|
|
1125
1103
|
// renumbered `[Image #N, WxH]` markers).
|
|
1126
1104
|
if (queuedImages.length > 0) {
|
|
1127
|
-
this.ctx.pendingImages.push(...queuedImages);
|
|
1128
|
-
this.ctx.pendingImageLinks.push(...queuedImages.map(() => undefined));
|
|
1129
|
-
this.ctx.editor.imageLinks = this.ctx.pendingImageLinks;
|
|
1105
|
+
this.ctx.editor.pendingImages.push(...queuedImages);
|
|
1106
|
+
this.ctx.editor.pendingImageLinks.push(...queuedImages.map(() => undefined));
|
|
1107
|
+
this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
|
|
1130
1108
|
}
|
|
1131
1109
|
this.ctx.updatePendingMessagesDisplay();
|
|
1132
1110
|
if (options?.abort) {
|
|
@@ -1148,14 +1126,14 @@ export class InputController {
|
|
|
1148
1126
|
this.ctx.sessionManager.putBlob.bind(this.ctx.sessionManager),
|
|
1149
1127
|
)
|
|
1150
1128
|
)?.[0];
|
|
1151
|
-
this.ctx.pendingImages.push({
|
|
1129
|
+
this.ctx.editor.pendingImages.push({
|
|
1152
1130
|
type: "image",
|
|
1153
1131
|
data: imageData.data,
|
|
1154
1132
|
mimeType: imageData.mimeType,
|
|
1155
1133
|
});
|
|
1156
|
-
this.ctx.pendingImageLinks.push(imageLink);
|
|
1157
|
-
this.ctx.editor.imageLinks = this.ctx.pendingImageLinks;
|
|
1158
|
-
const imageNum = this.ctx.pendingImages.length;
|
|
1134
|
+
this.ctx.editor.pendingImageLinks.push(imageLink);
|
|
1135
|
+
this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
|
|
1136
|
+
const imageNum = this.ctx.editor.pendingImages.length;
|
|
1159
1137
|
const dims = await this.#imageDimensions(imageData);
|
|
1160
1138
|
const label = dims ? `[Image #${imageNum}, ${dims.width}x${dims.height}]` : `[Image #${imageNum}]`;
|
|
1161
1139
|
this.ctx.editor.insertText(`${label} `);
|
|
@@ -408,8 +408,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
408
408
|
todoPhases: TodoPhase[] = [];
|
|
409
409
|
hideThinkingBlock = false;
|
|
410
410
|
proseOnlyThinking = true;
|
|
411
|
-
pendingImages: ImageContent[] = [];
|
|
412
|
-
pendingImageLinks: (string | undefined)[] = [];
|
|
413
411
|
compactionQueuedMessages: CompactionQueuedMessage[] = [];
|
|
414
412
|
pendingTools = new Map<string, ToolExecutionHandle>();
|
|
415
413
|
pendingBashComponents: BashExecutionComponent[] = [];
|
|
@@ -1094,7 +1092,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1094
1092
|
if (this.#goalSuppressNextContinuation) return;
|
|
1095
1093
|
if (this.#pendingSubmittedInput) return;
|
|
1096
1094
|
if (this.editor.getText().trim().length > 0) return;
|
|
1097
|
-
if ((this.pendingImages?.length ?? 0) > 0) return;
|
|
1095
|
+
if ((this.editor.pendingImages?.length ?? 0) > 0) return;
|
|
1098
1096
|
const state = this.session.getGoalModeState();
|
|
1099
1097
|
if (!state?.enabled || state.goal.status !== "active") return;
|
|
1100
1098
|
const prompt = this.session.goalRuntime.buildContinuationPrompt();
|
|
@@ -1113,7 +1111,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1113
1111
|
if (this.#isAutoSubmitBlocked()) return;
|
|
1114
1112
|
if (this.#pendingSubmittedInput) return;
|
|
1115
1113
|
if (this.editor.getText().trim().length > 0) return;
|
|
1116
|
-
if ((this.pendingImages?.length ?? 0) > 0) return;
|
|
1114
|
+
if ((this.editor.pendingImages?.length ?? 0) > 0) return;
|
|
1117
1115
|
const latestState = this.session.getGoalModeState();
|
|
1118
1116
|
if (!latestState?.enabled || latestState.goal.status !== "active") return;
|
|
1119
1117
|
this.#goalContinuationTurnInFlight = true;
|
|
@@ -1338,9 +1336,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1338
1336
|
this.#stopLoadingAnimation(true);
|
|
1339
1337
|
}
|
|
1340
1338
|
if (!submission.customType) {
|
|
1341
|
-
this.pendingImages = submission.images ? [...submission.images] : [];
|
|
1342
|
-
this.pendingImageLinks = submission.imageLinks ? [...submission.imageLinks] : [];
|
|
1343
|
-
this.editor.imageLinks = this.pendingImageLinks;
|
|
1339
|
+
this.editor.pendingImages = submission.images ? [...submission.images] : [];
|
|
1340
|
+
this.editor.pendingImageLinks = submission.imageLinks ? [...submission.imageLinks] : [];
|
|
1341
|
+
this.editor.imageLinks = this.editor.pendingImageLinks;
|
|
1344
1342
|
this.rebuildChatFromMessages();
|
|
1345
1343
|
this.editor.setText(submission.text);
|
|
1346
1344
|
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -169,6 +169,7 @@ export type SymbolKey =
|
|
|
169
169
|
| "lang.cpp"
|
|
170
170
|
| "lang.csharp"
|
|
171
171
|
| "lang.ruby"
|
|
172
|
+
| "lang.julia"
|
|
172
173
|
| "lang.php"
|
|
173
174
|
| "lang.swift"
|
|
174
175
|
| "lang.kotlin"
|
|
@@ -369,6 +370,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
369
370
|
"lang.cpp": "➕",
|
|
370
371
|
"lang.csharp": "♯",
|
|
371
372
|
"lang.ruby": "💎",
|
|
373
|
+
"lang.julia": "Ⓙ",
|
|
372
374
|
"lang.php": "🐘",
|
|
373
375
|
"lang.swift": "🕊",
|
|
374
376
|
"lang.kotlin": "🅺",
|
|
@@ -673,6 +675,7 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
673
675
|
"lang.cpp": "\u{E61D}",
|
|
674
676
|
"lang.csharp": "\u{E7BC}",
|
|
675
677
|
"lang.ruby": "\u{E791}",
|
|
678
|
+
"lang.julia": "\u{E624}",
|
|
676
679
|
"lang.php": "\u{E608}",
|
|
677
680
|
"lang.swift": "\u{E755}",
|
|
678
681
|
"lang.kotlin": "\u{E634}",
|
|
@@ -870,6 +873,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
870
873
|
"lang.cpp": "cpp",
|
|
871
874
|
"lang.csharp": "cs",
|
|
872
875
|
"lang.ruby": "rb",
|
|
876
|
+
"lang.julia": "jl",
|
|
873
877
|
"lang.php": "php",
|
|
874
878
|
"lang.swift": "swift",
|
|
875
879
|
"lang.kotlin": "kt",
|
|
@@ -1335,6 +1339,8 @@ const langMap: Record<string, SymbolKey> = {
|
|
|
1335
1339
|
cs: "lang.csharp",
|
|
1336
1340
|
ruby: "lang.ruby",
|
|
1337
1341
|
rb: "lang.ruby",
|
|
1342
|
+
julia: "lang.julia",
|
|
1343
|
+
jl: "lang.julia",
|
|
1338
1344
|
php: "lang.php",
|
|
1339
1345
|
swift: "lang.swift",
|
|
1340
1346
|
kotlin: "lang.kotlin",
|
|
@@ -1406,6 +1412,20 @@ const langMap: Record<string, SymbolKey> = {
|
|
|
1406
1412
|
bin: "lang.binary",
|
|
1407
1413
|
};
|
|
1408
1414
|
|
|
1415
|
+
/**
|
|
1416
|
+
* Brand colors for language icons, keyed by the resolved `lang.*` SymbolKey.
|
|
1417
|
+
* Used by {@link Theme.getLangIconStyled} so eval-kernel cell headers tint each
|
|
1418
|
+
* language with its recognizable hue (JS yellow, Ruby red, Julia purple, Python
|
|
1419
|
+
* blue) instead of a flat muted gray. Applied as truecolor/256 per the active
|
|
1420
|
+
* color mode; languages without an entry fall back to the muted theme color.
|
|
1421
|
+
*/
|
|
1422
|
+
const LANG_BRAND_COLORS: Partial<Record<SymbolKey, string>> = {
|
|
1423
|
+
"lang.javascript": "#f7df1e",
|
|
1424
|
+
"lang.python": "#3776ab",
|
|
1425
|
+
"lang.ruby": "#cc342d",
|
|
1426
|
+
"lang.julia": "#9558b2",
|
|
1427
|
+
};
|
|
1428
|
+
|
|
1409
1429
|
/**
|
|
1410
1430
|
* Resolve a theme color value (hex string or 256-color index) to a CSS hex string.
|
|
1411
1431
|
* Empty string represents the default terminal color.
|
|
@@ -1874,6 +1894,21 @@ export class Theme {
|
|
|
1874
1894
|
const key = langMap[normalized];
|
|
1875
1895
|
return key ? this.#symbols[key] : this.#symbols["lang.default"];
|
|
1876
1896
|
}
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* Language icon tinted with the language's brand color (see
|
|
1900
|
+
* {@link LANG_BRAND_COLORS}). Falls back to the muted theme color for
|
|
1901
|
+
* languages without a brand entry, and returns the bare (possibly empty)
|
|
1902
|
+
* icon when the active symbol preset has none.
|
|
1903
|
+
*/
|
|
1904
|
+
getLangIconStyled(lang: string | undefined): string {
|
|
1905
|
+
const icon = this.getLangIcon(lang);
|
|
1906
|
+
if (!icon) return icon;
|
|
1907
|
+
const key = lang ? langMap[lang.toLowerCase()] : undefined;
|
|
1908
|
+
const hex = key ? LANG_BRAND_COLORS[key] : undefined;
|
|
1909
|
+
if (!hex) return this.fg("muted", icon);
|
|
1910
|
+
return `${colorToAnsi(hex, this.mode)}${icon}\x1b[39m`;
|
|
1911
|
+
}
|
|
1877
1912
|
}
|
|
1878
1913
|
|
|
1879
1914
|
// ============================================================================
|
package/src/modes/types.ts
CHANGED
|
@@ -160,8 +160,6 @@ export interface InteractiveModeContext {
|
|
|
160
160
|
planModePlanFilePath?: string;
|
|
161
161
|
hideThinkingBlock: boolean;
|
|
162
162
|
proseOnlyThinking: boolean;
|
|
163
|
-
pendingImages: ImageContent[];
|
|
164
|
-
pendingImageLinks: (string | undefined)[];
|
|
165
163
|
compactionQueuedMessages: CompactionQueuedMessage[];
|
|
166
164
|
pendingTools: Map<string, ToolExecutionHandle>;
|
|
167
165
|
pendingBashComponents: BashExecutionComponent[];
|
|
@@ -22,7 +22,7 @@ export type MessageBlock = ({ kind: "code" } & CodeBlock) | ({ kind: "quote" } &
|
|
|
22
22
|
export interface LastCommand {
|
|
23
23
|
kind: "bash" | "eval";
|
|
24
24
|
code: string;
|
|
25
|
-
/** Highlight language: "bash" for bash, "python"/"javascript"
|
|
25
|
+
/** Highlight language: "bash" for bash, or the resolved eval language ("python"/"javascript"/"ruby"/"julia"). */
|
|
26
26
|
language: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -139,7 +139,8 @@ function extractEvalCode(args: unknown): { code: string; language: string } | un
|
|
|
139
139
|
if (typeof code !== "string" || code.length === 0) continue;
|
|
140
140
|
codeBlocks.push(code);
|
|
141
141
|
if (!languageResolved) {
|
|
142
|
-
|
|
142
|
+
const lang = (cell as { language?: unknown }).language;
|
|
143
|
+
language = lang === "js" ? "javascript" : lang === "rb" ? "ruby" : lang === "jl" ? "julia" : "python";
|
|
143
144
|
languageResolved = true;
|
|
144
145
|
}
|
|
145
146
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small helpers over {@link InteractiveModeContext} shared between
|
|
3
|
+
* {@link UiHelpers} and the input/event controllers, so the live chat surfaces
|
|
4
|
+
* construct components and reset editor state identically.
|
|
5
|
+
*/
|
|
6
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
7
|
+
import { AssistantMessageComponent } from "../components/assistant-message";
|
|
8
|
+
import type { InteractiveModeContext } from "../types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Construct an {@link AssistantMessageComponent} wired to the live context's
|
|
12
|
+
* thinking/image settings. `message` is omitted for the streaming placeholder
|
|
13
|
+
* component and supplied when rendering a persisted turn.
|
|
14
|
+
*/
|
|
15
|
+
export function createAssistantMessageComponent(
|
|
16
|
+
ctx: InteractiveModeContext,
|
|
17
|
+
message?: AssistantMessage,
|
|
18
|
+
): AssistantMessageComponent {
|
|
19
|
+
return new AssistantMessageComponent(
|
|
20
|
+
message,
|
|
21
|
+
ctx.hideThinkingBlock,
|
|
22
|
+
() => ctx.ui.requestRender(),
|
|
23
|
+
ctx.viewSession.extensionRunner?.getAssistantThinkingRenderers(),
|
|
24
|
+
ctx.ui.imageBudget,
|
|
25
|
+
ctx.proseOnlyThinking,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render helpers shared between the live transcript ({@link UiHelpers}) and the
|
|
3
|
+
* file/remote-backed {@link ChatTranscriptBuilder}. Both surfaces build the same
|
|
4
|
+
* transcript rows from persisted message entries; holding the row construction
|
|
5
|
+
* here keeps the two byte-for-byte identical.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
9
|
+
import { formatBytes, formatDuration } from "@oh-my-pi/pi-utils";
|
|
10
|
+
import { type CustomMessage, type FileMentionMessage, isSilentAbort, resolveAbortLabel } from "../../session/messages";
|
|
11
|
+
import { createIrcMessageCard } from "../../tools/irc";
|
|
12
|
+
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
13
|
+
import { TranscriptBlock } from "../components/transcript-container";
|
|
14
|
+
import { theme } from "../theme/theme";
|
|
15
|
+
|
|
16
|
+
type CustomOrHookMessage = Extract<AgentMessage, { role: "custom" | "hookMessage" }>;
|
|
17
|
+
type AssistantAgentMessage = Extract<AgentMessage, { role: "assistant" }>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render an `async-result` custom message (a completed background bash/task job,
|
|
21
|
+
* or a batch of them) as a transcript block of one "Background job completed"
|
|
22
|
+
* row per job.
|
|
23
|
+
*/
|
|
24
|
+
export function buildAsyncResultBlock(message: CustomOrHookMessage): TranscriptBlock {
|
|
25
|
+
const details = (
|
|
26
|
+
message as CustomMessage<{
|
|
27
|
+
jobId?: string;
|
|
28
|
+
type?: "bash" | "task";
|
|
29
|
+
label?: string;
|
|
30
|
+
durationMs?: number;
|
|
31
|
+
jobs?: Array<{ jobId?: string; type?: "bash" | "task"; label?: string; durationMs?: number }>;
|
|
32
|
+
}>
|
|
33
|
+
).details;
|
|
34
|
+
const jobs =
|
|
35
|
+
details?.jobs && details.jobs.length > 0
|
|
36
|
+
? details.jobs
|
|
37
|
+
: [
|
|
38
|
+
{
|
|
39
|
+
jobId: details?.jobId,
|
|
40
|
+
type: details?.type,
|
|
41
|
+
label: details?.label,
|
|
42
|
+
durationMs: details?.durationMs,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const block = new TranscriptBlock();
|
|
46
|
+
for (const job of jobs) {
|
|
47
|
+
const jobId = job.jobId ?? "unknown";
|
|
48
|
+
const typeLabel = job.type ? `[${job.type}]` : "[job]";
|
|
49
|
+
const duration = typeof job.durationMs === "number" ? formatDuration(job.durationMs) : undefined;
|
|
50
|
+
const line = [
|
|
51
|
+
theme.fg("success", `${theme.status.done} Background job completed`),
|
|
52
|
+
theme.fg("dim", typeLabel),
|
|
53
|
+
theme.fg("accent", jobId),
|
|
54
|
+
duration ? theme.fg("dim", `(${duration})`) : undefined,
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(" ");
|
|
58
|
+
block.addChild(new Text(line, 1, 0));
|
|
59
|
+
}
|
|
60
|
+
return block;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Render a live IRC traffic custom message (`irc:incoming` / `irc:autoreply` /
|
|
65
|
+
* `irc:relay`) as a transcript card. `getExpanded` supplies the live
|
|
66
|
+
* expanded-state getter for the cached card.
|
|
67
|
+
*/
|
|
68
|
+
export function buildIrcMessageCard(message: CustomOrHookMessage, getExpanded: () => boolean): Component {
|
|
69
|
+
const details = (
|
|
70
|
+
message as CustomMessage<{ from?: string; to?: string; message?: string; body?: string; replyTo?: string }>
|
|
71
|
+
).details;
|
|
72
|
+
const kind =
|
|
73
|
+
message.customType === "irc:incoming"
|
|
74
|
+
? ("incoming" as const)
|
|
75
|
+
: message.customType === "irc:autoreply"
|
|
76
|
+
? ("autoreply" as const)
|
|
77
|
+
: ("relay" as const);
|
|
78
|
+
return createIrcMessageCard(
|
|
79
|
+
{
|
|
80
|
+
kind,
|
|
81
|
+
from: details?.from,
|
|
82
|
+
to: details?.to,
|
|
83
|
+
body: kind === "incoming" ? details?.message : details?.body,
|
|
84
|
+
replyTo: details?.replyTo,
|
|
85
|
+
timestamp: message.timestamp,
|
|
86
|
+
},
|
|
87
|
+
getExpanded,
|
|
88
|
+
theme,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Render a `fileMention` message's files as a transcript block of "Read <path>"
|
|
94
|
+
* rows. `indent` sets the left pad: the live chat renders within an outer gutter
|
|
95
|
+
* (0), the transcript viewer renders body rows without one so rows own their pad
|
|
96
|
+
* (1).
|
|
97
|
+
*/
|
|
98
|
+
export function buildFileMentionBlock(files: FileMentionMessage["files"], indent: number): TranscriptBlock {
|
|
99
|
+
const block = new TranscriptBlock();
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
let suffix: string;
|
|
102
|
+
if (file.skippedReason === "tooLarge") {
|
|
103
|
+
const size = typeof file.byteSize === "number" ? formatBytes(file.byteSize) : "unknown size";
|
|
104
|
+
suffix = `(skipped: ${size})`;
|
|
105
|
+
} else {
|
|
106
|
+
suffix = file.image
|
|
107
|
+
? "(image)"
|
|
108
|
+
: file.lineCount === undefined
|
|
109
|
+
? "(unknown lines)"
|
|
110
|
+
: `(${file.lineCount} lines)`;
|
|
111
|
+
}
|
|
112
|
+
const text = `${theme.fg("dim", `${theme.tree.last} `)}${theme.fg("muted", "Read")} ${theme.fg(
|
|
113
|
+
"accent",
|
|
114
|
+
file.path,
|
|
115
|
+
)} ${theme.fg("dim", suffix)}`;
|
|
116
|
+
block.addChild(new Text(text, indent, 0));
|
|
117
|
+
}
|
|
118
|
+
return block;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether an assistant turn has visible text or thinking content (after
|
|
123
|
+
* canonicalization) — i.e. content that closes the current read-tool run.
|
|
124
|
+
*/
|
|
125
|
+
export function assistantHasVisibleContent(message: AssistantAgentMessage): boolean {
|
|
126
|
+
return message.content.some(
|
|
127
|
+
content =>
|
|
128
|
+
(content.type === "text" && canonicalizeMessage(content.text)) ||
|
|
129
|
+
(content.type === "thinking" && canonicalizeMessage(content.thinking)),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Normalize raw tool-call arguments to a plain record, collapsing non-object or
|
|
135
|
+
* array values to an empty object.
|
|
136
|
+
*/
|
|
137
|
+
export function normalizeToolArgs(args: unknown): Record<string, unknown> {
|
|
138
|
+
return args && typeof args === "object" && !Array.isArray(args) ? (args as Record<string, unknown>) : {};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resolve the inline error label, if any, for a turn-ending assistant message.
|
|
143
|
+
* Silent aborts yield no label. `retryAttempt` tunes the abort label wording.
|
|
144
|
+
*/
|
|
145
|
+
export function resolveAssistantErrorMessage(
|
|
146
|
+
message: AssistantAgentMessage,
|
|
147
|
+
retryAttempt = 0,
|
|
148
|
+
): { hasErrorStop: boolean; errorMessage: string | null } {
|
|
149
|
+
const isAbortedSilently = message.stopReason === "aborted" && isSilentAbort(message.errorMessage);
|
|
150
|
+
const hasErrorStop = !isAbortedSilently && (message.stopReason === "aborted" || message.stopReason === "error");
|
|
151
|
+
const errorMessage = hasErrorStop
|
|
152
|
+
? message.stopReason === "aborted"
|
|
153
|
+
? resolveAbortLabel(message.errorMessage, retryAttempt)
|
|
154
|
+
: message.errorMessage || "Error"
|
|
155
|
+
: null;
|
|
156
|
+
return { hasErrorStop, errorMessage };
|
|
157
|
+
}
|