@narumitw/pi-subagents 1.0.0 → 1.0.2

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.
@@ -0,0 +1,64 @@
1
+ import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
3
+ import type { SubagentInspectRuntime } from "./inspect.js";
4
+ import { renderInspectCall, renderInspectResult } from "./inspect-render.js";
5
+ import { SubagentInspectParams } from "./inspect-tool.js";
6
+
7
+ interface InspectExecutionModule {
8
+ executeSubagentInspect: typeof import("./inspect.js").executeSubagentInspect;
9
+ }
10
+
11
+ export interface InspectRegistrationDependencies {
12
+ loadExecution?: () => Promise<InspectExecutionModule>;
13
+ }
14
+
15
+ export function registerSubagentInspect(
16
+ pi: ExtensionAPI,
17
+ runtime: SubagentInspectRuntime,
18
+ dependencies: InspectRegistrationDependencies = {},
19
+ ): void {
20
+ const loadExecution = cachedModuleLoader(
21
+ dependencies.loadExecution ?? (() => import("./inspect.js")),
22
+ );
23
+ let lifecycleGeneration = 0;
24
+ pi.on("session_start", () => {
25
+ lifecycleGeneration += 1;
26
+ });
27
+ pi.on("session_shutdown", () => {
28
+ lifecycleGeneration += 1;
29
+ });
30
+ const definition: ToolDefinition<typeof SubagentInspectParams, Record<string, unknown>> = {
31
+ name: "subagent_inspect",
32
+ label: "Inspect Subagents",
33
+ description:
34
+ "Inspect available subagent definitions, models, retained runs, persisted blocking workflows, runtime status, and diagnostics without changing subagent or workspace state. This tool never starts a child, sends or acknowledges messages, interrupts or closes runs, changes settings, or modifies files.",
35
+ promptSnippet: "Inspect subagent metadata and runtime state without changing it",
36
+ parameters: SubagentInspectParams,
37
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
38
+ const ownerGeneration = lifecycleGeneration;
39
+ throwIfAborted(signal, "Subagent inspection loading was cancelled");
40
+ let executionModule: InspectExecutionModule;
41
+ try {
42
+ executionModule = await loadExecution();
43
+ } catch (error) {
44
+ throwIfAborted(signal, "Subagent inspection loading was cancelled");
45
+ if (ownerGeneration !== lifecycleGeneration) {
46
+ throw new DOMException("Subagent inspection session was replaced", "AbortError");
47
+ }
48
+ throw error;
49
+ }
50
+ throwIfAborted(signal, "Subagent inspection loading was cancelled");
51
+ if (ownerGeneration !== lifecycleGeneration) {
52
+ throw new DOMException("Subagent inspection session was replaced", "AbortError");
53
+ }
54
+ return executionModule.executeSubagentInspect(params, ctx, runtime);
55
+ },
56
+ renderCall(args, theme) {
57
+ return renderInspectCall(args, theme);
58
+ },
59
+ renderResult(result, options, theme, context) {
60
+ return renderInspectResult(result, options, theme, context);
61
+ },
62
+ };
63
+ pi.registerTool(definition);
64
+ }
@@ -0,0 +1,43 @@
1
+ import { StringEnum } from "@earendil-works/pi-ai";
2
+ import { type Static, Type } from "typebox";
3
+
4
+ const INSPECT_ACTIONS = [
5
+ "list_agents",
6
+ "get_agent",
7
+ "list_runs",
8
+ "get_run",
9
+ "list_workflows",
10
+ "get_workflow",
11
+ "list_models",
12
+ "preview_context",
13
+ "status",
14
+ "diagnose",
15
+ ] as const;
16
+
17
+ const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
18
+ default: "user",
19
+ description: "Agent definition scope. Project scopes require a trusted project.",
20
+ });
21
+ const LimitSchema = Type.Number({ minimum: 1, maximum: 100, multipleOf: 1 });
22
+ const ContextModeSchema = Type.Union([
23
+ StringEnum(["none", "all", "summary"] as const),
24
+ Type.Number({ minimum: 1, multipleOf: 1 }),
25
+ ]);
26
+
27
+ export const SubagentInspectParams = Type.Object(
28
+ {
29
+ action: StringEnum(INSPECT_ACTIONS),
30
+ agent: Type.Optional(Type.String({ minLength: 1 })),
31
+ agentId: Type.Optional(Type.String({ minLength: 1 })),
32
+ workflowId: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
33
+ agentScope: Type.Optional(AgentScopeSchema),
34
+ limit: Type.Optional(LimitSchema),
35
+ includeClosed: Type.Optional(Type.Boolean({ default: false })),
36
+ context: Type.Optional(ContextModeSchema),
37
+ contextEntryIds: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
38
+ },
39
+ { additionalProperties: false },
40
+ );
41
+
42
+ export type SubagentInspectParams = Static<typeof SubagentInspectParams>;
43
+ export { INSPECT_ACTIONS };
package/src/inspect.ts CHANGED
@@ -1,11 +1,5 @@
1
1
  import * as path from "node:path";
