@mjasnikovs/pi-task 0.38.2 → 0.38.4
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 +7 -0
- package/dist/config/config.js +10 -4
- package/dist/config/register.d.ts +37 -0
- package/dist/config/register.js +89 -114
- package/dist/remote/events.js +0 -3
- package/dist/remote/register.js +12 -3
- package/dist/task/auto-orchestrator.js +119 -94
- package/dist/task/command-run.d.ts +104 -0
- package/dist/task/command-run.js +138 -0
- package/dist/task/coverage-loop.d.ts +45 -0
- package/dist/task/critique-probes.d.ts +82 -0
- package/dist/task/critique-probes.js +156 -0
- package/dist/task/deep-render-check.d.ts +30 -0
- package/dist/task/deep-render-check.js +19 -11
- package/dist/task/enforce-guidelines.d.ts +14 -17
- package/dist/task/enforce-guidelines.js +44 -31
- package/dist/task/final-gate.d.ts +8 -10
- package/dist/task/final-gate.js +36 -74
- package/dist/task/gate-child.d.ts +104 -0
- package/dist/task/gate-child.js +177 -0
- package/dist/task/gate-deps.d.ts +13 -0
- package/dist/task/gate-deps.js +72 -208
- package/dist/task/orchestrator.js +13 -22
- package/dist/task/phases.js +109 -182
- package/dist/task/plan-session.d.ts +4 -22
- package/dist/task/plan-session.js +4 -33
- package/dist/task/question-dialog.d.ts +71 -0
- package/dist/task/question-dialog.js +89 -0
- package/dist/task/terminal-outcome.d.ts +67 -0
- package/dist/task/terminal-outcome.js +76 -0
- package/dist/task/type-only-answer.js +2 -3
- package/dist/workers/abstention.d.ts +71 -0
- package/dist/workers/abstention.js +108 -0
- package/dist/workers/docs-chunk.d.ts +74 -0
- package/dist/workers/docs-chunk.js +143 -0
- package/dist/workers/docs-core.d.ts +10 -1
- package/dist/workers/docs-core.js +22 -19
- package/dist/workers/docs-index.js +2 -69
- package/dist/workers/docs-project.d.ts +15 -1
- package/dist/workers/docs-project.js +27 -66
- package/dist/workers/fetch-core.d.ts +1 -1
- package/dist/workers/fetch-core.js +2 -1
- package/dist/workers/pi-worker-core.js +157 -86
- package/dist/workers/pi-worker-docs.js +5 -10
- package/dist/workers/pi-worker-fetch.js +8 -1
- package/dist/workers/typeonly-log.js +2 -10
- package/dist/workers/worker-failure.d.ts +91 -0
- package/dist/workers/worker-failure.js +82 -0
- package/package.json +1 -1
package/dist/config/config.d.ts
CHANGED
|
@@ -178,6 +178,13 @@ export declare const STREAM_INACTIVITY_OPTIONS: ReadonlyArray<{
|
|
|
178
178
|
/** Same pinning as {@link sanitizeRequestTimeoutMs}: a hand-edited value that is
|
|
179
179
|
* not one of the offered choices falls back to the default. */
|
|
180
180
|
export declare function sanitizeStreamInactivityMs(value: unknown): number;
|
|
181
|
+
/**
|
|
182
|
+
* The shipped defaults. Exported so tests can start from a KNOWN config instead
|
|
183
|
+
* of `getConfig()`, which reads whatever this machine happens to have on disk —
|
|
184
|
+
* a test that passes or fails depending on the developer's own settings is not
|
|
185
|
+
* testing the code.
|
|
186
|
+
*/
|
|
187
|
+
export declare const DEFAULT_CONFIG: PiTaskConfig;
|
|
181
188
|
/**
|
|
182
189
|
* A hand-edited config can hold anything; keep only string entries so a stray
|
|
183
190
|
* object/number can't reach the child argv as `-e [object Object]`.
|
package/dist/config/config.js
CHANGED
|
@@ -79,7 +79,13 @@ export function sanitizeStreamInactivityMs(value) {
|
|
|
79
79
|
value
|
|
80
80
|
: DEFAULT_STREAM_INACTIVITY_MS;
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* The shipped defaults. Exported so tests can start from a KNOWN config instead
|
|
84
|
+
* of `getConfig()`, which reads whatever this machine happens to have on disk —
|
|
85
|
+
* a test that passes or fails depending on the developer's own settings is not
|
|
86
|
+
* testing the code.
|
|
87
|
+
*/
|
|
88
|
+
export const DEFAULT_CONFIG = {
|
|
83
89
|
remote: true,
|
|
84
90
|
compressReasoning: true,
|
|
85
91
|
autoCommit: true,
|
|
@@ -113,7 +119,7 @@ export function sanitizeExtensionWhitelist(value) {
|
|
|
113
119
|
const CONFIG_PATH = path.join(os.homedir(), '.config', 'pi-task', 'config.json');
|
|
114
120
|
const _g = globalThis;
|
|
115
121
|
if (!_g.__piTaskConfig) {
|
|
116
|
-
_g.__piTaskConfig = { config: { ...
|
|
122
|
+
_g.__piTaskConfig = { config: { ...DEFAULT_CONFIG }, loaded: false };
|
|
117
123
|
}
|
|
118
124
|
const G = _g.__piTaskConfig;
|
|
119
125
|
// Load synchronously on module evaluation so getConfig() is always ready
|
|
@@ -136,10 +142,10 @@ if (!G.loaded) {
|
|
|
136
142
|
if (typeof parsed.yoloMode !== 'boolean')
|
|
137
143
|
delete parsed.yoloMode;
|
|
138
144
|
parsed.debugLogs = sanitizeDebugLogs(parsed.debugLogs);
|
|
139
|
-
G.config = { ...
|
|
145
|
+
G.config = { ...DEFAULT_CONFIG, ...parsed };
|
|
140
146
|
}
|
|
141
147
|
catch {
|
|
142
|
-
G.config = { ...
|
|
148
|
+
G.config = { ...DEFAULT_CONFIG };
|
|
143
149
|
}
|
|
144
150
|
G.loaded = true;
|
|
145
151
|
}
|
|
@@ -38,6 +38,43 @@ declare class BorderedBox implements Component {
|
|
|
38
38
|
invalidate(): void;
|
|
39
39
|
handleInput(data: string): void;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* One /task-config setting, and BOTH directions of its value.
|
|
43
|
+
*
|
|
44
|
+
* `format` renders the stored value for the panel; `apply` parses the chosen
|
|
45
|
+
* label back into it. They are per-row because the two used to be hand-written
|
|
46
|
+
* ladders — a `displayValue` arm to format, a matching `onChange` arm to parse,
|
|
47
|
+
* and three different idioms for the same parse across the four enum settings.
|
|
48
|
+
* Adding one enum setting meant four coordinated edits (row, format arm, parse
|
|
49
|
+
* arm, sanitizer) and NONE of them failed to compile if you forgot it: a missed
|
|
50
|
+
* format arm rendered `String(cfg[id])`, and a missed parse arm let the generic
|
|
51
|
+
* `else` write the boolean `newValue === 'on'` into an enum field. The comment
|
|
52
|
+
* that used to sit in that `else` — explaining why `debugLogs` must not fall
|
|
53
|
+
* into it — was the interface saying it was too shallow.
|
|
54
|
+
*
|
|
55
|
+
* With both directions on the row, `format(apply(cfg, v)) === v` is a property
|
|
56
|
+
* over the whole table, and the panel and the non-TUI listing cannot disagree
|
|
57
|
+
* because both read `format`.
|
|
58
|
+
*/
|
|
59
|
+
export interface ConfigItem {
|
|
60
|
+
id: keyof PiTaskConfig;
|
|
61
|
+
label: string;
|
|
62
|
+
description: string;
|
|
63
|
+
/** Offered values. Omitted for a boolean, which is always on/off. */
|
|
64
|
+
values?: string[];
|
|
65
|
+
/** What the panel shows for the current value. */
|
|
66
|
+
format: (cfg: PiTaskConfig) => string;
|
|
67
|
+
/** Write the chosen label back. A value it does not recognise is ignored. */
|
|
68
|
+
apply: (cfg: PiTaskConfig, chosen: string) => void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Every setting rendered by /task-config, in display order.
|
|
72
|
+
*
|
|
73
|
+
* Exported so the round-trip property below can be asserted over the WHOLE table
|
|
74
|
+
* rather than per setting — the check that would have caught a forgotten arm in
|
|
75
|
+
* either of the two ladders this replaced.
|
|
76
|
+
*/
|
|
77
|
+
export declare const ITEMS: ConfigItem[];
|
|
41
78
|
export declare function extensionItems(extensions: InstalledExtension[], whitelist: readonly string[]): {
|
|
42
79
|
id: string;
|
|
43
80
|
label: string;
|
package/dist/config/register.js
CHANGED
|
@@ -68,70 +68,64 @@ class BorderedBox {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
71
|
+
* The shared pair for a boolean setting: shown as on/off, stored as a boolean.
|
|
72
|
+
* Every non-enum row uses this, so a boolean cannot be given a bespoke parser by
|
|
73
|
+
* accident.
|
|
73
74
|
*/
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
id
|
|
77
|
-
label
|
|
78
|
-
description
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
id: 'parallelResearchWorkers',
|
|
116
|
-
label: 'parallel research',
|
|
117
|
-
description: 'Run the 4 research workers at once instead of one after another. Only faster if '
|
|
118
|
-
+ 'your model backend can answer several requests at the same time — on a single '
|
|
119
|
-
+ 'local GPU it is measurably slower, so leave it off there'
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
id: 'researchCache',
|
|
123
|
-
label: 'research cache',
|
|
124
|
-
description: 'Remember docs and web pages for the length of one run, so later tasks reuse what '
|
|
125
|
-
+ 'the first one already fetched instead of downloading it again. Only external '
|
|
126
|
-
+ 'sources, only successful fetches, and it is dropped when the run ends'
|
|
127
|
-
},
|
|
75
|
+
function booleanItem(id, label, description) {
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
label,
|
|
79
|
+
description,
|
|
80
|
+
format: cfg => (cfg[id] ? 'on' : 'off'),
|
|
81
|
+
apply: (cfg, chosen) => {
|
|
82
|
+
;
|
|
83
|
+
cfg[id] = chosen === 'on';
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Every setting rendered by /task-config, in display order.
|
|
89
|
+
*
|
|
90
|
+
* Exported so the round-trip property below can be asserted over the WHOLE table
|
|
91
|
+
* rather than per setting — the check that would have caught a forgotten arm in
|
|
92
|
+
* either of the two ladders this replaced.
|
|
93
|
+
*/
|
|
94
|
+
export const ITEMS = [
|
|
95
|
+
booleanItem('remote', 'remote control', 'Serve the task UI on your local network so you can follow and steer a run from '
|
|
96
|
+
+ 'your phone. Prints a QR code to scan when it starts'),
|
|
97
|
+
booleanItem('compressReasoning', 'compress thinking', "Shrink the model's thinking blocks once it has moved on, so a long run keeps more "
|
|
98
|
+
+ 'room for the work itself'),
|
|
99
|
+
booleanItem('autoCommit', 'auto-commit', 'Make a git commit before and after every sub-task, so each step is a checkpoint '
|
|
100
|
+
+ 'you can read back or roll back to'),
|
|
101
|
+
booleanItem('verifyWork', 'verify work', 'When a task says it is done, actually run the checks its spec asks for and report '
|
|
102
|
+
+ "PASS or FAIL instead of taking the model's word for it. This is also what lets "
|
|
103
|
+
+ '"enforce guidelines" fix things safely. /task waits for the work to finish'),
|
|
104
|
+
booleanItem('enforceGuidelines', 'enforce guidelines', 'Check what each task committed against your AGENTS.md / CLAUDE.md rules. With '
|
|
105
|
+
+ '"verify work" on it also fixes what it finds, undoing any fix that breaks the '
|
|
106
|
+
+ 'checks; on its own it only reports. /task waits for the work to finish'),
|
|
107
|
+
booleanItem('orientation', 'project tour', 'Show the research workers the shape of the project first — package manifest, '
|
|
108
|
+
+ 'types, schema — so they spend their steps on the question instead of on finding '
|
|
109
|
+
+ 'their way around'),
|
|
110
|
+
booleanItem('parallelResearchWorkers', 'parallel research', 'Run the 4 research workers at once instead of one after another. Only faster if '
|
|
111
|
+
+ 'your model backend can answer several requests at the same time — on a single '
|
|
112
|
+
+ 'local GPU it is measurably slower, so leave it off there'),
|
|
113
|
+
booleanItem('researchCache', 'research cache', 'Remember docs and web pages for the length of one run, so later tasks reuse what '
|
|
114
|
+
+ 'the first one already fetched instead of downloading it again. Only external '
|
|
115
|
+
+ 'sources, only successful fetches, and it is dropped when the run ends'),
|
|
128
116
|
{
|
|
129
117
|
id: 'searchProvider',
|
|
130
118
|
label: 'search engine',
|
|
131
119
|
description: 'Which engine backs web search. Exa and DuckDuckGo work with no setup; Brave needs '
|
|
132
120
|
+ 'a BRAVE_SEARCH_API_KEY in your environment',
|
|
133
121
|
// Display full engine names; the stored config value stays the short id.
|
|
134
|
-
values: SEARCH_PROVIDERS.map(p => SEARCH_PROVIDER_LABELS[p])
|
|
122
|
+
values: SEARCH_PROVIDERS.map(p => SEARCH_PROVIDER_LABELS[p]),
|
|
123
|
+
format: cfg => SEARCH_PROVIDER_LABELS[cfg.searchProvider],
|
|
124
|
+
apply: (cfg, chosen) => {
|
|
125
|
+
const provider = providerForLabel(chosen);
|
|
126
|
+
if (provider)
|
|
127
|
+
cfg.searchProvider = provider;
|
|
128
|
+
}
|
|
135
129
|
},
|
|
136
130
|
{
|
|
137
131
|
id: 'requestTimeoutMs',
|
|
@@ -141,7 +135,14 @@ const ITEMS = [
|
|
|
141
135
|
+ 'hung build. Covers the main session and the checking steps; "off" lets a stuck '
|
|
142
136
|
+ 'command hang the run until you notice',
|
|
143
137
|
// Display human labels; the stored config value stays the ms number.
|
|
144
|
-
values: COMMAND_TIMEOUT_OPTIONS.map(o => o.label)
|
|
138
|
+
values: COMMAND_TIMEOUT_OPTIONS.map(o => o.label),
|
|
139
|
+
format: cfg => COMMAND_TIMEOUT_OPTIONS.find(o => o.ms === cfg.requestTimeoutMs)?.label
|
|
140
|
+
?? `${cfg.requestTimeoutMs}ms`,
|
|
141
|
+
apply: (cfg, chosen) => {
|
|
142
|
+
const opt = COMMAND_TIMEOUT_OPTIONS.find(o => o.label === chosen);
|
|
143
|
+
if (opt)
|
|
144
|
+
cfg.requestTimeoutMs = opt.ms;
|
|
145
|
+
}
|
|
145
146
|
},
|
|
146
147
|
{
|
|
147
148
|
id: 'streamInactivityMs',
|
|
@@ -152,16 +153,19 @@ const ITEMS = [
|
|
|
152
153
|
+ 'pauses while a command runs — local models go quiet for minutes on long prompts, '
|
|
153
154
|
+ 'so leave room',
|
|
154
155
|
// Display human labels; the stored config value stays the ms number.
|
|
155
|
-
values: STREAM_INACTIVITY_OPTIONS.map(o => o.label)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
+ 'For throwaway projects you are not watching'
|
|
156
|
+
values: STREAM_INACTIVITY_OPTIONS.map(o => o.label),
|
|
157
|
+
format: cfg => STREAM_INACTIVITY_OPTIONS.find(o => o.ms === cfg.streamInactivityMs)?.label
|
|
158
|
+
?? `${cfg.streamInactivityMs}ms`,
|
|
159
|
+
apply: (cfg, chosen) => {
|
|
160
|
+
const opt = STREAM_INACTIVITY_OPTIONS.find(o => o.label === chosen);
|
|
161
|
+
if (opt)
|
|
162
|
+
cfg.streamInactivityMs = opt.ms;
|
|
163
|
+
}
|
|
164
164
|
},
|
|
165
|
+
booleanItem('yoloMode', 'yolo mode', 'Stop asking you anything: every question takes the option pi recommends, a failed '
|
|
166
|
+
+ 'check is accepted and written down as debt, and a failed final check is retried '
|
|
167
|
+
+ 'until the budget runs out. Each auto-answer is marked (YOLO) in the task file. '
|
|
168
|
+
+ 'For throwaway projects you are not watching'),
|
|
165
169
|
{
|
|
166
170
|
id: 'debugLogs',
|
|
167
171
|
label: 'debug logs',
|
|
@@ -170,29 +174,15 @@ const ITEMS = [
|
|
|
170
174
|
+ 'failed — a few lines per task. "full" adds everything the model said and every '
|
|
171
175
|
+ 'command it ran, which is most of the size and only useful while you are digging '
|
|
172
176
|
+ 'into a problem. "off" writes nothing, and nothing can be reconstructed later',
|
|
173
|
-
values: [...DEBUG_LOG_OPTIONS]
|
|
177
|
+
values: [...DEBUG_LOG_OPTIONS],
|
|
178
|
+
// The stored value IS the label here, but it still goes through the
|
|
179
|
+
// sanitizer rather than being written raw.
|
|
180
|
+
format: cfg => String(cfg.debugLogs),
|
|
181
|
+
apply: (cfg, chosen) => {
|
|
182
|
+
cfg.debugLogs = sanitizeDebugLogs(chosen);
|
|
183
|
+
}
|
|
174
184
|
}
|
|
175
185
|
];
|
|
176
|
-
/** Human label for the stored command-timeout ms (falls back to the raw ms). */
|
|
177
|
-
function timeoutLabel(ms) {
|
|
178
|
-
return COMMAND_TIMEOUT_OPTIONS.find(o => o.ms === ms)?.label ?? `${ms}ms`;
|
|
179
|
-
}
|
|
180
|
-
/** Human label for the stored stream-inactivity ms (falls back to the raw ms). */
|
|
181
|
-
function streamTimeoutLabel(ms) {
|
|
182
|
-
return STREAM_INACTIVITY_OPTIONS.find(o => o.ms === ms)?.label ?? `${ms}ms`;
|
|
183
|
-
}
|
|
184
|
-
/** What /task-config shows for a setting's current value. */
|
|
185
|
-
function displayValue(cfg, id, isEnum) {
|
|
186
|
-
if (id === 'searchProvider')
|
|
187
|
-
return SEARCH_PROVIDER_LABELS[cfg.searchProvider];
|
|
188
|
-
if (id === 'requestTimeoutMs')
|
|
189
|
-
return timeoutLabel(cfg.requestTimeoutMs);
|
|
190
|
-
if (id === 'streamInactivityMs')
|
|
191
|
-
return streamTimeoutLabel(cfg.streamInactivityMs);
|
|
192
|
-
if (isEnum)
|
|
193
|
-
return String(cfg[id]);
|
|
194
|
-
return cfg[id] ? 'on' : 'off';
|
|
195
|
-
}
|
|
196
186
|
/**
|
|
197
187
|
* One /task-config toggle per installed host extension (GitHub issue #4).
|
|
198
188
|
* The id carries the entry path behind a prefix so the shared onChange handler
|
|
@@ -291,11 +281,11 @@ export function createSettingsPanel(items, theme, onChange, onCancel) {
|
|
|
291
281
|
/** The full settings row list for the current config, in menu order. */
|
|
292
282
|
export function panelItems(cfg, installed, tools = []) {
|
|
293
283
|
return [
|
|
294
|
-
...ITEMS.map(({ id, label, description, values }) => ({
|
|
284
|
+
...ITEMS.map(({ id, label, description, values, format }) => ({
|
|
295
285
|
id: id,
|
|
296
286
|
label,
|
|
297
287
|
description,
|
|
298
|
-
currentValue:
|
|
288
|
+
currentValue: format(cfg),
|
|
299
289
|
values: values ?? ['on', 'off']
|
|
300
290
|
})),
|
|
301
291
|
...toolItems(tools, cfg.commandTimeoutExemptTools),
|
|
@@ -317,7 +307,9 @@ async function handleTaskConfig(_args, ctx, getTools = () => []) {
|
|
|
317
307
|
// it can only be read here, when the menu opens, never at registration.
|
|
318
308
|
const tools = getTools();
|
|
319
309
|
if (ctx.mode !== 'tui') {
|
|
320
|
-
|
|
310
|
+
// Reads the SAME `format` the panel does, so the two renderings cannot
|
|
311
|
+
// disagree about what a setting currently says.
|
|
312
|
+
const lines = ITEMS.map(({ label, format }) => `${label.padEnd(22)} ${format(cfg)}`);
|
|
321
313
|
for (const t of tools) {
|
|
322
314
|
const state = cfg.commandTimeoutExemptTools.includes(t.name) ? 'off' : 'on';
|
|
323
315
|
lines.push(`${('watch: ' + t.name).padEnd(22)} ${state}`);
|
|
@@ -336,30 +328,13 @@ async function handleTaskConfig(_args, ctx, getTools = () => []) {
|
|
|
336
328
|
else if (id.startsWith(TOOL_ID_PREFIX)) {
|
|
337
329
|
cfg.commandTimeoutExemptTools = applyToolToggle(cfg.commandTimeoutExemptTools, id.slice(TOOL_ID_PREFIX.length), newValue === 'on');
|
|
338
330
|
}
|
|
339
|
-
else if (id === 'searchProvider') {
|
|
340
|
-
const provider = providerForLabel(newValue);
|
|
341
|
-
if (provider)
|
|
342
|
-
cfg.searchProvider = provider;
|
|
343
|
-
}
|
|
344
|
-
else if (id === 'requestTimeoutMs') {
|
|
345
|
-
const opt = COMMAND_TIMEOUT_OPTIONS.find(o => o.label === newValue);
|
|
346
|
-
if (opt)
|
|
347
|
-
cfg.requestTimeoutMs = opt.ms;
|
|
348
|
-
}
|
|
349
|
-
else if (id === 'streamInactivityMs') {
|
|
350
|
-
const opt = STREAM_INACTIVITY_OPTIONS.find(o => o.label === newValue);
|
|
351
|
-
if (opt)
|
|
352
|
-
cfg.streamInactivityMs = opt.ms;
|
|
353
|
-
}
|
|
354
|
-
else if (id === 'debugLogs') {
|
|
355
|
-
// The stored value IS the label here, but it must still go
|
|
356
|
-
// through the sanitizer — the generic `else` below would
|
|
357
|
-
// write the boolean `newValue === 'on'` into an enum field.
|
|
358
|
-
cfg.debugLogs = sanitizeDebugLogs(newValue);
|
|
359
|
-
}
|
|
360
331
|
else {
|
|
361
|
-
|
|
362
|
-
|
|
332
|
+
// Every setting parses its own value. There is no generic
|
|
333
|
+
// fallback: the ladder this replaces ended in one that
|
|
334
|
+
// wrote `newValue === 'on'` into whatever field it was
|
|
335
|
+
// handed, so a new enum setting silently became a boolean
|
|
336
|
+
// until someone noticed.
|
|
337
|
+
ITEMS.find(item => item.id === id)?.apply(cfg, newValue);
|
|
363
338
|
}
|
|
364
339
|
saveConfig(cfg).catch(() => { });
|
|
365
340
|
}, () => done(undefined)), { overlay: true, overlayOptions: { width: OVERLAY_WIDTH } });
|
package/dist/remote/events.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { setAgentIdle } from './state.js';
|
|
2
1
|
import { publishNotify } from './bridge.js';
|
|
3
2
|
import { agentStart, appendText, textEnd, appendThinking, thinkingEnd, startTool, updateTool, endTool, agentEnd, addUserTurn, addError, addSystemNote } from './session-state.js';
|
|
4
3
|
/** Mirror pi agent events into the authoritative SessionState. Each handler
|
|
5
4
|
* drives a mutator, which updates the snapshot AND broadcasts the live delta. */
|
|
6
5
|
export function setupEvents(pi) {
|
|
7
6
|
pi.on('agent_start', (_event, ctx) => {
|
|
8
|
-
setAgentIdle(false);
|
|
9
7
|
agentStart(ctx.model?.name);
|
|
10
8
|
});
|
|
11
9
|
pi.on('message_update', (event, _ctx) => {
|
|
@@ -48,7 +46,6 @@ export function setupEvents(pi) {
|
|
|
48
46
|
endTool(event.toolCallId, event.toolName, event.result, event.isError);
|
|
49
47
|
});
|
|
50
48
|
pi.on('agent_end', (_event, ctx) => {
|
|
51
|
-
setAgentIdle(true);
|
|
52
49
|
agentEnd(ctx.getContextUsage(), ctx.model?.name);
|
|
53
50
|
// Deliberately no push: agent_end fires on EVERY host turn — every chat
|
|
54
51
|
// reply, every internal phase turn inside /task, and every internal /task
|
package/dist/remote/register.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { getConfig } from '../config/config.js';
|
|
2
2
|
import { getBridge, dispatchRemoteLine, dispatchRemoteNewSession, makeShimmedCtx, interruptAgent, registerBridgeCommand, registerRemoteOnlyCommand, publishNotify } from './bridge.js';
|
|
3
3
|
import { setupEvents } from './events.js';
|
|
4
|
-
import { reset, addUserTurn, setHeld } from './session-state.js';
|
|
4
|
+
import { reset, addUserTurn, setHeld, getState } from './session-state.js';
|
|
5
5
|
import { html } from './ui.js';
|
|
6
6
|
import { qrLines } from './qr.js';
|
|
7
7
|
import { startServer, formatAddresses } from './server.js';
|
|
8
8
|
import { ensureTailscaleServe, teardownTailscaleServe, planRemoteUrls, hostFromResult } from './tailscale.js';
|
|
9
|
-
import { isAgentIdle } from './state.js';
|
|
10
9
|
import { holdInput, isRunActive, clearHeldInput, heldInput, setHeldInputListener } from '../task/mid-run-input.js';
|
|
11
10
|
const _g = globalThis;
|
|
12
11
|
if (!_g.__piRemote)
|
|
@@ -19,7 +18,17 @@ const S = _g.__piRemote;
|
|
|
19
18
|
*/
|
|
20
19
|
export function routePlainLine(plain, send) {
|
|
21
20
|
addUserTurn(plain);
|
|
22
|
-
|
|
21
|
+
// Read the run flag from SessionState, which is the only thing that owns it.
|
|
22
|
+
// There used to be a mirror of it here (remote/state.ts: a module-level `let`
|
|
23
|
+
// plus a getter/setter, set alongside agentStart/agentEnd in events.ts). Two
|
|
24
|
+
// sources of truth for one boolean, and they disagreed twice over: SessionState
|
|
25
|
+
// also clears agentRunning in addError and reset, neither of which touched the
|
|
26
|
+
// mirror — so an errored turn or a /new left this branch steering into a turn
|
|
27
|
+
// SessionState already considered finished. And the mirror was a plain module
|
|
28
|
+
// `let` while every other piece of remote state deliberately lives on
|
|
29
|
+
// globalThis to survive jiti re-evaluation, so it silently reset on session
|
|
30
|
+
// switch. Deleting it removed complexity rather than moving it.
|
|
31
|
+
if (getState().agentRunning) {
|
|
23
32
|
// A live turn: steer it (inject into the current generation) so the
|
|
24
33
|
// nudge lands immediately.
|
|
25
34
|
send(plain, { deliverAs: 'steer' });
|