evolcore 0.0.19 → 0.0.21
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 +43 -1
- package/README.md +60 -9
- package/bin/install-codex-managed-hooks.mjs +61 -0
- package/dist/agents/baseagent.js +10 -6
- package/dist/agents/claude-runner.js +383 -111
- package/dist/agents/codex-app-server-client.js +10 -2
- package/dist/agents/codex-runner.js +405 -137
- package/dist/agents/ecagent-runner.js +174 -63
- package/dist/agents/gemini-runner.js +130 -30
- package/dist/agents/request-identity.js +25 -0
- package/dist/agents/runner-types.js +19 -0
- package/dist/aun/aid/agentmd.js +66 -2
- package/dist/aun/aid/identity.js +4 -1
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +86 -9
- package/dist/aun/msg/history.js +213 -36
- package/dist/aun/msg/managed-operation.js +58 -9
- package/dist/aun/msg/p2p.js +26 -11
- package/dist/aun/outbox.js +295 -68
- package/dist/aun/service-proxy.js +43 -25
- package/dist/channels/aun.js +1004 -273
- package/dist/channels/daemon.js +6 -1
- package/dist/cli/agent-command.js +4 -3
- package/dist/cli/agent.js +66 -56
- package/dist/cli/aun-commands.js +177 -42
- package/dist/cli/command-log.js +10 -11
- package/dist/cli/contact.js +1 -0
- package/dist/cli/daemon-commands.js +81 -121
- package/dist/cli/index.js +1 -0
- package/dist/cli/init.js +76 -24
- package/dist/cli/restart-monitor.js +3 -3
- package/dist/cli/task-context.js +46 -0
- package/dist/cli/trigger-command.js +1 -1
- package/dist/cli/watch-logs.js +10 -3
- package/dist/config/builtin-roles.js +1 -0
- package/dist/config/config-field-policy.js +16 -5
- package/dist/config/config-manager.js +226 -24
- package/dist/config/config-operation-service.js +1 -2
- package/dist/config/contact-operation-service.js +32 -1
- package/dist/config/contact-request-service.js +44 -0
- package/dist/config/daemon-services.js +186 -0
- package/dist/config/gateway-config.js +29 -16
- package/dist/config/lifecycle.js +16 -5
- package/dist/config/role-service.js +54 -3
- package/dist/config/schema-migration.js +550 -0
- package/dist/config-store.js +161 -12
- package/dist/core/agent-application-service.js +279 -0
- package/dist/core/audit/log-integrity.js +102 -0
- package/dist/core/auth/agent-delegation.js +31 -1
- package/dist/core/auth/auth-gateway.js +33 -4
- package/dist/core/auth/authorization-audit.js +155 -4
- package/dist/core/auth/operation-authorizer.js +41 -1
- package/dist/core/auth/operation-catalog.js +9 -1
- package/dist/core/bootstrap-messages.js +2 -2
- package/dist/core/bootstrap-service.js +27 -38
- package/dist/core/causation/aun-association.js +7 -4
- package/dist/core/channel-loader.js +0 -2
- package/dist/core/command/agent-control.js +56 -16
- package/dist/core/command/command-handler.js +290 -44
- package/dist/core/command/connect-menu.js +3 -4
- package/dist/core/command/group-menu.js +5 -7
- package/dist/core/command/menu-handler.js +279 -80
- package/dist/core/command/role-menu.js +21 -11
- package/dist/core/command/slash-gate.js +85 -18
- package/dist/core/command/slash-handler.js +350 -32
- package/dist/core/event-catalog.js +5 -0
- package/dist/core/evolagent.js +9 -4
- package/dist/core/handoff/dispatcher.js +4 -0
- package/dist/core/handoff/runtime.js +10 -0
- package/dist/core/handoff/store.js +32 -9
- package/dist/core/inference/text-inference.js +7 -15
- package/dist/core/message/im-renderer.js +83 -84
- package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
- package/dist/core/message/message-bridge.js +124 -10
- package/dist/core/message/message-log.js +14 -7
- package/dist/core/message/message-queue.js +206 -16
- package/dist/core/message/message-utils.js +12 -5
- package/dist/core/message/response-engine.js +495 -72
- package/dist/core/message/send-receipt.js +1 -0
- package/dist/core/message/stream-debouncer.js +9 -2
- package/dist/core/model/model-catalog.js +23 -15
- package/dist/core/model/model-diagnostics.js +28 -10
- package/dist/core/permission/approval-gateway.js +180 -6
- package/dist/core/permission/ec-command-parser.js +465 -56
- package/dist/core/permission/mode.js +18 -3
- package/dist/core/{protected-paths.js → permission/protected-paths.js} +17 -5
- package/dist/core/permission/readonly-shell-query.js +263 -9
- package/dist/core/permission/sandbox-runtime.js +205 -13
- package/dist/core/permission/tool-policy.js +673 -62
- package/dist/core/relation/peer-identity.js +18 -0
- package/dist/core/session/session-fs-store.js +154 -5
- package/dist/core/session/session-manager.js +299 -30
- package/dist/core/session/session-renew.js +19 -12
- package/dist/core/session/session-turn-coordinator.js +11 -4
- package/dist/eck/kit-renderer.js +18 -9
- package/dist/index.js +283 -65
- package/dist/ipc.js +374 -24
- package/dist/paths.js +64 -7
- package/dist/response-system/context-builder.js +1 -7
- package/dist/trigger/anomaly-store.js +1 -0
- package/dist/trigger/feedback.js +56 -5
- package/dist/trigger/history.js +79 -4
- package/dist/trigger/legacy-session-history.js +2 -2
- package/dist/trigger/parser.js +3 -2
- package/dist/trigger/validation.js +6 -1
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/ecweb-utils.js +16 -2
- package/dist/utils/error-utils.js +4 -1
- package/dist/utils/logger.js +21 -2
- package/dist/utils/process-tree-stats.js +24 -4
- package/dist/utils/process-tree-worker.js +31 -0
- package/dist/utils/project-path.js +1 -2
- package/dist/utils/stats.js +52 -18
- package/dist/utils/welcome.js +2 -2
- package/kits/docs/INDEX.md +1 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/contact.md +7 -1
- package/kits/docs/evolcore/msg.md +16 -0
- package/kits/rules/01-overview.md +1 -1
- package/kits/rules/03-identity.md +1 -1
- package/kits/rules/05-venue.md +1 -1
- package/kits/schemas/_meta.json +10 -5
- package/kits/schemas/agent-config.schema.10.json +2 -1
- package/kits/schemas/agent-config.schema.11.json +421 -0
- package/kits/schemas/daemon.schema.5.json +135 -0
- package/kits/schemas/daemon.schema.6.json +131 -0
- package/kits/schemas/defaults.schema.5.json +119 -0
- package/kits/schemas/migrations/README.md +3 -1
- package/kits/schemas/relation-config.schema.8.json +13 -0
- package/kits/schemas/role-config.schema.1.json +1 -2
- package/kits/schemas/single-session.schema.3.json +32 -0
- package/kits/templates/roles/admin.json +1 -0
- package/kits/templates/roles/member.json +1 -0
- package/kits/templates/roles/visitor.json +1 -0
- package/package.json +7 -3
- package/skills/eclink/SKILL.md +2 -0
|
@@ -10,6 +10,13 @@ const CODEX_POWERSHELL_EXECUTABLES = new Set([
|
|
|
10
10
|
'pwsh', 'pwsh.exe', 'powershell', 'powershell.exe',
|
|
11
11
|
]);
|
|
12
12
|
const CODEX_POWERSHELL_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Program Files[\\/]PowerShell[\\/][0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9.-]+)?[\\/]pwsh\.exe|Windows[\\/]System32[\\/]WindowsPowerShell[\\/]v1\.0[\\/]powershell\.exe)$/i;
|
|
13
|
+
const CODEX_CMD_EXECUTABLES = new Set(['cmd', 'cmd.exe']);
|
|
14
|
+
const CODEX_CMD_INSTALL_PATH_RE = /^[A-Za-z]:[\\/](?:Windows[\\/](?:System32|SysWOW64|Sysnative)[\\/]cmd\.exe)$/i;
|
|
15
|
+
// These switches affect only cmd startup/output formatting. `/c` is handled
|
|
16
|
+
// separately as the required execute switch. Flags such as `/k`, `/v:on`,
|
|
17
|
+
// `/e:*`, and `/f:*` can change process lifetime or command semantics and are
|
|
18
|
+
// intentionally not treated as a trusted Codex carrier.
|
|
19
|
+
const CODEX_CMD_INERT_SWITCHES = new Set(['/d', '/s', '/q', '/a', '/u']);
|
|
13
20
|
const SHELL_UNQUOTED_META = new Set([
|
|
14
21
|
';', '&', '|', '`', '<', '>', '$', '(', ')', '*', '?', '[', ']', '{', '}', '#',
|
|
15
22
|
]);
|
|
@@ -17,7 +24,57 @@ const POWERSHELL_UNQUOTED_META = new Set([
|
|
|
17
24
|
';', '&', '|', '<', '>', '$', '(', ')', '*', '?', '[', ']', '{', '}', '#', '@',
|
|
18
25
|
]);
|
|
19
26
|
const AMBIGUOUS_FILE_PATH_RE = /[\0\r\n$`*?\[\]{}<>|;&]/;
|
|
20
|
-
|
|
27
|
+
// This is intentionally limited to the command boundary. It is not a search
|
|
28
|
+
// for `ec` anywhere in the input: quoted arguments, regexps and log text must
|
|
29
|
+
// never establish an EC invocation.
|
|
30
|
+
function hasEcCommandPrefix(command) {
|
|
31
|
+
let quote = null;
|
|
32
|
+
let escaped = false;
|
|
33
|
+
let boundary = true;
|
|
34
|
+
for (let index = 0; index < command.length; index++) {
|
|
35
|
+
const char = command[index];
|
|
36
|
+
if (escaped) {
|
|
37
|
+
escaped = false;
|
|
38
|
+
boundary = false;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (quote === 'single') {
|
|
42
|
+
if (char === "'")
|
|
43
|
+
quote = null;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (quote === 'double') {
|
|
47
|
+
if (char === '\\') {
|
|
48
|
+
escaped = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === '"')
|
|
52
|
+
quote = null;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === "'") {
|
|
56
|
+
quote = 'single';
|
|
57
|
+
boundary = false;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (char === '"') {
|
|
61
|
+
quote = 'double';
|
|
62
|
+
boundary = false;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (/[;&|`()<>]/.test(char)) {
|
|
66
|
+
boundary = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (boundary && /\s/.test(char))
|
|
70
|
+
continue;
|
|
71
|
+
if (boundary && command.slice(index, index + 2).toLowerCase() === 'ec'
|
|
72
|
+
&& (index + 2 === command.length || /\s|[;&|`()<>]/.test(command[index + 2])))
|
|
73
|
+
return true;
|
|
74
|
+
boundary = false;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
21
78
|
function trimShellSpacing(value) {
|
|
22
79
|
return value.replace(/^[ \t]+|[ \t]+$/g, '');
|
|
23
80
|
}
|
|
@@ -90,12 +147,31 @@ function isLiteralArgBoundary(value) {
|
|
|
90
147
|
function isShellParameterExpansionStart(value) {
|
|
91
148
|
return value === '(' || value === '{' || !!value && /[A-Za-z0-9_@*#?$!\-]/.test(value);
|
|
92
149
|
}
|
|
150
|
+
function hasCarrierEcCommandPrefix(command, dialect) {
|
|
151
|
+
if (dialect !== 'cmd')
|
|
152
|
+
return hasEcCommandPrefix(command);
|
|
153
|
+
const input = trimShellSpacing(command);
|
|
154
|
+
const inner = input.length >= 2 && input.startsWith('"') && input.endsWith('"')
|
|
155
|
+
? input.slice(1, -1)
|
|
156
|
+
: input;
|
|
157
|
+
return /^\s*ec(?:\s|$)/i.test(inner);
|
|
158
|
+
}
|
|
159
|
+
function parseFailure(issue, input, offset, tokenIndex, dialect) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
issue,
|
|
163
|
+
offset: Math.max(0, Math.min(offset, input.length)),
|
|
164
|
+
tokenIndex: Math.max(0, tokenIndex),
|
|
165
|
+
dialect,
|
|
166
|
+
inputLength: input.length,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
93
169
|
function parseLiteralPowerShellCommand(command) {
|
|
94
170
|
const input = trimShellSpacing(command);
|
|
95
171
|
if (!input)
|
|
96
|
-
return
|
|
172
|
+
return parseFailure('empty-command', input, 0, 0, 'powershell');
|
|
97
173
|
if (input.includes('\0'))
|
|
98
|
-
return
|
|
174
|
+
return parseFailure('invalid-control-char', input, input.indexOf('\0'), 0, 'powershell');
|
|
99
175
|
const argv = [];
|
|
100
176
|
let token = '';
|
|
101
177
|
let tokenStarted = false;
|
|
@@ -116,7 +192,7 @@ function parseLiteralPowerShellCommand(command) {
|
|
|
116
192
|
}
|
|
117
193
|
else if (char === "'") {
|
|
118
194
|
if (!isLiteralArgBoundary(input[index + 1]))
|
|
119
|
-
return
|
|
195
|
+
return parseFailure('invalid-quote', input, index, argv.length, 'powershell');
|
|
120
196
|
quote = null;
|
|
121
197
|
}
|
|
122
198
|
else {
|
|
@@ -127,21 +203,21 @@ function parseLiteralPowerShellCommand(command) {
|
|
|
127
203
|
if (quote === 'double') {
|
|
128
204
|
if (char === '"') {
|
|
129
205
|
if (!isLiteralArgBoundary(input[index + 1]))
|
|
130
|
-
return
|
|
206
|
+
return parseFailure('invalid-quote', input, index, argv.length, 'powershell');
|
|
131
207
|
quote = null;
|
|
132
208
|
continue;
|
|
133
209
|
}
|
|
134
210
|
if (char === '`') {
|
|
135
211
|
const next = input[++index];
|
|
136
212
|
if (next === undefined)
|
|
137
|
-
return
|
|
213
|
+
return parseFailure('invalid-quote', input, index, argv.length, 'powershell');
|
|
138
214
|
if (next === '\r' || next === '\n')
|
|
139
|
-
return
|
|
215
|
+
return parseFailure('structural-newline', input, index, argv.length, 'powershell');
|
|
140
216
|
token += next;
|
|
141
217
|
continue;
|
|
142
218
|
}
|
|
143
219
|
if (char === '$' && isShellParameterExpansionStart(input[index + 1])) {
|
|
144
|
-
return
|
|
220
|
+
return parseFailure('unsafe-expansion', input, index, argv.length, 'powershell');
|
|
145
221
|
}
|
|
146
222
|
token += char;
|
|
147
223
|
continue;
|
|
@@ -151,9 +227,9 @@ function parseLiteralPowerShellCommand(command) {
|
|
|
151
227
|
continue;
|
|
152
228
|
}
|
|
153
229
|
if (char === '\r' || char === '\n')
|
|
154
|
-
return
|
|
230
|
+
return parseFailure('structural-newline', input, index, argv.length, 'powershell');
|
|
155
231
|
if (/\s/u.test(char) || char.charCodeAt(0) < 0x20 || char === '\x7f') {
|
|
156
|
-
return
|
|
232
|
+
return parseFailure('invalid-control-char', input, index, argv.length, 'powershell');
|
|
157
233
|
}
|
|
158
234
|
if (char === "'") {
|
|
159
235
|
tokenStarted = true;
|
|
@@ -168,9 +244,9 @@ function parseLiteralPowerShellCommand(command) {
|
|
|
168
244
|
if (char === '`') {
|
|
169
245
|
const next = input[++index];
|
|
170
246
|
if (next === undefined)
|
|
171
|
-
return
|
|
247
|
+
return parseFailure('invalid-quote', input, index, argv.length, 'powershell');
|
|
172
248
|
if (next === '\r' || next === '\n')
|
|
173
|
-
return
|
|
249
|
+
return parseFailure('structural-newline', input, index, argv.length, 'powershell');
|
|
174
250
|
tokenStarted = true;
|
|
175
251
|
token += next;
|
|
176
252
|
continue;
|
|
@@ -178,17 +254,245 @@ function parseLiteralPowerShellCommand(command) {
|
|
|
178
254
|
const isWordInitialMeta = (char === '#' || char === '@') && !tokenStarted;
|
|
179
255
|
const isAlwaysMeta = POWERSHELL_UNQUOTED_META.has(char) && char !== '#' && char !== '@';
|
|
180
256
|
if (isAlwaysMeta || isWordInitialMeta || (char === '~' && !tokenStarted)) {
|
|
181
|
-
return
|
|
257
|
+
return parseFailure('shell-composition', input, index, argv.length, 'powershell');
|
|
182
258
|
}
|
|
183
259
|
tokenStarted = true;
|
|
184
260
|
token += char;
|
|
185
261
|
}
|
|
186
262
|
if (quote)
|
|
187
|
-
return
|
|
263
|
+
return parseFailure('invalid-quote', input, input.length, argv.length, 'powershell');
|
|
188
264
|
finishToken();
|
|
189
265
|
return argv.length > 0
|
|
190
266
|
? { ok: true, argv }
|
|
191
|
-
:
|
|
267
|
+
: parseFailure('empty-command', input, input.length, argv.length, 'powershell');
|
|
268
|
+
}
|
|
269
|
+
function parseLiteralCmdCommand(command, options = {}) {
|
|
270
|
+
let input = trimShellSpacing(command);
|
|
271
|
+
if (!input)
|
|
272
|
+
return parseFailure('empty-command', input, 0, 0, 'cmd');
|
|
273
|
+
if (input.includes('\0'))
|
|
274
|
+
return parseFailure('invalid-control-char', input, input.indexOf('\0'), 0, 'cmd');
|
|
275
|
+
// `cmd.exe /c` commonly wraps the complete command in one quote pair.
|
|
276
|
+
// Those quotes belong to the carrier, not to the first EC argument; remove
|
|
277
|
+
// exactly that outer pair before tokenizing the inner command.
|
|
278
|
+
if (input.length >= 2 && input.startsWith('"') && input.endsWith('"')) {
|
|
279
|
+
input = input.slice(1, -1);
|
|
280
|
+
}
|
|
281
|
+
const argv = [];
|
|
282
|
+
let token = '';
|
|
283
|
+
let tokenStarted = false;
|
|
284
|
+
let quote = false;
|
|
285
|
+
const finishToken = () => {
|
|
286
|
+
if (!tokenStarted)
|
|
287
|
+
return;
|
|
288
|
+
argv.push(token);
|
|
289
|
+
token = '';
|
|
290
|
+
tokenStarted = false;
|
|
291
|
+
};
|
|
292
|
+
for (let index = 0; index < input.length; index++) {
|
|
293
|
+
const char = input[index];
|
|
294
|
+
if (quote) {
|
|
295
|
+
if (char === '"') {
|
|
296
|
+
// cmd.exe represents a literal quote in a quoted argument as `""`.
|
|
297
|
+
if (input[index + 1] === '"') {
|
|
298
|
+
token += '"';
|
|
299
|
+
index++;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
quote = false;
|
|
303
|
+
}
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
// Environment expansion and caret escaping remain active in cmd quoted
|
|
307
|
+
// strings, so accepting either would make the canonical argv differ
|
|
308
|
+
// from what cmd.exe actually executes.
|
|
309
|
+
if (char === '%' || char === '!' || char === '^') {
|
|
310
|
+
return parseFailure(char === '%' ? 'unsafe-expansion' : 'shell-composition', input, index, argv.length, 'cmd');
|
|
311
|
+
}
|
|
312
|
+
if (char === '\r' || char === '\n')
|
|
313
|
+
return parseFailure('structural-newline', input, index, argv.length, 'cmd');
|
|
314
|
+
token += char;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (char === ' ' || char === '\t') {
|
|
318
|
+
finishToken();
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (char === '\r' || char === '\n')
|
|
322
|
+
return parseFailure('structural-newline', input, index, argv.length, 'cmd');
|
|
323
|
+
if (/\s/u.test(char) || char.charCodeAt(0) < 0x20 || char === '\x7f') {
|
|
324
|
+
return parseFailure('invalid-control-char', input, index, argv.length, 'cmd');
|
|
325
|
+
}
|
|
326
|
+
if (char === '"') {
|
|
327
|
+
tokenStarted = true;
|
|
328
|
+
quote = true;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (char === '%' || char === '!') {
|
|
332
|
+
return parseFailure(char === '%' ? 'unsafe-expansion' : 'shell-composition', input, index, argv.length, 'cmd');
|
|
333
|
+
}
|
|
334
|
+
if (char === '^' || char === '&' || char === '|' || char === '<' || char === '>'
|
|
335
|
+
|| char === ';' || char === '(' || char === ')' || char === '*' || char === '?'
|
|
336
|
+
|| char === '[' || char === ']' || char === '{' || char === '}') {
|
|
337
|
+
return parseFailure('shell-composition', input, index, argv.length, 'cmd');
|
|
338
|
+
}
|
|
339
|
+
tokenStarted = true;
|
|
340
|
+
token += char;
|
|
341
|
+
}
|
|
342
|
+
if (quote)
|
|
343
|
+
return parseFailure('invalid-quote', input, input.length, argv.length, 'cmd');
|
|
344
|
+
finishToken();
|
|
345
|
+
return argv.length > 0
|
|
346
|
+
? {
|
|
347
|
+
ok: true,
|
|
348
|
+
argv: options.allowManagedTmpDir
|
|
349
|
+
? argv.map(value => restoreManagedTmpDirToken(value, options.managedTempDir))
|
|
350
|
+
: argv,
|
|
351
|
+
}
|
|
352
|
+
: parseFailure('empty-command', input, input.length, argv.length, 'cmd');
|
|
353
|
+
}
|
|
354
|
+
function isCodexCmdExecutable(value) {
|
|
355
|
+
if (!value)
|
|
356
|
+
return false;
|
|
357
|
+
const normalized = value.toLowerCase();
|
|
358
|
+
return CODEX_CMD_EXECUTABLES.has(normalized) || CODEX_CMD_INSTALL_PATH_RE.test(value);
|
|
359
|
+
}
|
|
360
|
+
function resolveCodexCmdCarrierArgv(argv) {
|
|
361
|
+
if (argv.length < 3 || !isCodexCmdExecutable(argv[0]))
|
|
362
|
+
return undefined;
|
|
363
|
+
const executeIndex = argv.slice(1, -1).findIndex(value => value.toLowerCase() === '/c');
|
|
364
|
+
if (executeIndex < 0)
|
|
365
|
+
return undefined;
|
|
366
|
+
const absoluteExecuteIndex = executeIndex + 1;
|
|
367
|
+
// The command must remain one argv item. Joining multiple items would lose
|
|
368
|
+
// the quoting boundary that the Windows launcher actually executed.
|
|
369
|
+
if (absoluteExecuteIndex !== argv.length - 2)
|
|
370
|
+
return undefined;
|
|
371
|
+
const seen = new Set();
|
|
372
|
+
for (const rawSwitch of argv.slice(1, absoluteExecuteIndex)) {
|
|
373
|
+
const switchValue = rawSwitch.toLowerCase();
|
|
374
|
+
if (!CODEX_CMD_INERT_SWITCHES.has(switchValue) || seen.has(switchValue))
|
|
375
|
+
return undefined;
|
|
376
|
+
seen.add(switchValue);
|
|
377
|
+
}
|
|
378
|
+
const command = argv.at(-1);
|
|
379
|
+
return typeof command === 'string' && command.trim()
|
|
380
|
+
? { command, dialect: 'cmd' }
|
|
381
|
+
: undefined;
|
|
382
|
+
}
|
|
383
|
+
/** Resolve a Windows cmd carrier when the app-server provides only a string. */
|
|
384
|
+
function resolveCodexCmdCarrierString(command) {
|
|
385
|
+
const input = trimShellSpacing(command);
|
|
386
|
+
// The executable may be quoted when the app-server serializes a Windows
|
|
387
|
+
// path. Parse the prefix into argv-like switches, then use the same
|
|
388
|
+
// validation as the commandArgv path so both representations agree.
|
|
389
|
+
const match = input.match(/^(?:"([^"]+)"|(\S+))((?:\s+\/[^\s]+)*)\s+(.+)$/i);
|
|
390
|
+
if (!match)
|
|
391
|
+
return undefined;
|
|
392
|
+
const executable = match[1] ?? match[2];
|
|
393
|
+
const rawSwitches = match[3].trim();
|
|
394
|
+
const switches = rawSwitches ? rawSwitches.split(/[ \t]+/) : [];
|
|
395
|
+
const argv = [executable, ...switches, match[4]];
|
|
396
|
+
return resolveCodexCmdCarrierArgv(argv);
|
|
397
|
+
}
|
|
398
|
+
/** Resolve a PowerShell carrier when the app-server provides only a string. */
|
|
399
|
+
function resolveCodexPowerShellCarrierString(command) {
|
|
400
|
+
const input = trimShellSpacing(command);
|
|
401
|
+
const match = input.match(/^(?:"([^"]+)"|(\S+))\s+-command\s+(.+)$/i);
|
|
402
|
+
if (!match)
|
|
403
|
+
return undefined;
|
|
404
|
+
const executable = match[1] ?? match[2];
|
|
405
|
+
const normalized = executable.toLowerCase();
|
|
406
|
+
const isPowerShell = CODEX_POWERSHELL_EXECUTABLES.has(normalized)
|
|
407
|
+
|| CODEX_POWERSHELL_INSTALL_PATH_RE.test(executable);
|
|
408
|
+
if (!isPowerShell)
|
|
409
|
+
return undefined;
|
|
410
|
+
// A string-form carrier has one additional command-line quoting layer. The
|
|
411
|
+
// command argument is commonly serialized as `"ec ..."`; remove exactly
|
|
412
|
+
// that outer pair and leave quotes belonging to EC arguments intact.
|
|
413
|
+
let nested = match[3].trim();
|
|
414
|
+
if (nested.length >= 2
|
|
415
|
+
&& ((nested.startsWith('"') && nested.endsWith('"'))
|
|
416
|
+
|| (nested.startsWith("'") && nested.endsWith("'")))) {
|
|
417
|
+
nested = nested.slice(1, -1);
|
|
418
|
+
}
|
|
419
|
+
return nested ? { command: nested, dialect: 'powershell' } : undefined;
|
|
420
|
+
}
|
|
421
|
+
export function resolveCodexShellCarrierString(command) {
|
|
422
|
+
return resolveCodexCmdCarrierString(command)
|
|
423
|
+
?? resolveCodexPowerShellCarrierString(command);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Extract a trusted cmd executable's command body without trusting its startup
|
|
427
|
+
* switches. This is used only to classify malformed EC carriers as a
|
|
428
|
+
* controlled denial; a command with a non-EC body remains an ordinary shell
|
|
429
|
+
* command and follows the normal approval path.
|
|
430
|
+
*/
|
|
431
|
+
function extractCodexCmdCarrierBody(command) {
|
|
432
|
+
const input = trimShellSpacing(command);
|
|
433
|
+
const match = input.match(/^(?:"([^"]+)"|(\S+))((?:\s+\/[^\s]+)*)\s+(.+)$/i);
|
|
434
|
+
if (!match)
|
|
435
|
+
return undefined;
|
|
436
|
+
const executable = match[1] ?? match[2];
|
|
437
|
+
if (!isCodexCmdExecutable(executable))
|
|
438
|
+
return undefined;
|
|
439
|
+
const body = match[4].trim();
|
|
440
|
+
return body || undefined;
|
|
441
|
+
}
|
|
442
|
+
function extractCodexCmdCarrierBodyArgv(argv) {
|
|
443
|
+
if (!argv.length || !isCodexCmdExecutable(argv[0]))
|
|
444
|
+
return undefined;
|
|
445
|
+
let bodyIndex = 1;
|
|
446
|
+
for (; bodyIndex < argv.length; bodyIndex++) {
|
|
447
|
+
const value = argv[bodyIndex];
|
|
448
|
+
if (value.toLowerCase() === '/c') {
|
|
449
|
+
bodyIndex++;
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
// This is deliberately loose. It is only used to identify a malformed
|
|
453
|
+
// carrier that should be denied, never to authorize a command.
|
|
454
|
+
if (!value.startsWith('/'))
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
const body = argv.slice(bodyIndex).join(' ').trim();
|
|
458
|
+
return body || undefined;
|
|
459
|
+
}
|
|
460
|
+
function looksLikeEcCommandBody(command) {
|
|
461
|
+
if (!command)
|
|
462
|
+
return false;
|
|
463
|
+
const input = command.trim();
|
|
464
|
+
const body = (input.startsWith('"') ? input.slice(1) : input).trimStart();
|
|
465
|
+
return /^ec(?:\s|$)/i.test(body);
|
|
466
|
+
}
|
|
467
|
+
/** True only when a trusted cmd executable is carrying an EC command body. */
|
|
468
|
+
export function hasCodexCmdCarrierEcIntent(input) {
|
|
469
|
+
if (Array.isArray(input.commandArgv)
|
|
470
|
+
&& input.commandArgv.every(value => typeof value === 'string')) {
|
|
471
|
+
return looksLikeEcCommandBody(extractCodexCmdCarrierBodyArgv(input.commandArgv));
|
|
472
|
+
}
|
|
473
|
+
return typeof input.command === 'string'
|
|
474
|
+
&& looksLikeEcCommandBody(extractCodexCmdCarrierBody(input.command));
|
|
475
|
+
}
|
|
476
|
+
function parseCodexCmdCarrierArgvFailure(argv) {
|
|
477
|
+
const body = extractCodexCmdCarrierBodyArgv(argv);
|
|
478
|
+
if (!body || !looksLikeEcCommandBody(body))
|
|
479
|
+
return undefined;
|
|
480
|
+
const nested = parseLiteralShellCommand(body, { dialect: 'cmd', allowManagedTmpDir: true });
|
|
481
|
+
if (!nested.ok)
|
|
482
|
+
return nested;
|
|
483
|
+
return parseFailure('shell-composition', body, 0, 0, 'cmd');
|
|
484
|
+
}
|
|
485
|
+
function parseCodexCmdCarrierStringFailure(command, options = {}) {
|
|
486
|
+
const body = extractCodexCmdCarrierBody(command);
|
|
487
|
+
if (!body || !looksLikeEcCommandBody(body))
|
|
488
|
+
return undefined;
|
|
489
|
+
const nested = parseLiteralShellCommand(body, {
|
|
490
|
+
...options,
|
|
491
|
+
dialect: 'cmd',
|
|
492
|
+
});
|
|
493
|
+
return nested.ok
|
|
494
|
+
? parseFailure('shell-composition', body, 0, 0, 'cmd')
|
|
495
|
+
: nested;
|
|
192
496
|
}
|
|
193
497
|
function doubleQuotedIssueAt(input, index) {
|
|
194
498
|
const char = input[index];
|
|
@@ -212,13 +516,16 @@ export function parseLiteralShellCommand(command, options = {}) {
|
|
|
212
516
|
if (options.dialect === 'powershell') {
|
|
213
517
|
return parseLiteralPowerShellCommand(command);
|
|
214
518
|
}
|
|
519
|
+
if (options.dialect === 'cmd') {
|
|
520
|
+
return parseLiteralCmdCommand(command, options);
|
|
521
|
+
}
|
|
215
522
|
const input = trimShellSpacing(options.allowManagedTmpDir
|
|
216
523
|
? substituteManagedTmpDirToken(command)
|
|
217
524
|
: command);
|
|
218
525
|
if (!input)
|
|
219
|
-
return
|
|
526
|
+
return parseFailure('empty-command', input, 0, 0, 'posix');
|
|
220
527
|
if (input.includes('\0'))
|
|
221
|
-
return
|
|
528
|
+
return parseFailure('invalid-control-char', input, input.indexOf('\0'), 0, 'posix');
|
|
222
529
|
const argv = [];
|
|
223
530
|
let token = '';
|
|
224
531
|
let tokenStarted = false;
|
|
@@ -235,7 +542,7 @@ export function parseLiteralShellCommand(command, options = {}) {
|
|
|
235
542
|
if (quote === 'single') {
|
|
236
543
|
if (char === "'") {
|
|
237
544
|
if (!isLiteralArgBoundary(input[index + 1]))
|
|
238
|
-
return
|
|
545
|
+
return parseFailure('invalid-quote', input, index, argv.length, 'posix');
|
|
239
546
|
quote = null;
|
|
240
547
|
}
|
|
241
548
|
else
|
|
@@ -245,13 +552,13 @@ export function parseLiteralShellCommand(command, options = {}) {
|
|
|
245
552
|
if (quote === 'double') {
|
|
246
553
|
if (char === '"') {
|
|
247
554
|
if (!isLiteralArgBoundary(input[index + 1]))
|
|
248
|
-
return
|
|
555
|
+
return parseFailure('invalid-quote', input, index, argv.length, 'posix');
|
|
249
556
|
quote = null;
|
|
250
557
|
continue;
|
|
251
558
|
}
|
|
252
559
|
const issue = doubleQuotedIssueAt(input, index);
|
|
253
560
|
if (issue)
|
|
254
|
-
return
|
|
561
|
+
return parseFailure(issue, input, index, argv.length, 'posix');
|
|
255
562
|
if (char === '\\') {
|
|
256
563
|
const next = input[++index];
|
|
257
564
|
token += ['"', '\\', '$', '`'].includes(next) ? next : `\\${next}`;
|
|
@@ -271,9 +578,9 @@ export function parseLiteralShellCommand(command, options = {}) {
|
|
|
271
578
|
// keeps inside the executable name; accepting those would misclassify a
|
|
272
579
|
// different executable as `ec`.
|
|
273
580
|
if (char === '\r' || char === '\n')
|
|
274
|
-
return
|
|
581
|
+
return parseFailure('structural-newline', input, index, argv.length, 'posix');
|
|
275
582
|
if (/\s/u.test(char) || char.charCodeAt(0) < 0x20 || char === '\x7f') {
|
|
276
|
-
return
|
|
583
|
+
return parseFailure('invalid-control-char', input, index, argv.length, 'posix');
|
|
277
584
|
}
|
|
278
585
|
if (char === "'") {
|
|
279
586
|
tokenStarted = true;
|
|
@@ -288,9 +595,9 @@ export function parseLiteralShellCommand(command, options = {}) {
|
|
|
288
595
|
if (char === '\\') {
|
|
289
596
|
const next = input[++index];
|
|
290
597
|
if (next === undefined)
|
|
291
|
-
return
|
|
598
|
+
return parseFailure('invalid-quote', input, index, argv.length, 'posix');
|
|
292
599
|
if (next === '\r' || next === '\n')
|
|
293
|
-
return
|
|
600
|
+
return parseFailure('structural-newline', input, index, argv.length, 'posix');
|
|
294
601
|
tokenStarted = true;
|
|
295
602
|
token += next;
|
|
296
603
|
continue;
|
|
@@ -299,12 +606,12 @@ export function parseLiteralShellCommand(command, options = {}) {
|
|
|
299
606
|
// peer keys such as `aun#alice` are ordinary literal arguments.
|
|
300
607
|
if ((SHELL_UNQUOTED_META.has(char) && (char !== '#' || !tokenStarted))
|
|
301
608
|
|| (char === '~' && !tokenStarted))
|
|
302
|
-
return
|
|
609
|
+
return parseFailure('shell-composition', input, index, argv.length, 'posix');
|
|
303
610
|
tokenStarted = true;
|
|
304
611
|
token += char;
|
|
305
612
|
}
|
|
306
613
|
if (quote)
|
|
307
|
-
return
|
|
614
|
+
return parseFailure('invalid-quote', input, input.length, argv.length, 'posix');
|
|
308
615
|
finishToken();
|
|
309
616
|
return argv.length > 0
|
|
310
617
|
? {
|
|
@@ -313,24 +620,28 @@ export function parseLiteralShellCommand(command, options = {}) {
|
|
|
313
620
|
? argv.map(value => restoreManagedTmpDirToken(value, options.managedTempDir))
|
|
314
621
|
: argv,
|
|
315
622
|
}
|
|
316
|
-
:
|
|
623
|
+
: parseFailure('empty-command', input, input.length, argv.length, 'posix');
|
|
317
624
|
}
|
|
318
625
|
export function parseLiteralShellArgv(command, options = {}) {
|
|
319
626
|
const result = parseLiteralShellCommand(command, options);
|
|
320
627
|
return result.ok ? result.argv : null;
|
|
321
628
|
}
|
|
322
629
|
export function resolveCodexShellCarrierArgv(argv) {
|
|
323
|
-
if (argv.length
|
|
324
|
-
return undefined;
|
|
325
|
-
if (argv[1] === '-lc' && CODEX_POSIX_SHELL_EXECUTABLES.has(argv[0])) {
|
|
630
|
+
if (argv.length === 3 && argv[1] === '-lc' && CODEX_POSIX_SHELL_EXECUTABLES.has(argv[0])) {
|
|
326
631
|
return { command: argv[2], dialect: 'posix' };
|
|
327
632
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
633
|
+
if (argv.length === 3) {
|
|
634
|
+
const powershellExecutable = argv[0].toLowerCase();
|
|
635
|
+
const isPowerShell = CODEX_POWERSHELL_EXECUTABLES.has(powershellExecutable)
|
|
636
|
+
|| CODEX_POWERSHELL_INSTALL_PATH_RE.test(argv[0]);
|
|
637
|
+
if (isPowerShell && argv[1].toLowerCase() === '-command') {
|
|
638
|
+
return { command: argv[2], dialect: 'powershell' };
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// Node/cross-spawn and Codex use cmd.exe with a small set of startup flags.
|
|
642
|
+
// The execute switch must be the final switch before one command argument;
|
|
643
|
+
// validation itself is shared with string-only Windows payloads below.
|
|
644
|
+
return resolveCodexCmdCarrierArgv(argv);
|
|
334
645
|
}
|
|
335
646
|
export function unwrapCodexShellCommandArgv(argv) {
|
|
336
647
|
return resolveCodexShellCarrierArgv(argv)?.command;
|
|
@@ -342,31 +653,81 @@ export function unwrapCodexShellCommandArgv(argv) {
|
|
|
342
653
|
*/
|
|
343
654
|
export function parseCodexShellCommandArgv(command, options = {}) {
|
|
344
655
|
const outer = parseLiteralShellArgv(command, options);
|
|
345
|
-
if (
|
|
656
|
+
if (outer) {
|
|
657
|
+
const carrier = resolveCodexShellCarrierArgv(outer);
|
|
658
|
+
if (carrier !== undefined) {
|
|
659
|
+
return parseLiteralShellArgv(carrier.command, { ...options, dialect: carrier.dialect });
|
|
660
|
+
}
|
|
661
|
+
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
662
|
+
if (stringCarrier) {
|
|
663
|
+
return parseLiteralShellArgv(stringCarrier.command, { ...options, dialect: stringCarrier.dialect });
|
|
664
|
+
}
|
|
665
|
+
if (parseCodexCmdCarrierStringFailure(command, options))
|
|
666
|
+
return null;
|
|
667
|
+
return outer;
|
|
668
|
+
}
|
|
669
|
+
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
670
|
+
if (!stringCarrier)
|
|
346
671
|
return null;
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
672
|
+
const parsed = parseLiteralShellCommand(stringCarrier.command, {
|
|
673
|
+
...options,
|
|
674
|
+
dialect: stringCarrier.dialect,
|
|
675
|
+
});
|
|
676
|
+
return parsed.ok ? parsed.argv : null;
|
|
351
677
|
}
|
|
352
|
-
/**
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (Array.isArray(input.commandArgv) && input.commandArgv.every(value => typeof value === 'string')) {
|
|
678
|
+
/** Parse a Codex tool input while preserving safe syntax failure metadata. */
|
|
679
|
+
export function parseCodexToolCommand(input) {
|
|
680
|
+
if (Array.isArray(input.commandArgv)) {
|
|
681
|
+
if (!input.commandArgv.every(value => typeof value === 'string')) {
|
|
682
|
+
return parseFailure('invalid-control-char', '', 0, 0, 'posix');
|
|
683
|
+
}
|
|
359
684
|
const outer = input.commandArgv;
|
|
685
|
+
const nulIndex = outer.findIndex(value => value.includes('\0'));
|
|
686
|
+
if (nulIndex >= 0)
|
|
687
|
+
return parseFailure('invalid-control-char', '', 0, nulIndex, 'posix');
|
|
360
688
|
const carrier = resolveCodexShellCarrierArgv(outer);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
689
|
+
if (carrier === undefined) {
|
|
690
|
+
return parseCodexCmdCarrierArgvFailure(outer) ?? { ok: true, argv: outer };
|
|
691
|
+
}
|
|
692
|
+
return parseLiteralShellCommand(carrier.command, {
|
|
693
|
+
allowManagedTmpDir: true,
|
|
694
|
+
dialect: carrier.dialect,
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
const command = typeof input.command === 'string' ? input.command : '';
|
|
698
|
+
const outer = parseLiteralShellCommand(command, { allowManagedTmpDir: true });
|
|
699
|
+
if (outer.ok) {
|
|
700
|
+
const carrier = resolveCodexShellCarrierArgv(outer.argv);
|
|
701
|
+
if (carrier !== undefined) {
|
|
702
|
+
return parseLiteralShellCommand(carrier.command, {
|
|
364
703
|
allowManagedTmpDir: true,
|
|
365
704
|
dialect: carrier.dialect,
|
|
366
705
|
});
|
|
706
|
+
}
|
|
707
|
+
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
708
|
+
return stringCarrier
|
|
709
|
+
? parseLiteralShellCommand(stringCarrier.command, {
|
|
710
|
+
allowManagedTmpDir: true,
|
|
711
|
+
dialect: stringCarrier.dialect,
|
|
712
|
+
})
|
|
713
|
+
: parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true }) ?? outer;
|
|
367
714
|
}
|
|
368
|
-
const
|
|
369
|
-
return
|
|
715
|
+
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
716
|
+
return stringCarrier
|
|
717
|
+
? parseLiteralShellCommand(stringCarrier.command, {
|
|
718
|
+
allowManagedTmpDir: true,
|
|
719
|
+
dialect: stringCarrier.dialect,
|
|
720
|
+
})
|
|
721
|
+
: parseCodexCmdCarrierStringFailure(command, { allowManagedTmpDir: true }) ?? outer;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Resolve a Codex command input while preserving the raw `commandArgv` form
|
|
725
|
+
* supplied by app-server. Joining that argv with spaces loses the quoting of
|
|
726
|
+
* the inner `-lc` command and was the source of a false proactive denial.
|
|
727
|
+
*/
|
|
728
|
+
export function parseCodexToolCommandArgv(input) {
|
|
729
|
+
const result = parseCodexToolCommand(input);
|
|
730
|
+
return result.ok ? result.argv : null;
|
|
370
731
|
}
|
|
371
732
|
function isSimpleExecStringLiteral(value) {
|
|
372
733
|
const quote = value[0];
|
|
@@ -1059,19 +1420,67 @@ export function classifyEvolcoreShellCommand(command, options = {}) {
|
|
|
1059
1420
|
});
|
|
1060
1421
|
if (nested.ok && nested.argv[0] === 'ec')
|
|
1061
1422
|
return { kind: 'literal' };
|
|
1062
|
-
if (!nested.ok &&
|
|
1423
|
+
if (!nested.ok && hasCarrierEcCommandPrefix(carrier.command, carrier.dialect)) {
|
|
1063
1424
|
return { kind: 'composite', issue: nested.issue };
|
|
1064
1425
|
}
|
|
1065
1426
|
}
|
|
1427
|
+
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
1428
|
+
if (stringCarrier) {
|
|
1429
|
+
const nested = parseLiteralShellCommand(stringCarrier.command, {
|
|
1430
|
+
...parseOptions,
|
|
1431
|
+
dialect: stringCarrier.dialect,
|
|
1432
|
+
});
|
|
1433
|
+
if (nested.ok && nested.argv[0] === 'ec')
|
|
1434
|
+
return { kind: 'literal' };
|
|
1435
|
+
if (!nested.ok && hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)) {
|
|
1436
|
+
return { kind: 'composite', issue: nested.issue };
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
const malformedCmdBody = extractCodexCmdCarrierBody(command);
|
|
1440
|
+
if (malformedCmdBody && looksLikeEcCommandBody(malformedCmdBody)) {
|
|
1441
|
+
const nested = parseLiteralShellCommand(malformedCmdBody, {
|
|
1442
|
+
...parseOptions,
|
|
1443
|
+
dialect: 'cmd',
|
|
1444
|
+
});
|
|
1445
|
+
return nested.ok
|
|
1446
|
+
? { kind: 'composite', issue: 'shell-composition' }
|
|
1447
|
+
: { kind: 'composite', issue: nested.issue };
|
|
1448
|
+
}
|
|
1066
1449
|
return { kind: 'none' };
|
|
1067
1450
|
}
|
|
1068
|
-
const
|
|
1451
|
+
const stringCarrier = resolveCodexShellCarrierString(command);
|
|
1452
|
+
if (stringCarrier) {
|
|
1453
|
+
const nested = parseLiteralShellCommand(stringCarrier.command, {
|
|
1454
|
+
...parseOptions,
|
|
1455
|
+
dialect: stringCarrier.dialect,
|
|
1456
|
+
});
|
|
1457
|
+
if (nested.ok && nested.argv[0] === 'ec')
|
|
1458
|
+
return { kind: 'literal' };
|
|
1459
|
+
if (!nested.ok && hasCarrierEcCommandPrefix(stringCarrier.command, stringCarrier.dialect)) {
|
|
1460
|
+
return { kind: 'composite', issue: nested.issue };
|
|
1461
|
+
}
|
|
1462
|
+
return { kind: 'none' };
|
|
1463
|
+
}
|
|
1464
|
+
const malformedCmdBody = extractCodexCmdCarrierBody(command);
|
|
1465
|
+
if (malformedCmdBody && looksLikeEcCommandBody(malformedCmdBody)) {
|
|
1466
|
+
const nested = parseLiteralShellCommand(malformedCmdBody, {
|
|
1467
|
+
...parseOptions,
|
|
1468
|
+
dialect: 'cmd',
|
|
1469
|
+
});
|
|
1470
|
+
return nested.ok
|
|
1471
|
+
? { kind: 'composite', issue: 'shell-composition' }
|
|
1472
|
+
: { kind: 'composite', issue: nested.issue };
|
|
1473
|
+
}
|
|
1474
|
+
const boundedOutput = options.dialect === 'powershell' || options.dialect === 'cmd'
|
|
1069
1475
|
? null
|
|
1070
1476
|
: parseBoundedOutputShellCommand(command);
|
|
1071
1477
|
if (boundedOutput && boundedOutput.argv[0] === 'ec') {
|
|
1072
1478
|
return { kind: 'bounded-output', command: boundedOutput };
|
|
1073
1479
|
}
|
|
1074
|
-
|
|
1480
|
+
// Only a command-boundary prefix is enough to preserve a useful syntax
|
|
1481
|
+
// error for a malformed direct EC invocation. Never scan for EC tokens in
|
|
1482
|
+
// arbitrary raw text after parsing fails.
|
|
1483
|
+
return hasEcCommandPrefix(command)
|
|
1075
1484
|
? { kind: 'composite', issue: literal.issue }
|
|
1076
1485
|
: { kind: 'none' };
|
|
1077
1486
|
}
|