2
- import { StringEnum } from "@earendil-works/pi-ai";
3
- import {
4
- CONFIG_DIR_NAME,
5
- type ExtensionAPI,
6
- type ExtensionContext,
7
- } from "@earendil-works/pi-coding-agent";
8
- import { type Static, Type } from "typebox";
2
+ import { CONFIG_DIR_NAME, type ExtensionContext } from "@earendil-works/pi-coding-agent";
9
3
  import { discoverAgents } from "./agents/discovery.js";
10
4
  import type {
11
5
  AgentConfig,
@@ -16,7 +10,7 @@ import type {
16
10
  import { projectCapabilityManifest } from "./capabilities.js";
17
11
  import { resolveConsultTools } from "./consult-policy.js";
18
12
  import { buildContextSnapshot, type ContextMode } from "./context.js";
19
- import { renderInspectCall, renderInspectResult } from "./inspect-render.js";
13
+ import { INSPECT_ACTIONS } from "./inspect-tool.js";
20
14
  import { DEFAULT_MAX_CONTEXT_BYTES } from "./limits.js";
21
15
  import { resolvePiInvocation } from "./pi-invocation.js";
22
16
  import type { AgentRunInspectionDetail, AgentRunInspectionSummary } from "./registry.js";
@@ -36,47 +30,10 @@ import type { StatefulSubagentRuntimeStatus } from "./stateful.js";
36
30
  import type { WorkItemLedgerSnapshot } from "./work-item-ledger.js";
37
31
  import { inspectSessionWorkflows } from "./work-item-persistence.js";
38
32
 
39
- const INSPECT_ACTIONS = [
40
- "list_agents",
41
- "get_agent",
42
- "list_runs",
43
- "get_run",
44
- "list_workflows",
45
- "get_workflow",
46
- "list_models",
47
- "preview_context",
48
- "status",
49
- "diagnose",
50
- ] as const;
51
-
52
- const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
53
- default: "user",
54
- description: "Agent definition scope. Project scopes require a trusted project.",
55
- });
56
-
57
- const LimitSchema = Type.Number({ minimum: 1, maximum: 100, multipleOf: 1 });
58
- const ContextModeSchema = Type.Union([
59
- StringEnum(["none", "all", "summary"] as const),
60
- Type.Number({ minimum: 1, multipleOf: 1 }),
61
- ]);
62
33
  const MAX_DETAILS_LIST_BYTES = 40 * 1024;
63
34
 
