@code-yeongyu/senpi 2026.5.15 → 2026.5.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 +1113 -1177
- package/README.md +1 -2
- package/dist/core/agent-session.d.ts +9 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +114 -8
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/dynamic-prompt/verification.d.ts +31 -0
- package/dist/core/dynamic-prompt/verification.d.ts.map +1 -1
- package/dist/core/dynamic-prompt/verification.js +41 -0
- package/dist/core/dynamic-prompt/verification.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/context-reduction.d.ts +97 -0
- package/dist/core/extensions/builtin/compaction/context-reduction.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/context-reduction.js +420 -0
- package/dist/core/extensions/builtin/compaction/context-reduction.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.js +168 -31
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +197 -0
- package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote.js +690 -0
- package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/prompts.d.ts +3 -3
- package/dist/core/extensions/builtin/compaction/prompts.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/prompts.js +0 -22
- package/dist/core/extensions/builtin/compaction/prompts.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts +4 -0
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js +48 -0
- package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/speculative.d.ts +3 -1
- package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/speculative.js +80 -33
- package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts +8 -0
- package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/todo-bridge.js +12 -6
- package/dist/core/extensions/builtin/compaction/todo-bridge.js.map +1 -1
- package/dist/core/extensions/builtin/diff.d.ts.map +1 -1
- package/dist/core/extensions/builtin/diff.js +1 -1
- package/dist/core/extensions/builtin/diff.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +0 -2
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/openai-web-search/index.d.ts +6 -2
- package/dist/core/extensions/builtin/openai-web-search/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/openai-web-search/index.js +82 -10
- package/dist/core/extensions/builtin/openai-web-search/index.js.map +1 -1
- package/dist/core/extensions/builtin/permission-system/prompt.d.ts.map +1 -1
- package/dist/core/extensions/builtin/permission-system/prompt.js +0 -5
- package/dist/core/extensions/builtin/permission-system/prompt.js.map +1 -1
- package/dist/core/extensions/builtin/system-messages.d.ts +1 -1
- package/dist/core/extensions/builtin/system-messages.d.ts.map +1 -1
- package/dist/core/extensions/builtin/system-messages.js.map +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/index.js +8 -4
- package/dist/core/extensions/builtin/tool-pair-guard/index.js.map +1 -1
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js +89 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js +122 -0
- package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +2 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +18 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +22 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/messages.d.ts +3 -3
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +5 -10
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +7 -22
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +0 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/thinking-levels.d.ts +6 -0
- package/dist/core/thinking-levels.d.ts.map +1 -0
- package/dist/core/thinking-levels.js +36 -0
- package/dist/core/thinking-levels.js.map +1 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +15 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +20 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +3 -1
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +8 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +137 -49
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/working-status.d.ts +15 -0
- package/dist/modes/interactive/working-status.d.ts.map +1 -0
- package/dist/modes/interactive/working-status.js +60 -0
- package/dist/modes/interactive/working-status.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/docs/extensions.md +0 -1
- package/docs/index.md +0 -1
- package/docs/models.md +9 -0
- package/docs/sdk.md +0 -1
- package/docs/settings.md +1 -29
- package/docs/termux.md +2 -2
- package/docs/usage.md +1 -1
- package/examples/README.md +1 -1
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/overlay-qa-tests.ts +1 -1
- package/package.json +4 -4
- package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts +0 -10
- package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/cancel-tool.js +0 -109
- package/dist/core/extensions/builtin/background-task/cancel-tool.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/index.d.ts +0 -3
- package/dist/core/extensions/builtin/background-task/index.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/index.js +0 -207
- package/dist/core/extensions/builtin/background-task/index.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/manager.d.ts +0 -17
- package/dist/core/extensions/builtin/background-task/manager.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/manager.js +0 -114
- package/dist/core/extensions/builtin/background-task/manager.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/notification.d.ts +0 -22
- package/dist/core/extensions/builtin/background-task/notification.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/notification.js +0 -105
- package/dist/core/extensions/builtin/background-task/notification.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/output-tool.d.ts +0 -11
- package/dist/core/extensions/builtin/background-task/output-tool.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/output-tool.js +0 -127
- package/dist/core/extensions/builtin/background-task/output-tool.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/spawner.d.ts +0 -8
- package/dist/core/extensions/builtin/background-task/spawner.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/spawner.js +0 -207
- package/dist/core/extensions/builtin/background-task/spawner.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/task-tool.d.ts +0 -20
- package/dist/core/extensions/builtin/background-task/task-tool.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/task-tool.js +0 -302
- package/dist/core/extensions/builtin/background-task/task-tool.js.map +0 -1
- package/dist/core/extensions/builtin/background-task/types.d.ts +0 -72
- package/dist/core/extensions/builtin/background-task/types.d.ts.map +0 -1
- package/dist/core/extensions/builtin/background-task/types.js +0 -32
- package/dist/core/extensions/builtin/background-task/types.js.map +0 -1
- package/docs/agents.md +0 -348
- package/examples/extensions/subagent/README.md +0 -172
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -987
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type WorkingStatusTextFrameStyle = {
|
|
2
|
+
base: (text: string) => string;
|
|
3
|
+
glow: (text: string) => string;
|
|
4
|
+
highlight: (text: string) => string;
|
|
5
|
+
shimmer?: (text: string, intensity: number) => string;
|
|
6
|
+
};
|
|
7
|
+
type WorkingStatusMessageFrameStyle = WorkingStatusTextFrameStyle & {
|
|
8
|
+
suffix: (text: string) => string;
|
|
9
|
+
};
|
|
10
|
+
export declare function formatWorkingElapsedSeconds(elapsedSeconds: number): string;
|
|
11
|
+
export declare function formatWorkingStatusMessage(message: string, elapsedSeconds: number, interruptKey: string): string;
|
|
12
|
+
export declare function formatWorkingStatusTextFrame(statusMessage: string, animationElapsedMs: number, style: WorkingStatusTextFrameStyle): string;
|
|
13
|
+
export declare function formatWorkingStatusMessageFrame(message: string, elapsedSeconds: number, interruptKey: string, animationElapsedMs: number, style: WorkingStatusMessageFrameStyle): string;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=working-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"working-status.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/working-status.ts"],"names":[],"mappings":"AAIA,KAAK,2BAA2B,GAAG;IAClC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/B,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD,CAAC;AAEF,KAAK,8BAA8B,GAAG,2BAA2B,GAAG;IACnE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACjC,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAa1E;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEhH;AAED,wBAAgB,4BAA4B,CAC3C,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,KAAK,EAAE,2BAA2B,GAChC,MAAM,CAsCR;AAED,wBAAgB,+BAA+B,CAC9C,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,EAC1B,KAAK,EAAE,8BAA8B,GACnC,MAAM,CAGR","sourcesContent":["const WORKING_STATUS_MESSAGE_SHIMMER_PADDING = 10;\nconst WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH = 5;\nconst WORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS = 2_000;\n\ntype WorkingStatusTextFrameStyle = {\n\tbase: (text: string) => string;\n\tglow: (text: string) => string;\n\thighlight: (text: string) => string;\n\tshimmer?: (text: string, intensity: number) => string;\n};\n\ntype WorkingStatusMessageFrameStyle = WorkingStatusTextFrameStyle & {\n\tsuffix: (text: string) => string;\n};\n\nexport function formatWorkingElapsedSeconds(elapsedSeconds: number): string {\n\tconst totalSeconds = Math.max(0, Math.floor(elapsedSeconds));\n\tconst seconds = totalSeconds % 60;\n\tconst totalMinutes = Math.floor(totalSeconds / 60);\n\tif (totalSeconds < 60) {\n\t\treturn `${totalSeconds}s`;\n\t}\n\tif (totalSeconds < 3600) {\n\t\treturn `${totalMinutes}m ${seconds.toString().padStart(2, \"0\")}s`;\n\t}\n\tconst hours = Math.floor(totalMinutes / 60);\n\tconst minutes = totalMinutes % 60;\n\treturn `${hours}h ${minutes.toString().padStart(2, \"0\")}m ${seconds.toString().padStart(2, \"0\")}s`;\n}\n\nexport function formatWorkingStatusMessage(message: string, elapsedSeconds: number, interruptKey: string): string {\n\treturn `${message} (${formatWorkingElapsedSeconds(elapsedSeconds)} • ${interruptKey} to interrupt)`;\n}\n\nexport function formatWorkingStatusTextFrame(\n\tstatusMessage: string,\n\tanimationElapsedMs: number,\n\tstyle: WorkingStatusTextFrameStyle,\n): string {\n\tconst chars = Array.from(statusMessage);\n\tif (chars.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst period = chars.length + WORKING_STATUS_MESSAGE_SHIMMER_PADDING * 2;\n\tconst sweepProgress =\n\t\t((Math.max(0, animationElapsedMs) % WORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS) /\n\t\t\tWORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS) *\n\t\tperiod;\n\n\treturn chars\n\t\t.map((char, index) => {\n\t\t\tif (char === \" \") {\n\t\t\t\treturn char;\n\t\t\t}\n\t\t\tconst distance = Math.abs(index + WORKING_STATUS_MESSAGE_SHIMMER_PADDING - sweepProgress);\n\t\t\tif (distance > WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH) {\n\t\t\t\tif (style.shimmer) {\n\t\t\t\t\treturn style.shimmer(char, 0);\n\t\t\t\t}\n\t\t\t\treturn style.base(char);\n\t\t\t}\n\n\t\t\tconst intensity = 0.5 * (1 + Math.cos(Math.PI * (distance / WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH)));\n\t\t\tif (style.shimmer) {\n\t\t\t\treturn style.shimmer(char, intensity);\n\t\t\t}\n\t\t\tif (intensity < 0.2) {\n\t\t\t\treturn style.base(char);\n\t\t\t}\n\t\t\tif (intensity < 0.6) {\n\t\t\t\treturn style.glow(char);\n\t\t\t}\n\t\t\treturn style.highlight(char);\n\t\t})\n\t\t.join(\"\");\n}\n\nexport function formatWorkingStatusMessageFrame(\n\tmessage: string,\n\telapsedSeconds: number,\n\tinterruptKey: string,\n\tanimationElapsedMs: number,\n\tstyle: WorkingStatusMessageFrameStyle,\n): string {\n\tconst suffix = ` (${formatWorkingElapsedSeconds(elapsedSeconds)} • ${interruptKey} to interrupt)`;\n\treturn `${formatWorkingStatusTextFrame(message, animationElapsedMs, style)}${style.suffix(suffix)}`;\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const WORKING_STATUS_MESSAGE_SHIMMER_PADDING = 10;
|
|
2
|
+
const WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH = 5;
|
|
3
|
+
const WORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS = 2_000;
|
|
4
|
+
export function formatWorkingElapsedSeconds(elapsedSeconds) {
|
|
5
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedSeconds));
|
|
6
|
+
const seconds = totalSeconds % 60;
|
|
7
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
8
|
+
if (totalSeconds < 60) {
|
|
9
|
+
return `${totalSeconds}s`;
|
|
10
|
+
}
|
|
11
|
+
if (totalSeconds < 3600) {
|
|
12
|
+
return `${totalMinutes}m ${seconds.toString().padStart(2, "0")}s`;
|
|
13
|
+
}
|
|
14
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
15
|
+
const minutes = totalMinutes % 60;
|
|
16
|
+
return `${hours}h ${minutes.toString().padStart(2, "0")}m ${seconds.toString().padStart(2, "0")}s`;
|
|
17
|
+
}
|
|
18
|
+
export function formatWorkingStatusMessage(message, elapsedSeconds, interruptKey) {
|
|
19
|
+
return `${message} (${formatWorkingElapsedSeconds(elapsedSeconds)} • ${interruptKey} to interrupt)`;
|
|
20
|
+
}
|
|
21
|
+
export function formatWorkingStatusTextFrame(statusMessage, animationElapsedMs, style) {
|
|
22
|
+
const chars = Array.from(statusMessage);
|
|
23
|
+
if (chars.length === 0) {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
const period = chars.length + WORKING_STATUS_MESSAGE_SHIMMER_PADDING * 2;
|
|
27
|
+
const sweepProgress = ((Math.max(0, animationElapsedMs) % WORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS) /
|
|
28
|
+
WORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS) *
|
|
29
|
+
period;
|
|
30
|
+
return chars
|
|
31
|
+
.map((char, index) => {
|
|
32
|
+
if (char === " ") {
|
|
33
|
+
return char;
|
|
34
|
+
}
|
|
35
|
+
const distance = Math.abs(index + WORKING_STATUS_MESSAGE_SHIMMER_PADDING - sweepProgress);
|
|
36
|
+
if (distance > WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH) {
|
|
37
|
+
if (style.shimmer) {
|
|
38
|
+
return style.shimmer(char, 0);
|
|
39
|
+
}
|
|
40
|
+
return style.base(char);
|
|
41
|
+
}
|
|
42
|
+
const intensity = 0.5 * (1 + Math.cos(Math.PI * (distance / WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH)));
|
|
43
|
+
if (style.shimmer) {
|
|
44
|
+
return style.shimmer(char, intensity);
|
|
45
|
+
}
|
|
46
|
+
if (intensity < 0.2) {
|
|
47
|
+
return style.base(char);
|
|
48
|
+
}
|
|
49
|
+
if (intensity < 0.6) {
|
|
50
|
+
return style.glow(char);
|
|
51
|
+
}
|
|
52
|
+
return style.highlight(char);
|
|
53
|
+
})
|
|
54
|
+
.join("");
|
|
55
|
+
}
|
|
56
|
+
export function formatWorkingStatusMessageFrame(message, elapsedSeconds, interruptKey, animationElapsedMs, style) {
|
|
57
|
+
const suffix = ` (${formatWorkingElapsedSeconds(elapsedSeconds)} • ${interruptKey} to interrupt)`;
|
|
58
|
+
return `${formatWorkingStatusTextFrame(message, animationElapsedMs, style)}${style.suffix(suffix)}`;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=working-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"working-status.js","sourceRoot":"","sources":["../../../src/modes/interactive/working-status.ts"],"names":[],"mappings":"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","sourcesContent":["const WORKING_STATUS_MESSAGE_SHIMMER_PADDING = 10;\nconst WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH = 5;\nconst WORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS = 2_000;\n\ntype WorkingStatusTextFrameStyle = {\n\tbase: (text: string) => string;\n\tglow: (text: string) => string;\n\thighlight: (text: string) => string;\n\tshimmer?: (text: string, intensity: number) => string;\n};\n\ntype WorkingStatusMessageFrameStyle = WorkingStatusTextFrameStyle & {\n\tsuffix: (text: string) => string;\n};\n\nexport function formatWorkingElapsedSeconds(elapsedSeconds: number): string {\n\tconst totalSeconds = Math.max(0, Math.floor(elapsedSeconds));\n\tconst seconds = totalSeconds % 60;\n\tconst totalMinutes = Math.floor(totalSeconds / 60);\n\tif (totalSeconds < 60) {\n\t\treturn `${totalSeconds}s`;\n\t}\n\tif (totalSeconds < 3600) {\n\t\treturn `${totalMinutes}m ${seconds.toString().padStart(2, \"0\")}s`;\n\t}\n\tconst hours = Math.floor(totalMinutes / 60);\n\tconst minutes = totalMinutes % 60;\n\treturn `${hours}h ${minutes.toString().padStart(2, \"0\")}m ${seconds.toString().padStart(2, \"0\")}s`;\n}\n\nexport function formatWorkingStatusMessage(message: string, elapsedSeconds: number, interruptKey: string): string {\n\treturn `${message} (${formatWorkingElapsedSeconds(elapsedSeconds)} • ${interruptKey} to interrupt)`;\n}\n\nexport function formatWorkingStatusTextFrame(\n\tstatusMessage: string,\n\tanimationElapsedMs: number,\n\tstyle: WorkingStatusTextFrameStyle,\n): string {\n\tconst chars = Array.from(statusMessage);\n\tif (chars.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst period = chars.length + WORKING_STATUS_MESSAGE_SHIMMER_PADDING * 2;\n\tconst sweepProgress =\n\t\t((Math.max(0, animationElapsedMs) % WORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS) /\n\t\t\tWORKING_STATUS_MESSAGE_SHIMMER_SWEEP_MS) *\n\t\tperiod;\n\n\treturn chars\n\t\t.map((char, index) => {\n\t\t\tif (char === \" \") {\n\t\t\t\treturn char;\n\t\t\t}\n\t\t\tconst distance = Math.abs(index + WORKING_STATUS_MESSAGE_SHIMMER_PADDING - sweepProgress);\n\t\t\tif (distance > WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH) {\n\t\t\t\tif (style.shimmer) {\n\t\t\t\t\treturn style.shimmer(char, 0);\n\t\t\t\t}\n\t\t\t\treturn style.base(char);\n\t\t\t}\n\n\t\t\tconst intensity = 0.5 * (1 + Math.cos(Math.PI * (distance / WORKING_STATUS_MESSAGE_SHIMMER_BAND_HALF_WIDTH)));\n\t\t\tif (style.shimmer) {\n\t\t\t\treturn style.shimmer(char, intensity);\n\t\t\t}\n\t\t\tif (intensity < 0.2) {\n\t\t\t\treturn style.base(char);\n\t\t\t}\n\t\t\tif (intensity < 0.6) {\n\t\t\t\treturn style.glow(char);\n\t\t\t}\n\t\t\treturn style.highlight(char);\n\t\t})\n\t\t.join(\"\");\n}\n\nexport function formatWorkingStatusMessageFrame(\n\tmessage: string,\n\telapsedSeconds: number,\n\tinterruptKey: string,\n\tanimationElapsedMs: number,\n\tstyle: WorkingStatusMessageFrameStyle,\n): string {\n\tconst suffix = ` (${formatWorkingElapsedSeconds(elapsedSeconds)} • ${interruptKey} to interrupt)`;\n\treturn `${formatWorkingStatusTextFrame(message, animationElapsedMs, style)}${style.suffix(suffix)}`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AASF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE9E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazE;AAmND,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA2CjC","sourcesContent":["import { spawnSync } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { clipboard } from \"./clipboard-native.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number; env?: NodeJS.ProcessEnv },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t\tenv: options?.env,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nfunction readClipboardImageViaPowerShell(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = runCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (!winPathResult.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.stdout.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psQuotedWinPath = winPath.replaceAll(\"'\", \"''\");\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t`$path = '${psQuotedWinPath}'`,\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = runCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t});\n\t\tif (!result.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.stdout.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null> {\n\tif (!clipboard
|
|
1
|
+
{"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AASF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE9E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazE;AAmND,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA2CjC","sourcesContent":["import { spawnSync } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { clipboard } from \"./clipboard-native.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number; env?: NodeJS.ProcessEnv },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t\tenv: options?.env,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nfunction readClipboardImageViaPowerShell(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = runCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (!winPathResult.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.stdout.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psQuotedWinPath = winPath.replaceAll(\"'\", \"''\");\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t`$path = '${psQuotedWinPath}'`,\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = runCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t});\n\t\tif (!result.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.stdout.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null> {\n\tif (!clipboard?.hasImage()) {\n\t\treturn null;\n\t}\n\n\tconst imageData = await clipboard.getImageBinary();\n\tif (!imageData || imageData.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\treturn { bytes, mimeType: \"image/png\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null = null;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tconst wayland = isWaylandSession(env);\n\n\t\tif (wayland || wsl) {\n\t\t\timage = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\n\t\tif (!image && wsl) {\n\t\t\timage = readClipboardImageViaPowerShell();\n\t\t}\n\n\t\tif (!image && !wayland) {\n\t\t\timage = await readClipboardImageViaNativeClipboard();\n\t\t}\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., BMP from WSLg) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
|
|
@@ -196,7 +196,7 @@ function readClipboardImageViaXclip() {
|
|
|
196
196
|
return null;
|
|
197
197
|
}
|
|
198
198
|
async function readClipboardImageViaNativeClipboard() {
|
|
199
|
-
if (!clipboard
|
|
199
|
+
if (!clipboard?.hasImage()) {
|
|
200
200
|
return null;
|
|
201
201
|
}
|
|
202
202
|
const imageData = await clipboard.getImageBinary();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { clipboard } from \"./clipboard-native.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number; env?: NodeJS.ProcessEnv },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t\tenv: options?.env,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nfunction readClipboardImageViaPowerShell(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = runCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (!winPathResult.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.stdout.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psQuotedWinPath = winPath.replaceAll(\"'\", \"''\");\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t`$path = '${psQuotedWinPath}'`,\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = runCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t});\n\t\tif (!result.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.stdout.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null> {\n\tif (!clipboard || !clipboard.hasImage()) {\n\t\treturn null;\n\t}\n\n\tconst imageData = await clipboard.getImageBinary();\n\tif (!imageData || imageData.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\treturn { bytes, mimeType: \"image/png\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null = null;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tconst wayland = isWaylandSession(env);\n\n\t\tif (wayland || wsl) {\n\t\t\timage = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\n\t\tif (!image && wsl) {\n\t\t\timage = readClipboardImageViaPowerShell();\n\t\t}\n\n\t\tif (!image && !wayland) {\n\t\t\timage = await readClipboardImageViaNativeClipboard();\n\t\t}\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., BMP from WSLg) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"clipboard-image.js","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { clipboard } from \"./clipboard-native.js\";\nimport { loadPhoton } from \"./photon.js\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number; env?: NodeJS.ProcessEnv },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t\tenv: options?.env,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nfunction readClipboardImageViaPowerShell(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = runCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (!winPathResult.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.stdout.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psQuotedWinPath = winPath.replaceAll(\"'\", \"''\");\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t`$path = '${psQuotedWinPath}'`,\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = runCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t});\n\t\tif (!result.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.stdout.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null> {\n\tif (!clipboard?.hasImage()) {\n\t\treturn null;\n\t}\n\n\tconst imageData = await clipboard.getImageBinary();\n\tif (!imageData || imageData.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\treturn { bytes, mimeType: \"image/png\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null = null;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tconst wayland = isWaylandSession(env);\n\n\t\tif (wayland || wsl) {\n\t\t\timage = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\n\t\tif (!image && wsl) {\n\t\t\timage = readClipboardImageViaPowerShell();\n\t\t}\n\n\t\tif (!image && !wayland) {\n\t\t\timage = await readClipboardImageViaNativeClipboard();\n\t\t}\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., BMP from WSLg) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
|
package/docs/extensions.md
CHANGED
|
@@ -2576,7 +2576,6 @@ All examples in [examples/extensions/](../examples/extensions/).
|
|
|
2576
2576
|
| `ssh.ts` | SSH remote execution | `registerFlag`, `on("user_bash")`, `on("before_agent_start")`, tool operations |
|
|
2577
2577
|
| `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
|
|
2578
2578
|
| `sandbox/` | Sandboxed tool execution | Tool operations |
|
|
2579
|
-
| `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
|
|
2580
2579
|
| **Games** |||
|
|
2581
2580
|
| `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
|
|
2582
2581
|
| `space-invaders.ts` | Space Invaders game | `registerCommand`, `ui.custom` |
|
package/docs/index.md
CHANGED
|
@@ -51,7 +51,6 @@ For the full first-run flow, see [Quickstart](quickstart.md).
|
|
|
51
51
|
## Reference
|
|
52
52
|
|
|
53
53
|
- [Session format](session-format.md) - JSONL session file format, entry types, and SessionManager API.
|
|
54
|
-
- [Agents](agents.md) - subagent profiles, custom agent definitions, and permission rules.
|
|
55
54
|
|
|
56
55
|
## Platform setup
|
|
57
56
|
|
package/docs/models.md
CHANGED
|
@@ -413,6 +413,15 @@ For providers with partial OpenAI compatibility, use the `compat` field.
|
|
|
413
413
|
| `openRouterRouting` | OpenRouter provider routing preferences. This object is sent as-is in the `provider` field of the [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |
|
|
414
414
|
| `vercelGatewayRouting` | Vercel AI Gateway routing config for provider selection (`only`, `order`) |
|
|
415
415
|
|
|
416
|
+
For `api: "openai-responses"` models, only Responses-specific `compat` fields apply:
|
|
417
|
+
|
|
418
|
+
| Field | Description |
|
|
419
|
+
|-------|-------------|
|
|
420
|
+
| `sendSessionIdHeader` | Send the OpenAI cache-affinity `session_id` header from `sessionId`. Default: `true`. |
|
|
421
|
+
| `supportsLongCacheRetention` | Accepts `prompt_cache_retention: "24h"` when cache retention is `long`. Default: `true`. |
|
|
422
|
+
| `supportsWebSocket` | Supports OpenAI Responses WebSocket transport. Default: `true` only for `api.openai.com`. |
|
|
423
|
+
| `supportsWebSearchPreview` | Supports OpenAI-native `web_search_preview` tools. Default: `true` only for `api.openai.com`; custom Responses proxies must opt in. |
|
|
424
|
+
|
|
416
425
|
`openrouter` uses `reasoning: { effort }`. `together` uses `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` uses top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that require `chat_template_kwargs.enable_thinking`.
|
|
417
426
|
|
|
418
427
|
`cacheControlFormat: "anthropic"` is for OpenAI-compatible providers that expose Anthropic-style prompt caching through `cache_control` markers on text content and tool definitions.
|
package/docs/sdk.md
CHANGED
|
@@ -8,7 +8,6 @@ The SDK provides programmatic access to pi's agent capabilities. Use it to embed
|
|
|
8
8
|
- Build a custom UI (web, desktop, mobile)
|
|
9
9
|
- Integrate agent capabilities into existing applications
|
|
10
10
|
- Create automated pipelines with agent reasoning
|
|
11
|
-
- Build custom tools that spawn sub-agents
|
|
12
11
|
- Test agent behavior programmatically
|
|
13
12
|
|
|
14
13
|
See [examples/sdk/](../examples/sdk/) for working examples from minimal to full control.
|
package/docs/settings.md
CHANGED
|
@@ -266,28 +266,6 @@ Object form filters which resources to load:
|
|
|
266
266
|
|
|
267
267
|
See [packages.md](packages.md) for package management details.
|
|
268
268
|
|
|
269
|
-
### Agent Defaults
|
|
270
|
-
|
|
271
|
-
| Setting | Type | Default | Description |
|
|
272
|
-
|---------|------|---------|-------------|
|
|
273
|
-
| `agentDefaults.permission` | object | `{}` | Default tool permissions applied to all agents (lowest priority) |
|
|
274
|
-
| `agentDefaults.model` | string | - | Default model ID for agents spawned via `task()` |
|
|
275
|
-
|
|
276
|
-
Permission values: `"allow"`, `"deny"`, `"ask"`. See [agents.md](agents.md) for details.
|
|
277
|
-
|
|
278
|
-
```json
|
|
279
|
-
{
|
|
280
|
-
"agentDefaults": {
|
|
281
|
-
"permission": {
|
|
282
|
-
"edit": "ask",
|
|
283
|
-
"write": "ask",
|
|
284
|
-
"bash": "allow"
|
|
285
|
-
},
|
|
286
|
-
"model": "anthropic/claude-haiku-4-5"
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
269
|
## Example
|
|
292
270
|
|
|
293
271
|
```json
|
|
@@ -310,13 +288,7 @@ Permission values: `"allow"`, `"deny"`, `"ask"`. See [agents.md](agents.md) for
|
|
|
310
288
|
"warnings": {
|
|
311
289
|
"anthropicExtraUsage": true
|
|
312
290
|
},
|
|
313
|
-
"packages": ["pi-skills"]
|
|
314
|
-
"agentDefaults": {
|
|
315
|
-
"permission": {
|
|
316
|
-
"edit": "ask",
|
|
317
|
-
"write": "ask"
|
|
318
|
-
}
|
|
319
|
-
}
|
|
291
|
+
"packages": ["pi-skills"]
|
|
320
292
|
}
|
|
321
293
|
```
|
|
322
294
|
|
package/docs/termux.md
CHANGED
|
@@ -36,7 +36,7 @@ Image clipboard is not supported on Termux (the `ctrl+v` image paste feature wil
|
|
|
36
36
|
|
|
37
37
|
Create `~/.senpi/agent/AGENTS.md` to help the agent understand the Termux environment:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
````markdown
|
|
40
40
|
# Agent Environment: Termux on Android
|
|
41
41
|
|
|
42
42
|
## Location
|
|
@@ -91,7 +91,7 @@ termux-camera-photo out.jpg # Take photo
|
|
|
91
91
|
- Termux:API app must be installed for `termux-*` commands
|
|
92
92
|
- Use `pkg install termux-api` for the command-line tools
|
|
93
93
|
- Storage permission needed for `/storage/emulated/0` access
|
|
94
|
-
|
|
94
|
+
````
|
|
95
95
|
|
|
96
96
|
## Limitations
|
|
97
97
|
|
package/docs/usage.md
CHANGED
|
@@ -272,6 +272,6 @@ pi --tools read,grep,find,ls -p "Review the code"
|
|
|
272
272
|
|
|
273
273
|
Pi keeps the core small and pushes workflow-specific behavior into extensions, skills, prompt templates, and packages.
|
|
274
274
|
|
|
275
|
-
It intentionally does not include built-in MCP,
|
|
275
|
+
It intentionally does not include built-in MCP, permission popups, plan mode, to-dos, or long-running shell orchestration. You can build or install those workflows as extensions or packages, or use external tools such as containers and tmux.
|
|
276
276
|
|
|
277
277
|
For the full rationale, read the [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/).
|
package/examples/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Programmatic usage via `createAgentSession()`. Shows how to customize models, pr
|
|
|
10
10
|
### [extensions/](extensions/)
|
|
11
11
|
Example extensions demonstrating:
|
|
12
12
|
- Lifecycle event handlers (tool interception, safety gates, context modifications)
|
|
13
|
-
- Custom tools (todo lists, questions,
|
|
13
|
+
- Custom tools (todo lists, questions, output truncation)
|
|
14
14
|
- Commands and keyboard shortcuts
|
|
15
15
|
- Custom UI (footers, headers, editors, overlays)
|
|
16
16
|
- Git integration (checkpoints, auto-commit)
|
|
@@ -39,7 +39,6 @@ cp permission-gate.ts ~/.senpi/agent/extensions/
|
|
|
39
39
|
| `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
|
|
40
40
|
| `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
|
|
41
41
|
| `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
|
|
42
|
-
| `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
|
|
43
42
|
|
|
44
43
|
### Commands & UI
|
|
45
44
|
|
|
@@ -469,7 +469,7 @@ class StreamingOverflowComponent extends BaseOverlay {
|
|
|
469
469
|
"-c",
|
|
470
470
|
`
|
|
471
471
|
echo "Starting streaming overflow test (30+ seconds)..."
|
|
472
|
-
echo "This simulates
|
|
472
|
+
echo "This simulates streaming output with colors, hyperlinks, and long paths"
|
|
473
473
|
echo ""
|
|
474
474
|
for i in $(seq 1 100); do
|
|
475
475
|
# Simulate long file paths with OSC 8 hyperlinks (clickable) - tests width overflow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.16",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@anthropic-ai/sdk": "^0.91.1",
|
|
43
|
-
"@earendil-works/pi-agent-core": "^2026.5.
|
|
44
|
-
"@earendil-works/pi-ai": "^2026.5.
|
|
45
|
-
"@earendil-works/pi-tui": "^2026.5.
|
|
43
|
+
"@earendil-works/pi-agent-core": "^2026.5.16",
|
|
44
|
+
"@earendil-works/pi-ai": "^2026.5.16",
|
|
45
|
+
"@earendil-works/pi-tui": "^2026.5.16",
|
|
46
46
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
47
47
|
"chalk": "^5.5.0",
|
|
48
48
|
"diff": "^8.0.2",
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import type { ToolDefinition } from "../../types.js";
|
|
3
|
-
import type { BackgroundManager } from "./manager.js";
|
|
4
|
-
declare const BackgroundCancelParams: Type.TObject<{
|
|
5
|
-
taskId: Type.TOptional<Type.TString>;
|
|
6
|
-
all: Type.TOptional<Type.TBoolean>;
|
|
7
|
-
}>;
|
|
8
|
-
export declare function createBackgroundCancelTool(manager: BackgroundManager): ToolDefinition<typeof BackgroundCancelParams>;
|
|
9
|
-
export {};
|
|
10
|
-
//# sourceMappingURL=cancel-tool.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cancel-tool.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/cancel-tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,QAAA,MAAM,sBAAsB;;;EAG1B,CAAC;AAEH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,OAAO,sBAAsB,CAAC,CAmHpH","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../types.js\";\nimport type { BackgroundManager } from \"./manager.js\";\n\nconst BackgroundCancelParams = Type.Object({\n\ttaskId: Type.Optional(Type.String({ description: \"Task ID to cancel (required if all=false)\" })),\n\tall: Type.Optional(Type.Boolean({ description: \"Cancel all running background tasks (default: false)\" })),\n});\n\nexport function createBackgroundCancelTool(manager: BackgroundManager): ToolDefinition<typeof BackgroundCancelParams> {\n\treturn {\n\t\tname: \"background_cancel\",\n\t\tlabel: \"BackgroundCancel\",\n\t\tdescription: \"Cancel a background task by taskId, or cancel all running tasks with all=true.\",\n\t\tpromptSnippet: \"Cancel background tasks by ID or cancel all running tasks.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool to cancel pending or running background tasks.\",\n\t\t\t\"Provide taskId to cancel a specific task, or set all=true to cancel all active tasks.\",\n\t\t\t\"Cancelled tasks will be marked as 'cancelled' and cannot be resumed.\",\n\t\t],\n\t\tparameters: BackgroundCancelParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n\t\t\tif (!params.taskId && !params.all) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t\tisError: true,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.all) {\n\t\t\t\tconst cancelled = manager.cancelAll();\n\n\t\t\t\tif (cancelled.length === 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No active tasks to cancel\" }],\n\t\t\t\t\t\tdetails: { cancelledTasks: [] },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst lines: string[] = [`Cancelled ${cancelled.length} task(s):`];\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tlines.push(`- ${task.id}: ${task.description}`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: lines.join(\"\\n\") }],\n\t\t\t\t\tdetails: { cancelledTasks: cancelled },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.taskId) {\n\t\t\t\tconst task = manager.getTask(params.taskId);\n\t\t\t\tif (!task) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Error: Task not found: ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst wasCancelled = manager.cancelTask(params.taskId);\n\n\t\t\t\tif (!wasCancelled) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Task ${params.taskId} is not active (status: ${task.status})` }],\n\t\t\t\t\t\tdetails: { task },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst cancelledTask = manager.getTask(params.taskId);\n\t\t\t\tif (!cancelledTask) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}: ${cancelledTask.description}` }],\n\t\t\t\t\tdetails: { task: cancelledTask },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\tdetails: undefined,\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tif (args.all) {\n\t\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", \"[all]\"), 0, 0);\n\t\t\t}\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", args.taskId ?? \"unknown\"),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst firstContent = result.content[0];\n\t\t\tconst text = firstContent?.type === \"text\" ? firstContent.text : \"(no output)\";\n\t\t\treturn new Text(theme.fg(\"muted\", text), 0, 0);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
2
|
-
import { Type } from "typebox";
|
|
3
|
-
const BackgroundCancelParams = Type.Object({
|
|
4
|
-
taskId: Type.Optional(Type.String({ description: "Task ID to cancel (required if all=false)" })),
|
|
5
|
-
all: Type.Optional(Type.Boolean({ description: "Cancel all running background tasks (default: false)" })),
|
|
6
|
-
});
|
|
7
|
-
export function createBackgroundCancelTool(manager) {
|
|
8
|
-
return {
|
|
9
|
-
name: "background_cancel",
|
|
10
|
-
label: "BackgroundCancel",
|
|
11
|
-
description: "Cancel a background task by taskId, or cancel all running tasks with all=true.",
|
|
12
|
-
promptSnippet: "Cancel background tasks by ID or cancel all running tasks.",
|
|
13
|
-
promptGuidelines: [
|
|
14
|
-
"Use this tool to cancel pending or running background tasks.",
|
|
15
|
-
"Provide taskId to cancel a specific task, or set all=true to cancel all active tasks.",
|
|
16
|
-
"Cancelled tasks will be marked as 'cancelled' and cannot be resumed.",
|
|
17
|
-
],
|
|
18
|
-
parameters: BackgroundCancelParams,
|
|
19
|
-
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
20
|
-
if (!params.taskId && !params.all) {
|
|
21
|
-
return {
|
|
22
|
-
content: [{ type: "text", text: "Error: Provide taskId or set all=true" }],
|
|
23
|
-
details: undefined,
|
|
24
|
-
isError: true,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
if (params.all) {
|
|
28
|
-
const cancelled = manager.cancelAll();
|
|
29
|
-
if (cancelled.length === 0) {
|
|
30
|
-
return {
|
|
31
|
-
content: [{ type: "text", text: "No active tasks to cancel" }],
|
|
32
|
-
details: { cancelledTasks: [] },
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
for (const task of cancelled) {
|
|
36
|
-
if (task.pid !== undefined) {
|
|
37
|
-
try {
|
|
38
|
-
process.kill(task.pid, "SIGTERM");
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
/* process may already be dead */
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const lines = [`Cancelled ${cancelled.length} task(s):`];
|
|
46
|
-
for (const task of cancelled) {
|
|
47
|
-
lines.push(`- ${task.id}: ${task.description}`);
|
|
48
|
-
}
|
|
49
|
-
return {
|
|
50
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
51
|
-
details: { cancelledTasks: cancelled },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
if (params.taskId) {
|
|
55
|
-
const task = manager.getTask(params.taskId);
|
|
56
|
-
if (!task) {
|
|
57
|
-
return {
|
|
58
|
-
content: [{ type: "text", text: `Error: Task not found: ${params.taskId}` }],
|
|
59
|
-
details: undefined,
|
|
60
|
-
isError: true,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
const wasCancelled = manager.cancelTask(params.taskId);
|
|
64
|
-
if (!wasCancelled) {
|
|
65
|
-
return {
|
|
66
|
-
content: [{ type: "text", text: `Task ${params.taskId} is not active (status: ${task.status})` }],
|
|
67
|
-
details: { task },
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
if (task.pid !== undefined) {
|
|
71
|
-
try {
|
|
72
|
-
process.kill(task.pid, "SIGTERM");
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
/* process may already be dead */
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const cancelledTask = manager.getTask(params.taskId);
|
|
79
|
-
if (!cancelledTask) {
|
|
80
|
-
return {
|
|
81
|
-
content: [{ type: "text", text: `Cancelled task ${params.taskId}` }],
|
|
82
|
-
details: undefined,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
content: [{ type: "text", text: `Cancelled task ${params.taskId}: ${cancelledTask.description}` }],
|
|
87
|
-
details: { task: cancelledTask },
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
content: [{ type: "text", text: "Error: Provide taskId or set all=true" }],
|
|
92
|
-
details: undefined,
|
|
93
|
-
isError: true,
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
renderCall(args, theme) {
|
|
97
|
-
if (args.all) {
|
|
98
|
-
return new Text(theme.fg("toolTitle", theme.bold("BackgroundCancel ")) + theme.fg("accent", "[all]"), 0, 0);
|
|
99
|
-
}
|
|
100
|
-
return new Text(theme.fg("toolTitle", theme.bold("BackgroundCancel ")) + theme.fg("accent", args.taskId ?? "unknown"), 0, 0);
|
|
101
|
-
},
|
|
102
|
-
renderResult(result, _options, theme) {
|
|
103
|
-
const firstContent = result.content[0];
|
|
104
|
-
const text = firstContent?.type === "text" ? firstContent.text : "(no output)";
|
|
105
|
-
return new Text(theme.fg("muted", text), 0, 0);
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
//# sourceMappingURL=cancel-tool.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cancel-tool.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/cancel-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAI/B,MAAM,sBAAsB,GAAG,IAAI,CAAC,MAAM,CAAC;IAC1C,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC,CAAC;IAChG,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,sDAAsD,EAAE,CAAC,CAAC;CACzG,CAAC,CAAC;AAEH,MAAM,UAAU,0BAA0B,CAAC,OAA0B,EAAiD;IACrH,OAAO;QACN,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,gFAAgF;QAC7F,aAAa,EAAE,4DAA4D;QAC3E,gBAAgB,EAAE;YACjB,8DAA8D;YAC9D,uFAAuF;YACvF,sEAAsE;SACtE;QACD,UAAU,EAAE,sBAAsB;QAClC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE;YAC5D,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;gBACnC,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,uCAAuC,EAAE,CAAC;oBAC1E,OAAO,EAAE,SAAS;oBAClB,OAAO,EAAE,IAAI;iBACb,CAAC;YACH,CAAC;YAED,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;gBAChB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;gBAEtC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5B,OAAO;wBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,EAAE,CAAC;wBAC9D,OAAO,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;qBAC/B,CAAC;gBACH,CAAC;gBAED,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;oBAC9B,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;wBAC5B,IAAI,CAAC;4BACJ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;wBACnC,CAAC;wBAAC,MAAM,CAAC;4BACR,iCAAiC;wBAClC,CAAC;oBACF,CAAC;gBACF,CAAC;gBAED,MAAM,KAAK,GAAa,CAAC,aAAa,SAAS,CAAC,MAAM,WAAW,CAAC,CAAC;gBACnE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;oBAC9B,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;gBACjD,CAAC;gBAED,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACnD,OAAO,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE;iBACtC,CAAC;YACH,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBACnB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;oBACX,OAAO;wBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;wBAC5E,OAAO,EAAE,SAAS;wBAClB,OAAO,EAAE,IAAI;qBACb,CAAC;gBACH,CAAC;gBAED,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAEvD,IAAI,CAAC,YAAY,EAAE,CAAC;oBACnB,OAAO;wBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,MAAM,CAAC,MAAM,2BAA2B,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;wBACjG,OAAO,EAAE,EAAE,IAAI,EAAE;qBACjB,CAAC;gBACH,CAAC;gBAED,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;oBAC5B,IAAI,CAAC;wBACJ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;oBACnC,CAAC;oBAAC,MAAM,CAAC;wBACR,iCAAiC;oBAClC,CAAC;gBACF,CAAC;gBAED,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACrD,IAAI,CAAC,aAAa,EAAE,CAAC;oBACpB,OAAO;wBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;wBACpE,OAAO,EAAE,SAAS;qBAClB,CAAC;gBACH,CAAC;gBAED,OAAO;oBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,MAAM,CAAC,MAAM,KAAK,aAAa,CAAC,WAAW,EAAE,EAAE,CAAC;oBAClG,OAAO,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;iBAChC,CAAC;YACH,CAAC;YAED,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,uCAAuC,EAAE,CAAC;gBAC1E,OAAO,EAAE,SAAS;gBAClB,OAAO,EAAE,IAAI;aACb,CAAC;QAAA,CACF;QACD,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE;YACvB,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;gBACd,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7G,CAAC;YACD,OAAO,IAAI,IAAI,CACd,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,EACrG,CAAC,EACD,CAAC,CACD,CAAC;QAAA,CACF;QACD,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;YACrC,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,YAAY,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC;YAC/E,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAC/C;KACD,CAAC;AAAA,CACF","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../types.js\";\nimport type { BackgroundManager } from \"./manager.js\";\n\nconst BackgroundCancelParams = Type.Object({\n\ttaskId: Type.Optional(Type.String({ description: \"Task ID to cancel (required if all=false)\" })),\n\tall: Type.Optional(Type.Boolean({ description: \"Cancel all running background tasks (default: false)\" })),\n});\n\nexport function createBackgroundCancelTool(manager: BackgroundManager): ToolDefinition<typeof BackgroundCancelParams> {\n\treturn {\n\t\tname: \"background_cancel\",\n\t\tlabel: \"BackgroundCancel\",\n\t\tdescription: \"Cancel a background task by taskId, or cancel all running tasks with all=true.\",\n\t\tpromptSnippet: \"Cancel background tasks by ID or cancel all running tasks.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool to cancel pending or running background tasks.\",\n\t\t\t\"Provide taskId to cancel a specific task, or set all=true to cancel all active tasks.\",\n\t\t\t\"Cancelled tasks will be marked as 'cancelled' and cannot be resumed.\",\n\t\t],\n\t\tparameters: BackgroundCancelParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n\t\t\tif (!params.taskId && !params.all) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t\tisError: true,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.all) {\n\t\t\t\tconst cancelled = manager.cancelAll();\n\n\t\t\t\tif (cancelled.length === 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No active tasks to cancel\" }],\n\t\t\t\t\t\tdetails: { cancelledTasks: [] },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst lines: string[] = [`Cancelled ${cancelled.length} task(s):`];\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tlines.push(`- ${task.id}: ${task.description}`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: lines.join(\"\\n\") }],\n\t\t\t\t\tdetails: { cancelledTasks: cancelled },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.taskId) {\n\t\t\t\tconst task = manager.getTask(params.taskId);\n\t\t\t\tif (!task) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Error: Task not found: ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst wasCancelled = manager.cancelTask(params.taskId);\n\n\t\t\t\tif (!wasCancelled) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Task ${params.taskId} is not active (status: ${task.status})` }],\n\t\t\t\t\t\tdetails: { task },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst cancelledTask = manager.getTask(params.taskId);\n\t\t\t\tif (!cancelledTask) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}: ${cancelledTask.description}` }],\n\t\t\t\t\tdetails: { task: cancelledTask },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\tdetails: undefined,\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tif (args.all) {\n\t\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", \"[all]\"), 0, 0);\n\t\t\t}\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", args.taskId ?? \"unknown\"),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst firstContent = result.content[0];\n\t\t\tconst text = firstContent?.type === \"text\" ? firstContent.text : \"(no output)\";\n\t\t\treturn new Text(theme.fg(\"muted\", text), 0, 0);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AA8LrE,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAiEtE","sourcesContent":["import type { ExtensionAPI, ExtensionContext } from \"../../types.js\";\nimport { sendBuiltinCustomMessage } from \"../system-messages.js\";\nimport { createBackgroundCancelTool } from \"./cancel-tool.js\";\nimport { BackgroundManager, getWidgetLines } from \"./manager.js\";\nimport { sendCompletionNotification } from \"./notification.js\";\nimport { createBackgroundOutputTool } from \"./output-tool.js\";\nimport { spawnSubagent } from \"./spawner.js\";\nimport { createTaskTool } from \"./task-tool.js\";\nimport { type BackgroundTask, TASK_ENTRY_TYPE } from \"./types.js\";\n\ntype SessionEntry = {\n\ttype: string;\n\tcustomType?: string;\n\tdata?: unknown;\n\tmessage?: unknown;\n};\n\nfunction isTaskStatus(value: unknown): value is BackgroundTask[\"status\"] {\n\treturn (\n\t\tvalue === \"pending\" || value === \"running\" || value === \"completed\" || value === \"error\" || value === \"cancelled\"\n\t);\n}\n\nfunction parseDate(value: unknown): Date | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (value instanceof Date) {\n\t\treturn Number.isNaN(value.getTime()) ? undefined : value;\n\t}\n\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\n\tconst parsedDate = new Date(value);\n\treturn Number.isNaN(parsedDate.getTime()) ? undefined : parsedDate;\n}\n\nfunction parseBackgroundTask(value: unknown): BackgroundTask | undefined {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn undefined;\n\t}\n\n\tconst taskRecord = value as Record<string, unknown>;\n\tconst startedAt = parseDate(taskRecord.startedAt);\n\tconst completedAt = parseDate(taskRecord.completedAt);\n\n\tif (\n\t\ttypeof taskRecord.id !== \"string\" ||\n\t\ttypeof taskRecord.description !== \"string\" ||\n\t\ttypeof taskRecord.prompt !== \"string\" ||\n\t\t!isTaskStatus(taskRecord.status) ||\n\t\t(taskRecord.model !== undefined && typeof taskRecord.model !== \"string\") ||\n\t\t(taskRecord.agentType !== undefined && typeof taskRecord.agentType !== \"string\") ||\n\t\t(taskRecord.pid !== undefined && typeof taskRecord.pid !== \"number\") ||\n\t\t(taskRecord.sessionPath !== undefined && typeof taskRecord.sessionPath !== \"string\") ||\n\t\t(taskRecord.activeToolNames !== undefined &&\n\t\t\t(!Array.isArray(taskRecord.activeToolNames) ||\n\t\t\t\ttaskRecord.activeToolNames.some((toolName) => typeof toolName !== \"string\"))) ||\n\t\tstartedAt === undefined ||\n\t\t(taskRecord.completedAt !== undefined && completedAt === undefined) ||\n\t\t(taskRecord.result !== undefined && typeof taskRecord.result !== \"string\") ||\n\t\t(taskRecord.error !== undefined && typeof taskRecord.error !== \"string\") ||\n\t\ttypeof taskRecord.parentSessionId !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tid: taskRecord.id,\n\t\tdescription: taskRecord.description,\n\t\tprompt: taskRecord.prompt,\n\t\tmodel: taskRecord.model,\n\t\tagentType: taskRecord.agentType,\n\t\tstatus: taskRecord.status,\n\t\tpid: taskRecord.pid,\n\t\tsessionPath: taskRecord.sessionPath,\n\t\tactiveToolNames: Array.isArray(taskRecord.activeToolNames) ? taskRecord.activeToolNames : [],\n\t\tstartedAt,\n\t\tcompletedAt,\n\t\tresult: taskRecord.result,\n\t\terror: taskRecord.error,\n\t\tparentSessionId: taskRecord.parentSessionId,\n\t};\n}\n\nfunction isTerminalTask(task: BackgroundTask): boolean {\n\treturn task.status === \"completed\" || task.status === \"error\" || task.status === \"cancelled\";\n}\n\nfunction getRestoredTasks(entries: SessionEntry[]): BackgroundTask[] {\n\tconst restoredTasks = new Map<string, BackgroundTask>();\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== TASK_ENTRY_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst task = parseBackgroundTask(entry.data);\n\t\tif (!task) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (isTerminalTask(task)) {\n\t\t\trestoredTasks.set(task.id, task);\n\t\t} else {\n\t\t\trestoredTasks.delete(task.id);\n\t\t}\n\t}\n\n\treturn Array.from(restoredTasks.values());\n}\n\nfunction getCompletionTaskId(message: { content?: unknown }): string | undefined {\n\tif (!Array.isArray(message.content)) {\n\t\treturn undefined;\n\t}\n\n\tfor (const part of message.content) {\n\t\tif (typeof part !== \"object\" || part === null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst contentPart = part as Record<string, unknown>;\n\t\tif (contentPart.type !== \"text\" || typeof contentPart.text !== \"string\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst match = contentPart.text.match(/\\b(bg_[0-9a-f]{8})\\b/);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1];\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nclass ObservableBackgroundManager extends BackgroundManager {\n\tconstructor(\n\t\tprivate readonly onChange: () => void,\n\t\tprivate readonly persistTask: (task: BackgroundTask) => void,\n\t) {\n\t\tsuper();\n\t}\n\n\toverride launch(task: Omit<BackgroundTask, \"id\" | \"status\" | \"startedAt\">): BackgroundTask {\n\t\tconst launchedTask = super.launch(task);\n\t\tthis.onChange();\n\t\treturn launchedTask;\n\t}\n\n\toverride updateTask(id: string, updates: Partial<BackgroundTask>): void {\n\t\tsuper.updateTask(id, updates);\n\t\tthis.onChange();\n\t}\n\n\toverride cancelTask(id: string): boolean {\n\t\tconst wasCancelled = super.cancelTask(id);\n\t\tif (wasCancelled) {\n\t\t\tconst cancelledTask = this.getTask(id);\n\t\t\tif (cancelledTask) {\n\t\t\t\tthis.persistTask(cancelledTask);\n\t\t\t}\n\t\t\tthis.onChange();\n\t\t}\n\t\treturn wasCancelled;\n\t}\n\n\toverride cancelAll(): BackgroundTask[] {\n\t\tconst cancelledTasks = super.cancelAll();\n\t\tfor (const task of cancelledTasks) {\n\t\t\tthis.persistTask(task);\n\t\t}\n\t\tif (cancelledTasks.length > 0) {\n\t\t\tthis.onChange();\n\t\t}\n\t\treturn cancelledTasks;\n\t}\n\n\treplaceTasks(tasks: BackgroundTask[]): void {\n\t\tthis.clearTasks();\n\t\tfor (const task of tasks) {\n\t\t\tthis.restoreTask(task);\n\t\t}\n\t\tthis.onChange();\n\t}\n}\n\nexport default function backgroundTaskExtension(pi: ExtensionAPI): void {\n\tlet currentContext: ExtensionContext | undefined;\n\n\tconst syncWidget = (): void => {\n\t\tcurrentContext?.ui.setWidget(\"background-tasks\", getWidgetLines(manager));\n\t};\n\n\tconst manager = new ObservableBackgroundManager(\n\t\t() => {\n\t\t\tsyncWidget();\n\t\t},\n\t\t(task) => {\n\t\t\tpi.appendEntry(TASK_ENTRY_TYPE, task);\n\t\t},\n\t);\n\n\tconst syncFromSession = (ctx: ExtensionContext): void => {\n\t\tcurrentContext = ctx;\n\t\tmanager.replaceTasks(getRestoredTasks(ctx.sessionManager.getBranch() as SessionEntry[]));\n\t};\n\n\tconst notifyingPi = {\n\t\t...pi,\n\t\tsendMessage(message, options) {\n\t\t\tif (message.customType === \"background-task.complete\") {\n\t\t\t\tconst taskId = getCompletionTaskId(message);\n\t\t\t\tif (taskId) {\n\t\t\t\t\tconst task = manager.getTask(taskId);\n\t\t\t\t\tif (task && isTerminalTask(task)) {\n\t\t\t\t\t\tsendCompletionNotification(pi, task, manager);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsendBuiltinCustomMessage(pi, \"background-task.notification\", message, options);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tpi.sendMessage(message, options);\n\t\t},\n\t} satisfies ExtensionAPI;\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsyncFromSession(ctx);\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsyncFromSession(ctx);\n\t});\n\n\tpi.registerTool(createTaskTool(manager, spawnSubagent, notifyingPi));\n\tpi.registerTool(createBackgroundOutputTool(manager));\n\tpi.registerTool(createBackgroundCancelTool(manager));\n\n\tprocess.on(\"exit\", () => {\n\t\tfor (const task of manager.getActiveTasks()) {\n\t\t\tif (task.pid === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t} catch {}\n\t\t}\n\t});\n}\n"]}
|