@mjasnikovs/pi-task 0.38.30 → 0.38.32
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 +2 -1
- package/dist/config/config.d.ts +16 -2
- package/dist/config/config.js +7 -2
- package/dist/config/group-args.d.ts +52 -0
- package/dist/config/group-args.js +110 -0
- package/dist/config/group-models.d.ts +88 -0
- package/dist/config/group-models.js +117 -0
- package/dist/config/groups.d.ts +76 -0
- package/dist/config/groups.js +110 -0
- package/dist/config/option-picker.d.ts +42 -0
- package/dist/config/option-picker.js +73 -0
- package/dist/config/reasoning.d.ts +22 -63
- package/dist/config/reasoning.js +37 -108
- package/dist/config/register.d.ts +98 -12
- package/dist/config/register.js +228 -23
- package/dist/index.js +4 -0
- package/dist/remote/push.js +1 -7
- package/dist/shared/command-watchdog.d.ts +63 -0
- package/dist/shared/command-watchdog.js +87 -0
- package/dist/shared/data-home.d.ts +8 -0
- package/dist/shared/data-home.js +14 -0
- package/dist/shared/model-endpoint.d.ts +53 -0
- package/dist/shared/model-endpoint.js +98 -2
- package/dist/shared/reasoning-capability.d.ts +25 -5
- package/dist/shared/reasoning-capability.js +18 -9
- package/dist/task/auto-orchestrator.js +14 -3
- package/dist/task/child-runner.d.ts +92 -15
- package/dist/task/child-runner.js +303 -66
- package/dist/task/context-usage.d.ts +46 -0
- package/dist/task/context-usage.js +41 -0
- package/dist/task/failure-classifier.js +24 -1
- package/dist/task/gate-child.d.ts +15 -4
- package/dist/task/gate-child.js +2 -2
- package/dist/task/gate-deps.js +7 -2
- package/dist/task/implementation-guards.d.ts +26 -0
- package/dist/task/implementation-guards.js +177 -0
- package/dist/task/implementation-hold.d.ts +118 -0
- package/dist/task/implementation-hold.js +165 -0
- package/dist/task/implementation-turn.d.ts +5 -0
- package/dist/task/implementation-turn.js +12 -1
- package/dist/task/loop-detector.d.ts +18 -0
- package/dist/task/loop-detector.js +22 -2
- package/dist/task/model-hold-stash.d.ts +43 -0
- package/dist/task/model-hold-stash.js +70 -0
- package/dist/task/orchestrator.d.ts +18 -5
- package/dist/task/orchestrator.js +63 -6
- package/dist/task/phases.js +18 -5
- package/dist/task/research-worker.d.ts +2 -2
- package/dist/task/research-worker.js +1 -1
- package/dist/workers/docs-core.js +2 -2
- package/dist/workers/docs-lookup.d.ts +4 -3
- package/dist/workers/docs-lookup.js +1 -1
- package/dist/workers/fetch-core.js +2 -2
- package/dist/workers/focused-extractor.d.ts +6 -4
- package/dist/workers/focused-extractor.js +17 -5
- package/dist/workers/index.js +2 -0
- package/dist/workers/model-warning.d.ts +69 -0
- package/dist/workers/model-warning.js +113 -0
- package/dist/workers/pi-worker-core.d.ts +9 -38
- package/dist/workers/pi-worker-core.js +8 -86
- package/dist/workers/pi-worker-docs.js +2 -2
- package/dist/workers/pi-worker.js +4 -4
- package/dist/workers/reasoning-warning.d.ts +17 -9
- package/dist/workers/reasoning-warning.js +69 -22
- package/dist/workers/single-read-guard.d.ts +6 -6
- package/dist/workers/single-read-guard.js +8 -8
- package/dist/workers/worker-profiles.d.ts +11 -3
- package/dist/workers/worker-profiles.js +33 -1
- package/package.json +1 -1
- package/dist/config/reasoning-args.d.ts +0 -23
- package/dist/config/reasoning-args.js +0 -28
- package/dist/task/implementation-thinking.d.ts +0 -56
- package/dist/task/implementation-thinking.js +0 -32
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
*
|
|
22
22
|
* - RepeatedCallGuard: "no identical search twice", for grep/find/ls — the
|
|
23
23
|
* shapes the read guard cannot see, such as the same grep pattern re-run
|
|
24
|
-
* against the same path. Keyed
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* against the same path. Keyed with `loopKey`, the same identity
|
|
25
|
+
* `LoopDetector.record` uses, so argument key order never causes a miss and
|
|
26
|
+
* only an identical repeat trips. A different pattern on the same file still
|
|
27
|
+
* passes.
|
|
28
28
|
*
|
|
29
29
|
* Pure logic, no I/O — the extension does path resolution and tool routing.
|
|
30
30
|
*/
|
|
31
|
-
import {
|
|
31
|
+
import { loopKey } from '../task/loop-detector.js';
|
|
32
32
|
/**
|
|
33
33
|
* The error text the model receives in place of the re-read's contents.
|
|
34
34
|
*
|
|
@@ -105,11 +105,11 @@ export class RepeatedCallGuard {
|
|
|
105
105
|
/**
|
|
106
106
|
* Record a `toolName` call with `args`. Returns a ReadBlock the second time
|
|
107
107
|
* the same (toolName, stable-stringified args) pair is seen (and every time
|
|
108
|
-
* after), else null on the first.
|
|
109
|
-
*
|
|
108
|
+
* after), else null on the first. Shares the LoopDetector's key, so argument
|
|
109
|
+
* key-order never causes a miss; only byte-identical calls collapse.
|
|
110
110
|
*/
|
|
111
111
|
check(toolName, args) {
|
|
112
|
-
const key =
|
|
112
|
+
const key = loopKey({ name: toolName, args });
|
|
113
113
|
if (this.seen.has(key)) {
|
|
114
114
|
return { block: true, reason: repeatedCallReason(toolName) };
|
|
115
115
|
}
|
|
@@ -242,15 +242,15 @@ export declare const DEFAULT_LOOP_PROGRESS: {
|
|
|
242
242
|
readonly limit: 8;
|
|
243
243
|
readonly churnFactor: 2;
|
|
244
244
|
};
|
|
245
|
-
export type WorkerProfileId = 'research' | 'gate' | 'adhoc';
|
|
245
|
+
export type WorkerProfileId = 'research' | 'gate' | 'adhoc' | 'phase';
|
|
246
246
|
/**
|
|
247
247
|
* The facts a profile needs that are NOT policy: user config, and which of the
|
|
248
248
|
* four research workers is the docs-capable one.
|
|
249
249
|
*/
|
|
250
250
|
export interface WorkerPolicyInputs {
|
|
251
|
-
/** gate: `config.requestTimeoutMs`. */
|
|
251
|
+
/** gate, phase: `config.requestTimeoutMs`. */
|
|
252
252
|
commandTimeoutMs?: number;
|
|
253
|
-
/** gate: `config.streamInactivityMs`. */
|
|
253
|
+
/** gate, phase: `config.streamInactivityMs`. */
|
|
254
254
|
streamInactivityMs?: number;
|
|
255
255
|
/** research: only `worker:apis` fans out, so only it can be scaled. */
|
|
256
256
|
fanoutBounded?: boolean;
|
|
@@ -289,6 +289,14 @@ export declare const WORKER_PROFILES: {
|
|
|
289
289
|
carryForward: false;
|
|
290
290
|
};
|
|
291
291
|
};
|
|
292
|
+
readonly phase: {
|
|
293
|
+
readonly id: "phase";
|
|
294
|
+
readonly why: string;
|
|
295
|
+
readonly resolve: (inputs: WorkerPolicyInputs) => {
|
|
296
|
+
guards: WorkerGuards;
|
|
297
|
+
carryForward: false;
|
|
298
|
+
};
|
|
299
|
+
};
|
|
292
300
|
};
|
|
293
301
|
/** Resolve one profile. The only way a caller should obtain a policy. */
|
|
294
302
|
export declare function workerPolicy(id: WorkerProfileId, inputs?: WorkerPolicyInputs): WorkerGuardPolicy;
|
|
@@ -50,7 +50,10 @@
|
|
|
50
50
|
* `RESTART_ORDER` and `FAILURE_ORDER` are untouched. This is a third view of
|
|
51
51
|
* the same key, not a merge of the two orderings.
|
|
52
52
|
*/
|
|
53
|
-
|
|
53
|
+
// From loop-detector.ts, NOT child-runner.ts: child-runner reads this table, and
|
|
54
|
+
// these are evaluated at module top level below, so that import would close a
|
|
55
|
+
// cycle whose only symptom is a TDZ ReferenceError on import order.
|
|
56
|
+
import { LOOP_THRESHOLD, LOOP_WINDOW, MAX_LOOP_RESTARTS } from '../task/loop-detector.js';
|
|
54
57
|
import { CONTEXT_CHURN_FACTOR, NO_PROGRESS_LIMIT } from '../task/stall-detector.js';
|
|
55
58
|
import { fanoutTimeoutPolicy, workerCarryForward, workerProgressCeilingMs } from '../task/research-fanout-budget.js';
|
|
56
59
|
/**
|
|
@@ -198,6 +201,35 @@ export const WORKER_PROFILES = {
|
|
|
198
201
|
guards['stream-stall'] = inputs.streamInactivityMs ?? 0;
|
|
199
202
|
return { guards, carryForward: false };
|
|
200
203
|
}
|
|
204
|
+
},
|
|
205
|
+
phase: {
|
|
206
|
+
id: 'phase',
|
|
207
|
+
why: 'The spec-pipeline and planning children (runPhaseChild). Every call site '
|
|
208
|
+
+ "but one passes `read` or `''`, and none of them EDITS, so the "
|
|
209
|
+
+ 'path-revisit rule stays ON, unlike gate: re-reading one file here is '
|
|
210
|
+
+ 'the thrash it was written for, not the job. '
|
|
211
|
+
+ 'THE COMMAND WATCHDOG IS WHY THIS ROW EXISTS. verify-tooling holds '
|
|
212
|
+
+ '`read,bash`, and a hung command there was unkillable: the stream '
|
|
213
|
+
+ 'watchdog SUSPENDS for the duration of a tool call, the dead-backend '
|
|
214
|
+
+ 'probe reads a reachable endpoint as alive, and both runaway detectors '
|
|
215
|
+
+ 'wait on a result that never arrives. Only a user ESC could end it. '
|
|
216
|
+
+ 'The wall clock stays OFF as PHASE_CHILD_TIMEOUT_MS decided for a FIXED '
|
|
217
|
+
+ "cap; that does not settle research's progress-based ceiling. "
|
|
218
|
+
+ 'PARTIALLY CONSUMED: runPhaseChild has its own strike loop and reads '
|
|
219
|
+
+ 'only `command-timeout`, `stream-stall`, `stalled` and `loop`, so '
|
|
220
|
+
+ 'setting `worker-timeout` or `connection-error` here does NOTHING.',
|
|
221
|
+
resolve: inputs => {
|
|
222
|
+
const guards = baseGuards();
|
|
223
|
+
// INERT for this profile — runPhaseChild never reads it. Zeroed anyway
|
|
224
|
+
// so the row cannot be mistaken for research's armed 240s cap.
|
|
225
|
+
guards['worker-timeout'] = { timeoutMs: 0, progressCeilingMs: null, fanout: null };
|
|
226
|
+
// Both ceilings are the user's own settings, as they are for gate: the
|
|
227
|
+
// number is theirs, the decision to arm it is this row's. 0 from a
|
|
228
|
+
// caller that hands none, so a harness cannot silently acquire a guard.
|
|
229
|
+
guards['command-timeout'] = inputs.commandTimeoutMs ?? 0;
|
|
230
|
+
guards['stream-stall'] = inputs.streamInactivityMs ?? 0;
|
|
231
|
+
return { guards, carryForward: false };
|
|
232
|
+
}
|
|
201
233
|
// `as const satisfies`, not an annotation — the same reason RESTART_ORDER
|
|
202
234
|
// gives: an annotation widens each row back to `WorkerProfile`, and the
|
|
203
235
|
// `why` strings and literal ids stop being visible to a reader or a test.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.32",
|
|
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",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The live-config bridge for reasoning profiles: group in, argv fragment out.
|
|
3
|
-
*
|
|
4
|
-
* Separate from reasoning.ts because that module must take no import with a
|
|
5
|
-
* runtime side effect — see its header. The `getConfig()` read lives here
|
|
6
|
-
* instead: this file imports both, and nothing in config/ imports it back, so
|
|
7
|
-
* the graph stays a tree.
|
|
8
|
-
*
|
|
9
|
-
* Read PER CALL, never cached at module scope, so a /task-config change lands on
|
|
10
|
-
* the next child without a restart. Same contract `childBaseArgs` keeps.
|
|
11
|
-
*/
|
|
12
|
-
import { type PiTaskConfig } from './config.js';
|
|
13
|
-
import { type ReasoningGroup } from './reasoning.js';
|
|
14
|
-
/**
|
|
15
|
-
* The `['--thinking', level]` fragment for a group, or `[]` when the group is
|
|
16
|
-
* `inherit` and the child should keep falling back to settings.json.
|
|
17
|
-
*
|
|
18
|
-
* Every argv builder calls this rather than reading config itself. The two
|
|
19
|
-
* callers that skip it are not argv builders: the host-session turn
|
|
20
|
-
* (implementation-thinking.ts) and the settings UI (register.ts) both need the
|
|
21
|
-
* level itself, not a flag, so they call `resolveReasoning` directly.
|
|
22
|
-
*/
|
|
23
|
-
export declare function groupThinkingArgs(group: ReasoningGroup, cfg?: PiTaskConfig): string[];
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The live-config bridge for reasoning profiles: group in, argv fragment out.
|
|
3
|
-
*
|
|
4
|
-
* Separate from reasoning.ts because that module must take no import with a
|
|
5
|
-
* runtime side effect — see its header. The `getConfig()` read lives here
|
|
6
|
-
* instead: this file imports both, and nothing in config/ imports it back, so
|
|
7
|
-
* the graph stays a tree.
|
|
8
|
-
*
|
|
9
|
-
* Read PER CALL, never cached at module scope, so a /task-config change lands on
|
|
10
|
-
* the next child without a restart. Same contract `childBaseArgs` keeps.
|
|
11
|
-
*/
|
|
12
|
-
import { getConfig } from './config.js';
|
|
13
|
-
import { resolveReasoning, thinkingArgs } from './reasoning.js';
|
|
14
|
-
/**
|
|
15
|
-
* The `['--thinking', level]` fragment for a group, or `[]` when the group is
|
|
16
|
-
* `inherit` and the child should keep falling back to settings.json.
|
|
17
|
-
*
|
|
18
|
-
* Every argv builder calls this rather than reading config itself. The two
|
|
19
|
-
* callers that skip it are not argv builders: the host-session turn
|
|
20
|
-
* (implementation-thinking.ts) and the settings UI (register.ts) both need the
|
|
21
|
-
* level itself, not a flag, so they call `resolveReasoning` directly.
|
|
22
|
-
*/
|
|
23
|
-
export function groupThinkingArgs(group, cfg) {
|
|
24
|
-
// The default is evaluated HERE, per call. Hoisting the read to module scope
|
|
25
|
-
// would leave every test green, so the optional parameter is what makes the
|
|
26
|
-
// per-call contract assertable.
|
|
27
|
-
return thinkingArgs(resolveReasoning(group, cfg ?? getConfig()));
|
|
28
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hold the host session at the `implementation` group's thinking level for the
|
|
3
|
-
* duration of one implementation turn, then put it back.
|
|
4
|
-
*
|
|
5
|
-
* WHY THIS GROUP IS NOT LIKE THE OTHERS
|
|
6
|
-
* -------------------------------------
|
|
7
|
-
* Every other reasoning group runs in a child process, so its level is one argv
|
|
8
|
-
* flag (`--thinking <level>`, built in reasoning-args.ts) and it dies with the
|
|
9
|
-
* child. The implementation turn runs in the USER'S OWN session
|
|
10
|
-
* (orchestrator.ts `sendSpec` -> `sendUserMessage` -> `superviseImplementation`),
|
|
11
|
-
* so the only lever is `pi.setThinkingLevel`, which is session-global.
|
|
12
|
-
*
|
|
13
|
-
* THREE THINGS pi DOES that this has to survive:
|
|
14
|
-
*
|
|
15
|
-
* 1. IT PERSISTS. pi-coding-agent's agent-session `setThinkingLevel` calls
|
|
16
|
-
* `settingsManager.setDefaultThinkingLevel(...)` whenever the effective
|
|
17
|
-
* level actually changes, and that writes pi's global settings file
|
|
18
|
-
* (`~/.pi/agent/settings.json`). Without the restore, running one task would
|
|
19
|
-
* silently rewrite the user's global default. That makes `release()`
|
|
20
|
-
* load-bearing, not tidy-up.
|
|
21
|
-
* 2. IT CLAMPS, to the levels the model declares. A model with no reasoning
|
|
22
|
-
* support offers only `off`, so asking for `medium` yields `off`. The
|
|
23
|
-
* restore therefore writes back what was READ after setting, never what was
|
|
24
|
-
* asked for — otherwise a clamp would ratchet the stored default a little
|
|
25
|
-
* further every run.
|
|
26
|
-
* 3. IT IS OBSERVABLE, and the user can change it mid-turn: `shift+tab` is the
|
|
27
|
-
* default binding for `app.thinking.cycle`, and a change invalidates the
|
|
28
|
-
* footer. Restoring blindly would clobber a choice they just made. We detect
|
|
29
|
-
* it by comparing the live level at release against what we applied: if it
|
|
30
|
-
* has moved, somebody else moved it, and we leave it alone.
|
|
31
|
-
*
|
|
32
|
-
* We compare rather than subscribe because the extension API's `on(...)` returns
|
|
33
|
-
* `void` — there is no unsubscribe handle — so a per-turn listener could only
|
|
34
|
-
* ever be added, never removed. The comparison answers the same question with no
|
|
35
|
-
* accumulating state.
|
|
36
|
-
*/
|
|
37
|
-
import type { ThinkingLevel } from '@earendil-works/pi-agent-core';
|
|
38
|
-
import { type GroupSetting } from '../config/reasoning.js';
|
|
39
|
-
/**
|
|
40
|
-
* The slice of the extension API this needs, named so tests can drive the
|
|
41
|
-
* hold-and-restore with a fake object instead of a live pi session.
|
|
42
|
-
*/
|
|
43
|
-
export interface ThinkingControl {
|
|
44
|
-
get(): ThinkingLevel;
|
|
45
|
-
set(level: ThinkingLevel): void;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Put the session at the implementation group's level and return the function
|
|
49
|
-
* that puts it back. Always call the returned function — `finally`, not the
|
|
50
|
-
* happy path.
|
|
51
|
-
*
|
|
52
|
-
* `inherit` makes NO call at all, not even a redundant set-to-current. It means
|
|
53
|
-
* the same thing here as in `thinkingArgs`, which emits no `--thinking` flag for
|
|
54
|
-
* it: leave the level wherever it already is.
|
|
55
|
-
*/
|
|
56
|
-
export declare function holdImplementationThinking(control: ThinkingControl, setting?: GroupSetting): () => void;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { getConfig } from '../config/config.js';
|
|
2
|
-
import { resolveReasoning } from '../config/reasoning.js';
|
|
3
|
-
/**
|
|
4
|
-
* Put the session at the implementation group's level and return the function
|
|
5
|
-
* that puts it back. Always call the returned function — `finally`, not the
|
|
6
|
-
* happy path.
|
|
7
|
-
*
|
|
8
|
-
* `inherit` makes NO call at all, not even a redundant set-to-current. It means
|
|
9
|
-
* the same thing here as in `thinkingArgs`, which emits no `--thinking` flag for
|
|
10
|
-
* it: leave the level wherever it already is.
|
|
11
|
-
*/
|
|
12
|
-
export function holdImplementationThinking(control, setting = resolveReasoning('implementation', getConfig())) {
|
|
13
|
-
if (setting === 'inherit')
|
|
14
|
-
return () => { };
|
|
15
|
-
const before = control.get();
|
|
16
|
-
control.set(setting);
|
|
17
|
-
// Post-clamp, so a model that cannot do `medium` does not leave us believing
|
|
18
|
-
// it is at `medium` and treating the user's later change as our own.
|
|
19
|
-
const applied = control.get();
|
|
20
|
-
if (applied === before)
|
|
21
|
-
return () => { };
|
|
22
|
-
let released = false;
|
|
23
|
-
return () => {
|
|
24
|
-
// Idempotent by contract: only the first call restores. A later call
|
|
25
|
-
// would write `before` on top of whatever the level is by then.
|
|
26
|
-
if (released)
|
|
27
|
-
return;
|
|
28
|
-
released = true;
|
|
29
|
-
if (control.get() === applied)
|
|
30
|
-
control.set(before);
|
|
31
|
-
};
|
|
32
|
-
}
|