pi-subagents 0.50.0 → 0.52.0
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/CHANGELOG.md +103 -0
- package/agents/oracle.md +3 -1
- package/agents/reviewer.md +1 -0
- package/agents/scout.md +2 -2
- package/agents/worker.md +2 -1
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +40 -2
- package/docs/configuration.md +30 -12
- package/docs/extension-api.md +42 -1
- package/docs/models.md +2 -0
- package/docs/observability.md +41 -5
- package/docs/tool-reference.md +38 -39
- package/docs/workflows.md +171 -3
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +6 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +12 -6
- package/skills/pi-subagents/references/execution-controls.md +29 -15
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +4 -2
- package/src/agents/agent-management.ts +124 -351
- package/src/agents/agents.ts +157 -31
- package/src/agents/skills.ts +1 -1
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/preflight.ts +36 -8
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +3 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +166 -88
- package/src/extension/public-execution.ts +31 -2
- package/src/extension/schemas.ts +12 -35
- package/src/extension/tool-description.ts +35 -24
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +2 -2
- package/src/intercom/native-supervisor-channel.ts +32 -10
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/missions/workflow-state.ts +2 -2
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +31 -8
- package/src/runs/background/async-execution.ts +68 -40
- package/src/runs/background/async-job-tracker.ts +20 -4
- package/src/runs/background/async-resume.ts +47 -15
- package/src/runs/background/async-retention.ts +886 -0
- package/src/runs/background/async-status.ts +39 -53
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/completion-dedupe.ts +5 -1
- package/src/runs/background/completion-replay.ts +22 -12
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/fleet-view.ts +68 -19
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/inspect-rpc.ts +443 -0
- package/src/runs/background/notify.ts +31 -5
- package/src/runs/background/result-files.ts +158 -90
- package/src/runs/background/result-watcher.ts +116 -20
- package/src/runs/background/resume-guidance.ts +8 -5
- package/src/runs/background/retained-children.ts +13 -3
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +11 -9
- package/src/runs/background/run-status.ts +27 -18
- package/src/runs/background/scheduled-runs.ts +24 -4
- package/src/runs/background/stale-run-reconciler.ts +6 -3
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +253 -140
- package/src/runs/background/subagent-wait.ts +8 -8
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +21 -4
- package/src/runs/background/wait-subscriptions.ts +81 -2
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +3 -1
- package/src/runs/foreground/subagent-executor.ts +666 -1579
- package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/acceptance.ts +4 -4
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/external-job-bridge.ts +444 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +98 -38
- package/src/runs/shared/orca-progress-tabs.ts +84 -22
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +4 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/session-lease.ts +0 -6
- package/src/runs/shared/subagent-control.ts +26 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +14 -2
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +71 -26
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-bridge.ts +2 -1
- package/src/slash/slash-commands.ts +29 -3
- package/src/tui/fleet.ts +63 -15
- package/src/watchdog/change-signature.ts +1 -1
- package/src/watchdog/lsp-diagnostics.ts +1 -0
- package/src/workflows/chat-progress.ts +2 -2
- package/src/workflows/scripted-workflow.ts +220 -67
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1581
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { isStorageCapacityError } from "./file-system-retry.ts";
|
|
2
|
+
import { writeAtomicJson } from "./atomic-json.ts";
|
|
3
|
+
|
|
4
|
+
type TimerApi = {
|
|
5
|
+
setTimeout(handler: () => void, delayMs: number): unknown;
|
|
6
|
+
clearTimeout(handle: unknown): void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const defaultTimerApi: TimerApi = {
|
|
10
|
+
setTimeout: (handler, delayMs) => setTimeout(handler, delayMs),
|
|
11
|
+
clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CapacityResilientJsonWriterOptions = {
|
|
15
|
+
/** The underlying write operation. It must be atomic for each target path. */
|
|
16
|
+
write?: (filePath: string, payload: object) => void;
|
|
17
|
+
/** Keep retry timers referenced by the event loop (needed by child runners). */
|
|
18
|
+
keepAlive?: boolean;
|
|
19
|
+
retryDelayMs?: number;
|
|
20
|
+
timerApi?: TimerApi;
|
|
21
|
+
onError?: (error: unknown, filePath: string) => void;
|
|
22
|
+
onSuccess?: (filePath: string, payload: object) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type CapacityResilientJsonWriter = {
|
|
26
|
+
write(filePath: string, payload: object, writeOperation?: (filePath: string, payload: object) => void): void;
|
|
27
|
+
pendingCount(): number;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Defers only storage-capacity failures, retaining the newest payload per path.
|
|
33
|
+
* Initial/ordinary writes remain synchronous and preserve their throwing contract;
|
|
34
|
+
* only ENOSPC-like failures enter the guarded retry loop.
|
|
35
|
+
*/
|
|
36
|
+
export function createCapacityResilientJsonWriter(options: CapacityResilientJsonWriterOptions = {}): CapacityResilientJsonWriter {
|
|
37
|
+
const defaultWrite = options.write ?? writeAtomicJson;
|
|
38
|
+
const keepAlive = options.keepAlive ?? false;
|
|
39
|
+
const retryDelayMs = options.retryDelayMs ?? 1_000;
|
|
40
|
+
const timerApi = options.timerApi ?? defaultTimerApi;
|
|
41
|
+
const onError = options.onError ?? ((error, filePath) => console.error(`Failed to persist JSON '${filePath}':`, error));
|
|
42
|
+
const onSuccess = options.onSuccess;
|
|
43
|
+
const reportError = (error: unknown, filePath: string): void => {
|
|
44
|
+
try {
|
|
45
|
+
onError(error, filePath);
|
|
46
|
+
} catch (callbackError) {
|
|
47
|
+
console.error(`Failed to report JSON persistence error for '${filePath}':`, callbackError);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const notifySuccess = (filePath: string, payload: object): void => {
|
|
51
|
+
if (!onSuccess) return;
|
|
52
|
+
try {
|
|
53
|
+
onSuccess(filePath, payload);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
reportError(error, filePath);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const pending = new Map<string, { payload: object; write: (filePath: string, payload: object) => void }>();
|
|
59
|
+
let retryTimer: unknown;
|
|
60
|
+
|
|
61
|
+
const scheduleRetry = (): void => {
|
|
62
|
+
if (retryTimer !== undefined) return;
|
|
63
|
+
retryTimer = timerApi.setTimeout(() => {
|
|
64
|
+
retryTimer = undefined;
|
|
65
|
+
for (const [filePath, entry] of pending) {
|
|
66
|
+
try {
|
|
67
|
+
entry.write(filePath, entry.payload);
|
|
68
|
+
pending.delete(filePath);
|
|
69
|
+
notifySuccess(filePath, entry.payload);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (isStorageCapacityError(error)) continue;
|
|
72
|
+
pending.delete(filePath);
|
|
73
|
+
reportError(error, filePath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (pending.size > 0) scheduleRetry();
|
|
77
|
+
}, retryDelayMs);
|
|
78
|
+
if (!keepAlive && typeof retryTimer === "object" && retryTimer !== null && "unref" in retryTimer && typeof retryTimer.unref === "function") {
|
|
79
|
+
retryTimer.unref();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
write(filePath, payload, writeOperation = defaultWrite): void {
|
|
85
|
+
try {
|
|
86
|
+
writeOperation(filePath, payload);
|
|
87
|
+
pending.delete(filePath);
|
|
88
|
+
notifySuccess(filePath, payload);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!isStorageCapacityError(error)) throw error;
|
|
91
|
+
pending.set(filePath, { payload, write: writeOperation });
|
|
92
|
+
scheduleRetry();
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
pendingCount: () => pending.size,
|
|
96
|
+
dispose: (): void => {
|
|
97
|
+
if (retryTimer !== undefined) timerApi.clearTimeout(retryTimer);
|
|
98
|
+
retryTimer = undefined;
|
|
99
|
+
pending.clear();
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const COMPLETION_OWNER_KEY = Symbol.for("pi-subagents.completion-owner-id");
|
|
4
|
+
|
|
5
|
+
type CompletionOwnerGlobal = typeof globalThis & {
|
|
6
|
+
[COMPLETION_OWNER_KEY]?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** Stable for one parent Pi process across extension reloads. */
|
|
10
|
+
export function currentCompletionOwnerId(): string {
|
|
11
|
+
const runtime = globalThis as CompletionOwnerGlobal;
|
|
12
|
+
runtime[COMPLETION_OWNER_KEY] ??= randomUUID();
|
|
13
|
+
return runtime[COMPLETION_OWNER_KEY];
|
|
14
|
+
}
|
|
@@ -1,8 +1,51 @@
|
|
|
1
1
|
const WAIT_BUFFER = typeof SharedArrayBuffer !== "undefined" ? new SharedArrayBuffer(4) : undefined;
|
|
2
2
|
const WAIT_VIEW = WAIT_BUFFER ? new Int32Array(WAIT_BUFFER) : undefined;
|
|
3
3
|
const RETRYABLE_FILE_SYSTEM_ERROR_CODES = new Set(["EACCES", "EBUSY", "EPERM"]);
|
|
4
|
+
const STORAGE_CAPACITY_ERROR_CODES = new Set(["EDQUOT", "EMFILE", "ENFILE", "ENOSPC"]);
|
|
4
5
|
|
|
5
|
-
export const
|
|
6
|
+
export const FS_RETRY_MAX_TOTAL_MS_ENV = "PI_SUBAGENT_FS_RETRY_MAX_TOTAL_MS";
|
|
7
|
+
|
|
8
|
+
const BASE_FILE_SYSTEM_RETRY_DELAYS_MS = [10, 25, 50, 100, 200, 500, 1000, 2000, 4000] as const;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Clamp the retry ladder to a total sleep budget, preserving its length.
|
|
12
|
+
*
|
|
13
|
+
* waitForFileSystemRetry blocks the calling thread, so the ladder above is also
|
|
14
|
+
* a ceiling on how long a single contended rename can stall that thread: about
|
|
15
|
+
* 7.9s. That is fine for a short-lived CLI. A long-lived host that loads this
|
|
16
|
+
* extension in-process runs the same writers on its event loop, where an 8s
|
|
17
|
+
* stall stops it serving anything at all -- and because Atomics.wait parks
|
|
18
|
+
* rather than spins, it presents as an unresponsive process at 0% CPU.
|
|
19
|
+
*
|
|
20
|
+
* The length is preserved deliberately: run-fanout-budget.ts and
|
|
21
|
+
* workflow-state.ts index this array by attempt number and treat running off
|
|
22
|
+
* the end as "timed out acquiring lock". Shortening it would quietly shrink
|
|
23
|
+
* those attempt budgets too, so only the sleeps shrink here.
|
|
24
|
+
*
|
|
25
|
+
* Unset by default, so behaviour is unchanged unless a host opts in. Opting in
|
|
26
|
+
* trades lock-wait tolerance for responsiveness: entries clamped to 0 return
|
|
27
|
+
* immediately, so contention that would previously have been waited out is
|
|
28
|
+
* surfaced as an error sooner.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveFileSystemRetryDelays(
|
|
31
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
32
|
+
base: readonly number[] = BASE_FILE_SYSTEM_RETRY_DELAYS_MS,
|
|
33
|
+
): readonly number[] {
|
|
34
|
+
const raw = env[FS_RETRY_MAX_TOTAL_MS_ENV];
|
|
35
|
+
if (raw === undefined || raw.trim() === "") return base;
|
|
36
|
+
const budget = Number(raw);
|
|
37
|
+
if (!Number.isInteger(budget) || budget < 0) {
|
|
38
|
+
throw new Error(`${FS_RETRY_MAX_TOTAL_MS_ENV} must be a non-negative integer number of milliseconds.`);
|
|
39
|
+
}
|
|
40
|
+
let spent = 0;
|
|
41
|
+
return base.map((delayMs) => {
|
|
42
|
+
const allowed = Math.max(0, Math.min(delayMs, budget - spent));
|
|
43
|
+
spent += allowed;
|
|
44
|
+
return allowed;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS = resolveFileSystemRetryDelays();
|
|
6
49
|
|
|
7
50
|
export type FileSystemRetryOptions = {
|
|
8
51
|
retryDelaysMs?: readonly number[];
|
|
@@ -32,6 +75,11 @@ export function isRetryableFileSystemError(error: unknown): boolean {
|
|
|
32
75
|
return typeof code === "string" && RETRYABLE_FILE_SYSTEM_ERROR_CODES.has(code);
|
|
33
76
|
}
|
|
34
77
|
|
|
78
|
+
export function isStorageCapacityError(error: unknown): boolean {
|
|
79
|
+
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
80
|
+
return typeof code === "string" && STORAGE_CAPACITY_ERROR_CODES.has(code);
|
|
81
|
+
}
|
|
82
|
+
|
|
35
83
|
export function runFileSystemOperationWithRetry<T>(operation: () => T, options: FileSystemRetryOptions = {}): T {
|
|
36
84
|
const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS;
|
|
37
85
|
const wait = options.wait ?? waitForFileSystemRetry;
|
|
@@ -56,6 +56,48 @@ export function resolveSubagentContext(value: unknown): SubagentExecutionContext
|
|
|
56
56
|
return value === "fork" ? "fork" : "fresh";
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
export interface PreferredForkAvailability {
|
|
60
|
+
getSessionFile(): string | undefined;
|
|
61
|
+
getLeafId?: () => string | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface PreferredForkSnapshot {
|
|
65
|
+
parentSessionFile?: string | null;
|
|
66
|
+
leafId?: string | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface SubagentLaunchContextInput {
|
|
70
|
+
explicitContext?: SubagentExecutionContext;
|
|
71
|
+
agentDefaultContext?: SubagentExecutionContext;
|
|
72
|
+
defaultSubagentContext?: SubagentExecutionContext;
|
|
73
|
+
canUseImplicitFork: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Resolve the actual launch context from explicit, global, and agent preferences. */
|
|
77
|
+
export function resolveSubagentLaunchContext(input: SubagentLaunchContextInput): SubagentExecutionContext {
|
|
78
|
+
if (input.explicitContext !== undefined) return input.explicitContext;
|
|
79
|
+
const preferredContext = input.defaultSubagentContext ?? input.agentDefaultContext ?? "fresh";
|
|
80
|
+
return preferredContext === "fork" && input.canUseImplicitFork ? "fork" : "fresh";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** True when an implicit `defaultContext: fork` can create a real branch now.
|
|
84
|
+
* Explicit `context: "fork"` stays strict and does not use this preference. */
|
|
85
|
+
export function canPreferFork(sessionManager: PreferredForkAvailability): boolean {
|
|
86
|
+
return canPreferForkFromSnapshot({
|
|
87
|
+
parentSessionFile: sessionManager.getSessionFile(),
|
|
88
|
+
leafId: sessionManager.getLeafId?.() ?? null,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function canPreferForkFromSnapshot(input: PreferredForkSnapshot): boolean {
|
|
93
|
+
if (!input.parentSessionFile || !input.leafId) return false;
|
|
94
|
+
try {
|
|
95
|
+
return fs.existsSync(input.parentSessionFile);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
59
101
|
/** Decide whether a resolved child model uses Anthropic's provider or message API, which
|
|
60
102
|
* requires the sanitized fork to disable thinking. Unknown models stay conservative. */
|
|
61
103
|
export function forkedChildRequiresThinkingOff(
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { parseFrontmatter } from "../agents/frontmatter.ts";
|
|
5
3
|
import { getAgentDir, getProjectConfigDir } from "./utils.ts";
|
|
6
4
|
|
|
7
|
-
const PROMPT_REF_PATTERN = /^(package|user|project):([A-Za-z0-9][A-Za-z0-9._-]{0,127})$/;
|
|
8
|
-
const PROMPT_VARIABLE_PATTERN = /\{\{(\w+)\}\}/g;
|
|
9
|
-
|
|
10
|
-
type PromptVariable = string | number | boolean;
|
|
11
|
-
|
|
12
5
|
export function getPromptDirectories(cwd: string) {
|
|
13
6
|
return {
|
|
14
7
|
package: path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "prompts"),
|
|
@@ -16,36 +9,3 @@ export function getPromptDirectories(cwd: string) {
|
|
|
16
9
|
project: path.join(getProjectConfigDir(cwd), "prompts"),
|
|
17
10
|
};
|
|
18
11
|
}
|
|
19
|
-
|
|
20
|
-
function promptVariables(vars: unknown): Record<string, PromptVariable> {
|
|
21
|
-
if (vars === undefined) return {};
|
|
22
|
-
if (!vars || typeof vars !== "object" || Array.isArray(vars)) throw new Error("prompts.render vars must be a plain object.");
|
|
23
|
-
const prototype = Object.getPrototypeOf(vars);
|
|
24
|
-
if (prototype !== null && prototype !== Object.prototype) throw new Error("prompts.render vars must be a plain object.");
|
|
25
|
-
for (const [name, value] of Object.entries(vars)) {
|
|
26
|
-
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
27
|
-
throw new Error(`prompts.render variable '${name}' must be a string, number, or boolean.`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return vars as Record<string, PromptVariable>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function renderWorkflowPrompt(ref: string, vars: unknown, cwd: string): string {
|
|
34
|
-
const match = ref.match(PROMPT_REF_PATTERN);
|
|
35
|
-
if (!match) throw new Error("prompts.render ref must use package:<name>, user:<name>, or project:<name>.");
|
|
36
|
-
const scope = match[1] as keyof ReturnType<typeof getPromptDirectories>;
|
|
37
|
-
const name = match[2]!;
|
|
38
|
-
const filePath = path.join(getPromptDirectories(cwd)[scope], `${name}.md`);
|
|
39
|
-
let content: string;
|
|
40
|
-
try {
|
|
41
|
-
if (!fs.lstatSync(filePath).isFile()) throw new Error("not a regular file");
|
|
42
|
-
content = fs.readFileSync(filePath, "utf-8");
|
|
43
|
-
} catch (error) {
|
|
44
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
45
|
-
throw new Error(`Could not read prompt fragment '${ref}': ${detail}`);
|
|
46
|
-
}
|
|
47
|
-
const variables = promptVariables(vars);
|
|
48
|
-
return parseFrontmatter(content).body.trim().replace(PROMPT_VARIABLE_PATTERN, (placeholder, variable: string) => {
|
|
49
|
-
return Object.hasOwn(variables, variable) ? String(variables[variable]) : placeholder;
|
|
50
|
-
});
|
|
51
|
-
}
|
package/src/shared/settings.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface SequentialStep {
|
|
|
64
64
|
acceptance?: AcceptanceInput;
|
|
65
65
|
agentContract?: AgentContract;
|
|
66
66
|
gateOn?: ChainGateLayer;
|
|
67
|
+
/** Internal workflow child isolation; public workflowScript supplies this on runs.run. */
|
|
68
|
+
worktree?: boolean;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/** Parallel task item within a parallel step */
|
|
@@ -119,24 +121,6 @@ export interface DynamicParallelStep {
|
|
|
119
121
|
gateOn?: ChainGateLayer;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
/** Approval checkpoint: pause before later chain steps until parent approval. */
|
|
123
|
-
export interface CheckpointStep {
|
|
124
|
-
checkpoint: string;
|
|
125
|
-
message?: string;
|
|
126
|
-
phase?: string;
|
|
127
|
-
label?: string;
|
|
128
|
-
agent?: string;
|
|
129
|
-
task?: string;
|
|
130
|
-
as?: string;
|
|
131
|
-
output?: OutputOverrideInput;
|
|
132
|
-
outputMode?: OutputMode;
|
|
133
|
-
reads?: string[] | false;
|
|
134
|
-
progress?: boolean;
|
|
135
|
-
skill?: string | string[] | false;
|
|
136
|
-
skills?: string[] | false;
|
|
137
|
-
model?: string;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
124
|
/** Parallel step: multiple agents running concurrently */
|
|
141
125
|
export interface ParallelStep {
|
|
142
126
|
parallel: ParallelTaskItem[];
|
|
@@ -149,16 +133,12 @@ export interface ParallelStep {
|
|
|
149
133
|
}
|
|
150
134
|
|
|
151
135
|
/** Union type for chain steps */
|
|
152
|
-
export type ChainStep = SequentialStep | ParallelStep | DynamicParallelStep
|
|
136
|
+
export type ChainStep = SequentialStep | ParallelStep | DynamicParallelStep;
|
|
153
137
|
|
|
154
138
|
// =============================================================================
|
|
155
139
|
// Type Guards
|
|
156
140
|
// =============================================================================
|
|
157
141
|
|
|
158
|
-
export function isCheckpointStep(step: ChainStep): step is CheckpointStep {
|
|
159
|
-
return "checkpoint" in step;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
142
|
export function isParallelStep(step: ChainStep): step is ParallelStep {
|
|
163
143
|
return "parallel" in step && Array.isArray((step as ParallelStep).parallel);
|
|
164
144
|
}
|
|
@@ -169,9 +149,6 @@ export function isDynamicParallelStep(step: ChainStep): step is DynamicParallelS
|
|
|
169
149
|
|
|
170
150
|
/** Get all agent names in a step (single for sequential, multiple for parallel) */
|
|
171
151
|
export function getStepAgents(step: ChainStep): string[] {
|
|
172
|
-
if (isCheckpointStep(step)) {
|
|
173
|
-
return [];
|
|
174
|
-
}
|
|
175
152
|
if (isParallelStep(step)) {
|
|
176
153
|
return step.parallel.map((t) => t.agent);
|
|
177
154
|
}
|
|
@@ -239,7 +216,6 @@ export function resolveChainTemplates(
|
|
|
239
216
|
steps: ChainStep[],
|
|
240
217
|
): ResolvedTemplates {
|
|
241
218
|
return steps.map((step, i) => {
|
|
242
|
-
if (isCheckpointStep(step)) return "";
|
|
243
219
|
if (isParallelStep(step)) {
|
|
244
220
|
// Parallel step: resolve each task's template
|
|
245
221
|
return step.parallel.map((task) => {
|
package/src/shared/types.ts
CHANGED
|
@@ -41,7 +41,7 @@ export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed"
|
|
|
41
41
|
|
|
42
42
|
export interface WorkflowGraphNode {
|
|
43
43
|
id: string;
|
|
44
|
-
kind: "step" | "parallel-group" | "dynamic-parallel-group" | "agent"
|
|
44
|
+
kind: "step" | "parallel-group" | "dynamic-parallel-group" | "agent";
|
|
45
45
|
agent?: string;
|
|
46
46
|
phase?: string;
|
|
47
47
|
label: string;
|
|
@@ -61,16 +61,6 @@ export interface WorkflowGraphNode {
|
|
|
61
61
|
structured?: boolean;
|
|
62
62
|
acceptanceStatus?: AcceptanceLedgerStatus;
|
|
63
63
|
error?: string;
|
|
64
|
-
checkpoint?: ChainCheckpointState;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface ChainCheckpointState {
|
|
68
|
-
name: string;
|
|
69
|
-
message?: string;
|
|
70
|
-
status: "pending" | "approved" | "rejected";
|
|
71
|
-
stepIndex: number;
|
|
72
|
-
approvedAt?: number;
|
|
73
|
-
rejectedAt?: number;
|
|
74
64
|
}
|
|
75
65
|
|
|
76
66
|
export interface WorkflowGraphSnapshot {
|
|
@@ -224,6 +214,10 @@ export interface ControlEvent {
|
|
|
224
214
|
currentPath?: string;
|
|
225
215
|
elapsedMs?: number;
|
|
226
216
|
recentFailureSummary?: string;
|
|
217
|
+
workflowKey?: string;
|
|
218
|
+
phase?: string;
|
|
219
|
+
label?: string;
|
|
220
|
+
taskPreview?: string;
|
|
227
221
|
}
|
|
228
222
|
|
|
229
223
|
export type SubagentResultStatus = "completed" | "failed" | "paused" | "stopped" | "detached";
|
|
@@ -282,7 +276,7 @@ export interface ParallelHandoffGroup {
|
|
|
282
276
|
export interface ParallelHandoffManifest {
|
|
283
277
|
version: 1;
|
|
284
278
|
runId: string;
|
|
285
|
-
mode: "parallel" | "chain";
|
|
279
|
+
mode: "single" | "parallel" | "chain";
|
|
286
280
|
source: "foreground" | "async";
|
|
287
281
|
cwd: string;
|
|
288
282
|
createdAt: number;
|
|
@@ -415,6 +409,12 @@ export type ProcessTerminalV1 =
|
|
|
415
409
|
diagnostic?: string;
|
|
416
410
|
});
|
|
417
411
|
|
|
412
|
+
/** Identifies the durable schedule that launched a run, so its completion is attributable. */
|
|
413
|
+
export interface ScheduleOrigin {
|
|
414
|
+
id: string;
|
|
415
|
+
name?: string;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
418
|
export type SteeringActionState = "delivered" | "scheduled" | "pending" | "partial" | "recovered" | "failed";
|
|
419
419
|
export type SteeringTargetState = "scheduled" | "pending" | "routed" | "queued" | "delivered" | "late" | "failed" | "recovered";
|
|
420
420
|
|
|
@@ -508,6 +508,7 @@ export interface SteeringRecoveryDescriptor {
|
|
|
508
508
|
sessionFile?: string;
|
|
509
509
|
cwd: string;
|
|
510
510
|
model?: string;
|
|
511
|
+
modelOverrideFromParent?: boolean;
|
|
511
512
|
fallbackModels?: string[];
|
|
512
513
|
thinking?: string;
|
|
513
514
|
tools?: string[];
|
|
@@ -1057,7 +1058,6 @@ export interface Details {
|
|
|
1057
1058
|
totalSteps?: number; // Total steps in chain
|
|
1058
1059
|
currentStepIndex?: number; // 0-indexed current step (for running chains)
|
|
1059
1060
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
1060
|
-
checkpoint?: ChainCheckpointState;
|
|
1061
1061
|
outputs?: ChainOutputMap;
|
|
1062
1062
|
// Aggregated child usage across all agents in the run
|
|
1063
1063
|
totalChildUsage?: Usage;
|
|
@@ -1087,9 +1087,9 @@ export interface Details {
|
|
|
1087
1087
|
workflow?: {
|
|
1088
1088
|
value?: unknown;
|
|
1089
1089
|
trace: Array<{
|
|
1090
|
-
operation: "run" | "status";
|
|
1090
|
+
operation: "run" | "status" | "steer";
|
|
1091
1091
|
key: string;
|
|
1092
|
-
state: "started" | "completed" | "failed" | "detached" | "stopped" | "reused";
|
|
1092
|
+
state: "started" | "completed" | "failed" | "detached" | "stopped" | "reused" | "queued" | "delivered" | "missed";
|
|
1093
1093
|
agent?: string;
|
|
1094
1094
|
runId?: string;
|
|
1095
1095
|
phase?: string;
|
|
@@ -1266,6 +1266,7 @@ export interface AsyncStartedEvent {
|
|
|
1266
1266
|
sessionRoot?: string;
|
|
1267
1267
|
pid?: number;
|
|
1268
1268
|
sessionId?: string;
|
|
1269
|
+
completionOwnerId?: string;
|
|
1269
1270
|
mode?: SubagentRunMode;
|
|
1270
1271
|
agent?: string;
|
|
1271
1272
|
agents?: string[];
|
|
@@ -1277,7 +1278,6 @@ export interface AsyncStartedEvent {
|
|
|
1277
1278
|
chainStepCount?: number;
|
|
1278
1279
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
1279
1280
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
1280
|
-
checkpoint?: ChainCheckpointState;
|
|
1281
1281
|
launchContractDigest?: string;
|
|
1282
1282
|
launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
|
|
1283
1283
|
runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
|
|
@@ -1298,6 +1298,11 @@ export type AgentRunnerConfig =
|
|
|
1298
1298
|
command: string;
|
|
1299
1299
|
args?: string[];
|
|
1300
1300
|
promptDelivery?: "stdin";
|
|
1301
|
+
}
|
|
1302
|
+
| {
|
|
1303
|
+
type: "external-job";
|
|
1304
|
+
provider: string;
|
|
1305
|
+
options?: Record<string, unknown>;
|
|
1301
1306
|
};
|
|
1302
1307
|
|
|
1303
1308
|
export interface ExternalCliRunnerStatus {
|
|
@@ -1314,6 +1319,35 @@ export interface ExternalCliRunnerStatus {
|
|
|
1314
1319
|
};
|
|
1315
1320
|
}
|
|
1316
1321
|
|
|
1322
|
+
export interface ExternalJobRunnerStatus {
|
|
1323
|
+
type: "external-job";
|
|
1324
|
+
provider: string;
|
|
1325
|
+
options: Record<string, unknown>;
|
|
1326
|
+
capabilities: {
|
|
1327
|
+
stop: false;
|
|
1328
|
+
steer: false;
|
|
1329
|
+
resume: false;
|
|
1330
|
+
structuredOutput: false;
|
|
1331
|
+
toolEvents: false;
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
export interface ExternalJobStatus {
|
|
1336
|
+
provider: string;
|
|
1337
|
+
providerJobId?: string;
|
|
1338
|
+
promptDigest: string;
|
|
1339
|
+
options: Record<string, unknown>;
|
|
1340
|
+
handleUrl?: string;
|
|
1341
|
+
conversationUrl?: string;
|
|
1342
|
+
resultArtifactPath?: string;
|
|
1343
|
+
state: "queued" | "running" | "completed" | "failed" | "stopped" | "blocked";
|
|
1344
|
+
failureCode?: string;
|
|
1345
|
+
failureMessage?: string;
|
|
1346
|
+
blockingJobId?: string;
|
|
1347
|
+
startedAt?: number;
|
|
1348
|
+
updatedAt?: number;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1317
1351
|
export interface ExternalProcessStatus {
|
|
1318
1352
|
pid?: number;
|
|
1319
1353
|
startedAt: number;
|
|
@@ -1328,6 +1362,8 @@ export interface ExternalProcessStatus {
|
|
|
1328
1362
|
export interface AsyncStatus {
|
|
1329
1363
|
lifecycleArtifactVersion?: SubagentLifecycleArtifactVersion;
|
|
1330
1364
|
runId: string;
|
|
1365
|
+
/** Parent Pi process/window that owns local completion delivery. */
|
|
1366
|
+
completionOwnerId?: string;
|
|
1331
1367
|
/** Host tool-call id retained when it differs from the internal run id. */
|
|
1332
1368
|
toolCallId?: string;
|
|
1333
1369
|
sessionId?: string;
|
|
@@ -1361,12 +1397,13 @@ export interface AsyncStatus {
|
|
|
1361
1397
|
usageBudget?: UsageBudgetState;
|
|
1362
1398
|
pid?: number;
|
|
1363
1399
|
cwd?: string;
|
|
1400
|
+
/** Parent-resolved child session root retained for trusted restored transcript lookup. */
|
|
1401
|
+
sessionRoot?: string;
|
|
1364
1402
|
currentStep?: number;
|
|
1365
1403
|
chainStepCount?: number;
|
|
1366
1404
|
pendingAppends?: number;
|
|
1367
1405
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
1368
1406
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
1369
|
-
checkpoint?: ChainCheckpointState;
|
|
1370
1407
|
processTerminal?: ProcessTerminalV1;
|
|
1371
1408
|
runFanoutBudget?: RunFanoutBudgetSnapshot;
|
|
1372
1409
|
runFanoutBudgetDescriptor?: RunFanoutBudgetDescriptor;
|
|
@@ -1378,10 +1415,13 @@ export interface AsyncStatus {
|
|
|
1378
1415
|
workflow?: Details["workflow"];
|
|
1379
1416
|
parentWorkflowRunId?: string;
|
|
1380
1417
|
workflowKey?: string;
|
|
1418
|
+
/** Set when a durable schedule launched this run, so completions can name their origin. */
|
|
1419
|
+
scheduleOrigin?: ScheduleOrigin;
|
|
1381
1420
|
steps?: Array<{
|
|
1382
1421
|
agent: string;
|
|
1383
|
-
runner?: ExternalCliRunnerStatus;
|
|
1422
|
+
runner?: ExternalCliRunnerStatus | ExternalJobRunnerStatus;
|
|
1384
1423
|
externalProcess?: ExternalProcessStatus;
|
|
1424
|
+
externalJob?: ExternalJobStatus;
|
|
1385
1425
|
/** Resolved launch context for this child step. */
|
|
1386
1426
|
context?: "fresh" | "fork";
|
|
1387
1427
|
/** Short caller-facing task/goal shown in fleet surfaces when available. */
|
|
@@ -1396,7 +1436,6 @@ export interface AsyncStatus {
|
|
|
1396
1436
|
parentWorkflowRunId?: string;
|
|
1397
1437
|
outputName?: string;
|
|
1398
1438
|
structured?: boolean;
|
|
1399
|
-
checkpoint?: ChainCheckpointState;
|
|
1400
1439
|
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused" | "stopped" | "rejected";
|
|
1401
1440
|
children?: NestedRunSummary[];
|
|
1402
1441
|
sessionFile?: string;
|
|
@@ -1476,6 +1515,7 @@ export interface AsyncJobState {
|
|
|
1476
1515
|
description?: string;
|
|
1477
1516
|
pid?: number;
|
|
1478
1517
|
sessionId?: string;
|
|
1518
|
+
completionOwnerId?: string;
|
|
1479
1519
|
activityState?: ActivityState;
|
|
1480
1520
|
lastActivityAt?: number;
|
|
1481
1521
|
currentTool?: string;
|
|
@@ -1492,7 +1532,6 @@ export interface AsyncJobState {
|
|
|
1492
1532
|
chainStepCount?: number;
|
|
1493
1533
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
1494
1534
|
steps?: AsyncJobStep[];
|
|
1495
|
-
checkpoint?: ChainCheckpointState;
|
|
1496
1535
|
stepsTotal?: number;
|
|
1497
1536
|
runningSteps?: number;
|
|
1498
1537
|
completedSteps?: number;
|
|
@@ -1570,7 +1609,6 @@ export interface ForegroundResumeRun {
|
|
|
1570
1609
|
/** Originating parent session. Detached exits can outlive the active session. */
|
|
1571
1610
|
sessionId?: string;
|
|
1572
1611
|
updatedAt: number;
|
|
1573
|
-
checkpoint?: ChainCheckpointState;
|
|
1574
1612
|
children: ForegroundResumeChild[];
|
|
1575
1613
|
}
|
|
1576
1614
|
|
|
@@ -1663,6 +1701,8 @@ export interface ActiveAsyncCapacitySnapshot {
|
|
|
1663
1701
|
export interface SubagentState {
|
|
1664
1702
|
baseCwd: string;
|
|
1665
1703
|
currentSessionId: string | null;
|
|
1704
|
+
/** Reload-stable identity for this parent Pi process/window. */
|
|
1705
|
+
completionOwnerId?: string;
|
|
1666
1706
|
/** Runtime-owned artifact resolution inputs used by Fleet transcript targeting. */
|
|
1667
1707
|
artifactDirPreference?: ArtifactDirPreference;
|
|
1668
1708
|
/** Runtime authority snapshot used by optional inspector controls. */
|
|
@@ -1811,6 +1851,8 @@ export interface RunSyncOptions {
|
|
|
1811
1851
|
nestedRoute?: NestedRouteInfo;
|
|
1812
1852
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
1813
1853
|
modelOverride?: string;
|
|
1854
|
+
/** The override came from the running parent session, not configuration. */
|
|
1855
|
+
modelOverrideFromParent?: boolean;
|
|
1814
1856
|
/** LLM intent arbiter for the completion mutation guard (rescues read-only review runs). */
|
|
1815
1857
|
llmIntentArbiter?: import("../runs/shared/llm-intent-arbiter.ts").TaskMutationArbiter;
|
|
1816
1858
|
/** Override the agent's default thinking level for this run */
|
|
@@ -1915,6 +1957,8 @@ export interface MainWindowRendererConfig {
|
|
|
1915
1957
|
|
|
1916
1958
|
export interface ExtensionConfig {
|
|
1917
1959
|
asyncByDefault?: boolean;
|
|
1960
|
+
/** Set the context for launches that omit an explicit context. */
|
|
1961
|
+
defaultSubagentContext?: "fresh" | "fork";
|
|
1918
1962
|
/** Optional shortcut that detaches the active foreground single-subagent run. */
|
|
1919
1963
|
foregroundDetachShortcut?: string;
|
|
1920
1964
|
/** Show the Claude Code-style navigable fleet. Defaults to true. */
|
|
@@ -1925,10 +1969,8 @@ export interface ExtensionConfig {
|
|
|
1925
1969
|
fleetKeybindings?: FleetKeybindingsConfig;
|
|
1926
1970
|
/** Show the under-editor async runs widget. Defaults to true, including when FleetView is enabled. */
|
|
1927
1971
|
asyncWidget?: boolean;
|
|
1928
|
-
/** Tool description variant registered for the parent-facing subagent tool. Defaults to
|
|
1972
|
+
/** Tool description variant registered for the parent-facing subagent tool. Defaults to split metadata. */
|
|
1929
1973
|
toolDescriptionMode?: ToolDescriptionMode;
|
|
1930
|
-
/** Include legacy append-step and checkpoint controls in the registered tool schema. Defaults to false. */
|
|
1931
|
-
legacyChainControls?: boolean;
|
|
1932
1974
|
/** Inline chat rendering for the subagent tool. Defaults to rich. */
|
|
1933
1975
|
inlineToolDisplay?: InlineToolDisplay;
|
|
1934
1976
|
/** Density controls for the main chat subagent call/result renderer. */
|
|
@@ -2067,7 +2109,10 @@ export function resolveTempScopeId(options?: {
|
|
|
2067
2109
|
|
|
2068
2110
|
const MAX_PARALLEL = 8;
|
|
2069
2111
|
export const MAX_CONCURRENCY = 4;
|
|
2070
|
-
|
|
2112
|
+
const configuredTempRoot = process.env.PI_SUBAGENTS_TEMP_ROOT?.trim();
|
|
2113
|
+
export const TEMP_ROOT_DIR = configuredTempRoot
|
|
2114
|
+
? path.resolve(configuredTempRoot)
|
|
2115
|
+
: path.join(os.tmpdir(), `pi-subagents-${resolveTempScopeId()}`);
|
|
2071
2116
|
export const RESULTS_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-results");
|
|
2072
2117
|
export const ASYNC_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-runs");
|
|
2073
2118
|
export const CHAIN_RUNS_DIR = path.join(TEMP_ROOT_DIR, "chain-runs");
|
|
@@ -2090,7 +2135,7 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
|
|
|
2090
2135
|
export const POLL_INTERVAL_MS = 250;
|
|
2091
2136
|
export const MAX_WIDGET_JOBS = 4;
|
|
2092
2137
|
export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
|
|
2093
|
-
export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "guide", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "debug.run", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "dismiss", "
|
|
2138
|
+
export const SUBAGENT_ACTIONS = ["list", "get", "models", "children.list", "guide", "create", "update", "delete", "eject", "disable", "enable", "reset", "mission.create", "mission.list", "mission.show", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "worktree.discard", "refine", "refine.show", "refine.rollback", "inspector.open", "inspector.status", "inspector.close", "project.open", "project.status", "project.close", "status", "debug.run", "grant-spawn-budget", "interrupt", "resume", "steer", "stop", "dismiss", "doctor", "watchdog.status", "watchdog.check", "watchdog.configure", "watchdog.recommend-model", "schedule.create", "schedule.list", "schedule.show", "schedule.history", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"] as const;
|
|
2094
2139
|
|
|
2095
2140
|
export const DEFAULT_FORK_PREAMBLE =
|
|
2096
2141
|
"You are a delegated subagent running from a fork of the parent session. " +
|
package/src/shared/utils.ts
CHANGED
|
@@ -102,6 +102,7 @@ export function getAgentDir(): string {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
const statusCache = new Map<string, { mtime: number; ctime: number; size: number; ino: number; status: AsyncStatus }>();
|
|
105
|
+
const MAX_STATUS_CACHE_ENTRIES = 512;
|
|
105
106
|
|
|
106
107
|
export function pruneStatusCacheForAsyncRoot(asyncDirRoot: string, runIds: Iterable<string>): number {
|
|
107
108
|
const root = path.resolve(asyncDirRoot);
|
|
@@ -163,6 +164,8 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
163
164
|
&& cached.size === stat.size
|
|
164
165
|
&& cached.ino === stat.ino
|
|
165
166
|
) {
|
|
167
|
+
statusCache.delete(statusPath);
|
|
168
|
+
statusCache.set(statusPath, cached);
|
|
166
169
|
return cached.status;
|
|
167
170
|
}
|
|
168
171
|
|
|
@@ -195,6 +198,11 @@ export function readStatus(asyncDir: string): AsyncStatus | null {
|
|
|
195
198
|
ino: stat.ino,
|
|
196
199
|
status,
|
|
197
200
|
});
|
|
201
|
+
while (statusCache.size > MAX_STATUS_CACHE_ENTRIES) {
|
|
202
|
+
const oldest = statusCache.keys().next().value as string | undefined;
|
|
203
|
+
if (oldest === undefined) break;
|
|
204
|
+
statusCache.delete(oldest);
|
|
205
|
+
}
|
|
198
206
|
return status;
|
|
199
207
|
}
|
|
200
208
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type FileWatchPurpose =
|
|
2
|
+
| "result-delivery"
|
|
3
|
+
| "supervisor-channel"
|
|
4
|
+
| "async-job-tracker"
|
|
5
|
+
| "runner-control-inbox"
|
|
6
|
+
| "child-steering-inbox";
|
|
7
|
+
|
|
8
|
+
export function shouldUseNativeFsWatch(_purpose: FileWatchPurpose, platform: NodeJS.Platform = process.platform): boolean {
|
|
9
|
+
return platform !== "darwin";
|
|
10
|
+
}
|