mixdog 1.2.4 → 1.2.6
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/package.json +1 -1
- package/scripts/lib/gate-scheduler.mjs +2 -2
- package/scripts/lib/run-node-tests.mjs +5 -14
- package/scripts/mobile-release.mjs +5 -4
- package/scripts/provider-pingpong-live.mjs +9 -9
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +11 -0
- package/src/defaults/skills/browser-use/SKILL.md +2 -1
- package/src/defaults/skills/computer-use/SKILL.md +3 -1
- package/src/defaults/skills/visualize/SKILL.md +3 -2
- package/src/lib/rules-builder.cjs +1 -1
- package/src/output-styles/common.md +22 -5
- package/src/output-styles/detailed.md +7 -5
- package/src/output-styles/minimal.md +7 -3
- package/src/output-styles/simple.md +2 -3
- package/src/rules/agent/AGENT.md +3 -1
- package/src/rules/lead/LEAD.md +10 -7
- package/src/rules/shared/10-tool-workflow.md +2 -1
- package/src/rules/shared/60-verification.md +4 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
- package/src/runtime/agent/orchestrator/config.mjs +6 -0
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
- package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
- package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +255 -32
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
- package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
- package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
- package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
- package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
- package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
- package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/attachments/office-extract.mjs +11 -9
- package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
- package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
- package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
- package/src/runtime/office/core/tabular.mjs +1 -1
- package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
- package/src/runtime/office/portable/docx-formatting.mjs +1 -1
- package/src/runtime/office/portable/docx-runs.mjs +1 -1
- package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
- package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
- package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
- package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
- package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
- package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
- package/src/runtime/office/portable/portable-opc.mjs +27 -13
- package/src/runtime/office/portable/portable-pivot.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
- package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
- package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
- package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
- package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
- package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
- package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
- package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
- package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
- package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
- package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
- package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
- package/src/runtime/office/portable/portable-xml.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +22 -13
- package/src/runtime/shared/background-tasks.mjs +60 -20
- package/src/runtime/shared/child-spawn-gate.mjs +26 -16
- package/src/runtime/shared/config.mjs +0 -10
- package/src/runtime/shared/file-lock.mjs +7 -1
- package/src/runtime/shared/file-permissions-worker.mjs +38 -0
- package/src/runtime/shared/file-permissions.d.mts +19 -4
- package/src/runtime/shared/file-permissions.mjs +310 -4
- package/src/runtime/shared/git-runner-worker.mjs +6 -78
- package/src/runtime/shared/llm/http-agent.mjs +7 -3
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
- package/src/runtime/shared/memory-snapshot.mjs +5 -1
- package/src/runtime/shared/notify-trace.mjs +8 -1
- package/src/runtime/shared/pristine-execution-contract.json +5 -1
- package/src/runtime/shared/process-runner-worker.mjs +7 -0
- package/src/runtime/shared/process-runner.mjs +38 -0
- package/src/runtime/shared/provider-accounts.mjs +2 -1
- package/src/runtime/shared/provider-api-key.mjs +0 -1
- package/src/runtime/shared/read-row-numbers.mjs +1 -1
- package/src/runtime/shared/resource-admission.mjs +60 -18
- package/src/runtime/shared/run-child.mjs +84 -0
- package/src/runtime/shared/session-activity-signal.mjs +23 -0
- package/src/runtime/shared/shell-output-summary.mjs +23 -0
- package/src/runtime/shared/singleton-owner.mjs +126 -42
- package/src/runtime/shared/tool-output-limit.mjs +4 -2
- package/src/runtime/shared/tool-output-marker.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +28 -9
- package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
- package/src/runtime/shared/worktrees/git-head.mjs +20 -2
- package/src/runtime/shared/worktrees/lock.mjs +3 -2
- package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
- package/src/runtime/tidy/runners/biome.mjs +3 -1
- package/src/runtime/tidy/shadow.mjs +2 -1
- package/src/runtime/web-search/index.mjs +5 -7
- package/src/runtime/web-search/lib/formatter.mjs +42 -6
- package/src/runtime/web-search/tool-defs.mjs +6 -1
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
- package/src/session-runtime/lifecycle/shared.mjs +19 -2
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +5 -2
- package/src/session-runtime/lifecycle-api.mjs +3 -1
- package/src/session-runtime/provider-auth/login.mjs +10 -6
- package/src/session-runtime/provider-auth/setup.mjs +19 -2
- package/src/session-runtime/provider-catalog-cache.mjs +101 -25
- package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
- package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
- package/src/session-runtime/provider-models.mjs +76 -10
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
- package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
- package/src/session-runtime/services/agent-tool.mjs +20 -0
- package/src/session-runtime/services/provider-admin.mjs +92 -20
- package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
- package/src/session-runtime/services/usage-dashboard.mjs +27 -69
- package/src/session-runtime/turn/session-ops.mjs +2 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/usage-stats-api.mjs +6 -1
- package/src/standalone/channel-transport/http-routes.mjs +10 -9
- package/src/standalone/daemon-desktop-runtime.mjs +20 -7
- package/src/standalone/daemon-owner.mjs +117 -25
- package/src/standalone/daemon.mjs +43 -13
- package/src/standalone/desktop-service-registry.mjs +32 -5
- package/src/standalone/session-call-cache.mjs +6 -0
- package/src/standalone/session-protocol.mjs +1 -1
- package/src/standalone/session-service/retention/busy.mjs +7 -2
- package/src/standalone/session-transport/routes.mjs +7 -6
- package/src/tui/app/model-options.mjs +39 -2
- package/src/tui/app/model-picker.mjs +16 -2
- package/src/tui/app/provider-setup-picker.mjs +1 -2
- package/src/tui/dist/index.mjs +436 -123
- package/src/tui/session/notice-text.mjs +0 -1
- package/src/tui/session/session-api/intake/submission.mjs +7 -13
- package/src/tui/session/session-api/integrations.mjs +1 -5
- package/src/tui/session/session-flow/drain.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +46 -0
- package/src/tui/session/session-flow.mjs +2 -0
- package/src/tui/session/turn-stream.mjs +33 -0
- package/src/tui/session/turn.mjs +30 -3
- package/src/ui/statusline.mjs +1 -6
- package/src/ui/streaming-markdown-heal.mjs +94 -25
- package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
- package/src/workflows/default/WORKFLOW.md +9 -13
|
@@ -4,6 +4,7 @@ import { normalizeErrorMessage } from '../path-diagnostics.mjs';
|
|
|
4
4
|
import { isBinaryBuffer } from '../binary-file.mjs';
|
|
5
5
|
import { isUncPath, isWindowsDevicePath, hasUnsafeWin32Component } from '../device-paths.mjs';
|
|
6
6
|
import { normalizeOutputPath, resolveAgainstCwd } from '../path-utils.mjs';
|
|
7
|
+
import { windowMarker } from '../tool-output-limit.mjs';
|
|
7
8
|
|
|
8
9
|
/** Report the requested path's result; suggestions never retarget the operation. */
|
|
9
10
|
export async function readFamilyPathEnoentOrError(workDir, fullPath, inputPath, err) {
|
|
@@ -52,8 +53,23 @@ export function guardedWalkRoot(inputPath, workDir) {
|
|
|
52
53
|
return { fullPath };
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
// Code-unit path order: the tiebreak that makes a size/mtime sort total, so
|
|
57
|
+
// equal keys cannot reorder with the walk (and shift across offset pages).
|
|
58
|
+
export function comparePathsStable(a, b) {
|
|
59
|
+
const x = String(a);
|
|
60
|
+
const y = String(b);
|
|
61
|
+
if (x === y) return 0;
|
|
62
|
+
return x < y ? -1 : 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
export function pageContinuationLine(offset, shown, total) {
|
|
56
|
-
return
|
|
66
|
+
return windowMarker({
|
|
67
|
+
unit: 'entries',
|
|
68
|
+
from: offset + 1,
|
|
69
|
+
to: offset + shown,
|
|
70
|
+
total,
|
|
71
|
+
nextOffset: offset + shown,
|
|
72
|
+
});
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
const LINE_COUNT_MAX_BYTES = 2 * 1024 * 1024;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// One shape for every piece of shell output handed to an agent — a foreground
|
|
2
|
+
// result, a background move, task wait/read and a completion notice:
|
|
3
|
+
// 1. ANSI stripped, then `\r` progress frames folded to their last frame
|
|
4
|
+
// (before anything is counted or cut);
|
|
5
|
+
// 2. a line longer than DELIVERY_LINE_MAX_CHARS is cut: `… [N chars cut]`;
|
|
6
|
+
// 3. DELIVERY_FULL_CHARS or less goes whole; otherwise a head of at most
|
|
7
|
+
// DELIVERY_HEAD_LINES lines (≤ DELIVERY_HEAD_MAX_CHARS), the marker, and
|
|
8
|
+
// the last ~DELIVERY_TAIL_CHARS;
|
|
9
|
+
// 4. the marker is the same everywhere and always ends with the full log:
|
|
10
|
+
// `[… N lines, X KB omitted — read with offset/limit or grep, full log: <path>]`;
|
|
11
|
+
// 5. anything shaped like a harness/system tag is escaped.
|
|
12
|
+
// MIXDOG_SHELL_DELIVERY_MAX_CHARS is the one knob (the whole-delivery cap); the
|
|
13
|
+
// head and tail sizes are fixed. It is separate from the 50 KB
|
|
14
|
+
// MIXDOG_TOOL_OUTPUT_MAX_BYTES that the other tools use.
|
|
15
|
+
import { randomBytes } from 'node:crypto';
|
|
16
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { getPluginData } from '../../../config.mjs';
|
|
19
|
+
import { stripAnsi } from '../../shell-exec-output.mjs';
|
|
20
|
+
import { createCarriageReturnFolder } from '../shell-lossless-compact.mjs';
|
|
21
|
+
import { positiveEnvInt } from '../../../../../shared/tool-output-limit.mjs';
|
|
22
|
+
import { escapeHarnessTags } from '../../../../../shared/shell-output-summary.mjs';
|
|
23
|
+
|
|
24
|
+
export const DELIVERY_FULL_CHARS = positiveEnvInt('MIXDOG_SHELL_DELIVERY_MAX_CHARS', 6_000);
|
|
25
|
+
const DELIVERY_HEAD_LINES = 10;
|
|
26
|
+
const DELIVERY_HEAD_MAX_CHARS = 1_000;
|
|
27
|
+
const DELIVERY_TAIL_CHARS = 2_000;
|
|
28
|
+
export const DELIVERY_LINE_MAX_CHARS = 2_000;
|
|
29
|
+
|
|
30
|
+
export { createCarriageReturnFolder };
|
|
31
|
+
|
|
32
|
+
const newlines = (text) => {
|
|
33
|
+
let count = 0;
|
|
34
|
+
for (let i = text.indexOf('\n'); i >= 0; i = text.indexOf('\n', i + 1)) count += 1;
|
|
35
|
+
return count;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function formatKb(bytes) {
|
|
39
|
+
return `${(Math.max(0, bytes) / 1024).toFixed(1)} KB`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The omission marker — identical on every delivery path, path last. */
|
|
43
|
+
export function omissionMarker({ lines, bytes, path }) {
|
|
44
|
+
return `[… ${lines} lines, ${formatKb(bytes)} omitted — read with offset/limit or grep, full log: ${path}]`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// `cut` is the number of chars removed from over-long lines.
|
|
48
|
+
function cutLongLines(text) {
|
|
49
|
+
if (text.length <= DELIVERY_LINE_MAX_CHARS) return { text, cut: 0 };
|
|
50
|
+
let cut = 0;
|
|
51
|
+
const cutText = text
|
|
52
|
+
.split('\n')
|
|
53
|
+
.map((line) => {
|
|
54
|
+
if (line.length <= DELIVERY_LINE_MAX_CHARS) return line;
|
|
55
|
+
cut += line.length - DELIVERY_LINE_MAX_CHARS;
|
|
56
|
+
return `${line.slice(0, DELIVERY_LINE_MAX_CHARS)} … [${line.length - DELIVERY_LINE_MAX_CHARS} chars cut]`;
|
|
57
|
+
})
|
|
58
|
+
.join('\n');
|
|
59
|
+
return { text: cutText, cut };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Step 1 for one chunk (the cut of step 2 is made where the text is
|
|
63
|
+
* delivered, so the uncut text can be archived). `fold` carries `\r` state
|
|
64
|
+
* across chunks of the same stream; omitted, the chunk is folded on its own. */
|
|
65
|
+
export function prepareShellText(text, fold = createCarriageReturnFolder()) {
|
|
66
|
+
return fold(stripAnsi(String(text ?? '')));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// `scale` is the share of the delivery budget a stream was given.
|
|
70
|
+
function headOf(text, scale = 1) {
|
|
71
|
+
let end = 0;
|
|
72
|
+
for (let line = 0; line < DELIVERY_HEAD_LINES && end < text.length; line += 1) {
|
|
73
|
+
const newline = text.indexOf('\n', end);
|
|
74
|
+
end = newline < 0 ? text.length : newline + 1;
|
|
75
|
+
}
|
|
76
|
+
return text.slice(0, Math.min(end, Math.floor(DELIVERY_HEAD_MAX_CHARS * scale)));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function tailOf(text, floor = 0, scale = 1) {
|
|
80
|
+
const start = Math.max(floor, text.length - Math.floor(DELIVERY_TAIL_CHARS * scale));
|
|
81
|
+
if (start === floor) return text.slice(start);
|
|
82
|
+
// Start on a line boundary when one is near.
|
|
83
|
+
const newline = text.indexOf('\n', start);
|
|
84
|
+
return newline >= 0 && newline - start < 200 && newline + 1 < text.length
|
|
85
|
+
? text.slice(newline + 1)
|
|
86
|
+
: text.slice(start);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** head + marker + tail; `omitted` counts what lies between them. */
|
|
90
|
+
function joinHeadTail(head, tail, omitted) {
|
|
91
|
+
const gap = head && !head.endsWith('\n') ? '\n' : '';
|
|
92
|
+
return `${head}${gap}${omissionMarker(omitted)}\n${tail}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// One stream's delivery: `natural` is the length it would have whole (escaped,
|
|
96
|
+
// markers included; Infinity when it can only be shown at its ends) and
|
|
97
|
+
// `render(budget)` the text within `budget` chars. Streams share one budget.
|
|
98
|
+
export const EMPTY_PIECE = { natural: 0, render: () => '' };
|
|
99
|
+
|
|
100
|
+
/** Step 3 when only the two ends of a long range were read: `total` is the
|
|
101
|
+
* whole range's bytes and lines. */
|
|
102
|
+
export function edgesPiece(headText, tailText, { totalBytes, totalLines, path }) {
|
|
103
|
+
return {
|
|
104
|
+
natural: Number.POSITIVE_INFINITY,
|
|
105
|
+
render: (budget) => {
|
|
106
|
+
const scale = budget / DELIVERY_FULL_CHARS;
|
|
107
|
+
const head = headOf(escapeHarnessTags(headText), scale);
|
|
108
|
+
const tail = tailOf(escapeHarnessTags(tailText), 0, scale);
|
|
109
|
+
return joinHeadTail(head, tail, {
|
|
110
|
+
lines: Math.max(0, totalLines - newlines(head) - newlines(tail)),
|
|
111
|
+
bytes: Math.max(0, totalBytes - Buffer.byteLength(head) - Buffer.byteLength(tail)),
|
|
112
|
+
path,
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { newlines as countNewlines };
|
|
119
|
+
|
|
120
|
+
/** Steps 2, 3 and 5 on prepared text. `path` names where the whole (uncut)
|
|
121
|
+
* text can be read; it is resolved only when something is removed. */
|
|
122
|
+
export function textPiece(text, { path }) {
|
|
123
|
+
let archive;
|
|
124
|
+
const where = () => (archive ??= typeof path === 'function' ? path() : path);
|
|
125
|
+
const { text: cutText, cut } = cutLongLines(text);
|
|
126
|
+
const shown = escapeHarnessTags(cutText);
|
|
127
|
+
// Cut long lines are removed content too: the whole text carries the marker.
|
|
128
|
+
const whole = cut
|
|
129
|
+
? `${shown}${shown.endsWith('\n') ? '' : '\n'}${omissionMarker({ lines: 0, bytes: cut, path: where() })}\n`
|
|
130
|
+
: shown;
|
|
131
|
+
return {
|
|
132
|
+
natural: whole.length,
|
|
133
|
+
render: (budget) => {
|
|
134
|
+
if (whole.length <= budget) return whole;
|
|
135
|
+
const scale = budget / DELIVERY_FULL_CHARS;
|
|
136
|
+
const head = headOf(shown, scale);
|
|
137
|
+
const tail = tailOf(shown, head.length, scale);
|
|
138
|
+
const middle = shown.slice(head.length, shown.length - tail.length);
|
|
139
|
+
return joinHeadTail(head, tail, { lines: newlines(middle), bytes: Buffer.byteLength(middle), path: where() });
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Max-min fair split of `budget` over the streams' natural lengths: a stream
|
|
145
|
+
* that fits its fair share is given exactly what it needs, the rest share
|
|
146
|
+
* what remains. */
|
|
147
|
+
function shareBudget(lengths, budget) {
|
|
148
|
+
const shares = lengths.map(() => 0);
|
|
149
|
+
let open = lengths.map((_, index) => index);
|
|
150
|
+
let left = Math.max(0, budget);
|
|
151
|
+
while (open.length) {
|
|
152
|
+
const fair = left / open.length;
|
|
153
|
+
const fits = open.filter((index) => lengths[index] <= fair);
|
|
154
|
+
if (!fits.length) {
|
|
155
|
+
for (const index of open) shares[index] = fair;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
for (const index of fits) {
|
|
159
|
+
shares[index] = lengths[index];
|
|
160
|
+
left -= lengths[index];
|
|
161
|
+
}
|
|
162
|
+
open = open.filter((index) => !fits.includes(index));
|
|
163
|
+
}
|
|
164
|
+
return shares;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Deliver streams within one shared budget (default: the whole-delivery cap),
|
|
168
|
+
* each escaped; the caller deducts what it adds around them. */
|
|
169
|
+
export function fitPieces(pieces, budget = DELIVERY_FULL_CHARS) {
|
|
170
|
+
const shares = shareBudget(
|
|
171
|
+
pieces.map((piece) => piece.natural),
|
|
172
|
+
budget
|
|
173
|
+
);
|
|
174
|
+
return pieces.map((piece, index) => escapeHarnessTags(piece.render(shares[index])));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Text that has no log of its own is kept where the marker can name it:
|
|
178
|
+
* the shell-output folder, swept with the spill files. */
|
|
179
|
+
export function persistDeliveryLog(text, label = 'output') {
|
|
180
|
+
const dir = join(getPluginData(), 'shell-output');
|
|
181
|
+
mkdirSync(dir, { recursive: true });
|
|
182
|
+
const file = join(dir, `delivered_${Date.now().toString(36)}_${randomBytes(4).toString('hex')}.${label}`);
|
|
183
|
+
writeFileSync(file, text);
|
|
184
|
+
return file;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Steps 1–5 for a whole string, as a piece. Without `path`, the original
|
|
188
|
+
* (uncut) text is saved when anything is removed, so the marker can name it. */
|
|
189
|
+
export function shellTextPiece(text, { path = null, label = 'output', fold } = {}) {
|
|
190
|
+
const prepared = prepareShellText(text, fold);
|
|
191
|
+
return textPiece(prepared, { path: path || (() => persistDeliveryLog(prepared, label)) });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function deliverShellText(text, options) {
|
|
195
|
+
return fitPieces([shellTextPiece(text, options)])[0];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Step 5 alone, for text the caller already capped. */
|
|
199
|
+
export { escapeHarnessTags };
|
|
@@ -1,11 +1,34 @@
|
|
|
1
|
+
// Incremental delivery of a shell task's output. Each task (the authorized
|
|
2
|
+
// task object) owns one cursor per stream; every wait, read, background move
|
|
3
|
+
// and completion notice delivers only the bytes after it, in the shared
|
|
4
|
+
// delivery shape (lib/shell-delivery.mjs). The cursor always advances over the
|
|
5
|
+
// whole unseen range, even when its middle is omitted: omitted bytes are never
|
|
6
|
+
// re-sent, the marker's log path is how to get them back.
|
|
1
7
|
import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
|
|
2
8
|
import { stripAnsi } from '../../shell-exec-output.mjs';
|
|
3
9
|
import { FileEncodingSniffer, ShellStreamDecoder, alignedOffset, oemTextEncoding } from '../../shell-codepage.mjs';
|
|
10
|
+
import {
|
|
11
|
+
DELIVERY_FULL_CHARS,
|
|
12
|
+
countNewlines,
|
|
13
|
+
createCarriageReturnFolder,
|
|
14
|
+
edgesPiece,
|
|
15
|
+
escapeHarnessTags,
|
|
16
|
+
fitPieces,
|
|
17
|
+
formatKb,
|
|
18
|
+
omissionMarker,
|
|
19
|
+
persistDeliveryLog,
|
|
20
|
+
prepareShellText,
|
|
21
|
+
textPiece,
|
|
22
|
+
} from './shell-delivery.mjs';
|
|
4
23
|
|
|
5
|
-
import { TOOL_OUTPUT_PAGE_BYTES, TOOL_OUTPUT_TAIL_BYTES } from '../../../../../shared/tool-output-budget.mjs';
|
|
6
24
|
const cursors = new WeakMap();
|
|
7
25
|
const ESC = '\x1b';
|
|
8
26
|
const PENDING_ANSI_INTRODUCER = new RegExp(`${ESC}(?:\\[[0-?]*[ -/]*|\\](?:(?!${ESC}\\\\)[^\x07\x9c])*)?$`);
|
|
27
|
+
// An unseen range up to this size is read whole; a longer one only at its two
|
|
28
|
+
// ends (the middle is counted, never loaded).
|
|
29
|
+
const FULL_READ_BYTES = 64 * 1024;
|
|
30
|
+
const EDGE_READ_BYTES = 16 * 1024;
|
|
31
|
+
const COUNT_CHUNK_BYTES = 1 << 20;
|
|
9
32
|
|
|
10
33
|
function newCursor() {
|
|
11
34
|
return {
|
|
@@ -13,6 +36,7 @@ function newCursor() {
|
|
|
13
36
|
preview: '',
|
|
14
37
|
decoder: new ShellStreamDecoder(),
|
|
15
38
|
ansi: '',
|
|
39
|
+
fold: createCarriageReturnFolder(),
|
|
16
40
|
sniffer: new FileEncodingSniffer(),
|
|
17
41
|
committed: false,
|
|
18
42
|
};
|
|
@@ -26,13 +50,6 @@ function pendingAnsiIntroducer(sequence) {
|
|
|
26
50
|
return '\x1b';
|
|
27
51
|
}
|
|
28
52
|
|
|
29
|
-
// First index at or after `start` that does not sit inside a UTF-8 sequence.
|
|
30
|
-
function utf8Boundary(bytes, start, end) {
|
|
31
|
-
let index = start;
|
|
32
|
-
while (index < end && (bytes[index] & 0xc0) === 0x80) index++;
|
|
33
|
-
return index;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
53
|
function visibleText(cursor, text) {
|
|
37
54
|
const value = cursor.ansi + text;
|
|
38
55
|
// Retain only the introducer of an unfinished CSI/OSC, not its potentially
|
|
@@ -45,21 +62,123 @@ function visibleText(cursor, text) {
|
|
|
45
62
|
return stripAnsi(value.slice(0, pending.index));
|
|
46
63
|
}
|
|
47
64
|
|
|
65
|
+
function readBytes(fd, start, length) {
|
|
66
|
+
const buffer = Buffer.alloc(Math.max(0, length));
|
|
67
|
+
const count = length > 0 ? readSync(fd, buffer, 0, length, start) : 0;
|
|
68
|
+
return buffer.subarray(0, count);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function countNewlinesInRange(fd, start, end) {
|
|
72
|
+
let count = 0;
|
|
73
|
+
const buffer = Buffer.alloc(Math.min(COUNT_CHUNK_BYTES, Math.max(1, end - start)));
|
|
74
|
+
for (let position = start; position < end; ) {
|
|
75
|
+
const bytes = readSync(fd, buffer, 0, Math.min(buffer.length, end - position), position);
|
|
76
|
+
if (bytes <= 0) break;
|
|
77
|
+
for (let i = 0; i < bytes; i += 1) if (buffer[i] === 10) count += 1;
|
|
78
|
+
position += bytes;
|
|
79
|
+
}
|
|
80
|
+
return count;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function decodeVisible(cursor, bytes, flush) {
|
|
84
|
+
let decoded = cursor.decoder.write(bytes);
|
|
85
|
+
if (flush) decoded += cursor.decoder.end();
|
|
86
|
+
return prepareShellText(visibleText(cursor, decoded), cursor.fold);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// One unseen file range [start, end) in the delivery shape. A long range is
|
|
90
|
+
// read only at its ends; the tail starts a fresh decoder at a character
|
|
91
|
+
// boundary, as the original paging did when it skipped bytes.
|
|
92
|
+
function readRange(fd, cursor, { start, end, label, flush, path }) {
|
|
93
|
+
const total = end - start;
|
|
94
|
+
if (total <= FULL_READ_BYTES) {
|
|
95
|
+
const text = decodeVisible(cursor, readBytes(fd, start, total), flush);
|
|
96
|
+
return { piece: textPiece(text, { path }), bytes: total, lines: countNewlines(text), text };
|
|
97
|
+
}
|
|
98
|
+
const headText = decodeVisible(cursor, readBytes(fd, start, EDGE_READ_BYTES), false);
|
|
99
|
+
const tailStart = alignedOffset(fd, end - EDGE_READ_BYTES, label || 'utf-8');
|
|
100
|
+
cursor.decoder = new ShellStreamDecoder(oemTextEncoding(), label);
|
|
101
|
+
cursor.ansi = '';
|
|
102
|
+
cursor.fold = createCarriageReturnFolder();
|
|
103
|
+
const tailText = decodeVisible(cursor, readBytes(fd, tailStart, end - tailStart), flush);
|
|
104
|
+
const lines = countNewlinesInRange(fd, start, end);
|
|
105
|
+
return {
|
|
106
|
+
piece: edgesPiece(headText, tailText, { totalBytes: total, totalLines: lines, path }),
|
|
107
|
+
bytes: total,
|
|
108
|
+
lines,
|
|
109
|
+
text: tailText,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const PERCENT_RE = /(\d{1,3}(?:\.\d+)?)\s?%/g;
|
|
114
|
+
const ETA_RE = /\b(?:ETA|eta|remaining|left)\b[:=\s]*(\d+(?::\d{2}){1,2}|\d+(?:\.\d+)?\s?(?:h|m|s|min|sec)\b)/g;
|
|
115
|
+
|
|
116
|
+
function lastMatch(pattern, text) {
|
|
117
|
+
let found = null;
|
|
118
|
+
for (const match of text.matchAll(pattern)) found = match;
|
|
119
|
+
return found;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** What changed since the agent last saw the job: growth, last line, any
|
|
123
|
+
* visible percent/ETA. Used where a still-running job is reported. */
|
|
124
|
+
function progressDigest(growth) {
|
|
125
|
+
const bytes = growth.reduce((sum, entry) => sum + entry.bytes, 0);
|
|
126
|
+
const lines = growth.reduce((sum, entry) => sum + entry.lines, 0);
|
|
127
|
+
const text = growth
|
|
128
|
+
.map((entry) => entry.text)
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.join('\n');
|
|
131
|
+
const parts = [
|
|
132
|
+
bytes > 0 ? `+${formatKb(bytes)}, +${lines} lines since you last saw it` : 'no new output since you last saw it',
|
|
133
|
+
];
|
|
134
|
+
const last = text
|
|
135
|
+
.split('\n')
|
|
136
|
+
.reverse()
|
|
137
|
+
.find((line) => line.trim());
|
|
138
|
+
if (last) parts.push(`last line: ${JSON.stringify(last.trim().slice(0, 200))}`);
|
|
139
|
+
const percent = lastMatch(PERCENT_RE, text);
|
|
140
|
+
if (percent && Number(percent[1]) <= 100) parts.push(`${percent[1]}%`);
|
|
141
|
+
const eta = lastMatch(ETA_RE, text);
|
|
142
|
+
if (eta) parts.push(`ETA ${eta[1].trim()}`);
|
|
143
|
+
return `[progress] ${parts.join(' · ')} — still running, not an error`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** A whole finished log as a piece of the delivery (a foreground command whose
|
|
147
|
+
* capture spilled to disk), read with a fresh cursor. */
|
|
148
|
+
export function shellLogPiece(path) {
|
|
149
|
+
const cursor = newCursor();
|
|
150
|
+
let fd;
|
|
151
|
+
try {
|
|
152
|
+
fd = openSync(path, 'r');
|
|
153
|
+
const size = fstatSync(fd).size;
|
|
154
|
+
const label = cursor.sniffer.probe(fd, size, true);
|
|
155
|
+
if (label) cursor.decoder.force(label);
|
|
156
|
+
return readRange(fd, cursor, { start: 0, end: size, label, flush: true, path }).piece;
|
|
157
|
+
} finally {
|
|
158
|
+
if (fd !== undefined) closeSync(fd);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
48
162
|
// Cursor ownership follows the authorized task object, not a globally supplied
|
|
49
163
|
// task id. Bounded synchronous reads make concurrent task reads non-overlapping.
|
|
50
164
|
// Only bytes actually read advance the cursor; previews never stand in for a
|
|
51
165
|
// file offset, since a preview may contain an omitted middle.
|
|
52
|
-
// Explicit tail reads never change the incremental cursor.
|
|
53
|
-
//
|
|
54
|
-
|
|
166
|
+
// Explicit tail reads (`output: 'tail'`) never change the incremental cursor.
|
|
167
|
+
// `terminal` flushes the decoders (the producer finished); `digest` prefixes
|
|
168
|
+
// the progress digest for a job reported while still running.
|
|
169
|
+
export function readShellTaskOutput(
|
|
170
|
+
task,
|
|
171
|
+
result = {},
|
|
172
|
+
{ output = 'new', terminal = task.status !== 'running', digest = false } = {}
|
|
173
|
+
) {
|
|
55
174
|
const replay = output === 'tail';
|
|
56
|
-
const
|
|
57
|
-
const limit = terminal || replay ? TOOL_OUTPUT_TAIL_BYTES : TOOL_OUTPUT_PAGE_BYTES;
|
|
175
|
+
const growth = [];
|
|
58
176
|
let streams = replay ? new Map() : cursors.get(task);
|
|
59
177
|
if (!streams) {
|
|
60
178
|
streams = new Map();
|
|
61
179
|
cursors.set(task, streams);
|
|
62
180
|
}
|
|
181
|
+
// A note, or a stream's body: stdout and stderr share one delivery budget.
|
|
63
182
|
const sections = [];
|
|
64
183
|
for (const stream of ['stdout', 'stderr']) {
|
|
65
184
|
const path = result[`${stream}_path`];
|
|
@@ -73,15 +192,15 @@ export function readShellTaskOutput(task, result = {}, { output = 'new' } = {})
|
|
|
73
192
|
if (!path) {
|
|
74
193
|
const preview = String(result[`${stream}_preview`] || '');
|
|
75
194
|
const delta = preview.startsWith(cursor.preview) ? preview.slice(cursor.preview.length) : preview;
|
|
76
|
-
const
|
|
77
|
-
const start = utf8Boundary(bytes, Math.max(0, bytes.length - limit), bytes.length);
|
|
78
|
-
if (start) {
|
|
79
|
-
sections.push(`[${stream}: ${start} preview bytes omitted; no original log path available]`);
|
|
80
|
-
cursor.ansi = '';
|
|
81
|
-
}
|
|
82
|
-
const text = visibleText(cursor, bytes.subarray(start).toString('utf8'));
|
|
195
|
+
const text = prepareShellText(visibleText(cursor, delta), cursor.fold);
|
|
83
196
|
cursor.preview = preview;
|
|
84
|
-
|
|
197
|
+
growth.push({ bytes: Buffer.byteLength(delta), lines: countNewlines(text), text });
|
|
198
|
+
// A preview has no log of its own: a capped one is saved for the marker.
|
|
199
|
+
if (text)
|
|
200
|
+
sections.push({
|
|
201
|
+
prefix: `[${stream}]\n`,
|
|
202
|
+
piece: textPiece(text, { path: () => persistDeliveryLog(text, stream) }),
|
|
203
|
+
});
|
|
85
204
|
continue;
|
|
86
205
|
}
|
|
87
206
|
let fd;
|
|
@@ -102,30 +221,37 @@ export function readShellTaskOutput(task, result = {}, { output = 'new' } = {})
|
|
|
102
221
|
cursor.decoder.force(label);
|
|
103
222
|
}
|
|
104
223
|
const readEnd = label ? size : cursor.sniffer.safeEnd(size);
|
|
105
|
-
let start =
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
224
|
+
let start = cursor.offset;
|
|
225
|
+
let earlier = '';
|
|
226
|
+
if (replay && readEnd - start > FULL_READ_BYTES) {
|
|
227
|
+
// A replay shows the recent part; everything before it is counted.
|
|
228
|
+
start = Math.min(readEnd, alignedOffset(fd, readEnd - FULL_READ_BYTES, label || 'utf-8'));
|
|
229
|
+
earlier = omissionMarker({ lines: countNewlinesInRange(fd, 0, start), bytes: start, path });
|
|
109
230
|
cursor.decoder = new ShellStreamDecoder(oemTextEncoding(), label);
|
|
110
|
-
cursor.ansi = '';
|
|
111
|
-
}
|
|
112
|
-
const buffer = Buffer.alloc(Math.min(limit, Math.max(0, readEnd - start)));
|
|
113
|
-
const count = buffer.length ? readSync(fd, buffer, 0, buffer.length, start) : 0;
|
|
114
|
-
cursor.offset = start + count;
|
|
115
|
-
let decoded = cursor.decoder.write(buffer.subarray(0, count));
|
|
116
|
-
if (terminal && cursor.offset === size) decoded += cursor.decoder.end();
|
|
117
|
-
const text = visibleText(cursor, decoded);
|
|
118
|
-
if (text) sections.push(`[${stream}]\n${text}`);
|
|
119
|
-
if (cursor.offset < size) {
|
|
120
|
-
sections.push(
|
|
121
|
-
`[${stream}: ${size - cursor.offset} unread bytes; task read continues, read ${path} for the original log]`
|
|
122
|
-
);
|
|
123
231
|
}
|
|
232
|
+
const range = readRange(fd, cursor, { start, end: readEnd, label, flush: terminal && readEnd === size, path });
|
|
233
|
+
cursor.offset = readEnd;
|
|
234
|
+
growth.push(range);
|
|
235
|
+
if (range.piece.natural)
|
|
236
|
+
sections.push({ prefix: `[${stream}]\n${earlier ? `${earlier}\n` : ''}`, piece: range.piece });
|
|
124
237
|
} catch (error) {
|
|
125
238
|
sections.push(`[${stream} read error: ${error.message}]`);
|
|
126
239
|
} finally {
|
|
127
240
|
if (fd !== undefined) closeSync(fd);
|
|
128
241
|
}
|
|
129
242
|
}
|
|
130
|
-
|
|
243
|
+
// Notes and prefixes come off the budget; the streams split what is left.
|
|
244
|
+
const fixed = sections.reduce(
|
|
245
|
+
(sum, item) => sum + (item.prefix ?? item).length,
|
|
246
|
+
2 * Math.max(0, sections.length - 1)
|
|
247
|
+
);
|
|
248
|
+
const pieces = sections.filter((item) => item.piece);
|
|
249
|
+
const bodies = fitPieces(
|
|
250
|
+
pieces.map((item) => item.piece),
|
|
251
|
+
DELIVERY_FULL_CHARS - fixed
|
|
252
|
+
);
|
|
253
|
+
const body =
|
|
254
|
+
sections.map((item) => (item.piece ? `${item.prefix}${bodies[pieces.indexOf(item)]}` : item)).join('\n\n') ||
|
|
255
|
+
(replay ? '(no output)' : '(no new output)');
|
|
256
|
+
return escapeHarnessTags(digest ? `${progressDigest(growth)}\n\n${body}` : body);
|
|
131
257
|
}
|
|
@@ -6,10 +6,16 @@ import { markScopedCacheIncomplete, markScopedCacheUnsafe } from '../../session/
|
|
|
6
6
|
import { cacheGet, cacheSet, lstatPathsForMtime } from './cache-layers.mjs';
|
|
7
7
|
import { runScopedSearchInFlight } from './lib/scoped-search-flight.mjs';
|
|
8
8
|
import { NOISE_DIR_NAMES, walkDir } from './glob-walk.mjs';
|
|
9
|
-
import { TOOL_OUTPUT_MAX_BYTES } from './tool-output-limit.mjs';
|
|
9
|
+
import { TOOL_OUTPUT_MAX_BYTES, capMarker } from './tool-output-limit.mjs';
|
|
10
10
|
import { runRgWindowedLines } from './native-search-runner.mjs';
|
|
11
11
|
import { tryServeListMetadata } from './native-search-client.mjs';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
countTextFileLines,
|
|
14
|
+
guardedWalkRoot,
|
|
15
|
+
comparePathsStable,
|
|
16
|
+
normalizeListHeadLimit,
|
|
17
|
+
pageContinuationLine,
|
|
18
|
+
} from './lib/list-helpers.mjs';
|
|
13
19
|
import { reportToolProgress } from './lib/tool-progress.mjs';
|
|
14
20
|
import { displayRelPath, statWalkRoot } from './list-tool-shared.mjs';
|
|
15
21
|
import { executeFindFilesTool } from './find-files-tool.mjs';
|
|
@@ -338,9 +344,9 @@ async function fillRowMetadata(rows, workDir, options) {
|
|
|
338
344
|
}
|
|
339
345
|
|
|
340
346
|
function sortListRows(rows, sort) {
|
|
341
|
-
if (sort === 'mtime') rows.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
342
|
-
else if (sort === 'size') rows.sort((a, b) => b.size - a.size);
|
|
343
|
-
else rows.sort((a, b) => a.path.localeCompare(b.path));
|
|
347
|
+
if (sort === 'mtime') rows.sort((a, b) => b.mtimeMs - a.mtimeMs || comparePathsStable(a.path, b.path));
|
|
348
|
+
else if (sort === 'size') rows.sort((a, b) => b.size - a.size || comparePathsStable(a.path, b.path));
|
|
349
|
+
else rows.sort((a, b) => a.path.localeCompare(b.path) || comparePathsStable(a.path, b.path));
|
|
344
350
|
}
|
|
345
351
|
|
|
346
352
|
// What an empty page means: offset past the end is a windowing condition,
|
|
@@ -390,15 +396,17 @@ async function scanList(request, cacheKey, workDir, options) {
|
|
|
390
396
|
if (root.error) return root.error;
|
|
391
397
|
if (!root.stat.isDirectory()) {
|
|
392
398
|
if (!root.stat.isFile()) return `Error: not a directory — ${normalizeOutputPath(fullPath)}`;
|
|
393
|
-
const out = listFileLine(
|
|
399
|
+
const out = listFileLine(
|
|
400
|
+
fullPath,
|
|
401
|
+
root.stat,
|
|
402
|
+
meta,
|
|
403
|
+
meta ? await countTextFileLines(fullPath, root.stat.size) : null
|
|
404
|
+
);
|
|
394
405
|
cacheSet(cacheKey, out, { scopes: [fullPath] });
|
|
395
406
|
return out;
|
|
396
407
|
}
|
|
397
|
-
const { rows, walkResult, walkWarnings, truncatedByCap, nativeDeep, cacheSafe, nativeMetaMissing } =
|
|
398
|
-
request,
|
|
399
|
-
workDir,
|
|
400
|
-
options
|
|
401
|
-
);
|
|
408
|
+
const { rows, walkResult, walkWarnings, truncatedByCap, nativeDeep, cacheSafe, nativeMetaMissing } =
|
|
409
|
+
await collectListRows(request, workDir, options);
|
|
402
410
|
const rootFailure = walkRootFailureLine(options, walkResult, walkWarnings, 'list walk');
|
|
403
411
|
if (rootFailure) return rootFailure;
|
|
404
412
|
if (!nativeDeep && needsGlobalStat) await fillRowMetadata(rows, workDir, options);
|
|
@@ -439,7 +447,11 @@ async function renderListPage({ request, rows, walkWarnings, truncatedByCap, nat
|
|
|
439
447
|
if (paged) lines.push(pageContinuationLine(offset, sliced.length, rows.length));
|
|
440
448
|
if (truncatedByCap) {
|
|
441
449
|
lines.push(
|
|
442
|
-
|
|
450
|
+
capMarker({
|
|
451
|
+
limit: `${LIST_ABSOLUTE_CAP} rows or ${LIST_WALK_TIMEOUT_MS}ms`,
|
|
452
|
+
shown: 'listing incomplete',
|
|
453
|
+
narrow: 'narrow the path or lower depth',
|
|
454
|
+
})
|
|
443
455
|
);
|
|
444
456
|
}
|
|
445
457
|
for (const warning of walkWarnings) lines.push(directoryReadFailureLine(warning, true));
|
|
@@ -516,7 +528,7 @@ function renderTreeOutput(lines, walkWarnings, { headLimit, offset }) {
|
|
|
516
528
|
outputCharTruncated = true;
|
|
517
529
|
out =
|
|
518
530
|
out.slice(0, TOOL_OUTPUT_MAX_BYTES) +
|
|
519
|
-
`\n
|
|
531
|
+
`\n${capMarker({ limit: TOOL_OUTPUT_MAX_BYTES, narrow: 'narrow path or lower depth' })}`;
|
|
520
532
|
}
|
|
521
533
|
return { out, outputCharTruncated };
|
|
522
534
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { READ_BATCH_RANGE_COALESCE_GAP_LINES } from './read-constants.mjs';
|
|
2
|
+
import { windowMarker } from './tool-output-limit.mjs';
|
|
2
3
|
|
|
3
4
|
// Slices a `read` body (full mode) to the line window [origOffset+1,
|
|
4
5
|
// origOffset+origLimit] (1-based line numbers in the body). Lines whose
|
|
@@ -81,15 +82,15 @@ export function sliceReadBodyByLines(body, origOffset, origLimit, readOffsetBase
|
|
|
81
82
|
else if (finiteLast) requestedLast = lastLine;
|
|
82
83
|
const emittedStart = keptFirst ?? firstLine;
|
|
83
84
|
const emittedLast = keptLast !== null ? Math.min(requestedLast, keptLast) : requestedLast;
|
|
84
|
-
const totalPart = haveTotal ? ` of ${totalNum}` : '';
|
|
85
85
|
const moreToRead = haveTotal ? emittedLast < totalNum : finiteLast;
|
|
86
86
|
// Report the next caller coordinate without prescribing another read;
|
|
87
87
|
// whatever remains fits one wider read, not a walk window by window.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
const newFooter = windowMarker({
|
|
89
|
+
from: emittedStart,
|
|
90
|
+
to: emittedLast,
|
|
91
|
+
total: haveTotal ? totalNum : null,
|
|
92
|
+
nextOffset: moreToRead && Number.isFinite(emittedLast) ? emittedLast + readOffsetBase : null,
|
|
93
|
+
});
|
|
93
94
|
return kept.join('\n') + (kept.length ? '\n' : '') + newFooter;
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { READ_LINE_NO_SEP } from '../../../../shared/read-row-numbers.mjs';
|
|
2
2
|
import { mergeReadRanges } from './read-ranges.mjs';
|
|
3
|
-
import { TOOL_OUTPUT_MAX_BYTES, positiveEnvInt } from './tool-output-limit.mjs';
|
|
3
|
+
import { TOOL_OUTPUT_MAX_BYTES, capMarker, positiveEnvInt } from './tool-output-limit.mjs';
|
|
4
4
|
|
|
5
5
|
// Smart-truncate cap: a no-window read returns the file until this cap, past
|
|
6
6
|
// which head+tail are shown and the model pages the rest with offset (footer
|
|
@@ -18,9 +18,23 @@ const READ_MAX_RENDERED_LINE_CHARS = 2_000;
|
|
|
18
18
|
// (runtime/shared/read-row-numbers.mjs).
|
|
19
19
|
export const LINE_NO_SEP = READ_LINE_NO_SEP;
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// Cap marker for the elided middle of a no-window read. With the omitted
|
|
22
|
+
// line span it names the continuation offset; without, narrowing advice.
|
|
23
|
+
export function buildSmartReadTruncationMarker(totalLines, fileBytes, omitted = null) {
|
|
22
24
|
const kb = Math.max(1, Math.round((Number(fileBytes) || 0) / 1024));
|
|
23
|
-
|
|
25
|
+
const limit = fileBytes > SMART_READ_MAX_BYTES ? SMART_READ_MAX_BYTES : `${SMART_READ_MAX_LINES} lines`;
|
|
26
|
+
if (!omitted) {
|
|
27
|
+
return capMarker({
|
|
28
|
+
limit,
|
|
29
|
+
shown: `${totalLines} lines / ${kb} KB`,
|
|
30
|
+
narrow: 'read located windows with {file_path, offset, limit}',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return capMarker({
|
|
34
|
+
limit,
|
|
35
|
+
shown: `lines ${omitted.from}-${omitted.to} of ${totalLines} omitted (${kb} KB file)`,
|
|
36
|
+
nextOffset: omitted.from,
|
|
37
|
+
});
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
function rangeFromRenderedReadRows(rows, fallbackStartLine = 1) {
|
|
@@ -56,9 +70,10 @@ export function smartReadTruncate(renderedWithLineNos, totalLines, fileBytes) {
|
|
|
56
70
|
const tailRows = rows.slice(tailStart);
|
|
57
71
|
const head = headRows.join('\n');
|
|
58
72
|
const tail = tailRows.join('\n');
|
|
59
|
-
const marker = buildSmartReadTruncationMarker(totalLines, fileBytes);
|
|
73
|
+
const marker = buildSmartReadTruncationMarker(totalLines, fileBytes, { from: headCount + 1, to: tailStart });
|
|
60
74
|
return {
|
|
61
75
|
text: `${head}\n${marker}\n${tail}`,
|
|
76
|
+
marker,
|
|
62
77
|
truncated: true,
|
|
63
78
|
totalLines,
|
|
64
79
|
ranges: mergeReadRanges(
|