@qvac/harness 0.3.3 → 0.5.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.
- package/CHANGELOG.md +700 -126
- package/README.md +156 -0
- package/dist/harness.bundle +71 -103
- package/dist/prebuilds-manifest.json +0 -8
- package/dist-lib/index.d.ts +42 -40
- package/dist-lib/index.js +307 -117
- package/dist-lib/lib/audio-transcode-bare.d.ts +2 -2
- package/dist-lib/lib/audio-transcode-bare.js +28 -18
- package/dist-lib/lib/audio-transcode-node.d.ts +2 -2
- package/dist-lib/lib/capabilities/audio-resample.d.ts +7 -6
- package/dist-lib/lib/capabilities/audio-resample.js +30 -18
- package/dist-lib/lib/capabilities/completion.d.ts +20 -19
- package/dist-lib/lib/capabilities/completion.js +194 -135
- package/dist-lib/lib/capabilities/download.d.ts +6 -6
- package/dist-lib/lib/capabilities/download.js +15 -8
- package/dist-lib/lib/capabilities/embed.js +2 -1
- package/dist-lib/lib/capabilities/enhance.d.ts +6 -1
- package/dist-lib/lib/capabilities/enhance.js +7 -4
- package/dist-lib/lib/capabilities/invoke.js +7 -4
- package/dist-lib/lib/capabilities/kv-cache.js +2 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +69 -0
- package/dist-lib/lib/capabilities/preview-model-source.js +439 -0
- package/dist-lib/lib/capabilities/speech-text.js +5 -4
- package/dist-lib/lib/capabilities/tool-selection.js +10 -9
- package/dist-lib/lib/capabilities/tool-turns.js +30 -16
- package/dist-lib/lib/capabilities/usage-breakdown.js +2 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +22 -12
- package/dist-lib/lib/connect.d.ts +15 -37
- package/dist-lib/lib/connect.js +161 -112
- package/dist-lib/lib/context-window.js +25 -21
- package/dist-lib/lib/contract-types.d.ts +26 -20
- package/dist-lib/lib/diagram-repair.d.ts +4 -4
- package/dist-lib/lib/diagram-repair.js +6 -3
- package/dist-lib/lib/docx/index.d.ts +3 -2
- package/dist-lib/lib/{error-fields.d.ts → guards.d.ts} +1 -0
- package/dist-lib/lib/{error-fields.js → guards.js} +3 -0
- package/dist-lib/lib/hardware/capability-probe.js +20 -10
- package/dist-lib/lib/hardware/gpu-classify.js +6 -3
- package/dist-lib/lib/hardware/gpu-probe-addon.js +8 -4
- package/dist-lib/lib/hardware/gpu-probe.js +4 -2
- package/dist-lib/lib/hardware/gpu-stats.native.cjs +8 -9
- package/dist-lib/lib/hardware/parsers.js +8 -4
- package/dist-lib/lib/helpers/buffer-burst.js +4 -2
- package/dist-lib/lib/helpers/control-tokens.js +4 -2
- package/dist-lib/lib/helpers/join-guard.d.ts +2 -2
- package/dist-lib/lib/helpers/join-guard.js +4 -2
- package/dist-lib/lib/helpers/load-stall.js +4 -2
- package/dist-lib/lib/helpers/markup-stream-filter.d.ts +2 -2
- package/dist-lib/lib/helpers/markup-stream-filter.js +6 -3
- package/dist-lib/lib/helpers/race-abort.js +2 -1
- package/dist-lib/lib/helpers/race-timeout.d.ts +5 -0
- package/dist-lib/lib/helpers/race-timeout.js +14 -0
- package/dist-lib/lib/helpers/run-outcome.d.ts +6 -8
- package/dist-lib/lib/helpers/run-outcome.js +36 -20
- package/dist-lib/lib/helpers/spawn-path.js +6 -3
- package/dist-lib/lib/helpers/text-coalescer.js +6 -3
- package/dist-lib/lib/helpers/thinking.js +38 -45
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +9 -7
- package/dist-lib/lib/helpers/tool-call-markup.js +36 -20
- package/dist-lib/lib/helpers/tool-intent.d.ts +3 -2
- package/dist-lib/lib/helpers/tool-intent.js +4 -2
- package/dist-lib/lib/helpers/tool-label.js +18 -9
- package/dist-lib/lib/hooks/built-in/arg-repair.js +23 -13
- package/dist-lib/lib/hooks/built-in/egress.js +2 -1
- package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
- package/dist-lib/lib/hooks/pipeline.d.ts +5 -4
- package/dist-lib/lib/hooks/pipeline.js +6 -3
- package/dist-lib/lib/hooks/run.js +12 -8
- package/dist-lib/lib/model-download-progress.js +10 -8
- package/dist-lib/lib/models/context.d.ts +1 -1
- package/dist-lib/lib/models/context.js +5 -1
- package/dist-lib/lib/models/custom-model-cache.d.ts +13 -0
- package/dist-lib/lib/models/custom-model-cache.js +70 -0
- package/dist-lib/lib/models/gguf-header.d.ts +31 -0
- package/dist-lib/lib/models/gguf-header.js +230 -0
- package/dist-lib/lib/models/idle-unload.d.ts +14 -0
- package/dist-lib/lib/models/idle-unload.js +51 -0
- package/dist-lib/lib/models/kv-cache.d.ts +3 -2
- package/dist-lib/lib/models/kv-cache.js +4 -3
- package/dist-lib/lib/models/llm-presets.d.ts +2 -2
- package/dist-lib/lib/models/llm-presets.js +4 -2
- package/dist-lib/lib/models/model-catalog-constants.js +3 -2
- package/dist-lib/lib/models/model-catalog.d.ts +19 -21
- package/dist-lib/lib/models/model-catalog.js +66 -33
- package/dist-lib/lib/models/model-delete.d.ts +4 -1
- package/dist-lib/lib/models/model-delete.js +30 -15
- package/dist-lib/lib/models/model-handle.js +2 -1
- package/dist-lib/lib/models/model-list.d.ts +14 -3
- package/dist-lib/lib/models/model-list.js +33 -6
- package/dist-lib/lib/models/model-pool.d.ts +44 -10
- package/dist-lib/lib/models/model-pool.js +47 -42
- package/dist-lib/lib/models/model-source.js +11 -6
- package/dist-lib/lib/models/music-catalog.d.ts +2 -2
- package/dist-lib/lib/models/nmt-catalog.d.ts +4 -3
- package/dist-lib/lib/models/nmt-catalog.js +10 -5
- package/dist-lib/lib/models/opencl-cache.d.ts +6 -1
- package/dist-lib/lib/models/opencl-cache.js +5 -3
- package/dist-lib/lib/models/sdk-faults.d.ts +11 -8
- package/dist-lib/lib/models/sdk-faults.js +9 -6
- package/dist-lib/lib/models/shards.d.ts +11 -0
- package/dist-lib/lib/models/shards.js +15 -0
- package/dist-lib/lib/models/tool-dialect.js +10 -5
- package/dist-lib/lib/no-redirect-fetch-bare.d.ts +3 -3
- package/dist-lib/lib/no-redirect-fetch-bare.js +1 -1
- package/dist-lib/lib/no-redirect-fetch-impl.js +6 -3
- package/dist-lib/lib/no-redirect-fetch-node.d.ts +1 -1
- package/dist-lib/lib/no-redirect-fetch-node.js +9 -9
- package/dist-lib/lib/office/index.d.ts +3 -2
- package/dist-lib/lib/office/python.d.ts +3 -2
- package/dist-lib/lib/office/python.js +2 -1
- package/dist-lib/lib/prompt.d.ts +5 -4
- package/dist-lib/lib/prompt.js +8 -4
- package/dist-lib/lib/queue.js +10 -5
- package/dist-lib/lib/resolution-detector.js +21 -11
- package/dist-lib/lib/rpc-session.js +2 -1
- package/dist-lib/lib/sdk-loader.js +2 -1
- package/dist-lib/lib/sdk-storage.js +4 -2
- package/dist-lib/lib/serve.js +128 -68
- package/dist-lib/lib/spawn.js +7 -4
- package/dist-lib/lib/tmp-path.d.ts +7 -6
- package/dist-lib/lib/tool-call-display.d.ts +2 -2
- package/dist-lib/lib/tools/create-documents.js +18 -10
- package/dist-lib/lib/tools/edit-documents.js +23 -13
- package/dist-lib/lib/tools/edit-image.d.ts +2 -2
- package/dist-lib/lib/tools/edit-image.js +60 -36
- package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +10 -10
- package/dist-lib/lib/tools/exec/adapters/python/attachments.d.ts +17 -11
- package/dist-lib/lib/tools/exec/adapters/python/attachments.js +53 -40
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +60 -32
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +33 -22
- package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +58 -29
- package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/loader.js +23 -12
- package/dist-lib/lib/tools/exec/adapters/python/memfs.d.ts +13 -11
- package/dist-lib/lib/tools/exec/adapters/python/memfs.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +3 -1
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.d.ts +6 -4
- package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +20 -10
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +14 -7
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +41 -34
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +316 -166
- package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +9 -8
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +56 -28
- package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +8 -10
- package/dist-lib/lib/tools/exec/adapters/python/wheels.js +22 -11
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +3 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +41 -26
- package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +27 -18
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -2
- package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +14 -13
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +10 -5
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.d.ts +4 -4
- package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.d.ts +6 -1
- package/dist-lib/lib/tools/exec/obsidian/cli-process.js +8 -5
- package/dist-lib/lib/tools/exec/obsidian/cli.d.ts +6 -6
- package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
- package/dist-lib/lib/tools/exec/obsidian/command.d.ts +3 -2
- package/dist-lib/lib/tools/exec/obsidian/command.js +6 -3
- package/dist-lib/lib/tools/exec/obsidian/output.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/route.d.ts +4 -6
- package/dist-lib/lib/tools/exec/obsidian/route.js +17 -11
- package/dist-lib/lib/tools/exec/obsidian/safe-path.js +2 -1
- package/dist-lib/lib/tools/exec/obsidian/vault-create.js +15 -7
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.d.ts +2 -2
- package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +20 -10
- package/dist-lib/lib/tools/exec/obsidian/vault-read.js +20 -13
- package/dist-lib/lib/tools/exec/obsidian/vault-write.js +24 -12
- package/dist-lib/lib/tools/exec/runner.d.ts +14 -14
- package/dist-lib/lib/tools/exec/runner.js +5 -3
- package/dist-lib/lib/tools/exec/scope.js +52 -28
- package/dist-lib/lib/tools/exec/shell-argv.js +31 -18
- package/dist-lib/lib/tools/exec/shell-guidance.d.ts +6 -1
- package/dist-lib/lib/tools/exec/shell-guidance.js +35 -20
- package/dist-lib/lib/tools/exec/video-tools.js +2 -2
- package/dist-lib/lib/tools/exec.d.ts +6 -8
- package/dist-lib/lib/tools/exec.js +113 -54
- package/dist-lib/lib/tools/file-glob.d.ts +4 -5
- package/dist-lib/lib/tools/file-glob.js +16 -11
- package/dist-lib/lib/tools/fs/glob.js +13 -18
- package/dist-lib/lib/tools/fs/remote-protocol.d.ts +10 -10
- package/dist-lib/lib/tools/fs/remote.d.ts +3 -2
- package/dist-lib/lib/tools/fs/remote.js +13 -7
- package/dist-lib/lib/tools/fs/scope.d.ts +8 -3
- package/dist-lib/lib/tools/fs/scope.js +20 -12
- package/dist-lib/lib/tools/fs/walk.js +6 -3
- package/dist-lib/lib/tools/fs/write.d.ts +3 -2
- package/dist-lib/lib/tools/generate-image.d.ts +11 -9
- package/dist-lib/lib/tools/generate-image.js +73 -37
- package/dist-lib/lib/tools/generate-music.d.ts +22 -11
- package/dist-lib/lib/tools/generate-music.js +79 -48
- package/dist-lib/lib/tools/grep.js +66 -41
- package/dist-lib/lib/tools/http-request/encode-fields.js +18 -9
- package/dist-lib/lib/tools/http-request/{utils.js → request.js} +20 -11
- package/dist-lib/lib/tools/http-request/truncate-response.js +57 -46
- package/dist-lib/lib/tools/http-request.d.ts +1 -1
- package/dist-lib/lib/tools/http-request.js +33 -18
- package/dist-lib/lib/tools/mcp/protocol.d.ts +53 -0
- package/dist-lib/lib/tools/mcp/{utils.js → protocol.js} +45 -23
- package/dist-lib/lib/tools/mcp-call.d.ts +1 -1
- package/dist-lib/lib/tools/mcp-call.js +37 -16
- package/dist-lib/lib/tools/operations/builders.js +10 -6
- package/dist-lib/lib/tools/operations/compile.js +20 -10
- package/dist-lib/lib/tools/operations/decoders.js +12 -6
- package/dist-lib/lib/tools/operations/gmail-message.js +3 -2
- package/dist-lib/lib/tools/outbound-guard.d.ts +4 -3
- package/dist-lib/lib/tools/outbound-guard.js +14 -8
- package/dist-lib/lib/tools/private-ip.js +8 -7
- package/dist-lib/lib/tools/rag-search.js +14 -7
- package/dist-lib/lib/tools/read-files.js +23 -20
- package/dist-lib/lib/tools/skill.js +19 -10
- package/dist-lib/lib/tools/skills/auth-resolver.d.ts +11 -18
- package/dist-lib/lib/tools/skills/auth-resolver.js +30 -18
- package/dist-lib/lib/tools/skills/bin-check.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bin-check.js +10 -3
- package/dist-lib/lib/tools/skills/bundled-dir.js +20 -10
- package/dist-lib/lib/tools/skills/bundled-hash.d.ts +3 -2
- package/dist-lib/lib/tools/skills/bundled-hash.js +6 -1
- package/dist-lib/lib/tools/skills/catalog.d.ts +3 -2
- package/dist-lib/lib/tools/skills/catalog.js +33 -28
- package/dist-lib/lib/tools/skills/cli-argv.d.ts +3 -2
- package/dist-lib/lib/tools/skills/cli-argv.js +55 -52
- package/dist-lib/lib/tools/skills/cli-schema-check.js +21 -10
- package/dist-lib/lib/tools/skills/cli-schema-load.js +28 -20
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +6 -6
- package/dist-lib/lib/tools/skills/cli-schema.js +2 -1
- package/dist-lib/lib/tools/skills/credential-check.d.ts +6 -3
- package/dist-lib/lib/tools/skills/credential-check.js +24 -12
- package/dist-lib/lib/tools/skills/credential-grants.js +1 -6
- package/dist-lib/lib/tools/skills/credential-health.d.ts +5 -1
- package/dist-lib/lib/tools/skills/credential-health.js +5 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.js +10 -5
- package/dist-lib/lib/tools/skills/manifest-parse.js +84 -64
- package/dist-lib/lib/tools/skills/mention.js +7 -6
- package/dist-lib/lib/tools/skills/obsidian-readiness.d.ts +2 -2
- package/dist-lib/lib/tools/skills/obsidian-readiness.js +11 -3
- package/dist-lib/lib/tools/skills/overlay-crud.d.ts +6 -4
- package/dist-lib/lib/tools/skills/overlay-crud.js +12 -6
- package/dist-lib/lib/tools/skills/overlay-dir.js +4 -2
- package/dist-lib/lib/tools/skills/overlay-watch.d.ts +3 -2
- package/dist-lib/lib/tools/skills/overlay-watch.js +8 -4
- package/dist-lib/lib/tools/skills/setup-routes.js +1 -3
- package/dist-lib/lib/tools/skills/skill-settings.js +4 -1
- package/dist-lib/lib/tools/skills/skill-state.d.ts +6 -11
- package/dist-lib/lib/tools/skills/skill-state.js +48 -29
- package/dist-lib/lib/tools/skills/tool-grants.js +6 -3
- package/dist-lib/lib/tools/toolsets.js +2 -1
- package/dist-lib/lib/tools/translate.d.ts +4 -5
- package/dist-lib/lib/tools/translate.js +31 -14
- package/dist-lib/lib/tools/url-guard.js +2 -1
- package/dist-lib/lib/tools/weather-lookup/place.js +8 -4
- package/dist-lib/lib/tools/weather-lookup/report.d.ts +7 -1
- package/dist-lib/lib/tools/weather-lookup/report.js +46 -9
- package/dist-lib/lib/tools/weather-lookup.js +18 -12
- package/dist-lib/lib/tools/web-fetch.js +47 -24
- package/dist-lib/lib/tools/web-search.js +8 -3
- package/dist-lib/lib/validating-lookup.d.ts +4 -4
- package/dist-lib/lib/wav-encode.d.ts +1 -1
- package/dist-lib/lib/wav-encode.js +11 -10
- package/dist-lib/lib/web-fetch/embedded-state.js +24 -12
- package/dist-lib/lib/web-fetch/extract.js +4 -2
- package/dist-lib/lib/web-fetch/images.js +8 -4
- package/dist-lib/lib/web-fetch/video.d.ts +2 -2
- package/dist-lib/lib/web-fetch/video.js +8 -4
- package/dist-lib/lib/web-search/engine-registry.d.ts +2 -2
- package/dist-lib/lib/web-search/engines/aol.js +1 -1
- package/dist-lib/lib/web-search/engines/bing.js +1 -1
- package/dist-lib/lib/web-search/engines/brave.js +4 -1
- package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -2
- package/dist-lib/lib/web-search/engines/google.js +7 -3
- package/dist-lib/lib/web-search/engines/wikipedia.js +5 -4
- package/dist-lib/lib/web-search/engines/yahoo.js +1 -1
- package/dist-lib/lib/web-search/fetch-html.d.ts +5 -4
- package/dist-lib/lib/web-search/fetch-html.js +13 -7
- package/dist-lib/lib/web-search/orchestrator.d.ts +9 -5
- package/dist-lib/lib/web-search/orchestrator.js +3 -3
- package/dist-lib/lib/wire.d.ts +3 -2
- package/dist-lib/lib/wire.js +23 -15
- package/dist-lib/schema/baseline.js +10 -5
- package/dist-lib/schema/build.js +2 -1
- package/dist-lib/schema/harness.d.ts +15 -2
- package/dist-lib/schema/harness.js +55 -2
- package/dist-lib/schema/release-tags.d.ts +3 -2
- package/dist-lib/schema/release-tags.js +6 -3
- package/dist-lib/spec/bind.js +6 -0
- package/dist-lib/spec/calls.js +2 -0
- package/dist-lib/spec/hrpc/hrpc.json +26 -0
- package/dist-lib/spec/hrpc/index.js +27 -3
- package/dist-lib/spec/hrpc/messages.js +207 -8
- package/dist-lib/spec/hyperschema/index.js +207 -8
- package/dist-lib/spec/hyperschema/schema.json +165 -1
- package/dist-lib/spec/hyperschema/types.d.ts +42 -1
- package/package.json +13 -14
- package/worker/harness-sidecar.mjs +16 -22
- package/worker/stow-entry.mjs +7 -4
- package/dist-lib/lib/tools/mcp/utils.d.ts +0 -47
- /package/dist-lib/lib/tools/http-request/{utils.d.ts → request.d.ts} +0 -0
|
@@ -19,8 +19,9 @@ function isAsciiWs(c) {
|
|
|
19
19
|
return c === ' ' || c === '\t' || c === '\n' || c === '\r' || c === '\f';
|
|
20
20
|
}
|
|
21
21
|
function skipWs(s, i) {
|
|
22
|
-
while (i < s.length && isAsciiWs(s
|
|
22
|
+
while (i < s.length && isAsciiWs(s.charAt(i))) {
|
|
23
23
|
i++;
|
|
24
|
+
}
|
|
24
25
|
return i;
|
|
25
26
|
}
|
|
26
27
|
function leadingToken(s, tokens) {
|
|
@@ -28,48 +29,60 @@ function leadingToken(s, tokens) {
|
|
|
28
29
|
if (!s.startsWith(t))
|
|
29
30
|
continue;
|
|
30
31
|
const next = s[t.length];
|
|
31
|
-
if (next !== undefined && /[A-Za-z0-9_]/.test(next))
|
|
32
|
+
if (next !== undefined && /[A-Za-z0-9_]/.test(next)) {
|
|
32
33
|
continue;
|
|
34
|
+
}
|
|
33
35
|
return t;
|
|
34
36
|
}
|
|
35
37
|
return null;
|
|
36
38
|
}
|
|
37
39
|
function unquote(payload) {
|
|
38
40
|
const quote = payload[0];
|
|
39
|
-
if (quote !== '"' && quote !== "'")
|
|
41
|
+
if (quote !== '"' && quote !== "'") {
|
|
40
42
|
return payload;
|
|
43
|
+
}
|
|
41
44
|
const body = payload.endsWith(quote) ? payload.slice(1, -1) : payload.slice(1);
|
|
42
45
|
return quote === '"' ? body.replace(/\\"/g, '"') : body;
|
|
43
46
|
}
|
|
44
47
|
function unwrapHeredoc(s) {
|
|
45
48
|
const interp = leadingToken(s, INTERPS);
|
|
46
|
-
if (!interp)
|
|
49
|
+
if (!interp) {
|
|
47
50
|
return null;
|
|
51
|
+
}
|
|
48
52
|
let i = skipWs(s, interp.length);
|
|
49
|
-
if (!s.startsWith('<<', i))
|
|
53
|
+
if (!s.startsWith('<<', i)) {
|
|
50
54
|
return null;
|
|
55
|
+
}
|
|
51
56
|
i += 2;
|
|
52
|
-
if (s[i] === '-')
|
|
57
|
+
if (s[i] === '-') {
|
|
53
58
|
i++;
|
|
59
|
+
}
|
|
54
60
|
i = skipWs(s, i);
|
|
55
|
-
const
|
|
56
|
-
|
|
61
|
+
const open = s.charAt(i);
|
|
62
|
+
const quote = open === '"' || open === "'" ? open : null;
|
|
63
|
+
if (quote) {
|
|
57
64
|
i++;
|
|
65
|
+
}
|
|
58
66
|
const tagStart = i;
|
|
59
|
-
while (i < s.length && /[A-Za-z0-9_]/.test(s
|
|
67
|
+
while (i < s.length && /[A-Za-z0-9_]/.test(s.charAt(i))) {
|
|
60
68
|
i++;
|
|
61
|
-
|
|
69
|
+
}
|
|
70
|
+
if (i === tagStart) {
|
|
62
71
|
return null;
|
|
72
|
+
}
|
|
63
73
|
const tag = s.slice(tagStart, i);
|
|
64
74
|
if (quote) {
|
|
65
|
-
if (s[i] !== quote)
|
|
75
|
+
if (s[i] !== quote) {
|
|
66
76
|
return null;
|
|
77
|
+
}
|
|
67
78
|
i++;
|
|
68
79
|
}
|
|
69
|
-
while (i < s.length && (s[i] === ' ' || s[i] === '\t'))
|
|
80
|
+
while (i < s.length && (s[i] === ' ' || s[i] === '\t')) {
|
|
70
81
|
i++;
|
|
71
|
-
|
|
82
|
+
}
|
|
83
|
+
if (s[i] !== '\n') {
|
|
72
84
|
return null;
|
|
85
|
+
}
|
|
73
86
|
i++;
|
|
74
87
|
const body = s.slice(i);
|
|
75
88
|
for (let j = 0; j < body.length; j++) {
|
|
@@ -77,54 +90,66 @@ function unwrapHeredoc(s) {
|
|
|
77
90
|
continue;
|
|
78
91
|
let k = j + 1;
|
|
79
92
|
const closeInterp = leadingToken(body.slice(k), INTERPS);
|
|
80
|
-
if (closeInterp)
|
|
93
|
+
if (closeInterp) {
|
|
81
94
|
k += closeInterp.length;
|
|
95
|
+
}
|
|
82
96
|
k = skipWs(body, k);
|
|
83
|
-
if (!body.startsWith(tag, k))
|
|
97
|
+
if (!body.startsWith(tag, k)) {
|
|
84
98
|
continue;
|
|
99
|
+
}
|
|
85
100
|
k += tag.length;
|
|
86
101
|
k = skipWs(body, k);
|
|
87
|
-
if (k === body.length)
|
|
102
|
+
if (k === body.length) {
|
|
88
103
|
return body.slice(0, j).trim();
|
|
104
|
+
}
|
|
89
105
|
}
|
|
90
106
|
return body.trim();
|
|
91
107
|
}
|
|
92
108
|
function unwrapDashC(s) {
|
|
93
109
|
const interp = leadingToken(s, INTERPS);
|
|
94
|
-
if (!interp)
|
|
110
|
+
if (!interp) {
|
|
95
111
|
return null;
|
|
112
|
+
}
|
|
96
113
|
let i = skipWs(s, interp.length);
|
|
97
|
-
if (!s.startsWith('-c', i))
|
|
114
|
+
if (!s.startsWith('-c', i)) {
|
|
98
115
|
return null;
|
|
116
|
+
}
|
|
99
117
|
i += 2;
|
|
100
|
-
if (i >= s.length || !isAsciiWs(s
|
|
118
|
+
if (i >= s.length || !isAsciiWs(s.charAt(i))) {
|
|
101
119
|
return null;
|
|
120
|
+
}
|
|
102
121
|
i = skipWs(s, i);
|
|
103
|
-
if (i >= s.length)
|
|
122
|
+
if (i >= s.length) {
|
|
104
123
|
return null;
|
|
124
|
+
}
|
|
105
125
|
const source = unquote(s.slice(i).trim()).trim();
|
|
106
126
|
return source || null;
|
|
107
127
|
}
|
|
108
128
|
function unwrapBareInterpreter(s) {
|
|
109
129
|
const interp = leadingToken(s, INTERPS);
|
|
110
|
-
if (!interp)
|
|
130
|
+
if (!interp) {
|
|
111
131
|
return null;
|
|
132
|
+
}
|
|
112
133
|
let i = interp.length;
|
|
113
|
-
while (i < s.length && (s[i] === ' ' || s[i] === '\t'))
|
|
134
|
+
while (i < s.length && (s[i] === ' ' || s[i] === '\t')) {
|
|
114
135
|
i++;
|
|
115
|
-
|
|
136
|
+
}
|
|
137
|
+
if (s[i] !== '\n') {
|
|
116
138
|
return null;
|
|
139
|
+
}
|
|
117
140
|
const body = s.slice(i + 1).trim();
|
|
118
141
|
return body || null;
|
|
119
142
|
}
|
|
120
143
|
export function unwrapPythonSource(command) {
|
|
121
144
|
const trimmed = withoutProgramDir(command).trim();
|
|
122
145
|
const heredoc = unwrapHeredoc(trimmed);
|
|
123
|
-
if (heredoc !== null)
|
|
146
|
+
if (heredoc !== null) {
|
|
124
147
|
return heredoc;
|
|
148
|
+
}
|
|
125
149
|
const dashC = unwrapDashC(trimmed);
|
|
126
|
-
if (dashC !== null)
|
|
150
|
+
if (dashC !== null) {
|
|
127
151
|
return dashC;
|
|
152
|
+
}
|
|
128
153
|
return unwrapBareInterpreter(trimmed);
|
|
129
154
|
}
|
|
130
155
|
export function withoutProgramDir(command) {
|
|
@@ -139,40 +164,50 @@ function pipInstallRest(command) {
|
|
|
139
164
|
}
|
|
140
165
|
else {
|
|
141
166
|
const interp = leadingToken(s, INTERPS);
|
|
142
|
-
if (!interp)
|
|
167
|
+
if (!interp) {
|
|
143
168
|
return null;
|
|
169
|
+
}
|
|
144
170
|
i = skipWs(s, interp.length);
|
|
145
|
-
if (!s.startsWith('-m', i))
|
|
171
|
+
if (!s.startsWith('-m', i)) {
|
|
146
172
|
return null;
|
|
173
|
+
}
|
|
147
174
|
i += 2;
|
|
148
|
-
if (i >= s.length || !isAsciiWs(s
|
|
175
|
+
if (i >= s.length || !isAsciiWs(s.charAt(i))) {
|
|
149
176
|
return null;
|
|
177
|
+
}
|
|
150
178
|
i = skipWs(s, i);
|
|
151
|
-
if (!s.startsWith('pip', i))
|
|
179
|
+
if (!s.startsWith('pip', i)) {
|
|
152
180
|
return null;
|
|
181
|
+
}
|
|
153
182
|
const after = s[i + 3];
|
|
154
|
-
if (after !== undefined && /[A-Za-z0-9_]/.test(after))
|
|
183
|
+
if (after !== undefined && /[A-Za-z0-9_]/.test(after)) {
|
|
155
184
|
return null;
|
|
185
|
+
}
|
|
156
186
|
i += 3;
|
|
157
187
|
}
|
|
158
|
-
if (i >= s.length || !isAsciiWs(s
|
|
188
|
+
if (i >= s.length || !isAsciiWs(s.charAt(i))) {
|
|
159
189
|
return null;
|
|
190
|
+
}
|
|
160
191
|
i = skipWs(s, i);
|
|
161
|
-
if (!s.startsWith('install', i))
|
|
192
|
+
if (!s.startsWith('install', i)) {
|
|
162
193
|
return null;
|
|
194
|
+
}
|
|
163
195
|
i += 'install'.length;
|
|
164
|
-
if (i >= s.length || !isAsciiWs(s
|
|
196
|
+
if (i >= s.length || !isAsciiWs(s.charAt(i))) {
|
|
165
197
|
return null;
|
|
198
|
+
}
|
|
166
199
|
i = skipWs(s, i);
|
|
167
|
-
if (i >= s.length)
|
|
200
|
+
if (i >= s.length) {
|
|
168
201
|
return null;
|
|
202
|
+
}
|
|
169
203
|
return s.slice(i);
|
|
170
204
|
}
|
|
171
205
|
const REQUIREMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*(\[[A-Za-z0-9,._-]+\])?([=<>!~]=?[^\s]+)?$/;
|
|
172
206
|
export function pipInstallTargets(command) {
|
|
173
207
|
const rest = pipInstallRest(command);
|
|
174
|
-
if (!rest)
|
|
208
|
+
if (!rest) {
|
|
175
209
|
return null;
|
|
210
|
+
}
|
|
176
211
|
const targets = [];
|
|
177
212
|
for (const raw of rest.split(/\s+/)) {
|
|
178
213
|
if (/[|;&<>()`$]/.test(raw))
|
|
@@ -181,22 +216,25 @@ export function pipInstallTargets(command) {
|
|
|
181
216
|
if (/^(-r|--requirement|-e|--editable|-c|--constraint|-f|--find-links)$/.test(token)) {
|
|
182
217
|
return null;
|
|
183
218
|
}
|
|
184
|
-
if (!token || token.startsWith('-'))
|
|
219
|
+
if (!token || token.startsWith('-')) {
|
|
185
220
|
continue;
|
|
186
|
-
|
|
221
|
+
}
|
|
222
|
+
if (!REQUIREMENT.test(token)) {
|
|
187
223
|
return null;
|
|
188
|
-
|
|
224
|
+
}
|
|
225
|
+
if (/^pip3?$/i.test(token.split(/[=<>!~[]/)[0] ?? '')) {
|
|
189
226
|
continue;
|
|
227
|
+
}
|
|
190
228
|
targets.push(token);
|
|
191
229
|
}
|
|
192
230
|
return targets.length > 0 ? targets : null;
|
|
193
231
|
}
|
|
194
232
|
const SANDBOX_NO_INSTALL = 'and sandbox mode installs only the packages bundled with this app, which do not include it. Use only what is already installed, or tell the user it is unavailable while sandbox mode is on.';
|
|
195
|
-
const specName = (spec) => normalizeDistribution(spec.split(/[=<>!~[]/)[0]);
|
|
233
|
+
const specName = (spec) => normalizeDistribution(spec.split(/[=<>!~[]/)[0] ?? '');
|
|
196
234
|
export function pinned(spec) {
|
|
197
235
|
return /[=<>!~]/.test(spec) ? spec : (vendoredSpec(spec) ?? spec);
|
|
198
236
|
}
|
|
199
|
-
export function pipMessage(targets, sandbox = false) {
|
|
237
|
+
export function pipMessage(targets, { sandbox = false } = {}) {
|
|
200
238
|
const specs = targets?.map(pinned) ?? null;
|
|
201
239
|
if (sandbox && !(specs?.length && planVendoredInstall(specs))) {
|
|
202
240
|
return `pip is not available, ${SANDBOX_NO_INSTALL}`;
|
|
@@ -207,24 +245,27 @@ export function pipMessage(targets, sandbox = false) {
|
|
|
207
245
|
const PIP_CALL = /\b(?:subprocess\.\w+|os\.(?:system|popen)|Popen|check_(?:output|call))\s*\(/;
|
|
208
246
|
const STRING_LITERAL = /"([^"\n]*)"|'([^'\n]*)'/g;
|
|
209
247
|
function pipLineTargets(line) {
|
|
210
|
-
const words = [...line.matchAll(STRING_LITERAL)].map((match) =>
|
|
248
|
+
const words = [...line.matchAll(STRING_LITERAL)].map((match) => match[1] ?? match[2] ?? '');
|
|
211
249
|
const whole = words.find((word) => /\bpip3?\b[^]*\binstall\b/.test(word));
|
|
212
|
-
if (whole !== undefined)
|
|
250
|
+
if (whole !== undefined) {
|
|
213
251
|
return pipInstallTargets(whole.trim());
|
|
252
|
+
}
|
|
214
253
|
const at = words.findIndex((word) => /^pip3?$/.test(word));
|
|
215
|
-
if (at === -1)
|
|
254
|
+
if (at === -1) {
|
|
216
255
|
return undefined;
|
|
256
|
+
}
|
|
217
257
|
return pipInstallTargets(words.slice(at).join(' '));
|
|
218
258
|
}
|
|
219
|
-
export function pipFromSourceHint(stderr, source, packages, sandbox = false) {
|
|
259
|
+
export function pipFromSourceHint(stderr, source, { packages, sandbox = false } = {}) {
|
|
220
260
|
if (!/emscripten does not support processes/.test(stderr))
|
|
221
261
|
return null;
|
|
222
262
|
for (const line of source.split('\n')) {
|
|
223
263
|
if (!PIP_CALL.test(line))
|
|
224
264
|
continue;
|
|
225
265
|
const targets = pipLineTargets(line);
|
|
226
|
-
if (targets === undefined)
|
|
266
|
+
if (targets === undefined) {
|
|
227
267
|
continue;
|
|
268
|
+
}
|
|
228
269
|
const declared = packages ?? [];
|
|
229
270
|
const has = new Set(declared.map(specName));
|
|
230
271
|
const missing = (targets ?? []).filter((t) => !has.has(specName(t))).map(pinned);
|
|
@@ -246,12 +287,15 @@ export function pipFromSourceHint(stderr, source, packages, sandbox = false) {
|
|
|
246
287
|
}
|
|
247
288
|
export function stringLiteralProgramHint(source) {
|
|
248
289
|
const first = source.trimStart()[0];
|
|
249
|
-
if (first !== '"' && first !== "'")
|
|
290
|
+
if (first !== '"' && first !== "'") {
|
|
250
291
|
return null;
|
|
251
|
-
|
|
292
|
+
}
|
|
293
|
+
if (!source.trimEnd().endsWith(first)) {
|
|
252
294
|
return null;
|
|
253
|
-
|
|
295
|
+
}
|
|
296
|
+
if (!/\bimport\b/.test(source)) {
|
|
254
297
|
return null;
|
|
298
|
+
}
|
|
255
299
|
return ('command is wrapped in a string literal, so Python evaluates it as text and runs ' +
|
|
256
300
|
'nothing: remove the wrapping quotes (and any escaped \\" quotes) so the first line ' +
|
|
257
301
|
'of command is the first line of the program, e.g. from fpdf import FPDF.');
|
|
@@ -287,8 +331,9 @@ export function mangledPptxApiHint(source) {
|
|
|
287
331
|
found.push(`${bad} → ${good}`);
|
|
288
332
|
}
|
|
289
333
|
}
|
|
290
|
-
if (found.length === 0)
|
|
334
|
+
if (found.length === 0) {
|
|
291
335
|
return null;
|
|
336
|
+
}
|
|
292
337
|
return (`python-pptx names keep their underscores — found stripped forms (${found.join(', ')}). ` +
|
|
293
338
|
'Restore them (text_frame not textframe, add_slide not addslide) and keep the skill ' +
|
|
294
339
|
'sample multi-line with real newlines.');
|
|
@@ -311,8 +356,9 @@ export function fpdfSizeKeywordHint(source) {
|
|
|
311
356
|
if (!/\bfpdf\b|\bFPDF\b/.test(source))
|
|
312
357
|
return null;
|
|
313
358
|
const match = FPDF_SIZE_KEYWORD.exec(source);
|
|
314
|
-
if (!match)
|
|
359
|
+
if (!match) {
|
|
315
360
|
return null;
|
|
361
|
+
}
|
|
316
362
|
return (`fpdf2 ${match[1]}() has no "${match[2]}" keyword — the size parameters are the positional ` +
|
|
317
363
|
'w and h: write every line (headings and body) with pdf.multi_cell(0, 6, text, ' +
|
|
318
364
|
'new_x="LMARGIN", new_y="NEXT"). Rename the arguments and keep new_x/new_y; ' +
|
|
@@ -320,8 +366,9 @@ export function fpdfSizeKeywordHint(source) {
|
|
|
320
366
|
}
|
|
321
367
|
export function fpdfKeywordErrorHint(stderr) {
|
|
322
368
|
const match = /TypeError: FPDF\.(\w+)\(\) got an unexpected keyword argument '(\w+)'/.exec(stderr);
|
|
323
|
-
if (!match)
|
|
369
|
+
if (!match) {
|
|
324
370
|
return null;
|
|
371
|
+
}
|
|
325
372
|
const sized = match[2] === 'width' || match[2] === 'height';
|
|
326
373
|
const fix = sized
|
|
327
374
|
? 'the size parameters are the positional w and h'
|
|
@@ -349,8 +396,9 @@ export function normalizeFpdfSource(source) {
|
|
|
349
396
|
if (!/\bfpdf\b|\bFPDF\b/.test(source))
|
|
350
397
|
return source;
|
|
351
398
|
let out = source;
|
|
352
|
-
for (const [from, to] of LATIN1_SUBSTITUTES)
|
|
399
|
+
for (const [from, to] of LATIN1_SUBSTITUTES) {
|
|
353
400
|
out = out.replace(from, to);
|
|
401
|
+
}
|
|
354
402
|
return out;
|
|
355
403
|
}
|
|
356
404
|
const CURLY_QUOTE = /[\u2018\u2019\u201a\u2032\u201c\u201d\u201e\u2033]/u;
|
|
@@ -376,8 +424,9 @@ export function fpdfNonLatin1Hint(source) {
|
|
|
376
424
|
if (source.includes('.replace('))
|
|
377
425
|
return null;
|
|
378
426
|
const bad = /[^\u0000-\u00ff]/u.exec(normalizeFpdfSource(source))?.[0];
|
|
379
|
-
if (!bad)
|
|
427
|
+
if (!bad) {
|
|
380
428
|
return null;
|
|
429
|
+
}
|
|
381
430
|
if (CURLY_QUOTE.test(bad)) {
|
|
382
431
|
return (`fpdf2 core fonts are latin-1 only and the curly quote ${bad} is outside it — rewrite ` +
|
|
383
432
|
'the strings with straight quotes \' and " and rerun.');
|
|
@@ -389,8 +438,9 @@ export function fpdfNonLatin1Hint(source) {
|
|
|
389
438
|
export function fpdfUnicodeErrorHint(stderr) {
|
|
390
439
|
const encoding = /FPDFUnicodeEncodingException/.test(stderr) ||
|
|
391
440
|
(/UnicodeEncodeError: 'latin-1' codec/.test(stderr) && /fpdf/.test(stderr));
|
|
392
|
-
if (!encoding)
|
|
441
|
+
if (!encoding) {
|
|
393
442
|
return null;
|
|
443
|
+
}
|
|
394
444
|
return ('a character outside latin-1 reached an fpdf2 core font: replace it with ASCII ' +
|
|
395
445
|
'(bullets "- ", straight quotes, plain hyphens). CJK and emoji cannot be rendered — ' +
|
|
396
446
|
'tell the user instead of retrying with the same string.');
|
|
@@ -404,46 +454,58 @@ export function fpdfHorizontalSpaceHint(stderr) {
|
|
|
404
454
|
}
|
|
405
455
|
export function rgbColorImportHint(stderr) {
|
|
406
456
|
const name = /cannot import name '([^']+)' from 'pptx\.dml\.color'/.exec(stderr)?.[1];
|
|
407
|
-
if (!name || name === 'RGBColor')
|
|
457
|
+
if (!name || name === 'RGBColor') {
|
|
408
458
|
return null;
|
|
409
|
-
|
|
459
|
+
}
|
|
460
|
+
if (!/^rgb_?color$/i.test(name)) {
|
|
410
461
|
return null;
|
|
462
|
+
}
|
|
411
463
|
return (`pptx.dml.color exports RGBColor (all-caps RGB), not ${name}: ` +
|
|
412
464
|
'from pptx.dml.color import RGBColor');
|
|
413
465
|
}
|
|
414
466
|
export function checkPythonSource(command) {
|
|
415
467
|
const source = unwrapPythonSource(command) ?? command;
|
|
416
468
|
const literal = stringLiteralProgramHint(source);
|
|
417
|
-
if (literal)
|
|
469
|
+
if (literal) {
|
|
418
470
|
return literal;
|
|
471
|
+
}
|
|
419
472
|
const collapsed = checkCollapsedCompound(source);
|
|
420
|
-
if (collapsed)
|
|
473
|
+
if (collapsed) {
|
|
421
474
|
return collapsed;
|
|
475
|
+
}
|
|
422
476
|
const mangled = mangledPptxApiHint(source);
|
|
423
|
-
if (mangled)
|
|
477
|
+
if (mangled) {
|
|
424
478
|
return mangled;
|
|
479
|
+
}
|
|
425
480
|
const rgb = wrongRgbColorHint(source);
|
|
426
|
-
if (rgb)
|
|
481
|
+
if (rgb) {
|
|
427
482
|
return rgb;
|
|
483
|
+
}
|
|
428
484
|
const fpdfKeyword = fpdfSizeKeywordHint(source);
|
|
429
|
-
if (fpdfKeyword)
|
|
485
|
+
if (fpdfKeyword) {
|
|
430
486
|
return fpdfKeyword;
|
|
487
|
+
}
|
|
431
488
|
const fpdfLatin1 = fpdfNonLatin1Hint(source);
|
|
432
|
-
if (fpdfLatin1)
|
|
489
|
+
if (fpdfLatin1) {
|
|
433
490
|
return fpdfLatin1;
|
|
491
|
+
}
|
|
434
492
|
const fpdfText = fpdfTextCallHint(source);
|
|
435
|
-
if (fpdfText)
|
|
493
|
+
if (fpdfText) {
|
|
436
494
|
return fpdfText;
|
|
437
|
-
|
|
438
|
-
|
|
495
|
+
}
|
|
496
|
+
const first = command.trimStart().split('\n')[0] ?? '';
|
|
497
|
+
if (!SHELL_WRAPPER.test(first)) {
|
|
439
498
|
return null;
|
|
440
|
-
|
|
499
|
+
}
|
|
500
|
+
if (pipInstallTargets(command)) {
|
|
441
501
|
return null;
|
|
502
|
+
}
|
|
442
503
|
if (/^pip3?\b/.test(first) || /^(python3?|py)\s+-m\s+pip\b/.test(first)) {
|
|
443
504
|
return 'language "python" does not run shell commands: install packages via the packages argument (e.g. packages: ["python-pptx==1.0.2"]), not pip';
|
|
444
505
|
}
|
|
445
|
-
if (unwrapPythonSource(command))
|
|
506
|
+
if (unwrapPythonSource(command)) {
|
|
446
507
|
return null;
|
|
508
|
+
}
|
|
447
509
|
return 'language "python" runs command as Python source: its first line is the first line of the program, so command must start with the code itself. There is no shell, no interpreter, and no script file to name.';
|
|
448
510
|
}
|
|
449
511
|
export function syntaxErrorHint(stderr, source) {
|
|
@@ -454,27 +516,36 @@ export function syntaxErrorHint(stderr, source) {
|
|
|
454
516
|
'SyntaxError means the Python source is invalid as written — keep real newlines for for/if/with/def bodies; do not collapse a multi-line program into one semicolon-separated line.');
|
|
455
517
|
}
|
|
456
518
|
export function checkPptxBuildSource(source, outputs, packages) {
|
|
457
|
-
if (!outputs?.some((name) => name.toLowerCase().endsWith('.pptx')))
|
|
519
|
+
if (!outputs?.some((name) => name.toLowerCase().endsWith('.pptx'))) {
|
|
458
520
|
return null;
|
|
521
|
+
}
|
|
459
522
|
const pkgPptx = (packages ?? []).some((p) => {
|
|
460
|
-
const name = p.split(/[=<>!~[]/)[0].trim().toLowerCase();
|
|
523
|
+
const name = (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase();
|
|
461
524
|
return name === 'python-pptx' || name === 'pptx';
|
|
462
525
|
});
|
|
463
526
|
const srcPptx = /\bpptx\b|\bPresentation\s*\(/.test(source);
|
|
464
|
-
if (!pkgPptx && !srcPptx)
|
|
527
|
+
if (!pkgPptx && !srcPptx) {
|
|
465
528
|
return null;
|
|
529
|
+
}
|
|
466
530
|
const builds = /\bPresentation\s*\(/.test(source) && /\.save\s*\(/.test(source);
|
|
467
|
-
if (builds)
|
|
531
|
+
if (builds) {
|
|
468
532
|
return null;
|
|
533
|
+
}
|
|
469
534
|
return ('outputs declare a .pptx but command does not build one: pass multi-line python-pptx ' +
|
|
470
535
|
'source that constructs Presentation(...) and calls save("deck.pptx") matching an ' +
|
|
471
536
|
'outputs entry. Do not use python -c, os.listdir, pip, or a different package pin — ' +
|
|
472
537
|
'language="python" already runs bare source, and packages must stay ["python-pptx==1.0.2"].');
|
|
473
538
|
}
|
|
539
|
+
export function tripleQuoteAt(source, index) {
|
|
540
|
+
if (source.startsWith('"""', index))
|
|
541
|
+
return '"""';
|
|
542
|
+
return source.startsWith("'''", index) ? "'''" : null;
|
|
543
|
+
}
|
|
474
544
|
export function isEscaped(source, index) {
|
|
475
545
|
let slashes = 0;
|
|
476
|
-
while (index - slashes > 0 && source[index - slashes - 1] === '\\')
|
|
546
|
+
while (index - slashes > 0 && source[index - slashes - 1] === '\\') {
|
|
477
547
|
slashes++;
|
|
548
|
+
}
|
|
478
549
|
return slashes % 2 === 1;
|
|
479
550
|
}
|
|
480
551
|
function splitCallArgs(source, open) {
|
|
@@ -483,11 +554,12 @@ function splitCallArgs(source, open) {
|
|
|
483
554
|
let depth = 0;
|
|
484
555
|
let quote = null;
|
|
485
556
|
for (let i = open; i < source.length; i++) {
|
|
486
|
-
const ch = source
|
|
557
|
+
const ch = source.charAt(i);
|
|
487
558
|
if (quote) {
|
|
488
559
|
arg += ch;
|
|
489
|
-
if (ch === quote && !isEscaped(source, i))
|
|
560
|
+
if (ch === quote && !isEscaped(source, i)) {
|
|
490
561
|
quote = null;
|
|
562
|
+
}
|
|
491
563
|
continue;
|
|
492
564
|
}
|
|
493
565
|
if (ch === '"' || ch === "'") {
|
|
@@ -497,8 +569,9 @@ function splitCallArgs(source, open) {
|
|
|
497
569
|
}
|
|
498
570
|
if (ch === '(' || ch === '[' || ch === '{') {
|
|
499
571
|
depth++;
|
|
500
|
-
if (depth > 1)
|
|
572
|
+
if (depth > 1) {
|
|
501
573
|
arg += ch;
|
|
574
|
+
}
|
|
502
575
|
continue;
|
|
503
576
|
}
|
|
504
577
|
if (ch === ')' || ch === ']' || ch === '}') {
|
|
@@ -530,20 +603,22 @@ function stripCommentsAndStrings(source) {
|
|
|
530
603
|
let out = '';
|
|
531
604
|
let i = 0;
|
|
532
605
|
while (i < source.length) {
|
|
533
|
-
const ch = source
|
|
534
|
-
const triple =
|
|
606
|
+
const ch = source.charAt(i);
|
|
607
|
+
const triple = tripleQuoteAt(source, i);
|
|
535
608
|
const quote = triple ?? (ch === '"' || ch === "'" ? ch : null);
|
|
536
609
|
if (quote) {
|
|
537
610
|
i += quote.length;
|
|
538
|
-
while (i < source.length && !(source.startsWith(quote, i) && !isEscaped(source, i)))
|
|
611
|
+
while (i < source.length && !(source.startsWith(quote, i) && !isEscaped(source, i))) {
|
|
539
612
|
i++;
|
|
613
|
+
}
|
|
540
614
|
out += '""';
|
|
541
615
|
i += quote.length;
|
|
542
616
|
continue;
|
|
543
617
|
}
|
|
544
618
|
if (ch === '#') {
|
|
545
|
-
while (i < source.length && source[i] !== '\n')
|
|
619
|
+
while (i < source.length && source[i] !== '\n') {
|
|
546
620
|
i++;
|
|
621
|
+
}
|
|
547
622
|
continue;
|
|
548
623
|
}
|
|
549
624
|
out += ch;
|
|
@@ -564,15 +639,19 @@ const LENGTH_LIBRARY = {
|
|
|
564
639
|
}
|
|
565
640
|
};
|
|
566
641
|
function lengthLibrary(source, packages) {
|
|
567
|
-
const names = (packages ?? []).map((p) => p.split(/[=<>!~[]/)[0].trim().toLowerCase());
|
|
568
|
-
if (names.includes('python-pptx') || names.includes('pptx'))
|
|
642
|
+
const names = (packages ?? []).map((p) => (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase());
|
|
643
|
+
if (names.includes('python-pptx') || names.includes('pptx')) {
|
|
569
644
|
return 'pptx';
|
|
570
|
-
|
|
645
|
+
}
|
|
646
|
+
if (names.includes('python-docx') || names.includes('docx')) {
|
|
571
647
|
return 'docx';
|
|
572
|
-
|
|
648
|
+
}
|
|
649
|
+
if (/\bpptx\b|\bPresentation\s*\(/.test(source)) {
|
|
573
650
|
return 'pptx';
|
|
574
|
-
|
|
651
|
+
}
|
|
652
|
+
if (/\bdocx\b|\bDocument\s*\(/.test(source)) {
|
|
575
653
|
return 'docx';
|
|
654
|
+
}
|
|
576
655
|
return null;
|
|
577
656
|
}
|
|
578
657
|
const LENGTH_KEYWORDS = ['left', 'top', 'width', 'height'];
|
|
@@ -582,22 +661,25 @@ function callLibrary(method, before, fallback) {
|
|
|
582
661
|
return 'pptx';
|
|
583
662
|
if (/\bshapes\s*\.\s*$/.test(before))
|
|
584
663
|
return 'pptx';
|
|
585
|
-
if (/\b(?:doc|document|run|paragraph|cell)\s*\.\s*$/.test(before))
|
|
664
|
+
if (/\b(?:doc|document|run|paragraph|cell)\s*\.\s*$/.test(before)) {
|
|
586
665
|
return 'docx';
|
|
666
|
+
}
|
|
587
667
|
return fallback;
|
|
588
668
|
}
|
|
589
669
|
export function checkBareLengthArgs(source, packages) {
|
|
590
670
|
const clean = stripCommentsAndStrings(source);
|
|
591
671
|
const library = lengthLibrary(clean, packages);
|
|
592
|
-
if (!library)
|
|
672
|
+
if (!library) {
|
|
593
673
|
return null;
|
|
674
|
+
}
|
|
594
675
|
for (const method of ['add_textbox', 'add_picture']) {
|
|
595
676
|
const calls = new RegExp(`\\b${method}\\s*\\(`, 'g');
|
|
596
677
|
let call;
|
|
597
678
|
while ((call = calls.exec(clean)) !== null) {
|
|
598
679
|
const args = splitCallArgs(clean, calls.lastIndex - 1);
|
|
599
|
-
if (!args)
|
|
680
|
+
if (!args) {
|
|
600
681
|
continue;
|
|
682
|
+
}
|
|
601
683
|
const offender = args.find((arg, index) => {
|
|
602
684
|
const eq = arg.indexOf('=');
|
|
603
685
|
const name = eq === -1 ? '' : arg.slice(0, eq).trim();
|
|
@@ -616,7 +698,7 @@ export function checkBareLengthArgs(source, packages) {
|
|
|
616
698
|
const assignments = /\bslide_(?:width|height)\s*=\s*([^\n]+)/g;
|
|
617
699
|
let assigned;
|
|
618
700
|
while ((assigned = assignments.exec(clean)) !== null) {
|
|
619
|
-
const value = assigned[1].trim();
|
|
701
|
+
const value = (assigned[1] ?? '').trim();
|
|
620
702
|
if (bareEmuMistake(value)) {
|
|
621
703
|
return bareLengthError('slide_width / slide_height', value, library);
|
|
622
704
|
}
|
|
@@ -636,15 +718,17 @@ export function checkPdfBuildSource(source, outputs, packages) {
|
|
|
636
718
|
if (!outputs?.some((name) => name.toLowerCase().endsWith('.pdf')))
|
|
637
719
|
return null;
|
|
638
720
|
const pkgPdf = (packages ?? []).some((p) => {
|
|
639
|
-
const name = p.split(/[=<>!~[]/)[0].trim().toLowerCase();
|
|
721
|
+
const name = (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase();
|
|
640
722
|
return name === 'fpdf2' || name === 'pypdf';
|
|
641
723
|
});
|
|
642
724
|
const srcPdf = /\b(fpdf|FPDF|pypdf|PdfWriter|PdfReader)\b/.test(source);
|
|
643
|
-
if (!pkgPdf && !srcPdf)
|
|
725
|
+
if (!pkgPdf && !srcPdf) {
|
|
644
726
|
return null;
|
|
727
|
+
}
|
|
645
728
|
const builds = /\.output\s*\(|\.write\s*\(/.test(source);
|
|
646
|
-
if (builds)
|
|
729
|
+
if (builds) {
|
|
647
730
|
return null;
|
|
731
|
+
}
|
|
648
732
|
return ('outputs declare a .pdf but command never writes one: pass multi-line Python that ' +
|
|
649
733
|
'builds the document and saves it under the declared name — fpdf2 ends with ' +
|
|
650
734
|
'pdf.output("name.pdf"), pypdf with writer.write("name.pdf"). Do not send diagnostics ' +
|
|
@@ -654,12 +738,13 @@ export function stripCommentsAndDocstrings(source) {
|
|
|
654
738
|
let out = '';
|
|
655
739
|
let i = 0;
|
|
656
740
|
while (i < source.length) {
|
|
657
|
-
const ch = source
|
|
658
|
-
const triple =
|
|
741
|
+
const ch = source.charAt(i);
|
|
742
|
+
const triple = tripleQuoteAt(source, i);
|
|
659
743
|
if (triple) {
|
|
660
744
|
i += triple.length;
|
|
661
|
-
while (i < source.length && !(source.startsWith(triple, i) && !isEscaped(source, i)))
|
|
745
|
+
while (i < source.length && !(source.startsWith(triple, i) && !isEscaped(source, i))) {
|
|
662
746
|
i++;
|
|
747
|
+
}
|
|
663
748
|
out += '""';
|
|
664
749
|
i += triple.length;
|
|
665
750
|
continue;
|
|
@@ -667,15 +752,17 @@ export function stripCommentsAndDocstrings(source) {
|
|
|
667
752
|
if (ch === '"' || ch === "'") {
|
|
668
753
|
const start = i;
|
|
669
754
|
i++;
|
|
670
|
-
while (i < source.length && !(source[i] === ch && !isEscaped(source, i)))
|
|
755
|
+
while (i < source.length && !(source[i] === ch && !isEscaped(source, i))) {
|
|
671
756
|
i++;
|
|
757
|
+
}
|
|
672
758
|
i++;
|
|
673
759
|
out += source.slice(start, Math.min(i, source.length));
|
|
674
760
|
continue;
|
|
675
761
|
}
|
|
676
762
|
if (ch === '#') {
|
|
677
|
-
while (i < source.length && source[i] !== '\n')
|
|
763
|
+
while (i < source.length && source[i] !== '\n') {
|
|
678
764
|
i++;
|
|
765
|
+
}
|
|
679
766
|
continue;
|
|
680
767
|
}
|
|
681
768
|
out += ch;
|
|
@@ -692,15 +779,18 @@ export function checkUnstagedRead(source, inputs) {
|
|
|
692
779
|
const reads = new RegExp(pattern.source, 'g');
|
|
693
780
|
let match;
|
|
694
781
|
while ((match = reads.exec(clean)) !== null) {
|
|
695
|
-
const name = match.slice(1).find((group) => group !== undefined);
|
|
782
|
+
const name = match.slice(1).find((group) => group !== undefined) ?? '';
|
|
696
783
|
const mode = match.groups?.mode;
|
|
697
|
-
if (mode !== undefined && /[wax]/.test(mode))
|
|
784
|
+
if (mode !== undefined && /[wax]/.test(mode)) {
|
|
698
785
|
continue;
|
|
699
|
-
|
|
786
|
+
}
|
|
787
|
+
if (staged.has(name)) {
|
|
700
788
|
continue;
|
|
789
|
+
}
|
|
701
790
|
const before = clean.slice(0, match.index);
|
|
702
|
-
if (before.includes(`"${name}"`) || before.includes(`'${name}'`))
|
|
791
|
+
if (before.includes(`"${name}"`) || before.includes(`'${name}'`)) {
|
|
703
792
|
continue;
|
|
793
|
+
}
|
|
704
794
|
return (`${name} does not exist in the working directory — it starts empty on every run, so ` +
|
|
705
795
|
'files from earlier calls are gone. Stage the attachment via inputs: ' +
|
|
706
796
|
`[{ "attachmentId": "<id from the tool result that produced it, or from the ` +
|
|
@@ -714,8 +804,9 @@ export function checkUnstagedRead(source, inputs) {
|
|
|
714
804
|
const ATTACHMENT_ID = /^[0-9a-f]{32}$/i;
|
|
715
805
|
export function missingFileHint(stderr) {
|
|
716
806
|
const name = /FileNotFoundError: \[Errno \d+\] No such file or directory: '([^']+)'/.exec(stderr)?.[1];
|
|
717
|
-
if (!name)
|
|
807
|
+
if (!name) {
|
|
718
808
|
return null;
|
|
809
|
+
}
|
|
719
810
|
if (ATTACHMENT_ID.test(name)) {
|
|
720
811
|
return (`"${name}" is an attachment id, not a file name, and nothing is on disk under it. ` +
|
|
721
812
|
'An id belongs in `attachmentId`; `path` is a bare file name you choose, and the code ' +
|
|
@@ -751,30 +842,31 @@ function inputEntry(value) {
|
|
|
751
842
|
export function parseInputs(value) {
|
|
752
843
|
if (value === undefined || value === null)
|
|
753
844
|
return undefined;
|
|
754
|
-
if (!Array.isArray(value))
|
|
845
|
+
if (!Array.isArray(value)) {
|
|
755
846
|
return { error: 'inputs must be an array' };
|
|
756
|
-
|
|
847
|
+
}
|
|
848
|
+
if (value.length > MAX_INPUTS) {
|
|
757
849
|
return { error: `inputs must declare at most ${MAX_INPUTS} files` };
|
|
850
|
+
}
|
|
758
851
|
const parsed = [];
|
|
759
852
|
const paths = new Set();
|
|
760
853
|
for (const raw of value) {
|
|
761
854
|
const entry = inputEntry(raw);
|
|
762
|
-
if (!entry)
|
|
855
|
+
if (!entry) {
|
|
763
856
|
return { error: 'each input must be an { attachmentId, path } object' };
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
: typeof entry.attachmentId === 'string'
|
|
767
|
-
? entry.attachmentId.trim()
|
|
768
|
-
: '';
|
|
857
|
+
}
|
|
858
|
+
const attachmentId = attachmentIdOf(entry.attachmentId);
|
|
769
859
|
if (attachmentId === '') {
|
|
770
860
|
return { error: 'attachmentId must be a non-empty string, or omitted for a user upload' };
|
|
771
861
|
}
|
|
772
862
|
const path = typeof entry.path === 'string' ? entry.path.trim() : '';
|
|
773
863
|
const nameError = checkFileName(path);
|
|
774
|
-
if (nameError)
|
|
864
|
+
if (nameError) {
|
|
775
865
|
return { error: `input path: ${nameError}` };
|
|
776
|
-
|
|
866
|
+
}
|
|
867
|
+
if (paths.has(path)) {
|
|
777
868
|
return { error: 'input paths must be unique' };
|
|
869
|
+
}
|
|
778
870
|
paths.add(path);
|
|
779
871
|
parsed.push({ attachmentId, path });
|
|
780
872
|
}
|
|
@@ -792,47 +884,57 @@ export function parseOutputs(value) {
|
|
|
792
884
|
const trimmed = [...new Set(value.map((o) => o.trim()))];
|
|
793
885
|
for (const name of trimmed) {
|
|
794
886
|
const nameError = checkOutputName(name);
|
|
795
|
-
if (nameError)
|
|
887
|
+
if (nameError) {
|
|
796
888
|
return { error: `output name: ${nameError}` };
|
|
889
|
+
}
|
|
797
890
|
}
|
|
798
891
|
return trimmed;
|
|
799
892
|
}
|
|
800
893
|
export function parseWorkdir(value) {
|
|
801
894
|
if (value === undefined || value === null)
|
|
802
895
|
return undefined;
|
|
803
|
-
if (typeof value !== 'string')
|
|
896
|
+
if (typeof value !== 'string') {
|
|
804
897
|
return { error: 'workdir must be a string' };
|
|
898
|
+
}
|
|
805
899
|
const trimmed = value.trim();
|
|
806
|
-
if (!trimmed)
|
|
900
|
+
if (!trimmed) {
|
|
807
901
|
return undefined;
|
|
902
|
+
}
|
|
808
903
|
const nameError = checkFileName(trimmed);
|
|
809
|
-
if (nameError)
|
|
904
|
+
if (nameError) {
|
|
810
905
|
return { error: `workdir: ${nameError}` };
|
|
906
|
+
}
|
|
811
907
|
return trimmed;
|
|
812
908
|
}
|
|
813
909
|
function prepare(args, cwd) {
|
|
814
910
|
const packages = parsePackages(args.packages);
|
|
815
|
-
if (packages && 'error' in packages)
|
|
911
|
+
if (packages && 'error' in packages) {
|
|
816
912
|
return packages;
|
|
913
|
+
}
|
|
817
914
|
const inputs = parseInputs(args.inputs);
|
|
818
|
-
if (inputs && 'error' in inputs)
|
|
915
|
+
if (inputs && 'error' in inputs) {
|
|
819
916
|
return inputs;
|
|
917
|
+
}
|
|
820
918
|
const outputs = parseOutputs(args.outputs);
|
|
821
|
-
if (outputs && 'error' in outputs)
|
|
919
|
+
if (outputs && 'error' in outputs) {
|
|
822
920
|
return outputs;
|
|
921
|
+
}
|
|
823
922
|
const workdir = parseWorkdir(cwd);
|
|
824
|
-
if (workdir && typeof workdir === 'object')
|
|
923
|
+
if (workdir && typeof workdir === 'object') {
|
|
825
924
|
return workdir;
|
|
925
|
+
}
|
|
826
926
|
return { packages, inputs, outputs, workdir };
|
|
827
927
|
}
|
|
828
|
-
export function missingModuleHint(stderr, packages, sandbox = false, network = false) {
|
|
928
|
+
export function missingModuleHint(stderr, { packages, sandbox = false, network = false } = {}) {
|
|
829
929
|
const module = /ModuleNotFoundError: No module named '([^']+)'/.exec(stderr)?.[1];
|
|
830
|
-
if (!module)
|
|
930
|
+
if (!module) {
|
|
831
931
|
return null;
|
|
832
|
-
|
|
932
|
+
}
|
|
933
|
+
const root = module.split('.')[0] ?? '';
|
|
833
934
|
const tool = videoTool(root);
|
|
834
|
-
if (tool && (!network || isTranscoder(tool)))
|
|
935
|
+
if (tool && (!network || isTranscoder(tool))) {
|
|
835
936
|
return network ? TRANSCODE_ERROR : VIDEO_TOOLS_ERROR;
|
|
937
|
+
}
|
|
836
938
|
if (module !== root) {
|
|
837
939
|
const then = sandbox
|
|
838
940
|
? 'If the path is right it ships as its own distribution under a shared namespace, which sandbox mode cannot install: tell the user.'
|
|
@@ -842,8 +944,9 @@ export function missingModuleHint(stderr, packages, sandbox = false, network = f
|
|
|
842
944
|
const distribution = moduleDistribution(root);
|
|
843
945
|
const provider = distribution ?? root;
|
|
844
946
|
const vendored = vendoredSpec(provider);
|
|
845
|
-
if (sandbox && !vendored)
|
|
947
|
+
if (sandbox && !vendored) {
|
|
846
948
|
return `module "${root}" is not installed, ${SANDBOX_NO_INSTALL}`;
|
|
949
|
+
}
|
|
847
950
|
const spec = vendored ?? provider;
|
|
848
951
|
const renamed = distribution !== null && distribution.toLowerCase() !== root.toLowerCase();
|
|
849
952
|
const declared = (packages ?? []).map(specName);
|
|
@@ -863,15 +966,17 @@ export function nonePlaceholderHint(stderr) {
|
|
|
863
966
|
}
|
|
864
967
|
export function slideShapesHint(stderr) {
|
|
865
968
|
const attribute = /AttributeError: 'Slide' object has no attribute '(add_picture|add_textbox|addpicture|addtextbox)'/.exec(stderr)?.[1];
|
|
866
|
-
if (!attribute)
|
|
969
|
+
if (!attribute) {
|
|
867
970
|
return null;
|
|
971
|
+
}
|
|
868
972
|
const method = attribute.startsWith('addp') ? 'add_picture' : 'add_textbox';
|
|
869
973
|
return `a Slide has no add_* methods of its own — they live on its shapes collection: slide.shapes.${method}(...), not slide.${method}(...).`;
|
|
870
974
|
}
|
|
871
975
|
export function shapeFontHint(stderr) {
|
|
872
976
|
const attribute = /AttributeError: '\w*(?:Placeholder|Shape|Slide)\w*' object has no attribute '(font|alignment|text_align|word_wrap|paragraphs|add_paragraph)'/.exec(stderr)?.[1];
|
|
873
|
-
if (!attribute)
|
|
977
|
+
if (!attribute) {
|
|
874
978
|
return null;
|
|
979
|
+
}
|
|
875
980
|
return `"${attribute}" belongs to the text frame or a paragraph inside it, not to the shape: go through shape.text_frame first (shape.text_frame.word_wrap, shape.text_frame.paragraphs[0].font.size, shape.text_frame.paragraphs[0].alignment). shape.text is the one shortcut that reads through to the frame.`;
|
|
876
981
|
}
|
|
877
982
|
export function shapesBackgroundHint(stderr) {
|
|
@@ -902,8 +1007,9 @@ export function paragraphIndexHint(stderr, outputs) {
|
|
|
902
1007
|
}
|
|
903
1008
|
export function shellNameHint(stderr) {
|
|
904
1009
|
const name = /NameError: name '(python3?|py|pip3?)' is not defined/.exec(stderr)?.[1];
|
|
905
|
-
if (!name)
|
|
1010
|
+
if (!name) {
|
|
906
1011
|
return null;
|
|
1012
|
+
}
|
|
907
1013
|
return `"${name}" is not defined because command already runs as Python source — remove the ${name} invocation and pass only the Python code itself`;
|
|
908
1014
|
}
|
|
909
1015
|
export function noProcessHint(stderr, sandbox = false) {
|
|
@@ -929,14 +1035,16 @@ export function noNetworkHint(stderr, tools, network = false) {
|
|
|
929
1035
|
}
|
|
930
1036
|
export function nativeWheelHint(stderr) {
|
|
931
1037
|
const name = /Can't find a pure Python 3 wheel for '([^']+)'/.exec(stderr)?.[1];
|
|
932
|
-
if (!name)
|
|
1038
|
+
if (!name) {
|
|
933
1039
|
return null;
|
|
1040
|
+
}
|
|
934
1041
|
return `"${name}" is built from compiled code, and this device installs only pure-Python packages plus the set already built into its Python. Nothing about this call can install it: use a pure-Python library instead, or tell the user it is not available here.`;
|
|
935
1042
|
}
|
|
936
1043
|
export function packageFetchFailedHint(stderr) {
|
|
937
1044
|
const name = /Can't fetch metadata for '([^']+)'/.exec(stderr)?.[1];
|
|
938
|
-
if (!name)
|
|
1045
|
+
if (!name) {
|
|
939
1046
|
return null;
|
|
1047
|
+
}
|
|
940
1048
|
return `"${name}" could not be fetched: either it is not a PyPI distribution name or this device has no network. Where a skill documents an exact spec for that library, send that spec verbatim; otherwise the package cannot be installed here — tell the user and stop. Do not try other names.`;
|
|
941
1049
|
}
|
|
942
1050
|
function fail(stderr) {
|
|
@@ -966,23 +1074,26 @@ export function softFailedDeckHint(stdout, exitCode, source) {
|
|
|
966
1074
|
return ('the deck soft-failed (caught an image/import error and continued): remove try/except ' +
|
|
967
1075
|
'around imports and add_picture, fix the error, and rerun once — a partial deck must not be saved');
|
|
968
1076
|
}
|
|
969
|
-
export function stagedImagesMissingFromDeck(result, inputs, outputs, source) {
|
|
1077
|
+
export function stagedImagesMissingFromDeck(result, { inputs, outputs, source }) {
|
|
970
1078
|
if (result.exitCode !== 0)
|
|
971
1079
|
return null;
|
|
972
1080
|
if (!/\bPresentation\s*\(/.test(source))
|
|
973
1081
|
return null;
|
|
974
1082
|
const images = (inputs ?? []).filter((i) => IMAGE_INPUT.test(i.path));
|
|
975
|
-
|
|
1083
|
+
const [first] = images;
|
|
1084
|
+
if (!first) {
|
|
976
1085
|
return null;
|
|
1086
|
+
}
|
|
977
1087
|
for (const name of outputs ?? []) {
|
|
978
1088
|
if (!name.toLowerCase().endsWith('.pptx'))
|
|
979
1089
|
continue;
|
|
980
1090
|
const bytes = result.files?.get(name);
|
|
981
|
-
if (!bytes)
|
|
1091
|
+
if (!bytes) {
|
|
982
1092
|
continue;
|
|
1093
|
+
}
|
|
983
1094
|
if (!pptxHasEmbeddedMedia(bytes)) {
|
|
984
1095
|
return (`staged image(s) ${images.map((i) => i.path).join(', ')} were not embedded: ` +
|
|
985
|
-
`call slide.shapes.add_picture("${
|
|
1096
|
+
`call slide.shapes.add_picture("${first.path}", …) without try/except, then save`);
|
|
986
1097
|
}
|
|
987
1098
|
}
|
|
988
1099
|
return null;
|
|
@@ -992,11 +1103,13 @@ export function checkStagedImagesUsed(source, inputs, outputs) {
|
|
|
992
1103
|
return null;
|
|
993
1104
|
if (!outputs?.some((n) => n.toLowerCase().endsWith('.pptx')))
|
|
994
1105
|
return null;
|
|
995
|
-
if (!/\bPresentation\s*\(/.test(source))
|
|
1106
|
+
if (!/\bPresentation\s*\(/.test(source)) {
|
|
996
1107
|
return null;
|
|
1108
|
+
}
|
|
997
1109
|
const images = inputs.filter((i) => IMAGE_INPUT.test(i.path));
|
|
998
|
-
if (!images.length)
|
|
1110
|
+
if (!images.length) {
|
|
999
1111
|
return null;
|
|
1112
|
+
}
|
|
1000
1113
|
if (!/\.add_picture\s*\(/.test(source)) {
|
|
1001
1114
|
return ('inputs stage image(s) but command never calls slide.shapes.add_picture(...): ' +
|
|
1002
1115
|
'embed each staged path or drop it from inputs');
|
|
@@ -1011,26 +1124,30 @@ export function checkStagedImagesUsed(source, inputs, outputs) {
|
|
|
1011
1124
|
}
|
|
1012
1125
|
return null;
|
|
1013
1126
|
}
|
|
1014
|
-
export function checkTurnImagesStaged(source, inputs, outputs, turnImages) {
|
|
1127
|
+
export function checkTurnImagesStaged(source, { inputs, outputs, turnImages }) {
|
|
1015
1128
|
if (!turnImages?.length)
|
|
1016
1129
|
return null;
|
|
1017
1130
|
if (!outputs?.some((n) => n.toLowerCase().endsWith('.pptx')))
|
|
1018
1131
|
return null;
|
|
1019
|
-
if (!/\bPresentation\s*\(/.test(source))
|
|
1132
|
+
if (!/\bPresentation\s*\(/.test(source)) {
|
|
1020
1133
|
return null;
|
|
1021
|
-
|
|
1134
|
+
}
|
|
1135
|
+
if ((inputs ?? []).some((i) => i.attachmentId === undefined)) {
|
|
1022
1136
|
return null;
|
|
1137
|
+
}
|
|
1023
1138
|
return (`the user's message has ${turnImages.length} uploaded image(s) but inputs stage none of them — ` +
|
|
1024
1139
|
'add one id-less { "path": "…" } entry per image and embed each with slide.shapes.add_picture(...)');
|
|
1025
1140
|
}
|
|
1026
1141
|
export function deckDeliveredStop(attachments, exitCode, source) {
|
|
1027
1142
|
if (exitCode !== 0 || !attachments?.length)
|
|
1028
1143
|
return null;
|
|
1029
|
-
if (source !== undefined && !/\bpptx\b|\bPresentation\s*\(/.test(source))
|
|
1144
|
+
if (source !== undefined && !/\bpptx\b|\bPresentation\s*\(/.test(source)) {
|
|
1030
1145
|
return null;
|
|
1146
|
+
}
|
|
1031
1147
|
const decks = attachments.filter((a) => a.fileName.toLowerCase().endsWith('.pptx'));
|
|
1032
|
-
if (decks.length === 0)
|
|
1148
|
+
if (decks.length === 0) {
|
|
1033
1149
|
return null;
|
|
1150
|
+
}
|
|
1034
1151
|
const names = decks.map((a) => a.fileName).join(', ');
|
|
1035
1152
|
const verb = decks.length === 1 ? 'is' : 'are';
|
|
1036
1153
|
return (`${names} ${verb} attached to the chat. STOP: do not call exec again for this deck. ` +
|
|
@@ -1040,18 +1157,20 @@ export function deckDeliveredStop(attachments, exitCode, source) {
|
|
|
1040
1157
|
async function stage(inputs, fetchAttachment, opts = {}) {
|
|
1041
1158
|
if (!inputs?.length)
|
|
1042
1159
|
return undefined;
|
|
1043
|
-
if (!fetchAttachment)
|
|
1160
|
+
if (!fetchAttachment) {
|
|
1044
1161
|
return { error: 'this device cannot read chat attachments' };
|
|
1162
|
+
}
|
|
1045
1163
|
return stageInputs(fetchAttachment, inputs, opts);
|
|
1046
1164
|
}
|
|
1047
|
-
async function collect(result, declared, attach, runBudget, source) {
|
|
1165
|
+
async function collect(result, { declared, attach, runBudget, source }) {
|
|
1048
1166
|
const base = {
|
|
1049
1167
|
stdout: result.stdout,
|
|
1050
1168
|
stderr: result.stderr,
|
|
1051
1169
|
exitCode: result.exitCode
|
|
1052
1170
|
};
|
|
1053
|
-
if (!declared?.length || !attach)
|
|
1171
|
+
if (!declared?.length || !attach) {
|
|
1054
1172
|
return base;
|
|
1173
|
+
}
|
|
1055
1174
|
const pptxBuild = source !== undefined && /\bpptx\b|\bPresentation\s*\(/.test(source);
|
|
1056
1175
|
const pptxNames = declared.filter((n) => n.toLowerCase().endsWith('.pptx') && result.files?.has(n));
|
|
1057
1176
|
const wouldAttachPptx = result.exitCode === 0 && pptxBuild && pptxNames.length > 0;
|
|
@@ -1064,7 +1183,7 @@ async function collect(result, declared, attach, runBudget, source) {
|
|
|
1064
1183
|
}
|
|
1065
1184
|
}
|
|
1066
1185
|
}
|
|
1067
|
-
const { attachments, missingOutputs, rejectedOutputs } = await attachOutputs(attach, (result.exitCode === 0 && result.files) || new Map(), declared, runBudget);
|
|
1186
|
+
const { attachments, missingOutputs, rejectedOutputs } = await attachOutputs(attach, (result.exitCode === 0 && result.files) || new Map(), { declared, runBudget });
|
|
1068
1187
|
const rejected = [...(result.rejected ?? []), ...rejectedOutputs];
|
|
1069
1188
|
const stop = deckDeliveredStop(attachments, result.exitCode, source);
|
|
1070
1189
|
if (stop && result.exitCode === 0) {
|
|
@@ -1073,8 +1192,9 @@ async function collect(result, declared, attach, runBudget, source) {
|
|
|
1073
1192
|
attached = new Set();
|
|
1074
1193
|
deckAttachedThisTurn.set(attach, attached);
|
|
1075
1194
|
}
|
|
1076
|
-
for (const n of pptxNames)
|
|
1195
|
+
for (const n of pptxNames) {
|
|
1077
1196
|
attached.add(n.toLowerCase());
|
|
1197
|
+
}
|
|
1078
1198
|
}
|
|
1079
1199
|
const stdout = stop && result.exitCode === 0
|
|
1080
1200
|
? `${base.stdout}${base.stdout && !base.stdout.endsWith('\n') ? '\n' : ''}${stop}`
|
|
@@ -1082,9 +1202,9 @@ async function collect(result, declared, attach, runBudget, source) {
|
|
|
1082
1202
|
return {
|
|
1083
1203
|
...base,
|
|
1084
1204
|
stdout,
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1205
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
1206
|
+
missingOutputs: missingOutputs.length > 0 ? missingOutputs : undefined,
|
|
1207
|
+
rejectedOutputs: rejected.length > 0 ? rejected : undefined
|
|
1088
1208
|
};
|
|
1089
1209
|
}
|
|
1090
1210
|
export function createPythonAdapter(disabled, loadPyodide, network = false) {
|
|
@@ -1105,33 +1225,46 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
|
|
|
1105
1225
|
const confirmConnect = ctx?.confirmConnect;
|
|
1106
1226
|
return {
|
|
1107
1227
|
async run(options) {
|
|
1108
|
-
if (!command)
|
|
1228
|
+
if (!command) {
|
|
1109
1229
|
return fail('command is required');
|
|
1230
|
+
}
|
|
1110
1231
|
const sourceError = checkPythonSource(command);
|
|
1111
|
-
if (sourceError)
|
|
1232
|
+
if (sourceError) {
|
|
1112
1233
|
return fail(sourceError);
|
|
1234
|
+
}
|
|
1113
1235
|
const source = normalizeFpdfSource(unwrapPythonSource(command) ?? command);
|
|
1114
1236
|
const prepared = prepare(args, options.cwd);
|
|
1115
|
-
if ('error' in prepared)
|
|
1237
|
+
if ('error' in prepared) {
|
|
1116
1238
|
return fail(prepared.error);
|
|
1239
|
+
}
|
|
1117
1240
|
const pptxError = checkPptxBuildSource(source, prepared.outputs, prepared.packages);
|
|
1118
|
-
if (pptxError)
|
|
1241
|
+
if (pptxError) {
|
|
1119
1242
|
return fail(pptxError);
|
|
1243
|
+
}
|
|
1120
1244
|
const pdfError = checkPdfBuildSource(source, prepared.outputs, prepared.packages);
|
|
1121
|
-
if (pdfError)
|
|
1245
|
+
if (pdfError) {
|
|
1122
1246
|
return fail(pdfError);
|
|
1247
|
+
}
|
|
1123
1248
|
const bareLength = checkBareLengthArgs(source, prepared.packages);
|
|
1124
|
-
if (bareLength)
|
|
1249
|
+
if (bareLength) {
|
|
1125
1250
|
return fail(bareLength);
|
|
1251
|
+
}
|
|
1126
1252
|
const unstagedRead = checkUnstagedRead(source, prepared.inputs);
|
|
1127
|
-
if (unstagedRead)
|
|
1253
|
+
if (unstagedRead) {
|
|
1128
1254
|
return fail(unstagedRead);
|
|
1255
|
+
}
|
|
1129
1256
|
const stagedUse = checkStagedImagesUsed(source, prepared.inputs, prepared.outputs);
|
|
1130
|
-
if (stagedUse)
|
|
1257
|
+
if (stagedUse) {
|
|
1131
1258
|
return fail(stagedUse);
|
|
1132
|
-
|
|
1133
|
-
|
|
1259
|
+
}
|
|
1260
|
+
const turnStage = checkTurnImagesStaged(source, {
|
|
1261
|
+
inputs: prepared.inputs,
|
|
1262
|
+
outputs: prepared.outputs,
|
|
1263
|
+
turnImages
|
|
1264
|
+
});
|
|
1265
|
+
if (turnStage) {
|
|
1134
1266
|
return fail(turnStage);
|
|
1267
|
+
}
|
|
1135
1268
|
if (prepared.outputs?.length && !attach) {
|
|
1136
1269
|
return fail('this device cannot persist produced files');
|
|
1137
1270
|
}
|
|
@@ -1139,14 +1272,15 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
|
|
|
1139
1272
|
turnImages,
|
|
1140
1273
|
signal: options.signal
|
|
1141
1274
|
});
|
|
1142
|
-
if (staged && 'error' in staged)
|
|
1275
|
+
if (staged && 'error' in staged) {
|
|
1143
1276
|
return fail(staged.error);
|
|
1277
|
+
}
|
|
1144
1278
|
const install = pipInstallTargets(command)?.map(pinned) ?? null;
|
|
1145
1279
|
const packages = install
|
|
1146
1280
|
? [...new Set([...(prepared.packages ?? []), ...install])]
|
|
1147
1281
|
: prepared.packages;
|
|
1148
1282
|
if (sandbox && install && !planVendoredInstall(packages ?? [])) {
|
|
1149
|
-
return fail(pipMessage(install, true));
|
|
1283
|
+
return fail(pipMessage(install, { sandbox: true }));
|
|
1150
1284
|
}
|
|
1151
1285
|
const result = await runPython(install ? 'pass' : source, {
|
|
1152
1286
|
timeoutMs: options.timeoutMs,
|
|
@@ -1162,15 +1296,21 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
|
|
|
1162
1296
|
askConnect: gated ? confirmConnect : undefined
|
|
1163
1297
|
});
|
|
1164
1298
|
const soft = softFailedDeckHint(result.stdout, result.exitCode, source);
|
|
1165
|
-
if (soft)
|
|
1299
|
+
if (soft) {
|
|
1166
1300
|
return fail(soft);
|
|
1167
|
-
|
|
1168
|
-
|
|
1301
|
+
}
|
|
1302
|
+
const missingPic = stagedImagesMissingFromDeck(result, {
|
|
1303
|
+
inputs: prepared.inputs,
|
|
1304
|
+
outputs: prepared.outputs,
|
|
1305
|
+
source
|
|
1306
|
+
});
|
|
1307
|
+
if (missingPic) {
|
|
1169
1308
|
return fail(missingPic);
|
|
1309
|
+
}
|
|
1170
1310
|
const hint = packageFetchFailedHint(result.stderr) ??
|
|
1171
1311
|
nativeWheelHint(result.stderr) ??
|
|
1172
|
-
missingModuleHint(result.stderr, packages, sandbox, reach) ??
|
|
1173
|
-
pipFromSourceHint(result.stderr, source, packages, sandbox) ??
|
|
1312
|
+
missingModuleHint(result.stderr, { packages, sandbox, network: reach }) ??
|
|
1313
|
+
pipFromSourceHint(result.stderr, source, { packages, sandbox }) ??
|
|
1174
1314
|
noProcessHint(result.stderr, sandbox) ??
|
|
1175
1315
|
noNetworkHint(result.stderr, toolNames, reach) ??
|
|
1176
1316
|
missingFileHint(result.stderr) ??
|
|
@@ -1202,9 +1342,19 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
|
|
|
1202
1342
|
stderr: `${installed.stderr}${installed.stderr ? '\n' : ''}${result.undeclared.join(', ')} ${result.undeclared.length === 1 ? 'was' : 'were'} written but not declared in outputs, so ${result.undeclared.length === 1 ? 'it was' : 'they were'} discarded and nothing reached the user. There is no shell to inspect it with and nothing to look for on disk: declare it in outputs and run the same code again.`
|
|
1203
1343
|
}
|
|
1204
1344
|
: installed;
|
|
1205
|
-
return collect(undelivered,
|
|
1345
|
+
return collect(undelivered, {
|
|
1346
|
+
declared: prepared.outputs,
|
|
1347
|
+
attach,
|
|
1348
|
+
runBudget,
|
|
1349
|
+
source
|
|
1350
|
+
});
|
|
1206
1351
|
}
|
|
1207
1352
|
};
|
|
1208
1353
|
}
|
|
1209
1354
|
};
|
|
1210
1355
|
}
|
|
1356
|
+
function attachmentIdOf(value) {
|
|
1357
|
+
if (value === undefined)
|
|
1358
|
+
return undefined;
|
|
1359
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
1360
|
+
}
|