@anthropic-ai/sdk 0.119.0 → 0.120.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/helpers/beta/environments.d.mts +5 -4
  3. package/helpers/beta/environments.d.mts.map +1 -1
  4. package/helpers/beta/environments.d.ts +5 -4
  5. package/helpers/beta/environments.d.ts.map +1 -1
  6. package/helpers/beta/environments.js +5 -4
  7. package/helpers/beta/environments.js.map +1 -1
  8. package/helpers/beta/environments.mjs +5 -4
  9. package/helpers/beta/environments.mjs.map +1 -1
  10. package/internal/file-store.d.mts +136 -0
  11. package/internal/file-store.d.mts.map +1 -0
  12. package/internal/file-store.d.ts +136 -0
  13. package/internal/file-store.d.ts.map +1 -0
  14. package/internal/file-store.js +531 -0
  15. package/internal/file-store.js.map +1 -0
  16. package/internal/file-store.mjs +523 -0
  17. package/internal/file-store.mjs.map +1 -0
  18. package/lib/environments/poller.d.mts +6 -0
  19. package/lib/environments/poller.d.mts.map +1 -1
  20. package/lib/environments/poller.d.ts +6 -0
  21. package/lib/environments/poller.d.ts.map +1 -1
  22. package/lib/environments/poller.js +46 -1
  23. package/lib/environments/poller.js.map +1 -1
  24. package/lib/environments/poller.mjs +46 -1
  25. package/lib/environments/poller.mjs.map +1 -1
  26. package/lib/environments/worker.d.mts +89 -12
  27. package/lib/environments/worker.d.mts.map +1 -1
  28. package/lib/environments/worker.d.ts +89 -12
  29. package/lib/environments/worker.d.ts.map +1 -1
  30. package/lib/environments/worker.js +336 -67
  31. package/lib/environments/worker.js.map +1 -1
  32. package/lib/environments/worker.mjs +336 -68
  33. package/lib/environments/worker.mjs.map +1 -1
  34. package/package.json +1 -1
  35. package/resources/beta/agents/agents.d.mts +322 -27
  36. package/resources/beta/agents/agents.d.mts.map +1 -1
  37. package/resources/beta/agents/agents.d.ts +322 -27
  38. package/resources/beta/agents/agents.d.ts.map +1 -1
  39. package/resources/beta/agents/agents.js.map +1 -1
  40. package/resources/beta/agents/agents.mjs.map +1 -1
  41. package/resources/beta/agents/index.d.mts +1 -1
  42. package/resources/beta/agents/index.d.mts.map +1 -1
  43. package/resources/beta/agents/index.d.ts +1 -1
  44. package/resources/beta/agents/index.d.ts.map +1 -1
  45. package/resources/beta/agents/index.js.map +1 -1
  46. package/resources/beta/agents/index.mjs.map +1 -1
  47. package/resources/beta/beta.d.mts +2 -2
  48. package/resources/beta/beta.d.mts.map +1 -1
  49. package/resources/beta/beta.d.ts +2 -2
  50. package/resources/beta/beta.d.ts.map +1 -1
  51. package/resources/beta/beta.js.map +1 -1
  52. package/resources/beta/beta.mjs.map +1 -1
  53. package/resources/beta/environments/work.d.mts +5 -1
  54. package/resources/beta/environments/work.d.mts.map +1 -1
  55. package/resources/beta/environments/work.d.ts +5 -1
  56. package/resources/beta/environments/work.d.ts.map +1 -1
  57. package/resources/beta/environments/work.js +0 -17
  58. package/resources/beta/environments/work.js.map +1 -1
  59. package/resources/beta/environments/work.mjs +0 -17
  60. package/resources/beta/environments/work.mjs.map +1 -1
  61. package/resources/beta/index.d.mts +1 -1
  62. package/resources/beta/index.d.mts.map +1 -1
  63. package/resources/beta/index.d.ts +1 -1
  64. package/resources/beta/index.d.ts.map +1 -1
  65. package/resources/beta/index.js.map +1 -1
  66. package/resources/beta/index.mjs.map +1 -1
  67. package/resources/beta/messages/messages.d.mts +1 -1
  68. package/resources/beta/messages/messages.d.ts +1 -1
  69. package/resources/messages/messages.d.mts +1 -1
  70. package/resources/messages/messages.d.ts +1 -1
  71. package/src/helpers/beta/environments.ts +5 -4
  72. package/src/internal/file-store.ts +550 -0
  73. package/src/lib/environments/poller.ts +47 -1
  74. package/src/lib/environments/worker.ts +419 -79
  75. package/src/resources/beta/agents/agents.ts +435 -30
  76. package/src/resources/beta/agents/index.ts +17 -0
  77. package/src/resources/beta/beta.ts +34 -0
  78. package/src/resources/beta/environments/work.ts +7 -0
  79. package/src/resources/beta/index.ts +17 -0
  80. package/src/resources/beta/messages/messages.ts +1 -1
  81. package/src/resources/messages/messages.ts +1 -1
  82. package/src/tools/agent-toolset/fs-util.ts +40 -24
  83. package/src/tools/agent-toolset/memories.ts +1017 -0
  84. package/src/tools/agent-toolset/node.browser.ts +68 -0
  85. package/src/tools/agent-toolset/node.ts +139 -53
  86. package/src/tools/agent-toolset/skills.ts +29 -9
  87. package/src/tools/agent-toolset/sync-interval.ts +30 -0
  88. package/src/version.ts +1 -1
  89. package/tools/agent-toolset/fs-util.d.mts +23 -10
  90. package/tools/agent-toolset/fs-util.d.mts.map +1 -1
  91. package/tools/agent-toolset/fs-util.d.ts +23 -10
  92. package/tools/agent-toolset/fs-util.d.ts.map +1 -1
  93. package/tools/agent-toolset/fs-util.js +40 -25
  94. package/tools/agent-toolset/fs-util.js.map +1 -1
  95. package/tools/agent-toolset/fs-util.mjs +38 -25
  96. package/tools/agent-toolset/fs-util.mjs.map +1 -1
  97. package/tools/agent-toolset/memories.d.mts +173 -0
  98. package/tools/agent-toolset/memories.d.mts.map +1 -0
  99. package/tools/agent-toolset/memories.d.ts +173 -0
  100. package/tools/agent-toolset/memories.d.ts.map +1 -0
  101. package/tools/agent-toolset/memories.js +902 -0
  102. package/tools/agent-toolset/memories.js.map +1 -0
  103. package/tools/agent-toolset/memories.mjs +895 -0
  104. package/tools/agent-toolset/memories.mjs.map +1 -0
  105. package/tools/agent-toolset/node.browser.d.mts +31 -0
  106. package/tools/agent-toolset/node.browser.d.mts.map +1 -1
  107. package/tools/agent-toolset/node.browser.d.ts +31 -0
  108. package/tools/agent-toolset/node.browser.d.ts.map +1 -1
  109. package/tools/agent-toolset/node.browser.js +59 -1
  110. package/tools/agent-toolset/node.browser.js.map +1 -1
  111. package/tools/agent-toolset/node.browser.mjs +54 -0
  112. package/tools/agent-toolset/node.browser.mjs.map +1 -1
  113. package/tools/agent-toolset/node.d.mts +54 -22
  114. package/tools/agent-toolset/node.d.mts.map +1 -1
  115. package/tools/agent-toolset/node.d.ts +54 -22
  116. package/tools/agent-toolset/node.d.ts.map +1 -1
  117. package/tools/agent-toolset/node.js +83 -44
  118. package/tools/agent-toolset/node.js.map +1 -1
  119. package/tools/agent-toolset/node.mjs +77 -44
  120. package/tools/agent-toolset/node.mjs.map +1 -1
  121. package/tools/agent-toolset/skills.d.mts +16 -7
  122. package/tools/agent-toolset/skills.d.mts.map +1 -1
  123. package/tools/agent-toolset/skills.d.ts +16 -7
  124. package/tools/agent-toolset/skills.d.ts.map +1 -1
  125. package/tools/agent-toolset/skills.js +27 -9
  126. package/tools/agent-toolset/skills.js.map +1 -1
  127. package/tools/agent-toolset/skills.mjs +27 -9
  128. package/tools/agent-toolset/skills.mjs.map +1 -1
  129. package/tools/agent-toolset/sync-interval.d.mts +19 -0
  130. package/tools/agent-toolset/sync-interval.d.mts.map +1 -0
  131. package/tools/agent-toolset/sync-interval.d.ts +19 -0
  132. package/tools/agent-toolset/sync-interval.d.ts.map +1 -0
  133. package/tools/agent-toolset/sync-interval.js +29 -0
  134. package/tools/agent-toolset/sync-interval.js.map +1 -0
  135. package/tools/agent-toolset/sync-interval.mjs +25 -0
  136. package/tools/agent-toolset/sync-interval.mjs.map +1 -0
  137. package/version.d.mts +1 -1
  138. package/version.d.ts +1 -1
  139. package/version.js +1 -1
  140. package/version.mjs +1 -1
