@yanlinglabs/winter-agent-runtime 0.0.34 → 0.0.36

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/dist/index.js CHANGED
@@ -2,11 +2,11 @@ import {
2
2
  createRpcBridge2,
3
3
  registerTool2,
4
4
  onCompaction2,
5
+ buildWorkflowWorkerSeatbeltProfile2,
5
6
  WinterPathsError2,
6
7
  sessionTempDir2,
7
8
  ensureTasksDir2,
8
9
  resolveProjectDirName2,
9
- buildWorkflowWorkerSeatbeltProfile2,
10
10
  WORKFLOW_SANDBOX_REFUSED_EXIT_CODE2,
11
11
  DEFAULT_CONTEXT_WINDOW_TOKENS2,
12
12
  createContextAccountant2,
@@ -55,7 +55,7 @@ import {
55
55
  DEFAULT_CLASSIFIER_TIMEOUT_MS2,
56
56
  createModelClassifier2,
57
57
  selectClassifierRoute2
58
- } from "./index-rprst8z5.js";
58
+ } from "./index-c8j57y8v.js";
59
59
  import"./index-1hef2gff.js";
60
60
  import"./index-mwew595z.js";
61
61
  import"./index-2wgfv0pa.js";
@@ -86,6 +86,8 @@ export declare function resolveMcpServerSources(sources: readonly McpServerSourc
86
86
  trustedWorkspace: boolean;
87
87
  reservedServerName?: string;
88
88
  }): ResolveMcpServerSourcesResult;
