@mjasnikovs/pi-task 0.38.29 → 0.38.31
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/dist/config/config.d.ts +70 -70
- package/dist/config/config.js +26 -35
- package/dist/config/extension-list.d.ts +6 -5
- package/dist/config/extension-list.js +3 -2
- package/dist/config/reasoning-args.d.ts +9 -7
- package/dist/config/reasoning-args.js +12 -10
- package/dist/config/reasoning.d.ts +44 -105
- package/dist/config/reasoning.js +27 -704
- package/dist/config/register.d.ts +34 -48
- package/dist/config/register.js +41 -51
- package/dist/config/tool-list.d.ts +16 -16
- package/dist/config/tool-list.js +1 -1
- package/dist/index.js +2 -0
- package/dist/remote/bridge.d.ts +19 -10
- package/dist/remote/bridge.js +3 -2
- package/dist/remote/broadcast.js +3 -1
- package/dist/remote/events.js +12 -11
- package/dist/remote/history.d.ts +1 -1
- package/dist/remote/protocol.d.ts +6 -3
- package/dist/remote/protocol.js +2 -1
- package/dist/remote/push.d.ts +16 -16
- package/dist/remote/push.js +27 -27
- package/dist/remote/register.d.ts +3 -3
- package/dist/remote/register.js +17 -19
- package/dist/remote/server.d.ts +9 -8
- package/dist/remote/server.js +15 -14
- package/dist/remote/session-state.d.ts +5 -4
- package/dist/remote/session-state.js +8 -5
- package/dist/remote/sw.d.ts +7 -6
- package/dist/remote/sw.js +7 -6
- package/dist/remote/tailscale.d.ts +4 -2
- package/dist/remote/tailscale.js +4 -2
- package/dist/remote/ui-highlight.js +6 -5
- package/dist/remote/ui-render.js +4 -4
- package/dist/remote/ui-script.js +24 -24
- package/dist/remote/ui-styles.d.ts +1 -1
- package/dist/remote/ui-styles.js +10 -13
- package/dist/remote/ui-tools.js +9 -6
- package/dist/shared/child-extensions.d.ts +29 -17
- package/dist/shared/child-extensions.js +29 -17
- package/dist/shared/child-output.d.ts +30 -24
- package/dist/shared/child-output.js +25 -17
- package/dist/shared/child-process.d.ts +47 -40
- package/dist/shared/child-process.js +50 -59
- package/dist/shared/command-watchdog.d.ts +85 -16
- package/dist/shared/command-watchdog.js +115 -21
- package/dist/shared/fs-text.d.ts +16 -10
- package/dist/shared/fs-text.js +16 -10
- package/dist/shared/git-runner.d.ts +25 -25
- package/dist/shared/git-runner.js +25 -25
- package/dist/shared/leaked-tool-call.d.ts +17 -11
- package/dist/shared/leaked-tool-call.js +23 -15
- package/dist/shared/model-endpoint.d.ts +29 -16
- package/dist/shared/model-endpoint.js +33 -21
- package/dist/shared/pi-invocation.d.ts +7 -4
- package/dist/shared/pi-invocation.js +12 -7
- package/dist/shared/pkg-version.d.ts +13 -5
- package/dist/shared/pkg-version.js +13 -5
- package/dist/shared/reasoning-capability.d.ts +35 -24
- package/dist/shared/reasoning-capability.js +35 -24
- package/dist/shared/stream-watchdog.d.ts +60 -44
- package/dist/shared/stream-watchdog.js +62 -45
- package/dist/task/accept-debt.d.ts +41 -43
- package/dist/task/accept-debt.js +73 -65
- package/dist/task/api-synthesis.d.ts +24 -21
- package/dist/task/api-synthesis.js +32 -26
- package/dist/task/apis-contract.d.ts +32 -64
- package/dist/task/apis-contract.js +32 -64
- package/dist/task/artifact-closure.d.ts +27 -13
- package/dist/task/artifact-closure.js +95 -67
- package/dist/task/auto-commit.d.ts +46 -35
- package/dist/task/auto-commit.js +51 -38
- package/dist/task/auto-io.d.ts +45 -25
- package/dist/task/auto-io.js +57 -29
- package/dist/task/auto-orchestrator.d.ts +26 -24
- package/dist/task/auto-orchestrator.js +192 -165
- package/dist/task/auto-prompts.d.ts +36 -24
- package/dist/task/auto-prompts.js +40 -26
- package/dist/task/autofix-ledger.d.ts +27 -25
- package/dist/task/autofix-ledger.js +29 -26
- package/dist/task/batch-test-task.d.ts +20 -12
- package/dist/task/batch-test-task.js +67 -60
- package/dist/task/boot-probe.d.ts +60 -44
- package/dist/task/boot-probe.js +91 -72
- package/dist/task/cancel-input.d.ts +30 -16
- package/dist/task/cancel-input.js +20 -11
- package/dist/task/cancel-points.d.ts +27 -20
- package/dist/task/cancel-points.js +30 -22
- package/dist/task/child-runner.d.ts +124 -55
- package/dist/task/child-runner.js +298 -90
- package/dist/task/child-status.d.ts +23 -16
- package/dist/task/child-status.js +23 -16
- package/dist/task/clamp-output.js +12 -5
- package/dist/task/command-run.d.ts +31 -28
- package/dist/task/command-run.js +44 -35
- package/dist/task/command-shrink.d.ts +25 -18
- package/dist/task/command-shrink.js +37 -31
- package/dist/task/command-watchdog.d.ts +9 -6
- package/dist/task/command-watchdog.js +21 -15
- package/dist/task/context-attribution.d.ts +34 -26
- package/dist/task/context-attribution.js +34 -26
- package/dist/task/context-silence.d.ts +39 -29
- package/dist/task/context-silence.js +35 -25
- package/dist/task/context-usage.d.ts +16 -9
- package/dist/task/context-usage.js +16 -9
- package/dist/task/contracts.d.ts +8 -4
- package/dist/task/contracts.js +25 -17
- package/dist/task/coverage-loop.d.ts +22 -18
- package/dist/task/coverage-loop.js +35 -30
- package/dist/task/critique-probes.d.ts +13 -14
- package/dist/task/critique-probes.js +50 -39
- package/dist/task/debug-log.d.ts +13 -5
- package/dist/task/debug-log.js +32 -20
- package/dist/task/decompose-fidelity.d.ts +11 -9
- package/dist/task/decompose-fidelity.js +38 -33
- package/dist/task/decompose-granularity.d.ts +41 -38
- package/dist/task/decompose-granularity.js +41 -38
- package/dist/task/deep-render-check.d.ts +22 -14
- package/dist/task/deep-render-check.js +40 -31
- package/dist/task/dropped-input.d.ts +12 -7
- package/dist/task/dropped-input.js +5 -2
- package/dist/task/enforce-attribution.d.ts +38 -47
- package/dist/task/enforce-attribution.js +46 -52
- package/dist/task/enforce-guidelines.d.ts +31 -20
- package/dist/task/enforce-guidelines.js +32 -21
- package/dist/task/enrichment.d.ts +7 -2
- package/dist/task/enrichment.js +26 -14
- package/dist/task/env-notes.d.ts +16 -7
- package/dist/task/env-notes.js +48 -31
- package/dist/task/env-template-closure.d.ts +4 -4
- package/dist/task/env-template-closure.js +42 -34
- package/dist/task/external-context.d.ts +28 -21
- package/dist/task/external-context.js +17 -12
- package/dist/task/failure-classifier.d.ts +4 -5
- package/dist/task/failure-classifier.js +30 -8
- package/dist/task/file-inventory.d.ts +15 -11
- package/dist/task/file-inventory.js +25 -22
- package/dist/task/final-gate-fix.d.ts +74 -86
- package/dist/task/final-gate-fix.js +97 -116
- package/dist/task/final-gate-progress.d.ts +29 -46
- package/dist/task/final-gate-progress.js +40 -51
- package/dist/task/final-gate.d.ts +64 -97
- package/dist/task/final-gate.js +192 -199
- package/dist/task/fix-child.d.ts +21 -27
- package/dist/task/fix-child.js +21 -27
- package/dist/task/foreign-path.d.ts +6 -5
- package/dist/task/foreign-path.js +0 -0
- package/dist/task/frozen-conflict.d.ts +9 -10
- package/dist/task/frozen-conflict.js +61 -64
- package/dist/task/frozen-path-guard.d.ts +35 -14
- package/dist/task/frozen-path-guard.js +56 -39
- package/dist/task/gate-child.d.ts +27 -28
- package/dist/task/gate-child.js +36 -35
- package/dist/task/gate-deps.d.ts +34 -27
- package/dist/task/gate-deps.js +169 -159
- package/dist/task/gate-tally.d.ts +77 -80
- package/dist/task/gate-tally.js +65 -68
- package/dist/task/git-state-guard.d.ts +15 -11
- package/dist/task/git-state-guard.js +76 -66
- package/dist/task/impl-widget.d.ts +25 -16
- package/dist/task/impl-widget.js +27 -17
- package/dist/task/implementation-guards.d.ts +26 -0
- package/dist/task/implementation-guards.js +177 -0
- package/dist/task/implementation-thinking.d.ts +33 -31
- package/dist/task/implementation-thinking.js +5 -6
- package/dist/task/implementation-turn.d.ts +39 -31
- package/dist/task/implementation-turn.js +41 -28
- package/dist/task/inline-markdown.d.ts +20 -7
- package/dist/task/inline-markdown.js +15 -6
- package/dist/task/launch-config-gap.js +25 -39
- package/dist/task/launch-contract.d.ts +18 -21
- package/dist/task/launch-contract.js +28 -30
- package/dist/task/launch-manifest.d.ts +6 -2
- package/dist/task/launch-manifest.js +35 -34
- package/dist/task/ledger.js +16 -14
- package/dist/task/lint-fix.d.ts +6 -8
- package/dist/task/lint-fix.js +67 -69
- package/dist/task/loop-detector.d.ts +27 -8
- package/dist/task/loop-detector.js +38 -14
- package/dist/task/mid-run-input.d.ts +17 -15
- package/dist/task/mid-run-input.js +17 -15
- package/dist/task/orchestrator.d.ts +24 -28
- package/dist/task/orchestrator.js +89 -66
- package/dist/task/orientation.d.ts +18 -23
- package/dist/task/orientation.js +24 -31
- package/dist/task/owned-freeze-conflict.d.ts +21 -20
- package/dist/task/owned-freeze-conflict.js +52 -85
- package/dist/task/owned-freeze-reassign.d.ts +40 -60
- package/dist/task/owned-freeze-reassign.js +41 -61
- package/dist/task/parsers.d.ts +4 -2
- package/dist/task/parsers.js +4 -4
- package/dist/task/phases.d.ts +41 -48
- package/dist/task/phases.js +196 -252
- package/dist/task/plan-io.d.ts +6 -7
- package/dist/task/plan-io.js +6 -7
- package/dist/task/plan-orchestrator.d.ts +10 -8
- package/dist/task/plan-orchestrator.js +14 -10
- package/dist/task/plan-prompts.d.ts +6 -5
- package/dist/task/plan-prompts.js +6 -5
- package/dist/task/plan-readonly.d.ts +4 -5
- package/dist/task/plan-readonly.js +4 -5
- package/dist/task/plan-rounds.d.ts +17 -29
- package/dist/task/plan-rounds.js +21 -34
- package/dist/task/plan-session.d.ts +58 -72
- package/dist/task/plan-session.js +61 -83
- package/dist/task/probe-gaming.d.ts +28 -27
- package/dist/task/probe-gaming.js +0 -0
- package/dist/task/prohibition-probe.d.ts +14 -16
- package/dist/task/prompts.d.ts +3 -4
- package/dist/task/prompts.js +17 -26
- package/dist/task/qa-transcript.d.ts +15 -22
- package/dist/task/qa-transcript.js +15 -21
- package/dist/task/question-box.d.ts +17 -13
- package/dist/task/question-box.js +19 -15
- package/dist/task/question-dedup.d.ts +6 -7
- package/dist/task/question-dedup.js +13 -14
- package/dist/task/question-dialog.d.ts +22 -32
- package/dist/task/question-dialog.js +22 -32
- package/dist/task/question-source.d.ts +18 -44
- package/dist/task/question-source.js +22 -51
- package/dist/task/refuted-constraint.d.ts +11 -31
- package/dist/task/refuted-constraint.js +27 -51
- package/dist/task/regenerable-artifacts.d.ts +12 -31
- package/dist/task/regenerable-artifacts.js +12 -31
- package/dist/task/render-check.d.ts +11 -22
- package/dist/task/render-check.js +33 -46
- package/dist/task/repo-health-check.d.ts +10 -14
- package/dist/task/repo-health-check.js +17 -23
- package/dist/task/requirements.d.ts +38 -71
- package/dist/task/requirements.js +78 -126
- package/dist/task/research-fanout-budget.d.ts +51 -88
- package/dist/task/research-fanout-budget.js +51 -88
- package/dist/task/research-worker.d.ts +29 -39
- package/dist/task/research-worker.js +37 -61
- package/dist/task/resume-gap.d.ts +14 -15
- package/dist/task/root-cause-repair.d.ts +9 -9
- package/dist/task/root-cause-repair.js +28 -40
- package/dist/task/run-bracket.d.ts +10 -13
- package/dist/task/run-end.d.ts +12 -22
- package/dist/task/run-end.js +8 -16
- package/dist/task/run-final-gate.d.ts +19 -21
- package/dist/task/run-final-gate.js +62 -80
- package/dist/task/runner-globs.d.ts +12 -13
- package/dist/task/runner-globs.js +12 -13
- package/dist/task/runner-resolve.d.ts +9 -9
- package/dist/task/runner-resolve.js +22 -23
- package/dist/task/script-escape.d.ts +10 -12
- package/dist/task/script-escape.js +13 -14
- package/dist/task/serve-entry.d.ts +1 -1
- package/dist/task/serve-entry.js +22 -25
- package/dist/task/service-blocks.js +4 -2
- package/dist/task/shipped-source.d.ts +11 -29
- package/dist/task/shipped-source.js +11 -29
- package/dist/task/skip-escape.js +10 -14
- package/dist/task/spec-urls.d.ts +26 -65
- package/dist/task/spec-urls.js +26 -65
- package/dist/task/spec-validation.d.ts +17 -20
- package/dist/task/spec-validation.js +17 -20
- package/dist/task/stall-detector.d.ts +23 -30
- package/dist/task/stall-detector.js +23 -30
- package/dist/task/stream-watchdog.d.ts +14 -12
- package/dist/task/stream-watchdog.js +14 -12
- package/dist/task/substitution-probe.d.ts +17 -20
- package/dist/task/substitution-probe.js +17 -20
- package/dist/task/task-gates.d.ts +36 -41
- package/dist/task/task-gates.js +95 -106
- package/dist/task/task-io.d.ts +4 -4
- package/dist/task/task-io.js +4 -4
- package/dist/task/task-parsers.js +4 -3
- package/dist/task/task-provenance.d.ts +2 -2
- package/dist/task/task-provenance.js +11 -13
- package/dist/task/task-types.d.ts +4 -3
- package/dist/task/terminal-outcome.d.ts +14 -16
- package/dist/task/terminal-outcome.js +12 -14
- package/dist/task/test-assembly.d.ts +13 -20
- package/dist/task/test-assembly.js +13 -20
- package/dist/task/timings.d.ts +5 -3
- package/dist/task/timings.js +5 -3
- package/dist/task/title-label.d.ts +9 -4
- package/dist/task/title-label.js +9 -4
- package/dist/task/type-only-answer.d.ts +44 -52
- package/dist/task/type-only-answer.js +44 -52
- package/dist/task/unfailable-command.d.ts +18 -24
- package/dist/task/unfailable-command.js +21 -27
- package/dist/task/unknown-routing.d.ts +10 -4
- package/dist/task/unknown-routing.js +10 -4
- package/dist/task/user-directives.d.ts +5 -8
- package/dist/task/user-directives.js +5 -8
- package/dist/task/verify-quality.d.ts +18 -22
- package/dist/task/verify-quality.js +45 -46
- package/dist/task/verify-reconcile.d.ts +15 -10
- package/dist/task/verify-reconcile.js +45 -43
- package/dist/task/verify-resolution.d.ts +24 -20
- package/dist/task/verify-resolution.js +51 -50
- package/dist/task/verify-work.d.ts +59 -66
- package/dist/task/verify-work.js +101 -138
- package/dist/task/widget.d.ts +15 -14
- package/dist/task/widget.js +22 -17
- package/dist/task/wiring-claims.d.ts +25 -32
- package/dist/task/wiring-claims.js +30 -35
- package/dist/task/write-guard.d.ts +39 -39
- package/dist/task/write-guard.js +48 -51
- package/dist/task/yolo.d.ts +34 -30
- package/dist/task/yolo.js +42 -37
- package/dist/workers/abstention.d.ts +21 -41
- package/dist/workers/abstention.js +27 -48
- package/dist/workers/brave-search.d.ts +4 -3
- package/dist/workers/brave-search.js +5 -2
- package/dist/workers/brave-warning.d.ts +7 -4
- package/dist/workers/brave-warning.js +19 -7
- package/dist/workers/ddg-search.d.ts +6 -6
- package/dist/workers/ddg-search.js +18 -12
- package/dist/workers/docs-cache.js +5 -2
- package/dist/workers/docs-chunk.d.ts +30 -37
- package/dist/workers/docs-chunk.js +37 -41
- package/dist/workers/docs-core.d.ts +28 -44
- package/dist/workers/docs-core.js +25 -44
- package/dist/workers/docs-index.js +4 -3
- package/dist/workers/docs-lookup.d.ts +15 -22
- package/dist/workers/docs-lookup.js +12 -21
- package/dist/workers/docs-project.d.ts +15 -9
- package/dist/workers/docs-project.js +17 -10
- package/dist/workers/docs-resolve.d.ts +19 -20
- package/dist/workers/docs-resolve.js +35 -32
- package/dist/workers/docs-retrieve.d.ts +5 -6
- package/dist/workers/docs-retrieve.js +18 -15
- package/dist/workers/exa-search.d.ts +9 -6
- package/dist/workers/exa-search.js +23 -12
- package/dist/workers/fetch-core.d.ts +13 -16
- package/dist/workers/fetch-core.js +23 -23
- package/dist/workers/focused-extractor.d.ts +13 -12
- package/dist/workers/focused-extractor.js +27 -19
- package/dist/workers/html-clean.js +24 -14
- package/dist/workers/http-request.d.ts +28 -20
- package/dist/workers/http-request.js +22 -17
- package/dist/workers/npm-version.d.ts +28 -11
- package/dist/workers/npm-version.js +24 -15
- package/dist/workers/phantom-imports.d.ts +15 -12
- package/dist/workers/phantom-imports.js +30 -24
- package/dist/workers/pi-worker-core.d.ts +65 -96
- package/dist/workers/pi-worker-core.js +93 -181
- package/dist/workers/pi-worker-docs.d.ts +24 -19
- package/dist/workers/pi-worker-docs.js +67 -76
- package/dist/workers/pi-worker-fetch.d.ts +7 -3
- package/dist/workers/pi-worker-fetch.js +27 -19
- package/dist/workers/pi-worker-search.js +12 -8
- package/dist/workers/pi-worker.d.ts +9 -4
- package/dist/workers/pi-worker.js +21 -14
- package/dist/workers/reasoning-warning.d.ts +18 -17
- package/dist/workers/reasoning-warning.js +22 -20
- package/dist/workers/research-cache.js +50 -78
- package/dist/workers/search-core.js +7 -5
- package/dist/workers/search-types.d.ts +10 -9
- package/dist/workers/search-types.js +9 -8
- package/dist/workers/session-hint.d.ts +13 -14
- package/dist/workers/session-hint.js +8 -9
- package/dist/workers/shared.d.ts +21 -25
- package/dist/workers/shared.js +0 -0
- package/dist/workers/single-read-extension.d.ts +14 -7
- package/dist/workers/single-read-extension.js +14 -7
- package/dist/workers/single-read-guard.d.ts +27 -30
- package/dist/workers/single-read-guard.js +36 -36
- package/dist/workers/typeonly-log.d.ts +12 -9
- package/dist/workers/typeonly-log.js +29 -33
- package/dist/workers/worker-channels.d.ts +15 -23
- package/dist/workers/worker-channels.js +15 -23
- package/dist/workers/worker-failure.d.ts +38 -46
- package/dist/workers/worker-failure.js +31 -39
- package/dist/workers/worker-kill.d.ts +25 -26
- package/dist/workers/worker-kill.js +16 -19
- package/dist/workers/worker-profiles.d.ts +54 -56
- package/dist/workers/worker-profiles.js +63 -39
- package/package.json +10 -8
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Command watchdog — the per-tool-call wall-clock machine, shared by both
|
|
3
3
|
* surfaces that can run a command which never returns.
|
|
4
4
|
*
|
|
5
|
-
* WHY THIS LIVES IN shared/: pi's bash tool takes an OPTIONAL `timeout`
|
|
6
|
-
* default
|
|
7
|
-
*
|
|
5
|
+
* WHY THIS LIVES IN shared/: pi's bash tool takes an OPTIONAL `timeout` and has
|
|
6
|
+
* no default for it. Its schema says so in as many words — "Timeout in seconds
|
|
7
|
+
* (optional, no default timeout)" — and its resolver returns `undefined` for an
|
|
8
|
+
* absent value, which skips arming any timer at all. So ANY command the model
|
|
9
|
+
* did not bound itself runs forever. That is true in two places, and they are
|
|
10
|
+
* disjoint:
|
|
8
11
|
*
|
|
9
12
|
* MAIN SESSION — the implementation turn, handed off via sendUserMessage
|
|
10
13
|
* (task/orchestrator.ts). Guarded by registerCommandWatchdog
|
|
@@ -20,7 +23,8 @@
|
|
|
20
23
|
*
|
|
21
24
|
* main session — ctx.abort() ends the whole agent operation, not just the one
|
|
22
25
|
* tool call (pi types it "Abort the current agent operation"),
|
|
23
|
-
* and pi
|
|
26
|
+
* and pi's agent defaults `toolExecution` to "parallel", so
|
|
27
|
+
* siblings really are in flight together. An
|
|
24
28
|
* overrun kills every tool in flight in that turn — including
|
|
25
29
|
* one exempted via commandTimeoutExemptTools, which only stops
|
|
26
30
|
* a timer being armed FOR that tool, not its being collateral
|
|
@@ -32,16 +36,17 @@
|
|
|
32
36
|
* {@link commandTimeoutHint} prepended. Coarser by necessity:
|
|
33
37
|
* the child's accumulated context is lost.
|
|
34
38
|
*/
|
|
35
|
-
/** Whole minutes
|
|
39
|
+
/** Whole minutes for the human-facing ceiling in both messages: rounded to
|
|
40
|
+
* nearest, then floored at 1 so a sub-minute ceiling never reads "0 minutes". */
|
|
36
41
|
function minutes(timeoutMs) {
|
|
37
42
|
const mins = Math.max(1, Math.round(timeoutMs / 60_000));
|
|
38
43
|
return `${mins} minute${mins === 1 ? '' : 's'}`;
|
|
39
44
|
}
|
|
40
45
|
/**
|
|
41
|
-
* The core correction, shared by both adapters' messages. Two jobs
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
46
|
+
* The core correction, shared by both adapters' messages. Two jobs: stop the
|
|
47
|
+
* model reporting a killed command as a success, and name the one mechanism
|
|
48
|
+
* that prevents a repeat — the bash tool's own `timeout` parameter — rather
|
|
49
|
+
* than leaving "be faster" as the takeaway.
|
|
45
50
|
*/
|
|
46
51
|
function correction() {
|
|
47
52
|
return (`The command was killed before it finished and produced NO result, so do not `
|
|
@@ -82,9 +87,9 @@ export function reminderMessage(toolName, timeoutMs) {
|
|
|
82
87
|
* child that believes it starts clean may re-apply them or misread the tree.
|
|
83
88
|
* Only the CONVERSATION is gone; the hint must say so precisely.
|
|
84
89
|
*
|
|
85
|
-
*
|
|
86
|
-
* "exploring too long"
|
|
87
|
-
* never mentions the timeout parameter.
|
|
90
|
+
* Used instead of the generic worker-timeout hint, which blames the child for
|
|
91
|
+
* "exploring too long" — the wrong diagnosis for a hung command, and one that
|
|
92
|
+
* never mentions the `timeout` parameter that would prevent it.
|
|
88
93
|
*/
|
|
89
94
|
export function commandTimeoutHint(toolName, timeoutMs, opts) {
|
|
90
95
|
const what = opts?.commandDetail ? ` (${opts.commandDetail})` : '';
|
|
@@ -103,9 +108,10 @@ export function commandTimeoutHint(toolName, timeoutMs, opts) {
|
|
|
103
108
|
}
|
|
104
109
|
export class CommandWatchdog {
|
|
105
110
|
deps;
|
|
106
|
-
/** Armed timers, keyed by the tool call they guard.
|
|
107
|
-
*
|
|
108
|
-
*
|
|
111
|
+
/** Armed timers, keyed by the tool call they guard. A map rather than one
|
|
112
|
+
* handle because pi's agent defaults `toolExecution` to "parallel": a batch
|
|
113
|
+
* of sibling calls is genuinely in flight together, so several timers can be
|
|
114
|
+
* armed at once and each must be cancelled by its own id. */
|
|
109
115
|
active = new Map();
|
|
110
116
|
constructor(deps) {
|
|
111
117
|
this.deps = deps;
|
|
@@ -154,14 +160,102 @@ export class CommandWatchdog {
|
|
|
154
160
|
* Real-clock schedule/cancel. Shared by both adapters; tests substitute a fake
|
|
155
161
|
* scheduler instead.
|
|
156
162
|
*
|
|
157
|
-
* REF'd,
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
+
* REF'd deliberately, and NOT unref'd. Measured on this platform, on both
|
|
164
|
+
* runtimes: an unref'd timer with nothing else ref'd and pending never fires at
|
|
165
|
+
* all — the process simply exits — while the identical ref'd timer does. A
|
|
166
|
+
* watchdog whose whole job is to fire while everything else is stuck cannot
|
|
167
|
+
* afford that. Holding the loop open is safe here because the timer is cancelled
|
|
168
|
+
* when the tool ends and again in runWorker's `finally`, so it cannot outlive
|
|
169
|
+
* the call it watches.
|
|
163
170
|
*/
|
|
164
171
|
export const realTimerDeps = {
|
|
165
172
|
schedule: (fn, ms) => setTimeout(fn, ms),
|
|
166
173
|
cancel: handle => clearTimeout(handle)
|
|
167
174
|
};
|
|
175
|
+
/**
|
|
176
|
+
* Build the child-side command watchdog for ONE attempt: a per-tool-call timer
|
|
177
|
+
* machine whose `onFire` aborts `signal`, which runChild turns into a
|
|
178
|
+
* process-GROUP kill — reaping the hung command itself, not just the pi child
|
|
179
|
+
* holding it.
|
|
180
|
+
*
|
|
181
|
+
* LIMIT: the group kill only reaches processes still IN the group. A hung command
|
|
182
|
+
* that detached a daemon (setsid, nohup, a background dev server) leaves it
|
|
183
|
+
* running, so the fresh attempt can hit a port the dead attempt's escapee still
|
|
184
|
+
* holds. There is no cheap fix from here; the restart hint's "check current state"
|
|
185
|
+
* line is the mitigation.
|
|
186
|
+
*
|
|
187
|
+
* Returns null when the watchdog is off, so the caller keeps the plain timeout
|
|
188
|
+
* signal and no per-call bookkeeping happens at all.
|
|
189
|
+
*/
|
|
190
|
+
export function commandWatch(timeoutMs) {
|
|
191
|
+
if (!(timeoutMs > 0))
|
|
192
|
+
return null;
|
|
193
|
+
const ctrl = new AbortController();
|
|
194
|
+
// pi's toolCallId pairs start↔end. When it is absent (a fake stream in a
|
|
195
|
+
// test, an older pi), fall back to one shared slot: tool executions in a
|
|
196
|
+
// child are sequential, so a single slot is still correctly paired.
|
|
197
|
+
const key = (id) => id ?? 'anon';
|
|
198
|
+
const details = new Map();
|
|
199
|
+
let killed;
|
|
200
|
+
const watchdog = new CommandWatchdog({
|
|
201
|
+
getTimeoutMs: () => timeoutMs,
|
|
202
|
+
...realTimerDeps,
|
|
203
|
+
onFire: (toolCallId, toolName, ms) => {
|
|
204
|
+
killed = {
|
|
205
|
+
toolName,
|
|
206
|
+
timeoutMs: ms,
|
|
207
|
+
...(details.has(toolCallId) ? { detail: details.get(toolCallId) } : {})
|
|
208
|
+
};
|
|
209
|
+
ctrl.abort();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
onStart: call => {
|
|
214
|
+
const id = key(call.toolCallId);
|
|
215
|
+
const args = call.args;
|
|
216
|
+
if (typeof args?.command === 'string') {
|
|
217
|
+
details.set(id, args.command.slice(0, 120));
|
|
218
|
+
}
|
|
219
|
+
watchdog.onStart(id, call.name);
|
|
220
|
+
},
|
|
221
|
+
onEnd: id => {
|
|
222
|
+
// Drop the command line with its call. The `'anon'` fallback above is a
|
|
223
|
+
// SHARED slot, so a stale entry would be attributed to whatever ran
|
|
224
|
+
// next: a `read` that later overran would be reported as
|
|
225
|
+
// "ran a `read` command (bun run dev)".
|
|
226
|
+
details.delete(key(id));
|
|
227
|
+
watchdog.onEnd(key(id));
|
|
228
|
+
},
|
|
229
|
+
killed: () => killed,
|
|
230
|
+
signal: ctrl.signal,
|
|
231
|
+
clear: () => watchdog.clearAll()
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The per-command ceiling for attempt N, halving each time a hang recurs.
|
|
236
|
+
*
|
|
237
|
+
* The first attempt gets the full configured ceiling — a genuinely slow build or
|
|
238
|
+
* test suite deserves it. But every hang-caused restart carries
|
|
239
|
+
* commandTimeoutHint, which tells the model in as many words to bound its
|
|
240
|
+
* command; a SECOND hang means it ignored an explicit instruction, and a third
|
|
241
|
+
* means it ignored it twice. Giving a non-complying child the full ceiling again
|
|
242
|
+
* makes the worst case three times the ceiling, resting entirely on the model
|
|
243
|
+
* obeying prose. Halving bounds it at under twice the ceiling while costing a
|
|
244
|
+
* complying child nothing.
|
|
245
|
+
*
|
|
246
|
+
* `priorHangs` counts watchdog kills specifically, NOT total restarts — the
|
|
247
|
+
* restart budget is shared with loop kills, and a child restarted for LOOPING
|
|
248
|
+
* never received the bound-your-command hint, so its first hang still deserves
|
|
249
|
+
* the full ceiling. Only a hang after a hang is defiance.
|
|
250
|
+
*
|
|
251
|
+
* Floored at 30s so repeated halving cannot shrink the ceiling to something no
|
|
252
|
+
* real command could finish inside — but the floor is `min(base, 30s)`, never
|
|
253
|
+
* above the configured ceiling, so a caller asking for 10s keeps 10s at every
|
|
254
|
+
* hang count. A base of 0 or less disables the watchdog and stays 0.
|
|
255
|
+
*/
|
|
256
|
+
export function commandCeilingForAttempt(baseMs, priorHangs) {
|
|
257
|
+
if (!(baseMs > 0))
|
|
258
|
+
return 0;
|
|
259
|
+
const floor = Math.min(baseMs, 30_000);
|
|
260
|
+
return Math.max(floor, Math.round(baseMs / 2 ** priorHangs));
|
|
261
|
+
}
|
package/dist/shared/fs-text.d.ts
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Text-file reads with line endings normalized to LF.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
* (an editor save, or a `git` checkout with `core.autocrlf=true`), and classic
|
|
8
|
-
* Mac tooling can emit lone CR. Any of those makes `parseFrontMatter` return
|
|
9
|
-
* null ("malformed front matter") and every `extractSection` miss.
|
|
4
|
+
* A checked-out task file need not hold LF. Confirmed against real git: with
|
|
5
|
+
* `core.autocrlf=true`, a blob committed as LF comes back into the working tree
|
|
6
|
+
* as CRLF while the stored object is unchanged. Other tooling can write a lone CR.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* CRLF alone would not need this any more — `parseFrontMatter` and `sectionRegex`
|
|
9
|
+
* both spell `\r?\n`, and a CRLF task file parses correctly with or without
|
|
10
|
+
* normalization. LONE CR is the case that still does. Measured on an otherwise
|
|
11
|
+
* valid task file: with CR line endings `parseFrontMatter` returns null and
|
|
12
|
+
* `extractSection` finds nothing; normalized first, both succeed. Neither parser
|
|
13
|
+
* regex can match a lone CR, because both require an `\n`.
|
|
14
|
+
*
|
|
15
|
+
* Normalizing once at the read boundary is what keeps that from becoming a third
|
|
16
|
+
* line-ending case in every parser. Every task-file read goes through
|
|
17
|
+
* `readTextFile`; the `readFileSync` calls elsewhere in the task layer read
|
|
18
|
+
* package.json, Makefiles and .env files, which have parsers of their own.
|
|
14
19
|
*/
|
|
15
|
-
/** Collapse CRLF and lone CR to LF. `\r\n?`
|
|
20
|
+
/** Collapse CRLF and lone CR to LF. The optional `\n` in `\r\n?` consumes a CRLF
|
|
21
|
+
* pair as one unit, so a CRLF file does not come back with doubled newlines. */
|
|
16
22
|
export declare function normalizeNewlines(text: string): string;
|
|
17
23
|
/** Read a UTF-8 text file with line endings normalized to LF. */
|
|
18
24
|
export declare function readTextFile(filePath: string): Promise<string>;
|
package/dist/shared/fs-text.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Text-file reads with line endings normalized to LF.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the
|
|
7
|
-
* (an editor save, or a `git` checkout with `core.autocrlf=true`), and classic
|
|
8
|
-
* Mac tooling can emit lone CR. Any of those makes `parseFrontMatter` return
|
|
9
|
-
* null ("malformed front matter") and every `extractSection` miss.
|
|
4
|
+
* A checked-out task file need not hold LF. Confirmed against real git: with
|
|
5
|
+
* `core.autocrlf=true`, a blob committed as LF comes back into the working tree
|
|
6
|
+
* as CRLF while the stored object is unchanged. Other tooling can write a lone CR.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* CRLF alone would not need this any more — `parseFrontMatter` and `sectionRegex`
|
|
9
|
+
* both spell `\r?\n`, and a CRLF task file parses correctly with or without
|
|
10
|
+
* normalization. LONE CR is the case that still does. Measured on an otherwise
|
|
11
|
+
* valid task file: with CR line endings `parseFrontMatter` returns null and
|
|
12
|
+
* `extractSection` finds nothing; normalized first, both succeed. Neither parser
|
|
13
|
+
* regex can match a lone CR, because both require an `\n`.
|
|
14
|
+
*
|
|
15
|
+
* Normalizing once at the read boundary is what keeps that from becoming a third
|
|
16
|
+
* line-ending case in every parser. Every task-file read goes through
|
|
17
|
+
* `readTextFile`; the `readFileSync` calls elsewhere in the task layer read
|
|
18
|
+
* package.json, Makefiles and .env files, which have parsers of their own.
|
|
14
19
|
*/
|
|
15
20
|
import * as fsp from 'node:fs/promises';
|
|
16
|
-
/** Collapse CRLF and lone CR to LF. `\r\n?`
|
|
21
|
+
/** Collapse CRLF and lone CR to LF. The optional `\n` in `\r\n?` consumes a CRLF
|
|
22
|
+
* pair as one unit, so a CRLF file does not come back with doubled newlines. */
|
|
17
23
|
export function normalizeNewlines(text) {
|
|
18
24
|
return text.replace(/\r\n?/g, '\n');
|
|
19
25
|
}
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* git-runner — the
|
|
3
|
-
*
|
|
4
|
-
* WHY IT EXISTS. The right shape already existed, privately, inside
|
|
5
|
-
* `task/git-state-guard.ts`: an abort-aware async runner carrying an injectable
|
|
6
|
-
* `spawnFn` seam, returning `{stdout, exitCode}` and never throwing. Nothing
|
|
7
|
-
* outside that file could reach it, so ~27 other sites hand-rolled their own —
|
|
8
|
-
* with FOUR incompatible failure contracts (throws / returns null / returns '' /
|
|
9
|
-
* returns a result object) and THREE different maxBuffer limits. A caller reading
|
|
10
|
-
* one of them learns nothing about the next, and a git failure means something
|
|
11
|
-
* different at every site. Lifting the seam here does not add a capability; it
|
|
12
|
-
* removes the ambiguity about which contract you are holding.
|
|
2
|
+
* git-runner — the shared async way to run `git`, and the one to reach for.
|
|
13
3
|
*
|
|
14
4
|
* THE CONTRACT, deliberately narrow:
|
|
15
|
-
* - NEVER THROWS.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
5
|
+
* - NEVER THROWS. Every failure arrives as a non-zero `exitCode`, measured
|
|
6
|
+
* rather than assumed: a non-repo cwd answers 128, a bogus subcommand
|
|
7
|
+
* answers 1, `git rev-parse --verify HEAD` on a freshly-init'd repo with an
|
|
8
|
+
* unborn HEAD answers 128, and even a MISSING binary comes back as
|
|
9
|
+
* `{exitCode: 1, aborted: false}` instead of rejecting. Callers branch on
|
|
10
|
+
* the number, they do not wrap in try/catch, and every guard built on this
|
|
11
|
+
* treats "git could not tell me" as "no claim" rather than a crash.
|
|
20
12
|
* - Only `stdout` and `exitCode` are exposed. stderr is deliberately absent:
|
|
21
|
-
* the
|
|
22
|
-
* sniffing) also
|
|
23
|
-
* two fields nobody else reads onto every call site.
|
|
13
|
+
* the caller that legitimately needs it (auto-commit's identity-failure
|
|
14
|
+
* sniffing) also needs `aborted`, and widening this type for it would push
|
|
15
|
+
* two fields nobody else reads onto every call site. That one caller goes
|
|
16
|
+
* to `runChildDefault` directly instead.
|
|
24
17
|
* - `signal` is honoured by the underlying runChild, including its listener
|
|
25
|
-
* detach discipline
|
|
26
|
-
*
|
|
27
|
-
* - `env` entries are MERGED over `process.env
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
18
|
+
* detach discipline — a run-long orchestrator signal does not accumulate
|
|
19
|
+
* one listener per git invocation.
|
|
20
|
+
* - `env` entries are MERGED over `process.env`, not substituted for it, which
|
|
21
|
+
* is what lets git-state-guard point `GIT_INDEX_FILE` at a throwaway index
|
|
22
|
+
* without losing PATH and HOME. Confirmed: such a call still resolves the
|
|
23
|
+
* repo normally.
|
|
24
|
+
* - `spawnFn` is the test seam: pass a fake from `test/test-utils/fake-spawn.ts`
|
|
25
|
+
* and the runner never touches a real repo.
|
|
26
|
+
*
|
|
27
|
+
* It is NOT universal, which matters before assuming any git call arrived here.
|
|
28
|
+
* Several sites still invoke git themselves with `spawnSync`, each carrying its
|
|
29
|
+
* own timeout and one its own maxBuffer, and accept-debt goes through the
|
|
30
|
+
* bounded command runner instead.
|
|
31
31
|
*/
|
|
32
32
|
import { type SpawnFn } from './child-process.js';
|
|
33
33
|
export interface GitRunner {
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* git-runner — the
|
|
3
|
-
*
|
|
4
|
-
* WHY IT EXISTS. The right shape already existed, privately, inside
|
|
5
|
-
* `task/git-state-guard.ts`: an abort-aware async runner carrying an injectable
|
|
6
|
-
* `spawnFn` seam, returning `{stdout, exitCode}` and never throwing. Nothing
|
|
7
|
-
* outside that file could reach it, so ~27 other sites hand-rolled their own —
|
|
8
|
-
* with FOUR incompatible failure contracts (throws / returns null / returns '' /
|
|
9
|
-
* returns a result object) and THREE different maxBuffer limits. A caller reading
|
|
10
|
-
* one of them learns nothing about the next, and a git failure means something
|
|
11
|
-
* different at every site. Lifting the seam here does not add a capability; it
|
|
12
|
-
* removes the ambiguity about which contract you are holding.
|
|
2
|
+
* git-runner — the shared async way to run `git`, and the one to reach for.
|
|
13
3
|
*
|
|
14
4
|
* THE CONTRACT, deliberately narrow:
|
|
15
|
-
* - NEVER THROWS.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
5
|
+
* - NEVER THROWS. Every failure arrives as a non-zero `exitCode`, measured
|
|
6
|
+
* rather than assumed: a non-repo cwd answers 128, a bogus subcommand
|
|
7
|
+
* answers 1, `git rev-parse --verify HEAD` on a freshly-init'd repo with an
|
|
8
|
+
* unborn HEAD answers 128, and even a MISSING binary comes back as
|
|
9
|
+
* `{exitCode: 1, aborted: false}` instead of rejecting. Callers branch on
|
|
10
|
+
* the number, they do not wrap in try/catch, and every guard built on this
|
|
11
|
+
* treats "git could not tell me" as "no claim" rather than a crash.
|
|
20
12
|
* - Only `stdout` and `exitCode` are exposed. stderr is deliberately absent:
|
|
21
|
-
* the
|
|
22
|
-
* sniffing) also
|
|
23
|
-
* two fields nobody else reads onto every call site.
|
|
13
|
+
* the caller that legitimately needs it (auto-commit's identity-failure
|
|
14
|
+
* sniffing) also needs `aborted`, and widening this type for it would push
|
|
15
|
+
* two fields nobody else reads onto every call site. That one caller goes
|
|
16
|
+
* to `runChildDefault` directly instead.
|
|
24
17
|
* - `signal` is honoured by the underlying runChild, including its listener
|
|
25
|
-
* detach discipline
|
|
26
|
-
*
|
|
27
|
-
* - `env` entries are MERGED over `process.env
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
18
|
+
* detach discipline — a run-long orchestrator signal does not accumulate
|
|
19
|
+
* one listener per git invocation.
|
|
20
|
+
* - `env` entries are MERGED over `process.env`, not substituted for it, which
|
|
21
|
+
* is what lets git-state-guard point `GIT_INDEX_FILE` at a throwaway index
|
|
22
|
+
* without losing PATH and HOME. Confirmed: such a call still resolves the
|
|
23
|
+
* repo normally.
|
|
24
|
+
* - `spawnFn` is the test seam: pass a fake from `test/test-utils/fake-spawn.ts`
|
|
25
|
+
* and the runner never touches a real repo.
|
|
26
|
+
*
|
|
27
|
+
* It is NOT universal, which matters before assuming any git call arrived here.
|
|
28
|
+
* Several sites still invoke git themselves with `spawnSync`, each carrying its
|
|
29
|
+
* own timeout and one its own maxBuffer, and accept-debt goes through the
|
|
30
|
+
* bounded command runner instead.
|
|
31
31
|
*/
|
|
32
32
|
import { runChildDefault } from './child-process.js';
|
|
33
33
|
export function makeGit(cwd, signal, spawnFn) {
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* Detect tool calls that leaked into a child's assistant *text* instead of
|
|
3
3
|
* being executed.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* pi's harness doesn't recognise — e.g.
|
|
5
|
+
* A call only counts when a structured `tool_execution_start` event fires —
|
|
6
|
+
* shared/child-process.ts recognises nothing else. Model-side tool-call markup
|
|
7
|
+
* like
|
|
9
8
|
*
|
|
10
9
|
* <tool_call>
|
|
11
10
|
* <function=bash>
|
|
@@ -13,14 +12,21 @@
|
|
|
13
12
|
* </function>
|
|
14
13
|
* </tool_call>
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* the unexecuted call flows downstream — a silently skipped beat.
|
|
15
|
+
* is not a format pi itself parses: those tags appear nowhere in any installed
|
|
16
|
+
* pi package. So whether such a turn RUNS is decided entirely by the inference
|
|
17
|
+
* server in front of it, and both outcomes are real.
|
|
20
18
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Checked against a live endpoint, the server parsed the dialect itself and
|
|
20
|
+
* handed pi structured tool calls — the markup EXECUTED rather than leaking, and
|
|
21
|
+
* the parse was sloppy enough to fold the closing tags and the surrounding prose
|
|
22
|
+
* into the command argument. A server that does not parse it produces the case
|
|
23
|
+
* this module exists for: pi receives the markup as ordinary assistant text, the
|
|
24
|
+
* command never runs, no event fires, and pi-task's guards never see it. The
|
|
25
|
+
* turn then clears its only acceptance gates — non-empty assistant text and exit
|
|
26
|
+
* 0 — and the unexecuted call flows downstream as a silently skipped beat.
|
|
27
|
+
*
|
|
28
|
+
* pi-task cannot fix the mismatch. What it CAN do is notice the markup in the
|
|
29
|
+
* answer and refuse the turn, so the skip becomes visible instead of silent.
|
|
24
30
|
*/
|
|
25
31
|
export declare const MAX_LEAK_RETRIES = 2;
|
|
26
32
|
/**
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* Detect tool calls that leaked into a child's assistant *text* instead of
|
|
3
3
|
* being executed.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* pi's harness doesn't recognise — e.g.
|
|
5
|
+
* A call only counts when a structured `tool_execution_start` event fires —
|
|
6
|
+
* shared/child-process.ts recognises nothing else. Model-side tool-call markup
|
|
7
|
+
* like
|
|
9
8
|
*
|
|
10
9
|
* <tool_call>
|
|
11
10
|
* <function=bash>
|
|
@@ -13,25 +12,34 @@
|
|
|
13
12
|
* </function>
|
|
14
13
|
* </tool_call>
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* the unexecuted call flows downstream — a silently skipped beat.
|
|
15
|
+
* is not a format pi itself parses: those tags appear nowhere in any installed
|
|
16
|
+
* pi package. So whether such a turn RUNS is decided entirely by the inference
|
|
17
|
+
* server in front of it, and both outcomes are real.
|
|
20
18
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Checked against a live endpoint, the server parsed the dialect itself and
|
|
20
|
+
* handed pi structured tool calls — the markup EXECUTED rather than leaking, and
|
|
21
|
+
* the parse was sloppy enough to fold the closing tags and the surrounding prose
|
|
22
|
+
* into the command argument. A server that does not parse it produces the case
|
|
23
|
+
* this module exists for: pi receives the markup as ordinary assistant text, the
|
|
24
|
+
* command never runs, no event fires, and pi-task's guards never see it. The
|
|
25
|
+
* turn then clears its only acceptance gates — non-empty assistant text and exit
|
|
26
|
+
* 0 — and the unexecuted call flows downstream as a silently skipped beat.
|
|
27
|
+
*
|
|
28
|
+
* pi-task cannot fix the mismatch. What it CAN do is notice the markup in the
|
|
29
|
+
* answer and refuse the turn, so the skip becomes visible instead of silent.
|
|
24
30
|
*/
|
|
25
31
|
// A child that wrote a tool call as plain text (wrong dialect, never executed)
|
|
26
32
|
// gets re-prompted with a correction hint up to this many times before the
|
|
27
33
|
// caller gives up. Mirrors MAX_LOOP_RESTARTS: 3 attempts total.
|
|
28
34
|
export const MAX_LEAK_RETRIES = 2;
|
|
29
|
-
// The Hermes-style wrapper
|
|
30
|
-
//
|
|
35
|
+
// The Hermes-style wrapper. Nothing else in this codebase emits the tag — its
|
|
36
|
+
// only other appearance is the correction hint below, which names it back to the
|
|
37
|
+
// model — so seeing it in an answer is signal enough on its own.
|
|
31
38
|
const TOOL_CALL_WRAPPER = /<tool_call\b[^>]*>/i;
|
|
32
39
|
// The "XML function call" dialect: <function=name> … <parameter=key>. Either tag
|
|
33
|
-
// alone is too weak
|
|
34
|
-
//
|
|
40
|
+
// alone is too weak — one can appear in prose or in source — so the structural
|
|
41
|
+
// PAIR is required. Confirmed: a lone <function=bash> and a lone
|
|
42
|
+
// <parameter=command> each return null, and only the two together flag.
|
|
35
43
|
const FUNCTION_TAG = /<function=[\w.-]+\s*>/i;
|
|
36
44
|
const PARAMETER_TAG = /<parameter=[\w.-]+\s*>/i;
|
|
37
45
|
/**
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
/** Base URLs of every custom provider pi is configured with (possibly none). */
|
|
2
2
|
export declare function discoverModelEndpoints(agentDir?: string): string[];
|
|
3
3
|
/**
|
|
4
|
-
* true → at least one endpoint ANSWERED
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* true → at least one endpoint ANSWERED. Any HTTP status counts, because the
|
|
5
|
+
* question is liveness, not correctness: probing a path that 404s still returns
|
|
6
|
+
* true, while a closed port returns false. Both confirmed against a live server.
|
|
7
|
+
* An empty list is true — nothing to probe means never kill.
|
|
8
|
+
*
|
|
9
|
+
* `models` is joined RELATIVELY here, and deliberately: it lives under the
|
|
10
|
+
* OpenAI-compatible prefix a baseUrl already carries. Contrast `/props` below.
|
|
7
11
|
*/
|
|
8
12
|
export declare function probeModelEndpoints(urls: string[], timeoutMs?: number): Promise<boolean>;
|
|
9
13
|
/**
|
|
10
14
|
* What a llama.cpp server's own chat template can actually do about reasoning,
|
|
11
15
|
* as reported by `GET /props`.
|
|
12
16
|
*
|
|
13
|
-
* This is the only source of truth that does NOT come from models.json
|
|
14
|
-
* answers the
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* This is the only source of truth that does NOT come from models.json, and it
|
|
18
|
+
* answers the question the host-side clamp cannot: is models.json lying about
|
|
19
|
+
* the server? The case that matters is pi's built-in llama.cpp provider, which
|
|
20
|
+
* really does hardcode `reasoning: false` — the literal line is in
|
|
21
|
+
* `extensions/llama/provider.js`, under `LLAMA_PROVIDER_ID = "llama.cpp"`. So
|
|
22
|
+
* anyone who reached their server through `/login llama.cpp` rather than a
|
|
23
|
+
* hand-written provider entry has a dead knob and nothing to tell them so.
|
|
19
24
|
*/
|
|
20
25
|
export interface ChatTemplateCaps {
|
|
21
26
|
/** The template reads `reasoning_effort` — i.e. levels, not just on/off. */
|
|
@@ -30,13 +35,21 @@ export interface ChatTemplateCaps {
|
|
|
30
35
|
* answer worth having" — not a llama.cpp server, unreachable, or a body in a
|
|
31
36
|
* shape this does not recognise.
|
|
32
37
|
*
|
|
33
|
-
* `null` is a first-class result, not an error:
|
|
34
|
-
* returns it, and the caller must degrade to the models.json view
|
|
35
|
-
* warn about a server it could not read. Never throws
|
|
38
|
+
* `null` is a first-class result, not an error: a backend that does not serve
|
|
39
|
+
* this shape returns it, and the caller must degrade to the models.json view
|
|
40
|
+
* rather than warn about a server it could not read. Never throws — a closed
|
|
41
|
+
* port answers `null`, confirmed.
|
|
42
|
+
*
|
|
43
|
+
* The LEADING SLASH in `/props` is load-bearing, because `/props` lives at the
|
|
44
|
+
* server ROOT while a configured baseUrl carries an OpenAI-compatible prefix.
|
|
45
|
+
* How a relative `'props'` resolves depends on whether that prefix ends in a
|
|
46
|
+
* slash, which is not something this can rely on:
|
|
47
|
+
*
|
|
48
|
+
* new URL('props', 'http://host/v1') → /props
|
|
49
|
+
* new URL('props', 'http://host/v1/') → /v1/props ← 404
|
|
50
|
+
* new URL('/props', either) → /props
|
|
36
51
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* the server root, so a relative `'props'` would resolve to `/v1/props` and 404 —
|
|
40
|
-
* which this would report as `null`, i.e. as a silent loss of the better signal.
|
|
52
|
+
* A 404 would come back from here as `null`, silently losing the better signal.
|
|
53
|
+
* The absolute form is right for both.
|
|
41
54
|
*/
|
|
42
55
|
export declare function probeChatTemplateCaps(baseUrl: string, timeoutMs?: number): Promise<ChatTemplateCaps | null>;
|