@@ -16,14 +16,82 @@
16
16
  import { AnthropicError } from '../../core/error';
17
17
  import type { Anthropic } from '../../client';
18
18
  import type { BetaRunnableTool } from '../../lib/tools/BetaRunnableTool';
19
+ import type { BetaManagedAgentsSession } from '../../resources/beta/sessions/sessions';
19
20
  import type { AgentToolContext } from './node';
21
+ import type { SessionMemoryStoresOptions } from './memories';
20
22
 
21
23
  export type { AgentToolContext } from './node';
24
+ export type { MemoryDeleteMode, SessionMemoryStoresOptions } from './memories';
22
25
 
23
26
  function nodeOnly(name: string): never {
24
27
  throw new AnthropicError(`${name} requires Node.js or a Node-compatible runtime`);
25
28
  }
26
29
 
30
+ // `./sync-interval` is runtime-agnostic, so these re-export directly.
31
+ export { DEFAULT_MEMORY_SYNC_INTERVAL_MS, MIN_MEMORY_SYNC_INTERVAL_MS } from './sync-interval';
32
+
33
+ /**
34
+ * Duplicated literal, not a re-export: importing the value from `./memories`
35
+ * would pull that module's Node built-ins into browser bundles. The stub test
36
+ * pins it to the real module's value.
37
+ */
38
+ export const MEMORY_FLUSH_TIMEOUT_MS = 30_000;
39
+ export const MARKER_PATH = '.anthropic-memory-store';
40
+
41
+ /**
42
+ * Duplicated declaration, for the same reason as the literal above. Nothing in
43
+ * a browser build can throw it — the store download is Node-only — so this
44
+ * exists to keep the stub's export surface identical to the real module's.
45
+ */
46
+ export class SessionMemoryError extends AnthropicError {
47
+ constructor(message: string, cause?: unknown) {
48
+ super(message);
49
+ this.name = 'SessionMemoryError';
50
+ // in some environments the 'cause' property is already declared
51
+ // @ts-ignore
52
+ if (cause !== undefined) this.cause = cause;
53
+ }
54
+ }
55
+
56
+ export class SessionMemoryStores {
57
+ constructor(_client: Anthropic, _opts: SessionMemoryStoresOptions) {
58
+ nodeOnly('SessionMemoryStores');
59
+ }
60
+
61
+ get roots(): string[] {
62
+ return nodeOnly('SessionMemoryStores');
63
+ }
64
+
65
+ get readOnlyRoots(): string[] {
66
+ return nodeOnly('SessionMemoryStores');
67
+ }
68
+
69
+ download(_session: BetaManagedAgentsSession): Promise<void> {
70
+ return nodeOnly('SessionMemoryStores');
71
+ }
72
+
73
+ finish(): Promise<void> {
74
+ return nodeOnly('SessionMemoryStores');
75
+ }
76
+
77
+ /** @internal */
78
+ syncAll(_final: boolean): Promise<void> {
79
+ return nodeOnly('SessionMemoryStores');
80
+ }
81
+
82
+ syncIfDue(): Promise<void> {
83
+ return nodeOnly('SessionMemoryStores');
84
+ }
85
+
86
+ flushWrites(_signal?: AbortSignal): Promise<void> {
87
+ return nodeOnly('SessionMemoryStores');
88
+ }
89
+
90
+ dispose(): Promise<void> {
91
+ return nodeOnly('SessionMemoryStores');
92
+ }
93
+ }
94
+
27
95
  export function setupSkills(_ctx: AgentToolContext): Promise<() => Promise<void>> {
28
96
  return nodeOnly('setupSkills');
29
97
  }