89
+ /** The text budget of an MCP result that carries images (≈ the default `MAX_MCP_OUTPUT_TOKENS` of 25000 at 4 characters a token). */
90
+ export declare const MCP_IMAGE_RESULT_TEXT_CHARS = 100000;
89
91
  export interface McpDiscoveryCache {
90
92
  get(key: string): McpToolInfo[] | undefined;
91
93
  set(key: string, tools: McpToolInfo[]): void;
@@ -27,7 +27,7 @@ export declare const P5_FIXTURE_SKILL_NAME = "p5probe";
27
27
  * `{"answer":"42","doubled":"4242"}` on every leg.
28
28
  */
29
29
  export declare const P5_WORKFLOW_SCRIPT: string;
30
- export type TestProviderName = "boom" | "tooluse" | "hang" | "reflect" | "modeswitch" | "bgtask" | "lanea" | "laneb" | "lanec" | "laned" | "lanee" | "mcpsdk" | "subagent" | "childmsg" | "subagentperm" | "toolsearch" | "p5compact" | "p5structured" | "p5structuredfail" | "p5skill" | "p5checkpoint" | "p5workflow";
30
+ export type TestProviderName = "boom" | "tooluse" | "hang" | "reflect" | "modeswitch" | "bgtask" | "lanea" | "laneb" | "lanec" | "laned" | "lanee" | "mcpsdk" | "subagent" | "childmsg" | "subagentperm" | "toolsearch" | "p5compact" | "p5structured" | "p5structuredfail" | "p5skill" | "p5checkpoint" | "p5workflow" | "readimage";
31
31
  export declare function isTestProviderName(v: string): v is TestProviderName;
32
32
  /**
33
33
  * The name the reserved `winter-test/<name>` namespace uses for the plain echo double.
@@ -43,6 +43,43 @@ export declare class SandboxConfigError extends Error {
43
43
  }
44
44
  export declare function resolveNetworkPosture(network: SandboxNetworkSettings | undefined): boolean;
45
45
  export declare function canonicalizePath(p: string): string;
46
+ /**
47
+ * Fix round 14 (Winter-specific hardening, NOT itself a claude port -- empirically discovered and
48
+ * verified while implementing `buildReadDenyWritePermitBlock` above, disclosed prominently rather
49
+ * than smoothed over): `pR`'s own trailing re-permit is a BLANKET, UNCONDITIONAL
50
+ * `(allow file-write-unlink file-write-create (subpath <every write root>))`, ported faithfully per
51
+ * the controller's own explicit instruction. Real `sandbox-exec` runs proved this is not merely
52
+ * "narrower than a later wildcard deny wins" (round 13's own finding, about an EARLIER explicit deny
53
+ * surviving a LATER broad `file-write*` allow) -- it runs the OTHER direction too: an EARLIER
54
+ * EXPLICIT `file-write-unlink`/`file-write-create` ALLOW is not overridden by a LATER, broader
55
+ * `(deny file-write* ...)` for the SAME target either. Seatbelt appears to give a clause naming
56
+ * `file-write-unlink`/`file-write-create` explicitly priority over one that only reaches those
57
+ * operations via the `file-write*` wildcard, independent of which clause is textually first or last.
58
+ *
59
+ * Every OTHER Winter-owned write-protection floor in this module that used only the `file-write*`
60
+ * wildcard was therefore silently punched through for CREATE and UNLINK/RENAME specifically (never
61
+ * for `file-write-data`, `file-write-mode`, etc., which this re-permit never names) by this ONE new
62
+ * block: the control-plane carve-outs (WS-12 §5.2's own "the seatbelt is the only enforcement point
63
+ * left" floor -- verified empirically: `mkdir -p .winter && echo '{}' > .winter/permissions.local.json`
64
+ * and `rm .winter/settings.json` both SUCCEEDED against the unpatched fix), the checkpoint/backup
65
+ * store write-deny (T8 rider 25's own identical floor), and a GLOB-shaped `denyWrite` entry (a plain
66
+ * `denyWritePaths` entry was already safe -- `Ch`'s own write-side ancestor-rename block, round 12,
67
+ * already emits an explicit `(subpath <path>)` deny for it; `Ch`'s own GLOB branch, by contrast, only
68
+ * ever emits a `(literal <fixedPrefix>)` -- protecting the prefix DIRECTORY's own identity against a
69
+ * rename-shuffle, never the glob-matched files themselves).
70
+ *
71
+ * The fix, verified against real `sandbox-exec` (a `(deny file-write* file-write-unlink
72
+ * file-write-create (regex ...))` clause DOES win back the CREATE it needs to, confirmed by a direct
73
+ * before/after run rather than assumed): every one of those floors now names
74
+ * `file-write-unlink`/`file-write-create` EXPLICITLY, alongside the `file-write*` wildcard it already
75
+ * carried (for the OTHER write operations the wildcard alone still covers correctly) -- this constant
76
+ * is that shared operation-name list, applied wherever `file-write*` ALONE previously appeared on a
77
+ * deny this round's own re-permit could otherwise reach. `fR` (`buildReadDenyKeepInPlaceBlock`) is
78
+ * deliberately NOT touched here: it already names `file-write-unlink` explicitly (never `create`, by
79
+ * claude's own design -- see that function's own header), so it was never in the affected set.
80
+ */
81
+ /** The runtime's image working directory under the winter/store home (tools/image-prep.ts) -- write-denied to the shell. */
82
+ export declare const IMAGE_PREP_DIRNAME = "image-prep";
46
83
  /**
47
84
  * P7a fix r1 (Important-1): render ONE brand token as a case-insensitive, regex-escaped SBPL literal.
48
85
  *
@@ -478,7 +478,7 @@ export declare class TranscriptWriter implements SessionPersistence {
478
478
  private dialectRecord;
479
479
  static readBack(store: SessionStore, key: SessionKey): Promise<SessionStoreEntry[]>;
480
480
  }
481
- export declare const RUNTIME_ENGINE_VERSION = "0.0.34";
481
+ export declare const RUNTIME_ENGINE_VERSION = "0.0.36";
482
482
  export interface ResolvedEngineSession {
483
483
  config: RuntimeConfig;
484
484
  store: SessionPersistence | undefined;
package/dist/testing.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  runEngine2,
4
4
  echoProvider2,
5
5
  resolveEngineSession2
6
- } from "./index-rprst8z5.js";
6
+ } from "./index-c8j57y8v.js";
7
7
  import"./index-1hef2gff.js";
8
8
  import"./index-mwew595z.js";
9
9
  import"./index-2wgfv0pa.js";
@@ -14,7 +14,7 @@ import {
14
14
  buildProductionWiring,
15
15
  withAutoSkillPermissions,
16
16
  restoreChildRoster
17
- } from "./index-dske1c00.js";
17
+ } from "./index-83p1g9ap.js";
18
18
  import {
19
19
  Queue
20
20
  } from "./index-97t2rmtf.js";
@@ -0,0 +1,208 @@
1
+ import type { ToolResultBlock } from "./registry.js";
2
+ /**
3
+ * The exact refusal a model with no image input gets (the host's composer shows the same words). Exported
4
+ * so a host or a test matches it rather than retyping it.
5
+ */
6
+ export declare const MODEL_DOES_NOT_SUPPORT_IMAGES = "The selected model doesn't support images";
7
+ /** The image types every provider adapter can carry (Anthropic, Bedrock, Gemini and OpenAI all accept exactly these four). */
8
+ export declare const DELIVERABLE_IMAGE_TYPES: ReadonlySet<string>;
9
+ /**
10
+ * The largest image handed to a model, in raw bytes: 3.75 MiB, whose base64 is exactly 5 MiB.
11
+ *
12
+ * The strictest per-image limit among the providers Winter drives: "5 MB (base64-encoded) on Amazon
13
+ * Bedrock and Google Cloud" for Claude (https://platform.claude.com/docs/en/build-with-claude/vision,
14
+ * "Request limits"; the Claude API itself takes 10 MB). An image over a provider's limit stays in the
15
+ * conversation and fails EVERY later request, so the limit is applied here, before the image enters
16
+ * history, not in an adapter. A SOURCE file may be larger (up to `IMAGE_MAX_INPUT_BYTES`): it is shrunk
17
+ * and, if it must be, re-encoded as JPEG to fit (`prepareImageForModel`).
18
+ */
19
+ export declare const READ_IMAGE_MAX_BYTES = 3932160;
20
+ /** The largest width or height any provider accepts: "The maximum dimensions per image are 8000x8000 px" (same page). */
21
+ export declare const READ_IMAGE_MAX_DIMENSION = 8000;
22
+ /** What the bytes actually are, from their magic numbers -- never trusting the extension. `undefined` when none matches. */
23
+ export declare function sniffImageType(buf: Uint8Array): string | undefined;
24
+ export declare function parsePngDimensions(buf: Uint8Array): {
25
+ width: number;
26
+ height: number;
27
+ } | undefined;
28
+ export declare function parseGifDimensions(buf: Uint8Array): {
29
+ width: number;
30
+ height: number;
31
+ } | undefined;
32
+ export declare function parseBmpDimensions(buf: Uint8Array): {
33
+ width: number;
34
+ height: number;
35
+ } | undefined;
36
+ export declare function parseJpegDimensions(buf: Uint8Array): {
37
+ width: number;
38
+ height: number;
39
+ } | undefined;
40
+ /** WebP's canvas size from its VP8 (lossy), VP8L (lossless) or VP8X (extended) header. */
41
+ export declare function parseWebpDimensions(buf: Uint8Array): {
42
+ width: number;
43
+ height: number;
44
+ } | undefined;
45
+ /** A TIFF's first image size, from its first IFD's ImageWidth (256) / ImageLength (257) tags (SHORT or LONG). */
46
+ export declare function parseTiffDimensions(buf: Uint8Array): {
47
+ width: number;
48
+ height: number;
49
+ } | undefined;
50
+ /**
51
+ * A HEIC's size from its `ispe` (image spatial extents) boxes: the LARGEST one, since a grid image carries
52
+ * a small `ispe` per tile and one for the whole picture. A scan for well-formed `ispe` boxes (size 20,
53
+ * version/flags 0), not a full box walk; `sips -g`'s answer is taken too and the larger wins.
54
+ */
55
+ export declare function parseHeicDimensions(buf: Uint8Array): {
56
+ width: number;
57
+ height: number;
58
+ } | undefined;
59
+ /**
60
+ * A GIF's size for the PIXEL CHECK: the larger of its logical screen and every frame's own descriptor --
61
+ * a tiny logical screen can front a frame that declares 60000x60000 px, and the decoder allocates for the
62
+ * frame.
63
+ */
64
+ export declare function parseGifExtent(buf: Uint8Array): {
65
+ width: number;
66
+ height: number;
67
+ } | undefined;
68
+ export declare function parseImageDimensions(mediaType: string, bytes: Uint8Array): {
69
+ width: number;
70
+ height: number;
71
+ } | undefined;
72
+ /**
73
+ * The long edge an image is shrunk to before a model sees it: 1568 px, the size above which Claude's
74
+ * standard tier downsizes an image anyway ("Resolution and token cost",
75
+ * https://platform.claude.com/docs/en/build-with-claude/vision). Sending more only costs request bytes:
76
+ * the per-request limits (32 MB on the Claude API, 20 MB of inline data on Gemini) fill up after a few
77
+ * full-size screenshots, and past 20 images Anthropic refuses any image over 2000 px. At 1568 none of
78
+ * that is reached by an ordinary session.
79
+ */
80
+ export declare const IMAGE_MAX_LONG_EDGE = 1568;
81
+ /** The largest file Read or an MCP result may hand the resizer at all (a guard against absurd inputs, not a provider limit). */
82
+ export declare const IMAGE_MAX_INPUT_BYTES: number;
83
+ /**
84
+ * The most pixels an image may DECLARE before anything decodes it: 100 megapixels. A few-kilobyte PNG can
85
+ * declare 40000x40000 px, and decoding that (sips does, to resize it) takes gigabytes; a declared size is
86
+ * all a header costs. 100 MP is above any camera or screenshot in ordinary use (a 12K frame is ~80 MP).
87
+ */
88
+ export declare const IMAGE_MAX_PIXELS = 100000000;
89
+ /** An image ready for a model: the bytes to send, and what happened to them on the way. */
90
+ export interface PreparedImage {
91
+ ok: true;
92
+ mediaType: string;
93
+ bytes: Buffer;
94
+ width?: number;
95
+ height?: number;
96
+ /** The source as found. */
97
+ original: {
98
+ mediaType: string;
99
+ bytes: number;
100
+ width?: number;
101
+ height?: number;
102
+ };
103
+ /** True when the pixels were scaled down. */
104
+ resized: boolean;
105
+ /** Set when the format changed (a conversion, or a JPEG re-encode to fit the byte limit): the quality used for a JPEG. */
106
+ jpegQuality?: number;
107
+ }
108
+ export type ImagePreparation = PreparedImage | {
109
+ ok: false;
110
+ reason: string;
111
+ };
112
+ export interface PrepareImageOptions {
113
+ /**
114
+ * The runtime-owned directory the per-call working directory is made in: `<winter or store home>/
115
+ * image-prep` (`imagePrepWorkRoot`), which the Bash sandbox's seatbelt profile write-denies
116
+ * (sandbox/profile.ts, `IMAGE_PREP_DIRNAME`) -- NEVER the session temp dir, which a sandboxed shell can
117
+ * write (a link planted there would make `sips` write through it). A getter, read only when `sips`
118
+ * actually runs. `undefined`: no private place to work, so nothing is converted or resized.
119
+ */
120
+ workRoot: () => string | undefined;
121
+ /** Test seam: the `sips` binary (a missing path simulates a platform without it). Default `/usr/bin/sips`. */
122
+ sipsPath?: string;
123
+ /** Test seam: the byte limit. Default `READ_IMAGE_MAX_BYTES`. */
124
+ maxBytes?: number;
125
+ /** The turn's abort: an interrupt kills a running `sips`. */
126
+ signal?: AbortSignal;
127
+ }
128
+ /** A short, model-facing description of what happened, e.g. `image/png, 25856 bytes, 1568x1018, resized from 3024x1964`. */
129
+ export declare function describePreparedImage(image: PreparedImage): string;
130
+ /**
131
+ * Reads `path` only if it is a regular file, and never through a link: `O_NOFOLLOW` refuses a symlink at
132
+ * open time, and the type is checked on the OPENED descriptor, so nothing can be swapped in between the
133
+ * check and the read. `undefined` for anything else (a link, a directory, a missing file).
134
+ */
135
+ export declare function readRegularFileNoFollow(path: string): Buffer | undefined;
136
+ /**
137
+ * Where image work happens for a session: `<store home or winter home>/image-prep` -- the two anchors the
138
+ * seatbelt profile write-denies `image-prep` under, both handed to a session EXPLICITLY by its host. With
139
+ * neither, `undefined`: nothing is resized or converted (an image already within every limit is still
140
+ * sent as it is). There is deliberately NO fallback, and here is why each candidate is unsafe:
141
+ * - `~/<homeDirName>`: a guess at the user's real winter home, which a process given no home must not
142
+ * write into (and which a test would reach);
143
+ * - the session temp dir, the cwd, the outputs dir and any `allowWrite` entry: sandbox-WRITABLE roots,
144
+ * where a sandboxed command could plant a link;
145
+ * - `$TMPDIR` (the per-user temp dir): its DIRECT children are sandbox-writable (the profile's mktemp
146
+ * allowance), so a folder made there could be renamed away and replaced by a link mid-run.
147
+ */
148
+ export declare function imagePrepWorkRoot(ctx: {
149
+ storeHome?: string;
150
+ winterHome?: string;
151
+ }): string | undefined;
152
+ /**
153
+ * How many frames a GIF holds (image descriptors), walking its block structure; `undefined` when the
154
+ * structure cannot be walked. OpenAI takes only a "non-animated GIF"
155
+ * (https://developers.openai.com/api/docs/guides/images-vision) and Claude reads only the first frame.
156
+ */
157
+ export declare function gifFrameCount(buf: Uint8Array): number | undefined;
158
+ /** Every frame's own size (its image descriptor), in order; `undefined` when the structure cannot be walked to its end. */
159
+ export declare function gifFrameSizes(buf: Uint8Array): Array<{
160
+ width: number;
161
+ height: number;
162
+ }> | undefined;
163
+ /**
164
+ * Makes `source` ready for a model, or says why it cannot be.
165
+ *
166
+ * - The type is SNIFFED from the bytes; something that is none of PNG/JPEG/GIF/WebP/BMP/TIFF/HEIC is refused.
167
+ * - An image whose long edge is over `IMAGE_MAX_LONG_EDGE` is scaled down to it (aspect kept).
168
+ * - The format is kept where a provider takes it: PNG stays PNG and JPEG stays JPEG (quality 85 when
169
+ * re-encoded). `sips` cannot WRITE WebP, so a WebP that has to be rewritten becomes PNG. EVERY GIF
170
+ * becomes PNG (its first frame): Gemini reads no GIF, OpenAI no animated one, Claude only the first
171
+ * frame. BMP and TIFF, which no provider takes, become PNG; HEIC (a camera photo) becomes JPEG. A WebP
172
+ * that needs no rewrite is sent as it is.
173
+ * - An image DECLARING more than `IMAGE_MAX_PIXELS` is refused before anything decodes it.
174
+ * - Still over `READ_IMAGE_MAX_BYTES`? Re-encoded as JPEG at quality 85, 70, 55, then 40; still over,
175
+ * refused. Never truncated.
176
+ * - Without `sips`, or when it fails: the image as it is if it is already a deliverable type within the
177
+ * byte limit and `READ_IMAGE_MAX_DIMENSION` (and, for a GIF, a single frame), else a refusal naming the
178
+ * reason.
179
+ * - `sips` works in a FRESH private directory per call (`mkdtemp`) under `imagePrepWorkRoot` -- a tree
180
+ * the Bash sandbox is denied writing to, so no link can be planted where `sips` writes -- its input is
181
+ * written with `wx`, and its output is read through `readRegularFileNoFollow` (no link, no swap).
182
+ */
183
+ export declare function prepareImageForModel(source: Buffer, opts: PrepareImageOptions): Promise<ImagePreparation>;
184
+ /** One part of a result before it is made model-ready: text, or an image's raw bytes. */
185
+ export type RawResultPart = {
186
+ type: "text";
187
+ text: string;
188
+ } | {
189
+ type: "image";
190
+ bytes: Buffer;
191
+ };
192
+ export interface ModelResult {
193
+ /** The model-facing blocks, in order (adjacent text merged). */
194
+ blocks: ToolResultBlock[];
195
+ /** A text rendering of the same result (each image as `[image: …]`) -- for hooks, logs and a text-only reader. */
196
+ text: string;
197
+ /** True when at least one image block survived. */
198
+ hasImage: boolean;
199
+ }
200
+ /**
201
+ * Makes a mixed text/image result model-ready: every image goes through `prepareImageForModel`, and an
202
+ * image that cannot be shown -- a text-only model (`readsImages: false`), a refusal, or images past the
203
+ * result's total of `maxBytes` -- becomes a one-line `[image omitted: …]` note IN ITS PLACE, never
204
+ * dropped and never base64 text.
205
+ */
206
+ export declare function resultBlocksForModel(parts: readonly RawResultPart[], opts: PrepareImageOptions & {
207
+ readsImages: boolean;
208
+ }): Promise<ModelResult>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A real, decodable 8-bit RGB PNG of `width` x `height`. `noise: true` fills it with random pixels (it
3
+ * does not compress, so it is large); otherwise a repeating gradient (tiny once compressed).
4
+ */
5
+ export declare function realPng(width: number, height: number, opts?: {
6
+ noise?: boolean;
7
+ }): Buffer;
@@ -34,6 +34,8 @@ declare function buildMonitorRunCommandOptions(ctx: ToolExecutionContext): {
34
34
  home: string;
35
35
  /** Phase 5 fix wave, I1: the resolved winter root, distinct from the OS home above. */
36
36
  winterHome?: string;
37
+ /** WS-21 fix round 1, item 4: the shared store home -- see `ToolExecutionContext.storeHome`. */
38
+ storeHome?: string;
37
39
  /** P7a (D19): the session's brand -- the dot-dir names the seatbelt fences. */
38
40
  brand?: SandboxBrand;
39
41
  /** Fix round 16, item 2: `sandbox.filesystem.allowGitConfig` -- see `SandboxFilesystemSettings.allowGitConfig`'s own header. */
@@ -1,44 +1,3 @@
1
1
  import "../descriptors/index.ts";
2
- export type ReadBlock = {
3
- type: "text";
4
- text: string;
5
- } | {
6
- type: "image";
7
- media_type: string;
8
- bytes: number;
9
- width?: number;
10
- height?: number;
11
- data?: string;
12
- note?: string;
13
- } | {
14
- type: "pdf";
15
- media_type: "application/pdf";
16
- bytes: number;
17
- totalPages?: number;
18
- requestedPages?: {
19
- start: number;
20
- end: number;
21
- };
22
- data?: string;
23
- note: string;
24
- };
25
- export interface ReadBlocksEnvelope {
26
- winterReadBlocks: ReadBlock[];
27
- }
28
- export declare function parsePngDimensions(buf: Uint8Array): {
29
- width: number;
30
- height: number;
31
- } | undefined;
32
- export declare function parseGifDimensions(buf: Uint8Array): {
33
- width: number;
34
- height: number;
35
- } | undefined;
36
- export declare function parseBmpDimensions(buf: Uint8Array): {
37
- width: number;
38
- height: number;
39
- } | undefined;
40
- export declare function parseJpegDimensions(buf: Uint8Array): {
41
- width: number;
42
- height: number;
43
- } | undefined;
2
+ export { IMAGE_MAX_LONG_EDGE, MODEL_DOES_NOT_SUPPORT_IMAGES, READ_IMAGE_MAX_BYTES, READ_IMAGE_MAX_DIMENSION, parseBmpDimensions, parseGifDimensions, parseJpegDimensions, parsePngDimensions, parseWebpDimensions, sniffImageType, } from "../image-prep.js";
44
3
  export declare function countPdfPages(bytes: Buffer): number | undefined;
@@ -64,8 +64,34 @@ export interface ToolDescriptor {
64
64
  }
65
65
  export interface ToolResultPayload {
66
66
  output: string;
67
+ /**
68
+ * Code-mode images: what the MODEL is shown for this call, when it is more than text. Present only on
69
+ * a result that carries an image (the Read tool on an image file or a notebook with plot output);
70
+ * the engine then writes these blocks as the `tool_result`'s content array -- claude's own shape --
71
+ * in place of `output`. `output` stays a short TEXT rendering of the same result, and it is what every
72
+ * text-only consumer reads: the PostToolUse hook's `tool_response`, a log line, a flattening. Kept
73
+ * separate so no text channel ever carries base64 (a hook payload rides the same capped stdout line
74
+ * a frame does).
75
+ */
76
+ blocks?: ToolResultBlock[];
67
77
  isError?: boolean;
68
78
  }
79
+ /**
80
+ * One block of a multimodal tool result (see `ToolResultPayload.blocks`). Structurally the engine's own
81
+ * `text` and `image` ContentBlock variants, so the engine carries them into history, the transcript and
82
+ * every provider adapter unchanged.
83
+ */
84
+ export type ToolResultBlock = {
85
+ type: "text";
86
+ text: string;
87
+ } | {
88
+ type: "image";
89
+ source: {
90
+ type: "base64";
91
+ media_type: string;
92
+ data: string;
93
+ };
94
+ };
69
95
  /** See `ToolExecutionContext.permission`. */
70
96
  export interface ToolCallPermission {
71
97
  explicitApproval: "prompt" | "rule";
@@ -132,6 +158,13 @@ export interface ToolExecutionContext {
132
158
  * ABSENT READS AS `WINTER_BRAND`, which is byte-identical to the behaviour before this field.
133
159
  */
134
160
  brand?: BrandProfile;
161
+ /**
162
+ * Code-mode images: whether the model the session is on RIGHT NOW accepts image input (its catalog
163
+ * row's `inputModalities` lists "image"). The Read tool answers an image file with a short text
164
+ * refusal instead of image blocks when this is `false`. Absent reads as "yes" -- a scripted double or
165
+ * a row with no modality facts, the same default `ModelDescription.readsImages` has.
166
+ */
167
+ modelReadsImages?: boolean;
135
168
  sessionId: string;
136
169
  readState: SessionReadState;
137
170
  emitFrame: (frame: BackgroundTaskMessage) => void;
@@ -366,6 +399,8 @@ export interface EngineToolCall {
366
399
  }
367
400
  export interface EngineToolResult {
368
401
  output: string;
402
+ /** See `ToolResultPayload.blocks`: when present, the model-facing content of the `tool_result`. */
403
+ blocks?: ToolResultBlock[];
369
404
  /**
370
405
  * Spawn-surface parity (R-S4, research gap 6): the executor's own `isError`, carried to the engine
371
406
  * so the model-facing `tool_result` block says `is_error: true` -- claude's shape for every tool
@@ -381,6 +416,8 @@ export interface EngineFacingToolExecutor {
381
416
  export interface RegistryToolExecutorDeps {
382
417
  sessionId: string;
383
418
  home: string;
419
+ /** Code-mode images: read per call, so a `set_model` since the last call is seen -- see `ToolExecutionContext.modelReadsImages`. */
420
+ modelReadsImages?: () => boolean;
384
421
  /** Phase 5 fix wave, I1: the resolved winter root -- see `ToolExecutionContext.winterHome`. */
385
422
  winterHome?: string;
386
423
  /** WS-21 fix round 1, item 4: the shared store home -- see `ToolExecutionContext.storeHome`. */
@@ -0,0 +1,41 @@
1
+ import type { ToolExecutionContext } from "./registry.js";
2
+ import { type GlobDenyEntry } from "../permissions/file-rules.js";
3
+ import type { SandboxBrand } from "../sandbox/profile.js";
4
+ /** Everything about the SESSION the seatbelt profile needs, for any tool that runs a shell command. */
5
+ export interface SandboxRunInputs {
6
+ cwd: string;
7
+ env: NodeJS.ProcessEnv;
8
+ settings: ToolExecutionContext["sandboxSettings"];
9
+ writableRoots: string[];
10
+ denyWritePaths?: string[];
11
+ denyReadPaths?: string[];
12
+ /** Fix round 11: glob-shaped denyWrite/denyRead entries, pre-converted to SBPL regex source -- see `splitDenyPathsByGlobShape`'s own header. */
13
+ denyWriteRegexes?: string[];
14
+ denyReadRegexes?: string[];
15
+ /** Fix round 12: each glob-shaped denyWrite/denyRead entry's own canonicalized fixed-prefix directory -- feeds the ancestor-rename-bypass fix. */
16
+ denyWriteGlobFixedPrefixes?: string[];
17
+ denyReadGlobFixedPrefixes?: string[];
18
+ /** Fix round 13: each glob-shaped denyRead entry, its regex PAIRED with its own fixed prefix -- feeds the read-deny-keep-in-place fix. */
19
+ denyReadGlobEntries?: GlobDenyEntry[];
20
+ home: string;
21
+ /** Phase 5 fix wave, I1: the resolved winter root, distinct from the OS home above. */
22
+ winterHome?: string;
23
+ /** WS-21 fix round 1, item 4: the shared store home -- see `ToolExecutionContext.storeHome`. */
24
+ storeHome?: string;
25
+ /** P7a (D19): the session's brand -- the dot-dir names the seatbelt fences. */
26
+ brand?: SandboxBrand;
27
+ /** Fix round 16, item 2: `sandbox.filesystem.allowGitConfig` -- see `SandboxFilesystemSettings.allowGitConfig`'s own header. */
28
+ allowGitConfigWrites?: boolean;
29
+ }
30
+ /**
31
+ * Where a spawn may write: the session scratch dir, the session's bounded roots (the cwd among them),
32
+ * the outputs dir when configured, and `filesystem.allowWrite` (WS-12 §12 Q5: additive, never a
33
+ * replacement).
34
+ */
35
+ export declare function computeWritableRoots(ctx: ToolExecutionContext): string[];
36
+ /** The child's environment: this process's, with `TMPDIR` (and `OUTDIR` when configured) pointed at the session's own dirs. */
37
+ export declare function buildSandboxChildEnv(ctx: ToolExecutionContext): NodeJS.ProcessEnv;
38
+ /** `filesystem.denyWrite`/`denyRead`, split into plain paths and glob-shaped regexes (C1 / fix rounds 11-13). */
39
+ export declare function computeDenyPaths(ctx: ToolExecutionContext): Pick<SandboxRunInputs, "denyWritePaths" | "denyReadPaths" | "denyWriteRegexes" | "denyReadRegexes" | "denyWriteGlobFixedPrefixes" | "denyReadGlobFixedPrefixes" | "denyReadGlobEntries">;
40
+ /** The session's sandbox inputs, identical for every tool that runs a shell command. */
41
+ export declare function sandboxRunInputs(ctx: ToolExecutionContext): SandboxRunInputs;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.0.34";
1
+ export declare const RUNTIME_VERSION = "0.0.36";
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var RUNTIME_VERSION = "0.0.34";
2
+ var RUNTIME_VERSION = "0.0.36";
3
3
  export {
4
4
  RUNTIME_VERSION
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-runtime",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -73,9 +73,9 @@
73
73
  "@modelcontextprotocol/client": "^2.1.0",
74
74
  "ajv": "^8.20.0",
75
75
  "ignore": "7.0.5",
76
- "@yanlinglabs/winter-agent-sdk": "0.0.34",
77
- "@yanlinglabs/winter-provider-runtime": "0.0.34",
78
- "@yanlinglabs/winter-provider-catalog": "0.0.34"
76
+ "@yanlinglabs/winter-provider-catalog": "0.0.36",
77
+ "@yanlinglabs/winter-provider-runtime": "0.0.36",
78
+ "@yanlinglabs/winter-agent-sdk": "0.0.36"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@modelcontextprotocol/server": "^2.1.0"