scream-code 0.16.7 → 0.16.9
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/README.md +1 -1
- package/dist/{app-DDepBvBx.mjs → app-BJSym649.mjs} +150 -55
- package/dist/{dispatch-Vw9ccFm7.mjs → dispatch-CIOuINvn.mjs} +2139 -2961
- package/dist/{dispatch-CIGyjBDa.mjs → dispatch-ChbwC2I5.mjs} +1 -1
- package/dist/main.mjs +1 -1
- package/dist/src-BhZujafw.mjs +4141 -0
- package/dist/{src-h6ZExh4A.mjs → src-CUURS-i6.mjs} +1 -1
- package/dist/{text-input-dialog-B-6Pgo6m.mjs → text-input-dialog-DBgQwxaO.mjs} +1 -1
- package/dist/{text-input-dialog-D9ByqSsK.mjs → text-input-dialog-XT3cOEjD.mjs} +7 -3
- package/package.json +5 -2
- package/dist/src-tDEINaMV.mjs +0 -2249
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
21
|
<p align="center">
|
|
22
|
-
<img width="
|
|
22
|
+
<img width="2880" height="2880" alt="scream" src="https://github.com/user-attachments/assets/7268e848-b1b8-4540-9627-d21c94332229" />
|
|
23
23
|
</p>
|
|
24
24
|
|
|
25
25
|
---
|
|
@@ -4,8 +4,9 @@ import { dirname as __cjsShimDirname } from 'node:path';
|
|
|
4
4
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
6
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { a as
|
|
7
|
+
import { k as MemoryMemoStore } from "./src-BhZujafw.mjs";
|
|
8
|
+
import { $n as encodeWorkDirKey, $t as lerpGradient, A as highlightLines, An as embeddingFailureDetail, At as isTurnElapsedEnabled, B as ActivityGroupComponent, Bn as detectShellEnvironment, Bt as ENABLE_TERMINAL_THEME_REPORTING, Cn as getLlmNotSetMessage, Ct as clearInfoPanelState, D as ToolCallComponent, Dn as sortSlashCommands, Dt as createEditorTheme, E as UserMessageComponent, En as BUILTIN_SLASH_COMMANDS, Et as resolveThemeSync, F as StatusMessageComponent, Fn as PULSE_WAVE_FRAMES, Ft as detectTerminalTheme, G as AgentGroupComponent, Gn as CLI_COMMAND_NAME, Gt as QUERY_TERMINAL_THEME, H as estimateTokens, Hn as getInputHistoryFile, Ht as OSC11_RESPONSE, I as replaceTabs, In as TuiConfigParseError, It as parseOsc11BackgroundTheme, J as getBreathingFrame, Jn as PRODUCT_NAME, Jt as TERMINAL_THEME_DARK, K as WelcomeComponent, Kn as CLI_UI_MODE, Kt as TERMINAL_FOCUS_IN, L as sanitizeShellOutput, Ln as TuiLikePreferencesSchema, Lt as DISABLE_TERMINAL_FOCUS_REPORTING, M as CachedContainer, Mn as hasEmbeddingModelCache, Mt as createThemeStyles, N as ThinkingComponent, Nn as startManualEmbeddingDownload, Nt as contrastTextHex, O as renderDiffLines, On as isExperimentalFlagEnabled, Ot as createMarkdownTheme, P as NoticeMessageComponent, Pn as PIXEL_PULSE_FRAMES, Pt as getColorPalette, Q as refineGoal, Qn as ScreamHarness, Qt as FooterComponent, R as SkillActivationComponent, Rn as loadTuiConfig, Rt as DISABLE_TERMINAL_THEME_REPORTING, S as readJsonlFile, Sn as getCtrlDHint, St as supportsBalance, Tn as buildSkillSlashCommands, U as getSharedSpeedTracker, Un as getLogDir, Ut as OSC11_RESPONSE_PREFIX, V as charsForTokenBudget, Vn as getDataDir, Vt as OSC11_QUERY, W as BackgroundAgentStatusComponent, Wn as detectInstallSource, Wt as OSC11_RESPONSE_PREFIX_NO_ESC, X as CompactionComponent, Xn as fetchCatalog, Xt as isBusy, Y as resetBreathingClock, Yn as DEFAULT_CATALOG_URL, Yt as TERMINAL_THEME_LIGHT, Z as clearGoalState, Zn as saveCatalogCache, Zt as isStreaming, _n as EXIT_CONFIRM_WINDOW_MS, a as syncDiagramCapabilityPrompt, an as printableChar, ar as isRealUserPrompt, at as toTerminalHyperlink, b as readUpdateCache, bn as TIP_ROTATION_INTERVAL_MS, c as disposeChildren, cn as appendStreamingArgsPreview, cr as log, d as isPlanExpandable, dn as isTodoItemShape, dr as isOrphanedToolCallError, en as SubagentSlots, er as normalizeWorkDir, f as MoonLoader, fn as parseStreamingArgs, fr as ErrorCodes, ft as handleFusionPlanCommand, gn as EMPTY_SESSION_HINT_URL, hn as truncateErrorMessage, i as buildRoleAdditionalText, it as isInternalMessage, j as langFromPath, jn as getKnowledgeStore, jt as toggleEmptySessionHint, k as renderDiffLinesClustered, kn as setExperimentalFlags, kt as isEmptySessionHintDismissed, l as hasDispose, ln as argsRecord, lr as resolveGlobalLogPath, mn as stringValue, mt as handlePlanCommand, n as clearEvalPanelState, nr as readSessionIndex, on as STATUS_BULLET, or as resolveScreamHome, ot as changeThinkingLevel, p as formatMemoryMemoForInjection, pn as serializeToolResultOutput, pr as SCREAM_ERROR_INFO, q as BREATHE_CYCLE_MS, qn as CLI_USER_AGENT_PRODUCT, qt as TERMINAL_FOCUS_OUT, r as openUrl, rr as removeSessionIndexEntry, sn as appendSessionHeaderHint, sr as flushDiagnosticLogs, st as getModelCycleLevel, t as dispatchInput, tr as appendSessionIndexEntry, u as isExpandable, un as formatErrorMessage, ur as isScreamError, v as selectUpdateTarget, vn as MAIN_AGENT_ID, w as getDaemonInstructions, wn as getNoActiveSessionMessage, x as appendJsonlLine, xn as getCtrlCHint, xt as refreshProviderBalance, y as refreshUpdateCache, yn as SESSION_TIPS, z as AssistantMessageComponent, zn as saveTuiConfig, zt as ENABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-CIOuINvn.mjs";
|
|
9
|
+
import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-XT3cOEjD.mjs";
|
|
9
10
|
import { createRequire } from "node:module";
|
|
10
11
|
import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
11
12
|
import { access, appendFile, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -14,10 +15,10 @@ import { WebSocket, WebSocketServer } from "ws";
|
|
|
14
15
|
import { appendFileSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
16
|
import * as path$1 from "node:path";
|
|
16
17
|
import path, { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
18
|
+
import { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
17
19
|
import { z } from "zod";
|
|
18
20
|
import { exec, execFile, execSync, spawn, spawnSync } from "node:child_process";
|
|
19
21
|
import { promisify } from "node:util";
|
|
20
|
-
import { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
21
22
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
23
|
import { createServer } from "node:http";
|
|
23
24
|
import process$1 from "node:process";
|
|
@@ -32,7 +33,7 @@ function optionalBuildString(value) {
|
|
|
32
33
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
33
34
|
}
|
|
34
35
|
const SCREAM_BUILD_INFO = {
|
|
35
|
-
version: optionalBuildString("0.16.
|
|
36
|
+
version: optionalBuildString("0.16.9"),
|
|
36
37
|
channel: optionalBuildString(""),
|
|
37
38
|
commit: optionalBuildString(""),
|
|
38
39
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -1675,20 +1676,33 @@ function resolveEditorCommand(configured) {
|
|
|
1675
1676
|
for (const c of candidates) if (typeof c === "string" && c.trim().length > 0) return c.trim();
|
|
1676
1677
|
}
|
|
1677
1678
|
/**
|
|
1678
|
-
* Launch `command` (tokenised
|
|
1679
|
-
* with `initialText`. Returns the edited contents on
|
|
1680
|
-
* `undefined` if the editor exited non-zero / the file
|
|
1679
|
+
* Launch `command` (tokenised by the platform's command interpreter) against
|
|
1680
|
+
* a temp file seeded with `initialText`. Returns the edited contents on
|
|
1681
|
+
* success, or `undefined` if the editor exited non-zero / the file
|
|
1682
|
+
* disappeared.
|
|
1681
1683
|
*
|
|
1682
|
-
* The command
|
|
1683
|
-
*
|
|
1684
|
+
* The command may carry argv-style arguments (`"code --wait"`,
|
|
1685
|
+
* `"nvim +set ft=markdown"`), so it is handed to the interpreter the host
|
|
1686
|
+
* platform actually has: `/bin/sh -c` on POSIX, `cmd.exe /d /s /c` on
|
|
1687
|
+
* Windows. The two disagree on quoting, so each branch carries its own
|
|
1688
|
+
* escaping rather than borrowing the other's. `platform` is injectable so
|
|
1689
|
+
* both branches stay testable off their native host.
|
|
1684
1690
|
*/
|
|
1685
|
-
async function editInExternalEditor(initialText, command) {
|
|
1691
|
+
async function editInExternalEditor(initialText, command, platform = process.platform) {
|
|
1686
1692
|
const dir = await mkdtemp(join(tmpdir(), "scream-edit-"));
|
|
1687
1693
|
const file = join(dir, "prompt.md");
|
|
1688
1694
|
await writeFile(file, initialText, "utf-8");
|
|
1689
1695
|
try {
|
|
1690
1696
|
if (await new Promise((resolve, reject) => {
|
|
1691
|
-
const child =
|
|
1697
|
+
const child = platform === "win32" ? spawn(windowsCommandShell(), [
|
|
1698
|
+
"/d",
|
|
1699
|
+
"/s",
|
|
1700
|
+
"/c",
|
|
1701
|
+
`"${command} ${cmdQuote(file)}"`
|
|
1702
|
+
], {
|
|
1703
|
+
stdio: "inherit",
|
|
1704
|
+
windowsVerbatimArguments: true
|
|
1705
|
+
}) : spawn("/bin/sh", ["-c", `${command} ${shellQuote(file)}`], { stdio: "inherit" });
|
|
1692
1706
|
child.on("exit", (c) => {
|
|
1693
1707
|
resolve(c ?? 0);
|
|
1694
1708
|
});
|
|
@@ -1702,9 +1716,18 @@ async function editInExternalEditor(initialText, command) {
|
|
|
1702
1716
|
}).catch(() => {});
|
|
1703
1717
|
}
|
|
1704
1718
|
}
|
|
1719
|
+
/** The interpreter `cmd.exe`-style command lines run through, as Node's own
|
|
1720
|
+
* `shell: true` resolves it on Windows. */
|
|
1721
|
+
function windowsCommandShell() {
|
|
1722
|
+
const comspec = process.env["ComSpec"];
|
|
1723
|
+
return comspec !== void 0 && comspec.trim().length > 0 ? comspec : "cmd.exe";
|
|
1724
|
+
}
|
|
1705
1725
|
function shellQuote(path) {
|
|
1706
1726
|
return `'${path.replaceAll("'", "'\\''")}'`;
|
|
1707
1727
|
}
|
|
1728
|
+
function cmdQuote(path) {
|
|
1729
|
+
return `"${path.replaceAll("\"", "\"\"")}"`;
|
|
1730
|
+
}
|
|
1708
1731
|
//#endregion
|
|
1709
1732
|
//#region src/tui/controllers/editor-keyboard.ts
|
|
1710
1733
|
var EditorKeyboardController = class {
|
|
@@ -1877,7 +1900,7 @@ var EditorKeyboardController = class {
|
|
|
1877
1900
|
if (media === null) return;
|
|
1878
1901
|
const meta = parseImageMeta(media.bytes);
|
|
1879
1902
|
if (meta === null) return;
|
|
1880
|
-
const attachment = this.imageStore.addImage(media.bytes, meta.mime, meta.width, meta.height);
|
|
1903
|
+
const attachment = this.imageStore.addImage(media.bytes, meta.mime, meta.width, meta.height, path);
|
|
1881
1904
|
this.host.state.editor.insertTextAtCursor?.(`${attachment.placeholder} `);
|
|
1882
1905
|
this.host.state.ui.requestRender();
|
|
1883
1906
|
}
|
|
@@ -3164,7 +3187,8 @@ var SessionEventHandler = class {
|
|
|
3164
3187
|
this.host.streamingUI.resetToolUi();
|
|
3165
3188
|
this.host.streamingUI.finalizeLiveTextBuffers();
|
|
3166
3189
|
this.host.streamingUI.endActivityGroup();
|
|
3167
|
-
|
|
3190
|
+
const shown = appendSessionHeaderHint(`[${event.code}] ${event.message}`, (name) => t("error.session_header_hint", { name }));
|
|
3191
|
+
this.host.showError(shown);
|
|
3168
3192
|
if (event.code === "turn.agent_busy" && !hadActiveTurn && this.host.state.appState.streamingPhase !== "idle") {
|
|
3169
3193
|
this.host.setAppState({ streamingPhase: "idle" });
|
|
3170
3194
|
this.host.resetLivePane();
|
|
@@ -3787,7 +3811,7 @@ function createReplayRenderContext() {
|
|
|
3787
3811
|
}
|
|
3788
3812
|
function limitReplayRecordsByTurn(records, maxTurns) {
|
|
3789
3813
|
if (maxTurns <= 0) return [];
|
|
3790
|
-
const turnStarts = records.flatMap((record, index) =>
|
|
3814
|
+
const turnStarts = records.flatMap((record, index) => record.type === "message" && isRealUserPrompt(record.message) ? [index] : []);
|
|
3791
3815
|
if (turnStarts.length <= maxTurns) return records;
|
|
3792
3816
|
return records.slice(turnStarts[turnStarts.length - maxTurns]);
|
|
3793
3817
|
}
|
|
@@ -3862,23 +3886,6 @@ function formatHookResultMessageForTranscript(text, fallbackEvent, blocked) {
|
|
|
3862
3886
|
if (results.length === 0 || text.slice(lastIndex).trim().length > 0) return formatHookResultBlock(fallbackEvent, text, blocked);
|
|
3863
3887
|
return results.map(({ event, body }) => formatHookResultBlock(event, body, blocked)).join("\n\n");
|
|
3864
3888
|
}
|
|
3865
|
-
function isReplayUserTurnRecord(record) {
|
|
3866
|
-
if (record.type !== "message") return false;
|
|
3867
|
-
const { message } = record;
|
|
3868
|
-
if (message.role !== "user") return false;
|
|
3869
|
-
switch (message.origin?.kind) {
|
|
3870
|
-
case void 0:
|
|
3871
|
-
case "user": return true;
|
|
3872
|
-
case "skill_activation": return message.origin.trigger === "user-slash";
|
|
3873
|
-
case "background_task":
|
|
3874
|
-
case "compaction_summary":
|
|
3875
|
-
case "cron_job":
|
|
3876
|
-
case "cron_missed":
|
|
3877
|
-
case "hook_result":
|
|
3878
|
-
case "injection":
|
|
3879
|
-
case "system_trigger": return false;
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3882
3889
|
function parseReplayToolArguments(value) {
|
|
3883
3890
|
if (value === null || value.length === 0) return {};
|
|
3884
3891
|
try {
|
|
@@ -4621,6 +4628,7 @@ var StreamingUIController = class StreamingUIController {
|
|
|
4621
4628
|
completeToolResult(toolCallId, result) {
|
|
4622
4629
|
const matchedCall = this._activeToolCalls.get(toolCallId);
|
|
4623
4630
|
if (matchedCall !== void 0) this.onToolCallEnd(toolCallId, result);
|
|
4631
|
+
else if (this._pendingToolComponents.has(toolCallId)) this.onToolCallEnd(toolCallId, result);
|
|
4624
4632
|
this._activeToolCalls.delete(toolCallId);
|
|
4625
4633
|
this._streamingToolCallArguments.delete(toolCallId);
|
|
4626
4634
|
return matchedCall;
|
|
@@ -6246,10 +6254,28 @@ function cronDetail(data) {
|
|
|
6246
6254
|
}
|
|
6247
6255
|
//#endregion
|
|
6248
6256
|
//#region src/tui/utils/image-attachment-store.ts
|
|
6257
|
+
/**
|
|
6258
|
+
* Registry for media pasted into the input box.
|
|
6259
|
+
*
|
|
6260
|
+
* Each paste produces an `ImageAttachment` with an auto-incrementing id
|
|
6261
|
+
* or `VideoAttachment` with a human-readable placeholder (`[image #1
|
|
6262
|
+
* (640×480)]` / `[video #2 sample.mov]`). The placeholder is what the
|
|
6263
|
+
* user sees in the input field; on submit, `extractMediaAttachments`
|
|
6264
|
+
* walks the text and expands image placeholders to image content parts
|
|
6265
|
+
* and video placeholders to file-path tags for `ReadMediaFile`.
|
|
6266
|
+
*
|
|
6267
|
+
* Scope is per-`ScreamTUI` instance. Reloads (`/new`, `/clear`,
|
|
6268
|
+
* session switch) call `clear()` so ids restart from 1 and stale
|
|
6269
|
+
* prompt attachments are dropped. We intentionally do NOT persist
|
|
6270
|
+
* attachments across sessions — coding-agent doesn't either, and
|
|
6271
|
+
* `--resume` wouldn't know how to materialize the files anyway.
|
|
6272
|
+
*/
|
|
6273
|
+
/** Temp-file basename prefix used by path-mode image materialization. */
|
|
6274
|
+
const TEMP_ATTACHMENT_PREFIX = "scream-attachment-";
|
|
6249
6275
|
var ImageAttachmentStore = class {
|
|
6250
6276
|
nextId = 1;
|
|
6251
6277
|
byId = /* @__PURE__ */ new Map();
|
|
6252
|
-
addImage(bytes, mime, width, height) {
|
|
6278
|
+
addImage(bytes, mime, width, height, sourcePath) {
|
|
6253
6279
|
const id = this.nextId;
|
|
6254
6280
|
this.nextId += 1;
|
|
6255
6281
|
const attachment = {
|
|
@@ -6259,7 +6285,8 @@ var ImageAttachmentStore = class {
|
|
|
6259
6285
|
mime,
|
|
6260
6286
|
width,
|
|
6261
6287
|
height,
|
|
6262
|
-
placeholder: formatPlaceholder(id, width, height)
|
|
6288
|
+
placeholder: formatPlaceholder(id, width, height),
|
|
6289
|
+
...sourcePath !== void 0 && sourcePath.length > 0 ? { sourcePath } : {}
|
|
6263
6290
|
};
|
|
6264
6291
|
this.byId.set(id, attachment);
|
|
6265
6292
|
return attachment;
|
|
@@ -6285,6 +6312,14 @@ var ImageAttachmentStore = class {
|
|
|
6285
6312
|
return this.byId.get(id);
|
|
6286
6313
|
}
|
|
6287
6314
|
clear() {
|
|
6315
|
+
const tempBase = join(tmpdir(), TEMP_ATTACHMENT_PREFIX);
|
|
6316
|
+
for (const attachment of this.byId.values()) {
|
|
6317
|
+
if (attachment.kind !== "image") continue;
|
|
6318
|
+
const path = attachment.sourcePath;
|
|
6319
|
+
if (path !== void 0 && path.startsWith(tempBase)) try {
|
|
6320
|
+
unlinkSync(path);
|
|
6321
|
+
} catch {}
|
|
6322
|
+
}
|
|
6288
6323
|
this.byId.clear();
|
|
6289
6324
|
this.nextId = 1;
|
|
6290
6325
|
}
|
|
@@ -8688,8 +8723,25 @@ var QueuePaneComponent = class extends Container {
|
|
|
8688
8723
|
};
|
|
8689
8724
|
//#endregion
|
|
8690
8725
|
//#region src/tui/utils/image-placeholder.ts
|
|
8726
|
+
/**
|
|
8727
|
+
* Scan submitted text for media placeholders and produce
|
|
8728
|
+
* the `PromptPart[]` we'll send to the SDK prompt endpoint.
|
|
8729
|
+
*
|
|
8730
|
+
* Rules:
|
|
8731
|
+
* - Only placeholders that resolve against `store` get extracted.
|
|
8732
|
+
* A literal `[image #999 ...]` the user typed themselves stays in
|
|
8733
|
+
* the text (we can't hallucinate files for it).
|
|
8734
|
+
* - Order is preserved for text/image/video segments. Image placeholders
|
|
8735
|
+
* expand to image content parts so the prompt reaches the provider
|
|
8736
|
+
* without relying on a model tool call. Video placeholders still expand
|
|
8737
|
+
* to file-path tags so `ReadMediaFile` can own video upload behavior.
|
|
8738
|
+
* - Adjacent text segments are flattened — empty / whitespace-only
|
|
8739
|
+
* segments drop out so we never emit `{type:'text', text:' '}`
|
|
8740
|
+
* noise between two media parts.
|
|
8741
|
+
*/
|
|
8691
8742
|
const PLACEHOLDER_REGEX = /\[(image|video) #(\d+) (?:(\(\d+×\d+\))|([^\]]+))\]/g;
|
|
8692
|
-
function extractMediaAttachments(text, store) {
|
|
8743
|
+
function extractMediaAttachments(text, store, options = {}) {
|
|
8744
|
+
const imageMode = options.imageMode ?? "inline";
|
|
8693
8745
|
const parts = [];
|
|
8694
8746
|
const imageAttachmentIds = [];
|
|
8695
8747
|
const videoAttachmentIds = [];
|
|
@@ -8709,6 +8761,13 @@ function extractMediaAttachments(text, store) {
|
|
|
8709
8761
|
if (attachment.kind === "video") {
|
|
8710
8762
|
pushText(parts, tagTextForVideo(attachment));
|
|
8711
8763
|
videoAttachmentIds.push(id);
|
|
8764
|
+
} else if (imageMode === "path") {
|
|
8765
|
+
try {
|
|
8766
|
+
pushText(parts, formatMediaTag("image", ensureImagePath(attachment)));
|
|
8767
|
+
} catch {
|
|
8768
|
+
parts.push(imagePartForAttachment(attachment));
|
|
8769
|
+
}
|
|
8770
|
+
imageAttachmentIds.push(id);
|
|
8712
8771
|
} else {
|
|
8713
8772
|
parts.push(imagePartForAttachment(attachment));
|
|
8714
8773
|
imageAttachmentIds.push(id);
|
|
@@ -8747,6 +8806,15 @@ function imagePartForAttachment(att) {
|
|
|
8747
8806
|
imageUrl: { url: `data:${att.mime};base64,${base64}` }
|
|
8748
8807
|
};
|
|
8749
8808
|
}
|
|
8809
|
+
/** Persist bytes to a temp file when path delivery needs a disk path. */
|
|
8810
|
+
function ensureImagePath(att) {
|
|
8811
|
+
if (att.sourcePath !== void 0 && att.sourcePath.length > 0) return att.sourcePath;
|
|
8812
|
+
const ext = att.mime === "image/jpeg" ? "jpg" : att.mime.split("/")[1]?.replace("+xml", "").replace("+json", "") ?? "png";
|
|
8813
|
+
const path = join(tmpdir(), `${TEMP_ATTACHMENT_PREFIX}${randomUUID()}.${ext}`);
|
|
8814
|
+
writeFileSync(path, att.bytes);
|
|
8815
|
+
att.sourcePath = path;
|
|
8816
|
+
return path;
|
|
8817
|
+
}
|
|
8750
8818
|
function tagTextForVideo(att) {
|
|
8751
8819
|
return formatMediaTag("video", att.sourcePath);
|
|
8752
8820
|
}
|
|
@@ -8914,7 +8982,8 @@ var InputController = class InputController {
|
|
|
8914
8982
|
this.dispatchUserInput(text, session);
|
|
8915
8983
|
}
|
|
8916
8984
|
dispatchUserInput(text, session) {
|
|
8917
|
-
const
|
|
8985
|
+
const canInlineImages = this.supportsCurrentModelCapability("image_in");
|
|
8986
|
+
const extraction = extractMediaAttachments(text, this.host.imageStore, { imageMode: canInlineImages ? "inline" : "path" });
|
|
8918
8987
|
if (!this.validateMediaCapabilities(extraction)) return;
|
|
8919
8988
|
if (extraction.hasMedia) this.sendMessage(session, text, {
|
|
8920
8989
|
hasMedia: true,
|
|
@@ -9091,10 +9160,6 @@ var InputController = class InputController {
|
|
|
9091
9160
|
}
|
|
9092
9161
|
validateMediaCapabilities(extraction) {
|
|
9093
9162
|
if (!extraction.hasMedia) return true;
|
|
9094
|
-
if (extraction.imageAttachmentIds.length > 0 && !this.supportsCurrentModelCapability("image_in")) {
|
|
9095
|
-
this.host.showError(t("error.image_not_supported"));
|
|
9096
|
-
return false;
|
|
9097
|
-
}
|
|
9098
9163
|
if (extraction.videoAttachmentIds.length > 0 && !this.supportsCurrentModelCapability("video_in")) {
|
|
9099
9164
|
this.host.showError(t("error.video_not_supported"));
|
|
9100
9165
|
return false;
|
|
@@ -9926,6 +9991,20 @@ function extractBracketedImagePastePaths(data) {
|
|
|
9926
9991
|
if (paths.length === 0 || segmentStart !== pasted.length) return void 0;
|
|
9927
9992
|
return paths;
|
|
9928
9993
|
}
|
|
9994
|
+
/**
|
|
9995
|
+
* True only when bracketed-paste markers span the entire input and the
|
|
9996
|
+
* body is strictly empty — Cmd+V with an image-only clipboard (macOS
|
|
9997
|
+
* wraps nothing). Whitespace-only pastes and coalesced keystrokes around
|
|
9998
|
+
* the marker pair are not image pastes.
|
|
9999
|
+
*/
|
|
10000
|
+
function isCompleteEmptyBracketedPaste(data) {
|
|
10001
|
+
if (!data.includes(BRACKET_PASTE_START)) return false;
|
|
10002
|
+
const startIndex = data.indexOf(BRACKET_PASTE_START);
|
|
10003
|
+
const endIndex = data.indexOf(BRACKET_PASTE_END, startIndex + 6);
|
|
10004
|
+
if (endIndex === -1) return false;
|
|
10005
|
+
if (startIndex !== 0 || endIndex + 6 !== data.length) return false;
|
|
10006
|
+
return data.slice(startIndex + 6, endIndex).length === 0;
|
|
10007
|
+
}
|
|
9929
10008
|
const KITTY_CSI_U = /^\[(\d+);(\d+)((?::\d+)*)u$/;
|
|
9930
10009
|
const CAPS_LOCK_BIT = 64;
|
|
9931
10010
|
const CTRL_BIT = 4;
|
|
@@ -10138,6 +10217,11 @@ var CustomEditor = class extends Editor {
|
|
|
10138
10217
|
if (!normalized.includes(BRACKET_PASTE_END)) this.consumingPaste = true;
|
|
10139
10218
|
return;
|
|
10140
10219
|
}
|
|
10220
|
+
if (this.onPasteImage !== void 0 && isCompleteEmptyBracketedPaste(normalized)) {
|
|
10221
|
+
const handler = this.onPasteImage;
|
|
10222
|
+
handler();
|
|
10223
|
+
return;
|
|
10224
|
+
}
|
|
10141
10225
|
if (matchesKey(normalized, process.platform === "win32" ? "alt+v" : Key.ctrl("v"))) {
|
|
10142
10226
|
if (this.expandPasteMarkerAtCursor()) return;
|
|
10143
10227
|
if (this.onPasteImage !== void 0) {
|
|
@@ -14053,7 +14137,7 @@ var ScreamTUI = class {
|
|
|
14053
14137
|
const card = new OnboardingCardComponent({
|
|
14054
14138
|
colors: this.state.theme.colors,
|
|
14055
14139
|
onConfigure: () => {
|
|
14056
|
-
import("./dispatch-
|
|
14140
|
+
import("./dispatch-ChbwC2I5.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
|
|
14057
14141
|
this.restoreEditor();
|
|
14058
14142
|
onDone?.();
|
|
14059
14143
|
});
|
|
@@ -16516,23 +16600,34 @@ function removeLastTurnsBefore(messages, count, beforeSeq) {
|
|
|
16516
16600
|
}
|
|
16517
16601
|
messages.splice(0, messages.length, ...prefix, ...tail);
|
|
16518
16602
|
}
|
|
16603
|
+
/**
|
|
16604
|
+
* The Web projection's anchor kind for one prompt origin.
|
|
16605
|
+
*
|
|
16606
|
+
* "Does this origin start a turn the user could `/undo`?" is the core rule, and
|
|
16607
|
+
* it now comes from `isRealUserPrompt` — public surface through the SDK, so this
|
|
16608
|
+
* projection no longer needs a copy of it. A copy is exactly what drifts from
|
|
16609
|
+
* the core and makes `/undo N` delete a different number of rows than the
|
|
16610
|
+
* history it mirrors. What is left here is the rendering split the core has no
|
|
16611
|
+
* opinion about: a user-typed skill turn carries no visible user row in the Web
|
|
16612
|
+
* transcript, so it is tagged `skill` and `/undo` removes that assistant block on
|
|
16613
|
+
* its own.
|
|
16614
|
+
*
|
|
16615
|
+
* Both callers ask about a *user* prompt, which is what an anchor is: the seed
|
|
16616
|
+
* path classifies each history message and only reads the answer for the user
|
|
16617
|
+
* ones, and a `turn.started` origin is the origin of the turn's prompt.
|
|
16618
|
+
*/
|
|
16519
16619
|
function classifyUndoAnchor(origin) {
|
|
16520
|
-
if (
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
return "other";
|
|
16526
|
-
|
|
16527
|
-
|
|
16620
|
+
if (!isRealUserPrompt({
|
|
16621
|
+
role: "user",
|
|
16622
|
+
content: [],
|
|
16623
|
+
toolCalls: [],
|
|
16624
|
+
origin
|
|
16625
|
+
})) return "other";
|
|
16626
|
+
return origin?.kind === "skill_activation" ? "skill" : "user";
|
|
16627
|
+
}
|
|
16628
|
+
/** The core `/undo` anchor rule, applied to the whole history. */
|
|
16528
16629
|
function countUndoAnchors(history) {
|
|
16529
|
-
|
|
16530
|
-
for (const message of history) {
|
|
16531
|
-
if (message.role !== "user") continue;
|
|
16532
|
-
const origin = message.origin;
|
|
16533
|
-
if (origin === void 0 || origin.kind === "user" || origin.kind === "skill_activation" && origin.trigger === "user-slash") count += 1;
|
|
16534
|
-
}
|
|
16535
|
-
return count;
|
|
16630
|
+
return history.filter(isRealUserPrompt).length;
|
|
16536
16631
|
}
|
|
16537
16632
|
const execFileAsync = promisify(execFile);
|
|
16538
16633
|
/** Raw porcelain rows: repo-root-relative path + XY status. `getGitStatus`
|