mixdog 1.2.5 → 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/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 +10 -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 -6
- 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 +4 -3
- 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 +258 -43
- 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/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 +385 -104
- 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 +24 -7
- 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
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
// eval), so the static cycle through session/manager.mjs is safe — same
|
|
9
9
|
// pattern as loop/tool-exec.mjs.
|
|
10
10
|
import { enqueuePendingMessage, markCompletionEntry } from '../../session/manager.mjs';
|
|
11
|
-
import { completeBackgroundTask } from '../../../../shared/background-tasks.mjs';
|
|
11
|
+
import { completeBackgroundTask, getBackgroundTask } from '../../../../shared/background-tasks.mjs';
|
|
12
12
|
import {
|
|
13
13
|
cancelNativeTask,
|
|
14
14
|
getNativeTask,
|
|
@@ -35,29 +35,45 @@ import {
|
|
|
35
35
|
retireShellJobRecord,
|
|
36
36
|
takeReleaseRequest,
|
|
37
37
|
} from './lib/shell-job-records.mjs';
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
createProcessTreeTracker,
|
|
40
|
+
killAfterSnapshot,
|
|
41
|
+
PROCESS_TABLE_READ_BOUND_MS,
|
|
42
|
+
watchTermination,
|
|
43
|
+
withinBound,
|
|
44
|
+
} from '../lib/shell-descendants.mjs';
|
|
39
45
|
import {
|
|
40
46
|
flushPendingOwnerCancels,
|
|
47
|
+
prepareOwnedShellTreeKills,
|
|
41
48
|
registerOwnedShellTree,
|
|
42
49
|
unregisterOwnedShellTree,
|
|
43
50
|
} from '../lib/owned-shell-trees.mjs';
|
|
44
51
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
52
|
+
// Bound on how long a runtime/session shutdown waits for the sweep of what a
|
|
53
|
+
// cancelled job started outside its process group.
|
|
54
|
+
const SHUTDOWN_TREE_CONFIRM_MS = 3_000;
|
|
55
|
+
// Lead of the pre-deadline snapshot over a native timeout kill: one table
|
|
56
|
+
// read's bound plus margin.
|
|
57
|
+
const PRE_DEADLINE_CAPTURE_MS = PROCESS_TABLE_READ_BOUND_MS + 1_000;
|
|
58
|
+
|
|
59
|
+
// Pre-kill snapshot of a tracked background job: always a fresh read, since
|
|
60
|
+
// the promotion baseline predates whatever the job started since (a server,
|
|
61
|
+
// an npm child); the baseline still stands when the fresh read fails. Marks
|
|
62
|
+
// the job killed, so its settle relies on this snapshot instead of an
|
|
63
|
+
// after-exit observation.
|
|
48
64
|
function prepareTrackedKill(entry) {
|
|
49
65
|
entry.killed = true;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
(complete) => complete || entry.tracker.capture().catch(() => false)
|
|
66
|
+
return (entry.baseline || Promise.resolve(false)).then(() =>
|
|
67
|
+
entry.tracker.capture().catch(() => entry.tracker.complete)
|
|
53
68
|
);
|
|
54
69
|
}
|
|
55
70
|
import { isPidAlive } from '../../../../shared/pid-liveness.mjs';
|
|
56
71
|
import {
|
|
57
72
|
renderShellCompletionEnvelope,
|
|
58
|
-
renderShellCompletionNotice,
|
|
59
73
|
shellCompletionInstruction,
|
|
60
74
|
} from '../../../../shared/task-notification-envelope.mjs';
|
|
75
|
+
import { formatDuration } from '../../../../shared/shell-output-summary.mjs';
|
|
76
|
+
import { readShellTaskOutput } from './lib/shell-task-output.mjs';
|
|
61
77
|
|
|
62
78
|
export { shellJobPublicTaskResult } from './lib/shell-job-insights.mjs';
|
|
63
79
|
|
|
@@ -105,7 +121,17 @@ function trackShellJobRecord(task, options) {
|
|
|
105
121
|
adoptShellWorkspaceAdmission(jobId, hold);
|
|
106
122
|
}
|
|
107
123
|
const tracker = task?.cwd ? createProcessTreeTracker({ pid: task.pid }) : null;
|
|
108
|
-
|
|
124
|
+
let resolveTerminated = null;
|
|
125
|
+
const entry = {
|
|
126
|
+
tracker,
|
|
127
|
+
killed: false,
|
|
128
|
+
baseline: null,
|
|
129
|
+
// Settles once every process of the job is confirmed gone (or the user
|
|
130
|
+
// force-released it): what `task cancel` waits on before it reports.
|
|
131
|
+
terminated: new Promise((resolve) => {
|
|
132
|
+
resolveTerminated = resolve;
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
109
135
|
if (tracker) {
|
|
110
136
|
backgroundTrackers.set(jobId, entry);
|
|
111
137
|
// Baseline identities while the promoted root is known to run, so a later
|
|
@@ -117,9 +143,24 @@ function trackShellJobRecord(task, options) {
|
|
|
117
143
|
}
|
|
118
144
|
let unsubscribe = null;
|
|
119
145
|
let settled = false;
|
|
146
|
+
// The native runner enforces the job's deadline itself, with no snapshot
|
|
147
|
+
// from here: one is taken shortly before it, so the timeout kill has the
|
|
148
|
+
// identities of what the job started since its promotion.
|
|
149
|
+
const deadlineMs = Math.floor(Number(options?.timeoutMs) || 0);
|
|
150
|
+
const deadlineCapture =
|
|
151
|
+
tracker && deadlineMs > 0
|
|
152
|
+
? setTimeout(
|
|
153
|
+
() => {
|
|
154
|
+
if (!settled) void tracker.capture().catch(() => false);
|
|
155
|
+
},
|
|
156
|
+
Math.max(0, deadlineMs - PRE_DEADLINE_CAPTURE_MS)
|
|
157
|
+
)
|
|
158
|
+
: null;
|
|
159
|
+
deadlineCapture?.unref?.();
|
|
120
160
|
const settle = (detail) => {
|
|
121
161
|
if (settled || !detail || detail.status === 'running') return;
|
|
122
162
|
settled = true;
|
|
163
|
+
clearTimeout(deadlineCapture);
|
|
123
164
|
try {
|
|
124
165
|
unsubscribe?.();
|
|
125
166
|
} catch {}
|
|
@@ -144,10 +185,13 @@ function trackShellJobRecord(task, options) {
|
|
|
144
185
|
stop();
|
|
145
186
|
clearTerminationHold(jobId);
|
|
146
187
|
if (backgroundTrackers.get(jobId) === entry) backgroundTrackers.delete(jobId);
|
|
188
|
+
resolveTerminated(true);
|
|
147
189
|
return completeShellJobRecord(jobId, normalized);
|
|
148
190
|
};
|
|
149
191
|
const stop = watchTermination({
|
|
150
192
|
tracker,
|
|
193
|
+
// A killed job is not over while processes it started still run.
|
|
194
|
+
sweep: killed,
|
|
151
195
|
onTerminated: finish,
|
|
152
196
|
releaseRequested: () => takeReleaseRequest(jobId),
|
|
153
197
|
onUnconfirmed: () => {
|
|
@@ -226,7 +270,18 @@ function shellJobFailureCause(detail) {
|
|
|
226
270
|
return `exited with code ${exitCode}`;
|
|
227
271
|
}
|
|
228
272
|
|
|
229
|
-
|
|
273
|
+
function completionStreams(detail) {
|
|
274
|
+
return {
|
|
275
|
+
stdout_path: detail?.stdoutPath || null,
|
|
276
|
+
stderr_path: detail?.mergeStderr === true ? null : detail?.stderrPath || null,
|
|
277
|
+
stdout_preview: detail?.stdoutPreview || '',
|
|
278
|
+
stderr_preview: detail?.mergeStderr === true ? '' : detail?.stderrPreview || '',
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** `output`: the unseen output to deliver (the task cursor's remainder). By
|
|
283
|
+
* default the whole capture is read with a fresh cursor, consuming nothing. */
|
|
284
|
+
export function buildShellCompletion(jobId, detail, { output } = {}) {
|
|
230
285
|
const exitCode = typeof detail?.exitCode === 'number' ? detail.exitCode : null;
|
|
231
286
|
const status = detail?.status || 'unknown';
|
|
232
287
|
const taskStatus = shellJobTaskStatus(detail);
|
|
@@ -234,29 +289,36 @@ export function buildShellCompletion(jobId, detail) {
|
|
|
234
289
|
// native task has no `error` of its own.
|
|
235
290
|
const failureCause = taskStatus === 'failed' ? shellJobFailureCause(detail) : null;
|
|
236
291
|
const reported = failureCause && !detail?.error ? { ...detail, error: failureCause } : detail;
|
|
292
|
+
const startedMs = Date.parse(detail?.startedAt || '');
|
|
293
|
+
const finishedMs = Date.parse(detail?.finishedAt || '');
|
|
294
|
+
// The completion carries the result itself — exit code, duration and the
|
|
295
|
+
// output (whole when short, else a summary with the log path) — so the
|
|
296
|
+
// agent never needs a follow-up read for it.
|
|
237
297
|
const body = renderShellCompletionEnvelope({
|
|
238
298
|
jobId,
|
|
239
299
|
status: taskStatus,
|
|
240
300
|
exitCode,
|
|
241
301
|
command: detail?.command,
|
|
242
|
-
summary: detail?.summary || failureCause || null,
|
|
243
|
-
stdoutPreview: detail?.stdoutPreview,
|
|
244
|
-
stderrPreview: detail?.stderrPreview,
|
|
245
|
-
mergeStderr: detail?.mergeStderr,
|
|
246
302
|
outputFile: detail?.stdoutPath,
|
|
247
303
|
error: failureCause,
|
|
304
|
+
result: [
|
|
305
|
+
[
|
|
306
|
+
exitCode !== null ? `exit code: ${exitCode}` : null,
|
|
307
|
+
Number.isFinite(startedMs) && Number.isFinite(finishedMs)
|
|
308
|
+
? `duration: ${formatDuration(finishedMs - startedMs)}`
|
|
309
|
+
: null,
|
|
310
|
+
]
|
|
311
|
+
.filter(Boolean)
|
|
312
|
+
.join(', ') || null,
|
|
313
|
+
output ?? readShellTaskOutput({ status: 'completed' }, completionStreams(detail), { terminal: true }),
|
|
314
|
+
]
|
|
315
|
+
.filter(Boolean)
|
|
316
|
+
.join('\n'),
|
|
248
317
|
});
|
|
249
318
|
return {
|
|
250
319
|
taskStatus,
|
|
251
320
|
body,
|
|
252
|
-
notification:
|
|
253
|
-
jobId,
|
|
254
|
-
status: taskStatus,
|
|
255
|
-
exitCode,
|
|
256
|
-
command: detail?.command,
|
|
257
|
-
outputFile: detail?.stdoutPath,
|
|
258
|
-
error: failureCause,
|
|
259
|
-
}),
|
|
321
|
+
notification: body,
|
|
260
322
|
instruction: shellCompletionInstruction({ jobId, status, exitCode }),
|
|
261
323
|
result: shellJobPublicTaskResult(reported),
|
|
262
324
|
error: failureCause,
|
|
@@ -343,6 +405,15 @@ export function killShellJob(jobId) {
|
|
|
343
405
|
return { ...attachJobInsights(normalizeShellJobDetail(detail)), killed: wasRunning };
|
|
344
406
|
}
|
|
345
407
|
|
|
408
|
+
/** True once every process of a tracked background job is confirmed gone,
|
|
409
|
+
* false while any is still alive when `timeoutMs` passes. A job without a
|
|
410
|
+
* process tracker (or one already finished) has nothing left to confirm. */
|
|
411
|
+
export async function waitShellJobTerminated(jobId, timeoutMs) {
|
|
412
|
+
const entry = backgroundTrackers.get(String(jobId || ''));
|
|
413
|
+
if (!entry) return true;
|
|
414
|
+
return withinBound(entry.terminated, timeoutMs, false);
|
|
415
|
+
}
|
|
416
|
+
|
|
346
417
|
export function clearShellJobNotifyCtx(jobId) {
|
|
347
418
|
jobNotifyCtxByJobId.delete(jobId);
|
|
348
419
|
}
|
|
@@ -383,7 +454,14 @@ export function watchBackgroundShellJob(jobId, notifyCtx) {
|
|
|
383
454
|
backgroundShellJobWatchers.delete(jobId);
|
|
384
455
|
jobNotifyCtxByJobId.delete(jobId);
|
|
385
456
|
const enriched = attachJobInsights(normalizeShellJobDetail(detail));
|
|
386
|
-
|
|
457
|
+
// The notice carries only what the agent has not seen yet: the task's
|
|
458
|
+
// cursor remainder. Kept on the task so a wait/read that reaches the
|
|
459
|
+
// finished task first returns that same remainder (and suppresses the
|
|
460
|
+
// notice) instead of an empty read.
|
|
461
|
+
const task = getBackgroundTask(jobId);
|
|
462
|
+
const output = task ? readShellTaskOutput(task, completionStreams(enriched), { terminal: true }) : undefined;
|
|
463
|
+
if (task) task.completionOutput = output;
|
|
464
|
+
const completion = buildShellCompletion(jobId, enriched, { output });
|
|
387
465
|
const completedTask = completeBackgroundTask(jobId, {
|
|
388
466
|
status: completion.taskStatus,
|
|
389
467
|
result: completion.result,
|
|
@@ -471,7 +549,7 @@ export async function promoteForegroundShellJob({
|
|
|
471
549
|
}
|
|
472
550
|
// Added before tracking: the record's settlement removes it again.
|
|
473
551
|
promotedJobIds.add(native.jobId);
|
|
474
|
-
trackShellJobRecord(native, { ownerSessionId, clientHostPid, startedAtMs: started });
|
|
552
|
+
trackShellJobRecord(native, { ownerSessionId, clientHostPid, startedAtMs: started, timeoutMs });
|
|
475
553
|
return native;
|
|
476
554
|
}
|
|
477
555
|
|
|
@@ -593,18 +671,26 @@ export async function shutdownShellJobs(_reason = 'runtime-close', { scope = nul
|
|
|
593
671
|
(task) => task.status === 'running' && (!scoped || String(task.ownerSessionId || '') === ownerSessionId)
|
|
594
672
|
);
|
|
595
673
|
let cancelledWatchers = 0;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
674
|
+
// Identities first, as every other kill does: once a root dies, what it
|
|
675
|
+
// started outside its process group is reparented and can no longer be
|
|
676
|
+
// found. The cancel is sent at the snapshot bound regardless.
|
|
677
|
+
await killAfterSnapshot(Promise.all(prepareOwnedShellTreeKills(scoped ? ownerSessionId : null)), () => {
|
|
678
|
+
for (const task of jobs) {
|
|
679
|
+
if (cancelBackgroundShellJobWatch(task.jobId)) cancelledWatchers += 1;
|
|
680
|
+
try {
|
|
681
|
+
cancelNativeTask(task.jobId);
|
|
682
|
+
} catch {}
|
|
683
|
+
jobNotifyCtxByJobId.delete(task.jobId);
|
|
684
|
+
}
|
|
685
|
+
});
|
|
603
686
|
const settled = await Promise.all(jobs.map((task) => waitNativeTask(task.jobId, 1_200).catch(() => null)));
|
|
604
687
|
// A process still running after the wait is not forgotten: it keeps being
|
|
605
688
|
// stopped until its exit is confirmed.
|
|
606
689
|
const unconfirmed = jobs.filter((_, index) => settled[index]?.status === 'running').map((task) => task.jobId);
|
|
607
690
|
for (const jobId of unconfirmed) retryShutdownSurvivor(jobId, 1);
|
|
691
|
+
// The roots are gone; what they started outside their group is swept by
|
|
692
|
+
// each job's termination watch. Shutdown waits for that, bounded.
|
|
693
|
+
const treesGone = await Promise.all(jobs.map((task) => waitShellJobTerminated(task.jobId, SHUTDOWN_TREE_CONFIRM_MS)));
|
|
608
694
|
if (scoped) {
|
|
609
695
|
// The owning session is gone: nothing can query its tasks again, so
|
|
610
696
|
// drop ALL of its retained state — not only the ones that happened to
|
|
@@ -624,6 +710,7 @@ export async function shutdownShellJobs(_reason = 'runtime-close', { scope = nul
|
|
|
624
710
|
cancelledJobs: jobs.length,
|
|
625
711
|
confirmedJobs: settled.filter((task) => task && task.status !== 'running').length,
|
|
626
712
|
unconfirmedJobs: unconfirmed,
|
|
713
|
+
treeUnconfirmedJobs: jobs.filter((_, index) => !treesGone[index]).map((task) => task.jobId),
|
|
627
714
|
cancelledWatchers,
|
|
628
715
|
};
|
|
629
716
|
}
|
|
@@ -213,6 +213,11 @@ function buildSuccessSummary(command, stdout) {
|
|
|
213
213
|
return null;
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// A line redrawn with `\r` shows only its last non-empty frame.
|
|
217
|
+
function lastVisibleFrame(frames) {
|
|
218
|
+
return [...frames].reverse().find((frame) => frame !== '') ?? '';
|
|
219
|
+
}
|
|
220
|
+
|
|
216
221
|
export function foldCarriageReturnFrames(value) {
|
|
217
222
|
const text = String(value ?? '');
|
|
218
223
|
if (!/\r(?!\n)/.test(text)) return null;
|
|
@@ -223,12 +228,32 @@ export function foldCarriageReturnFrames(value) {
|
|
|
223
228
|
const frames = line.split('\r');
|
|
224
229
|
if (frames.length === 1) return line;
|
|
225
230
|
omitted += frames.length - 1;
|
|
226
|
-
return
|
|
231
|
+
return lastVisibleFrame(frames);
|
|
227
232
|
});
|
|
228
233
|
if (!omitted) return null;
|
|
229
234
|
return `${out.join('\n')}\n... [${omitted} overwritten progress frames omitted]`;
|
|
230
235
|
}
|
|
231
236
|
|
|
237
|
+
/** The same `\r` folding for output that arrives in chunks: the line still
|
|
238
|
+
* open at a chunk's end keeps its state, so a progress bar redrawn across
|
|
239
|
+
* deliveries shows its latest frame once, while plain continuation text of
|
|
240
|
+
* that line is passed through as is. */
|
|
241
|
+
export function createCarriageReturnFolder() {
|
|
242
|
+
let open = '';
|
|
243
|
+
return function fold(chunk) {
|
|
244
|
+
const lines = String(chunk ?? '')
|
|
245
|
+
.replace(/\r\n/g, '\n')
|
|
246
|
+
.split('\n');
|
|
247
|
+
const out = lines.map((line, index) => {
|
|
248
|
+
const redraw = line.includes('\r');
|
|
249
|
+
const visible = redraw ? lastVisibleFrame((index === 0 ? open + line : line).split('\r')) : line;
|
|
250
|
+
if (index === lines.length - 1) open = index === 0 && !redraw ? open + line : visible;
|
|
251
|
+
return visible;
|
|
252
|
+
});
|
|
253
|
+
return out.join('\n');
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
232
257
|
function hasDiagnosticHazard(value) {
|
|
233
258
|
return /\b(?:errors?|warnings?|failed|failure|panic|fatal|xpass|xfailed|cancelled|todo|skipped)\b/i.test(
|
|
234
259
|
String(value ?? '')
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { countSplitLines } from './path-utils.mjs';
|
|
2
2
|
import { TOOL_OUTPUT_MAX_BYTES } from './tool-output-limit.mjs';
|
|
3
|
+
import { DELIVERY_FULL_CHARS, fitPieces, shellTextPiece } from './lib/shell-delivery.mjs';
|
|
3
4
|
|
|
4
5
|
const SHELL_OUTPUT_MAX_BYTES = TOOL_OUTPUT_MAX_BYTES;
|
|
5
6
|
|
|
@@ -7,11 +8,6 @@ export const SMART_BASH_MAX_LINES = 400;
|
|
|
7
8
|
export const SMART_BASH_MAX_BYTES = TOOL_OUTPUT_MAX_BYTES;
|
|
8
9
|
export const SMART_BASH_HEAD_LINES = 80;
|
|
9
10
|
export const SMART_BASH_TAIL_LINES = 80;
|
|
10
|
-
export const BACKGROUND_PARTIAL_OUTPUT_MAX_BYTES = 10 * 1024;
|
|
11
|
-
const BACKGROUND_PARTIAL_OUTPUT_HEAD_BYTES = 2 * 1024;
|
|
12
|
-
|
|
13
|
-
const BACKGROUND_PARTIAL_TRUNCATION_MARKER = '\n\n... [partial output truncated; head and tail shown] ...\n\n';
|
|
14
|
-
|
|
15
11
|
function utf8Prefix(value, maxBytes) {
|
|
16
12
|
const buffer = Buffer.from(String(value ?? ''), 'utf8');
|
|
17
13
|
if (buffer.length <= maxBytes) return buffer.toString('utf8');
|
|
@@ -20,31 +16,20 @@ function utf8Prefix(value, maxBytes) {
|
|
|
20
16
|
return buffer.subarray(0, end).toString('utf8');
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (buffer.length <= maxBytes) return buffer.toString('utf8');
|
|
26
|
-
let start = buffer.length - maxBytes;
|
|
27
|
-
while (start < buffer.length && (buffer[start] & 0xc0) === 0x80) start += 1;
|
|
28
|
-
return buffer.subarray(start).toString('utf8');
|
|
29
|
-
}
|
|
30
|
-
|
|
19
|
+
// Partial output of a promoted command that has no task record, in the shared
|
|
20
|
+
// delivery shape (lib/shell-delivery.mjs).
|
|
31
21
|
export function renderBackgroundPartialOutput(stdout, stderr) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const markerBytes = Buffer.byteLength(BACKGROUND_PARTIAL_TRUNCATION_MARKER, 'utf8');
|
|
42
|
-
const tailBytes = BACKGROUND_PARTIAL_OUTPUT_MAX_BYTES - BACKGROUND_PARTIAL_OUTPUT_HEAD_BYTES - markerBytes;
|
|
43
|
-
return (
|
|
44
|
-
utf8Prefix(merged, BACKGROUND_PARTIAL_OUTPUT_HEAD_BYTES) +
|
|
45
|
-
BACKGROUND_PARTIAL_TRUNCATION_MARKER +
|
|
46
|
-
utf8Suffix(merged, tailBytes)
|
|
22
|
+
const streams = [
|
|
23
|
+
['stdout', String(stdout ?? '')],
|
|
24
|
+
['stderr', String(stderr ?? '')],
|
|
25
|
+
].filter(([, text]) => text);
|
|
26
|
+
const headings = streams.map(([label]) => `[partial ${label}]\n`);
|
|
27
|
+
const fixed = headings.reduce((sum, heading) => sum + heading.length, 2 * Math.max(0, streams.length - 1));
|
|
28
|
+
const bodies = fitPieces(
|
|
29
|
+
streams.map(([label, text]) => shellTextPiece(text, { label })),
|
|
30
|
+
DELIVERY_FULL_CHARS - fixed
|
|
47
31
|
);
|
|
32
|
+
return streams.map((_, index) => `${headings[index]}${bodies[index]}`).join('\n\n');
|
|
48
33
|
}
|
|
49
34
|
|
|
50
35
|
export function smartMiddleTruncate(content) {
|
|
@@ -9,10 +9,13 @@ import {
|
|
|
9
9
|
shellJobPublicTaskResult,
|
|
10
10
|
shellJobTaskStatus,
|
|
11
11
|
waitForShellJob,
|
|
12
|
+
waitShellJobTerminated,
|
|
12
13
|
} from './shell-jobs.mjs';
|
|
14
|
+
import { beginSessionTaskWait } from '../../session/manager/runtime-liveness.mjs';
|
|
13
15
|
import { TASK_WAIT_TIMEOUT_DEFAULT_MS, TASK_WAIT_TIMEOUT_MAX_MS, TASK_WAIT_TIMEOUT_MIN_MS } from './builtin-tools.mjs';
|
|
16
|
+
import { AGENT_TASK_WAIT_DEFAULT_MS } from './wait-budgets.mjs';
|
|
14
17
|
import { getAbortSignalForSession } from '../../session/abort-lookup.mjs';
|
|
15
|
-
import { beginInterruptibleTaskWait } from '../../session/task-wait-control.mjs';
|
|
18
|
+
import { TASK_WAIT_WAKE_NOTIFICATION, beginInterruptibleTaskWait } from '../../session/task-wait-control.mjs';
|
|
16
19
|
import {
|
|
17
20
|
acknowledgeBackgroundTaskCompletion,
|
|
18
21
|
cancelBackgroundTask,
|
|
@@ -31,6 +34,18 @@ import { readShellTaskOutput } from './lib/shell-task-output.mjs';
|
|
|
31
34
|
// wait returns the moment the native task settles, so this only bounds a
|
|
32
35
|
// process that ignores the kill.
|
|
33
36
|
const TASK_CANCEL_CONFIRM_MS = 5_000;
|
|
37
|
+
// Ceiling for confirming that every process the task started is gone once its
|
|
38
|
+
// root exited: a sweep is a table read, the kill and a second read (each read
|
|
39
|
+
// ~1-2 s on Windows).
|
|
40
|
+
const TASK_CANCEL_TREE_CONFIRM_MS = 15_000;
|
|
41
|
+
|
|
42
|
+
function cancelPendingText(taskId) {
|
|
43
|
+
return [
|
|
44
|
+
'status: cancel-pending',
|
|
45
|
+
`task_id: ${taskId}`,
|
|
46
|
+
'the kill was sent and the command itself exited, but processes it started are still alive. They keep being terminated and stay tracked (the folder stays held) until they are gone; re-run task cancel or task read to check again.',
|
|
47
|
+
].join('\n');
|
|
48
|
+
}
|
|
34
49
|
|
|
35
50
|
function recoveredTaskMatches(record, options = {}) {
|
|
36
51
|
const context =
|
|
@@ -84,12 +99,22 @@ function renderTaskCancelSuccess(taskId, task) {
|
|
|
84
99
|
|
|
85
100
|
// Clamp lives next to the caller; the bounds themselves are published by the
|
|
86
101
|
// tool schema, so an out-of-range request is corrected rather than rejected.
|
|
87
|
-
function resolveTaskWaitTimeoutMs(value) {
|
|
102
|
+
function resolveTaskWaitTimeoutMs(value, defaultMs = TASK_WAIT_TIMEOUT_DEFAULT_MS) {
|
|
88
103
|
const raw = Number(value);
|
|
89
|
-
if (!Number.isFinite(raw) || raw <= 0) return
|
|
104
|
+
if (!Number.isFinite(raw) || raw <= 0) return defaultMs;
|
|
90
105
|
return Math.min(TASK_WAIT_TIMEOUT_MAX_MS, Math.max(TASK_WAIT_TIMEOUT_MIN_MS, Math.floor(raw)));
|
|
91
106
|
}
|
|
92
107
|
|
|
108
|
+
// Agents run on the 5 m prompt cache, so their default wakes before it lapses;
|
|
109
|
+
// the Lead keeps the 1 h cache and the longer default.
|
|
110
|
+
async function defaultTaskWaitMs(sessionId) {
|
|
111
|
+
if (!sessionId) return TASK_WAIT_TIMEOUT_DEFAULT_MS;
|
|
112
|
+
const { getSession } = await import('../../session/manager.mjs');
|
|
113
|
+
const session = getSession(String(sessionId));
|
|
114
|
+
const agentOwned = session?.owner === 'agent' || session?.visibility === 'agent-only';
|
|
115
|
+
return agentOwned ? AGENT_TASK_WAIT_DEFAULT_MS : TASK_WAIT_TIMEOUT_DEFAULT_MS;
|
|
116
|
+
}
|
|
117
|
+
|
|
93
118
|
// Read-only counts of this runtime's shell work owned by OTHER sessions.
|
|
94
119
|
// Admission is runtime-wide while task list/cancel are session-scoped, so
|
|
95
120
|
// without this those holders are invisible. Counts only: another session's
|
|
@@ -128,8 +153,15 @@ async function listTasks(options) {
|
|
|
128
153
|
return sections.join('\n');
|
|
129
154
|
}
|
|
130
155
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
156
|
+
// Ceiling hits per still-running task, so a wait that keeps timing out on the
|
|
157
|
+
// same job can suggest a different move. Keyed by the task object, so the count
|
|
158
|
+
// goes with the task however it settles or is removed.
|
|
159
|
+
const waitCeilingHits = new WeakMap();
|
|
160
|
+
|
|
161
|
+
// Blocks until the shell task settles, the timeout elapses, the turn is
|
|
162
|
+
// cancelled, or anything deliverable is queued for this session (user input,
|
|
163
|
+
// steering, or any task notification: an agent or another shell job
|
|
164
|
+
// finishing). Returns what cut the wait short.
|
|
133
165
|
async function waitForShellTask(task, taskId, args, options) {
|
|
134
166
|
// The turn's abort signal must cut the wait short; a cancelled turn
|
|
135
167
|
// cannot sit out the remaining ceiling.
|
|
@@ -140,9 +172,12 @@ async function waitForShellTask(task, taskId, args, options) {
|
|
|
140
172
|
turnSignal = null;
|
|
141
173
|
}
|
|
142
174
|
const waitControl = beginInterruptibleTaskWait(options?.sessionId, turnSignal);
|
|
175
|
+
const releaseWaitState = beginSessionTaskWait(options?.sessionId);
|
|
143
176
|
const waitSignal = waitControl.signal;
|
|
144
|
-
const waitTimeoutMs = resolveTaskWaitTimeoutMs(args.timeout_ms);
|
|
177
|
+
const waitTimeoutMs = resolveTaskWaitTimeoutMs(args.timeout_ms, await defaultTaskWaitMs(options?.sessionId));
|
|
145
178
|
let interruptedByUser = false;
|
|
179
|
+
let notified = false;
|
|
180
|
+
let notifiedTaskId = null;
|
|
146
181
|
try {
|
|
147
182
|
// A shell task whose descendants outlived the shell process has no
|
|
148
183
|
// native job to subscribe to — the process it belonged to is gone.
|
|
@@ -175,9 +210,12 @@ async function waitForShellTask(task, taskId, args, options) {
|
|
|
175
210
|
}
|
|
176
211
|
} finally {
|
|
177
212
|
interruptedByUser = waitControl.interruptedByUser;
|
|
213
|
+
notified = waitControl.wakeReason === TASK_WAIT_WAKE_NOTIFICATION;
|
|
214
|
+
notifiedTaskId = waitControl.wakeDetail;
|
|
178
215
|
waitControl.dispose();
|
|
216
|
+
releaseWaitState();
|
|
179
217
|
}
|
|
180
|
-
return interruptedByUser;
|
|
218
|
+
return { interruptedByUser, notified, notifiedTaskId };
|
|
181
219
|
}
|
|
182
220
|
|
|
183
221
|
// Outcome of a shell cancel: `outcome` is 'cancelled' only when the process is
|
|
@@ -208,6 +246,11 @@ async function cancelShellTaskOutcome(task, taskId, options) {
|
|
|
208
246
|
].join('\n'),
|
|
209
247
|
};
|
|
210
248
|
}
|
|
249
|
+
// The root exited; what it started outside its process group is swept
|
|
250
|
+
// by the job's termination watch. Cancelled means all of it is gone.
|
|
251
|
+
if (confirmed && !(await waitShellJobTerminated(taskId, TASK_CANCEL_TREE_CONFIRM_MS))) {
|
|
252
|
+
return { outcome: 'unconfirmed', text: cancelPendingText(taskId) };
|
|
253
|
+
}
|
|
211
254
|
if (!confirmed) {
|
|
212
255
|
// No terminal event and no observable task: absence of
|
|
213
256
|
// state is not proof of termination. Stop tracking it so it
|
|
@@ -228,6 +271,10 @@ async function cancelShellTaskOutcome(task, taskId, options) {
|
|
|
228
271
|
cancelBackgroundShellJobWatch(taskId);
|
|
229
272
|
clearShellJobNotifyCtx(taskId);
|
|
230
273
|
cancelBackgroundTask(taskId, 'cancelled by task control');
|
|
274
|
+
// A task whose own cancel hook kills (what a finished command left running)
|
|
275
|
+
// reports what that kill confirmed.
|
|
276
|
+
const hookKill = job ? null : await task.cancelOutcome;
|
|
277
|
+
if (hookKill && hookKill.terminated !== true) return { outcome: 'unconfirmed', text: cancelPendingText(taskId) };
|
|
231
278
|
return job || wasRunning
|
|
232
279
|
? {
|
|
233
280
|
outcome: 'cancelled',
|
|
@@ -323,27 +370,59 @@ export async function executeTaskTool(args, options = {}) {
|
|
|
323
370
|
|
|
324
371
|
if (action === 'read' || action === 'wait') {
|
|
325
372
|
let waitInterruptedByUser = false;
|
|
373
|
+
let waitNotified = false;
|
|
374
|
+
let notifiedTaskId = null;
|
|
326
375
|
if (action === 'wait') {
|
|
327
376
|
if (!isShellTask) return `Error: task wait is only available for shell tasks: ${taskId}`;
|
|
328
|
-
|
|
377
|
+
({
|
|
378
|
+
interruptedByUser: waitInterruptedByUser,
|
|
379
|
+
notified: waitNotified,
|
|
380
|
+
notifiedTaskId,
|
|
381
|
+
} = await waitForShellTask(task, taskId, args, options));
|
|
329
382
|
}
|
|
330
383
|
if (isShellTask) refreshShellTask(taskId, { includeRunning: true });
|
|
331
384
|
const latest = getBackgroundTask(taskId, { context: options }) || task;
|
|
385
|
+
// A finished task whose completion notice is still unconsumed returns that
|
|
386
|
+
// notice's output (the unseen remainder) once; everything else reads the
|
|
387
|
+
// cursor. A wait that leaves the job running carries the progress digest.
|
|
388
|
+
const pendingCompletionOutput =
|
|
389
|
+
isShellTask &&
|
|
390
|
+
latest.status !== 'running' &&
|
|
391
|
+
latest.completionAcknowledged !== true &&
|
|
392
|
+
args.output !== 'tail' &&
|
|
393
|
+
typeof latest.completionOutput === 'string'
|
|
394
|
+
? latest.completionOutput
|
|
395
|
+
: null;
|
|
332
396
|
const visible =
|
|
333
397
|
isShellTask && latest.result && typeof latest.result === 'object'
|
|
334
|
-
? {
|
|
398
|
+
? {
|
|
399
|
+
...latest,
|
|
400
|
+
resultText:
|
|
401
|
+
pendingCompletionOutput ??
|
|
402
|
+
readShellTaskOutput(latest, latest.result, {
|
|
403
|
+
output: args.output,
|
|
404
|
+
digest: action === 'wait' && latest.status === 'running',
|
|
405
|
+
}),
|
|
406
|
+
}
|
|
335
407
|
: latest;
|
|
336
408
|
let rendered = renderBackgroundTask(visible, { includeResult: true });
|
|
337
409
|
if (action === 'wait' && latest.status === 'running') {
|
|
338
410
|
if (waitInterruptedByUser) {
|
|
339
411
|
rendered +=
|
|
340
|
-
'\n\nWait
|
|
412
|
+
'\n\nWait ended by new user input; task still running in the background. Handle the new message first.';
|
|
413
|
+
} else if (waitNotified) {
|
|
414
|
+
const from = notifiedTaskId && notifiedTaskId !== taskId ? ` (task_id: ${notifiedTaskId})` : '';
|
|
415
|
+
rendered += `\n\nWait ended because a notification arrived${from}; it is delivered to you now. This task (${taskId}) is still running in the background.`;
|
|
341
416
|
} else {
|
|
417
|
+
const hits = (waitCeilingHits.get(latest) || 0) + 1;
|
|
418
|
+
waitCeilingHits.set(latest, hits);
|
|
342
419
|
// In-band report cue: the static "periodic reports" rule does not
|
|
343
420
|
// survive a long tool loop, so a ceiling-hit wait carries the
|
|
344
421
|
// protocol reminder itself.
|
|
345
422
|
rendered +=
|
|
346
|
-
|
|
423
|
+
hits > 1
|
|
424
|
+
? `\n\nWait ceiling reached again (${hits} times); task still running. Check whether it is progressing (task read output:tail, or the log file) and cancel it if it is stuck; otherwise continue other work — its result is delivered automatically.`
|
|
425
|
+
: '\n\nWait ceiling reached; task still running and its result is delivered automatically. If periodic reports were requested, write the user-facing report now; otherwise continue independent work or end the turn.';
|
|
347
426
|
}
|
|
348
427
|
}
|
|
349
428
|
if (acknowledgeBackgroundTaskCompletion(taskId, { context: options })) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The one place the shell hold and the task wait budgets are set. Every
|
|
2
|
+
// schema, clamp, description and result text reads them from here.
|
|
3
|
+
//
|
|
4
|
+
// Shell hold: how long a foreground command may hold the tool call before it
|
|
5
|
+
// moves to the background (it keeps running; completion is delivered
|
|
6
|
+
// automatically). New user input ends the hold early. `wait_ms` picks a
|
|
7
|
+
// different hold for one call, clamped to MIN..MAX.
|
|
8
|
+
export const SHELL_HOLD_DEFAULT_MS = 60_000;
|
|
9
|
+
export const SHELL_HOLD_MIN_MS = 1_000;
|
|
10
|
+
export const SHELL_HOLD_MAX_MS = 600_000;
|
|
11
|
+
|
|
12
|
+
// `task wait` ceiling. The wait returns the moment the task settles or new
|
|
13
|
+
// user input arrives, so the ceiling only bounds a still-running task. The
|
|
14
|
+
// floor stops a caller from turning wait into a busy poll. An agent's default
|
|
15
|
+
// stays under the 5 m prompt-cache TTL with room for the turn's own generation
|
|
16
|
+
// time, so a wait that runs to the ceiling wakes to a warm cache.
|
|
17
|
+
export const TASK_WAIT_DEFAULT_MS = 300_000;
|
|
18
|
+
export const AGENT_TASK_WAIT_DEFAULT_MS = 240_000;
|
|
19
|
+
export const TASK_WAIT_MIN_MS = 10_000;
|
|
20
|
+
export const TASK_WAIT_MAX_MS = 3_600_000;
|
|
21
|
+
|
|
22
|
+
/** Whole seconds for descriptions and result text. */
|
|
23
|
+
export const budgetSeconds = (ms) => Math.round(ms / 1000);
|