64
- export const SubagentInspectParams = Type.Object(
65
- {
66
- action: StringEnum(INSPECT_ACTIONS),
67
- agent: Type.Optional(Type.String({ minLength: 1 })),
68
- agentId: Type.Optional(Type.String({ minLength: 1 })),
69
- workflowId: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
70
- agentScope: Type.Optional(AgentScopeSchema),
71
- limit: Type.Optional(LimitSchema),
72
- includeClosed: Type.Optional(Type.Boolean({ default: false })),
73
- context: Type.Optional(ContextModeSchema),
74
- contextEntryIds: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
75
- },
76
- { additionalProperties: false },
77
- );
78
-
79
- export type SubagentInspectParams = Static<typeof SubagentInspectParams>;
35
+ export { registerSubagentInspect } from "./inspect-registration.js";
36
+ export { SubagentInspectParams } from "./inspect-tool.js";
80
37
 
81
38
  export interface SubagentInspectRuntime {
82
39
  getBlockingEnabled(): boolean;
@@ -106,26 +63,6 @@ type ValidatedInspectOperation =
106
63
  | { action: "status" }
107
64
  | { action: "diagnose" };
108
65
 
109
- export function registerSubagentInspect(pi: ExtensionAPI, runtime: SubagentInspectRuntime): void {
110
- pi.registerTool({
111
- name: "subagent_inspect",
112
- label: "Inspect Subagents",
113
- description:
114
- "Inspect available subagent definitions, models, retained runs, persisted blocking workflows, runtime status, and diagnostics without changing subagent or workspace state. This tool never starts a child, sends or acknowledges messages, interrupts or closes runs, changes settings, or modifies files.",
115
- promptSnippet: "Inspect subagent metadata and runtime state without changing it",
116
- parameters: SubagentInspectParams,
117
- async execute(_toolCallId, params, _signal, _onUpdate, ctx): Promise<InspectToolResult> {
118
- return executeSubagentInspect(validateInspectParams(params), ctx, runtime);
119
- },
120
- renderCall(args, theme) {
121
- return renderInspectCall(args, theme);
122
- },
123
- renderResult(result, options, theme, context) {
124
- return renderInspectResult(result, options, theme, context);
125
- },
126
- });
127
- }
128
-
129
66
  export function validateInspectParams(params: unknown): ValidatedInspectOperation {
130
67
  const values = parameterRecord(params);
131
68
  const rawAction = values.action;
@@ -195,11 +132,12 @@ export function validateInspectParams(params: unknown): ValidatedInspectOperatio
195
132
  return { action };
196
133
  }
197
134
 
198
- async function executeSubagentInspect(
199
- operation: ValidatedInspectOperation,
135
+ export async function executeSubagentInspect(
136
+ params: unknown,
200
137
  ctx: ExtensionContext,
201
138
  runtime: SubagentInspectRuntime,
202
139
  ): Promise<InspectToolResult> {
140
+ const operation = validateInspectParams(params);
203
141
  if (operation.action === "list_agents" || operation.action === "get_agent") {
204
142
  assertTrustedScope(operation.agentScope, ctx);
205
143
  const settings = inspectSubagentSettings().settings;
@@ -1,9 +1,9 @@
1
1
  import type { AgentConfig } from "./agents/types.js";
2
2
  import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
3
+ import type { PanelPreset } from "./panel-presets.js";
3
4
  import { type WorkItemDefinition, WorkItemLedger } from "./work-item-ledger.js";
4
5
 
5
- export const PANEL_PRESETS = ["code-review", "research", "security-review", "custom"] as const;
6
- export type PanelPreset = (typeof PANEL_PRESETS)[number];
6
+ export type { PanelPreset } from "./panel-presets.js";
7
7
 
8
8
  export interface PanelReviewerRequest {
9
9
  id: string;
@@ -0,0 +1,3 @@
1
+ export const PANEL_PRESETS = ["code-review", "research", "security-review", "custom"] as const;
2
+
3
+ export type PanelPreset = (typeof PANEL_PRESETS)[number];
package/src/params.ts CHANGED
@@ -3,10 +3,10 @@ import { type Static, Type } from "typebox";
3
3
  import { THINKING_LEVELS } from "./agents/types.js";
4
4
  import { DelegationContractSchema } from "./delegation-contract.js";
5
5
  import { MAX_CONFIGURABLE_PARALLEL_TASKS, MAX_SUBAGENT_TIMEOUT_MS } from "./limits.js";
6
- import { PANEL_PRESETS } from "./panel-planning.js";
6
+ import { PANEL_PRESETS } from "./panel-presets.js";
7
7
  import { SUBAGENT_RESULT_FORMATS } from "./result-contract.js";
8
8
  import { MAX_SUBAGENT_TOOL_CALLS, MAX_SUBAGENT_TURNS } from "./turn-budget.js";
9
- import { VerifiedExecutionContractSchema } from "./verified-execution-contract.js";
9
+ import { VerifiedExecutionContractSchema } from "./verified-execution-schema.js";
10
10
 
11
11
  const TimeoutMs = Type.Number({
12
12
  description:
package/src/pi-args.ts ADDED
@@ -0,0 +1,41 @@
1
+ import type { SubagentThinkingLevel } from "./agents/types.js";
2
+
3
+ export interface PiArgsOptions {
4
+ model?: string;
5
+ thinkingLevel?: SubagentThinkingLevel;
6
+ tools?: string[];
7
+ disableExtensions?: boolean;
8
+ disableSkills?: boolean;
9
+ disablePromptTemplates?: boolean;
10
+ disableContextFiles?: boolean;
11
+ projectTrust?: boolean;
12
+ baseSystemPromptPath?: string;
13
+ appendSystemPromptPaths?: string[];
14
+ /** Existing single append prompt path retained for compatibility. */
15
+ systemPromptPath?: string;
16
+ task: string;
17
+ }
18
+
19
+ export function buildPiArgs(options: PiArgsOptions): string[] {
20
+ const args: string[] = ["--mode", "json", "-p", "--no-session"];
21
+ if (options.model) args.push("--model", options.model);
22
+ if (options.thinkingLevel) args.push("--thinking", options.thinkingLevel);
23
+ if (options.disableExtensions) args.push("--no-extensions");
24
+ if (options.disableSkills) args.push("--no-skills");
25
+ if (options.disablePromptTemplates) args.push("--no-prompt-templates");
26
+ if (options.disableContextFiles) args.push("--no-context-files");
27
+ if (options.projectTrust !== undefined) {
28
+ args.push(options.projectTrust ? "--approve" : "--no-approve");
29
+ }
30
+ if (Array.isArray(options.tools)) {
31
+ if (options.tools.length > 0) args.push("--tools", options.tools.join(","));
32
+ else args.push("--no-tools");
33
+ }
34
+ if (options.baseSystemPromptPath) args.push("--system-prompt", options.baseSystemPromptPath);
35
+ for (const promptPath of options.appendSystemPromptPaths ?? []) {
36
+ args.push("--append-system-prompt", promptPath);
37
+ }
38
+ if (options.systemPromptPath) args.push("--append-system-prompt", options.systemPromptPath);
39
+ args.push(`Task: ${options.task}`);
40
+ return args;
41
+ }
package/src/render.ts CHANGED
@@ -7,16 +7,13 @@ import {
7
7
  type ToolRenderResultOptions,
8
8
  } from "@earendil-works/pi-coding-agent";
9
9
  import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
10
- import type { AgentScope, SubagentThinkingLevel } from "./agents/types.js";
10
+ import type { AgentScope } from "./agents/types.js";
11
11
  import { renderPanelCall, renderPanelResult } from "./panel-render.js";
12
12
  import { hasUsableAggregator, type SubagentParams } from "./params.js";
13
13
  import { expansionHint, formatToolActivity, safeBlock, safeLine } from "./render-common.js";
14
- import {
15
- getResultFinalOutput,
16
- isResultError,
17
- type SingleResult,
18
- type SubagentDetails,
19
- } from "./runner.js";
14
+ import type { SingleResult, SubagentDetails } from "./runner.js";
15
+ import { getResultFinalOutput, isResultError } from "./runner-outcome.js";
16
+ import { formatUsageStats } from "./usage-format.js";
20
17
 
21
18
  const COLLAPSED_ITEM_COUNT = 5;
22
19
 
@@ -29,46 +26,6 @@ function previewAgent(agent: unknown): string {
29
26
  return safeLine(agent, "...", 256);
30
27
  }
31
28
 
32
- export function formatTokens(count: number): string {
33
- if (count < 1000) return count.toString();
34
- if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
35
- if (count < 1000000) return `${Math.round(count / 1000)}k`;
36
- return `${(count / 1000000).toFixed(1)}M`;
37
- }
38
-
39
- export function formatUsageStats(
40
- usage: {
41
- input: number;
42
- output: number;
43
- cacheRead: number;
44
- cacheWrite: number;
45
- cost: number;
46
- contextTokens?: number;
47
- turns?: number;
48
- },
49
- model?: string,
50
- thinkingLevel?: SubagentThinkingLevel,
51
- actualProvider?: string,
52
- actualModel?: string,
53
- ): string {
54
- const parts: string[] = [];
55
- if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
56
- if (usage.input) parts.push(`↑${formatTokens(usage.input)}`);
57
- if (usage.output) parts.push(`↓${formatTokens(usage.output)}`);
58
- if (usage.cacheRead) parts.push(`R${formatTokens(usage.cacheRead)}`);
59
- if (usage.cacheWrite) parts.push(`W${formatTokens(usage.cacheWrite)}`);
60
- if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
61
- if (usage.contextTokens && usage.contextTokens > 0)
62
- parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
63
- const safeProvider = actualProvider ? safeLine(actualProvider, "", 256) : undefined;
64
- const safeModel = actualModel ? safeLine(actualModel, "", 256) : undefined;
65
- const actual =
66
- safeProvider && safeModel ? `${safeProvider}/${safeModel}` : (safeModel ?? safeProvider);
67
- if (actual ?? model) parts.push(actual ?? safeLine(model, "", 256));
68
- if (thinkingLevel) parts.push(`requested-thinking:${safeLine(thinkingLevel, "", 128)}`);
69
- return parts.join(" ");
70
- }
71
-
72
29
  function formatResultUsageStats(result: SingleResult): string {
73
30
  return formatUsageStats(
74
31
  result.usage,
@@ -0,0 +1,31 @@
1
+ import type { SingleResult } from "./runner.js";
2
+ import {
3
+ formatResultFailure as formatBaseResultFailure,
4
+ getResultFinalOutput as getBaseResultFinalOutput,
5
+ isResultError as isBaseResultError,
6
+ } from "./runner-result.js";
7
+
8
+ export function getResultFinalOutput(result: SingleResult): string {
9
+ return getBaseResultFinalOutput(result);
10
+ }
11
+
12
+ export function isResultError(result: SingleResult): boolean {
13
+ return (
14
+ isBaseResultError(result) ||
15
+ result.resultContractInvalid === true ||
16
+ (result.outcome !== undefined &&
17
+ result.outcome.status !== "completed" &&
18
+ result.outcome.status !== "partial")
19
+ );
20
+ }
21
+
22
+ export function formatResultFailure(result: SingleResult): string {
23
+ const contractError = result.resultContractInvalid
24
+ ? `Subagent returned an invalid ${result.resultFormat ?? "structured"} result contract`
25
+ : result.outcome && !["completed", "partial"].includes(result.outcome.status)
26
+ ? `Subagent outcome ${result.outcome.status}${result.outcome.reasonCode ? ` (${result.outcome.reasonCode})` : ""}; recovery: ${result.outcome.recoveryActions.join(", ") || "none"}`
27
+ : undefined;
28
+ return contractError
29
+ ? formatBaseResultFailure({ ...result, errorMessage: contractError })
30
+ : formatBaseResultFailure(result);
31
+ }
package/src/runner.ts CHANGED
@@ -31,6 +31,7 @@ import type { PanelSynthesis } from "./panel-contract.js";
31
31
  import type { PanelEvidenceArtifact } from "./panel-evidence.js";
32
32
  import type { PanelFailure } from "./panel-failure.js";
33
33
  import type { PanelPhaseBudgets, PanelPreset } from "./panel-planning.js";
34
+ import { buildPiArgs } from "./pi-args.js";
34
35
  import { resolvePiInvocation } from "./pi-invocation.js";
35
36
  import { JsonLineDecoder } from "./protocol.js";
36
37
  import {
@@ -38,12 +39,8 @@ import {
38
39
  parseAnyStructuredSubagentResult,
39
40
  type SubagentResultFormat,
40
41
  } from "./result-contract.js";
41
- import {
42
- formatResultFailure as formatBaseResultFailure,
43
- getResultFinalOutput as getBaseResultFinalOutput,
44
- getFinalOutput,
45
- isResultError as isBaseResultError,
46
- } from "./runner-result.js";
42
+ import { formatResultFailure, getResultFinalOutput, isResultError } from "./runner-outcome.js";
43
+ import { getFinalOutput } from "./runner-result.js";
47
44
  import {
48
45
  addUsageValue,
49
46
  mergeUsageStats,
@@ -68,6 +65,13 @@ import type { WorkItemLedgerSnapshot } from "./work-item-ledger.js";
68
65
 
69
66
  export const KILL_GRACE_MS = 5000;
70
67
 
68
+ export type { PiArgsOptions } from "./pi-args.js";
69
+ export { buildPiArgs } from "./pi-args.js";
70
+ export {
71
+ formatResultFailure,
72
+ getResultFinalOutput,
73
+ isResultError,
74
+ } from "./runner-outcome.js";
71
75
  export type { UsageStats } from "./runner-usage.js";
72
76
  export type RecentActivityItem =
73
77
  | { type: "text"; text: string }
@@ -154,31 +158,6 @@ export interface SubagentDetails {
154
158
  isError?: boolean;
155
159
  }
156
160
 
157
- export function getResultFinalOutput(result: SingleResult): string {
158
- return getBaseResultFinalOutput(result);
159
- }
160
-
161
- export function isResultError(result: SingleResult): boolean {
162
- return (
163
- isBaseResultError(result) ||
164
- result.resultContractInvalid === true ||
165
- (result.outcome !== undefined &&
166
- result.outcome.status !== "completed" &&
167
- result.outcome.status !== "partial")
168
- );
169
- }
170
-
171
- export function formatResultFailure(result: SingleResult): string {
172
- const contractError = result.resultContractInvalid
173
- ? `Subagent returned an invalid ${result.resultFormat ?? "structured"} result contract`
174
- : result.outcome && !["completed", "partial"].includes(result.outcome.status)
175
- ? `Subagent outcome ${result.outcome.status}${result.outcome.reasonCode ? ` (${result.outcome.reasonCode})` : ""}; recovery: ${result.outcome.recoveryActions.join(", ") || "none"}`
176
- : undefined;
177
- return contractError
178
- ? formatBaseResultFailure({ ...result, errorMessage: contractError })
179
- : formatBaseResultFailure(result);
180
- }
181
-
182
161
  function boundMessageText(
183
162
  message: Message,
184
163
  maxBytes: number,
@@ -359,46 +338,6 @@ async function writePromptToTempFile(
359
338
  return { dir: tmpDir, filePath };
360
339
  }
361
340
 
362
- export interface PiArgsOptions {
363
- model?: string;
364
- thinkingLevel?: SubagentThinkingLevel;
365
- tools?: string[];
366
- disableExtensions?: boolean;
367
- disableSkills?: boolean;
368
- disablePromptTemplates?: boolean;
369
- disableContextFiles?: boolean;
370
- projectTrust?: boolean;
371
- baseSystemPromptPath?: string;
372
- appendSystemPromptPaths?: string[];
373
- /** Existing single append prompt path retained for compatibility. */
374
- systemPromptPath?: string;
375
- task: string;
376
- }
377
-
378
- export function buildPiArgs(options: PiArgsOptions): string[] {
379
- const args: string[] = ["--mode", "json", "-p", "--no-session"];
380
- if (options.model) args.push("--model", options.model);
381
- if (options.thinkingLevel) args.push("--thinking", options.thinkingLevel);
382
- if (options.disableExtensions) args.push("--no-extensions");
383
- if (options.disableSkills) args.push("--no-skills");
384
- if (options.disablePromptTemplates) args.push("--no-prompt-templates");
385
- if (options.disableContextFiles) args.push("--no-context-files");
386
- if (options.projectTrust !== undefined) {
387
- args.push(options.projectTrust ? "--approve" : "--no-approve");
388
- }
389
- if (Array.isArray(options.tools)) {
390
- if (options.tools.length > 0) args.push("--tools", options.tools.join(","));
391
- else args.push("--no-tools");
392
- }
393
- if (options.baseSystemPromptPath) args.push("--system-prompt", options.baseSystemPromptPath);
394
- for (const promptPath of options.appendSystemPromptPaths ?? []) {
395
- args.push("--append-system-prompt", promptPath);
396
- }
397
- if (options.systemPromptPath) args.push("--append-system-prompt", options.systemPromptPath);
398
- args.push(`Task: ${options.task}`);
399
- return args;
400
- }
401
-
402
341
  function signalProcess(proc: ReturnType<typeof spawn>, signal: NodeJS.Signals): void {
403
342
  if (process.platform !== "win32" && proc.pid) {
404
343
  try {
@@ -857,16 +796,16 @@ export async function runSingleAgent(
857
796
  onExceeded: stopForBudget,
858
797
  });
859
798
 
860
- timeout = setTimeout(() => {
861
- stopForBudget({
862
- reason: launchPolicy?.workTimeoutReason ?? "work_timeout",
863
- limit: launchPolicy?.workTimeoutReportLimit ?? timeoutMs,
864
- });
865
- }, timeoutMs);
866
- timeout.unref();
867
-
868
799
  proc.once("spawn", () => {
869
800
  currentResult.processStarted = true;
801
+ if (settled || budgetStop || wasAborted) return;
802
+ timeout = setTimeout(() => {
803
+ stopForBudget({
804
+ reason: launchPolicy?.workTimeoutReason ?? "work_timeout",
805
+ limit: launchPolicy?.workTimeoutReportLimit ?? timeoutMs,
806
+ });
807
+ }, timeoutMs);
808
+ timeout.unref();
870
809
  });
871
810
  proc.stdout?.on("data", (data) => decoder.push(data));
872
811
  proc.stderr?.on("data", (data) => {
package/src/settings.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
+ import { createRequire } from "node:module";
3
4
  import * as path from "node:path";
4
5
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
- import lockfile from "proper-lockfile";
6
6
  import type {
7
7
  AgentConfig,
8
8
  CompletionDelivery,
@@ -76,6 +76,8 @@ export {
76
76
 
77
77
  const SETTINGS_FILE = "pi-subagents.json";
78
78
  const LEGACY_SETTINGS_FILE = "pi-subagents-config.json";
79
+ const require = createRequire(import.meta.url);
80
+
79
81
  const SETTINGS_LOCK_FS_ADAPTER = {
80
82
  mkdir: fs.mkdir,
81
83
  mkdirSync: fs.mkdirSync,
@@ -511,6 +513,7 @@ function writeSettingsObjectUnlocked(settings: object, replaceCanonical?: boolea
511
513
  }
512
514
 
513
515
  function withSettingsMutationLock<T>(mutate: () => T): T {
516
+ const lockfile = require("proper-lockfile") as typeof import("proper-lockfile");
514
517
  const agentDir = getAgentDir();
515
518
  fs.mkdirSync(agentDir, { recursive: true });
516
519
  const configPath = path.join(agentDir, SETTINGS_FILE);