agent-nuvira 3.3.3 → 3.3.4
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/README.md +13 -5
- package/dist/agent-sdk/src/agent.d.ts +2 -0
- package/dist/agent-sdk/src/agent.d.ts.map +1 -1
- package/dist/agent-sdk/src/define.d.ts +64 -0
- package/dist/agent-sdk/src/define.d.ts.map +1 -0
- package/dist/agent-sdk/src/define.js +76 -0
- package/dist/agent-sdk/src/define.js.map +1 -0
- package/dist/agent-sdk/src/index.d.ts +9 -0
- package/dist/agent-sdk/src/index.d.ts.map +1 -1
- package/dist/agent-sdk/src/index.js +9 -0
- package/dist/agent-sdk/src/index.js.map +1 -1
- package/dist/agent-sdk/src/scaffold.d.ts +11 -0
- package/dist/agent-sdk/src/scaffold.d.ts.map +1 -1
- package/dist/agent-sdk/src/scaffold.js +16 -6
- package/dist/agent-sdk/src/scaffold.js.map +1 -1
- package/dist/agents/long-form-plan.d.ts.map +1 -1
- package/dist/agents/long-form-plan.js +2 -1
- package/dist/agents/long-form-plan.js.map +1 -1
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +5 -4
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/cli/agent.d.ts +2 -2
- package/dist/cli/agent.js +10 -10
- package/dist/cli/chat.d.ts +94 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +354 -20
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +5 -0
- package/dist/cli/cli-program.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +9 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +3 -2
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/edit.js +2 -2
- package/dist/cli/eval.d.ts +17 -0
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +105 -2
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/execute.d.ts +16 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +192 -22
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +55 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +212 -13
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/model.d.ts.map +1 -1
- package/dist/cli/model.js +9 -8
- package/dist/cli/model.js.map +1 -1
- package/dist/cli/models.d.ts +1 -1
- package/dist/cli/models.js +4 -4
- package/dist/cli/parity.d.ts +85 -0
- package/dist/cli/parity.d.ts.map +1 -0
- package/dist/cli/parity.js +506 -0
- package/dist/cli/parity.js.map +1 -0
- package/dist/cli/plan.d.ts.map +1 -1
- package/dist/cli/plan.js +2 -1
- package/dist/cli/plan.js.map +1 -1
- package/dist/cli/retrieval.d.ts.map +1 -1
- package/dist/cli/retrieval.js +5 -4
- package/dist/cli/retrieval.js.map +1 -1
- package/dist/cli/sdk.js +4 -4
- package/dist/cli/sdk.js.map +1 -1
- package/dist/cli/trace.d.ts.map +1 -1
- package/dist/cli/trace.js +2 -1
- package/dist/cli/trace.js.map +1 -1
- package/dist/cli/workflow.js +2 -2
- package/dist/cli/workflow.js.map +1 -1
- package/dist/config/types.d.ts +44 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/findings/verdicts.d.ts +241 -0
- package/dist/findings/verdicts.d.ts.map +1 -0
- package/dist/findings/verdicts.js +284 -0
- package/dist/findings/verdicts.js.map +1 -0
- package/dist/gateway/adapters.d.ts +65 -0
- package/dist/gateway/adapters.d.ts.map +1 -1
- package/dist/gateway/adapters.js +216 -10
- package/dist/gateway/adapters.js.map +1 -1
- package/dist/gateway/channel-directory.d.ts +31 -0
- package/dist/gateway/channel-directory.d.ts.map +1 -1
- package/dist/gateway/channel-directory.js +40 -0
- package/dist/gateway/channel-directory.js.map +1 -1
- package/dist/gateway/gateway-log.d.ts +1 -1
- package/dist/gateway/gateway-log.d.ts.map +1 -1
- package/dist/gateway/gateway-log.js.map +1 -1
- package/dist/gateway/hooks.d.ts +87 -19
- package/dist/gateway/hooks.d.ts.map +1 -1
- package/dist/gateway/hooks.js +62 -23
- package/dist/gateway/hooks.js.map +1 -1
- package/dist/gateway/inbound-media.d.ts +147 -0
- package/dist/gateway/inbound-media.d.ts.map +1 -0
- package/dist/gateway/inbound-media.js +317 -0
- package/dist/gateway/inbound-media.js.map +1 -0
- package/dist/gateway/inbox.d.ts +8 -1
- package/dist/gateway/inbox.d.ts.map +1 -1
- package/dist/gateway/inbox.js.map +1 -1
- package/dist/gateway/platform-config.d.ts +14 -0
- package/dist/gateway/platform-config.d.ts.map +1 -1
- package/dist/gateway/platform-config.js +26 -8
- package/dist/gateway/platform-config.js.map +1 -1
- package/dist/gateway/realtime.d.ts +114 -0
- package/dist/gateway/realtime.d.ts.map +1 -0
- package/dist/gateway/realtime.js +402 -0
- package/dist/gateway/realtime.js.map +1 -0
- package/dist/gateway/registry.d.ts +31 -0
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +224 -4
- package/dist/gateway/registry.js.map +1 -1
- package/dist/gateway/whatsapp/baileys-bridge.d.ts +11 -1
- package/dist/gateway/whatsapp/baileys-bridge.d.ts.map +1 -1
- package/dist/gateway/whatsapp/baileys-bridge.js +123 -4
- package/dist/gateway/whatsapp/baileys-bridge.js.map +1 -1
- package/dist/gateway/whatsapp/bridge.d.ts +6 -2
- package/dist/gateway/whatsapp/bridge.d.ts.map +1 -1
- package/dist/gateway/whatsapp/bridge.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/inference/factory.d.ts +14 -0
- package/dist/inference/factory.d.ts.map +1 -1
- package/dist/inference/factory.js +17 -0
- package/dist/inference/factory.js.map +1 -1
- package/dist/inference/groq-adapter.d.ts +2 -0
- package/dist/inference/groq-adapter.d.ts.map +1 -1
- package/dist/inference/groq-adapter.js +16 -6
- package/dist/inference/groq-adapter.js.map +1 -1
- package/dist/inference/tools.d.ts.map +1 -1
- package/dist/inference/tools.js +29 -0
- package/dist/inference/tools.js.map +1 -1
- package/dist/learning/benchmark.d.ts.map +1 -1
- package/dist/learning/benchmark.js +3 -2
- package/dist/learning/benchmark.js.map +1 -1
- package/dist/learning/continuation.d.ts.map +1 -1
- package/dist/learning/continuation.js +2 -1
- package/dist/learning/continuation.js.map +1 -1
- package/dist/learning/cost-tracker.d.ts.map +1 -1
- package/dist/learning/cost-tracker.js +2 -1
- package/dist/learning/cost-tracker.js.map +1 -1
- package/dist/learning/deferred-task.d.ts.map +1 -1
- package/dist/learning/deferred-task.js +13 -4
- package/dist/learning/deferred-task.js.map +1 -1
- package/dist/learning/eval-framework.d.ts.map +1 -1
- package/dist/learning/eval-framework.js +2 -1
- package/dist/learning/eval-framework.js.map +1 -1
- package/dist/learning/long-form.d.ts.map +1 -1
- package/dist/learning/long-form.js +2 -1
- package/dist/learning/long-form.js.map +1 -1
- package/dist/learning/model-registry.d.ts.map +1 -1
- package/dist/learning/model-registry.js +2 -1
- package/dist/learning/model-registry.js.map +1 -1
- package/dist/learning/reasoning-cache.d.ts.map +1 -1
- package/dist/learning/reasoning-cache.js +2 -1
- package/dist/learning/reasoning-cache.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +37 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +66 -0
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +2 -1
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/learning/retrieval.d.ts.map +1 -1
- package/dist/learning/retrieval.js +2 -1
- package/dist/learning/retrieval.js.map +1 -1
- package/dist/learning/seeded-benchmark.d.ts +160 -0
- package/dist/learning/seeded-benchmark.d.ts.map +1 -0
- package/dist/learning/seeded-benchmark.js +321 -0
- package/dist/learning/seeded-benchmark.js.map +1 -0
- package/dist/learning/seeded-bugs.d.ts +142 -0
- package/dist/learning/seeded-bugs.d.ts.map +1 -0
- package/dist/learning/seeded-bugs.js +535 -0
- package/dist/learning/seeded-bugs.js.map +1 -0
- package/dist/learning/step-checkpoint.d.ts +127 -0
- package/dist/learning/step-checkpoint.d.ts.map +1 -0
- package/dist/learning/step-checkpoint.js +244 -0
- package/dist/learning/step-checkpoint.js.map +1 -0
- package/dist/nlu/intent-confirm.d.ts +23 -1
- package/dist/nlu/intent-confirm.d.ts.map +1 -1
- package/dist/nlu/intent-confirm.js +85 -2
- package/dist/nlu/intent-confirm.js.map +1 -1
- package/dist/nlu/learnings.d.ts +7 -0
- package/dist/nlu/learnings.d.ts.map +1 -1
- package/dist/nlu/learnings.js +7 -0
- package/dist/nlu/learnings.js.map +1 -1
- package/dist/observability/debug-log.d.ts +250 -0
- package/dist/observability/debug-log.d.ts.map +1 -0
- package/dist/observability/debug-log.js +500 -0
- package/dist/observability/debug-log.js.map +1 -0
- package/dist/observability/event-bus.d.ts.map +1 -1
- package/dist/observability/event-bus.js +4 -1
- package/dist/observability/event-bus.js.map +1 -1
- package/dist/observability/otel.d.ts +278 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +590 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/parity/drivers.d.ts +99 -0
- package/dist/parity/drivers.d.ts.map +1 -0
- package/dist/parity/drivers.js +1362 -0
- package/dist/parity/drivers.js.map +1 -0
- package/dist/parity/graph.d.ts +73 -0
- package/dist/parity/graph.d.ts.map +1 -0
- package/dist/parity/graph.js +162 -0
- package/dist/parity/graph.js.map +1 -0
- package/dist/parity/matrix.d.ts +105 -0
- package/dist/parity/matrix.d.ts.map +1 -0
- package/dist/parity/matrix.js +352 -0
- package/dist/parity/matrix.js.map +1 -0
- package/dist/parity/observation.d.ts +444 -0
- package/dist/parity/observation.d.ts.map +1 -0
- package/dist/parity/observation.js +333 -0
- package/dist/parity/observation.js.map +1 -0
- package/dist/parity/scenarios.d.ts +229 -0
- package/dist/parity/scenarios.d.ts.map +1 -0
- package/dist/parity/scenarios.js +175 -0
- package/dist/parity/scenarios.js.map +1 -0
- package/dist/parity/surfaces.d.ts +122 -0
- package/dist/parity/surfaces.d.ts.map +1 -0
- package/dist/parity/surfaces.js +190 -0
- package/dist/parity/surfaces.js.map +1 -0
- package/dist/runtime/fault-injection.d.ts +173 -0
- package/dist/runtime/fault-injection.d.ts.map +1 -0
- package/dist/runtime/fault-injection.js +281 -0
- package/dist/runtime/fault-injection.js.map +1 -0
- package/dist/tools/child-agent-entry.d.ts +23 -0
- package/dist/tools/child-agent-entry.d.ts.map +1 -0
- package/dist/tools/child-agent-entry.js +129 -0
- package/dist/tools/child-agent-entry.js.map +1 -0
- package/dist/tools/child-agent-runtime.d.ts +124 -0
- package/dist/tools/child-agent-runtime.d.ts.map +1 -0
- package/dist/tools/child-agent-runtime.js +704 -0
- package/dist/tools/child-agent-runtime.js.map +1 -0
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +82 -10
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/delegation-system.d.ts +31 -0
- package/dist/tools/delegation-system.d.ts.map +1 -1
- package/dist/tools/delegation-system.js +70 -9
- package/dist/tools/delegation-system.js.map +1 -1
- package/dist/tools/extract/docx.d.ts +27 -0
- package/dist/tools/extract/docx.d.ts.map +1 -0
- package/dist/tools/extract/docx.js +48 -0
- package/dist/tools/extract/docx.js.map +1 -0
- package/dist/tools/extract/html-text.d.ts +27 -0
- package/dist/tools/extract/html-text.d.ts.map +1 -0
- package/dist/tools/extract/html-text.js +86 -0
- package/dist/tools/extract/html-text.js.map +1 -0
- package/dist/tools/extract/pdf-ocr.d.ts +58 -0
- package/dist/tools/extract/pdf-ocr.d.ts.map +1 -0
- package/dist/tools/extract/pdf-ocr.js +116 -0
- package/dist/tools/extract/pdf-ocr.js.map +1 -0
- package/dist/tools/extract/pdf.d.ts +65 -0
- package/dist/tools/extract/pdf.d.ts.map +1 -0
- package/dist/tools/extract/pdf.js +197 -0
- package/dist/tools/extract/pdf.js.map +1 -0
- package/dist/tools/extract/pptx.d.ts +32 -0
- package/dist/tools/extract/pptx.d.ts.map +1 -0
- package/dist/tools/extract/pptx.js +77 -0
- package/dist/tools/extract/pptx.js.map +1 -0
- package/dist/tools/extract/xlsx.d.ts +47 -0
- package/dist/tools/extract/xlsx.d.ts.map +1 -0
- package/dist/tools/extract/xlsx.js +111 -0
- package/dist/tools/extract/xlsx.js.map +1 -0
- package/dist/tools/finding-tool.d.ts +76 -0
- package/dist/tools/finding-tool.d.ts.map +1 -0
- package/dist/tools/finding-tool.js +125 -0
- package/dist/tools/finding-tool.js.map +1 -0
- package/dist/tools/messaging-tools.d.ts +41 -11
- package/dist/tools/messaging-tools.d.ts.map +1 -1
- package/dist/tools/messaging-tools.js +104 -55
- package/dist/tools/messaging-tools.js.map +1 -1
- package/dist/tools/neutts-synth.d.ts +27 -3
- package/dist/tools/neutts-synth.d.ts.map +1 -1
- package/dist/tools/neutts-synth.js +57 -13
- package/dist/tools/neutts-synth.js.map +1 -1
- package/dist/tools/pipeline-tool.d.ts +43 -1
- package/dist/tools/pipeline-tool.d.ts.map +1 -1
- package/dist/tools/pipeline-tool.js +13 -2
- package/dist/tools/pipeline-tool.js.map +1 -1
- package/dist/tools/read-extract.d.ts +116 -45
- package/dist/tools/read-extract.d.ts.map +1 -1
- package/dist/tools/read-extract.js +494 -158
- package/dist/tools/read-extract.js.map +1 -1
- package/dist/tools/registry.d.ts +2 -2
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +152 -23
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/subagent-refusal.d.ts +15 -0
- package/dist/tools/subagent-refusal.d.ts.map +1 -0
- package/dist/tools/subagent-refusal.js +18 -0
- package/dist/tools/subagent-refusal.js.map +1 -0
- package/dist/tools/subagent-spawner.d.ts +122 -0
- package/dist/tools/subagent-spawner.d.ts.map +1 -1
- package/dist/tools/subagent-spawner.js +249 -28
- package/dist/tools/subagent-spawner.js.map +1 -1
- package/dist/tools/tool-hooks.d.ts +177 -0
- package/dist/tools/tool-hooks.d.ts.map +1 -0
- package/dist/tools/tool-hooks.js +427 -0
- package/dist/tools/tool-hooks.js.map +1 -0
- package/dist/tools/tool-loop.d.ts +82 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +167 -9
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/tool-refusal.d.ts +68 -0
- package/dist/tools/tool-refusal.d.ts.map +1 -0
- package/dist/tools/tool-refusal.js +78 -0
- package/dist/tools/tool-refusal.js.map +1 -0
- package/dist/tools/toolsets.d.ts +8 -0
- package/dist/tools/toolsets.d.ts.map +1 -1
- package/dist/tools/toolsets.js +12 -2
- package/dist/tools/toolsets.js.map +1 -1
- package/dist/tools/vision-tools.d.ts +88 -83
- package/dist/tools/vision-tools.d.ts.map +1 -1
- package/dist/tools/vision-tools.js +134 -103
- package/dist/tools/vision-tools.js.map +1 -1
- package/dist/tools/worktree.d.ts +210 -0
- package/dist/tools/worktree.d.ts.map +1 -0
- package/dist/tools/worktree.js +374 -0
- package/dist/tools/worktree.js.map +1 -0
- package/dist/utils/format.d.ts +3 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +32 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/web-dashboard/attachment-extract.d.ts +64 -0
- package/dist/web-dashboard/attachment-extract.d.ts.map +1 -0
- package/dist/web-dashboard/attachment-extract.js +154 -0
- package/dist/web-dashboard/attachment-extract.js.map +1 -0
- package/dist/web-dashboard/chat-console.d.ts +108 -1
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +36 -0
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/hub-data.d.ts +37 -0
- package/dist/web-dashboard/hub-data.d.ts.map +1 -1
- package/dist/web-dashboard/hub-data.js +61 -1
- package/dist/web-dashboard/hub-data.js.map +1 -1
- package/dist/web-dashboard/server.d.ts +14 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +247 -13
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +196 -48
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/package.json +18 -3
- package/src/web-dashboard/public/assets/{index-Cyd6tIew.css → index-XJjj2cBX.css} +1 -1
- package/src/web-dashboard/public/assets/index-YWF9FpwQ.js +207 -0
- package/src/web-dashboard/public/assets/index-YWF9FpwQ.js.map +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/dist/tools/child-agent-worker.js +0 -212
- package/src/web-dashboard/public/assets/index-CxDj7p6i.js +0 -207
- package/src/web-dashboard/public/assets/index-CxDj7p6i.js.map +0 -1
|
@@ -1,22 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vision Tools —
|
|
2
|
+
* Vision Tools — image analysis backed by a REAL engine, or a typed refusal.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* What is real here, and what is not:
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
6
|
+
* - `analyze` reports the image's actual format and pixel dimensions (parsed from
|
|
7
|
+
* the PNG/JPEG/GIF/BMP headers) and, when a vision backend is configured,
|
|
8
|
+
* a genuine description from it.
|
|
9
|
+
* - `ocr` transcribes an image through the configured vision model
|
|
10
|
+
* (`./modality/vision.js` — a local Ollama vision model such as llava /
|
|
11
|
+
* llama3.2-vision, or Gemini when a key is set). A vision-model transcription
|
|
12
|
+
* is not a confidence-scored OCR engine, so the result says which engine ran
|
|
13
|
+
* and never invents a `confidence` number.
|
|
14
|
+
* - `detect-elements`, `find-element` and `compare` have **no backend** and refuse
|
|
15
|
+
* with `not_configured`. They previously returned `[]` / `{similarity: 0}` —
|
|
16
|
+
* empty-but-valid payloads that read as "ran, found nothing", which is the same
|
|
17
|
+
* class of defect as the `read_extract` PDF stub.
|
|
18
|
+
*
|
|
19
|
+
* The registry's `vision` tool ("analyze images, extract text via OCR, detect UI
|
|
20
|
+
* elements") pointed at this file while `describe_image` pointed at the real
|
|
21
|
+
* engine — two tools for one capability, one of them a stub. See
|
|
22
|
+
* TOOL_TRUTHFULNESS_TRACKER.md finding #6.
|
|
11
23
|
*/
|
|
12
24
|
import { readFile } from 'node:fs/promises';
|
|
13
25
|
import { extname } from 'node:path';
|
|
26
|
+
import { describeImage, isVisionAvailable } from './modality/vision.js';
|
|
27
|
+
import { refuse } from './tool-refusal.js';
|
|
28
|
+
/**
|
|
29
|
+
* Render a `ToolRefusal` into this module's result shape — the message field here
|
|
30
|
+
* is named `error` rather than `reason`, so the mapping is explicit instead of
|
|
31
|
+
* spreading `refuse()` output and ending up with an undeclared `reason` key.
|
|
32
|
+
*/
|
|
33
|
+
function asVisionRefusal(r) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
code: r.code,
|
|
37
|
+
error: r.reason,
|
|
38
|
+
...(r.alternatives ? { alternatives: r.alternatives } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Prompt used for OCR-via-vision-model. Asks for the text ONLY, no commentary. */
|
|
42
|
+
const OCR_PROMPT = 'Transcribe ALL visible text in this image exactly as it appears, preserving line breaks and any table '
|
|
43
|
+
+ 'rows/columns. Output only the transcription, with no preamble and no commentary.';
|
|
44
|
+
/** What a user can do when no vision backend is configured. */
|
|
45
|
+
const VISION_SETUP_ALTERNATIVES = [
|
|
46
|
+
'Configure a vision backend: a local Ollama vision model (llava / llama3.2-vision) or a Gemini API key',
|
|
47
|
+
'Ask the user to paste the text instead',
|
|
48
|
+
];
|
|
14
49
|
// ─── Vision Analyzer ──────────────────────────────────────────────────────
|
|
15
50
|
export class VisionAnalyzer {
|
|
16
51
|
/**
|
|
17
|
-
* Analyze an image
|
|
52
|
+
* Analyze an image: real format + dimensions always, plus a real description
|
|
53
|
+
* when a vision backend is available.
|
|
18
54
|
*/
|
|
19
|
-
async analyze(imagePath) {
|
|
55
|
+
async analyze(imagePath, opts = {}) {
|
|
20
56
|
const ext = extname(imagePath).toLowerCase();
|
|
21
57
|
const content = await readFile(imagePath).catch(() => null);
|
|
22
58
|
if (!content) {
|
|
@@ -24,63 +60,128 @@ export class VisionAnalyzer {
|
|
|
24
60
|
path: imagePath,
|
|
25
61
|
dimensions: { width: 0, height: 0 },
|
|
26
62
|
format: ext.slice(1) || 'unknown',
|
|
27
|
-
|
|
28
|
-
description: 'Failed to read image',
|
|
63
|
+
description: `Could not read the image at ${imagePath}.`,
|
|
29
64
|
tags: [],
|
|
65
|
+
ok: false,
|
|
66
|
+
code: 'no_data',
|
|
67
|
+
error: `Could not read image: ${imagePath}`,
|
|
68
|
+
alternatives: ['Check the path and that the file is a readable image'],
|
|
30
69
|
};
|
|
31
70
|
}
|
|
32
|
-
// Basic image info
|
|
33
71
|
const format = ext.slice(1) || 'unknown';
|
|
34
72
|
const dimensions = this.getImageDimensions(content, format);
|
|
73
|
+
const metadataLine = `Image: ${format.toUpperCase()} (${dimensions.width}x${dimensions.height})`;
|
|
74
|
+
// Prefer a real description when a backend exists; otherwise report honestly
|
|
75
|
+
// that only the metadata was read.
|
|
76
|
+
if (await isVisionAvailable(opts)) {
|
|
77
|
+
const described = await describeImage(imagePath, undefined, opts);
|
|
78
|
+
if (described.ok && described.description) {
|
|
79
|
+
return {
|
|
80
|
+
path: imagePath,
|
|
81
|
+
dimensions,
|
|
82
|
+
format,
|
|
83
|
+
description: described.description,
|
|
84
|
+
tags: [format],
|
|
85
|
+
ok: true,
|
|
86
|
+
via: 'vision-model',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
path: imagePath,
|
|
91
|
+
dimensions,
|
|
92
|
+
format,
|
|
93
|
+
description: metadataLine,
|
|
94
|
+
tags: [format],
|
|
95
|
+
ok: true,
|
|
96
|
+
via: 'metadata',
|
|
97
|
+
note: `The vision backend was configured but failed (${described.error ?? 'unknown error'}), so only image metadata was read.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
35
100
|
return {
|
|
36
101
|
path: imagePath,
|
|
37
102
|
dimensions,
|
|
38
103
|
format,
|
|
39
|
-
|
|
40
|
-
description: `Image: ${format.toUpperCase()} (${dimensions.width}x${dimensions.height})`,
|
|
104
|
+
description: metadataLine,
|
|
41
105
|
tags: [format],
|
|
106
|
+
ok: true,
|
|
107
|
+
via: 'metadata',
|
|
108
|
+
note: 'No vision backend is configured, so only image metadata was read — no description of the contents.',
|
|
109
|
+
alternatives: VISION_SETUP_ALTERNATIVES,
|
|
42
110
|
};
|
|
43
111
|
}
|
|
44
112
|
/**
|
|
45
|
-
*
|
|
113
|
+
* Transcribe the text in an image through the configured vision model.
|
|
114
|
+
*
|
|
115
|
+
* Refuses when no backend is configured — it never returns an empty string as
|
|
116
|
+
* if the image simply contained no text.
|
|
46
117
|
*/
|
|
47
|
-
async ocr(imagePath) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
118
|
+
async ocr(imagePath, opts = {}) {
|
|
119
|
+
if (!(await isVisionAvailable(opts))) {
|
|
120
|
+
return {
|
|
121
|
+
text: '',
|
|
122
|
+
...asVisionRefusal(refuse('not_configured', 'No vision backend is configured (a local Ollama vision model such as llava/llama3.2-vision, or a Gemini API key), so no text could be read from the image.', VISION_SETUP_ALTERNATIVES)),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const described = await describeImage(imagePath, OCR_PROMPT, opts);
|
|
126
|
+
if (!described.ok || described.description === undefined) {
|
|
127
|
+
return {
|
|
128
|
+
text: '',
|
|
129
|
+
...asVisionRefusal(refuse('unavailable', `The vision backend could not read ${imagePath}: ${described.error ?? 'no output'}`, ['Retry once — a local vision model can time out on a large image'])),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { text: described.description.trim(), ok: true, via: 'vision-model' };
|
|
54
133
|
}
|
|
55
134
|
/**
|
|
56
135
|
* Detect UI elements in a screenshot.
|
|
136
|
+
*
|
|
137
|
+
* There is no detector backend (no ML model, no DOM access from a raster image),
|
|
138
|
+
* so this refuses rather than returning an empty element list.
|
|
57
139
|
*/
|
|
58
140
|
async detectUIElements(imagePath) {
|
|
59
|
-
|
|
60
|
-
|
|
141
|
+
return {
|
|
142
|
+
elements: [],
|
|
143
|
+
...asVisionRefusal(refuse('not_configured', `UI-element detection has no backend — it needs a DOM or an ML detector, and neither is wired up, so no elements were detected in ${imagePath}.`, [
|
|
144
|
+
'Use the browser tools (browser / browser_supervisor) to query the live DOM instead of a screenshot',
|
|
145
|
+
'Use describe_image to get a prose description of the screenshot',
|
|
146
|
+
])),
|
|
147
|
+
};
|
|
61
148
|
}
|
|
62
149
|
/**
|
|
63
150
|
* Compare two images.
|
|
151
|
+
*
|
|
152
|
+
* No pixel-diff or embedding backend is wired up, so this refuses. It used to
|
|
153
|
+
* return `{ similarity: 0, differences: [] }`, which reads as "identical images
|
|
154
|
+
* with no differences" — the opposite of what it knew.
|
|
64
155
|
*/
|
|
65
156
|
async compare(imagePath1, imagePath2) {
|
|
66
|
-
return
|
|
157
|
+
return asVisionRefusal(refuse('not_configured', `Image comparison has no backend, so ${imagePath1} and ${imagePath2} were NOT compared.`, [
|
|
158
|
+
'Describe both images with describe_image and compare the descriptions',
|
|
159
|
+
'Compare the files byte-wise if you only need to know whether they differ',
|
|
160
|
+
]));
|
|
67
161
|
}
|
|
68
162
|
/**
|
|
69
|
-
* Extract text from a screenshot
|
|
163
|
+
* Extract text from a screenshot (thin alias over `ocr`, kept for callers that
|
|
164
|
+
* expect a bare string). A refusal yields an empty string — prefer `ocr` when
|
|
165
|
+
* you need to tell "no text in the image" apart from "no backend configured".
|
|
70
166
|
*/
|
|
71
|
-
async extractText(imagePath) {
|
|
72
|
-
const result = await this.ocr(imagePath);
|
|
167
|
+
async extractText(imagePath, opts = {}) {
|
|
168
|
+
const result = await this.ocr(imagePath, opts);
|
|
73
169
|
return result.text;
|
|
74
170
|
}
|
|
75
171
|
/**
|
|
76
|
-
* Find element
|
|
172
|
+
* Find an element whose text matches. Refuses for the same reason as
|
|
173
|
+
* `detectUIElements` — there is no detector to search.
|
|
77
174
|
*/
|
|
78
175
|
async findElementByText(imagePath, text) {
|
|
79
|
-
const
|
|
80
|
-
|
|
176
|
+
const result = await this.detectUIElements(imagePath);
|
|
177
|
+
if (!result.ok)
|
|
178
|
+
return null;
|
|
179
|
+
return result.elements.find((e) => e.text?.toLowerCase().includes(text.toLowerCase())) || null;
|
|
81
180
|
}
|
|
82
181
|
/**
|
|
83
|
-
* Get image dimensions (
|
|
182
|
+
* Get image dimensions from the format header (PNG / JPEG / GIF / BMP).
|
|
183
|
+
* Returns {0,0} when the header is not one of those — an unknown size, never a
|
|
184
|
+
* guessed one.
|
|
84
185
|
*/
|
|
85
186
|
getImageDimensions(buffer, format) {
|
|
86
187
|
// PNG header
|
|
@@ -89,7 +190,7 @@ export class VisionAnalyzer {
|
|
|
89
190
|
const height = buffer.readUInt32BE(20);
|
|
90
191
|
return { width, height };
|
|
91
192
|
}
|
|
92
|
-
// JPEG header (
|
|
193
|
+
// JPEG header (search the SOF marker)
|
|
93
194
|
if (format === 'jpeg' || format === 'jpg') {
|
|
94
195
|
let offset = 2;
|
|
95
196
|
while (offset < buffer.length - 1) {
|
|
@@ -123,81 +224,11 @@ export class VisionAnalyzer {
|
|
|
123
224
|
return { width: 0, height: 0 };
|
|
124
225
|
}
|
|
125
226
|
}
|
|
126
|
-
// ─── Screenshot Analyzer ──────────────────────────────────────────────────
|
|
127
|
-
export class ScreenshotAnalyzer {
|
|
128
|
-
analyzer;
|
|
129
|
-
constructor() {
|
|
130
|
-
this.analyzer = new VisionAnalyzer();
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Analyze a browser screenshot.
|
|
134
|
-
*/
|
|
135
|
-
async analyzeBrowserScreenshot(screenshotPath) {
|
|
136
|
-
const elements = await this.analyzer.detectUIElements(screenshotPath);
|
|
137
|
-
const text = await this.analyzer.extractText(screenshotPath);
|
|
138
|
-
return {
|
|
139
|
-
elements,
|
|
140
|
-
text,
|
|
141
|
-
layout: this.inferLayout(elements),
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Find clickable elements.
|
|
146
|
-
*/
|
|
147
|
-
async findClickableElements(screenshotPath) {
|
|
148
|
-
const elements = await this.analyzer.detectUIElements(screenshotPath);
|
|
149
|
-
return elements.filter((e) => e.interactive);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Generate automation instructions.
|
|
153
|
-
*/
|
|
154
|
-
async generateAutomationInstructions(screenshotPath, goal) {
|
|
155
|
-
const elements = await this.analyzer.detectUIElements(screenshotPath);
|
|
156
|
-
const instructions = [];
|
|
157
|
-
// Simple heuristic-based instruction generation
|
|
158
|
-
const buttons = elements.filter((e) => e.type === 'button');
|
|
159
|
-
const inputs = elements.filter((e) => e.type === 'input');
|
|
160
|
-
const links = elements.filter((e) => e.type === 'link');
|
|
161
|
-
if (goal.toLowerCase().includes('click')) {
|
|
162
|
-
for (const btn of buttons) {
|
|
163
|
-
if (btn.text)
|
|
164
|
-
instructions.push(`Click button "${btn.text}"`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (goal.toLowerCase().includes('type') || goal.toLowerCase().includes('enter')) {
|
|
168
|
-
for (const input of inputs) {
|
|
169
|
-
instructions.push(`Type in ${input.text || 'input field'}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (goal.toLowerCase().includes('navigate')) {
|
|
173
|
-
for (const link of links) {
|
|
174
|
-
if (link.text)
|
|
175
|
-
instructions.push(`Click link "${link.text}"`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return instructions;
|
|
179
|
-
}
|
|
180
|
-
inferLayout(elements) {
|
|
181
|
-
if (elements.length === 0)
|
|
182
|
-
return 'empty';
|
|
183
|
-
if (elements.length > 20)
|
|
184
|
-
return 'dense';
|
|
185
|
-
if (elements.some((e) => e.type === 'container'))
|
|
186
|
-
return 'structured';
|
|
187
|
-
return 'simple';
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
227
|
// ─── Singletons ───────────────────────────────────────────────────────────
|
|
191
228
|
let _visionAnalyzer = null;
|
|
192
|
-
let _screenshotAnalyzer = null;
|
|
193
229
|
export function getVisionAnalyzer() {
|
|
194
230
|
if (!_visionAnalyzer)
|
|
195
231
|
_visionAnalyzer = new VisionAnalyzer();
|
|
196
232
|
return _visionAnalyzer;
|
|
197
233
|
}
|
|
198
|
-
export function getScreenshotAnalyzer() {
|
|
199
|
-
if (!_screenshotAnalyzer)
|
|
200
|
-
_screenshotAnalyzer = new ScreenshotAnalyzer();
|
|
201
|
-
return _screenshotAnalyzer;
|
|
202
|
-
}
|
|
203
234
|
//# sourceMappingURL=vision-tools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vision-tools.js","sourceRoot":"","sources":["../../src/tools/vision-tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"vision-tools.js","sourceRoot":"","sources":["../../src/tools/vision-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WS5 (#27) — a turn's own git worktree, and the diff it leaves behind.
|
|
3
|
+
*
|
|
4
|
+
* WHY A WORKTREE AT ALL. The ask is "try this and show me what changed". Running
|
|
5
|
+
* that in the operator's own tree makes the answer inseparable from the damage: a
|
|
6
|
+
* turn that edits twelve files to test one hypothesis has changed the tree the
|
|
7
|
+
* operator is working in, and the only record of it is whatever they notice. A
|
|
8
|
+
* second checkout of the SAME repository (a `git worktree`) gives the turn a real
|
|
9
|
+
* tree — same code, same test runner, same `node_modules` — that can be thrown
|
|
10
|
+
* away, and turns "what changed" into a diff against the commit it started from.
|
|
11
|
+
*
|
|
12
|
+
* WHY THE BASE IS `HEAD`, AND WHY THE NOTICE SAYS SO. `git worktree add … HEAD`
|
|
13
|
+
* checks out a commit, so UNCOMMITTED work in the operator's tree is not visible
|
|
14
|
+
* inside the copy. That is a real trap — a run that "did not see my edit" — so the
|
|
15
|
+
* number of dirty files in the source tree is MEASURED at creation and reported,
|
|
16
|
+
* rather than left to be discovered. A clean tree reports nothing.
|
|
17
|
+
*
|
|
18
|
+
* WHY IT REFUSES INSTEAD OF DEGRADING. Not every directory can be isolated: not a
|
|
19
|
+
* repository, no commit yet, no `git` at all. The tempting behaviour — run in the
|
|
20
|
+
* real tree and report the turn normally — is the one outcome this feature exists
|
|
21
|
+
* to prevent, because the caller acts on a diff that describes a tree nobody ran
|
|
22
|
+
* in. So isolation either happens or the turn STOPS, and the refusal says why.
|
|
23
|
+
*
|
|
24
|
+
* WHY THE DIFF IS MEASURED BEFORE TEARDOWN. Removing the worktree is what
|
|
25
|
+
* destroys the evidence, so the order is not a detail: measure, then remove. A
|
|
26
|
+
* teardown that fails (a git lock, a file handle) is reported as "not removed"
|
|
27
|
+
* rather than assumed, since the directory is left in the operator's profile with
|
|
28
|
+
* a branch pointing at it.
|
|
29
|
+
*
|
|
30
|
+
* GIT'S OWN STDOUT IS KEPT SEPARATE FROM THE MESSAGE. One helper here returns a
|
|
31
|
+
* never-empty `out` (git printing nothing is a fact worth a sentence), and a
|
|
32
|
+
* second field `raw` holding exactly what git wrote. Counting or parsing `out` is
|
|
33
|
+
* how a PRISTINE repository came to be reported as having one uncommitted change:
|
|
34
|
+
* `git status --porcelain` prints nothing, the fallback sentence has one line, and
|
|
35
|
+
* the count was taken from the sentence (see TOOL_TRUTHFULNESS_TRACKER).
|
|
36
|
+
*/
|
|
37
|
+
/** The environment key a deployment asks for isolation through. */
|
|
38
|
+
export declare const WORKTREE_ENABLE_ENV = "NUVIRA_ISOLATE";
|
|
39
|
+
/** The environment keys a forked child reads to learn which worktree it is in. */
|
|
40
|
+
export declare const WORKTREE_DIR_ENV = "NUVIRA_WORKTREE_DIR";
|
|
41
|
+
export declare const WORKTREE_BASE_ENV = "NUVIRA_WORKTREE_BASE";
|
|
42
|
+
/** How long `git worktree add` may take — a big repository is still a checkout. */
|
|
43
|
+
export declare const WORKTREE_CREATE_TIMEOUT_MS = 20000;
|
|
44
|
+
/** Bound on one file's diff body, so a huge change cannot be handed over whole. */
|
|
45
|
+
export declare const WORKTREE_PATCH_CHARS = 120000;
|
|
46
|
+
/** ─── Requests ─────────────────────────────────────────────────────────────── */
|
|
47
|
+
/**
|
|
48
|
+
* What an isolation request resolved to.
|
|
49
|
+
*
|
|
50
|
+
* Exactly two fields, and no more: the parity suite asserts the shape of a
|
|
51
|
+
* default request (`{asked: false, keep: false}`), so a third field added here
|
|
52
|
+
* would be a silent change to a documented value. `asked` and `keep` are kept
|
|
53
|
+
* apart because asking to be isolated and asking to KEEP the result are separate
|
|
54
|
+
* decisions, and a plain `--worktree` must not leave a directory behind.
|
|
55
|
+
*/
|
|
56
|
+
export interface IsolationRequest {
|
|
57
|
+
asked: boolean;
|
|
58
|
+
keep: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve an isolation request from a caller's options, with the environment as
|
|
62
|
+
* the fallback — not the other way round.
|
|
63
|
+
*
|
|
64
|
+
* `undefined` means "nobody said anything", and only then does `NUVIRA_ISOLATE`
|
|
65
|
+
* decide. An explicit `false` is a DECISION (a surface that offers an off switch
|
|
66
|
+
* must be able to turn a deployment's `NUVIRA_ISOLATE=1` off for one turn), and
|
|
67
|
+
* it outranks the environment. This is the distinction a commander-style `false`
|
|
68
|
+
* default destroys: with one, every CLI turn carries an explicit decline and the
|
|
69
|
+
* documented `NUVIRA_ISOLATE=1` is unreachable from the command line.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveIsolationRequest(input: {
|
|
72
|
+
worktree?: boolean;
|
|
73
|
+
keepWorktree?: boolean;
|
|
74
|
+
}): IsolationRequest;
|
|
75
|
+
/**
|
|
76
|
+
* Why `repoCwd` cannot be isolated, or null when it can.
|
|
77
|
+
*
|
|
78
|
+
* Shared by the refusal path and by the parity harness, which uses it to assert
|
|
79
|
+
* that the checkout it is about to drive CAN be isolated — a harness that
|
|
80
|
+
* silently proved nothing because it ran somewhere unisolatable would be worse
|
|
81
|
+
* than a failure.
|
|
82
|
+
*/
|
|
83
|
+
export declare function worktreeRefusal(repoCwd: string): string | null;
|
|
84
|
+
/** The refusal, as the operator reads it. A failure, not a degradation. */
|
|
85
|
+
export declare function isolationRefusalText(why: string): string;
|
|
86
|
+
/** ─── The worktree ─────────────────────────────────────────────────────────── */
|
|
87
|
+
/** A live worktree: where it is, and the commit everything is measured against. */
|
|
88
|
+
export interface IsolatedWorktree {
|
|
89
|
+
/** The checkout the turn runs in. */
|
|
90
|
+
dir: string;
|
|
91
|
+
/** The commit the diff is against (full sha). */
|
|
92
|
+
base: string;
|
|
93
|
+
/** The branch the worktree is checked out on, created with it. */
|
|
94
|
+
branch: string;
|
|
95
|
+
/** The repository it was created from. */
|
|
96
|
+
repoCwd: string;
|
|
97
|
+
/** Uncommitted paths in the SOURCE tree, which the copy cannot see. */
|
|
98
|
+
sourceDirty: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Make a worktree of `repoCwd`, or return null when that is impossible.
|
|
102
|
+
*
|
|
103
|
+
* `node_modules` is LINKED rather than installed: a worktree has no dependencies
|
|
104
|
+
* of its own, and an isolated turn that could not run a test would measure
|
|
105
|
+
* nothing. The link is best-effort — a filesystem that cannot symlink still gets
|
|
106
|
+
* an isolated tree, just one without dependencies.
|
|
107
|
+
*/
|
|
108
|
+
export declare function createIsolatedWorktree(input: {
|
|
109
|
+
repoCwd: string;
|
|
110
|
+
label: string;
|
|
111
|
+
timeoutMs?: number;
|
|
112
|
+
}): IsolatedWorktree | null;
|
|
113
|
+
/** ─── The diff ─────────────────────────────────────────────────────────────── */
|
|
114
|
+
/**
|
|
115
|
+
* What an isolated turn changed.
|
|
116
|
+
*
|
|
117
|
+
* NOTE THE TWO HALVES OF `diff`, because they are not the same list and reading
|
|
118
|
+
* the wrong one renders an empty card: `files` is the changed PATHS (one string
|
|
119
|
+
* per file — what a reader scans), and `payload.files[].body` is the unified diff
|
|
120
|
+
* of each. `payload` deliberately has the shape the `git:diff` event already
|
|
121
|
+
* carries, so an isolated change renders with the diff card that is already
|
|
122
|
+
* there instead of a second renderer that could drift from it.
|
|
123
|
+
*/
|
|
124
|
+
export interface WorktreeDiff {
|
|
125
|
+
/** Changed paths, sorted as git reports them. */
|
|
126
|
+
files: string[];
|
|
127
|
+
summary: string;
|
|
128
|
+
/** True when the run changed nothing — a fact, not a failure. */
|
|
129
|
+
unchanged: boolean;
|
|
130
|
+
payload: {
|
|
131
|
+
files: Array<{
|
|
132
|
+
path: string;
|
|
133
|
+
body: string;
|
|
134
|
+
}>;
|
|
135
|
+
summary: string;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Measure the diff against the worktree's base commit.
|
|
140
|
+
*
|
|
141
|
+
* UNTRACKED FILES COUNT, and that is why the index is staged first: a run that
|
|
142
|
+
* CREATES a file has changed the tree, and `git diff` alone would report nothing
|
|
143
|
+
* for it. Staging inside a worktree that is about to be thrown away is free, and
|
|
144
|
+
* a worktree that is KEPT has its new files staged — which is what `git diff
|
|
145
|
+
* --cached` reads on the next measurement.
|
|
146
|
+
*/
|
|
147
|
+
export declare function worktreeDiff(handle: IsolatedWorktree, opts?: {
|
|
148
|
+
timeoutMs?: number;
|
|
149
|
+
}): WorktreeDiff;
|
|
150
|
+
/**
|
|
151
|
+
* Remove a worktree and the branch that was made with it.
|
|
152
|
+
*
|
|
153
|
+
* Returns whether the directory is GONE, not whether git was happy: a removal
|
|
154
|
+
* that succeeded but left the directory (a file handle, a lock) has still left a
|
|
155
|
+
* directory in the operator's profile, and "removed: true" is the claim the
|
|
156
|
+
* console prints.
|
|
157
|
+
*/
|
|
158
|
+
export declare function discardWorktree(handle: IsolatedWorktree, opts?: {
|
|
159
|
+
timeoutMs?: number;
|
|
160
|
+
}): boolean;
|
|
161
|
+
/** ─── Lifecycle ────────────────────────────────────────────────────────────── */
|
|
162
|
+
/** A started isolation: the worktree, or the refusal that replaced it. */
|
|
163
|
+
export type IsolationStart = {
|
|
164
|
+
ok: true;
|
|
165
|
+
worktree: IsolatedWorktree;
|
|
166
|
+
} | {
|
|
167
|
+
ok: false;
|
|
168
|
+
refusal: string;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Start isolation for a turn, or return null when nobody asked for one.
|
|
172
|
+
*
|
|
173
|
+
* Null and a refusal are different answers and both are load-bearing: null means
|
|
174
|
+
* the turn runs where it always did, and a refusal means the turn DOES NOT RUN.
|
|
175
|
+
*/
|
|
176
|
+
export declare function beginIsolation(input: {
|
|
177
|
+
request: IsolationRequest;
|
|
178
|
+
repoCwd: string;
|
|
179
|
+
label: string;
|
|
180
|
+
}): IsolationStart | null;
|
|
181
|
+
/** The environment a forked child reads, so a tool can say where it is running. */
|
|
182
|
+
export declare function worktreeEnv(handle: IsolatedWorktree): Record<string, string>;
|
|
183
|
+
/**
|
|
184
|
+
* Where the turn is running, said when it starts.
|
|
185
|
+
*
|
|
186
|
+
* The base commit is named as a commit AND as a branch, and the dirty count is
|
|
187
|
+
* reported when it is non-zero — the two facts that decide whether the diff at
|
|
188
|
+
* the end describes what the operator expected.
|
|
189
|
+
*/
|
|
190
|
+
export declare function worktreeNotice(handle: IsolatedWorktree): string;
|
|
191
|
+
/** What the turn changed, what happened to the directory, and the diff itself. */
|
|
192
|
+
export interface IsolationOutcome {
|
|
193
|
+
dir: string;
|
|
194
|
+
base: string;
|
|
195
|
+
diff: WorktreeDiff;
|
|
196
|
+
/** False when the directory was KEPT (`--keep-worktree`). */
|
|
197
|
+
removed: boolean;
|
|
198
|
+
/** The operator-facing line (see `worktreeNotice` / this module's `notice`). */
|
|
199
|
+
notice: string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Finish isolation: measure the diff, then tear the worktree down (unless kept).
|
|
203
|
+
*
|
|
204
|
+
* Never throws — the caller attaches this to the turn's own result, and a git
|
|
205
|
+
* error must not replace an answer with a message about a directory.
|
|
206
|
+
*/
|
|
207
|
+
export declare function endIsolation(handle: IsolatedWorktree, opts: {
|
|
208
|
+
keep: boolean;
|
|
209
|
+
}): IsolationOutcome;
|
|
210
|
+
//# sourceMappingURL=worktree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/tools/worktree.ts"],"names":[],"mappings":"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"}
|