@@ -24,9 +24,9 @@
24
24
  * const tools2 = betaAgentToolset20260401({ workdir: '/work' }).filter((t) => t.name !== 'bash');
25
25
  * ```
26
26
  *
27
- * Trust model: the file tools confine to `workdir` (symlink-aware) and are safe
28
- * without a sandbox; `bash` is unrestricted and should run inside one. See
29
- * {@link AgentToolContext}.
27
+ * Trust model: the file tools confine to `workdir` plus any `allowedRoots`
28
+ * (symlink-aware) and are safe without a sandbox; `bash` is unrestricted and
29
+ * should run inside one. See {@link AgentToolContext}.
30
30
  */
31
31
 
32
32
  import * as fs from 'node:fs/promises';
@@ -36,14 +36,33 @@ import * as cp from 'node:child_process';
36
36
  import * as crypto from 'node:crypto';
37
37
  import * as readline from 'node:readline';
38
38
  import type { Anthropic } from '../../client';
39
+ import type { BetaManagedAgentsSession } from '../../resources/beta/sessions/sessions';
39
40
  import { AnthropicError } from '../../core/error';
40
41
  import type { BetaRunnableTool } from '../../lib/tools/BetaRunnableTool';
41
42
  import { ToolError } from '../../lib/tools/ToolError';
42
43
  import { betaTool } from '../../helpers/beta/json-schema';
43
44
  import { promiseWithResolvers } from '../../internal/utils/promise';
44
- import { atomicWriteFile, confineToRoot, DIR_CREATE_MODE, fsErrorMessage } from './fs-util';
45
+ import {
46
+ atomicWriteFile,
47
+ canonicalize,
48
+ confineToRoot,
49
+ containingRoot,
50
+ DIR_CREATE_MODE,
51
+ fsErrorMessage,
52
+ isWithin,
53
+ } from './fs-util';
45
54
 
46
55
  export { setupSkills, resolveSkillVersion, extractSkillArchive } from './skills';
56
+ export {
57
+ SessionMemoryStores,
58
+ SessionMemoryError,
59
+ DEFAULT_MEMORY_SYNC_INTERVAL_MS,
60
+ MIN_MEMORY_SYNC_INTERVAL_MS,
61
+ MEMORY_FLUSH_TIMEOUT_MS,
62
+ MARKER_PATH,
63
+ type MemoryDeleteMode,
64
+ type SessionMemoryStoresOptions,
65
+ } from './memories';
47
66
 
48
67
  const BASH_OUTPUT_LIMIT = 100 * 1024;
49
68
  const BASH_DEFAULT_TIMEOUT_MS = 120_000;
@@ -88,6 +107,20 @@ function resolveMaxBytes(configured: number | null | undefined): number | null {
88
107
  return configured === undefined ? DEFAULT_MAX_FILE_BYTES : configured;
89
108
  }
90
109
 
110
+ /**
111
+ * Throw when the deprecated {@link AgentToolContext.unrestrictedPaths} was
112
+ * passed at all. Nothing else reads that property.
113
+ */
114
+ function rejectUnrestrictedPaths(value: boolean | undefined): void {
115
+ if (value === undefined) return;
116
+ throw new AnthropicError(
117
+ 'The `unrestrictedPaths` option you passed to the agent toolset (AgentToolContext) is no longer ' +
118
+ "supported. The toolset's file tools (read, write, edit, glob, grep) are now always confined to " +
119
+ 'the working directory plus the directories listed in `allowedRoots`. Remove `unrestrictedPaths` ' +
120
+ 'from your context; to let the file tools reach any other directory, add it to `allowedRoots`.',
121
+ );
122
+ }
123
+
91
124
  /**
92
125
  * Workdir + path-policy for the agent toolset.
93
126
  *
@@ -95,12 +128,12 @@ function resolveMaxBytes(configured: number | null | undefined): number | null {
95
128
  *
96
129
  * - The file tools ({@link betaReadTool}, {@link betaWriteTool},
97
130
  * {@link betaEditTool}, {@link betaGlobTool}, {@link betaGrepTool}) confine to
98
- * `workdir` unless `unrestrictedPaths` is set. {@link resolvePath}
99
- * canonicalizes the target (resolving every symlink, including the leaf)
100
- * before the check *and* returns that canonical path for the operation, so a
101
- * symlink inside the workdir that points outside it neither passes the check
102
- * nor gets followed afterwards — this is a real boundary, not a lexical hint
103
- * (modulo the residual TOCTOU noted on {@link resolvePath}).
131
+ * `workdir` plus any `allowedRoots`. {@link resolvePath} canonicalizes the
132
+ * target (resolving every symlink, including the leaf) before the check
133
+ * *and* returns that canonical path for the operation, so a symlink inside
134
+ * the workdir that points outside it neither passes the check nor gets
135
+ * followed afterwards — this is a real boundary, not a lexical hint (modulo
136
+ * the residual TOCTOU noted on {@link resolvePath}).
104
137
  * - {@link betaBashTool} runs an unrestricted `/bin/bash` and cannot be
105
138
  * confined. Run it — and, for defense in depth, the whole toolset — inside a
106
139
  * sandbox the host controls (e.g. a self-hosted environment runner).
@@ -109,19 +142,48 @@ export interface AgentToolContext {
109
142
  /** Base directory for resolving relative tool paths. */
110
143
  workdir: string;
111
144
  /**
112
- * When `false` (default), the file tools reject paths that resolve outside
113
- * `workdir` (symlinks resolved). Does **not** constrain {@link betaBashTool}.
145
+ * @deprecated No longer accepted: the file tools are always confined to
146
+ * `workdir` plus `allowedRoots`, which is neither behavior this flag used to
147
+ * select, so passing either value throws. Remove it; list extra directories
148
+ * in {@link AgentToolContext.allowedRoots}. The property is removed in a
149
+ * future release.
114
150
  */
115
151
  unrestrictedPaths?: boolean;
152
+ /**
153
+ * Absolute directories outside `workdir` the file tools may also reach,
154
+ * resolved at check time. The environment worker sets this to the session's
155
+ * memory-store folders. Does **not** constrain {@link betaBashTool}.
156
+ */
157
+ allowedRoots?: string[];
116
158
  /**
117
159
  * Anthropic client. Optional — the bare toolset needs no client; it is only
118
- * used by `setupSkills`, which (together with {@link AgentToolContext.sessionId})
119
- * fetches the session's resolved agent and downloads each of its skills into
120
- * `{workdir}/skills/<name>/`.
160
+ * used by `setupSkills`, which (together with {@link AgentToolContext.session},
161
+ * or the deprecated {@link AgentToolContext.sessionId}) downloads each of the
162
+ * session agent's skills into `{workdir}/skills/<name>/`.
121
163
  */
122
164
  client?: Anthropic;
123
- /** Session whose agent's skills `setupSkills` should download. */
165
+ /**
166
+ * The already-fetched session whose agent's skills `setupSkills` downloads.
167
+ * A session's resources cannot change while it runs, so the caller fetches it
168
+ * once and shares that snapshot with the memory-store download; the two can
169
+ * then never disagree about them.
170
+ */
171
+ session?: BetaManagedAgentsSession;
172
+ /**
173
+ * @deprecated `setupSkills` fetches the session itself when given only an id,
174
+ * one extra round trip per context. Prefer {@link AgentToolContext.session}.
175
+ * Kept for callers written before `session` existed; ignored when `session`
176
+ * is set.
177
+ */
124
178
  sessionId?: string;
179
+ /**
180
+ * Directories the write and edit tools refuse to modify, resolved at check
181
+ * time exactly like `allowedRoots`. The worker sets this to the roots of
182
+ * read-only memory stores so the agent sees the error at write time instead
183
+ * of the change silently never syncing; the mechanism is generic to any
184
+ * directory.
185
+ */
186
+ readOnlyRoots?: string[];
125
187
  /**
126
188
  * Optional environment for the bash subprocess. When unset, the bash tool
127
189
  * inherits the process environment with the runner's `ANTHROPIC_*`
@@ -174,13 +236,14 @@ export function betaAgentToolset20260401(ctx: AgentToolContext): BetaRunnableToo
174
236
  }
175
237
 
176
238
  /**
177
- * Resolve `p` against `ctx.workdir`. Absolute and relative inputs go through
178
- * the same canonicalise-then-contain check an absolute path that lands inside
179
- * the workdir is permitted, only paths that resolve *outside* are rejected.
180
- * Every symlink in `p` (including the leaf, even a dangling one) is resolved
181
- * before the workdir check, and the resolved path is what the tool then operates
182
- * on, so a symlink inside the workdir that points outside it can neither pass
183
- * the check nor be followed afterwards. See the trust model on
239
+ * Resolve `p` against `ctx.workdir`; reject results outside `ctx.workdir` and
240
+ * `ctx.allowedRoots`. Absolute and relative inputs go through the same
241
+ * canonicalise-then-contain check an absolute path that lands inside a
242
+ * permitted root is accepted, only paths that resolve *outside* all of them
243
+ * are rejected. Every symlink in `p` (including the leaf, even a dangling one)
244
+ * is resolved before the check, and the resolved path is what the tool then
245
+ * operates on, so a symlink inside the workdir that points outside it can
246
+ * neither pass the check nor be followed afterwards. See the trust model on
184
247
  * {@link AgentToolContext}.
185
248
  *
186
249
  * Residual TOCTOU: a component could still be swapped for a symlink between this
@@ -189,8 +252,19 @@ export function betaAgentToolset20260401(ctx: AgentToolContext): BetaRunnableToo
189
252
  * residual exposure exists in `tools/memory/node` and is why a sandbox is still
190
253
  * recommended for the toolset as a whole.
191
254
  */
192
- export function resolvePath(ctx: AgentToolContext, p: string): Promise<string> {
193
- return confineToRoot(ctx.workdir, p, { allowOutside: ctx.unrestrictedPaths ?? false });
255
+ export async function resolvePath(ctx: AgentToolContext, p: string): Promise<string> {
256
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
257
+ return confineToRoot(ctx.workdir, p, { allowedRoots: ctx.allowedRoots ?? [] });
258
+ }
259
+
260
+ /**
261
+ * The read-only root `target` falls under, or `undefined`. `target` arrives
262
+ * fully canonicalized (from {@link resolvePath}), so each root is
263
+ * canonicalized too — a root recorded through a symlinked workdir must still
264
+ * match the resolved write target.
265
+ */
266
+ function readOnlyRootFor(ctx: AgentToolContext, target: string): Promise<string | undefined> {
267
+ return containingRoot(ctx.readOnlyRoots ?? [], target);
194
268
  }
195
269
 
196
270
  // ---- bash ----------------------------------------------------------------
@@ -363,6 +437,7 @@ export class BashSession {
363
437
  }
364
438
 
365
439
  export function betaBashTool(ctx: AgentToolContext): BetaRunnableTool {
440
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
366
441
  let session: BashSession | undefined;
367
442
  // Concurrent run() callers chain onto this promise so writes to the shared
368
443
  // shell's stdin can't interleave (which would corrupt the sentinel-match
@@ -431,6 +506,7 @@ export function betaBashTool(ctx: AgentToolContext): BetaRunnableTool {
431
506
  // ---- fs ------------------------------------------------------------------
432
507
 
433
508
  export function betaReadTool(ctx: AgentToolContext): BetaRunnableTool {
509
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
434
510
  return betaTool({
435
511
  name: 'read',
436
512
  description: 'Read a UTF-8 text file relative to the workdir.',
@@ -482,6 +558,7 @@ export function betaReadTool(ctx: AgentToolContext): BetaRunnableTool {
482
558
  }
483
559
 
484
560
  export function betaWriteTool(ctx: AgentToolContext): BetaRunnableTool {
561
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
485
562
  return betaTool({
486
563
  name: 'write',
487
564
  description: 'Write a UTF-8 text file relative to the workdir, creating parent directories as needed.',
@@ -493,6 +570,10 @@ export function betaWriteTool(ctx: AgentToolContext): BetaRunnableTool {
493
570
  run: async ({ file_path, content }) => {
494
571
  if (!file_path) throw new ToolError('write: file_path is required');
495
572
  const abs = await resolvePath(ctx, file_path);
573
+ const ro = await readOnlyRootFor(ctx, abs);
574
+ if (ro !== undefined) {
575
+ throw new ToolError(`write: ${file_path} is inside read-only directory ${ro}`);
576
+ }
496
577
  try {
497
578
  await fs.mkdir(path.dirname(abs), { recursive: true, mode: DIR_CREATE_MODE });
498
579
  await atomicWriteFile(abs, content ?? '');
@@ -505,6 +586,7 @@ export function betaWriteTool(ctx: AgentToolContext): BetaRunnableTool {
505
586
  }
506
587
 
507
588
  export function betaEditTool(ctx: AgentToolContext): BetaRunnableTool {
589
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
508
590
  return betaTool({
509
591
  name: 'edit',
510
592
  description:
@@ -523,6 +605,10 @@ export function betaEditTool(ctx: AgentToolContext): BetaRunnableTool {
523
605
  if (!file_path) throw new ToolError('edit: file_path is required');
524
606
  if (!old_string) throw new ToolError('edit: old_string is required');
525
607
  const abs = await resolvePath(ctx, file_path);
608
+ const ro = await readOnlyRootFor(ctx, abs);
609
+ if (ro !== undefined) {
610
+ throw new ToolError(`edit: ${file_path} is inside read-only directory ${ro}`);
611
+ }
526
612
  let data: string;
527
613
  try {
528
614
  // stat() before any open() — same guard as `read`: the size cap stops a
@@ -570,7 +656,17 @@ export function betaEditTool(ctx: AgentToolContext): BetaRunnableTool {
570
656
 
571
657
  // ---- search --------------------------------------------------------------
572
658
 
659
+ /**
660
+ * Best-effort: stops `fs.glob` from walking out of the root via a literal or
661
+ * brace-expanded `..`. The realpath post-filter in {@link betaGlobTool} is the
662
+ * boundary; this only avoids the walk.
663
+ */
664
+ function patternCanAscend(pattern: string): boolean {
665
+ return pattern.split(/[\\/{},]/).includes('..');
666
+ }
667
+
573
668
  export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
669
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
574
670
  return betaTool({
575
671
  name: 'glob',
576
672
  description:
@@ -585,34 +681,31 @@ export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
585
681
  },
586
682
  run: async ({ pattern, path: searchPath }) => {
587
683
  if (!pattern) throw new ToolError('glob: pattern is required');
588
- let root = path.resolve(ctx.workdir);
589
- let pat = pattern;
590
684
  if (path.isAbsolute(pattern)) {
591
- if (!ctx.unrestrictedPaths) throw new ToolError('glob: absolute pattern not permitted');
592
- root = path.parse(pattern).root;
593
- pat = path.relative(root, pattern);
594
- } else if (searchPath) {
595
- root = await resolvePath(ctx, searchPath);
685
+ throw new ToolError(
686
+ 'glob: absolute pattern not permitted; pass a relative pattern (and optionally path)',
687
+ );
596
688
  }
597
- // A `..` in the *pattern itself* (e.g. `../../*`) walks `fs.glob` out of
598
- // the search root — this is separate from the `searchPath` confinement
599
- // above, which only covers the path argument. Reject it outright when the
600
- // toolset is confined.
601
- if (!ctx.unrestrictedPaths && pat.split(/[\\/]/).includes('..')) {
689
+ if (patternCanAscend(pattern)) {
602
690
  throw new ToolError('glob: ".." is not permitted in the pattern');
603
691
  }
692
+ const root = searchPath ? await resolvePath(ctx, searchPath) : path.resolve(ctx.workdir);
604
693
  // Compare canonical against canonical: a workdir that is itself a
605
694
  // symlink would otherwise falsely reject every realpath'd match below.
606
- const realRoot = ctx.unrestrictedPaths ? root : await fs.realpath(root).catch(() => root);
695
+ const realRoot = searchPath ? root : await canonicalize(root);
607
696
  const matches: { path: string; mtime: number }[] = [];
697
+ // Bounds the walk for patterns that match, or brace-expand into,
698
+ // enormous trees.
699
+ let remaining = WALK_MAX_ENTRIES;
608
700
  try {
609
701
  // Native `fs.glob` (Node 22+). `exclude` prunes the noisy dirs the
610
702
  // legacy walker skipped; only regular files are collected.
611
- for await (const entry of fsGlob(pat, {
703
+ for await (const entry of fsGlob(pattern, {
612
704
  cwd: root,
613
705
  withFileTypes: true,
614
706
  exclude: (d) => d.name === '.git' || d.name === 'node_modules',
615
707
  })) {
708
+ if (remaining-- <= 0) break;
616
709
  if (!entry.isFile()) continue;
617
710
  const full = path.join(entry.parentPath, entry.name);
618
711
  // Drop any match that resolves outside the search root. A pattern
@@ -622,15 +715,13 @@ export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
622
715
  // the raw parent path, so a lexical check on `full` alone would
623
716
  // pass `root/link_out/secret` even though it lives outside the
624
717
  // jail. Resolve-failure (ELOOP, EACCES, a racing unlink) is a deny.
625
- if (!ctx.unrestrictedPaths) {
626
- let real: string;
627
- try {
628
- real = await fs.realpath(full);
629
- } catch {
630
- continue;
631
- }
632
- if (!isWithin(realRoot, real)) continue;
718
+ let real: string;
719
+ try {
720
+ real = await fs.realpath(full);
721
+ } catch {
722
+ continue;
633
723
  }
724
+ if (!isWithin(realRoot, real)) continue;
634
725
  let mtime = 0;
635
726
  try {
636
727
  mtime = (await fs.stat(full)).mtimeMs;
@@ -653,6 +744,7 @@ export function betaGlobTool(ctx: AgentToolContext): BetaRunnableTool {
653
744
  }
654
745
 
655
746
  export function betaGrepTool(ctx: AgentToolContext): BetaRunnableTool {
747
+ rejectUnrestrictedPaths(ctx.unrestrictedPaths);
656
748
  return betaTool({
657
749
  name: 'grep',
658
750
  description: 'Search file contents for a regex. Uses ripgrep if available, otherwise a built-in walker.',
@@ -765,12 +857,6 @@ async function grepFile(file: string, re: RegExp, push: (line: string) => boolea
765
857
 
766
858
  // ---- utils ---------------------------------------------------------------
767
859
 
768
- /** True when `p` is `root` itself or lexically contained within it. */
769
- function isWithin(root: string, p: string): boolean {
770
- const rel = path.relative(root, p);
771
- return rel === '' || (!rel.startsWith('..' + path.sep) && rel !== '..' && !path.isAbsolute(rel));
772
- }
773
-
774
860
  const WALK_MAX_DEPTH = 40;
