@yanlinglabs/winter-agent-runtime 0.0.41 → 0.0.44

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.
Files changed (60) hide show
  1. package/dist/commands/resolver.d.ts +8 -13
  2. package/dist/context/agent-listing.d.ts +7 -11
  3. package/dist/context/attachments.d.ts +29 -23
  4. package/dist/context/dynamic-sections.d.ts +5 -5
  5. package/dist/context/git-branch-fixture.d.ts +22 -0
  6. package/dist/context/git-status.d.ts +2 -2
  7. package/dist/context/memory.d.ts +1 -1
  8. package/dist/context/output-styles.d.ts +2 -2
  9. package/dist/context/request-layout.d.ts +16 -17
  10. package/dist/context/seam.d.ts +7 -6
  11. package/dist/embedded-host.js +1 -1
  12. package/dist/embedded-worker.js +4 -4
  13. package/dist/embedded.js +3 -3
  14. package/dist/engine.d.ts +3 -3
  15. package/dist/{index-97t2rmtf.js → index-dne3dw18.js} +11 -0
  16. package/dist/{index-z9yngxj4.js → index-hbkdq3xs.js} +1072 -827
  17. package/dist/{index-hdvvezf8.js → index-r1pter2v.js} +239 -155
  18. package/dist/{index-ey5c52j3.js → index-x7hdare6.js} +2 -2
  19. package/dist/index.js +2 -2
  20. package/dist/mcp/lifecycle.d.ts +2 -1
  21. package/dist/permissions/edit-recognition.d.ts +1 -1
  22. package/dist/permissions/evaluator.d.ts +19 -30
  23. package/dist/permissions/file-rules.d.ts +177 -277
  24. package/dist/permissions/grammar.d.ts +58 -0
  25. package/dist/permissions/policy-state.d.ts +1 -1
  26. package/dist/permissions/ruleset.d.ts +1 -1
  27. package/dist/permissions/shell-structure.d.ts +1 -1
  28. package/dist/plugins/bundle.d.ts +4 -6
  29. package/dist/plugins/loader.corpus-fixture.d.ts +26 -0
  30. package/dist/plugins/loader.d.ts +3 -4
  31. package/dist/plugins/manifest.d.ts +23 -46
  32. package/dist/production-wiring.d.ts +1 -1
  33. package/dist/protocol/channel.d.ts +6 -0
  34. package/dist/provider/lean-prompt.d.ts +1 -6
  35. package/dist/sandbox/profile.d.ts +39 -79
  36. package/dist/sandbox/spawn.d.ts +5 -6
  37. package/dist/skills/listing.d.ts +8 -26
  38. package/dist/skills/store.d.ts +2 -3
  39. package/dist/store/dialect.d.ts +1 -1
  40. package/dist/subagents/availability.d.ts +10 -8
  41. package/dist/subagents/child-handle.d.ts +5 -5
  42. package/dist/subagents/definitions.d.ts +7 -6
  43. package/dist/subagents/fork.d.ts +4 -8
  44. package/dist/subagents/notification-queue.d.ts +30 -38
  45. package/dist/testing.js +3 -3
  46. package/dist/tools/descriptors/web-fetch.d.ts +1 -1
  47. package/dist/tools/descriptors/web-search.d.ts +1 -1
  48. package/dist/tools/impl/_search-budget.d.ts +1 -1
  49. package/dist/tools/impl/_web-fetch-net.d.ts +6 -6
  50. package/dist/tools/impl/_web-search-assembler.d.ts +7 -20
  51. package/dist/tools/impl/background-task-runtime.d.ts +13 -13
  52. package/dist/tools/impl/bash.d.ts +3 -3
  53. package/dist/tools/impl/monitor.d.ts +2 -2
  54. package/dist/toolsearch/exposure.d.ts +3 -3
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.js +1 -1
  57. package/dist/web/fetchable-url.d.ts +1 -1
  58. package/dist/web/preapproved-hosts.d.ts +9 -31
  59. package/dist/workflows/store.d.ts +23 -31
  60. package/package.json +4 -4
@@ -100,6 +100,12 @@ export declare function extractRedirectWrites(rawCommand: string): RedirectWrite
100
100
  /** `extractRedirectWrites`, target paths only (quotes removed). */
101
101
  export declare function extractRedirectTargets(command: string): string[];
102
102
  export declare function isRecognizedReadOnly(command: string): boolean;
103
+ /**
104
+ * `s` with a backslash put before every character a JavaScript regular expression gives a meaning
105
+ * to -- `. * + ? ^ $ { } ( ) | [ ] \` -- so it can be embedded as literal text in a `RegExp` built at
106
+ * run time. Every other character is left as it is. Shared with `commands/resolver.ts`,
107
+ * `skills/permission-rules.ts` and `settings/env-filter.ts`.
108
+ */
103
109
  export declare function escapeRegExpLiteral(s: string): string;
104
110
  /** The call's `url` as a parsed `URL`, or `undefined` when it is absent, not a string, or unparseable. NEVER throws. */
105
111
  export declare function webFetchUrlOf(input: Record<string, unknown>): URL | undefined;
@@ -136,4 +142,56 @@ export interface PermissionRuleValidation {
136
142
  error?: string;
137
143
  suggestion?: string;
138
144
  }
