@mjasnikovs/pi-task 0.10.2 → 0.10.3
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/task/child-runner.d.ts +2 -0
- package/dist/task/orchestrator.js +11 -5
- package/dist/task/phases.js +17 -2
- package/dist/task/prompts.js +3 -1
- package/dist/workers/docs-extension.d.ts +2 -0
- package/dist/workers/docs-extension.js +4 -0
- package/dist/workers/pi-worker-core.d.ts +4 -0
- package/dist/workers/pi-worker-core.js +4 -2
- package/package.json +1 -1
|
@@ -38,6 +38,8 @@ interface PhaseDeps {
|
|
|
38
38
|
*/
|
|
39
39
|
recordSubStep?: (label: string, ms: number) => void;
|
|
40
40
|
spawn?: SpawnFn;
|
|
41
|
+
/** Write a timestamped line to the per-task debug log. Fire-and-forget. */
|
|
42
|
+
logDebug?: (msg: string) => void;
|
|
41
43
|
}
|
|
42
44
|
export type { PhaseDeps };
|
|
43
45
|
/**
|
|
@@ -164,6 +164,13 @@ export class TaskRunner {
|
|
|
164
164
|
// any phase work — and recover it if the session dies mid-pipeline.
|
|
165
165
|
if (this._onStart)
|
|
166
166
|
await this._onStart(id);
|
|
167
|
+
// Wire up per-task debug log (<cwd>/.pi-tasks/TASK_XXXX-debug.log).
|
|
168
|
+
const debugLogPath = path.join(tasksDir(cwd), `${id}-debug.log`);
|
|
169
|
+
this._deps.logDebug = (msg) => {
|
|
170
|
+
const line = `${new Date().toISOString()} ${msg}\n`;
|
|
171
|
+
fsp.appendFile(debugLogPath, line).catch(() => { });
|
|
172
|
+
};
|
|
173
|
+
this._deps.logDebug(`run: start phase=${resumePhase}`);
|
|
167
174
|
// Register as active.
|
|
168
175
|
this._widgetState.taskId = id;
|
|
169
176
|
this._widgetState.title = title;
|
|
@@ -194,6 +201,7 @@ export class TaskRunner {
|
|
|
194
201
|
continue;
|
|
195
202
|
}
|
|
196
203
|
await advance(phase.name);
|
|
204
|
+
this._deps.logDebug?.(`phase:${phase.name}: start`);
|
|
197
205
|
const children = [];
|
|
198
206
|
this._currentPhaseChildren = children;
|
|
199
207
|
const phaseStart = Date.now();
|
|
@@ -202,12 +210,10 @@ export class TaskRunner {
|
|
|
202
210
|
out = await phase.run(this._deps, this._pc);
|
|
203
211
|
}
|
|
204
212
|
finally {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
ms: Date.now() - phaseStart,
|
|
208
|
-
children
|
|
209
|
-
});
|
|
213
|
+
const phaseMs = Date.now() - phaseStart;
|
|
214
|
+
this._timings.push({ label: phase.name, ms: phaseMs, children });
|
|
210
215
|
this._currentPhaseChildren = null;
|
|
216
|
+
this._deps.logDebug?.(`phase:${phase.name}: done ms=${phaseMs}`);
|
|
211
217
|
}
|
|
212
218
|
await setTaskSection(cwd, id, phase.section, out);
|
|
213
219
|
this._pc[phase.field] = out;
|
package/dist/task/phases.js
CHANGED
|
@@ -74,6 +74,7 @@ export async function phaseVerifyTooling(deps, research) {
|
|
|
74
74
|
await setTaskSection(deps.cwd, deps.taskId, 'verified tooling', verifiedSection);
|
|
75
75
|
return replaceToolingWithVerified(research, parsed.verified);
|
|
76
76
|
}
|
|
77
|
+
const DOCS_EXTENSION_PATH = new URL('../workers/docs-extension.js', import.meta.url).pathname;
|
|
77
78
|
export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
78
79
|
const docsRawFn = researchDeps.docsRaw ?? docsRaw;
|
|
79
80
|
const fetchRawFn = researchDeps.fetchRaw ?? fetchRaw;
|
|
@@ -192,7 +193,12 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
192
193
|
label: 'worker:files',
|
|
193
194
|
prompt: appendNoThink(promptHeader + RESEARCH_FILES_PROMPT(refined))
|
|
194
195
|
},
|
|
195
|
-
{
|
|
196
|
+
{
|
|
197
|
+
label: 'worker:apis',
|
|
198
|
+
prompt: appendNoThink(promptHeader + RESEARCH_APIS_PROMPT(refined)),
|
|
199
|
+
tools: 'read,grep,find,ls,pi-worker-docs',
|
|
200
|
+
extensions: [DOCS_EXTENSION_PATH]
|
|
201
|
+
},
|
|
196
202
|
{
|
|
197
203
|
label: 'worker:context',
|
|
198
204
|
prompt: appendNoThink(promptHeader + RESEARCH_CONTEXT_PROMPT(refined)),
|
|
@@ -209,13 +215,22 @@ export async function phaseResearch(deps, refined, researchDeps = {}) {
|
|
|
209
215
|
];
|
|
210
216
|
const workerResults = [];
|
|
211
217
|
for (const spec of workerSpecs) {
|
|
218
|
+
deps.logDebug?.(`${spec.label}: start`);
|
|
212
219
|
const r = await recordWorker(spec.label, runWorker({
|
|
213
220
|
prompt: spec.prompt,
|
|
214
221
|
cwd: deps.cwd,
|
|
215
222
|
signal: deps.signal,
|
|
216
223
|
spawn: deps.spawn,
|
|
217
|
-
...(spec.tools ? { tools: spec.tools } : {})
|
|
224
|
+
...(spec.tools ? { tools: spec.tools } : {}),
|
|
225
|
+
...(spec.extensions ? { extensions: spec.extensions } : {}),
|
|
226
|
+
onLine: line => {
|
|
227
|
+
deps.logDebug?.(`${spec.label}: ${line}`);
|
|
228
|
+
deps.onChildOutput?.(`${spec.label}: ${line}`);
|
|
229
|
+
}
|
|
218
230
|
}));
|
|
231
|
+
deps.logDebug?.(`${spec.label}: done exit=${r.exitCode} wait=${r.waitMs}ms work=${r.workMs}ms`
|
|
232
|
+
+ (r.stderr ? ` stderr=${r.stderr.slice(0, 300)}` : '')
|
|
233
|
+
+ (r.leakedToolCall ? ` leaked=${r.leakedToolCall.trim().slice(0, 80)}` : ''));
|
|
219
234
|
updateProgress();
|
|
220
235
|
workerResults.push(r);
|
|
221
236
|
}
|
package/dist/task/prompts.js
CHANGED
|
@@ -88,7 +88,9 @@ No section header. No other sections. No preamble.
|
|
|
88
88
|
|
|
89
89
|
Task:
|
|
90
90
|
${refined}`;
|
|
91
|
-
const RESEARCH_APIS_PROMPT = (refined) => `You are doing targeted research for an AI coding agent. Use the read, grep, find, and ls tools to identify the commands, functions, types, and interfaces the agent will use for the following task.
|
|
91
|
+
const RESEARCH_APIS_PROMPT = (refined) => `You are doing targeted research for an AI coding agent. Use the read, grep, find, and ls tools — and \`pi-worker-docs\` for installed npm packages — to identify the commands, functions, types, and interfaces the agent will use for the following task.
|
|
92
|
+
|
|
93
|
+
NPM PACKAGES — use pi-worker-docs, NOT file reads: for any third-party npm package (e.g. "zod", "hono", "drizzle-orm"), call \`pi-worker-docs(module, query)\` to get its type signatures and API surface. Do NOT open node_modules source files directly — those reads are expensive and produce far more noise than the tool. The tool returns a compact, focused excerpt in a fraction of the token cost.
|
|
92
94
|
|
|
93
95
|
APIS owns symbols and commands BY NAME ONLY. Do NOT include any file path or path fragment — no \`package.json\`, no \`./src/foo.ts\`, no \`package.json#scripts.lint\`. If the symbol is a script defined in package.json, write the invocation (\`npm run lint\`), not its location. If the symbol is a config file, it does not belong in APIS at all — it belongs in FILES.
|
|
94
96
|
|
|
@@ -6,6 +6,10 @@ export interface RunWorkerInput {
|
|
|
6
6
|
spawn?: SpawnFn;
|
|
7
7
|
/** Comma-separated tool whitelist passed to `pi --tools`. Defaults to read,grep,find,ls. */
|
|
8
8
|
tools?: string;
|
|
9
|
+
/** Extension entry-point paths to load via `-e <path>` before CHILD_BASE_ARGS. */
|
|
10
|
+
extensions?: string[];
|
|
11
|
+
/** Called for each tool execution start and text-writing event inside the worker. */
|
|
12
|
+
onLine?: (line: string) => void;
|
|
9
13
|
}
|
|
10
14
|
export interface RunWorkerResult {
|
|
11
15
|
text: string;
|
|
@@ -12,7 +12,8 @@ import { detectLeakedToolCall, leakedToolCallHint, MAX_LEAK_RETRIES } from '../s
|
|
|
12
12
|
const DEFAULT_TOOLS = 'read,grep,find,ls';
|
|
13
13
|
export async function runWorker(input) {
|
|
14
14
|
const tools = input.tools ?? DEFAULT_TOOLS;
|
|
15
|
-
const
|
|
15
|
+
const extensionArgs = (input.extensions ?? []).flatMap(e => ['-e', e]);
|
|
16
|
+
const baseArgs = [...extensionArgs, ...CHILD_BASE_ARGS, '--mode', 'json', '--tools', tools];
|
|
16
17
|
let hint = null;
|
|
17
18
|
for (let attempt = 0;; attempt++) {
|
|
18
19
|
const prompt = hint === null ? input.prompt : `${hint}\n\n${input.prompt}`;
|
|
@@ -23,7 +24,8 @@ export async function runWorker(input) {
|
|
|
23
24
|
const result = await runChildDefault(invocation, input.cwd, input.signal, {
|
|
24
25
|
mode: 'json-events',
|
|
25
26
|
onFirstByte: () => (tFirstByte = Date.now()),
|
|
26
|
-
onToolCall: call => loopDetector.record(call)
|
|
27
|
+
onToolCall: call => loopDetector.record(call),
|
|
28
|
+
onLine: input.onLine
|
|
27
29
|
}, input.spawn);
|
|
28
30
|
const tEnd = Date.now();
|
|
29
31
|
const waitMs = tFirstByte === null ? tEnd - tStart : tFirstByte - tStart;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|