775
861
  const WALK_MAX_ENTRIES = 50_000;
776
862
 
@@ -23,13 +23,22 @@ const execFileAsync = promisify(execFile);
23
23
  /**
24
24
  * Download the session agent's skills into `{ctx.workdir}/skills/<name>/`.
25
25
  *
26
- * No-op (returns a no-op cleanup) unless both `ctx.client` and `ctx.sessionId`
27
- * are set. Looks up the session's resolved agent and, for each skill, fetches
28
- * its files via `client.beta.skills.versions.download` and extracts the archive
29
- * (a zip or tar.* archive) into a directory named after the skill. A failure on
30
- * one skill is logged and does not block the others. Call this before starting
31
- * the session tool runner (e.g. right after the bash session / workdir is
32
- * ready).
26
+ * No-op (returns a no-op cleanup) unless `ctx.client` is set together with
27
+ * `ctx.session` (or the deprecated `ctx.sessionId`). Reads the resolved agent
28
+ * off the session and, for each skill, fetches its files via
29
+ * `client.beta.skills.versions.download` and extracts the archive (a zip or
30
+ * tar.* archive) into a directory named after the skill. A failure on one skill
31
+ * is logged and does not block the others. Call this before starting the
32
+ * session tool runner (e.g. right after the bash session / workdir is ready).
33
+ *
34
+ * Pass `ctx.session`. A session's resources cannot change while it runs, so the
35
+ * caller fetches it once and shares that snapshot with the memory-store
36
+ * download — the two can then never disagree about the attached resources.
37
+ *
38
+ * `ctx.sessionId` is deprecated: it costs an extra `sessions.retrieve` round
39
+ * trip on every call, and a caller that uses it for both this and the
40
+ * memory-store download fetches the session twice. It remains supported for
41
+ * callers written before `session` existed.
33
42
  *
34
43
  * Returns a cleanup function that removes the skill directories this call
35
44
  * created — call it once the work item is done so downloaded skills do not
@@ -37,9 +46,20 @@ const execFileAsync = promisify(execFile);
37
46
  */