145
+ /**
146
+ * Validates one raw rule string for `direction`. Every count and position below is taken on the RAW
147
+ * text as given, except where the split text is named. The checks, in order -- the first that fails
148
+ * decides:
149
+ *
150
+ * 1. Empty, or only whitespace: `{valid:false, error:"Permission rule cannot be empty"}`.
151
+ * 2. The number of unescaped `(` differs from the number of unescaped `)`:
152
+ * error `"Mismatched parentheses"`, suggestion `"Ensure all opening parentheses have matching
153
+ * closing parentheses"`.
154
+ * 3. Somewhere an unescaped `(` is immediately followed by `)`. Let `prefix` be the text before the
155
+ * FIRST `(` character of the raw rule, escaped or not. If `prefix` is empty: error
156
+ * `"Empty parentheses with no tool name"`, suggestion `"Specify a tool name before the
157
+ * parentheses"`; otherwise error `"Empty parentheses"`, suggestion
158
+ * `` `Either specify a pattern or use just "${prefix}" without parentheses` ``.
159
+ * 4. Split the trimmed text (`splitRuleText`). If it is not `Tool(content)`, the tool name is the
160
+ * whole trimmed text and there is no content; if the raw content is `""` or `"*"`, there is no
161
+ * content; otherwise the content is the unescaped raw content.
162
+ * 5. MCP names: split the tool name on `__`; when the first part is `mcp` and the second part is not
163
+ * empty, the second part is the server name and the rule is an MCP rule:
164
+ * - with content, or with any unescaped `(` in the raw text: error `"MCP rules do not support
165
+ * patterns in parentheses"`, suggestion
166
+ * `` `Use "${toolName}" without parentheses, or use "mcp__${server}__*" for all tools` ``;
167
+ * - for `allow`, a `*` in the server name is refused as in check 7 (a `*` only after a literal
168
+ * `mcp__<server>__` is fine);
169
+ * - otherwise valid. No further check applies to an MCP rule.
170
+ * 6. An empty tool name: error `"Tool name cannot be empty"`.
171
+ * 7. For `allow`, a tool name containing `*`: error
172
+ * `` `Wildcard tool name "${toolName}" is not supported in allow rules` ``, suggestion
173
+ * `"An allow pattern must name the scope it widens \u2014 globs are permitted only in the tool
174
+ * position after a literal mcp__<server>__ prefix. Deny and ask rules accept wildcards anywhere"`
175
+ * (with a real em dash, U+2014).
176
+ * 8. A tool name with no `_` whose first UTF-16 code unit differs from its upper-case form: error
177
+ * `"Tool names must start with uppercase"`, suggestion `` `Use "${first upper-cased}${rest}"` ``.
178
+ * 9. With content:
179
+ * - `WebSearch` content containing `*` or `?`: error `"WebSearch does not support wildcards"`,
180
+ * suggestion `"Use exact search terms without * or ?"`;
181
+ * - `WebFetch` content containing `://` or starting with `http`: error `"WebFetch permissions use
182
+ * domain format, not URLs"`, suggestion `'Use "domain:hostname" format'`; else content not
183
+ * starting with `domain:`: error `'WebFetch permissions must use "domain:" prefix'`, same
184
+ * suggestion.
185
+ * 10. `Bash` with content: content that contains `:*` but does not END with `:*` (an earlier `:*` is
186
+ * accepted when the content also ends with one, e.g. `a:*b:*`): error `"The :* pattern
187
+ * must be at the end"`, suggestion `"Move :* to the end for prefix matching, or use * for wildcard
188
+ * matching"`; content exactly `:*`: error `"Prefix cannot be empty before :*"`, suggestion
189
+ * `"Specify a command prefix before :*"`.
190
+ * 11. A file-pattern tool -- `Read`, `Write`, `Edit`, `Glob`, `NotebookRead`, `NotebookEdit`, `Cd`
191
+ * (this load-time list, not `FILE_RULE_TOOLS`: it has `NotebookRead` and `Cd` and not `Grep`) --
192
+ * with content containing `:*`: error `'The ":*" syntax is only for Bash prefix rules'`,
193
+ * suggestion `'Use glob patterns like "*" or "**" for file matching'`.
194
+ * 12. Otherwise `{valid:true}`.
195
+ * A result carries `suggestion` only where one is named above.
196
+ */
139
197
  export declare function validatePermissionRuleString(raw: string, direction: "allow" | "deny" | "ask"): PermissionRuleValidation;
