@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.
@@ -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
- this._timings.push({
206
- label: phase.name,
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;
@@ -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
- { label: 'worker:apis', prompt: appendNoThink(promptHeader + RESEARCH_APIS_PROMPT(refined)) },
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
  }
@@ -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
 
@@ -0,0 +1,2 @@
1
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+ export default function (pi: ExtensionAPI): void;
@@ -0,0 +1,4 @@
1
+ import { registerPiWorkerDocs } from './pi-worker-docs.js';
2
+ export default function (pi) {
3
+ registerPiWorkerDocs(pi);
4
+ }
@@ -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 baseArgs = [...CHILD_BASE_ARGS, '--mode', 'json', '--tools', tools];
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.2",
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",