@mjasnikovs/pi-task 0.38.18 → 0.38.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/config/config.d.ts +19 -0
- package/dist/config/config.js +10 -2
- package/dist/config/reasoning-args.d.ts +10 -0
- package/dist/config/reasoning-args.js +23 -0
- package/dist/config/reasoning.d.ts +160 -0
- package/dist/config/reasoning.js +530 -0
- package/dist/config/register.d.ts +72 -2
- package/dist/config/register.js +182 -33
- package/dist/shared/child-process.d.ts +19 -0
- package/dist/shared/child-process.js +9 -10
- package/dist/shared/model-endpoint.d.ts +34 -0
- package/dist/shared/model-endpoint.js +36 -0
- package/dist/shared/reasoning-capability.d.ts +86 -0
- package/dist/shared/reasoning-capability.js +82 -0
- package/dist/task/child-runner.d.ts +36 -26
- package/dist/task/child-runner.js +63 -6
- package/dist/task/child-status.d.ts +11 -3
- package/dist/task/child-status.js +14 -3
- package/dist/task/context-usage.d.ts +1 -1
- package/dist/task/context-usage.js +1 -1
- package/dist/task/decompose-fidelity.d.ts +21 -8
- package/dist/task/decompose-fidelity.js +98 -17
- package/dist/task/gate-child.d.ts +10 -0
- package/dist/task/gate-child.js +5 -1
- package/dist/task/gate-deps.js +4 -0
- package/dist/task/implementation-thinking.d.ts +54 -0
- package/dist/task/implementation-thinking.js +33 -0
- package/dist/task/orchestrator.d.ts +7 -0
- package/dist/task/orchestrator.js +46 -14
- package/dist/task/phases.js +47 -24
- package/dist/task/prompts.d.ts +0 -23
- package/dist/task/prompts.js +0 -25
- package/dist/task/reasoning-groups.d.ts +36 -0
- package/dist/task/reasoning-groups.js +36 -0
- package/dist/task/spec-validation.d.ts +28 -0
- package/dist/task/spec-validation.js +44 -0
- package/dist/task/stall-detector.d.ts +5 -0
- package/dist/task/stall-detector.js +5 -0
- package/dist/task/title-label.js +2 -2
- package/dist/workers/docs-core.js +4 -0
- package/dist/workers/fetch-core.js +4 -0
- package/dist/workers/focused-extractor.d.ts +12 -1
- package/dist/workers/focused-extractor.js +6 -2
- package/dist/workers/index.js +2 -0
- package/dist/workers/pi-worker-core.d.ts +33 -4
- package/dist/workers/pi-worker-core.js +28 -7
- package/dist/workers/pi-worker-docs.js +4 -0
- package/dist/workers/pi-worker.js +11 -1
- package/dist/workers/reasoning-warning.d.ts +64 -0
- package/dist/workers/reasoning-warning.js +142 -0
- package/package.json +4 -4
package/dist/workers/index.js
CHANGED
|
@@ -3,10 +3,12 @@ import { registerPiWorkerSearch } from './pi-worker-search.js';
|
|
|
3
3
|
import { registerPiWorkerFetch } from './pi-worker-fetch.js';
|
|
4
4
|
import { registerPiWorkerDocs } from './pi-worker-docs.js';
|
|
5
5
|
import { registerBraveKeyWarning } from './brave-warning.js';
|
|
6
|
+
import { registerReasoningWarning } from './reasoning-warning.js';
|
|
6
7
|
export function registerWorkers(pi) {
|
|
7
8
|
registerPiWorker(pi);
|
|
8
9
|
registerPiWorkerSearch(pi);
|
|
9
10
|
registerPiWorkerFetch(pi);
|
|
10
11
|
registerPiWorkerDocs(pi);
|
|
11
12
|
registerBraveKeyWarning(pi);
|
|
13
|
+
registerReasoningWarning(pi);
|
|
12
14
|
}
|
|
@@ -33,6 +33,18 @@ export { isGroundingRetrieval } from './worker-channels.js';
|
|
|
33
33
|
* at no particular column and does not repeat that shape.
|
|
34
34
|
*/
|
|
35
35
|
export declare function hasAnswerContent(text: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Is ONE line an entry — a name, a gap, then a description — rather than prose?
|
|
38
|
+
*
|
|
39
|
+
* Split out of `hasAnswerContent` so the same rule can decide what a line IS,
|
|
40
|
+
* not just how many of them there are. A FILES section's paths are read back
|
|
41
|
+
* with it, and a scorer that used its own idea of an entry counted a preamble
|
|
42
|
+
* sentence and a leaked `</tool_call>` as invented paths.
|
|
43
|
+
*
|
|
44
|
+
* Prose wraps at no particular column, so it carries no two-space gap and no
|
|
45
|
+
* spaced dash; when it does, it ends in `.` or `:` and an entry does not.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isEntryLine(raw: string): boolean;
|
|
36
48
|
/**
|
|
37
49
|
* Frame a discarded attempt's output as work already done.
|
|
38
50
|
*
|
|
@@ -62,12 +74,19 @@ export interface RunWorkerInput {
|
|
|
62
74
|
toolCallId?: string;
|
|
63
75
|
}) => void;
|
|
64
76
|
/**
|
|
65
|
-
* Called for each
|
|
66
|
-
* stream the phase children parse). Lets a
|
|
67
|
-
* tokens/window progress bar for the worker
|
|
68
|
-
* instead of omitting it.
|
|
77
|
+
* Called for each context snapshot derived from the child's `message_end`
|
|
78
|
+
* events (same `--mode json` stream the phase children parse). Lets a
|
|
79
|
+
* caller's status widget show the tokens/window progress bar for the worker
|
|
80
|
+
* exactly like the phase widget, instead of omitting it.
|
|
69
81
|
*/
|
|
70
82
|
onContextUsage?: (snapshot: ContextSnapshot) => void;
|
|
83
|
+
/**
|
|
84
|
+
* The worker child's context window in tokens. pi's event stream carries no
|
|
85
|
+
* window (issue #16), so a caller that wants a progress bar rather than a
|
|
86
|
+
* bare token count has to supply the parent session's — which is the child's
|
|
87
|
+
* too, since workers are spawned without `-m`.
|
|
88
|
+
*/
|
|
89
|
+
contextWindow?: number;
|
|
71
90
|
/**
|
|
72
91
|
* Per-worker wall-clock timeout in ms. Defaults to RESEARCH_WORKER_TIMEOUT_MS.
|
|
73
92
|
* Pass 0 to disable the timeout entirely (run until the child exits on its
|
|
@@ -170,6 +189,16 @@ export interface RunWorkerInput {
|
|
|
170
189
|
chars: number;
|
|
171
190
|
promptCharsBefore: number;
|
|
172
191
|
}) => void;
|
|
192
|
+
/**
|
|
193
|
+
* An already-resolved `['--thinking', level]` fragment, or `[]`/omitted to
|
|
194
|
+
* inherit the session default exactly as before.
|
|
195
|
+
*
|
|
196
|
+
* Resolved by the CALLER because runWorker serves three different reasoning
|
|
197
|
+
* groups — the research workers, the post-implementation gates, and the
|
|
198
|
+
* ad-hoc `pi-worker` tool — and has nothing in its input that tells them
|
|
199
|
+
* apart. Guessing here would give a verify gate the research workers' level.
|
|
200
|
+
*/
|
|
201
|
+
thinking?: readonly string[];
|
|
173
202
|
/**
|
|
174
203
|
* Called once per DISCARDED attempt, at the moment the worker decides to
|
|
175
204
|
* re-spawn — the only window in which a restart is observable at all.
|
|
@@ -114,11 +114,22 @@ const CARRY_FORWARD_REASONS = new Set([
|
|
|
114
114
|
* at no particular column and does not repeat that shape.
|
|
115
115
|
*/
|
|
116
116
|
export function hasAnswerContent(text) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
return text.split('\n').filter(isEntryLine).length >= 2;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Is ONE line an entry — a name, a gap, then a description — rather than prose?
|
|
121
|
+
*
|
|
122
|
+
* Split out of `hasAnswerContent` so the same rule can decide what a line IS,
|
|
123
|
+
* not just how many of them there are. A FILES section's paths are read back
|
|
124
|
+
* with it, and a scorer that used its own idea of an entry counted a preamble
|
|
125
|
+
* sentence and a leaked `</tool_call>` as invented paths.
|
|
126
|
+
*
|
|
127
|
+
* Prose wraps at no particular column, so it carries no two-space gap and no
|
|
128
|
+
* spaced dash; when it does, it ends in `.` or `:` and an entry does not.
|
|
129
|
+
*/
|
|
130
|
+
export function isEntryLine(raw) {
|
|
131
|
+
const l = raw.replace(/^\s*(?:[-*•]|\d+[.)])\s+/, '').trim();
|
|
132
|
+
return /^\S.*?(?:\s{2,}|\s+[—–-]\s+)\S/.test(l) && !/[.:]$/.test(l);
|
|
122
133
|
}
|
|
123
134
|
/**
|
|
124
135
|
* Frame a discarded attempt's output as work already done.
|
|
@@ -420,7 +431,14 @@ function commandWatch(timeoutMs) {
|
|
|
420
431
|
}
|
|
421
432
|
export async function runWorker(input) {
|
|
422
433
|
const tools = input.tools ?? DEFAULT_TOOLS;
|
|
423
|
-
const baseArgs = [
|
|
434
|
+
const baseArgs = [
|
|
435
|
+
...childBaseArgs(input.extensions ?? []),
|
|
436
|
+
...(input.thinking ?? []),
|
|
437
|
+
'--mode',
|
|
438
|
+
'json',
|
|
439
|
+
'--tools',
|
|
440
|
+
tools
|
|
441
|
+
];
|
|
424
442
|
const timeoutMs = input.timeoutMs ?? RESEARCH_WORKER_TIMEOUT_MS;
|
|
425
443
|
let hint = null;
|
|
426
444
|
// Loop-kill and timeout share one restart budget, mirroring
|
|
@@ -549,7 +567,10 @@ export async function runWorker(input) {
|
|
|
549
567
|
cmdWatch?.onEnd(r.toolCallId);
|
|
550
568
|
input.onToolResult?.(r);
|
|
551
569
|
},
|
|
552
|
-
onContextUsage: input.onContextUsage
|
|
570
|
+
onContextUsage: input.onContextUsage,
|
|
571
|
+
...(input.contextWindow && input.contextWindow > 0 ?
|
|
572
|
+
{ contextWindow: input.contextWindow }
|
|
573
|
+
: {})
|
|
553
574
|
}, input.spawn);
|
|
554
575
|
}
|
|
555
576
|
finally {
|
|
@@ -14,6 +14,7 @@ import { normalizeQuery } from './research-cache.js';
|
|
|
14
14
|
import { projectDocsRaw, buildProjectPrompt } from './docs-project.js';
|
|
15
15
|
import { projectDocsBudget, projectDocsBudgetExhausted } from '../task/research-fanout-budget.js';
|
|
16
16
|
import { isAbstention } from './abstention.js';
|
|
17
|
+
import { groupThinkingArgs } from '../config/reasoning-args.js';
|
|
17
18
|
const RENDER_QUERY_MAX = 100;
|
|
18
19
|
const Params = Type.Object({
|
|
19
20
|
module: Type.String({
|
|
@@ -137,6 +138,9 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
137
138
|
cwd: ctx.cwd,
|
|
138
139
|
signal,
|
|
139
140
|
spawn,
|
|
141
|
+
// The `extraction` group's level. Resolved at the call site so the
|
|
142
|
+
// extractor itself never reads ambient config.
|
|
143
|
+
thinking: groupThinkingArgs('extraction'),
|
|
140
144
|
abortedMessage
|
|
141
145
|
});
|
|
142
146
|
// ── Project source lookup ───────────────────────────────────────
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Text } from '@earendil-works/pi-tui';
|
|
10
10
|
import { Type } from '@sinclair/typebox';
|
|
11
|
+
import { groupThinkingArgs } from '../config/reasoning-args.js';
|
|
11
12
|
import { runWorker } from './pi-worker-core.js';
|
|
12
13
|
import { childFailureReason, formatChildFailure, makeWorkerTool, workerAnswer, workerUnavailable } from './shared.js';
|
|
13
14
|
const RENDER_PROMPT_MAX = 120;
|
|
@@ -38,7 +39,16 @@ export function registerPiWorker(pi) {
|
|
|
38
39
|
+ '- The task needs the web — use `pi-worker-search` / `pi-worker-fetch`',
|
|
39
40
|
parameters: WorkerParams,
|
|
40
41
|
async run(params, signal, ctx) {
|
|
41
|
-
|
|
42
|
+
// Grouped with `research`: this is the same read-only exploration
|
|
43
|
+
// loop the four research workers run, just dispatched by a model
|
|
44
|
+
// rather than by the pipeline. Left ungrouped it would be the one
|
|
45
|
+
// child that never honoured a profile.
|
|
46
|
+
const result = await runWorker({
|
|
47
|
+
prompt: params.prompt,
|
|
48
|
+
cwd: ctx.cwd,
|
|
49
|
+
signal,
|
|
50
|
+
thinking: groupThinkingArgs('research')
|
|
51
|
+
});
|
|
42
52
|
const details = { exitCode: result.exitCode };
|
|
43
53
|
const failure = formatChildFailure(result, 'Worker aborted.');
|
|
44
54
|
if (failure !== null) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-line startup hint shown when /task-config asks for a thinking level the
|
|
3
|
+
* connected model will not honour.
|
|
4
|
+
*
|
|
5
|
+
* WHY IT HAS TO EXIST. pi never says it ignored or downgraded a level. Measured
|
|
6
|
+
* live with a proxy on the request body: a model with `reasoning: false` given
|
|
7
|
+
* `--thinking medium` sends no reasoning field at all, and a model whose
|
|
8
|
+
* `thinkingLevelMap` nulls `off` given `--thinking off` is clamped UP to
|
|
9
|
+
* `medium` — thinking stays on. Both are silent. A reasoning profile the user
|
|
10
|
+
* set and the model erased is worse than no profile feature, because it looks
|
|
11
|
+
* like it worked.
|
|
12
|
+
*
|
|
13
|
+
* ANTI-NAG. This warns once per session and clears on the first keystroke, and
|
|
14
|
+
* that is the whole mechanism — deliberately no "already warned about model X"
|
|
15
|
+
* file. Such a record goes stale the moment models.json is edited, and would
|
|
16
|
+
* suppress the warning at exactly the moment a `/model` switch made it true.
|
|
17
|
+
* brave-warning.ts nags every session for a standing misconfiguration and that
|
|
18
|
+
* is correct; this is the same class. The real anti-nag is `inherit`: with the
|
|
19
|
+
* shipped all-`inherit` table, `reasoningMismatches` returns empty for every
|
|
20
|
+
* model and nothing renders at all.
|
|
21
|
+
*/
|
|
22
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
23
|
+
import { type PiTaskConfig } from '../config/config.js';
|
|
24
|
+
import { REASONING_GROUPS, resolveReasoning } from '../config/reasoning.js';
|
|
25
|
+
import { type ReasoningMismatch } from '../shared/reasoning-capability.js';
|
|
26
|
+
/** Every group's current setting, in the shape `reasoningMismatches` wants. */
|
|
27
|
+
export type GroupSettings = Array<{
|
|
28
|
+
group: (typeof REASONING_GROUPS)[number];
|
|
29
|
+
setting: ReturnType<typeof resolveReasoning>;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Read every group's effective setting from a config.
|
|
33
|
+
*
|
|
34
|
+
* Takes the config rather than calling `getConfig()` so the caller decides where
|
|
35
|
+
* it comes from. A test that has to mutate the live singleton to drive this is a
|
|
36
|
+
* test whose result depends on whatever the developer had saved before it ran.
|
|
37
|
+
*/
|
|
38
|
+
export declare function settingsFrom(cfg: PiTaskConfig): GroupSettings;
|
|
39
|
+
/**
|
|
40
|
+
* The warning line for a set of mismatches.
|
|
41
|
+
*
|
|
42
|
+
* Names the MODEL it checked, because children carry no `-m` and resolve pi's
|
|
43
|
+
* default model, which need not be the host session's — a warning that does not
|
|
44
|
+
* say what it looked at cannot be acted on. Names at most two groups; the count
|
|
45
|
+
* carries the rest, since a line long enough to list seven is a line nobody
|
|
46
|
+
* reads.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatReasoningWarning(modelName: string, mismatches: readonly ReasoningMismatch[]): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* The extra cause line, when the SERVER disagrees with models.json.
|
|
51
|
+
*
|
|
52
|
+
* This is the `/login llama.cpp` case and the only thing the host-side clamp
|
|
53
|
+
* cannot see: pi's built-in llama.cpp provider hardcodes `reasoning: false`, so
|
|
54
|
+
* a perfectly capable server is described to pi as having no reasoning at all.
|
|
55
|
+
* Returns null whenever the two agree, or when there was nothing to compare.
|
|
56
|
+
*/
|
|
57
|
+
export declare function formatCapabilityConflict(serverSupportsEffort: boolean | null, modelDeclaresReasoning: boolean): string | null;
|
|
58
|
+
export declare function registerReasoningWarning(pi: ExtensionAPI,
|
|
59
|
+
/**
|
|
60
|
+
* Where the group settings come from. Defaults to the live config, read at
|
|
61
|
+
* `session_start` so a /task-config change since the last session counts.
|
|
62
|
+
* Injected by tests, which must not depend on the developer's saved config.
|
|
63
|
+
*/
|
|
64
|
+
readSettings?: () => GroupSettings): void;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-line startup hint shown when /task-config asks for a thinking level the
|
|
3
|
+
* connected model will not honour.
|
|
4
|
+
*
|
|
5
|
+
* WHY IT HAS TO EXIST. pi never says it ignored or downgraded a level. Measured
|
|
6
|
+
* live with a proxy on the request body: a model with `reasoning: false` given
|
|
7
|
+
* `--thinking medium` sends no reasoning field at all, and a model whose
|
|
8
|
+
* `thinkingLevelMap` nulls `off` given `--thinking off` is clamped UP to
|
|
9
|
+
* `medium` — thinking stays on. Both are silent. A reasoning profile the user
|
|
10
|
+
* set and the model erased is worse than no profile feature, because it looks
|
|
11
|
+
* like it worked.
|
|
12
|
+
*
|
|
13
|
+
* ANTI-NAG. This warns once per session and clears on the first keystroke, and
|
|
14
|
+
* that is the whole mechanism — deliberately no "already warned about model X"
|
|
15
|
+
* file. Such a record goes stale the moment models.json is edited, and would
|
|
16
|
+
* suppress the warning at exactly the moment a `/model` switch made it true.
|
|
17
|
+
* brave-warning.ts nags every session for a standing misconfiguration and that
|
|
18
|
+
* is correct; this is the same class. The real anti-nag is `inherit`: with the
|
|
19
|
+
* shipped all-`inherit` table, `reasoningMismatches` returns empty for every
|
|
20
|
+
* model and nothing renders at all.
|
|
21
|
+
*/
|
|
22
|
+
import { getConfig } from '../config/config.js';
|
|
23
|
+
import { REASONING_GROUPS, resolveReasoning } from '../config/reasoning.js';
|
|
24
|
+
import { reasoningMismatches } from '../shared/reasoning-capability.js';
|
|
25
|
+
import { probeChatTemplateCaps } from '../shared/model-endpoint.js';
|
|
26
|
+
const WIDGET_KEY = 'pi-task-reasoning-warning';
|
|
27
|
+
/**
|
|
28
|
+
* Read every group's effective setting from a config.
|
|
29
|
+
*
|
|
30
|
+
* Takes the config rather than calling `getConfig()` so the caller decides where
|
|
31
|
+
* it comes from. A test that has to mutate the live singleton to drive this is a
|
|
32
|
+
* test whose result depends on whatever the developer had saved before it ran.
|
|
33
|
+
*/
|
|
34
|
+
export function settingsFrom(cfg) {
|
|
35
|
+
return REASONING_GROUPS.map(group => ({ group, setting: resolveReasoning(group, cfg) }));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The warning line for a set of mismatches.
|
|
39
|
+
*
|
|
40
|
+
* Names the MODEL it checked, because children carry no `-m` and resolve pi's
|
|
41
|
+
* default model, which need not be the host session's — a warning that does not
|
|
42
|
+
* say what it looked at cannot be acted on. Names at most two groups; the count
|
|
43
|
+
* carries the rest, since a line long enough to list seven is a line nobody
|
|
44
|
+
* reads.
|
|
45
|
+
*/
|
|
46
|
+
export function formatReasoningWarning(modelName, mismatches) {
|
|
47
|
+
if (mismatches.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
const shown = mismatches
|
|
50
|
+
.slice(0, 2)
|
|
51
|
+
.map(m => `${m.group} ${m.wanted}→${m.actual}`)
|
|
52
|
+
.join(', ');
|
|
53
|
+
const rest = mismatches.length > 2 ? ` (+${mismatches.length - 2} more)` : '';
|
|
54
|
+
return (`⚠ pi-task: model "${modelName}" will not run the reasoning levels /task-config asks `
|
|
55
|
+
+ `for — ${shown}${rest}. pi clamps to what the model declares. Fix "reasoning" / `
|
|
56
|
+
+ '"thinkingLevelMap" for it in ~/.pi/agent/models.json, or set those steps back to '
|
|
57
|
+
+ '"inherit" in /task-config');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The extra cause line, when the SERVER disagrees with models.json.
|
|
61
|
+
*
|
|
62
|
+
* This is the `/login llama.cpp` case and the only thing the host-side clamp
|
|
63
|
+
* cannot see: pi's built-in llama.cpp provider hardcodes `reasoning: false`, so
|
|
64
|
+
* a perfectly capable server is described to pi as having no reasoning at all.
|
|
65
|
+
* Returns null whenever the two agree, or when there was nothing to compare.
|
|
66
|
+
*/
|
|
67
|
+
export function formatCapabilityConflict(serverSupportsEffort, modelDeclaresReasoning) {
|
|
68
|
+
if (serverSupportsEffort === null)
|
|
69
|
+
return null;
|
|
70
|
+
if (serverSupportsEffort && !modelDeclaresReasoning) {
|
|
71
|
+
return (" — the server's chat template DOES support reasoning; it is the model entry pi is "
|
|
72
|
+
+ 'using that says reasoning:false. `/login llama.cpp` hardcodes that, so a '
|
|
73
|
+
+ 'hand-written models.json provider entry is the fix');
|
|
74
|
+
}
|
|
75
|
+
if (!serverSupportsEffort && modelDeclaresReasoning) {
|
|
76
|
+
return " — the server's chat template does not read reasoning_effort, so only on/off takes effect";
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
export function registerReasoningWarning(pi,
|
|
81
|
+
/**
|
|
82
|
+
* Where the group settings come from. Defaults to the live config, read at
|
|
83
|
+
* `session_start` so a /task-config change since the last session counts.
|
|
84
|
+
* Injected by tests, which must not depend on the developer's saved config.
|
|
85
|
+
*/
|
|
86
|
+
readSettings = () => settingsFrom(getConfig())) {
|
|
87
|
+
pi.on('session_start', (_event, ctx) => {
|
|
88
|
+
// Terminal-only hint: needs an interactive TUI to render and to catch the
|
|
89
|
+
// keystroke that dismisses it.
|
|
90
|
+
if (ctx.mode !== 'tui')
|
|
91
|
+
return;
|
|
92
|
+
const model = ctx.model;
|
|
93
|
+
const mismatches = reasoningMismatches(model, readSettings());
|
|
94
|
+
if (mismatches.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
const base = formatReasoningWarning(model?.name ?? model?.id ?? 'unknown', mismatches);
|
|
97
|
+
if (base === null)
|
|
98
|
+
return;
|
|
99
|
+
let unsubscribe = null;
|
|
100
|
+
let cleared = false;
|
|
101
|
+
const clear = () => {
|
|
102
|
+
cleared = true;
|
|
103
|
+
try {
|
|
104
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* stale ctx after a session switch — nothing to clear */
|
|
108
|
+
}
|
|
109
|
+
unsubscribe?.();
|
|
110
|
+
unsubscribe = null;
|
|
111
|
+
};
|
|
112
|
+
const render = (text) => {
|
|
113
|
+
try {
|
|
114
|
+
ctx.ui.setWidget(WIDGET_KEY, [ctx.ui.theme.fg('warning', text)]);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
if (!render(base))
|
|
122
|
+
return;
|
|
123
|
+
unsubscribe = ctx.ui.onTerminalInput(() => {
|
|
124
|
+
clear();
|
|
125
|
+
return undefined;
|
|
126
|
+
});
|
|
127
|
+
// Fire-and-forget: the server probe only ever REFINES the cause line, so
|
|
128
|
+
// it must not delay the warning or be able to prevent it. A 2s budget and
|
|
129
|
+
// a swallowed failure mean a non-llama.cpp backend costs nothing.
|
|
130
|
+
if (model?.baseUrl) {
|
|
131
|
+
void probeChatTemplateCaps(model.baseUrl)
|
|
132
|
+
.then(caps => {
|
|
133
|
+
if (cleared || caps === null)
|
|
134
|
+
return;
|
|
135
|
+
const extra = formatCapabilityConflict(caps.supportsReasoningEffort, model.reasoning);
|
|
136
|
+
if (extra)
|
|
137
|
+
render(base + extra);
|
|
138
|
+
})
|
|
139
|
+
.catch(() => { });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.20",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"ws": "8.21.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@earendil-works/pi-agent-core": "0.
|
|
36
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
37
|
-
"@earendil-works/pi-tui": "0.
|
|
35
|
+
"@earendil-works/pi-agent-core": "0.84.2",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
37
|
+
"@earendil-works/pi-tui": "0.84.2",
|
|
38
38
|
"@eslint/js": "10.0.1",
|
|
39
39
|
"@types/bun": "1.3.12",
|
|
40
40
|
"@types/qrcode": "1.5.6",
|