@@ -14,7 +14,7 @@ export declare class WinterPermissionError extends Error {
14
14
  export declare const PERMISSION_MODES: ReadonlySet<PermissionMode>;
15
15
  export declare function isPermissionMode(value: string): value is PermissionMode;
16
16
  /**
17
- * SDK 0.0.16 (P16-7): claude's fork agent (`Ex`) carries `permissionMode: "bubble"` -- NOT a member
17
+ * SDK 0.0.16 (P16-7): Claude Code's fork agent carries `permissionMode: "bubble"` -- NOT a member
18
18
  * of `PERMISSION_MODES` and never widened into one (`PermissionMode` is a closed 6-value union read
19
19
  * exhaustively elsewhere -- `classifyPermissionMode` in particular). "bubble" is an explicit ALIAS
20
20
  * for "no override": the child keeps whatever mode the parent session is CURRENTLY running (never a
@@ -6,7 +6,7 @@ export interface SourcedRuleEntry {
6
6
  source: RuleSource;
7
7
  ruleValue: PermissionRuleValue;
8
8
  /**
9
- * Fix round 11 ("important" item, claude's own `TFt`/`bl`, dump byte 15441060): the settings
9
+ * Fix round 11 ("important" item): the settings
10
10
  * SOURCE's own directory, for a SINGLE-`/`-anchored pattern's root (`resolveFileRuleAnchor`'s own
11
11
  * `/`-branch, file-rules.ts -- already fully wired to consume this, INERT_ANCHOR when absent, a
12
12
  * pre-existing disclosed gap this field closes). Populated ONLY by `buildSettingsRuleSeed`
@@ -9,7 +9,7 @@ export interface HeredocExtraction {
9
9
  * bodies bash will still expand. Bodies are removed BEFORE line continuations are joined: a QUOTED
10
10
  * here-document's body is literal (`\<newline>` is not a continuation there), and joining first could
11
11
  * shift its delimiter so that a later line -- `> /etc/passwd` -- was swallowed into the body and never
12
- * seen (claude's `extractOutputRedirections` documents that exact attack).
12
+ * seen.
13
13
  *
14
14
  * This is the one scanner that DROPS text, so it follows bash's contexts closely enough never to drop
15
15
  * a command bash runs: `<<` is a here-document only in a command context (not inside `((…))`,
@@ -68,12 +68,10 @@ export interface PluginBundle {
68
68
  * WS-21 fix round 4 (minors, M-3's last bullet): the manifest's own `workflows` key (`string |
69
69
  * string[]`, `manifest.ts`'s own citation), resolved to absolute paths that exist and do not
70
70
  * escape the plugin root -- present iff the manifest declares the key AND at least one entry
71
- * resolved (the pinned binary's own `if(qn.length>0)ve.workflowsPaths=qn`, manifest.ts's
72
- * citation). REPLACES `workflowsPath` rather than adding to it, and the replacement fires on the
73
- * key's mere PRESENCE, not on whether anything resolved (the pinned binary's `Lt` gate is
74
- * `!j.workflows&&...`, checked independently of `qn.length`): a plugin whose every declared entry
75
- * is invalid ends up with NEITHER field set, exactly as claude leaves it with no workflows source
76
- * at all rather than silently falling back to the shadowed default directory. Each entry may be a
71
+ * resolved. REPLACES `workflowsPath` rather than adding to it, and the replacement fires on the
72
+ * key's mere PRESENCE, not on whether anything resolved: a plugin whose every declared entry is
73
+ * invalid ends up with NEITHER field set, exactly as claude leaves it with no workflows source at
74
+ * all rather than silently falling back to the shadowed default directory. Each entry may be a
77
75
  * directory or a single workflow file.
78
76
  */
79
77
  workflowsPaths?: string[];
@@ -0,0 +1,26 @@
1
+ import { loadPlugins } from "./loader.js";
2
+ export interface PluginLayout {
3
+ dirs?: string[];
4
+ files?: Record<string, string>;
5
+ links?: Record<string, string>;
6
+ manifest?: unknown;
7
+ }
8
+ export type LoadPluginsFn = typeof loadPlugins;
9
+ /** Expands a content shorthand (see the header) into the file text it stands for; anything else is literal. */
10
+ export declare function expandContent(content: string): string;
11
+ export declare function materializeLayout(layout: PluginLayout): {
12
+ base: string;
13
+ root: string;
14
+ };
15
+ /** Runs `load` (defaults to the real `loadPlugins`) on a materialised layout; the result is JSON with `<tmp>` in place of the temp dir. */
16
+ export declare function runLayout(layout: PluginLayout, load?: LoadPluginsFn): unknown;
17
+ /**
18
+ * Whether a layout's result can depend on the file system folding case: some two distinct names it
19
+ * involves -- a path segment of a created directory, file or link, a link target, any string (or key)
20
+ * of the manifest, or a name the loader looks for -- differ only in case (`Skill.MD` beside the
21
+ * loader's `SKILL.md`, a manifest's `./Commands` beside `commands/`). On a case-insensitive volume
22
+ * (macOS, where the corpus was recorded) such names find each other; on a case-sensitive one they do
23
+ * not. Deliberately coarse: it compares single segments, so it may flag a layout whose answer would
24
+ * not in fact change, never miss one that would.
25
+ */
26
+ export declare function dependsOnCaseFolding(layout: PluginLayout): boolean;
@@ -23,9 +23,8 @@ export interface LoadPluginsResult {
23
23
  agentFileRejections: AgentDefinitionRejection[];
24
24
  /**
25
25
  * Fix round 3 (M-5): a `hooks/hooks.json` that exists, parses as an object, but carries no
26
- * top-level `"hooks"` key -- claude's own `hook-load-failed` diagnostic (`hooks.json must have
27
- * \`hooks\` (the hook matchers) or \`modules\` (hooks modules), or both`, dump-confirmed). The
28
- * plugin itself still loads (a malformed hooks file is not a whole-plugin rejection, matching
26
+ * top-level `"hooks"` key -- claude reports the same file as a hook-load failure (`hooks.json must
27
+ * have \`hooks\` (the hook matchers) or \`modules\` (hooks modules), or both`). The plugin itself still loads (a malformed hooks file is not a whole-plugin rejection, matching
29
28
  * `agentFileRejections`'s own precedent immediately above), so this is the one channel that ever
30
29
  * names it. `production-wiring.ts` folds these into the same `warnings` list.
31
30
  */
@@ -35,7 +34,7 @@ export interface LoadPluginsResult {
35
34
  * `workflowsPathWarnings` -- ONE fold site, one channel, for every manifest custom-path override
36
35
  * this loader resolves, not a parallel field per component): a manifest `workflows`/`agents`/
37
36
  * `output-styles`/`commands`/`skills` entry that could not be used -- not a string, escapes the
38
- * plugin directory (lexically OR through a symlink -- fix round 5's own Aoe/KGe port), does not
37
+ * plugin directory (lexically OR through a symlink -- fix round 5's realpath fence), does not
39
38
  * exist, or (skills only) is a file where a directory is required -- plus a
40
39
  * `folder-shadowed-by-manifest` notice when an override silently drops an existing default
41
40
  * directory. Named per-plugin, per-entry, on the SAME "recoverable, not a whole-plugin rejection"
@@ -27,70 +27,47 @@ export interface PluginManifest {
27
27
  mcpServers?: Record<string, unknown>;
28
28
  /**
29
29
  * WS-21 fix round 4 (minors, M-3's last bullet): custom workflow source path(s), REPLACING the
30
- * default `workflows/` directory rather than adding to it -- confirmed by content search against
31
- * the installed claude CLI binary (`opt/homebrew/Caskroom/claude-code@latest`, 2.1.280; the pinned
32
- * 2.1.250 was unavailable locally, so this is the closest available build, cited by content, not
33
- * by offset): `if(j.workflows){let jn=Array.isArray(j.workflows)?j.workflows:[j.workflows],
34
- * qn=await Tb(jn,e,j.name,n,"workflows","Workflow","specified in manifest but",M,B,!1,h);if
35
- * (qn.length>0)ve.workflowsPaths=qn}`, and the default directory is populated only through a
36
- * SEPARATE `Lt=!j.workflows&&Be` gate a few lines above -- `Lt` is false whenever `j.workflows` is
37
- * present at all, regardless of whether any entry resolves. Each entry may name a DIRECTORY
38
- * (scanned the same way as the default one) or a single FILE (the SAME `Tb` call site passes
39
- * `requireDirectory:!1` for `workflows`, unlike the `!0` it passes for `skills`, which is the
40
- * fourth-from-last argument and the one place the two calls differ). `plugins/loader.ts`'s
41
- * `resolveManifestComponentOverride` (fix round 5's own generalisation) is where this is resolved
42
- * into `PluginBundle.workflowsPaths`.
30
+ * default `workflows/` directory rather than adding to it, as claude does: the default directory is
31
+ * not read whenever the key is present at all, regardless of whether any entry resolves. Each entry
32
+ * may name a DIRECTORY (scanned the same way as the default one) or a single FILE.
33
+ * `plugins/loader.ts`'s `resolveManifestComponentOverride` (fix round 5's own generalisation) is
34
+ * where this is resolved into `PluginBundle.workflowsPaths`.
43
35
  */
44
36
  workflows?: string | string[];
45
37
  /**
46
38
  * WS-21 fix round 5: the same custom-path-override shape as `workflows` above, for the built-in
47
- * `agents/` component -- content-search confirmed against the installed claude CLI binary
48
- * (2.1.280): `if(pt)ve.agentsPath=$t;if(j.agents){let jn=Array.isArray(j.agents)?j.agents:
49
- * [j.agents],qn=await Tb(jn,e,j.name,n,"agents","Agent","specified in manifest but",M,B,!1,h);if
50
- * (qn.length>0)ve.agentsPaths=qn}` -- `pt=!j.agents&&Fe` shadows the default directory on the
51
- * key's mere presence, the identical `!j.X&&Y` shape `workflows`' own `Lt` gate has. The CONSUMER
52
- * side (dump-confirmed separately, a different chunk of the same binary) stat()s each entry and
53
- * branches directory-vs-file: `M.agentsPaths.map(async(he)=>{let ve=await stat(he);if(ve.
54
- * isDirectory()){...scan the dir...}else if(...){...one file...}})` -- ported as
55
- * `scanPluginAgentsOverride`.
39
+ * `agents/` component -- the key's mere presence shadows the default directory, and each entry is a
40
+ * directory (scanned like the default one) or a single agent file. `plugins/loader.ts`'s
41
+ * `scanPluginAgentsOverride` reads the entries.
56
42
  */
57
43
  agents?: string | string[];
58
44
  /**
59
45
  * WS-21 fix round 5: the same custom-path-override shape as `workflows`/`agents`, for `commands`
60
- * -- PLUS an inline `{<name>: {source?: string; content?: string}}` object-map form claude's own
61
- * `eqt` also accepts (content-search confirmed against the installed claude CLI binary, 2.1.280),
62
- * letting a manifest embed a command's text directly instead of pointing at a file. Winter does
63
- * NOT port the inline form this round (`plugins/loader.ts`'s `resolveCommandsManifestOverride` has
64
- * the full disclosed-scope note); it is typed here only so a manifest using it is not silently
65
- * miscast, and so the shadow-on-presence check still fires for it.
46
+ * -- PLUS an inline `{<name>: {source?: string; content?: string}}` object-map form claude also
47
+ * accepts, letting a manifest embed a command's text directly instead of pointing at a file. Winter
48
+ * does NOT support the inline form yet (`plugins/loader.ts`'s `resolveCommandsManifestOverride`
49
+ * warns about it); it is typed here only so a manifest using it is not silently miscast, and so the
50
+ * shadow-on-presence check still fires for it.
66
51
  */
67
52
  commands?: string | string[] | Record<string, {
68
53
  source?: string;
69
54
  content?: string;
70
55
  }>;
71
56
  /**
72
- * WS-21 fix round 5: `skills` is a manifest custom-path override too, but content-search confirmed
73
- * against the installed claude CLI binary (2.1.280) shows a DIFFERENT precedence than every other
74
- * component here -- ADDITIVE, not shadow-on-presence. The builder's own gate (`_t=Le`, no `!j.
75
- * skills` negation, unlike `commands`' `ht=!j.commands&&Me`/`agents`' `pt=!j.agents&&Fe`/
76
- * `workflows`' `Lt=!j.workflows&&Be`) sets the default `ve.skillsPath` REGARDLESS of whether
77
- * `j.skills` is also present; the CONSUMER (a separate chunk of the same binary) confirms it:
78
- * `if(h.skillsPath){...load the default...}if(h.skillsPaths){...ALSO load every override entry...}`
79
- * -- both run, never either/or. Consistent with this: `skills` is ABSENT from the
80
- * `folder-shadowed-by-manifest` tuple list (`["commands",...],["agents",...],["outputStyles",...],
81
- * ["themes",...]` -- no `"skills"` entry), so no shadow warning is possible for it.
82
- * `plugins/loader.ts`'s `scanPluginSkillsOverride` is where this is resolved and merged.
57
+ * WS-21 fix round 5: `skills` is a manifest custom-path override too, but with a DIFFERENT
58
+ * precedence than every other component here -- ADDITIVE, not shadow-on-presence: claude loads the
59
+ * default `skills/` directory AND every override entry, never either/or, and never reports the
60
+ * default skills folder as shadowed. Each entry must be a directory (a parent of skill
61
+ * directories). `plugins/loader.ts`'s `resolvePluginSkills` is where this is resolved and merged.
83
62
  */
84
63
  skills?: string | string[];
85
64
  /**
86
65
  * WS-21 fix round 5: the same custom-path-override shape as `agents`/`workflows` (shadow-on-
87
- * presence, `requireDirectory:false`) for `output-styles` -- content-search confirmed against the
88
- * installed claude CLI binary (2.1.280): `gt=j.outputStyles` (note the CAMEL-CASE manifest key,
89
- * unlike the kebab-case default DIRECTORY name `output-styles/`), `Rt=!gt&&He` shadows the default
90
- * on presence, and `if(gt){...Tb(...,"output-styles","Output style",...,!1,...);if(qn.length>0)
91
- * ve.outputStylesPaths=qn}`. Resolved LAZILY (unlike commands/agents/skills, which are eagerly
92
- * scanned at load time): `context/output-styles.ts`'s `PluginOutputStyleSource.outputStylesPaths`
93
- * is where a `<plugin>:<style>` lookup actually reads it.
66
+ * presence, a bare file allowed) for `output-styles` -- note the CAMEL-CASE manifest key, unlike
67
+ * the kebab-case default DIRECTORY name `output-styles/`. Resolved LAZILY (unlike
68
+ * commands/agents/skills, which are eagerly scanned at load time):
69
+ * `context/output-styles.ts`'s `PluginOutputStyleSource.outputStylesPaths` is where a
70
+ * `<plugin>:<style>` lookup actually reads it.
94
71
  */
95
72
  outputStyles?: string | string[];
96
73
  [key: string]: unknown;
@@ -275,7 +275,7 @@ export interface ProductionWiring {
275
275
  * WS-21 §3.4.4 step 4 / §6.3 item 6: every settings tier's `env` block, claude's per-tier filters
276
276
  * applied (`settings/env-filter.ts`), merged HIGHEST-PRECEDENCE-WINS. APPLIED by this function
277
277
  * (fix round 1, item 5): `Object.assign(env, settingsEnv)` after every settings read, which reaches a
278
- * tool spawn's process env the way claude's own `Object.assign(process.env, filtered)` does -- in
278
+ * tool spawn's process env, as claude applies each tier's filtered env to its own process env -- in
279
279
  * production `env` IS `process.env`, and each test passes its own isolated object. Also exposed here
280
280
  * so a caller (and this file's tests) can see exactly which variables were applied.
281
281
  */
@@ -14,6 +14,12 @@ export declare class Queue<T> implements AsyncIterable<T> {
14
14
  private ended;
15
15
  write(v: T): void;
16
16
  end(): void;
17
+ /** The values written but not yet read, oldest first. A copy: changing it changes nothing here. */
18
+ pending(): T[];
19
+ /** Drops these values if they are still waiting to be read (one already read is untouched); answers how many were dropped. */
20
+ remove(values: ReadonlySet<T>): number;
21
+ /** Puts values taken with `remove` back at the HEAD, in order (they were the oldest). For a reader that is mid-item, never one waiting on `write`. */
22
+ restore(values: readonly T[]): void;
17
23
  [Symbol.asyncIterator](): AsyncGenerator<Awaited<T>, void, unknown>;
18
24
  }
19
25
  export declare function createInMemoryChannel(): {
@@ -1,7 +1,2 @@
1
- /**
2
- * `modelKey` is a provider-qualified key (`anthropic/claude-opus-5`) or a bare id. claude compares a
3
- * NORMALIZED id, so the Opus match tolerates the two spellings a catalog row really uses for the same
4
- * build: a trailing `-YYYYMMDD` snapshot date, and a dotted minor (`claude-opus-4.1`). The original
5
- * Opus 4 has no minor in its dated id (`claude-opus-4-20250514`); it is `claude-opus-4-0`.
6
- */
1
+ /** Whether a model key takes the full interface texts. */
7
2
  export declare function claudeModelTakesFullPrompt(modelKey: string): boolean;
@@ -12,11 +12,11 @@ export interface SandboxFilesystemSettings {
12
12
  allowRead?: string[];
13
13
  denyRead?: string[];
14
14
  /**
15
- * Fix round 16, item 2 (claude's own `ag()`, dump-verified: `function ag(){return
16
- * pe?.filesystem?.allowGitConfig??!1}`): `sandbox.filesystem.allowGitConfig` in settings.json --
17
- * the ONE settings-facing door for `SeatbeltProfileInput.allowGitConfigWrites`/
18
- * `RunCommandOptions.allowGitConfigWrites`, which this module and spawn.ts already had (round 15)
19
- * but nothing set. Wired through `tools/impl/{bash,monitor}.ts`'s own options-builders (each reads
15
+ * Fix round 16, item 2: `sandbox.filesystem.allowGitConfig` in settings.json (default false) -- when
16
+ * true, a sandboxed command may write `.git/config`. The ONE settings-facing door for
17
+ * `SeatbeltProfileInput.allowGitConfigWrites`/`RunCommandOptions.allowGitConfigWrites`, which this
18
+ * module and spawn.ts already had (round 15) but nothing set. Wired through
19
+ * `tools/impl/{bash,monitor}.ts`'s own options-builders (each reads
20
20
  * `ctx.sandboxSettings.filesystem?.allowGitConfig` directly) -- `buildSeatbeltProfile` itself never
21
21
  * reads this field; it is CONSUMED at the caller boundary (spawn.ts's own `allowGitConfigWrites`
22
22
  * param), matching every other filesystem key's own "settings shape carries it, a caller resolves
@@ -43,41 +43,6 @@ 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
46
  /** The runtime's image working directory under the winter/store home (tools/image-prep.ts) -- write-denied to the shell. */
82
47
  export declare const IMAGE_PREP_DIRNAME = "image-prep";
83
48
  /**
@@ -94,7 +59,7 @@ export declare const IMAGE_PREP_DIRNAME = "image-prep";
94
59
  * ESCAPE FIRST, FOLD SECOND, per character: a letter becomes `[Xx]`, and everything else is escaped
95
60
  * exactly as `sbplRegexLiteral` escapes it (the brand grammar admits `-` and, for a dot-dir, the
96
61
  * leading `.` -- which MUST be escaped or it matches any character). Applied to Winter's own dot-dir
97
- * it renders `\.[Ww][Ii][Nn][Tt][Ee][Rr]`, byte for byte what the constant it replaces spelled, so
62
+ * it renders `\.[Ww][Ii][Nn][Tt][Ee][Rr]`, exactly what the constant it replaces spelled, so
98
63
  * the rendered profile is unchanged under `WINTER_BRAND` (a test diffs the whole profile text).
99
64
  *
100
65
  * Exported so the deny suite can assert the rendering directly rather than by reading the profile.
@@ -110,46 +75,42 @@ export interface SeatbeltProfileInput {
110
75
  /** WS-12 §5.3: filesystem.denyRead, layered AFTER the read-allow block (last-match-wins). */
111
76
  denyReadPaths?: string[];
112
77
  /**
113
- * Fix round 11 (claude's `Li`/`Rt`, dump byte 15365905/15282610, pinned 2.1.250): glob-shaped
114
- * deny entries, PRE-CONVERTED by the caller to SBPL regex SOURCE TEXT (`permissions/file-rules.ts`'s
115
- * `globToSbplRegexSource`/`recursiveGlobToSbplRegexSource`/`splitDenyPathsByGlobShape`) -- this
116
- * module has no glob grammar of its own (mirrors `denyWritePaths`/`denyReadPaths`'s own "already
117
- * resolved by the caller" posture) and only quotes/renders. Claude's own macOS sandbox profile
118
- * builder renders a glob-shaped deny as `(regex ...)` and a plain one as `(subpath ...)` (`Li`); a
119
- * `subpath` deny alone -- Winter's pre-round-11 posture -- silently drops a glob-shaped Edit deny
120
- * (e.g. a globstar-anchored `.env` pattern) or `denyWrite` entry from the sandbox layer entirely
121
- * (the PERMISSION-RULE layer still enforced it for a recognized tool call; a bash-invoked
122
- * `tee`/`cp` bypassing that layer did not).
78
+ * Fix round 11: glob-shaped deny entries, PRE-CONVERTED by the caller to SBPL regex SOURCE TEXT
79
+ * (`permissions/file-rules.ts`'s `globToSbplRegexSource`/`recursiveGlobToSbplRegexSource`/
80
+ * `splitDenyPathsByGlobShape`) -- this module has no glob grammar of its own (mirrors
81
+ * `denyWritePaths`/`denyReadPaths`'s own "already resolved by the caller" posture) and only
82
+ * quotes/renders. A glob-shaped deny renders as `(regex ...)` and a plain one as `(subpath ...)`, as
83
+ * claude's macOS sandbox does; a `subpath` deny alone -- Winter's pre-round-11 posture -- silently
84
+ * dropped a glob-shaped Edit deny (e.g. a globstar-anchored `.env` pattern) or `denyWrite` entry from
85
+ * the sandbox layer entirely (the PERMISSION-RULE layer still enforced it for a recognized tool call;
86
+ * a bash-invoked `tee`/`cp` bypassing that layer did not).
123
87
  */
124
88
  denyWriteRegexes?: string[];
125
89
  denyReadRegexes?: string[];
126
90
  /**
127
- * Fix round 12 ("Important" item, claude's own `Ch`/`ed`/`mR`/`pR`, dump byte 15368116/15367994/
128
- * 15369065/15368380, pinned 2.1.250): the ancestor-rename-bypass fix. `denyWriteGlobFixedPrefixes`/
129
- * `denyReadGlobFixedPrefixes` are the CANONICALIZED fixed-prefix directory of each glob-shaped
130
- * denyWrite/denyRead entry (`permissions/file-rules.ts`'s `splitDenyPathsByGlobShape`, its own
131
- * `globFixedPrefixes` output) -- this module has no glob grammar of its own, mirrors the other
132
- * caller-pre-resolved fields above. Combined with `denyWritePaths`/`denyReadPaths` (the PLAIN
133
- * entries, reused directly -- no new field needed for those), `buildAncestorRenameBypassBlock`
134
- * below builds a `(deny file-write-unlink file-write-create ...)` clause naming every ANCESTOR of
135
- * each denied path/glob-fixed-prefix, PLUS the fixed prefix itself, so a sandboxed `mv <ancestor>
136
- * <elsewhere> && <write inside where it used to be> && mv <elsewhere> <ancestor>` cannot rename an
137
- * ancestor of a denied path out of the way (and back) to slip a write past the deny.
91
+ * Fix round 12: the ancestor-rename fence. `denyWriteGlobFixedPrefixes`/`denyReadGlobFixedPrefixes`
92
+ * are the CANONICALIZED fixed-prefix directory of each glob-shaped denyWrite/denyRead entry
93
+ * (`permissions/file-rules.ts`'s `splitDenyPathsByGlobShape`, its own `globFixedPrefixes` output) --
94
+ * this module has no glob grammar of its own, mirrors the other caller-pre-resolved fields above.
95
+ * Combined with `denyWritePaths`/`denyReadPaths` (the PLAIN entries, reused directly),
96
+ * `buildAncestorRenameBypassBlock` builds a `(deny file-write-unlink file-write-create ...)` clause
97
+ * naming every ANCESTOR of each denied path/glob-fixed-prefix, PLUS the fixed prefix itself, so a
98
+ * sandboxed `mv <ancestor> <elsewhere> && <write inside where it used to be> && mv <elsewhere>
99
+ * <ancestor>` cannot rename an ancestor of a denied path out of the way (and back) to slip a write
100
+ * past the deny.
138
101
  */
139
102
  denyWriteGlobFixedPrefixes?: string[];
140
103
  denyReadGlobFixedPrefixes?: string[];
141
104
  /**
142
- * Fix round 13 ("Important" item 1, claude's own `fR`, dump byte 15367091): "keep read-denied
143
- * paths inside write roots in place." Winter's read-deny/write-allow sections previously composed
144
- * exactly the way claude's OWN `mR`/`pR` alone would -- last-match-wins, and the write-allow
145
- * (`(allow file-write* (subpath <root>))`) is emitted AFTER the read-deny section, so it silently
146
- * overrides any read-deny's own implicit protection against being UNLINKED (renamed away): with
147
- * `Read(.env)` denied and cwd writable, a sandboxed `mv .env x && cat x` renamed the read-denied
148
- * file to a new, non-denied name and read the secret through it. claude closes this with a THIRD
149
- * section, `fR`, emitted AFTER the write-allow block: for each read-denied path (or glob-shaped
150
- * entry, `denyReadGlobEntries` below) that sits inside a write root, denies `file-write-unlink` on
151
- * its own recursive clause (minus any write root nested INSIDE it, carved back out) and on every
152
- * one of its ancestor directories that is ALSO inside a write root.
105
+ * Fix round 13: "keep read-denied paths inside write roots in place." The write-allow block
106
+ * (`(allow file-write* (subpath <root>))`) is emitted AFTER the read-deny section and, last match
107
+ * winning, overrode a read-deny's implicit protection against being UNLINKED (renamed away): with
108
+ * `Read(.env)` denied and cwd writable, a sandboxed `mv .env x && cat x` renamed the read-denied file
109
+ * to a new, non-denied name and read the secret through it. claude closes this too. A third section,
110
+ * emitted AFTER the write-allow block (`buildReadDenyKeepInPlaceBlock`), denies `file-write-unlink`
111
+ * for each read-denied path (or glob-shaped entry, this field) that sits inside a write root -- minus
112
+ * any write root nested INSIDE it, carved back out -- and for each of its ancestor directories that is
113
+ * ALSO inside a write root. Each entry pairs the glob's regex with its own fixed prefix.
153
114
  */
154
115
  denyReadGlobEntries?: GlobDenyEntry[];
155
116
  /** Resolved network posture -- see resolveNetworkPosture's own header for why this is a plain boolean here. */
@@ -210,12 +171,11 @@ export interface SeatbeltProfileInput {
210
171
  */
211
172
  brand?: SandboxBrand;
212
173
  /**
213
- * Fix round 15 (claude's own `cR(e=false)` / `mR`'s own `r=false` default parameter): when true,
214
- * `.git/config` is NOT added to the default write-protected entries (`buildDefaultWriteProtectionBlock`
215
- * above) -- every OTHER default protection (shell/tool config files, editor/agent dot-dirs,
216
- * `.git/hooks`) is unaffected; this flag only ever gates `.git/config`, matching `cR`'s own `!e`
217
- * guard exactly. Omitted = `false` = protected, matching claude's own default. No caller sets this
218
- * true yet -- kept for parity since claude's own signature carries the knob.
174
+ * Fix round 15: when true, `.git/config` is NOT among the default write-protected entries
175
+ * (`buildDefaultWriteProtectionBlock`) -- every OTHER default protection (shell/tool config files,
176
+ * editor/agent dot-dirs, `.git/hooks`) is unaffected; this flag only ever gates `.git/config`.
177
+ * Omitted = `false` = protected, claude's default too. Set from `sandbox.filesystem.allowGitConfig`
178
+ * (round 16).
219
179
  */
220
180
  allowGitConfigWrites?: boolean;
221
181
  }
@@ -84,7 +84,7 @@ export interface RunCommandOptions {
84
84
  denyWritePaths?: string[];
85
85
  denyReadPaths?: string[];
86
86
  /**
87
- * Fix round 11 (claude's `Li`/`Rt`, dump byte 15365905/15282610): glob-shaped `denyWritePaths`/
87
+ * Fix round 11: glob-shaped `denyWritePaths`/
88
88
  * `denyReadPaths` entries, PRE-CONVERTED to SBPL regex source by the caller (`permissions/
89
89
  * file-rules.ts`'s `splitDenyPathsByGlobShape`) -- this module stays glob-grammar-free, exactly
90
90
  * like `denyWritePaths`/`denyReadPaths` themselves are already resolved, absolute paths by the
@@ -93,7 +93,7 @@ export interface RunCommandOptions {
93
93
  denyWriteRegexes?: string[];
94
94
  denyReadRegexes?: string[];
95
95
  /**
96
- * Fix round 12 ("Important" item, claude's own `Ch`/`ed`): the ancestor-rename-bypass fix -- each
96
+ * Fix round 12: the ancestor-rename-bypass fix -- each
97
97
  * glob-shaped `denyWritePaths`/`denyReadPaths` entry's OWN canonicalized fixed-prefix directory
98
98
  * (see `SeatbeltProfileInput.denyWriteGlobFixedPrefixes`'s own header). Same "caller pre-resolves,
99
99
  * this module stays glob-grammar-free" posture as `denyWriteRegexes`/`denyReadRegexes` above.
@@ -101,10 +101,9 @@ export interface RunCommandOptions {
101
101
  denyWriteGlobFixedPrefixes?: string[];
102
102
  denyReadGlobFixedPrefixes?: string[];
103
103
  /**
104
- * Fix round 13 ("Important" item 1, claude's own `fR`): the read-deny-keep-in-place fix -- each
105
- * glob-shaped `denyReadPaths` entry, PAIRED with its own fixed prefix (see
106
- * `SeatbeltProfileInput.denyReadGlobEntries`'s own header). Read-only -- claude's own `fR` is a
107
- * read-deny-specific concern.
104
+ * Fix round 13: the read-deny-keep-in-place fix -- each glob-shaped `denyReadPaths` entry, PAIRED
105
+ * with its own fixed prefix (see `SeatbeltProfileInput.denyReadGlobEntries`'s own header).
106
+ * Read-side only -- keeping read-denied paths in place is a read-deny-specific concern.
108
107
  */
109
108
  denyReadGlobEntries?: GlobDenyEntry[];
110
109
  /**
@@ -41,49 +41,31 @@ export interface BuildSkillListingOptions {
41
41
  export declare function isModelVisible(overrides: SkillOverrides | undefined, skill: SkillMeta | string): boolean;
42
42
  /** True when a USER `/name` may still reach this skill. Only `off` closes that door. */
43
43
  export declare function isUserInvocable(overrides: SkillOverrides | undefined, skill: SkillMeta | string): boolean;
44
- /**
45
- * claude's `Ckn`: the context window a budget is sized against when the session reports none.
46
- */
44
+ /** The context window (tokens) a budget is sized against when the session reports none. */
47
45
  export declare const DEFAULT_SKILL_LISTING_CONTEXT_WINDOW_TOKENS = 200000;
48
- /**
49
- * The whole-listing character budget -- claude's `Ige`: `floor(window x chars-per-token x fraction)`,
50
- * with claude's 200k-token default window when the session reports none (or a non-finite one).
51
- * An explicit `budgetChars` wins (claude's `SLASH_COMMAND_TOOL_CHAR_BUDGET` analog), and there `0`
52
- * means "no budget" -- a Winter extension, never "no listing".
53
- */
46
+ /** The whole-listing character budget: an explicit `budgetChars` (`0` = no budget), or a share of the context window in characters. */
54
47
  export declare function skillListingBudgetChars(opts: {
55
48
  contextWindowTokens?: number | undefined;
56
49
  budgetFraction?: number | undefined;
57
50
  budgetChars?: number | undefined;
58
51
  }): number;
59
52
  /**
60
- * claude's `xkn`: a description longer than the cap keeps `cap - 1` characters and gains the
61
- * ellipsis, so the result is exactly `cap` characters long.
53
+ * A description longer than the cap keeps `cap - 1` characters and gains the ellipsis, so the result
54
+ * is exactly `cap` characters long (the same shape claude's listing uses).
62
55
  */
63
56
  export declare function truncateSkillDescription(description: string, maxDescChars: number): string;
64
57
  /**
65
- * claude's per-skill line (`Mkn`): `- <name>: <description>`, or `- <name>` for an entry whose
66
- * description is empty (a `name-only` override, or one the budget reduced to its name).
58
+ * One listing line: `- <name>: <description>`, or `- <name>` for an entry whose description is
59
+ * empty (a `name-only` override, or one the budget reduced to its name).
67
60
  */
68
61
  export declare function renderSkillListingLine(entry: {
69
62
  name: string;
70
63
  description: string;
71
64
  }): string;
72
- /** The `skill_listing` attachment's `content`: one line per entry, newline-joined (claude's `Rot` output). */
65
+ /** The `skill_listing` attachment's `content`: one line per entry, newline-joined. */
73
66
  export declare function renderSkillListingContent(entries: readonly {
74
67
  name: string;
75
68
  description: string;
76
69
  }[]): string;
77
- /**
78
- * Build the model-facing listing -- claude 0.3.250's `Rot`, applied here once so every consumer sees
79
- * the budgeted result.
80
- *
81
- * ORDER IS PRECEDENCE ORDER (the index's own): project first, builtin last. Every visible skill is
82
- * listed; the budget decides only which ones keep their DESCRIPTION:
83
- * - everything fits -> every line is full;
84
- * - over budget -> `name-only` entries and Winter's builtin (claude: bundled) skills stay full,
85
- * every other entry starts as its bare name, and descriptions are restored in priority order
86
- * while the remaining budget holds them. claude orders that priority by its per-skill usage
87
- * score; Winter keeps no usage history, so every score ties and precedence order decides.
88
- */
70
+ /** The model-facing listing: every visible skill, with the budget deciding which keep their description. */
89
71
  export declare function buildSkillListing(skills: readonly SkillMeta[], opts?: BuildSkillListingOptions): SkillListing;
@@ -88,9 +88,8 @@ export interface SkillIndexOptions {
88
88
  bodyBytes?: number | undefined;
89
89
  /**
90
90
  * Fix round 4 (I-E, the router same-view test): a workflow, registered as a SYNTHETIC skill so
91
- * `Skill("<name>")` and the slash-command surface both run it -- claude's own `m()` turns every
92
- * discovered workflow into exactly this shape (dump-confirmed: `{type:"prompt", kind:"workflow",
93
- * ...}`). Unlike every other entry `discover()` finds, a synthetic entry carries its own BODY
91
+ * `Skill("<name>")` and the slash-command surface both run it -- claude likewise offers every
92
+ * discovered workflow as a prompt-type command under the workflow's name. Unlike every other entry `discover()` finds, a synthetic entry carries its own BODY
94
93
  * directly (`load()` returns it as-is, never reading `path` from disk -- there is no SKILL.md a
95
94
  * workflow script's own identity could point `load()` at). `path` is still required on the
96
95
  * resulting `SkillMeta` (a synthetic, non-filesystem marker -- `workflows/store.ts`'s own
@@ -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.41";
481
+ export declare const RUNTIME_ENGINE_VERSION = "0.0.44";
482
482
  export interface ResolvedEngineSession {
483
483
  config: RuntimeConfig;
484
484
  store: SessionPersistence | undefined;