@deepseek-harness-tui/dsh-tui 0.10.2 → 0.11.0
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 +171 -124
- package/bin/dsh-tui.js +748 -22
- package/cordis.patch.yml +38 -10
- package/cordis.yml +2 -0
- package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
- package/lib/types/adapter/channel/session-projection.js +2 -0
- package/lib/types/adapter/channel/ui-policy.d.ts +4 -1
- package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
- package/lib/types/adapter/channel/ui-policy.js +5 -1
- package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
- package/lib/types/adapter/kernel/host-facade.js +1 -0
- package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
- package/lib/types/adapter/kernel/replay.js +3 -0
- package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
- package/lib/types/adapter/kernel/runtime.js +2 -0
- package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -1
- package/lib/types/adapter/kernel/slices/workspace.js +1 -0
- package/lib/types/adapter/ports/channel-ui.d.ts +22 -2
- package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts +66 -0
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/adapter/ports/channel-workspace.d.ts +23 -0
- package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -1
- package/lib/types/adapter/ports/workspace.d.ts +2 -0
- package/lib/types/adapter/ports/workspace.d.ts.map +1 -1
- package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -1
- package/lib/types/adapter/upstream/workspace-driver.js +10 -2
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +1 -0
- package/lib/types/components/ContextBarView.d.ts +15 -7
- package/lib/types/components/ContextBarView.d.ts.map +1 -1
- package/lib/types/components/ContextBarView.js +26 -22
- package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -1
- package/lib/types/components/ImagePreviewOverlay.js +11 -5
- package/lib/types/components/Markdown.d.ts +11 -1
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +20 -2
- package/lib/types/components/MermaidDiagram.d.ts +13 -0
- package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
- package/lib/types/components/MermaidDiagram.js +61 -0
- package/lib/types/components/MessageList.d.ts +26 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +72 -7
- package/lib/types/components/PageMargin.d.ts +1 -1
- package/lib/types/components/PromptInput.d.ts +37 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +243 -28
- package/lib/types/components/ScrollbarGutter.d.ts +20 -5
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
- package/lib/types/components/ScrollbarGutter.js +88 -10
- package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
- package/lib/types/components/StreamingMarkdown.js +30 -28
- package/lib/types/components/Tooltip.d.ts.map +1 -1
- package/lib/types/components/Tooltip.js +45 -7
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +5 -7
- package/lib/types/components/messages/useImageInspection.d.ts.map +1 -1
- package/lib/types/components/messages/useImageInspection.js +19 -9
- package/lib/types/components/promptDraftCache.d.ts +102 -0
- package/lib/types/components/promptDraftCache.d.ts.map +1 -0
- package/lib/types/components/promptDraftCache.js +76 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +17 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +40 -7
- package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
- package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
- package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
- package/lib/types/components/sessions/SessionListRow.d.ts +23 -1
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionListRow.js +52 -5
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
- package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
- package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
- package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
- package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
- package/lib/types/dsh-adapter/bundled-presets.js +80 -0
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/action-readiness.js +3 -0
- package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/agent-view-projection.js +19 -2
- package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/background-action.js +52 -29
- package/lib/types/dsh-adapter/channel/binding.d.ts +19 -2
- package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding.js +78 -18
- package/lib/types/dsh-adapter/channel/composer-images.d.ts +5 -0
- package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/composer-images.js +105 -6
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +8 -0
- package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/context-bookkeeping.js +3 -2
- package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
- package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
- package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts +6 -1
- package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/input-delivery.js +9 -1
- package/lib/types/dsh-adapter/channel/local-actions.d.ts +3 -19
- package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/local-actions.js +2 -1
- package/lib/types/dsh-adapter/channel/model-actions.d.ts +4 -1
- package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-actions.js +82 -6
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-switch.js +49 -31
- package/lib/types/dsh-adapter/channel/projection.d.ts +4 -0
- package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/projection.js +29 -19
- package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/reports.js +4 -12
- package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-fork.js +26 -2
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +3 -0
- package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-live-adoption.js +1 -0
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts +10 -0
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-metadata.js +2 -2
- package/lib/types/dsh-adapter/channel/session-resume.d.ts +11 -2
- package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-resume.js +291 -94
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-rewind.js +34 -16
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.js +19 -3
- package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/settings-host.js +4 -3
- package/lib/types/dsh-adapter/channel/state.d.ts +1 -1
- package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/state.js +1 -1
- package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/transcript.js +6 -12
- package/lib/types/dsh-adapter/channel/types.d.ts +6 -1
- package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +4 -1
- package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/workspace-actions.js +41 -1
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +67 -6
- package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
- package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/messages.js +15 -0
- package/lib/types/dsh-adapter/compat/settings.d.ts +33 -0
- package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/settings.js +43 -0
- package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
- package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
- package/lib/types/dsh-adapter/compat/shell.js +11 -0
- package/lib/types/dsh-adapter/contract.d.ts +4 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +15 -6
- package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
- package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
- package/lib/types/dsh-adapter/ide-channel.js +556 -0
- package/lib/types/dsh-adapter/index.d.ts +18 -3
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +32 -5
- package/lib/types/dsh-adapter/plugin.d.ts +5 -2
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +173 -42
- package/lib/types/dsh-adapter/preset-resolution.d.ts +8 -0
- package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
- package/lib/types/dsh-adapter/preset-resolution.js +10 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
- package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessionTree.js +3 -2
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +6 -5
- package/lib/types/dsh-adapter/workspaces.d.ts +46 -3
- package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
- package/lib/types/dsh-adapter/workspaces.js +116 -0
- package/lib/types/effortPrefs.d.ts +15 -3
- package/lib/types/effortPrefs.d.ts.map +1 -1
- package/lib/types/effortPrefs.js +37 -3
- package/lib/types/homePrefs.d.ts +15 -0
- package/lib/types/homePrefs.d.ts.map +1 -0
- package/lib/types/homePrefs.js +53 -0
- package/lib/types/i18n.d.ts +257 -296
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +78 -84
- package/lib/types/ink/colorize.d.ts +10 -0
- package/lib/types/ink/colorize.d.ts.map +1 -1
- package/lib/types/ink/colorize.js +21 -0
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.js +12 -1
- package/lib/types/ink/dom.d.ts +1 -0
- package/lib/types/ink/dom.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-copy-on-select.d.ts +5 -1
- package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
- package/lib/types/ink/hooks/use-copy-on-select.js +15 -1
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +36 -1
- package/lib/types/ink/output.d.ts +10 -1
- package/lib/types/ink/output.d.ts.map +1 -1
- package/lib/types/ink/output.js +109 -17
- package/lib/types/ink/render-border.d.ts +6 -1
- package/lib/types/ink/render-border.d.ts.map +1 -1
- package/lib/types/ink/render-border.js +25 -9
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +48 -6
- package/lib/types/ink/renderer.d.ts +6 -0
- package/lib/types/ink/renderer.d.ts.map +1 -1
- package/lib/types/ink/renderer.js +2 -1
- package/lib/types/ink/selection.d.ts +60 -0
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +129 -0
- package/lib/types/ink/sixel-graphics.d.ts +19 -1
- package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
- package/lib/types/ink/sixel-graphics.js +124 -12
- package/lib/types/ink/terminal-image.d.ts +18 -1
- package/lib/types/ink/terminal-image.d.ts.map +1 -1
- package/lib/types/ink/terminal.d.ts +17 -0
- package/lib/types/ink/terminal.d.ts.map +1 -1
- package/lib/types/ink/terminal.js +20 -1
- package/lib/types/screens/Chat.d.ts +17 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +284 -72
- package/lib/types/screens/SessionSupervisor.d.ts +84 -0
- package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
- package/lib/types/screens/SessionSupervisor.js +350 -0
- package/lib/types/screens/SessionTree.js +2 -2
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +32 -9
- package/lib/types/screens/StatusLine.d.ts +13 -0
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +36 -17
- package/lib/types/screens/StatusMetrics.d.ts +79 -22
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
- package/lib/types/screens/StatusMetrics.js +153 -88
- package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
- package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/model.js +69 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
- package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +593 -0
- package/lib/types/sessionMounts.d.ts +272 -0
- package/lib/types/sessionMounts.d.ts.map +1 -0
- package/lib/types/sessionMounts.js +706 -0
- package/lib/types/sessions/format.d.ts.map +1 -1
- package/lib/types/sessions/format.js +4 -1
- package/lib/types/sessions/resumeFailure.d.ts +38 -0
- package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
- package/lib/types/sessions/resumeFailure.js +58 -0
- package/lib/types/terminal-utils/mermaid.d.ts +44 -0
- package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
- package/lib/types/terminal-utils/mermaid.js +83 -0
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +175 -19
- package/lib/types/tuiDisplayPrefs.d.ts +7 -9
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
- package/lib/types/tuiDisplayPrefs.js +31 -28
- package/lib/types/update.d.ts +61 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +194 -0
- package/lib/types/utils/imageResize.d.ts +90 -0
- package/lib/types/utils/imageResize.d.ts.map +1 -0
- package/lib/types/utils/imageResize.js +272 -0
- package/lib/types/utils/keymap.d.ts +5 -1
- package/lib/types/utils/keymap.d.ts.map +1 -1
- package/lib/types/utils/keymap.js +23 -0
- package/lib/types/workspaces.d.ts +2 -1
- package/lib/types/workspaces.d.ts.map +1 -1
- package/lib/types/workspaces.js +1 -1
- package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +10 -10
- package/package.json +120 -103
- package/presets/liangshen/.dsh-tui-managed.json +1 -1
- package/presets/liangshen/agent.cordis.yml +15 -0
- package/lib/types/screens/AgentView.d.ts +0 -23
- package/lib/types/screens/AgentView.d.ts.map +0 -1
- package/lib/types/screens/AgentView.js +0 -546
- package/lib/types/screens/SessionBrowser.d.ts +0 -42
- package/lib/types/screens/SessionBrowser.d.ts.map +0 -1
- package/lib/types/screens/SessionBrowser.js +0 -911
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { rememberImagePath, transcriptImageFromAttachment } from '../transcript-images.js';
|
|
3
|
+
import { probeImageSize, adaptImageForAdmission, withinDimensionLimits } from '../../utils/imageResize.js';
|
|
3
4
|
import { mentionAttachments } from './mentions.js';
|
|
4
5
|
/** Every visible composer placeholder token, in source order. */
|
|
5
6
|
export const COMPOSER_IMAGE_TOKEN = /\[Image #\d+\]/gu;
|
|
@@ -45,6 +46,24 @@ export function firstStaleComposerToken(text, ordered) {
|
|
|
45
46
|
* content-addressed and durable; this map only connects editable
|
|
46
47
|
* placeholders to them. */
|
|
47
48
|
const STAGED_IMAGE_LIMIT = 128;
|
|
49
|
+
/** Failure text for a refusal the ingress gate can state before the store:
|
|
50
|
+
* every reason except a missing sharp describes an image the deployment
|
|
51
|
+
* would reject anyway, so the paste is refused with the cause named. */
|
|
52
|
+
function admissionFailure(outcome, context) {
|
|
53
|
+
switch (outcome.reason) {
|
|
54
|
+
case 'sharp-missing':
|
|
55
|
+
return context.probe !== null
|
|
56
|
+
? `image is ${context.probe.width}×${context.probe.height} (over ${context.limits.maxImageDimension}px) and sharp is unavailable to resample it`
|
|
57
|
+
: `${context.mediaType} is not accepted by this profile and sharp is unavailable to convert it`;
|
|
58
|
+
case 'animated-unsupported':
|
|
59
|
+
return `resizing or converting this animated image cannot keep its frames (${outcome.detail})`;
|
|
60
|
+
default:
|
|
61
|
+
return `image could not be decoded to resize or convert it (${outcome.detail})`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** A store-reported pixel dimension, or the gate's own value when the store
|
|
65
|
+
* reported none (lean attachment fakes in the verify scripts). */
|
|
66
|
+
const positiveOr = (value, fallback) => typeof value === 'number' && value > 0 ? value : fallback;
|
|
48
67
|
/**
|
|
49
68
|
* Owns the editable-composer image capabilities: the session epoch, the
|
|
50
69
|
* capability map, its preview facades and the `[Image #N]` compatibility
|
|
@@ -121,16 +140,78 @@ export function createComposerImages(ctx, owner, deps) {
|
|
|
121
140
|
if (generation !== stagedImageEpoch) {
|
|
122
141
|
throw new Error('the session changed while the image was being staged');
|
|
123
142
|
}
|
|
124
|
-
if (!attachments.imageLimits.mediaTypes.includes(input.mediaType)) {
|
|
125
|
-
throw new Error(`${input.mediaType} images are not accepted by this profile`);
|
|
126
|
-
}
|
|
127
143
|
if (input.data.byteLength > attachments.imageLimits.maxImageBytes) {
|
|
128
144
|
throw new Error(`image exceeds this profile's per-image size limit`);
|
|
129
145
|
}
|
|
146
|
+
// Ingress adaptation (issue #938): the store admits by media type, byte
|
|
147
|
+
// budget and the per-side / total pixel caps, and refuses on all of them
|
|
148
|
+
// when the bytes reach it. Measure and re-encode HERE instead, while the
|
|
149
|
+
// paste can still be explained to the user. Admission is judged against
|
|
150
|
+
// the pair this gate HANDS OVER (the store may normalize further on its
|
|
151
|
+
// own — that step is outside this feature), and both triggers are
|
|
152
|
+
// size-independent:
|
|
153
|
+
// * a source media type the profile does not accept is converted, so a
|
|
154
|
+
// small PNG is never refused while a large one succeeds;
|
|
155
|
+
// * a probe-recognized image inside the caps skips sharp entirely;
|
|
156
|
+
// * an unrecognized format is decoded for measurement (never trusted by
|
|
157
|
+
// byte budget alone — the #432 gap);
|
|
158
|
+
// * with sharp absent the image goes to the store unchanged ONLY while
|
|
159
|
+
// it is still admissible as-is — an accepted format whose size the
|
|
160
|
+
// probe could not prove fits (upstream stays the backstop); a measured
|
|
161
|
+
// oversize, a decode failure, an unaccepted format or a multi-frame
|
|
162
|
+
// image this gate would have to re-encode is reported now instead of
|
|
163
|
+
// becoming a token that dies at save time.
|
|
164
|
+
// The runtime guards keep older attachment fakes (whose imageLimits
|
|
165
|
+
// predate the dimension fields) on the legacy synchronous path.
|
|
166
|
+
const dimensionCap = attachments.imageLimits.maxImageDimension;
|
|
167
|
+
const pixelCap = attachments.imageLimits.maxImagePixels;
|
|
168
|
+
let mediaType = input.mediaType;
|
|
169
|
+
let data = input.data;
|
|
170
|
+
let adapted;
|
|
171
|
+
if (typeof dimensionCap === 'number' && typeof pixelCap === 'number'
|
|
172
|
+
&& Number.isFinite(dimensionCap) && Number.isFinite(pixelCap)) {
|
|
173
|
+
const dimensionLimits = { maxImageDimension: dimensionCap, maxImagePixels: pixelCap };
|
|
174
|
+
const probe = probeImageSize(data);
|
|
175
|
+
const sourceAccepted = attachments.imageLimits.mediaTypes.includes(mediaType);
|
|
176
|
+
// Only a probe-measured oversize is *known* to be refused by the store;
|
|
177
|
+
// an unmeasurable image may well be admissible, which is what keeps the
|
|
178
|
+
// sharp-less degradation a degradation rather than a guess.
|
|
179
|
+
const knownOversize = probe !== null && !withinDimensionLimits(probe, dimensionLimits);
|
|
180
|
+
if (!sourceAccepted || probe === null || knownOversize) {
|
|
181
|
+
const outcome = await adaptImageForAdmission(data, mediaType, dimensionLimits, attachments.imageLimits.mediaTypes);
|
|
182
|
+
if (outcome.kind === 'adapted') {
|
|
183
|
+
// outcome.mediaType comes from the accepted list inside the gate, so
|
|
184
|
+
// it is one of the profile's media types at runtime — narrowing,
|
|
185
|
+
// not trusting.
|
|
186
|
+
mediaType = outcome.mediaType;
|
|
187
|
+
data = outcome.data;
|
|
188
|
+
adapted = outcome;
|
|
189
|
+
if (data.byteLength > attachments.imageLimits.maxImageBytes) {
|
|
190
|
+
throw new Error(`image still exceeds this profile's per-image size limit after resampling`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (outcome.kind === 'unavailable') {
|
|
194
|
+
// Degrade ONLY when the paste is still admissible as-is: an accepted
|
|
195
|
+
// format whose size the probe could not prove fits. Everything else
|
|
196
|
+
// (measured oversize, unaccepted format, decode failure, animation
|
|
197
|
+
// that cannot survive) is a refusal we can state now.
|
|
198
|
+
const degraded = outcome.reason === 'sharp-missing' && sourceAccepted && !knownOversize;
|
|
199
|
+
if (!degraded)
|
|
200
|
+
throw new Error(admissionFailure(outcome, { probe, mediaType, limits: dimensionLimits }));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// What the store will actually see: an accepted media type, inside the
|
|
205
|
+
// byte cap. A media type can still be unaccepted here only when the
|
|
206
|
+
// adaptation did not run (legacy limits) or could not produce one.
|
|
207
|
+
if (!attachments.imageLimits.mediaTypes.includes(mediaType)) {
|
|
208
|
+
throw new Error(`${mediaType} images are not accepted by this profile`);
|
|
209
|
+
}
|
|
130
210
|
// The source path is TUI-side display metadata; the store only sees the
|
|
131
|
-
// fields its contract names.
|
|
211
|
+
// fields its contract names. mediaType/data may be the adapted pair from
|
|
212
|
+
// the ingress gate above.
|
|
132
213
|
const { path, ...stored } = input;
|
|
133
|
-
const attachment = await attachments.saveImage(stored);
|
|
214
|
+
const attachment = await attachments.saveImage({ ...stored, mediaType, data });
|
|
134
215
|
// A session change (/new, resume, rewind, model switch, background)
|
|
135
216
|
// cleared the maps while the save was in flight: the durable object is
|
|
136
217
|
// harmless, but the OLD session's capability must not reach the new one.
|
|
@@ -151,7 +232,23 @@ export function createComposerImages(ctx, owner, deps) {
|
|
|
151
232
|
break;
|
|
152
233
|
deleteStagedImage(oldest);
|
|
153
234
|
}
|
|
154
|
-
|
|
235
|
+
// The adjustment travels with the capability so the composer can say what
|
|
236
|
+
// happened instead of letting a rewritten image reach the store silently.
|
|
237
|
+
// Its numbers come from the STORE's own report, not from what this gate
|
|
238
|
+
// handed over: the store normalizes further (dimensions, and the media type
|
|
239
|
+
// too), and a notice quoting the gate's numbers would contradict the
|
|
240
|
+
// preview card the user is looking at.
|
|
241
|
+
const adjustment = adapted === undefined ? undefined : {
|
|
242
|
+
sourceMediaType: input.mediaType,
|
|
243
|
+
mediaType: attachment.mediaType,
|
|
244
|
+
width: positiveOr(attachment.width, adapted.width),
|
|
245
|
+
height: positiveOr(attachment.height, adapted.height),
|
|
246
|
+
resized: adapted.resized
|
|
247
|
+
|| positiveOr(attachment.width, adapted.width) !== adapted.width
|
|
248
|
+
|| positiveOr(attachment.height, adapted.height) !== adapted.height,
|
|
249
|
+
flattened: adapted.flattened,
|
|
250
|
+
};
|
|
251
|
+
return adjustment === undefined ? { stageId } : { stageId, adjustment };
|
|
155
252
|
};
|
|
156
253
|
return {
|
|
157
254
|
stagedImageGeneration() {
|
|
@@ -195,6 +292,8 @@ export function createComposerImages(ctx, owner, deps) {
|
|
|
195
292
|
return {
|
|
196
293
|
maxImageBytes: limits.maxImageBytes,
|
|
197
294
|
maxImagesPerMessage: limits.maxImagesPerMessage,
|
|
295
|
+
maxImageDimension: limits.maxImageDimension,
|
|
296
|
+
maxImagePixels: limits.maxImagePixels,
|
|
198
297
|
};
|
|
199
298
|
},
|
|
200
299
|
clearStagedImages() {
|
|
@@ -5,6 +5,14 @@ export declare function createContextBookkeeping(state: () => {
|
|
|
5
5
|
tokens: {
|
|
6
6
|
input: number;
|
|
7
7
|
};
|
|
8
|
+
/** Last turn's billed token shape; the honest numerator for the
|
|
9
|
+
* context-low warning (input alone misses cached reads/writes, which
|
|
10
|
+
* resumed sessions replay back at full size). */
|
|
11
|
+
lastUsage: {
|
|
12
|
+
input: number;
|
|
13
|
+
cacheRead: number;
|
|
14
|
+
cacheWrite: number;
|
|
15
|
+
} | undefined;
|
|
8
16
|
pending: PendingMessage[];
|
|
9
17
|
emit(): void;
|
|
10
18
|
}, notify: (text: string, options: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-bookkeeping.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/context-bookkeeping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAElE,+EAA+E;AAC/E,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM;IACX,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,IAAI,IAAI,IAAI,CAAA;CACb,EACD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,EACnF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EAC3C,mBAAmB,EAAE,MAAM;;;;+
|
|
1
|
+
{"version":3,"file":"context-bookkeeping.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/context-bookkeeping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAElE,+EAA+E;AAC/E,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM;IACX,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB;;qDAEiD;IACjD,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;IAC/E,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,IAAI,IAAI,IAAI,CAAA;CACb,EACD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,EACnF,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,EAC3C,mBAAmB,EAAE,MAAM;;;;+BAyBK,IAAI;+BAtBJ,IAAI;4BAWL;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;KAAE,aAAa,cAAc,CAAC,WAAW,CAAC,KAAG,IAAI;gCAK7G,MAAM,KAAG,IAAI;EAQjD"}
|
|
@@ -3,9 +3,10 @@ export function createContextBookkeeping(state, notify, lowContextText, warningB
|
|
|
3
3
|
const warning = { value: false };
|
|
4
4
|
const checkContextWarning = () => {
|
|
5
5
|
const channel = state();
|
|
6
|
-
if (warning.value || channel.contextWindow === undefined)
|
|
6
|
+
if (warning.value || channel.contextWindow === undefined || channel.lastUsage === undefined)
|
|
7
7
|
return;
|
|
8
|
-
const
|
|
8
|
+
const used = channel.lastUsage.input + channel.lastUsage.cacheRead + channel.lastUsage.cacheWrite;
|
|
9
|
+
const remaining = channel.contextWindow - used;
|
|
9
10
|
if (remaining >= warningBufferTokens)
|
|
10
11
|
return;
|
|
11
12
|
warning.value = true;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ChannelSelection, SelectionAttachment } from '../../adapter/ports/channel-view.js';
|
|
2
|
+
import type { MentionExpansion, MentionFs } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Escape a path for reuse inside a quoted `<attached-file path="…">` attribute:
|
|
5
|
+
* POSIX filenames are legal with `"`, `&`, `<`, `>` — a raw interpolated path
|
|
6
|
+
* could break out of the attribute or smuggle markup into the model-facing
|
|
7
|
+
* block. `&` first so the replacements themselves are not re-escaped.
|
|
8
|
+
*/
|
|
9
|
+
export declare function escapeSnippetAttr(value: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Build the `<attached-file path="…" selection>` block for an IDE selection:
|
|
12
|
+
* coordinates arrive 0-based inclusive from the extension and convert to
|
|
13
|
+
* sliceLines' 1-based; an endLine past EOF clamps to the last line (the model
|
|
14
|
+
* receives every remaining line), while a startLine past EOF yields undefined
|
|
15
|
+
* — the caller silently skips instead of attaching an empty or stale block.
|
|
16
|
+
* An empty selection is rejected here too so the guard cannot be bypassed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildSelectionBlock(selection: ChannelSelection, content: string): {
|
|
19
|
+
text: string;
|
|
20
|
+
lines: number;
|
|
21
|
+
} | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Append the selection's attached-file block to a message's content. Returns
|
|
24
|
+
* what was attached ({lines, path}) or undefined when there is nothing to
|
|
25
|
+
* attach — no selection, or an unresolvable/unreadable file. The caller
|
|
26
|
+
* passes the ENQUEUE-time snapshot (and its fs handle) so a selection made
|
|
27
|
+
* after submit can never attach to the wrong message.
|
|
28
|
+
*
|
|
29
|
+
* Protocol 2 carries the editor buffer's own `text` (unsaved edits
|
|
30
|
+
* included): when present it is attached VERBATIM and the filesystem is not
|
|
31
|
+
* touched — reading the disk copy would hand the model a version the user
|
|
32
|
+
* never saw (maintainer review round 3). The disk read remains only as the
|
|
33
|
+
* protocol-1 fallback.
|
|
34
|
+
*/
|
|
35
|
+
export declare function attachIdeSelection(blocks: MentionExpansion['blocks'], cwd: string, selection: ChannelSelection | undefined, fs: MentionFs | undefined): Promise<SelectionAttachment | undefined>;
|
|
36
|
+
/**
|
|
37
|
+
* Derive the transcript indicator from the DURABLE user-message content:
|
|
38
|
+
* the `<attached-file path="…" selection count="N">` block the submit path
|
|
39
|
+
* appended is part of the persisted event, so a replayed session can rebuild
|
|
40
|
+
* the "Selected N lines from <file>" line even though the in-memory
|
|
41
|
+
* message-id → attachment map starts empty (maintainer review round 3: the
|
|
42
|
+
* session log is the source of truth — the indicator must not depend on
|
|
43
|
+
* process-local state).
|
|
44
|
+
*/
|
|
45
|
+
export declare function replaySelectionAttachment(content: readonly unknown[] | undefined): SelectionAttachment | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Bounded map from a submitted message id → what its selection attached.
|
|
48
|
+
* The delivery path remembers at submit; the transcript projection reads it
|
|
49
|
+
* when the durable `user/message` event lands (the event carries the same
|
|
50
|
+
* message id) to stamp the row's indicator. FIFO-capped like the other
|
|
51
|
+
* row-keyed caches — ids grow monotonically and a miss only loses the
|
|
52
|
+
* indicator, never the attachment itself.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createSelectionAttachments(): {
|
|
55
|
+
remember(messageId: string, info: SelectionAttachment): void;
|
|
56
|
+
take(messageId: string): SelectionAttachment | undefined;
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=ide-selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ide-selection.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/ide-selection.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AAEhG,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAuB7D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAyCD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAc7C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,gBAAgB,GAAG,SAAS,EACvC,EAAE,EAAE,SAAS,GAAG,SAAS,GACxB,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAoC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GACtC,mBAAmB,GAAG,SAAS,CA8BjC;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,IAAI;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAC5D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAA;CACzD,CAeA"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE selection consumption: turn a live editor-selection snapshot into the
|
|
3
|
+
* model-facing `<attached-file … selection>` block. Protocol 2 pushes carry
|
|
4
|
+
* the editor buffer's own selection text — that is attached verbatim; the
|
|
5
|
+
* disk read below remains only as the protocol-1 fallback, and every failure
|
|
6
|
+
* mode is silent (an IDE-side extra must never block a send).
|
|
7
|
+
*
|
|
8
|
+
* The block builder never goes through text parsing (unlike `@`-mentions):
|
|
9
|
+
* selection paths may contain spaces no tokenizer could survive. It reuses
|
|
10
|
+
* the same line-slice semantics and size-cap policy as mention attachments.
|
|
11
|
+
*/
|
|
12
|
+
import { isAbsolute, join } from 'node:path';
|
|
13
|
+
import { MENTION_MAX_FILE_CHARS } from './mentions.js';
|
|
14
|
+
/**
|
|
15
|
+
* Slice `content` into the 1-based inclusive [`startLine`, `endLine`] range.
|
|
16
|
+
* Returns undefined for malformed ranges (non-safe integers, zero/negative
|
|
17
|
+
* start, inverted) and for a start past EOF — counted against REAL lines: a
|
|
18
|
+
* trailing newline splits into a phantom final element that is not a line.
|
|
19
|
+
*/
|
|
20
|
+
function sliceLines(content, startLine, endLine) {
|
|
21
|
+
const end = endLine ?? startLine;
|
|
22
|
+
if (!Number.isSafeInteger(startLine) || startLine < 1)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (!Number.isSafeInteger(end) || end < startLine)
|
|
25
|
+
return undefined;
|
|
26
|
+
const lines = content.split('\n');
|
|
27
|
+
// A trailing newline splits into a phantom final element that is not a line.
|
|
28
|
+
if (lines.length > 1 && lines[lines.length - 1] === '')
|
|
29
|
+
lines.pop();
|
|
30
|
+
if (startLine > lines.length)
|
|
31
|
+
return undefined;
|
|
32
|
+
return lines.slice(startLine - 1, end).join('\n');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Escape a path for reuse inside a quoted `<attached-file path="…">` attribute:
|
|
36
|
+
* POSIX filenames are legal with `"`, `&`, `<`, `>` — a raw interpolated path
|
|
37
|
+
* could break out of the attribute or smuggle markup into the model-facing
|
|
38
|
+
* block. `&` first so the replacements themselves are not re-escaped.
|
|
39
|
+
*/
|
|
40
|
+
export function escapeSnippetAttr(value) {
|
|
41
|
+
return value.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Format the attached-file block from an ALREADY-SLICED body: cap it like
|
|
45
|
+
* @-mention attachments (same MENTION_MAX_FILE_CHARS policy, same visible
|
|
46
|
+
* truncation marker) and count the lines the model gets — the truncated body,
|
|
47
|
+
* not the request (review round 3).
|
|
48
|
+
*
|
|
49
|
+
* `allowEmpty` keeps a selection that covers ONE blank line: the body is
|
|
50
|
+
* legitimately empty there while the badge still says "1 line selected", and
|
|
51
|
+
* dropping the block made the footer and the transcript disagree (review
|
|
52
|
+
* round 7). Callers pass it only for a single-line range that really exists.
|
|
53
|
+
*/
|
|
54
|
+
function cappedSelectionBlock(path, sliced, allowEmpty = false) {
|
|
55
|
+
if (sliced === '' && !allowEmpty)
|
|
56
|
+
return undefined;
|
|
57
|
+
let body = sliced;
|
|
58
|
+
let attached = sliced;
|
|
59
|
+
if (body.length > MENTION_MAX_FILE_CHARS) {
|
|
60
|
+
attached = body.slice(0, MENTION_MAX_FILE_CHARS);
|
|
61
|
+
body = `${attached}\n[… truncated]`;
|
|
62
|
+
}
|
|
63
|
+
// `count` is the number of lines in the attached BODY — the truncation
|
|
64
|
+
// marker line is not counted (it rides on top of the cap and the live
|
|
65
|
+
// indicator reports the same number). It exists so the replay path never has
|
|
66
|
+
// to GUESS that number from the body tail: a selection whose last line is
|
|
67
|
+
// literally `[… truncated]` used to lose one line when the indicator was
|
|
68
|
+
// rebuilt from the persisted event (fuzz rounds found 46/400 such bodies).
|
|
69
|
+
// Deliberately NOT `lines`: @-mention attachments already use `lines="2-4"`
|
|
70
|
+
// for the REQUESTED range, and overloading the key with a count would make
|
|
71
|
+
// the two block flavours read alike.
|
|
72
|
+
const lines = attached.split('\n').length;
|
|
73
|
+
return {
|
|
74
|
+
text: `<attached-file path="${escapeSnippetAttr(path)}" selection count="${lines}">\n${body}\n</attached-file>`,
|
|
75
|
+
lines,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build the `<attached-file path="…" selection>` block for an IDE selection:
|
|
80
|
+
* coordinates arrive 0-based inclusive from the extension and convert to
|
|
81
|
+
* sliceLines' 1-based; an endLine past EOF clamps to the last line (the model
|
|
82
|
+
* receives every remaining line), while a startLine past EOF yields undefined
|
|
83
|
+
* — the caller silently skips instead of attaching an empty or stale block.
|
|
84
|
+
* An empty selection is rejected here too so the guard cannot be bypassed.
|
|
85
|
+
*/
|
|
86
|
+
export function buildSelectionBlock(selection, content) {
|
|
87
|
+
if (selection.isEmpty)
|
|
88
|
+
return undefined;
|
|
89
|
+
// 0-based inclusive [start, end] → 1-based inclusive [start+1, end+1].
|
|
90
|
+
// sliceLines clamps an oversized end itself (Array#slice bounds); a start
|
|
91
|
+
// past EOF returns undefined and the whole attach is silently dropped.
|
|
92
|
+
const sliced = sliceLines(content, selection.startLine + 1, selection.endLine + 1);
|
|
93
|
+
if (sliced === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
// sliceLines returning '' (not undefined) means the requested line EXISTS and
|
|
96
|
+
// is blank — a single blank line is a real selection the badge reports.
|
|
97
|
+
return cappedSelectionBlock(selection.path, sliced, selection.startLine === selection.endLine);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Append the selection's attached-file block to a message's content. Returns
|
|
101
|
+
* what was attached ({lines, path}) or undefined when there is nothing to
|
|
102
|
+
* attach — no selection, or an unresolvable/unreadable file. The caller
|
|
103
|
+
* passes the ENQUEUE-time snapshot (and its fs handle) so a selection made
|
|
104
|
+
* after submit can never attach to the wrong message.
|
|
105
|
+
*
|
|
106
|
+
* Protocol 2 carries the editor buffer's own `text` (unsaved edits
|
|
107
|
+
* included): when present it is attached VERBATIM and the filesystem is not
|
|
108
|
+
* touched — reading the disk copy would hand the model a version the user
|
|
109
|
+
* never saw (maintainer review round 3). The disk read remains only as the
|
|
110
|
+
* protocol-1 fallback.
|
|
111
|
+
*/
|
|
112
|
+
export async function attachIdeSelection(blocks, cwd, selection, fs) {
|
|
113
|
+
if (selection === undefined || selection.isEmpty)
|
|
114
|
+
return undefined;
|
|
115
|
+
if (selection.text !== undefined) {
|
|
116
|
+
// Protocol 2: `text` is the editor's OWN text for the selection
|
|
117
|
+
// (document.getText(selection)) — the ABSOLUTE document coordinates ride
|
|
118
|
+
// along for the badge/indicator, but they must NOT slice this string:
|
|
119
|
+
// it already contains exactly the selected lines, so anything not
|
|
120
|
+
// starting at line 0 would be mis-cut or dropped entirely (review
|
|
121
|
+
// round 4). Attach it verbatim with the shared size cap. A full-line
|
|
122
|
+
// selection ends at column 0 of the NEXT line, so getText hands back a
|
|
123
|
+
// trailing '\n' — strip that one terminator or the body grows a phantom
|
|
124
|
+
// blank line and the indicator over-counts by one (review round 5).
|
|
125
|
+
let text = selection.text;
|
|
126
|
+
if (text.endsWith('\n'))
|
|
127
|
+
text = text.slice(0, -1);
|
|
128
|
+
// Triple-clicking ONE blank line hands back "\n": after the strip the body
|
|
129
|
+
// is empty but a line WAS selected (the footer badge says so) — keep the
|
|
130
|
+
// block so the indicator and the badge agree instead of attaching nothing.
|
|
131
|
+
const block = cappedSelectionBlock(selection.path, text, selection.text !== '');
|
|
132
|
+
if (block === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
blocks.push({ type: 'text', text: block.text });
|
|
135
|
+
return { lines: block.lines, path: selection.path };
|
|
136
|
+
}
|
|
137
|
+
if (fs === undefined)
|
|
138
|
+
return undefined;
|
|
139
|
+
try {
|
|
140
|
+
const absolute = isAbsolute(selection.path) ? selection.path : join(cwd, selection.path);
|
|
141
|
+
const target = await fs.resolve(absolute);
|
|
142
|
+
const info = await fs.stat(target);
|
|
143
|
+
if (info?.type !== 'file')
|
|
144
|
+
return undefined;
|
|
145
|
+
const content = await fs.readText(target);
|
|
146
|
+
const block = buildSelectionBlock(selection, content);
|
|
147
|
+
if (block === undefined)
|
|
148
|
+
return undefined;
|
|
149
|
+
blocks.push({ type: 'text', text: block.text });
|
|
150
|
+
return { lines: block.lines, path: selection.path };
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Derive the transcript indicator from the DURABLE user-message content:
|
|
158
|
+
* the `<attached-file path="…" selection count="N">` block the submit path
|
|
159
|
+
* appended is part of the persisted event, so a replayed session can rebuild
|
|
160
|
+
* the "Selected N lines from <file>" line even though the in-memory
|
|
161
|
+
* message-id → attachment map starts empty (maintainer review round 3: the
|
|
162
|
+
* session log is the source of truth — the indicator must not depend on
|
|
163
|
+
* process-local state).
|
|
164
|
+
*/
|
|
165
|
+
export function replaySelectionAttachment(content) {
|
|
166
|
+
if (content === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
for (const block of content) {
|
|
169
|
+
if (block === null || typeof block !== 'object')
|
|
170
|
+
continue;
|
|
171
|
+
const text = block.text;
|
|
172
|
+
if (block.type !== 'text' || typeof text !== 'string')
|
|
173
|
+
continue;
|
|
174
|
+
const opened = /^<attached-file path="([^"]+)" selection(?: count="(\d+)")?>\n/.exec(text);
|
|
175
|
+
if (opened === null)
|
|
176
|
+
continue;
|
|
177
|
+
let closed = text.slice(opened[0].length);
|
|
178
|
+
if (closed.endsWith('</attached-file>'))
|
|
179
|
+
closed = closed.slice(0, -'</attached-file>'.length);
|
|
180
|
+
// The builder writes `body\n</attached-file>` — strip the newline the
|
|
181
|
+
// close tag rode on, or every body counts one phantom line.
|
|
182
|
+
if (closed.endsWith('\n'))
|
|
183
|
+
closed = closed.slice(0, -1);
|
|
184
|
+
const lines = closed.split('\n').length;
|
|
185
|
+
// `count` is authoritative when present (the builder always writes it).
|
|
186
|
+
// Blocks persisted before it existed fall back to the old tail heuristic,
|
|
187
|
+
// which misreads a body whose last line is literally the truncation marker.
|
|
188
|
+
const counted = opened[2] === undefined ? undefined : Number(opened[2]);
|
|
189
|
+
return {
|
|
190
|
+
lines: counted ?? (closed.endsWith('\n[… truncated]') ? lines - 1 : lines),
|
|
191
|
+
// Reverse escapeSnippetAttr so the replayed indicator shows the path
|
|
192
|
+
// exactly as the live one did (`&` first so entities are not re-baked).
|
|
193
|
+
path: opened[1]
|
|
194
|
+
.replace(/</g, '<')
|
|
195
|
+
.replace(/>/g, '>')
|
|
196
|
+
.replace(/"/g, '"')
|
|
197
|
+
.replace(/&/g, '&'),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Bounded map from a submitted message id → what its selection attached.
|
|
204
|
+
* The delivery path remembers at submit; the transcript projection reads it
|
|
205
|
+
* when the durable `user/message` event lands (the event carries the same
|
|
206
|
+
* message id) to stamp the row's indicator. FIFO-capped like the other
|
|
207
|
+
* row-keyed caches — ids grow monotonically and a miss only loses the
|
|
208
|
+
* indicator, never the attachment itself.
|
|
209
|
+
*/
|
|
210
|
+
export function createSelectionAttachments() {
|
|
211
|
+
const byMessageId = new Map();
|
|
212
|
+
return {
|
|
213
|
+
remember(messageId, info) {
|
|
214
|
+
byMessageId.set(messageId, info);
|
|
215
|
+
while (byMessageId.size > 128) {
|
|
216
|
+
const oldest = byMessageId.keys().next().value;
|
|
217
|
+
if (oldest === undefined)
|
|
218
|
+
break;
|
|
219
|
+
byMessageId.delete(oldest);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
take(messageId) {
|
|
223
|
+
return byMessageId.get(messageId);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
|
3
3
|
import { type UserMessage } from '@deepseek-ai/dsh-llm';
|
|
4
4
|
import { type ComposerImages } from './composer-images.js';
|
|
5
5
|
import type { ChannelOwner } from './owner.js';
|
|
6
|
+
import type { ChannelSelection, SelectionAttachment } from '../../adapter/ports/channel-view.js';
|
|
6
7
|
import type { ChannelImageBlock, ChannelState, ComposerImageRef, MentionAttachments, MentionFs, PendingMessage, StagedImageInput } from './types.js';
|
|
7
8
|
/** One submission's enqueue-time world: the session it was typed in, the
|
|
8
9
|
* services that resolve its references, and the capabilities live then. */
|
|
@@ -14,6 +15,10 @@ interface UserTextOrigin {
|
|
|
14
15
|
readonly fs: MentionFs | undefined;
|
|
15
16
|
readonly attachments: MentionAttachments | undefined;
|
|
16
17
|
readonly stagedImages: ReadonlyMap<string, ChannelImageBlock['attachment']>;
|
|
18
|
+
/** Live editor selection AT ENQUEUE (undefined = none). Captured here so a
|
|
19
|
+
* selection made while the FIFO or mention expansion parks the delivery
|
|
20
|
+
* can never attach to this message. */
|
|
21
|
+
readonly selection: ChannelSelection | undefined;
|
|
17
22
|
}
|
|
18
23
|
/** Input FIFO, staged attachments and decision notice timers share one lifetime. */
|
|
19
24
|
export declare function createInputDelivery(ctx: Context, owner: ChannelOwner, binding: {
|
|
@@ -22,7 +27,7 @@ export declare function createInputDelivery(ctx: Context, owner: ChannelOwner, b
|
|
|
22
27
|
id: string;
|
|
23
28
|
text: string;
|
|
24
29
|
images?: readonly ComposerImageRef[];
|
|
25
|
-
}, placement: PendingMessage['placement']) => void, untrackPending: (id: string) => void, composer: ComposerImages): {
|
|
30
|
+
}, placement: PendingMessage['placement']) => void, untrackPending: (id: string) => void, composer: ComposerImages, selection: () => ChannelSelection | undefined, rememberSelection: (messageId: string, info: SelectionAttachment) => void): {
|
|
26
31
|
dispatchUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[], attach?: UserMessage) => void;
|
|
27
32
|
deliverUserText: (text: string, placement: PendingMessage["placement"], images?: readonly ComposerImageRef[], attach?: UserMessage) => void;
|
|
28
33
|
claimAttachments: (messages: readonly UserMessage[]) => UserMessage[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-delivery.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/input-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAI1E,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"input-delivery.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/input-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAI1E,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAA;AAK7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAA;AAChG,OAAO,KAAK,EACV,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAEnB;4EAC4E;AAC5E,UAAU,cAAc;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,SAAS,GAAG,SAAS,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACpD,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAA;IAC3E;;4CAEwC;IACxC,QAAQ,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAA;CACjD;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,CAClC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CAAE,EACrE,KAAK,EAAE,MAAM,IAAI,CAAC,YAAY,EAAE,KAAK,GAAG,SAAS,GAAG,wBAAwB,CAAC,EAC7E,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,EAC9B,YAAY,EAAE,CAAC,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,EAAE,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,KAAK,IAAI,EAC3I,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACpC,QAAQ,EAAE,cAAc,EACxB,SAAS,EAAE,MAAM,gBAAgB,GAAG,SAAS,EAC7C,iBAAiB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,KAAK,IAAI;6BAkRhE,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;4BAcC,MAAM,aACD,cAAc,CAAC,WAAW,CAAC,WAC9B,SAAS,gBAAgB,EAAE,WAC1B,WAAW,KACnB,IAAI;iCAlR6B,SAAS,WAAW,EAAE,KAAG,WAAW,EAAE;kCAarC,MAAM,KAAG,IAAI;0BAuIrB,CAAC,QAAQ,MAAM,WAAW,OAAO,CAAC,CAAC,CAAC,WAAW,cAAc,KAAG,OAAO,CAAC,CAAC,CAAC;6BAkIzE,IAAI;wBAaZ,gBAAgB,KAAG,OAAO,CAAC,MAAM,CAAC;wBACpC,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;;EAGzE"}
|
|
@@ -5,9 +5,10 @@ import { dispatchTuiDecision } from '../extension-events.js';
|
|
|
5
5
|
import { firstStaleComposerToken, formatMissingReference, orderedComposerImages, } from './composer-images.js';
|
|
6
6
|
import { expandComposerMentions } from './composer-mentions.js';
|
|
7
7
|
import { normalizeInputDecision } from './decisions.js';
|
|
8
|
+
import { attachIdeSelection } from './ide-selection.js';
|
|
8
9
|
import { mentionAttachments, mentionFs } from './mentions.js';
|
|
9
10
|
/** Input FIFO, staged attachments and decision notice timers share one lifetime. */
|
|
10
|
-
export function createInputDelivery(ctx, owner, binding, state, notify, trackPending, untrackPending, composer) {
|
|
11
|
+
export function createInputDelivery(ctx, owner, binding, state, notify, trackPending, untrackPending, composer, selection, rememberSelection) {
|
|
11
12
|
/**
|
|
12
13
|
* `@` file mentions (issue #15): expansion reads files asynchronously, so
|
|
13
14
|
* every user-text delivery (submit / steer / interrupt-requeue) funnels
|
|
@@ -82,6 +83,7 @@ export function createInputDelivery(ctx, owner, binding, state, notify, trackPen
|
|
|
82
83
|
fs: mentionFs(ctx),
|
|
83
84
|
attachments: mentionAttachments(ctx),
|
|
84
85
|
stagedImages: composer.snapshot(),
|
|
86
|
+
selection: selection(),
|
|
85
87
|
});
|
|
86
88
|
/**
|
|
87
89
|
* Expand the text's `@` mentions and deliver ONE user message: the typed
|
|
@@ -107,10 +109,16 @@ export function createInputDelivery(ctx, owner, binding, state, notify, trackPen
|
|
|
107
109
|
notify(t('ext-stale-dropped'), { color: 'warning', timeoutMs: 4000 });
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
112
|
+
// IDE selection consumption: append the ENQUEUE-time selection as its own
|
|
113
|
+
// attached-file block — direct construction, never text parsing, failures
|
|
114
|
+
// silently skipped (an IDE-side extra must never block a send).
|
|
115
|
+
const selectionAttached = await attachIdeSelection(expansion.blocks, origin.cwd, origin.selection, origin.fs);
|
|
110
116
|
const message = createUserMessage({
|
|
111
117
|
content: expansion.blocks,
|
|
112
118
|
source: { kind: 'user' },
|
|
113
119
|
});
|
|
120
|
+
if (selectionAttached !== undefined)
|
|
121
|
+
rememberSelection(message.id, selectionAttached);
|
|
114
122
|
// The message is real from here on: remember its attached context BEFORE
|
|
115
123
|
// the agent call so the pre-step listener can find it (D6). A throwing
|
|
116
124
|
// followup/steer rolls both the pending preview and this entry back.
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
+
import { type ForegroundShell } from '../compat/shell.js';
|
|
1
2
|
import { type foldBack as FoldBack } from './transcript.js';
|
|
2
3
|
import type { ChannelState, ToolViewPresenter } from './types.js';
|
|
3
|
-
type Shell = {
|
|
4
|
-
resolve(request: {
|
|
5
|
-
command: string;
|
|
6
|
-
workdir?: string;
|
|
7
|
-
timeoutMs: number;
|
|
8
|
-
}): unknown;
|
|
9
|
-
run(spec: unknown): Promise<{
|
|
10
|
-
stdout: {
|
|
11
|
-
text: string;
|
|
12
|
-
};
|
|
13
|
-
stderr: {
|
|
14
|
-
text: string;
|
|
15
|
-
};
|
|
16
|
-
timedOut: boolean;
|
|
17
|
-
}>;
|
|
18
|
-
};
|
|
19
4
|
/** Owns local-only transcript mutations, shell output, and live subagent queries. */
|
|
20
5
|
export declare function createLocalActions(deps: {
|
|
21
6
|
ctx: {
|
|
@@ -54,9 +39,9 @@ export declare function createLocalActions(deps: {
|
|
|
54
39
|
badge: string;
|
|
55
40
|
label: string;
|
|
56
41
|
};
|
|
57
|
-
commandShell(cwd: string): Promise<
|
|
42
|
+
commandShell(cwd: string): Promise<ForegroundShell | undefined>;
|
|
58
43
|
};
|
|
59
|
-
shell?:
|
|
44
|
+
shell?: ForegroundShell;
|
|
60
45
|
notify: ChannelState['notify'];
|
|
61
46
|
}): {
|
|
62
47
|
loadOlder(): number;
|
|
@@ -66,5 +51,4 @@ export declare function createLocalActions(deps: {
|
|
|
66
51
|
listSubagents(): Promise<string[]>;
|
|
67
52
|
runLocalCommand(command: string, includeInContext: boolean): Promise<void>;
|
|
68
53
|
};
|
|
69
|
-
export {};
|
|
70
54
|
//# sourceMappingURL=local-actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-actions.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/local-actions.ts"],"names":[],"mappings":"AAOA,OAAO,
|
|
1
|
+
{"version":3,"file":"local-actions.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/channel/local-actions.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAC7E,OAAO,EAA+B,KAAK,QAAQ,IAAI,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEjE,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,GAAG,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;IACnC,KAAK,EAAE;QAAE,OAAO,IAAI,OAAO,CAAA;KAAE,CAAA;IAC7B,OAAO,EAAE;QACP,OAAO,IAAI,OAAO,CAAA;QAClB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;QACpC,QAAQ,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;SAAE,CAAA;KACvE,CAAA;IACD,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IACzB,SAAS,EAAE;QAAE,KAAK,IAAI,IAAI,CAAC;QAAC,eAAe,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,iBAAiB,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAClJ,SAAS,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC/B,IAAI,EAAE;QAAE,QAAQ,IAAI,IAAI,CAAA;KAAE,CAAA;IAC1B,QAAQ,EAAE,OAAO,QAAQ,CAAA;IACzB,SAAS,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAA;KAAE,CAAA;IACrJ,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;CAC/B;iBAIgB,MAAM;aAKV,IAAI;qBAYI,MAAM,SAAS,SAAS,MAAM,EAAE,GAAG,IAAI;4BAKhC,MAAM,GAAG,OAAO;qBAajB,OAAO,CAAC,MAAM,EAAE,CAAC;6BAmBT,MAAM,oBAAoB,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;EAqBnF"}
|
|
@@ -4,6 +4,7 @@ import { writeActivityFrames } from '../../activityPrefs.js';
|
|
|
4
4
|
import { isPresetName, normalizeActivityPreset } from '../../components/activityFrames.js';
|
|
5
5
|
import { t } from '../../i18n.js';
|
|
6
6
|
import { snapshotLiveSessionEvents } from '../compat/liveSession.js';
|
|
7
|
+
import { runForegroundShell } from '../compat/shell.js';
|
|
7
8
|
import { LOCAL_OUTPUT_LIMIT, preview } from './transcript.js';
|
|
8
9
|
/** Owns local-only transcript mutations, shell output, and live subagent queries. */
|
|
9
10
|
export function createLocalActions(deps) {
|
|
@@ -90,7 +91,7 @@ export function createLocalActions(deps) {
|
|
|
90
91
|
return;
|
|
91
92
|
if (executor) {
|
|
92
93
|
try {
|
|
93
|
-
const result = await
|
|
94
|
+
const result = await runForegroundShell(executor, { command, workdir: cwd, timeoutMs: 30000 });
|
|
94
95
|
output = result.stdout.text.trim() || result.stderr.text.trim() || (result.timedOut ? '(timed out)' : '(no output)');
|
|
95
96
|
}
|
|
96
97
|
catch (error) {
|
|
@@ -11,13 +11,16 @@ type Binding = ReturnType<typeof createChannelBinding>;
|
|
|
11
11
|
* This owns the async catalog caches and their generation fences; callers only
|
|
12
12
|
* compose its surface into ChannelState after the state has been constructed.
|
|
13
13
|
*/
|
|
14
|
-
export declare function createModelActions(ctx: Context, state: Pick<ChannelState, 'provider' | 'model' | 'reasoningEffort' | 'effortLevels' | 'agentPreset' | 'working' | 'emit'>, deps: {
|
|
14
|
+
export declare function createModelActions(ctx: Context, state: Pick<ChannelState, 'provider' | 'model' | 'reasoningEffort' | 'effortLevels' | 'agentPreset' | 'working' | 'contextWindow' | 'emit'>, deps: {
|
|
15
15
|
owner: Pick<ChannelOwner, 'current'>;
|
|
16
16
|
binding: Pick<Binding, 'capture' | 'isCurrent'>;
|
|
17
17
|
selection: ModelSelectionRef;
|
|
18
18
|
initialEffort?: string;
|
|
19
19
|
agent(): Agent;
|
|
20
20
|
notify: ChannelState['notify'];
|
|
21
|
+
/** Re-check the context-low warning after a refreshed route metadata
|
|
22
|
+
* answer carries a newer contextWindow than the session started with. */
|
|
23
|
+
checkContextWarning(): void;
|
|
21
24
|
}): {
|
|
22
25
|
selection: ModelSelectionRef;
|
|
23
26
|
applyPreferredEffort: () => Promise<void>;
|