@juspay/neurolink 13.8.17 → 13.8.18
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/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/action/actionExecutor.d.ts +33 -3
- package/dist/action/actionExecutor.js +71 -15
- package/dist/action/actionInputs.js +4 -1
- package/dist/action/cliVersion.d.ts +25 -0
- package/dist/action/cliVersion.js +26 -0
- package/dist/action/index.d.ts +2 -1
- package/dist/action/index.js +2 -1
- package/dist/adapters/video/directorPipeline.js +46 -36
- package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
- package/dist/adapters/video/ffmpegAdapter.js +32 -5
- package/dist/adapters/video/frameExtractor.js +1 -1
- package/dist/adapters/video/videoMerger.js +5 -2
- package/dist/agent/arithmeticEvaluator.d.ts +32 -0
- package/dist/agent/arithmeticEvaluator.js +349 -0
- package/dist/agent/directTools.d.ts +6 -18
- package/dist/agent/directTools.js +17 -56
- package/dist/artifacts/artifactStore.d.ts +22 -1
- package/dist/artifacts/artifactStore.js +66 -6
- package/dist/auth/anthropicOAuth.d.ts +26 -1
- package/dist/auth/anthropicOAuth.js +81 -5
- package/dist/auth/secureFiles.d.ts +37 -0
- package/dist/auth/secureFiles.js +95 -0
- package/dist/auth/tokenStore.d.ts +43 -6
- package/dist/auth/tokenStore.js +260 -79
- package/dist/avatar/providers/DIDAvatar.js +49 -45
- package/dist/avatar/providers/ReplicateAvatar.js +33 -40
- package/dist/browser/neurolink.min.js +485 -558
- package/dist/cli/commands/auth.js +112 -70
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +128 -13
- package/dist/cli/commands/serve.d.ts +10 -1
- package/dist/cli/commands/serve.js +98 -17
- package/dist/cli/commands/server.js +17 -5
- package/dist/cli/index.js +146 -5
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/utils/envManager.js +17 -3
- package/dist/cli/utils/serverUtils.d.ts +39 -3
- package/dist/cli/utils/serverUtils.js +63 -6
- package/dist/core/baseProvider.js +7 -1
- package/dist/core/modules/MessageBuilder.js +5 -0
- package/dist/core/modules/ToolsManager.d.ts +46 -1
- package/dist/core/modules/ToolsManager.js +190 -43
- package/dist/files/fileReferenceRegistry.d.ts +2 -23
- package/dist/files/fileReferenceRegistry.js +55 -5
- package/dist/hitl/hitlManager.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/mcp/auth/tokenStorage.d.ts +7 -0
- package/dist/mcp/auth/tokenStorage.js +23 -4
- package/dist/mcp/externalServerManager.d.ts +13 -3
- package/dist/mcp/externalServerManager.js +154 -136
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/mcpClientFactory.d.ts +14 -0
- package/dist/mcp/mcpClientFactory.js +70 -7
- package/dist/mcp/projectConfigTrust.d.ts +72 -0
- package/dist/mcp/projectConfigTrust.js +237 -0
- package/dist/mcp/transportUrlPolicy.d.ts +39 -0
- package/dist/mcp/transportUrlPolicy.js +172 -0
- package/dist/music/providers/BeatovenMusic.js +35 -46
- package/dist/music/providers/ReplicateMusic.js +33 -40
- package/dist/neurolink.d.ts +16 -1
- package/dist/neurolink.js +64 -11
- package/dist/processors/archive/ArchiveProcessor.js +15 -1
- package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
- package/dist/processors/archive/zipEntryIndex.js +33 -0
- package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
- package/dist/processors/base/BaseFileProcessor.js +40 -111
- package/dist/processors/data/YamlProcessor.d.ts +13 -1
- package/dist/processors/data/YamlProcessor.js +93 -2
- package/dist/processors/document/OpenDocumentProcessor.js +7 -2
- package/dist/processors/document/PptxProcessor.js +12 -7
- package/dist/processors/media/VideoProcessor.js +93 -15
- package/dist/proxy/inboundAuth.d.ts +89 -0
- package/dist/proxy/inboundAuth.js +219 -0
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +13 -1
- package/dist/proxy/updateState.js +4 -4
- package/dist/rag/document/loaders.d.ts +0 -6
- package/dist/rag/document/loaders.js +16 -10
- package/dist/rag/ragIntegration.d.ts +2 -2
- package/dist/rag/ragIntegration.js +35 -10
- package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
- package/dist/rag/retrieval/vectorQueryTool.js +54 -17
- package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
- package/dist/server/abstract/baseServerAdapter.js +119 -6
- package/dist/server/adapters/expressAdapter.d.ts +3 -0
- package/dist/server/adapters/expressAdapter.js +78 -6
- package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
- package/dist/server/adapters/fastifyAdapter.js +165 -70
- package/dist/server/adapters/honoAdapter.d.ts +9 -1
- package/dist/server/adapters/honoAdapter.js +99 -21
- package/dist/server/adapters/koaAdapter.d.ts +1 -0
- package/dist/server/adapters/koaAdapter.js +153 -74
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/middleware/auth.js +6 -2
- package/dist/server/middleware/cache.js +6 -1
- package/dist/server/middleware/rateLimit.js +29 -4
- package/dist/server/routes/agentRoutes.js +24 -11
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/server/routes/claudeProxyRoutes.js +273 -84
- package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
- package/dist/server/routes/codexProxyRoutes.js +125 -40
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +35 -4
- package/dist/server/routes/mcpRoutes.d.ts +6 -2
- package/dist/server/routes/mcpRoutes.js +21 -7
- package/dist/server/routes/memoryRoutes.js +7 -6
- package/dist/server/routes/toolRoutes.d.ts +12 -2
- package/dist/server/routes/toolRoutes.js +33 -3
- package/dist/server/routes/transcribeRoutes.js +60 -13
- package/dist/server/utils/clientAddress.d.ts +23 -0
- package/dist/server/utils/clientAddress.js +65 -0
- package/dist/server/utils/clientError.d.ts +35 -0
- package/dist/server/utils/clientError.js +129 -0
- package/dist/server/utils/pathMatch.d.ts +21 -0
- package/dist/server/utils/pathMatch.js +41 -0
- package/dist/server/utils/validation.d.ts +9 -0
- package/dist/server/utils/validation.js +9 -0
- package/dist/server/websocket/WebSocketHandler.js +22 -3
- package/dist/server/websocket/frameQueue.d.ts +19 -0
- package/dist/server/websocket/frameQueue.js +41 -3
- package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
- package/dist/server/websocket/transcribeWebSocket.js +223 -34
- package/dist/skills/skillStores.js +5 -0
- package/dist/tasks/store/fileTaskStore.d.ts +2 -0
- package/dist/tasks/store/fileTaskStore.js +11 -4
- package/dist/types/attachments.d.ts +117 -0
- package/dist/types/attachments.js +15 -0
- package/dist/types/auth.d.ts +6 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/file.d.ts +8 -0
- package/dist/types/hitl.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcp.d.ts +98 -0
- package/dist/types/multimodal.js +0 -41
- package/dist/types/processor.d.ts +14 -1
- package/dist/types/proxy.d.ts +62 -3
- package/dist/types/rag.d.ts +9 -0
- package/dist/types/safeFetch.d.ts +40 -0
- package/dist/types/server.d.ts +206 -10
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/transcribe.d.ts +6 -0
- package/dist/utils/attachmentPolicy.d.ts +89 -0
- package/dist/utils/attachmentPolicy.js +327 -0
- package/dist/utils/dotenvBootstrap.d.ts +48 -19
- package/dist/utils/dotenvBootstrap.js +172 -31
- package/dist/utils/fileDetector.js +115 -65
- package/dist/utils/json/coerce.js +11 -1
- package/dist/utils/logSanitize.d.ts +5 -0
- package/dist/utils/logSanitize.js +8 -0
- package/dist/utils/logger.js +73 -1
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +118 -105
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +2 -0
- package/dist/utils/redis.d.ts +12 -0
- package/dist/utils/redis.js +72 -0
- package/dist/utils/safeFetch.d.ts +22 -1
- package/dist/utils/safeFetch.js +132 -38
- package/dist/utils/safeStoreId.d.ts +19 -0
- package/dist/utils/safeStoreId.js +29 -0
- package/dist/utils/schemaConversion.d.ts +30 -3
- package/dist/utils/schemaConversion.js +457 -84
- package/dist/utils/sizeGuard.js +2 -2
- package/dist/utils/ssrfGuard.d.ts +3 -3
- package/dist/utils/ssrfGuard.js +71 -39
- package/dist/voice/eval/sttEvalStore.d.ts +26 -3
- package/dist/voice/eval/sttEvalStore.js +77 -12
- package/dist/voice/livekit/agentDefinition.d.ts +18 -0
- package/dist/voice/livekit/agentDefinition.js +22 -0
- package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
- package/dist/voice/livekit/realtimeMcpTools.js +8 -7
- package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
- package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
- package/dist/voice/livekit/voiceAgent.d.ts +2 -2
- package/dist/voice/livekit/voiceAgent.js +2 -1
- package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
- package/dist/voice/livekit/xaiSessionWatch.js +30 -0
- package/dist/voice/providers/GeminiLive.js +21 -6
- package/dist/voice/providers/GoogleSTT.js +8 -5
- package/dist/voice/transcribe.d.ts +2 -2
- package/dist/voice/transcribe.js +34 -18
- package/dist/voice/whistle/audio.js +11 -5
- package/docs-site/static/search-index.json +71 -49
- package/package.json +171 -156
- package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arithmetic evaluator for the built-in `calculateMath` tool.
|
|
3
|
+
*
|
|
4
|
+
* The expression is MODEL output. It is parsed by a small recursive-descent
|
|
5
|
+
* parser and evaluated over numbers only: there is no `Function`, no `eval`,
|
|
6
|
+
* no property access and no identifier that is not on the fixed allow-list
|
|
7
|
+
* below. Anything the grammar does not cover is reported as an error.
|
|
8
|
+
*
|
|
9
|
+
* Grammar (precedence low → high):
|
|
10
|
+
*
|
|
11
|
+
* expression := term (("+" | "-") term)*
|
|
12
|
+
* term := unary (("*" | "/" | "%") unary)*
|
|
13
|
+
* unary := ("-" | "+") unary | power
|
|
14
|
+
* power := primary ("**" unary)? right-associative
|
|
15
|
+
* primary := number | constant | function "(" args ")" | "(" expression ")"
|
|
16
|
+
*
|
|
17
|
+
* Numbers are decimal literals (`12`, `3.5`, `.5`, `1e-3`). Functions and
|
|
18
|
+
* constants are the listed `Math` members, accepted with or without the
|
|
19
|
+
* `Math.` prefix. Unary minus binds looser than `**`, so `-2 ** 2` is `-4`,
|
|
20
|
+
* as in mathematics (JavaScript rejects that spelling outright).
|
|
21
|
+
*/
|
|
22
|
+
/** Longest expression accepted, in characters. */
|
|
23
|
+
const MAX_EXPRESSION_LENGTH = 1_000;
|
|
24
|
+
/** Deepest nesting of parentheses and function calls accepted. */
|
|
25
|
+
const MAX_NESTING_DEPTH = 64;
|
|
26
|
+
/** Most arguments one function call may take. */
|
|
27
|
+
const MAX_FUNCTION_ARGUMENTS = 16;
|
|
28
|
+
const MATH_FUNCTIONS = Object.freeze({
|
|
29
|
+
abs: { fn: Math.abs, arity: 1 },
|
|
30
|
+
ceil: { fn: Math.ceil, arity: 1 },
|
|
31
|
+
floor: { fn: Math.floor, arity: 1 },
|
|
32
|
+
round: { fn: Math.round, arity: 1 },
|
|
33
|
+
trunc: { fn: Math.trunc, arity: 1 },
|
|
34
|
+
sign: { fn: Math.sign, arity: 1 },
|
|
35
|
+
sqrt: { fn: Math.sqrt, arity: 1 },
|
|
36
|
+
cbrt: { fn: Math.cbrt, arity: 1 },
|
|
37
|
+
pow: { fn: Math.pow, arity: 2 },
|
|
38
|
+
exp: { fn: Math.exp, arity: 1 },
|
|
39
|
+
log: { fn: Math.log, arity: 1 },
|
|
40
|
+
log2: { fn: Math.log2, arity: 1 },
|
|
41
|
+
log10: { fn: Math.log10, arity: 1 },
|
|
42
|
+
sin: { fn: Math.sin, arity: 1 },
|
|
43
|
+
cos: { fn: Math.cos, arity: 1 },
|
|
44
|
+
tan: { fn: Math.tan, arity: 1 },
|
|
45
|
+
asin: { fn: Math.asin, arity: 1 },
|
|
46
|
+
acos: { fn: Math.acos, arity: 1 },
|
|
47
|
+
atan: { fn: Math.atan, arity: 1 },
|
|
48
|
+
atan2: { fn: Math.atan2, arity: 2 },
|
|
49
|
+
sinh: { fn: Math.sinh, arity: 1 },
|
|
50
|
+
cosh: { fn: Math.cosh, arity: 1 },
|
|
51
|
+
tanh: { fn: Math.tanh, arity: 1 },
|
|
52
|
+
hypot: { fn: Math.hypot, arity: null },
|
|
53
|
+
min: { fn: Math.min, arity: null },
|
|
54
|
+
max: { fn: Math.max, arity: null },
|
|
55
|
+
});
|
|
56
|
+
const MATH_CONSTANTS = Object.freeze({
|
|
57
|
+
PI: Math.PI,
|
|
58
|
+
E: Math.E,
|
|
59
|
+
LN2: Math.LN2,
|
|
60
|
+
LN10: Math.LN10,
|
|
61
|
+
LOG2E: Math.LOG2E,
|
|
62
|
+
LOG10E: Math.LOG10E,
|
|
63
|
+
SQRT2: Math.SQRT2,
|
|
64
|
+
SQRT1_2: Math.SQRT1_2,
|
|
65
|
+
});
|
|
66
|
+
/** The names the model may use, for the error text and the tool description. */
|
|
67
|
+
export const ARITHMETIC_FUNCTION_NAMES = Object.freeze(Object.keys(MATH_FUNCTIONS));
|
|
68
|
+
export const ARITHMETIC_CONSTANT_NAMES = Object.freeze(Object.keys(MATH_CONSTANTS));
|
|
69
|
+
class ArithmeticSyntaxError extends Error {
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = "ArithmeticSyntaxError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const NUMBER_PATTERN = /^(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/;
|
|
76
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/;
|
|
77
|
+
function tokenize(expression) {
|
|
78
|
+
const tokens = [];
|
|
79
|
+
let index = 0;
|
|
80
|
+
while (index < expression.length) {
|
|
81
|
+
const char = expression[index];
|
|
82
|
+
if (char === " " || char === "\t" || char === "\n" || char === "\r") {
|
|
83
|
+
index++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const rest = expression.slice(index);
|
|
87
|
+
const numberMatch = NUMBER_PATTERN.exec(rest);
|
|
88
|
+
if (numberMatch) {
|
|
89
|
+
const value = Number(numberMatch[0]);
|
|
90
|
+
if (!Number.isFinite(value)) {
|
|
91
|
+
throw new ArithmeticSyntaxError(`Number literal at position ${index} is out of range`);
|
|
92
|
+
}
|
|
93
|
+
tokens.push({ kind: "number", value, at: index });
|
|
94
|
+
index += numberMatch[0].length;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const identifierMatch = IDENTIFIER_PATTERN.exec(rest);
|
|
98
|
+
if (identifierMatch) {
|
|
99
|
+
tokens.push({ kind: "identifier", name: identifierMatch[0], at: index });
|
|
100
|
+
index += identifierMatch[0].length;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (rest.startsWith("**")) {
|
|
104
|
+
tokens.push({ kind: "operator", value: "**", at: index });
|
|
105
|
+
index += 2;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (char === "+" ||
|
|
109
|
+
char === "-" ||
|
|
110
|
+
char === "*" ||
|
|
111
|
+
char === "/" ||
|
|
112
|
+
char === "%") {
|
|
113
|
+
tokens.push({ kind: "operator", value: char, at: index });
|
|
114
|
+
index++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (char === "(" || char === ")") {
|
|
118
|
+
tokens.push({ kind: "paren", value: char, at: index });
|
|
119
|
+
index++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (char === ",") {
|
|
123
|
+
tokens.push({ kind: "comma", at: index });
|
|
124
|
+
index++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
throw new ArithmeticSyntaxError(`Unexpected character "${char}" at position ${index}`);
|
|
128
|
+
}
|
|
129
|
+
tokens.push({ kind: "end", at: expression.length });
|
|
130
|
+
return tokens;
|
|
131
|
+
}
|
|
132
|
+
/** `Math.sqrt` and `sqrt` name the same function; anything else is refused. */
|
|
133
|
+
function resolveName(name) {
|
|
134
|
+
return name.startsWith("Math.") ? name.slice("Math.".length) : name;
|
|
135
|
+
}
|
|
136
|
+
class Parser {
|
|
137
|
+
tokens;
|
|
138
|
+
position = 0;
|
|
139
|
+
depth = 0;
|
|
140
|
+
constructor(tokens) {
|
|
141
|
+
this.tokens = tokens;
|
|
142
|
+
}
|
|
143
|
+
parse() {
|
|
144
|
+
const value = this.parseExpression();
|
|
145
|
+
const token = this.peek();
|
|
146
|
+
if (token.kind !== "end") {
|
|
147
|
+
throw new ArithmeticSyntaxError(`Unexpected input at position ${token.at}`);
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
peek() {
|
|
152
|
+
return this.tokens[this.position];
|
|
153
|
+
}
|
|
154
|
+
next() {
|
|
155
|
+
const token = this.tokens[this.position];
|
|
156
|
+
this.position++;
|
|
157
|
+
return token;
|
|
158
|
+
}
|
|
159
|
+
isOperator(token, value) {
|
|
160
|
+
return token.kind === "operator" && token.value === value;
|
|
161
|
+
}
|
|
162
|
+
enter() {
|
|
163
|
+
this.depth++;
|
|
164
|
+
if (this.depth > MAX_NESTING_DEPTH) {
|
|
165
|
+
throw new ArithmeticSyntaxError(`Expression nests deeper than ${MAX_NESTING_DEPTH} levels`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
leave() {
|
|
169
|
+
this.depth--;
|
|
170
|
+
}
|
|
171
|
+
parseExpression() {
|
|
172
|
+
let left = this.parseTerm();
|
|
173
|
+
for (;;) {
|
|
174
|
+
const token = this.peek();
|
|
175
|
+
if (this.isOperator(token, "+")) {
|
|
176
|
+
this.next();
|
|
177
|
+
left += this.parseTerm();
|
|
178
|
+
}
|
|
179
|
+
else if (this.isOperator(token, "-")) {
|
|
180
|
+
this.next();
|
|
181
|
+
left -= this.parseTerm();
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return left;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
parseTerm() {
|
|
189
|
+
let left = this.parseUnary();
|
|
190
|
+
for (;;) {
|
|
191
|
+
const token = this.peek();
|
|
192
|
+
if (this.isOperator(token, "*")) {
|
|
193
|
+
this.next();
|
|
194
|
+
left *= this.parseUnary();
|
|
195
|
+
}
|
|
196
|
+
else if (this.isOperator(token, "/")) {
|
|
197
|
+
this.next();
|
|
198
|
+
left /= this.parseUnary();
|
|
199
|
+
}
|
|
200
|
+
else if (this.isOperator(token, "%")) {
|
|
201
|
+
this.next();
|
|
202
|
+
left %= this.parseUnary();
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
return left;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
parseUnary() {
|
|
210
|
+
const token = this.peek();
|
|
211
|
+
if (this.isOperator(token, "-")) {
|
|
212
|
+
this.next();
|
|
213
|
+
this.enter();
|
|
214
|
+
const value = -this.parseUnary();
|
|
215
|
+
this.leave();
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
if (this.isOperator(token, "+")) {
|
|
219
|
+
this.next();
|
|
220
|
+
this.enter();
|
|
221
|
+
const value = this.parseUnary();
|
|
222
|
+
this.leave();
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
return this.parsePower();
|
|
226
|
+
}
|
|
227
|
+
parsePower() {
|
|
228
|
+
const base = this.parsePrimary();
|
|
229
|
+
if (this.isOperator(this.peek(), "**")) {
|
|
230
|
+
this.next();
|
|
231
|
+
this.enter();
|
|
232
|
+
const exponent = this.parseUnary();
|
|
233
|
+
this.leave();
|
|
234
|
+
return base ** exponent;
|
|
235
|
+
}
|
|
236
|
+
return base;
|
|
237
|
+
}
|
|
238
|
+
parsePrimary() {
|
|
239
|
+
const token = this.next();
|
|
240
|
+
if (token.kind === "number") {
|
|
241
|
+
return token.value;
|
|
242
|
+
}
|
|
243
|
+
if (token.kind === "paren" && token.value === "(") {
|
|
244
|
+
this.enter();
|
|
245
|
+
const value = this.parseExpression();
|
|
246
|
+
this.leave();
|
|
247
|
+
this.expectClosingParen();
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
if (token.kind === "identifier") {
|
|
251
|
+
return this.parseIdentifier(token.name, token.at);
|
|
252
|
+
}
|
|
253
|
+
if (token.kind === "end") {
|
|
254
|
+
throw new ArithmeticSyntaxError("Expression ended unexpectedly");
|
|
255
|
+
}
|
|
256
|
+
throw new ArithmeticSyntaxError(`Unexpected token at position ${token.at}`);
|
|
257
|
+
}
|
|
258
|
+
expectClosingParen() {
|
|
259
|
+
const token = this.next();
|
|
260
|
+
if (token.kind !== "paren" || token.value !== ")") {
|
|
261
|
+
throw new ArithmeticSyntaxError(`Expected ")" at position ${token.at}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
parseIdentifier(name, at) {
|
|
265
|
+
const resolved = resolveName(name);
|
|
266
|
+
const following = this.peek();
|
|
267
|
+
const isCall = following.kind === "paren" && following.value === "(";
|
|
268
|
+
if (isCall) {
|
|
269
|
+
const definition = Object.prototype.hasOwnProperty.call(MATH_FUNCTIONS, resolved)
|
|
270
|
+
? MATH_FUNCTIONS[resolved]
|
|
271
|
+
: undefined;
|
|
272
|
+
if (!definition) {
|
|
273
|
+
throw new ArithmeticSyntaxError(`Unknown function "${name}" at position ${at}. Allowed: ${ARITHMETIC_FUNCTION_NAMES.join(", ")}`);
|
|
274
|
+
}
|
|
275
|
+
this.next();
|
|
276
|
+
this.enter();
|
|
277
|
+
const args = this.parseArguments();
|
|
278
|
+
this.leave();
|
|
279
|
+
if (definition.arity !== null && args.length !== definition.arity) {
|
|
280
|
+
throw new ArithmeticSyntaxError(`"${name}" takes ${definition.arity} argument(s), got ${args.length}`);
|
|
281
|
+
}
|
|
282
|
+
return definition.fn(...args);
|
|
283
|
+
}
|
|
284
|
+
if (Object.prototype.hasOwnProperty.call(MATH_CONSTANTS, resolved)) {
|
|
285
|
+
return MATH_CONSTANTS[resolved];
|
|
286
|
+
}
|
|
287
|
+
throw new ArithmeticSyntaxError(`Unknown identifier "${name}" at position ${at}. Allowed constants: ${ARITHMETIC_CONSTANT_NAMES.join(", ")}`);
|
|
288
|
+
}
|
|
289
|
+
/** Called after the opening parenthesis has been consumed. */
|
|
290
|
+
parseArguments() {
|
|
291
|
+
const args = [];
|
|
292
|
+
if (this.peek().kind === "paren") {
|
|
293
|
+
this.expectClosingParen();
|
|
294
|
+
return args;
|
|
295
|
+
}
|
|
296
|
+
for (;;) {
|
|
297
|
+
args.push(this.parseExpression());
|
|
298
|
+
if (args.length > MAX_FUNCTION_ARGUMENTS) {
|
|
299
|
+
throw new ArithmeticSyntaxError(`A function call may take at most ${MAX_FUNCTION_ARGUMENTS} arguments`);
|
|
300
|
+
}
|
|
301
|
+
const token = this.next();
|
|
302
|
+
if (token.kind === "comma") {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (token.kind === "paren" && token.value === ")") {
|
|
306
|
+
return args;
|
|
307
|
+
}
|
|
308
|
+
throw new ArithmeticSyntaxError(`Expected "," or ")" at position ${token.at}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Evaluate an arithmetic expression without executing it as code.
|
|
314
|
+
*
|
|
315
|
+
* Total: every failure — unsupported syntax, an unknown name, a result that is
|
|
316
|
+
* not a finite number — comes back as `{ ok: false, error }`, never a throw.
|
|
317
|
+
*/
|
|
318
|
+
export function evaluateArithmetic(expression) {
|
|
319
|
+
if (typeof expression !== "string") {
|
|
320
|
+
return { ok: false, error: "Expression must be a string" };
|
|
321
|
+
}
|
|
322
|
+
if (expression.trim().length === 0) {
|
|
323
|
+
return { ok: false, error: "Expression is empty" };
|
|
324
|
+
}
|
|
325
|
+
if (expression.length > MAX_EXPRESSION_LENGTH) {
|
|
326
|
+
return {
|
|
327
|
+
ok: false,
|
|
328
|
+
error: `Expression is ${expression.length} characters long; the limit is ${MAX_EXPRESSION_LENGTH}`,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const value = new Parser(tokenize(expression)).parse();
|
|
333
|
+
if (!Number.isFinite(value)) {
|
|
334
|
+
return {
|
|
335
|
+
ok: false,
|
|
336
|
+
error: Number.isNaN(value)
|
|
337
|
+
? "Result is not a number"
|
|
338
|
+
: "Result is not a finite number",
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return { ok: true, value };
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
return {
|
|
345
|
+
ok: false,
|
|
346
|
+
error: error instanceof Error ? error.message : "Could not parse expression",
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -104,21 +104,15 @@ export declare function createDirectAgentTools(policy: FileToolRootPolicy): {
|
|
|
104
104
|
}, {
|
|
105
105
|
success: boolean;
|
|
106
106
|
error: string;
|
|
107
|
-
expression
|
|
107
|
+
expression: string;
|
|
108
108
|
result?: undefined;
|
|
109
109
|
type?: undefined;
|
|
110
110
|
} | {
|
|
111
111
|
success: boolean;
|
|
112
112
|
expression: string;
|
|
113
|
-
result:
|
|
114
|
-
type:
|
|
113
|
+
result: number;
|
|
114
|
+
type: string;
|
|
115
115
|
error?: undefined;
|
|
116
|
-
} | {
|
|
117
|
-
success: boolean;
|
|
118
|
-
error: string;
|
|
119
|
-
expression: string;
|
|
120
|
-
result?: undefined;
|
|
121
|
-
type?: undefined;
|
|
122
116
|
}>;
|
|
123
117
|
writeFile: Tool;
|
|
124
118
|
analyzeCSV: Tool;
|
|
@@ -232,21 +226,15 @@ export declare const directAgentTools: {
|
|
|
232
226
|
}, {
|
|
233
227
|
success: boolean;
|
|
234
228
|
error: string;
|
|
235
|
-
expression
|
|
229
|
+
expression: string;
|
|
236
230
|
result?: undefined;
|
|
237
231
|
type?: undefined;
|
|
238
232
|
} | {
|
|
239
233
|
success: boolean;
|
|
240
234
|
expression: string;
|
|
241
|
-
result:
|
|
242
|
-
type:
|
|
235
|
+
result: number;
|
|
236
|
+
type: string;
|
|
243
237
|
error?: undefined;
|
|
244
|
-
} | {
|
|
245
|
-
success: boolean;
|
|
246
|
-
error: string;
|
|
247
|
-
expression: string;
|
|
248
|
-
result?: undefined;
|
|
249
|
-
type?: undefined;
|
|
250
238
|
}>;
|
|
251
239
|
writeFile: Tool;
|
|
252
240
|
analyzeCSV: Tool;
|
|
@@ -5,6 +5,7 @@ import { execFile } from "child_process";
|
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
import { CSVProcessor } from "../utils/csvProcessor.js";
|
|
7
7
|
import { shouldEnableBashTool } from "../utils/toolUtils.js";
|
|
8
|
+
import { ARITHMETIC_CONSTANT_NAMES, ARITHMETIC_FUNCTION_NAMES, evaluateArithmetic, } from "./arithmeticEvaluator.js";
|
|
8
9
|
import { tool } from "../utils/tool.js";
|
|
9
10
|
import { withTimeout } from "../utils/async/withTimeout.js";
|
|
10
11
|
import { resolvePathWithinRoot, resolveWithinRoot, } from "../utils/pathSandbox.js";
|
|
@@ -689,11 +690,11 @@ const policyFreeTools = {
|
|
|
689
690
|
},
|
|
690
691
|
}),
|
|
691
692
|
calculateMath: tool({
|
|
692
|
-
description: "
|
|
693
|
+
description: "Evaluate an arithmetic expression: numbers, + - * / % **, parentheses and Math functions such as sqrt, pow, sin, log, min, max (with or without the Math. prefix). Not a JavaScript evaluator.",
|
|
693
694
|
inputSchema: z.object({
|
|
694
695
|
expression: z
|
|
695
696
|
.string()
|
|
696
|
-
.describe('
|
|
697
|
+
.describe('Arithmetic expression to evaluate (e.g., "2 + 2", "Math.sqrt(16)", "pow(2, 10) % 7")'),
|
|
697
698
|
precision: z
|
|
698
699
|
.number()
|
|
699
700
|
.optional()
|
|
@@ -701,64 +702,24 @@ const policyFreeTools = {
|
|
|
701
702
|
.default(2),
|
|
702
703
|
}),
|
|
703
704
|
execute: async ({ expression, precision }) => {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const allowedMathFunctions = [
|
|
710
|
-
"Math.abs",
|
|
711
|
-
"Math.ceil",
|
|
712
|
-
"Math.floor",
|
|
713
|
-
"Math.round",
|
|
714
|
-
"Math.sqrt",
|
|
715
|
-
"Math.pow",
|
|
716
|
-
"Math.sin",
|
|
717
|
-
"Math.cos",
|
|
718
|
-
"Math.tan",
|
|
719
|
-
"Math.log",
|
|
720
|
-
"Math.exp",
|
|
721
|
-
"Math.PI",
|
|
722
|
-
"Math.E",
|
|
723
|
-
];
|
|
724
|
-
let safeExpression = expression;
|
|
725
|
-
for (const func of allowedMathFunctions) {
|
|
726
|
-
safeExpression = safeExpression.replace(new RegExp(func, "g"), func);
|
|
727
|
-
}
|
|
728
|
-
// Remove remaining non-safe characters except Math functions
|
|
729
|
-
const mathSafe = /^[0-9+\-*/().\s]|Math\.(abs|ceil|floor|round|sqrt|pow|sin|cos|tan|log|exp|PI|E)/g;
|
|
730
|
-
if (!safeExpression
|
|
731
|
-
.split("")
|
|
732
|
-
.every((char) => mathSafe.test(char) ||
|
|
733
|
-
char === "(" ||
|
|
734
|
-
char === ")" ||
|
|
735
|
-
char === "," ||
|
|
736
|
-
char === " ")) {
|
|
737
|
-
return {
|
|
738
|
-
success: false,
|
|
739
|
-
error: `Unsafe expression: Only basic math operations and Math functions are allowed`,
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
// Use Function constructor for safe evaluation
|
|
744
|
-
const result = new Function(`'use strict'; return (${expression})`)();
|
|
745
|
-
const roundedResult = typeof result === "number"
|
|
746
|
-
? Number(result.toFixed(precision))
|
|
747
|
-
: result;
|
|
748
|
-
return {
|
|
749
|
-
success: true,
|
|
750
|
-
expression,
|
|
751
|
-
result: roundedResult,
|
|
752
|
-
type: typeof result,
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
catch (error) {
|
|
705
|
+
// The expression is model output. It is parsed and evaluated by
|
|
706
|
+
// `evaluateArithmetic` — a recursive-descent parser over numbers and a
|
|
707
|
+
// fixed list of Math functions — never run as JavaScript.
|
|
708
|
+
const evaluation = evaluateArithmetic(expression);
|
|
709
|
+
if (!evaluation.ok) {
|
|
756
710
|
return {
|
|
757
711
|
success: false,
|
|
758
|
-
error: error
|
|
712
|
+
error: `${evaluation.error}. Only numbers, + - * / % ** parentheses and the Math functions ${ARITHMETIC_FUNCTION_NAMES.join(", ")} (constants ${ARITHMETIC_CONSTANT_NAMES.join(", ")}) are supported.`,
|
|
759
713
|
expression,
|
|
760
714
|
};
|
|
761
715
|
}
|
|
716
|
+
const digits = Math.min(Math.max(Math.trunc(precision ?? 2), 0), 100);
|
|
717
|
+
return {
|
|
718
|
+
success: true,
|
|
719
|
+
expression,
|
|
720
|
+
result: Number(evaluation.value.toFixed(digits)),
|
|
721
|
+
type: "number",
|
|
722
|
+
};
|
|
762
723
|
},
|
|
763
724
|
}),
|
|
764
725
|
websearchGrounding: tool({
|
|
@@ -907,7 +868,7 @@ export function createDirectAgentTools(policy) {
|
|
|
907
868
|
*/
|
|
908
869
|
export function createBashTool(policy) {
|
|
909
870
|
return tool({
|
|
910
|
-
description: "Execute a bash/shell command and return stdout, stderr, and exit code. Supports full shell syntax including pipes, redirects, and variable expansion.
|
|
871
|
+
description: "Execute a bash/shell command and return stdout, stderr, and exit code. Supports full shell syntax including pipes, redirects, and variable expansion. When HITL is enabled on the NeuroLink instance, every call waits for a human to approve it before it runs. The command runs with the full privileges of the process: tool roots constrain only its starting directory (cwd), not where the command itself can reach.",
|
|
911
872
|
inputSchema: z.object({
|
|
912
873
|
command: z
|
|
913
874
|
.string()
|
|
@@ -46,8 +46,13 @@ export declare class LocalTempArtifactStore implements ArtifactStore {
|
|
|
46
46
|
private readonly dir;
|
|
47
47
|
private readonly index;
|
|
48
48
|
private readonly rehydrateFromDisk;
|
|
49
|
+
/** Resolves once the directory has been created and judged ours. */
|
|
50
|
+
private dirReady;
|
|
49
51
|
/**
|
|
50
|
-
* @param dir - Storage directory; defaults to
|
|
52
|
+
* @param dir - Storage directory; defaults to
|
|
53
|
+
* `tmpdir()/neurolink-artifacts-<uid>` (per unix user, so two accounts on
|
|
54
|
+
* one host never share a directory; plain `neurolink-artifacts` where the
|
|
55
|
+
* platform has no uid)
|
|
51
56
|
* @param options - `rehydrateFromDisk` (default true) lets `retrieve()` and
|
|
52
57
|
* `delete()` fall back to the on-disk sidecar index on an in-memory miss,
|
|
53
58
|
* which makes artifacts READABLE AND DELETABLE ACROSS PROCESSES sharing
|
|
@@ -58,6 +63,17 @@ export declare class LocalTempArtifactStore implements ArtifactStore {
|
|
|
58
63
|
constructor(dir?: string, options?: {
|
|
59
64
|
rehydrateFromDisk?: boolean;
|
|
60
65
|
});
|
|
66
|
+
/**
|
|
67
|
+
* Create the directory if needed and refuse one this user does not own.
|
|
68
|
+
*
|
|
69
|
+
* The OS temp directory is shared, so a pre-existing path with our name may
|
|
70
|
+
* have been planted by another local account: a symlink to somewhere we
|
|
71
|
+
* would then write into, or a directory they can read. Judge the directory
|
|
72
|
+
* with `lstat` (never following a link), require a real directory owned by
|
|
73
|
+
* this uid, and tighten its mode to 0o700 when it is ours but looser. Done
|
|
74
|
+
* once per instance, before the first write or read.
|
|
75
|
+
*/
|
|
76
|
+
private ensureOwnedDir;
|
|
61
77
|
generatePreview(payload: string): string;
|
|
62
78
|
store(payload: string, meta: Omit<ArtifactMeta, "createdAt">): Promise<ArtifactRef>;
|
|
63
79
|
retrieve(id: string): Promise<string | null>;
|
|
@@ -83,5 +99,10 @@ export declare class LocalTempArtifactStore implements ArtifactStore {
|
|
|
83
99
|
* now that a miss probes `join(dir, id + ext)` the id — which arrives from
|
|
84
100
|
* the model — reaches the path layer. See `isSafeArtifactId`.
|
|
85
101
|
*/
|
|
102
|
+
/**
|
|
103
|
+
* `rehydrate` after the directory has been judged ours: a sidecar in a
|
|
104
|
+
* directory another account controls is not a record to trust.
|
|
105
|
+
*/
|
|
106
|
+
private rehydrateChecked;
|
|
86
107
|
private rehydrate;
|
|
87
108
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* @module artifacts/artifactStore
|
|
20
20
|
*/
|
|
21
21
|
import { randomUUID } from "node:crypto";
|
|
22
|
-
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
22
|
+
import { lstat, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
23
23
|
import { tmpdir } from "node:os";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { logger } from "../utils/logger.js";
|
|
@@ -27,6 +27,15 @@ import { generateArtifactPreview, isSafeArtifactId } from "./artifactReader.js";
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
// LocalTempArtifactStore
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* `tmpdir()/neurolink-artifacts-<uid>`: a per-user directory name, so two
|
|
32
|
+
* accounts on one host never contend for — or read — the same path. Where the
|
|
33
|
+
* platform has no uid the old shared name is kept.
|
|
34
|
+
*/
|
|
35
|
+
function defaultArtifactDir() {
|
|
36
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : -1;
|
|
37
|
+
return join(tmpdir(), uid >= 0 ? `neurolink-artifacts-${uid}` : "neurolink-artifacts");
|
|
38
|
+
}
|
|
30
39
|
/**
|
|
31
40
|
* Sidecar written beside every payload so a process that never called
|
|
32
41
|
* `store()` can still resolve the id (see the index-miss path in `retrieve`).
|
|
@@ -103,8 +112,13 @@ export class LocalTempArtifactStore {
|
|
|
103
112
|
dir;
|
|
104
113
|
index = new Map();
|
|
105
114
|
rehydrateFromDisk;
|
|
115
|
+
/** Resolves once the directory has been created and judged ours. */
|
|
116
|
+
dirReady;
|
|
106
117
|
/**
|
|
107
|
-
* @param dir - Storage directory; defaults to
|
|
118
|
+
* @param dir - Storage directory; defaults to
|
|
119
|
+
* `tmpdir()/neurolink-artifacts-<uid>` (per unix user, so two accounts on
|
|
120
|
+
* one host never share a directory; plain `neurolink-artifacts` where the
|
|
121
|
+
* platform has no uid)
|
|
108
122
|
* @param options - `rehydrateFromDisk` (default true) lets `retrieve()` and
|
|
109
123
|
* `delete()` fall back to the on-disk sidecar index on an in-memory miss,
|
|
110
124
|
* which makes artifacts READABLE AND DELETABLE ACROSS PROCESSES sharing
|
|
@@ -113,16 +127,48 @@ export class LocalTempArtifactStore {
|
|
|
113
127
|
* isolation: ids not stored by this process resolve to nothing.
|
|
114
128
|
*/
|
|
115
129
|
constructor(dir, options) {
|
|
116
|
-
this.dir = dir ??
|
|
130
|
+
this.dir = dir ?? defaultArtifactDir();
|
|
117
131
|
this.rehydrateFromDisk =
|
|
118
132
|
options?.rehydrateFromDisk ??
|
|
119
133
|
process.env.NEUROLINK_ARTIFACT_REHYDRATE !== "false";
|
|
120
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Create the directory if needed and refuse one this user does not own.
|
|
137
|
+
*
|
|
138
|
+
* The OS temp directory is shared, so a pre-existing path with our name may
|
|
139
|
+
* have been planted by another local account: a symlink to somewhere we
|
|
140
|
+
* would then write into, or a directory they can read. Judge the directory
|
|
141
|
+
* with `lstat` (never following a link), require a real directory owned by
|
|
142
|
+
* this uid, and tighten its mode to 0o700 when it is ours but looser. Done
|
|
143
|
+
* once per instance, before the first write or read.
|
|
144
|
+
*/
|
|
145
|
+
ensureOwnedDir() {
|
|
146
|
+
this.dirReady ??= (async () => {
|
|
147
|
+
await mkdir(this.dir, { recursive: true, mode: 0o700 });
|
|
148
|
+
const info = await lstat(this.dir);
|
|
149
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
150
|
+
throw new Error(`[ArtifactStore] refusing to use "${this.dir}": not a plain directory`);
|
|
151
|
+
}
|
|
152
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : -1;
|
|
153
|
+
if (uid >= 0 && info.uid !== uid) {
|
|
154
|
+
throw new Error(`[ArtifactStore] refusing to use "${this.dir}": owned by uid ${info.uid}, not ${uid}`);
|
|
155
|
+
}
|
|
156
|
+
if (uid >= 0 && (info.mode & 0o077) !== 0) {
|
|
157
|
+
const { chmod } = await import("node:fs/promises");
|
|
158
|
+
await chmod(this.dir, 0o700);
|
|
159
|
+
}
|
|
160
|
+
})().catch((error) => {
|
|
161
|
+
// Let the next call retry rather than caching a transient failure.
|
|
162
|
+
this.dirReady = undefined;
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
return this.dirReady;
|
|
166
|
+
}
|
|
121
167
|
generatePreview(payload) {
|
|
122
168
|
return generateArtifactPreview(payload);
|
|
123
169
|
}
|
|
124
170
|
async store(payload, meta) {
|
|
125
|
-
await
|
|
171
|
+
await this.ensureOwnedDir();
|
|
126
172
|
const id = randomUUID();
|
|
127
173
|
const ext = meta.contentType === "json" ? ".json" : ".txt";
|
|
128
174
|
const filePath = join(this.dir, `${id}${ext}`);
|
|
@@ -145,7 +191,7 @@ export class LocalTempArtifactStore {
|
|
|
145
191
|
}
|
|
146
192
|
async retrieve(id) {
|
|
147
193
|
const entry = this.index.get(id) ??
|
|
148
|
-
(this.rehydrateFromDisk ? await this.
|
|
194
|
+
(this.rehydrateFromDisk ? await this.rehydrateChecked(id) : undefined);
|
|
149
195
|
if (!entry) {
|
|
150
196
|
logger.debug(`[ArtifactStore] Artifact ${id} not found on disk`);
|
|
151
197
|
return null;
|
|
@@ -162,7 +208,7 @@ export class LocalTempArtifactStore {
|
|
|
162
208
|
}
|
|
163
209
|
async delete(id) {
|
|
164
210
|
const entry = this.index.get(id) ??
|
|
165
|
-
(this.rehydrateFromDisk ? await this.
|
|
211
|
+
(this.rehydrateFromDisk ? await this.rehydrateChecked(id) : undefined);
|
|
166
212
|
if (!entry) {
|
|
167
213
|
return;
|
|
168
214
|
}
|
|
@@ -222,6 +268,20 @@ export class LocalTempArtifactStore {
|
|
|
222
268
|
* now that a miss probes `join(dir, id + ext)` the id — which arrives from
|
|
223
269
|
* the model — reaches the path layer. See `isSafeArtifactId`.
|
|
224
270
|
*/
|
|
271
|
+
/**
|
|
272
|
+
* `rehydrate` after the directory has been judged ours: a sidecar in a
|
|
273
|
+
* directory another account controls is not a record to trust.
|
|
274
|
+
*/
|
|
275
|
+
async rehydrateChecked(id) {
|
|
276
|
+
try {
|
|
277
|
+
await this.ensureOwnedDir();
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
logger.warn(`[ArtifactStore] ${error instanceof Error ? error.message : String(error)}`);
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
return this.rehydrate(id);
|
|
284
|
+
}
|
|
225
285
|
async rehydrate(id) {
|
|
226
286
|
if (!isSafeArtifactId(id)) {
|
|
227
287
|
logger.debug(`[ArtifactStore] Rejected unsafe artifact id "${id}"`);
|