38
47
  export async function setupSkills(ctx: AgentToolContext): Promise<() => Promise<void>> {
39
48
  const { client, sessionId } = ctx;
40
- if (!client || !sessionId) return async () => {};
49
+ if (!client) return async () => {};
41
50
  const log = loggerFor(client);
42
- const session = await client.beta.sessions.retrieve(sessionId);
51
+ let session = ctx.session;
52
+ if (!session) {
53
+ if (sessionId === undefined) return async () => {};
54
+ log.warn(
55
+ 'AgentToolContext.sessionId is deprecated and costs an extra session fetch; ' +
56
+ 'fetch the session once and set `session` instead',
57
+ { component: 'agent-tool-context' },
58
+ );
59
+ // The sessions/skills resources inject their anthropic-beta headers
60
+ // (managed-agents / skills) themselves — no need to pass `betas` here.
61
+ session = await client.beta.sessions.retrieve(sessionId);
62
+ }
43
63
  const skillsRoot = path.resolve(ctx.workdir, 'skills');
44
64
  const created: string[] = [];
45
65
  for (const skill of session.agent.skills) {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The memory sync cadence, split out of `memories.ts` so runtime-agnostic
3
+ * callers (the environment worker) can validate an interval up front without
4
+ * reaching into the Node-only toolset module.
5
+ */
6
+
7
+ import { AnthropicError } from '../../core/error';
8
+
9
+ /**
10
+ * How often (milliseconds) the worker syncs the session's memory stores back
11
+ * while the session runs. Checked after each dispatched tool call.
12
+ */
13
+ export const DEFAULT_MEMORY_SYNC_INTERVAL_MS = 15_000;
14
+
15
+ /**
16
+ * The shortest sync interval accepted. Each sync lists every attached store,
17
+ * so anything tighter mostly spends requests rediscovering that nothing
18
+ * changed.
19
+ */
20
+ export const MIN_MEMORY_SYNC_INTERVAL_MS = 5_000;
21
+
22
+ /** Throw unless `ms` is a usable sync interval. `option` names it in the message. */
23
+ export function checkMemorySyncInterval(ms: number, option: string): void {
24
+ if (!(ms >= MIN_MEMORY_SYNC_INTERVAL_MS)) {
25
+ throw new AnthropicError(
26
+ `${option} must be at least ${MIN_MEMORY_SYNC_INTERVAL_MS}ms (got ${ms}); ` +
27
+ 'to run without memory sync, pass `memorySyncIntervalMs: null` to the worker instead',
28
+ );
29
+ }
30
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.119.0'; // x-release-please-version
1
+ export const VERSION = '0.120.0'; // x-release-please-version
@@ -8,6 +8,17 @@
8
8
  export declare const DIR_CREATE_MODE = 493;
9
9
  /** Mode for files the file tools create. */
10
10
  export declare const FILE_CREATE_MODE = 420;
11
+ /** True when `p` is `root` itself or lexically contained within it. */
12
+ export declare function isWithin(root: string, p: string): boolean;
13
+ /**
14
+ * The first entry of `roots` whose canonical form contains the
15
+ * already-canonical `target`, returned as configured; `undefined` when none
16
+ * does. Each root goes through {@link canonicalize} at check time, exactly like
17
+ * the workdir in {@link confineToRoot}, so granting access (`allowedRoots`)
18
+ * and refusing writes (`readOnlyRoots`) can never resolve the same entry two
19
+ * different ways.
20
+ */
21
+ export declare function containingRoot(roots: readonly string[], target: string): Promise<string | undefined>;
11
22
  /** The `code` of a Node system error, or `undefined` for anything else. */
12
23
  export declare function errnoCode(err: unknown): string | undefined;
13
24
  /**
@@ -25,17 +36,19 @@ export declare function errnoCode(err: unknown): string | undefined;
25
36
  */
26
37
  export declare function canonicalize(abs: string): Promise<string>;
27
38
  /**
28
- * Resolve `p` and confine it to `root`.
39
+ * Resolve `p` against `root` and confine it to `root` or one of `allowedRoots`
40
+ * (absolute paths, resolved at check time exactly like `root`).
29
41
  *
30
42
  * Absolute and relative inputs go through the same canonicalise-then-contain
31
- * check — an absolute path that lands inside `root` is permitted, only paths
32
- * that resolve *outside* are rejected. Every symlink in `p` (including the
33
- * leaf, even a dangling one) is resolved before the confinement check, and the
34
- * resolved path is what the caller then operates on, so a symlink inside `root`
35
- * that points outside it can neither pass the check nor be followed afterwards.
36
- * `..` is collapsed lexically before any symlink is followed. A path that cannot
37
- * be resolved (symlink loop, unreadable component) is rejected with a
38
- * `ToolError` naming `p`, never the host's absolute path.
43
+ * check — an absolute path that lands inside a permitted root is accepted,
44
+ * only paths that resolve *outside* all of them are rejected. Every symlink in
45
+ * `p` (including the leaf, even a dangling one) is resolved before the
46
+ * confinement check, and the resolved path is what the caller then operates
47
+ * on, so a symlink inside `root` that points outside it can neither pass the
48
+ * check nor be followed afterwards. `..` is collapsed lexically before any
49
+ * symlink is followed. A path that cannot be resolved (symlink loop, unreadable
50
+ * component) is rejected with a `ToolError` naming `p`, never the host's
51
+ * absolute path.
39
52
  *
40
53
  * Residual TOCTOU: a component could still be swapped for a symlink between this
41
54
  * call and the eventual `fs` operation. Closing that fully needs per-component
@@ -43,7 +56,7 @@ export declare function canonicalize(abs: string): Promise<string>;
43
56
  * sandbox is still recommended for the toolset as a whole.
44
57
  */
45
58
  export declare function confineToRoot(root: string, p: string, opts?: {
46
- allowOutside?: boolean;
59
+ allowedRoots?: readonly string[];
47
60
  }): Promise<string>;
48
61
  /**
49
62
  * Atomically write `content` to `targetPath`: write a sibling temp file, fsync
@@ -1 +1 @@
1
- {"version":3,"file":"fs-util.d.mts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AActC,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBjE"}
1
+ {"version":3,"file":"fs-util.d.mts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AAEtC,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK1G;AAKD,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC1C,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBjE"}
@@ -8,6 +8,17 @@
8
8
  export declare const DIR_CREATE_MODE = 493;
9
9
  /** Mode for files the file tools create. */
10
10
  export declare const FILE_CREATE_MODE = 420;
11
+ /** True when `p` is `root` itself or lexically contained within it. */
12
+ export declare function isWithin(root: string, p: string): boolean;
13
+ /**
14
+ * The first entry of `roots` whose canonical form contains the
15
+ * already-canonical `target`, returned as configured; `undefined` when none
16
+ * does. Each root goes through {@link canonicalize} at check time, exactly like
17
+ * the workdir in {@link confineToRoot}, so granting access (`allowedRoots`)
18
+ * and refusing writes (`readOnlyRoots`) can never resolve the same entry two
19
+ * different ways.
20
+ */
21
+ export declare function containingRoot(roots: readonly string[], target: string): Promise<string | undefined>;
11
22
  /** The `code` of a Node system error, or `undefined` for anything else. */
12
23
  export declare function errnoCode(err: unknown): string | undefined;
13
24
  /**
@@ -25,17 +36,19 @@ export declare function errnoCode(err: unknown): string | undefined;
25
36
  */
26
37
  export declare function canonicalize(abs: string): Promise<string>;
27
38
  /**
28
- * Resolve `p` and confine it to `root`.
39
+ * Resolve `p` against `root` and confine it to `root` or one of `allowedRoots`
40
+ * (absolute paths, resolved at check time exactly like `root`).
29
41
  *
30
42
  * Absolute and relative inputs go through the same canonicalise-then-contain
31
- * check — an absolute path that lands inside `root` is permitted, only paths
32
- * that resolve *outside* are rejected. Every symlink in `p` (including the
33
- * leaf, even a dangling one) is resolved before the confinement check, and the
34
- * resolved path is what the caller then operates on, so a symlink inside `root`
35
- * that points outside it can neither pass the check nor be followed afterwards.
36
- * `..` is collapsed lexically before any symlink is followed. A path that cannot
37
- * be resolved (symlink loop, unreadable component) is rejected with a
38
- * `ToolError` naming `p`, never the host's absolute path.
43
+ * check — an absolute path that lands inside a permitted root is accepted,
44
+ * only paths that resolve *outside* all of them are rejected. Every symlink in
45
+ * `p` (including the leaf, even a dangling one) is resolved before the
46
+ * confinement check, and the resolved path is what the caller then operates
47
+ * on, so a symlink inside `root` that points outside it can neither pass the
48
+ * check nor be followed afterwards. `..` is collapsed lexically before any
49
+ * symlink is followed. A path that cannot be resolved (symlink loop, unreadable
50
+ * component) is rejected with a `ToolError` naming `p`, never the host's
51
+ * absolute path.
39
52
  *
40
53
  * Residual TOCTOU: a component could still be swapped for a symlink between this
41
54
  * call and the eventual `fs` operation. Closing that fully needs per-component
@@ -43,7 +56,7 @@ export declare function canonicalize(abs: string): Promise<string>;
43
56
  * sandbox is still recommended for the toolset as a whole.
44
57
  */
45
58
  export declare function confineToRoot(root: string, p: string, opts?: {
46
- allowOutside?: boolean;
59
+ allowedRoots?: readonly string[];
47
60
  }): Promise<string>;
48
61
  /**
49
62
  * Atomically write `content` to `targetPath`: write a sibling temp file, fsync
@@ -1 +1 @@
1
- {"version":3,"file":"fs-util.d.ts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AActC,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBjE"}
1
+ {"version":3,"file":"fs-util.d.ts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AAEtC,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAK1G;AAKD,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC1C,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBjE"}