@oh-my-pi/pi-coding-agent 15.10.11 → 15.10.12
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 +44 -0
- package/dist/cli.js +5349 -5328
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +7 -2
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +14 -2
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/index.d.ts +2 -2
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/package.json +10 -10
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/cli/args.ts +3 -0
- package/src/cli-commands.ts +29 -0
- package/src/cli.ts +8 -9
- package/src/commands/launch.ts +4 -0
- package/src/commit/model-selection.ts +3 -2
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/model-registry.ts +97 -30
- package/src/config/model-resolver.ts +60 -0
- package/src/config/settings-schema.ts +43 -15
- package/src/config/settings.ts +61 -3
- package/src/edit/hashline/execute.ts +39 -2
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/eval/completion-bridge.ts +1 -0
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -1
- package/src/eval/py/kernel.ts +31 -11
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +82 -3
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/hindsight/bank.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/main.ts +18 -6
- package/src/memories/index.ts +2 -0
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +151 -4
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/assistant-message.ts +19 -21
- package/src/modes/components/footer.ts +3 -1
- package/src/modes/components/status-line/component.ts +118 -34
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +1 -0
- package/src/modes/controllers/mcp-command-controller.ts +38 -3
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +39 -9
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/types.ts +2 -0
- package/src/sdk.ts +8 -1
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +179 -54
- package/src/session/streaming-output.ts +166 -10
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/system-prompt.ts +14 -0
- package/src/task/executor.ts +13 -12
- package/src/task/index.ts +9 -8
- package/src/task/render.ts +18 -3
- package/src/task/types.ts +9 -0
- package/src/thinking.ts +7 -0
- package/src/tiny/title-client.ts +34 -5
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/bash.ts +46 -5
- package/src/tools/image-gen.ts +11 -4
- package/src/tools/index.ts +13 -1
- package/src/tools/inspect-image.ts +1 -0
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
|
@@ -11,6 +11,16 @@ export const DEFAULT_MAX_LINES = 3000;
|
|
|
11
11
|
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
12
|
export const DEFAULT_MAX_COLUMN = 512; // Max chars per grep match line
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Default artifact-on-disk cap for {@link OutputSink}.
|
|
16
|
+
*
|
|
17
|
+
* `0` means unbounded: by default, `artifact://<id>` references preserve the
|
|
18
|
+
* complete raw stream instead of a capped head/tail sample.
|
|
19
|
+
*/
|
|
20
|
+
export const ARTIFACT_DEFAULT_MAX_BYTES = 0;
|
|
21
|
+
/** Default head budget; the remainder becomes the rolling tail window. */
|
|
22
|
+
export const ARTIFACT_DEFAULT_HEAD_BYTES = 3 * 1024 * 1024; // 3 MiB
|
|
23
|
+
|
|
14
24
|
const NL = "\n";
|
|
15
25
|
const ELLIPSIS = "…";
|
|
16
26
|
|
|
@@ -58,6 +68,20 @@ export interface OutputSinkOptions {
|
|
|
58
68
|
onChunk?: (chunk: string) => void;
|
|
59
69
|
/** Minimum ms between onChunk calls. 0 = every chunk (default). */
|
|
60
70
|
chunkThrottleMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Optional cap on bytes written to the artifact-on-disk file. When the cap
|
|
73
|
+
* is hit, the head window is preserved verbatim and subsequent output feeds
|
|
74
|
+
* a rolling tail window; on close, the sink writes a single
|
|
75
|
+
* `[ARTIFACT TRUNCATED: …]` notice between them. Default
|
|
76
|
+
* {@link ARTIFACT_DEFAULT_MAX_BYTES} (unbounded).
|
|
77
|
+
*/
|
|
78
|
+
artifactMaxBytes?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Bytes reserved for the head window of the capped artifact file. The
|
|
81
|
+
* tail window receives `artifactMaxBytes - artifactHeadBytes`. Default
|
|
82
|
+
* {@link ARTIFACT_DEFAULT_HEAD_BYTES}; clamped to `[0, artifactMaxBytes]`.
|
|
83
|
+
*/
|
|
84
|
+
artifactHeadBytes?: number;
|
|
61
85
|
}
|
|
62
86
|
|
|
63
87
|
export interface TruncationResult {
|
|
@@ -676,6 +700,21 @@ export class OutputSink {
|
|
|
676
700
|
readonly #chunkThrottleMs: number;
|
|
677
701
|
readonly #maxColumns: number;
|
|
678
702
|
|
|
703
|
+
// Optional artifact-on-disk cap. When `#artifactMaxBytes > 0` the file sink
|
|
704
|
+
// owns a head budget + a rolling tail buffer; once the head is closed,
|
|
705
|
+
// subsequent chunks are diverted into `#artifactTailRing` (bounded by
|
|
706
|
+
// `#artifactTailBudget`). On `dump()` the tail is flushed back to the sink
|
|
707
|
+
// behind a `[ARTIFACT TRUNCATED: …]` notice. The default cap is disabled so
|
|
708
|
+
// advertised `artifact://<id>` captures are lossless.
|
|
709
|
+
readonly #artifactMaxBytes: number;
|
|
710
|
+
readonly #artifactHeadBudget: number;
|
|
711
|
+
readonly #artifactTailBudget: number;
|
|
712
|
+
#artifactHeadBytesWritten = 0;
|
|
713
|
+
#artifactHeadClosed = false;
|
|
714
|
+
#artifactTailRing = "";
|
|
715
|
+
#artifactTailRingBytes = 0;
|
|
716
|
+
#artifactTailIncomingBytes = 0;
|
|
717
|
+
|
|
679
718
|
constructor(options?: OutputSinkOptions) {
|
|
680
719
|
const {
|
|
681
720
|
artifactPath,
|
|
@@ -685,6 +724,8 @@ export class OutputSink {
|
|
|
685
724
|
maxColumns = 0,
|
|
686
725
|
onChunk,
|
|
687
726
|
chunkThrottleMs = 0,
|
|
727
|
+
artifactMaxBytes = ARTIFACT_DEFAULT_MAX_BYTES,
|
|
728
|
+
artifactHeadBytes = ARTIFACT_DEFAULT_HEAD_BYTES,
|
|
688
729
|
} = options ?? {};
|
|
689
730
|
this.#artifactPath = artifactPath;
|
|
690
731
|
this.#artifactId = artifactId;
|
|
@@ -693,6 +734,9 @@ export class OutputSink {
|
|
|
693
734
|
this.#maxColumns = Math.max(0, maxColumns);
|
|
694
735
|
this.#onChunk = onChunk;
|
|
695
736
|
this.#chunkThrottleMs = chunkThrottleMs;
|
|
737
|
+
this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
|
|
738
|
+
this.#artifactHeadBudget = Math.max(0, Math.min(artifactHeadBytes, this.#artifactMaxBytes));
|
|
739
|
+
this.#artifactTailBudget = Math.max(0, this.#artifactMaxBytes - this.#artifactHeadBudget);
|
|
696
740
|
}
|
|
697
741
|
|
|
698
742
|
/**
|
|
@@ -865,14 +909,18 @@ export class OutputSink {
|
|
|
865
909
|
/**
|
|
866
910
|
* Write a chunk to the artifact file. Handles the async file sink creation
|
|
867
911
|
* by queuing writes until the sink is ready, then draining synchronously.
|
|
912
|
+
* Once the sink is up, every byte flows through {@link #emitToSink} which
|
|
913
|
+
* owns the head + tail cap so artifacts cannot grow beyond
|
|
914
|
+
* `#artifactMaxBytes` on disk.
|
|
868
915
|
*/
|
|
869
916
|
#writeToFile(chunk: string): void {
|
|
870
917
|
if (this.#fileReady && this.#file) {
|
|
871
|
-
|
|
872
|
-
this.#file.sink.write(chunk);
|
|
918
|
+
this.#emitToSink(chunk);
|
|
873
919
|
return;
|
|
874
920
|
}
|
|
875
|
-
// File sink not yet created — queue this chunk and kick off creation
|
|
921
|
+
// File sink not yet created — queue this chunk and kick off creation.
|
|
922
|
+
// The queue is bounded only by how many chunks arrive before the open
|
|
923
|
+
// resolves (typically <2). The cap is enforced on drain.
|
|
876
924
|
if (!this.#pendingFileWrites) {
|
|
877
925
|
this.#pendingFileWrites = [chunk];
|
|
878
926
|
void this.#createFileSink();
|
|
@@ -881,31 +929,99 @@ export class OutputSink {
|
|
|
881
929
|
}
|
|
882
930
|
}
|
|
883
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Cap-aware sink writer. Bytes flow into the head window verbatim until the
|
|
934
|
+
* budget is exhausted; subsequent bytes are diverted into a rolling tail
|
|
935
|
+
* ring, evicted from the front so total RAM stays bounded by
|
|
936
|
+
* `#artifactTailBudget`. `dump()` replays the ring behind a single notice
|
|
937
|
+
* line before closing the sink.
|
|
938
|
+
*
|
|
939
|
+
* When the cap is disabled (`#artifactMaxBytes === 0`) this collapses to a
|
|
940
|
+
* straight pass-through, preserving the historical "stream everything"
|
|
941
|
+
* contract.
|
|
942
|
+
*/
|
|
943
|
+
#emitToSink(chunk: string): void {
|
|
944
|
+
if (!this.#file || chunk.length === 0) return;
|
|
945
|
+
if (this.#artifactMaxBytes === 0) {
|
|
946
|
+
this.#file.sink.write(chunk);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf-8");
|
|
950
|
+
const room = this.#artifactHeadClosed ? 0 : this.#artifactHeadBudget - this.#artifactHeadBytesWritten;
|
|
951
|
+
if (room >= chunkBytes) {
|
|
952
|
+
this.#file.sink.write(chunk);
|
|
953
|
+
this.#artifactHeadBytesWritten += chunkBytes;
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
let overflow = chunk;
|
|
957
|
+
if (room > 0) {
|
|
958
|
+
const headSlice = truncateHeadBytes(chunk, room);
|
|
959
|
+
if (headSlice.bytes > 0) {
|
|
960
|
+
this.#file.sink.write(headSlice.text);
|
|
961
|
+
this.#artifactHeadBytesWritten += headSlice.bytes;
|
|
962
|
+
}
|
|
963
|
+
// Even when UTF-8 boundary safety leaves a few bytes of nominal room,
|
|
964
|
+
// this chunk has already overflowed the head window. Close it now so a
|
|
965
|
+
// later small ASCII chunk cannot be written before this overflow tail.
|
|
966
|
+
this.#artifactHeadClosed = true;
|
|
967
|
+
overflow = chunk.substring(headSlice.text.length);
|
|
968
|
+
}
|
|
969
|
+
if (overflow.length === 0 || this.#artifactTailBudget === 0) {
|
|
970
|
+
// No tail budget: count the dropped bytes so the notice reflects them.
|
|
971
|
+
if (overflow.length > 0) {
|
|
972
|
+
this.#artifactTailIncomingBytes += Buffer.byteLength(overflow, "utf-8");
|
|
973
|
+
}
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
this.#pushArtifactTail(overflow);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
#pushArtifactTail(chunk: string): void {
|
|
980
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf-8");
|
|
981
|
+
this.#artifactTailIncomingBytes += chunkBytes;
|
|
982
|
+
const budget = this.#artifactTailBudget;
|
|
983
|
+
if (chunkBytes >= budget) {
|
|
984
|
+
// Chunk alone dominates — keep only its tail slice.
|
|
985
|
+
const { text, bytes } = truncateTailBytes(chunk, budget);
|
|
986
|
+
this.#artifactTailRing = text;
|
|
987
|
+
this.#artifactTailRingBytes = bytes;
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
this.#artifactTailRing += chunk;
|
|
991
|
+
this.#artifactTailRingBytes += chunkBytes;
|
|
992
|
+
if (this.#artifactTailRingBytes > budget) {
|
|
993
|
+
const { text, bytes } = truncateTailBytes(this.#artifactTailRing, budget);
|
|
994
|
+
this.#artifactTailRing = text;
|
|
995
|
+
this.#artifactTailRingBytes = bytes;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
884
999
|
async #createFileSink(): Promise<void> {
|
|
885
1000
|
if (!this.#artifactPath || this.#fileReady) return;
|
|
886
1001
|
try {
|
|
887
1002
|
const sink = Bun.file(this.#artifactPath).writer();
|
|
888
1003
|
this.#file = { path: this.#artifactPath, artifactId: this.#artifactId, sink };
|
|
1004
|
+
this.#fileReady = true;
|
|
889
1005
|
|
|
890
1006
|
// Head-retained bytes precede the rolling tail buffer in the capture.
|
|
1007
|
+
// Route through #emitToSink so they count against the artifact head
|
|
1008
|
+
// budget — a direct sink.write would let them escape the cap.
|
|
891
1009
|
if (this.#head.length > 0) {
|
|
892
|
-
|
|
1010
|
+
this.#emitToSink(this.#head);
|
|
893
1011
|
}
|
|
894
1012
|
|
|
895
1013
|
// Flush existing buffer to file BEFORE it gets trimmed further.
|
|
896
1014
|
if (this.#buffer.length > 0) {
|
|
897
|
-
|
|
1015
|
+
this.#emitToSink(this.#buffer);
|
|
898
1016
|
}
|
|
899
1017
|
|
|
900
|
-
// Drain any chunks that arrived while the sink was being created
|
|
1018
|
+
// Drain any chunks that arrived while the sink was being created.
|
|
901
1019
|
if (this.#pendingFileWrites) {
|
|
902
1020
|
for (const pending of this.#pendingFileWrites) {
|
|
903
|
-
|
|
1021
|
+
this.#emitToSink(pending);
|
|
904
1022
|
}
|
|
905
1023
|
this.#pendingFileWrites = undefined;
|
|
906
1024
|
}
|
|
907
|
-
|
|
908
|
-
this.#fileReady = true;
|
|
909
1025
|
} catch {
|
|
910
1026
|
try {
|
|
911
1027
|
await this.#file?.sink?.end();
|
|
@@ -914,6 +1030,7 @@ export class OutputSink {
|
|
|
914
1030
|
}
|
|
915
1031
|
this.#file = undefined;
|
|
916
1032
|
this.#pendingFileWrites = undefined;
|
|
1033
|
+
this.#fileReady = false;
|
|
917
1034
|
}
|
|
918
1035
|
}
|
|
919
1036
|
|
|
@@ -961,6 +1078,42 @@ export class OutputSink {
|
|
|
961
1078
|
this.#pendingChunk = "";
|
|
962
1079
|
}
|
|
963
1080
|
|
|
1081
|
+
/**
|
|
1082
|
+
* Replay the rolling tail ring back into the artifact sink. When bytes
|
|
1083
|
+
* were actually dropped from the middle (the head budget was exhausted
|
|
1084
|
+
* *and* the tail ring evicted), a single `[ARTIFACT TRUNCATED: …]`
|
|
1085
|
+
* notice is injected between head and tail so a reader of
|
|
1086
|
+
* `artifact://<id>` understands the gap. When the total stream simply
|
|
1087
|
+
* spilled past the head budget but still fits below `artifactMaxBytes`,
|
|
1088
|
+
* `droppedBytes` is zero — head + tail together are the verbatim stream
|
|
1089
|
+
* and the notice is suppressed so we don't corrupt the artifact with a
|
|
1090
|
+
* misleading "0 B elided" marker (PR #2083 review by codex).
|
|
1091
|
+
*
|
|
1092
|
+
* No-op when the cap was never hit at all (head budget never exhausted,
|
|
1093
|
+
* tail ring empty).
|
|
1094
|
+
*/
|
|
1095
|
+
#flushArtifactTailIfCapped(): void {
|
|
1096
|
+
if (!this.#file) return;
|
|
1097
|
+
if (this.#artifactMaxBytes === 0) return;
|
|
1098
|
+
const tailBytes = this.#artifactTailRingBytes;
|
|
1099
|
+
const droppedBytes = Math.max(0, this.#artifactTailIncomingBytes - tailBytes);
|
|
1100
|
+
if (tailBytes === 0 && droppedBytes === 0) return;
|
|
1101
|
+
|
|
1102
|
+
if (droppedBytes > 0) {
|
|
1103
|
+
const headWritten = this.#artifactHeadBytesWritten;
|
|
1104
|
+
const totalCapped = headWritten + this.#artifactTailIncomingBytes;
|
|
1105
|
+
const headSep = headWritten > 0 ? "\n" : "";
|
|
1106
|
+
const tailSep = tailBytes > 0 && !this.#artifactTailRing.startsWith("\n") ? "\n" : "";
|
|
1107
|
+
const notice =
|
|
1108
|
+
`${headSep}[ARTIFACT TRUNCATED: kept first ${formatBytes(headWritten)} + last ${formatBytes(tailBytes)} ` +
|
|
1109
|
+
`of ${formatBytes(totalCapped)}; ${formatBytes(droppedBytes)} elided from the middle]${tailSep}`;
|
|
1110
|
+
this.#file.sink.write(notice);
|
|
1111
|
+
}
|
|
1112
|
+
if (tailBytes > 0) {
|
|
1113
|
+
this.#file.sink.write(this.#artifactTailRing);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
964
1117
|
async dump(notice?: string): Promise<OutputSummary> {
|
|
965
1118
|
const noticeLine = notice ? `[${notice}]\n` : "";
|
|
966
1119
|
|
|
@@ -973,7 +1126,10 @@ export class OutputSink {
|
|
|
973
1126
|
}
|
|
974
1127
|
const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
|
|
975
1128
|
|
|
976
|
-
if (this.#file)
|
|
1129
|
+
if (this.#file) {
|
|
1130
|
+
this.#flushArtifactTailIfCapped();
|
|
1131
|
+
await this.#file.sink.end();
|
|
1132
|
+
}
|
|
977
1133
|
|
|
978
1134
|
// Compose the visible output. With head retention, splice head + marker
|
|
979
1135
|
// + tail when content was elided. Otherwise return the rolling buffer.
|
|
@@ -5,6 +5,30 @@ import type { AcpBuiltinSlashCommandResult, SlashCommandRuntime } from "./types"
|
|
|
5
5
|
|
|
6
6
|
export type { AcpBuiltinSlashCommandResult } from "./types";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* All names (primary + aliases) that are reserved by ACP builtins. Used to
|
|
10
|
+
* filter out extension commands that would shadow a builtin or its alias at
|
|
11
|
+
* dispatch time (e.g. `models` is an alias for `/model`, so an extension
|
|
12
|
+
* registering `models` would appear in the palette but execute the builtin).
|
|
13
|
+
*/
|
|
14
|
+
export const ACP_BUILTIN_RESERVED_NAMES: ReadonlySet<string> = new Set(
|
|
15
|
+
BUILTIN_SLASH_COMMANDS_INTERNAL.filter(c => c.handle !== undefined).flatMap(c => [c.name, ...(c.aliases ?? [])]),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether an extension command named `name` would be captured by ACP builtin
|
|
20
|
+
* dispatch before reaching the extension handler. Beyond exact name/alias
|
|
21
|
+
* collisions, `parseSlashCommand` treats `:` as a name/args separator, so a
|
|
22
|
+
* colon-namespaced name whose prefix is a handled builtin (e.g. `model:foo`)
|
|
23
|
+
* executes the `/model` builtin with `foo` as args. Such names must not be
|
|
24
|
+
* advertised to ACP clients.
|
|
25
|
+
*/
|
|
26
|
+
export function isAcpBuiltinShadowedName(name: string): boolean {
|
|
27
|
+
if (ACP_BUILTIN_RESERVED_NAMES.has(name)) return true;
|
|
28
|
+
const colon = name.indexOf(":");
|
|
29
|
+
return colon !== -1 && ACP_BUILTIN_RESERVED_NAMES.has(name.slice(0, colon));
|
|
30
|
+
}
|
|
31
|
+
|
|
8
32
|
/**
|
|
9
33
|
* Commands advertised to ACP clients. Entries without a text-mode `handle`
|
|
10
34
|
* (e.g. `/quit`, `/login`, dashboards) are filtered out so the client doesn't
|
|
@@ -82,6 +82,23 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
82
82
|
runtime.ctx.editor.setText("");
|
|
83
83
|
},
|
|
84
84
|
},
|
|
85
|
+
{
|
|
86
|
+
name: "setup",
|
|
87
|
+
aliases: ["providers"],
|
|
88
|
+
description: "Open provider setup",
|
|
89
|
+
allowArgs: true,
|
|
90
|
+
subcommands: [{ name: "providers", description: "Configure sign-in and web search providers" }],
|
|
91
|
+
handleTui: async (command, runtime) => {
|
|
92
|
+
const args = command.args.trim().toLowerCase();
|
|
93
|
+
const opensProviders = args === "" || args === "providers";
|
|
94
|
+
if (opensProviders) {
|
|
95
|
+
await runtime.ctx.showProviderSetup();
|
|
96
|
+
} else {
|
|
97
|
+
runtime.ctx.showWarning(`Usage: /${command.name} [providers]`);
|
|
98
|
+
}
|
|
99
|
+
runtime.ctx.editor.setText("");
|
|
100
|
+
},
|
|
101
|
+
},
|
|
85
102
|
{
|
|
86
103
|
name: "plan",
|
|
87
104
|
description: "Toggle plan mode (agent plans before executing)",
|
|
@@ -1697,10 +1714,13 @@ for (const command of BUILTIN_SLASH_COMMAND_REGISTRY) {
|
|
|
1697
1714
|
}
|
|
1698
1715
|
}
|
|
1699
1716
|
|
|
1717
|
+
export const BUILTIN_SLASH_COMMAND_RESERVED_NAMES: ReadonlySet<string> = new Set(BUILTIN_SLASH_COMMAND_LOOKUP.keys());
|
|
1718
|
+
|
|
1700
1719
|
/** Builtin command metadata used for slash-command autocomplete and help text. */
|
|
1701
1720
|
export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = BUILTIN_SLASH_COMMAND_REGISTRY.map(
|
|
1702
1721
|
command => ({
|
|
1703
1722
|
name: command.name,
|
|
1723
|
+
aliases: command.aliases,
|
|
1704
1724
|
description: command.description,
|
|
1705
1725
|
subcommands: command.subcommands,
|
|
1706
1726
|
inlineHint: command.inlineHint,
|
|
@@ -14,6 +14,7 @@ export interface SubcommandDef {
|
|
|
14
14
|
/** Declarative builtin slash command metadata used by autocomplete and help UI. */
|
|
15
15
|
export interface BuiltinSlashCommand {
|
|
16
16
|
name: string;
|
|
17
|
+
aliases?: string[];
|
|
17
18
|
description: string;
|
|
18
19
|
/** Subcommands for dropdown completion (e.g. /mcp add, /mcp list). */
|
|
19
20
|
subcommands?: SubcommandDef[];
|
|
@@ -82,7 +83,6 @@ export interface TuiSlashCommandRuntime {
|
|
|
82
83
|
|
|
83
84
|
/** Unified slash-command spec consumed by both TUI and ACP dispatchers. */
|
|
84
85
|
export interface SlashCommandSpec extends BuiltinSlashCommand {
|
|
85
|
-
aliases?: string[];
|
|
86
86
|
/** When false, the dispatcher refuses to handle invocations that include arguments. */
|
|
87
87
|
allowArgs?: boolean;
|
|
88
88
|
/**
|
package/src/system-prompt.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger, prom
|
|
|
8
8
|
import { $ } from "bun";
|
|
9
9
|
import { contextFileCapability } from "./capability/context-file";
|
|
10
10
|
import { systemPromptCapability } from "./capability/system-prompt";
|
|
11
|
+
import { findConfigFile } from "./config";
|
|
11
12
|
import type { SkillsSettings } from "./config/settings";
|
|
12
13
|
import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
|
|
13
14
|
import { expandAtImports } from "./discovery/at-imports";
|
|
@@ -208,6 +209,19 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
|
|
|
208
209
|
return entries.filter((e): e is { label: string; value: string } => !!e.value);
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
/** Discover TITLE_SYSTEM.md file for automatic session-title prompt overrides */
|
|
213
|
+
export function discoverTitleSystemPromptFile(cwd?: string): string | undefined {
|
|
214
|
+
const projectPath = findConfigFile("TITLE_SYSTEM.md", { user: false, cwd });
|
|
215
|
+
if (projectPath) {
|
|
216
|
+
return projectPath;
|
|
217
|
+
}
|
|
218
|
+
const globalPath = findConfigFile("TITLE_SYSTEM.md", { user: true, cwd });
|
|
219
|
+
if (globalPath) {
|
|
220
|
+
return globalPath;
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
211
225
|
/** Resolve input as file path or literal string */
|
|
212
226
|
export async function resolvePromptInput(input: string | undefined, description: string): Promise<string | undefined> {
|
|
213
227
|
if (!input) {
|
package/src/task/executor.ts
CHANGED
|
@@ -162,6 +162,7 @@ export interface ExecutorOptions {
|
|
|
162
162
|
description?: string;
|
|
163
163
|
index: number;
|
|
164
164
|
id: string;
|
|
165
|
+
parentToolCallId?: string;
|
|
165
166
|
modelOverride?: string | string[];
|
|
166
167
|
/**
|
|
167
168
|
* Active model selector of the parent session, used as an auth-aware fallback
|
|
@@ -840,6 +841,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
840
841
|
agent: agent.name,
|
|
841
842
|
agentSource: agent.source,
|
|
842
843
|
task,
|
|
844
|
+
parentToolCallId: options.parentToolCallId,
|
|
843
845
|
assignment,
|
|
844
846
|
progress: { ...progress },
|
|
845
847
|
sessionFile: subtaskSessionFile,
|
|
@@ -922,20 +924,16 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
922
924
|
progress.recentOutput = [];
|
|
923
925
|
};
|
|
924
926
|
|
|
927
|
+
const emitSubagentEvent = (event: AgentSessionEvent) => {
|
|
928
|
+
if (!options.eventBus) return;
|
|
929
|
+
options.eventBus.emit(TASK_SUBAGENT_EVENT_CHANNEL, {
|
|
930
|
+
id,
|
|
931
|
+
event,
|
|
932
|
+
});
|
|
933
|
+
};
|
|
934
|
+
|
|
925
935
|
const processEvent = (event: AgentEvent) => {
|
|
926
936
|
if (resolved) return;
|
|
927
|
-
|
|
928
|
-
if (options.eventBus) {
|
|
929
|
-
options.eventBus.emit(TASK_SUBAGENT_EVENT_CHANNEL, {
|
|
930
|
-
index,
|
|
931
|
-
agent: agent.name,
|
|
932
|
-
agentSource: agent.source,
|
|
933
|
-
task,
|
|
934
|
-
assignment,
|
|
935
|
-
event,
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
|
|
939
937
|
const now = Date.now();
|
|
940
938
|
let flushProgress = false;
|
|
941
939
|
|
|
@@ -1354,6 +1352,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1354
1352
|
options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
1355
1353
|
id,
|
|
1356
1354
|
agent: agent.name,
|
|
1355
|
+
parentToolCallId: options.parentToolCallId,
|
|
1357
1356
|
agentSource: agent.source,
|
|
1358
1357
|
description: options.description,
|
|
1359
1358
|
status: "started",
|
|
@@ -1452,6 +1451,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1452
1451
|
|
|
1453
1452
|
const MAX_YIELD_RETRIES = 3;
|
|
1454
1453
|
unsubscribe = session.subscribe(event => {
|
|
1454
|
+
emitSubagentEvent(event);
|
|
1455
1455
|
if (event.type === "auto_retry_start") {
|
|
1456
1456
|
progress.retryState = {
|
|
1457
1457
|
attempt: event.attempt,
|
|
@@ -1704,6 +1704,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1704
1704
|
options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
1705
1705
|
id,
|
|
1706
1706
|
agent: agent.name,
|
|
1707
|
+
parentToolCallId: options.parentToolCallId,
|
|
1707
1708
|
agentSource: agent.source,
|
|
1708
1709
|
description: options.description,
|
|
1709
1710
|
status: progress.status as "completed" | "failed" | "aborted",
|
package/src/task/index.ts
CHANGED
|
@@ -119,6 +119,7 @@ export type {
|
|
|
119
119
|
AgentDefinition,
|
|
120
120
|
AgentProgress,
|
|
121
121
|
SingleResult,
|
|
122
|
+
SubagentEventPayload,
|
|
122
123
|
SubagentLifecyclePayload,
|
|
123
124
|
SubagentProgressPayload,
|
|
124
125
|
TaskParams,
|
|
@@ -412,7 +413,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
async execute(
|
|
415
|
-
|
|
416
|
+
toolCallId: string,
|
|
416
417
|
rawParams: unknown,
|
|
417
418
|
signal?: AbortSignal,
|
|
418
419
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
@@ -427,7 +428,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
427
428
|
const asyncEnabled = this.session.settings.get("async.enabled");
|
|
428
429
|
const selectedAgent = this.#discoveredAgents.find(agent => agent.name === params.agent);
|
|
429
430
|
if (!asyncEnabled || selectedAgent?.blocking === true) {
|
|
430
|
-
return this.#executeSync(
|
|
431
|
+
return this.#executeSync(toolCallId, params, signal, onUpdate);
|
|
431
432
|
}
|
|
432
433
|
|
|
433
434
|
const manager = this.session.asyncJobManager;
|
|
@@ -437,12 +438,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
437
438
|
// to the sync path keeps the tool usable; only background/job-poll
|
|
438
439
|
// semantics are lost.
|
|
439
440
|
logger.warn("task: async.enabled but no AsyncJobManager registered; falling back to sync execution");
|
|
440
|
-
return this.#executeSync(
|
|
441
|
+
return this.#executeSync(toolCallId, params, signal, onUpdate);
|
|
441
442
|
}
|
|
442
443
|
|
|
443
444
|
const taskItems = params.tasks ?? [];
|
|
444
445
|
if (taskItems.length === 0) {
|
|
445
|
-
return this.#executeSync(
|
|
446
|
+
return this.#executeSync(toolCallId, params, signal, onUpdate);
|
|
446
447
|
}
|
|
447
448
|
|
|
448
449
|
const taskIdProblem = validateTaskIds(taskItems);
|
|
@@ -552,9 +553,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
552
553
|
buildAsyncDetails("running", startedJobs[0]?.jobId ?? label) as unknown as Record<string, unknown>,
|
|
553
554
|
);
|
|
554
555
|
try {
|
|
555
|
-
const result = await this.#executeSync(
|
|
556
|
-
uniqueId,
|
|
557
|
-
]);
|
|
556
|
+
const result = await this.#executeSync(toolCallId, singleParams, runSignal, undefined, [uniqueId]);
|
|
558
557
|
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
559
558
|
const singleResult = result.details?.results[0];
|
|
560
559
|
// A missing per-task result means #executeSync failed at the
|
|
@@ -707,7 +706,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
707
706
|
}
|
|
708
707
|
|
|
709
708
|
async #executeSync(
|
|
710
|
-
|
|
709
|
+
toolCallId: string,
|
|
711
710
|
params: TaskParams,
|
|
712
711
|
signal?: AbortSignal,
|
|
713
712
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
@@ -1035,6 +1034,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1035
1034
|
planReference,
|
|
1036
1035
|
description: task.description,
|
|
1037
1036
|
index,
|
|
1037
|
+
parentToolCallId: toolCallId,
|
|
1038
1038
|
id: task.id,
|
|
1039
1039
|
taskDepth,
|
|
1040
1040
|
modelOverride,
|
|
@@ -1097,6 +1097,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1097
1097
|
planReference,
|
|
1098
1098
|
description: task.description,
|
|
1099
1099
|
index,
|
|
1100
|
+
parentToolCallId: toolCallId,
|
|
1100
1101
|
id: task.id,
|
|
1101
1102
|
taskDepth,
|
|
1102
1103
|
modelOverride,
|
package/src/task/render.ts
CHANGED
|
@@ -1072,6 +1072,20 @@ function renderAgentResult(
|
|
|
1072
1072
|
return lines;
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
|
+
/**
|
|
1076
|
+
* Order live progress entries so finished agents render first and unfinished
|
|
1077
|
+
* (pending/running) ones stay pinned at the bottom as tasks complete. Stable
|
|
1078
|
+
* within each group, so agents keep their dispatch order.
|
|
1079
|
+
*/
|
|
1080
|
+
function orderProgressForDisplay(progress: readonly AgentProgress[]): AgentProgress[] {
|
|
1081
|
+
const finished: AgentProgress[] = [];
|
|
1082
|
+
const unfinished: AgentProgress[] = [];
|
|
1083
|
+
for (const p of progress) {
|
|
1084
|
+
(p.status === "pending" || p.status === "running" ? unfinished : finished).push(p);
|
|
1085
|
+
}
|
|
1086
|
+
return finished.concat(unfinished);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1075
1089
|
/**
|
|
1076
1090
|
* Render the tool result.
|
|
1077
1091
|
*/
|
|
@@ -1140,7 +1154,7 @@ export function renderResult(
|
|
|
1140
1154
|
const shouldRenderProgress =
|
|
1141
1155
|
Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
|
|
1142
1156
|
if (shouldRenderProgress && details.progress) {
|
|
1143
|
-
details.progress.forEach(progress => {
|
|
1157
|
+
orderProgressForDisplay(details.progress).forEach(progress => {
|
|
1144
1158
|
lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame));
|
|
1145
1159
|
});
|
|
1146
1160
|
} else if (details.results && details.results.length > 0) {
|
|
@@ -1269,8 +1283,9 @@ function renderNestedTaskTree(
|
|
|
1269
1283
|
}
|
|
1270
1284
|
const inflight = details.progress;
|
|
1271
1285
|
if (inflight && inflight.length > 0) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1286
|
+
const ordered = orderProgressForDisplay(inflight);
|
|
1287
|
+
ordered.forEach((prog, index) => {
|
|
1288
|
+
const { prefix, continuePrefix } = nestedMarkers(index === ordered.length - 1, theme);
|
|
1274
1289
|
lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame));
|
|
1275
1290
|
});
|
|
1276
1291
|
}
|
package/src/task/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
|
+
import type { AgentSessionEvent } from "../session/agent-session";
|
|
5
6
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
6
7
|
import type { NestedRepoPatch } from "./worktree";
|
|
7
8
|
|
|
@@ -41,11 +42,18 @@ export interface SubagentProgressPayload {
|
|
|
41
42
|
agent: string;
|
|
42
43
|
agentSource: AgentSource;
|
|
43
44
|
task: string;
|
|
45
|
+
parentToolCallId?: string;
|
|
44
46
|
assignment?: string;
|
|
45
47
|
progress: AgentProgress;
|
|
46
48
|
sessionFile?: string;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
/** Payload emitted on TASK_SUBAGENT_EVENT_CHANNEL */
|
|
52
|
+
export interface SubagentEventPayload {
|
|
53
|
+
id: string;
|
|
54
|
+
event: AgentSessionEvent;
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
/** Payload emitted on TASK_SUBAGENT_LIFECYCLE_CHANNEL */
|
|
50
58
|
export interface SubagentLifecyclePayload {
|
|
51
59
|
id: string;
|
|
@@ -54,6 +62,7 @@ export interface SubagentLifecyclePayload {
|
|
|
54
62
|
description?: string;
|
|
55
63
|
status: "started" | "completed" | "failed" | "aborted";
|
|
56
64
|
sessionFile?: string;
|
|
65
|
+
parentToolCallId?: string;
|
|
57
66
|
index: number;
|
|
58
67
|
}
|
|
59
68
|
|
package/src/thinking.ts
CHANGED
|
@@ -71,6 +71,13 @@ export function toReasoningEffort(level: ThinkingLevel | undefined): Effort | un
|
|
|
71
71
|
return level;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* True when a selector explicitly requests provider-side reasoning disablement.
|
|
76
|
+
*/
|
|
77
|
+
export function shouldDisableReasoning(level: ThinkingLevel | undefined): boolean {
|
|
78
|
+
return level === ThinkingLevel.Off;
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
/**
|
|
75
82
|
* Resolves a selector against the current model while preserving explicit "off".
|
|
76
83
|
*/
|
package/src/tiny/title-client.ts
CHANGED
|
@@ -39,6 +39,17 @@ export interface TinyTitleDownloadOptions {
|
|
|
39
39
|
onProgress?: (event: TinyTitleProgressEvent) => void;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Per-request controls for {@link TinyTitleClient.generate}.
|
|
44
|
+
*
|
|
45
|
+
* Carries the optional abort signal and title-system-prompt override used by
|
|
46
|
+
* callers that customize automatic session-title generation.
|
|
47
|
+
*/
|
|
48
|
+
export interface TinyTitleGenerateOptions {
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
systemPrompt?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
// Cold-starting the worker subprocess from a compiled binary (decompress + module
|
|
43
54
|
// graph load) is slow on contended CI runners — the macos-15-intel release smoke
|
|
44
55
|
// blew past 5s while arm64/linux/win passed. The probe only needs to prove the
|
|
@@ -46,6 +57,14 @@ export interface TinyTitleDownloadOptions {
|
|
|
46
57
|
// generous bound removes the flake without weakening the check.
|
|
47
58
|
const SMOKE_TEST_TIMEOUT_MS = 30_000;
|
|
48
59
|
|
|
60
|
+
function normalizeTinyTitleGenerateOptions(
|
|
61
|
+
options: AbortSignal | TinyTitleGenerateOptions | undefined,
|
|
62
|
+
): TinyTitleGenerateOptions {
|
|
63
|
+
if (!options) return {};
|
|
64
|
+
if ("aborted" in options && "addEventListener" in options) return { signal: options };
|
|
65
|
+
return options;
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
/**
|
|
50
69
|
* Hidden subcommand on the main CLI that boots the tiny-model worker in the
|
|
51
70
|
* spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
|
|
@@ -295,9 +314,16 @@ export class TinyTitleClient {
|
|
|
295
314
|
return () => this.#progressListeners.delete(listener);
|
|
296
315
|
}
|
|
297
316
|
|
|
298
|
-
async generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null
|
|
317
|
+
async generate(modelKey: string, message: string, signal?: AbortSignal): Promise<string | null>;
|
|
318
|
+
async generate(modelKey: string, message: string, options?: TinyTitleGenerateOptions): Promise<string | null>;
|
|
319
|
+
async generate(
|
|
320
|
+
modelKey: string,
|
|
321
|
+
message: string,
|
|
322
|
+
optionsOrSignal?: AbortSignal | TinyTitleGenerateOptions,
|
|
323
|
+
): Promise<string | null> {
|
|
324
|
+
const options = normalizeTinyTitleGenerateOptions(optionsOrSignal);
|
|
299
325
|
if (!isTinyTitleLocalModelKey(modelKey)) return null;
|
|
300
|
-
if (signal?.aborted) return null;
|
|
326
|
+
if (options.signal?.aborted) return null;
|
|
301
327
|
|
|
302
328
|
try {
|
|
303
329
|
const worker = this.#ensureWorker();
|
|
@@ -310,12 +336,15 @@ export class TinyTitleClient {
|
|
|
310
336
|
this.#pending.delete(id);
|
|
311
337
|
pending.resolve(null);
|
|
312
338
|
};
|
|
313
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
339
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
314
340
|
try {
|
|
315
|
-
|
|
341
|
+
const request: TinyTitleWorkerInbound = options.systemPrompt
|
|
342
|
+
? { type: "generate", id, modelKey, message, systemPrompt: options.systemPrompt }
|
|
343
|
+
: { type: "generate", id, modelKey, message };
|
|
344
|
+
worker.send(request);
|
|
316
345
|
return await promise;
|
|
317
346
|
} finally {
|
|
318
|
-
signal?.removeEventListener("abort", abort);
|
|
347
|
+
options.signal?.removeEventListener("abort", abort);
|
|
319
348
|
this.#pending.delete(id);
|
|
320
349
|
}
|
|
321
350
|
} catch (error) {
|