@shanepadgett/tau-agent 0.33.0 → 0.34.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/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +130 -93
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
package/schemas/tau.schema.json
CHANGED
|
@@ -12,6 +12,26 @@
|
|
|
12
12
|
"extensions": {
|
|
13
13
|
"type": "object",
|
|
14
14
|
"properties": {
|
|
15
|
+
"checkpoint": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"required": [
|
|
18
|
+
"checkpointTokenLimit"
|
|
19
|
+
],
|
|
20
|
+
"properties": {
|
|
21
|
+
"showToolRows": {
|
|
22
|
+
"type": "boolean",
|
|
23
|
+
"default": false,
|
|
24
|
+
"description": "Show checkpoint and injected-file rows in the TUI for debugging."
|
|
25
|
+
},
|
|
26
|
+
"checkpointTokenLimit": {
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"minimum": 1,
|
|
29
|
+
"default": 150000,
|
|
30
|
+
"description": "Context-token ceiling before a checkpoint is required."
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"additionalProperties": false
|
|
34
|
+
},
|
|
15
35
|
"context": {
|
|
16
36
|
"type": "object",
|
|
17
37
|
"properties": {
|
|
@@ -262,39 +282,6 @@
|
|
|
262
282
|
}
|
|
263
283
|
},
|
|
264
284
|
"additionalProperties": false
|
|
265
|
-
},
|
|
266
|
-
"workingMemory": {
|
|
267
|
-
"type": "object",
|
|
268
|
-
"properties": {
|
|
269
|
-
"enabled": {
|
|
270
|
-
"type": "boolean",
|
|
271
|
-
"default": true,
|
|
272
|
-
"description": "Enable working-memory checkpoints."
|
|
273
|
-
},
|
|
274
|
-
"nudgeEveryTokens": {
|
|
275
|
-
"type": "integer",
|
|
276
|
-
"default": 40000,
|
|
277
|
-
"minimum": 1,
|
|
278
|
-
"description": "Active-context token interval between advisory working-memory reminders."
|
|
279
|
-
},
|
|
280
|
-
"nudgeInstructions": {
|
|
281
|
-
"type": "array",
|
|
282
|
-
"items": {
|
|
283
|
-
"type": "string",
|
|
284
|
-
"minLength": 1,
|
|
285
|
-
"maxLength": 2000
|
|
286
|
-
},
|
|
287
|
-
"default": [
|
|
288
|
-
"Reassess working memory. Continue coherent exploration when its evidence remains useful; otherwise prune known dead ends, obsolete outputs, and other context with no expected value.",
|
|
289
|
-
"Context is materially larger. Prune stale or bulky irrelevant evidence when safe, but keep active working evidence that would otherwise need to be reread.",
|
|
290
|
-
"Strongly reassess before more broad work. Remove accumulated waste and carry useful information at cheapest sufficient fidelity without scrubbing the active working set."
|
|
291
|
-
],
|
|
292
|
-
"minItems": 1,
|
|
293
|
-
"maxItems": 5,
|
|
294
|
-
"description": "Ordered advisory working-memory instructions. Later reminders repeat final instruction."
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
"additionalProperties": false
|
|
298
285
|
}
|
|
299
286
|
},
|
|
300
287
|
"additionalProperties": true,
|
|
@@ -63,6 +63,7 @@ export class BoundedTextResultBuilder {
|
|
|
63
63
|
this.strategy = strategy;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// fallow-ignore-next-line unused-class-member -- head/tail strategy API; callers use completeBlocks today
|
|
66
67
|
async append(text: string): Promise<void> {
|
|
67
68
|
if (this.strategy === "completeBlocks") throw new Error("Complete-block results require appendBlock");
|
|
68
69
|
this.requireOpen();
|
package/shared/events.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { ModelEffort } from "./model-effort.ts";
|
|
3
|
-
import type { OutlineInjectionRequest, OutlineInjectionResponse } from "./outline-injection.js";
|
|
4
3
|
import type { ToolRowVisualState } from "./tool-row-state.js";
|
|
4
|
+
import type { FileInjectionRequest, PreparedFileInjection } from "../src/file-injection/index.ts";
|
|
5
5
|
|
|
6
6
|
export type TauAgentEvents = {
|
|
7
7
|
"tau:agent.blocked": {
|
|
@@ -31,16 +31,14 @@ export type TauAgentEvents = {
|
|
|
31
31
|
snapshotRanges?: Array<{ startLine: number; endLine: number }>;
|
|
32
32
|
}>;
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
cwd: string;
|
|
38
|
-
batchId: string;
|
|
39
|
-
files: Array<{ path: string }>;
|
|
34
|
+
/** @internal Runtime-scoped discovery for deferred tool groups. */
|
|
35
|
+
"tau:deferred-tool-group.request": {
|
|
36
|
+
accept(group: { id: string; description: string; toolNames: readonly string[] }): void;
|
|
40
37
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
/** @internal Runtime-scoped request for Explore-owned file preparation. */
|
|
39
|
+
"tau:file-injection.prepare": {
|
|
40
|
+
request: FileInjectionRequest;
|
|
41
|
+
accept(preparation: Promise<PreparedFileInjection[]>): void;
|
|
44
42
|
};
|
|
45
43
|
"tau:footer-item": {
|
|
46
44
|
id: string;
|
|
@@ -105,6 +103,25 @@ export function onTauEvent<Name extends keyof TauAgentEvents>(
|
|
|
105
103
|
owner: string,
|
|
106
104
|
name: Name,
|
|
107
105
|
handler: TauEventHandler<Name>,
|
|
106
|
+
): () => void {
|
|
107
|
+
return subscribeToTauEvent(pi, owner, name, handler, false);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function onTauEventImmediately<Name extends keyof TauAgentEvents>(
|
|
111
|
+
pi: TauEventAPI,
|
|
112
|
+
owner: string,
|
|
113
|
+
name: Name,
|
|
114
|
+
handler: TauEventHandler<Name>,
|
|
115
|
+
): () => void {
|
|
116
|
+
return subscribeToTauEvent(pi, owner, name, handler, true);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function subscribeToTauEvent<Name extends keyof TauAgentEvents>(
|
|
120
|
+
pi: TauEventAPI,
|
|
121
|
+
owner: string,
|
|
122
|
+
name: Name,
|
|
123
|
+
handler: TauEventHandler<Name>,
|
|
124
|
+
attachImmediately: boolean,
|
|
108
125
|
): () => void {
|
|
109
126
|
if (owner.length === 0) throw new Error("Tau event owner is required.");
|
|
110
127
|
|
|
@@ -135,6 +152,7 @@ export function onTauEvent<Name extends keyof TauAgentEvents>(
|
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
subscriptions.set(name, subscription);
|
|
155
|
+
if (attachImmediately) attach();
|
|
138
156
|
pi.on("session_start", attach);
|
|
139
157
|
pi.on("session_shutdown", detach);
|
|
140
158
|
return subscription.stop;
|
|
@@ -32,6 +32,61 @@ export interface IsolatedSessionResource {
|
|
|
32
32
|
dispose(): Promise<void>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
type ModelAuth = Awaited<ReturnType<ExtensionContext["modelRegistry"]["getApiKeyAndHeaders"]>>;
|
|
36
|
+
|
|
37
|
+
async function tryPreferredModel(
|
|
38
|
+
ctx: ExtensionContext,
|
|
39
|
+
preferredModel: string,
|
|
40
|
+
onWarning?: (warning: string) => void,
|
|
41
|
+
): Promise<{ model: SelectedModel; auth: ModelAuth } | undefined> {
|
|
42
|
+
const separator = preferredModel.indexOf("/");
|
|
43
|
+
const configured = ctx.modelRegistry.find(preferredModel.slice(0, separator), preferredModel.slice(separator + 1));
|
|
44
|
+
if (!configured) {
|
|
45
|
+
onWarning?.(`model ${preferredModel} is unavailable; using parent model`);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(configured);
|
|
49
|
+
if (!auth.ok) {
|
|
50
|
+
onWarning?.(`model ${preferredModel} is unavailable: ${auth.error}; using parent model`);
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return { model: configured, auth };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function thinkingSupported(
|
|
57
|
+
model: SelectedModel | undefined,
|
|
58
|
+
preferred: IsolatedSessionThinkingLevel,
|
|
59
|
+
): preferred is IsolatedSessionThinkingLevel {
|
|
60
|
+
const mapped = model?.thinkingLevelMap?.[preferred];
|
|
61
|
+
if (!model?.reasoning || mapped === null) return false;
|
|
62
|
+
if ((preferred === "xhigh" || preferred === "max") && mapped === undefined) return false;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveThinkingLevel(
|
|
67
|
+
model: SelectedModel | undefined,
|
|
68
|
+
parentThinkingLevel: IsolatedSessionThinkingLevel,
|
|
69
|
+
preferredThinkingLevel: IsolatedSessionThinkingLevel | undefined,
|
|
70
|
+
allowPreferred: boolean,
|
|
71
|
+
onWarning?: (warning: string) => void,
|
|
72
|
+
): IsolatedSessionThinkingLevel {
|
|
73
|
+
if (!preferredThinkingLevel || !allowPreferred) return parentThinkingLevel;
|
|
74
|
+
if (thinkingSupported(model, preferredThinkingLevel)) return preferredThinkingLevel;
|
|
75
|
+
onWarning?.(`thinking ${preferredThinkingLevel} is unavailable for the selected model; using parent thinking`);
|
|
76
|
+
return parentThinkingLevel;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function runtimeApiKeyFor(
|
|
80
|
+
ctx: ExtensionContext,
|
|
81
|
+
model: SelectedModel,
|
|
82
|
+
provider: SelectedProvider,
|
|
83
|
+
auth: ModelAuth,
|
|
84
|
+
): string | undefined {
|
|
85
|
+
if (!auth.ok) return undefined;
|
|
86
|
+
if (!(auth.apiKey && provider.auth.apiKey && !ctx.modelRegistry.isUsingOAuth(model))) return undefined;
|
|
87
|
+
return auth.apiKey;
|
|
88
|
+
}
|
|
89
|
+
|
|
35
90
|
export async function resolveIsolatedSessionModel(options: {
|
|
36
91
|
label: string;
|
|
37
92
|
preferredModel: string | undefined;
|
|
@@ -44,37 +99,25 @@ export async function resolveIsolatedSessionModel(options: {
|
|
|
44
99
|
}): Promise<Pick<IsolatedSessionInputs, "model" | "provider" | "runtimeApiKey" | "thinkingLevel">> {
|
|
45
100
|
const { ctx, signal, onWarning } = options;
|
|
46
101
|
let model = ctx.model;
|
|
47
|
-
let
|
|
102
|
+
let selectedAuth: ModelAuth | undefined;
|
|
48
103
|
let preferredModelSelected = false;
|
|
49
|
-
|
|
104
|
+
|
|
50
105
|
if (options.preferredModel) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!configured) onWarning?.(`model ${options.preferredModel} is unavailable; using parent model`);
|
|
57
|
-
else {
|
|
58
|
-
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(configured);
|
|
59
|
-
if (!auth.ok) onWarning?.(`model ${options.preferredModel} is unavailable: ${auth.error}; using parent model`);
|
|
60
|
-
else {
|
|
61
|
-
model = configured;
|
|
62
|
-
selectedAuth = auth;
|
|
63
|
-
preferredModelSelected = true;
|
|
64
|
-
}
|
|
106
|
+
const preferred = await tryPreferredModel(ctx, options.preferredModel, onWarning);
|
|
107
|
+
if (preferred) {
|
|
108
|
+
model = preferred.model;
|
|
109
|
+
selectedAuth = preferred.auth;
|
|
110
|
+
preferredModelSelected = true;
|
|
65
111
|
}
|
|
66
112
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
onWarning?.(`thinking ${preferred} is unavailable for the selected model; using parent thinking`);
|
|
76
|
-
else thinkingLevel = preferred;
|
|
77
|
-
}
|
|
113
|
+
|
|
114
|
+
const thinkingLevel = resolveThinkingLevel(
|
|
115
|
+
model,
|
|
116
|
+
options.parentThinkingLevel,
|
|
117
|
+
options.preferredThinkingLevel,
|
|
118
|
+
preferredModelSelected || options.usePreferredThinkingAfterModelFallback,
|
|
119
|
+
onWarning,
|
|
120
|
+
);
|
|
78
121
|
if (!model) throw new Error(`${options.label} startup failed: parent has no model`);
|
|
79
122
|
const auth = selectedAuth ?? (await ctx.modelRegistry.getApiKeyAndHeaders(model));
|
|
80
123
|
if (!auth.ok) throw new Error(`${options.label} startup failed: ${auth.error}`);
|
|
@@ -84,8 +127,7 @@ export async function resolveIsolatedSessionModel(options: {
|
|
|
84
127
|
return {
|
|
85
128
|
model,
|
|
86
129
|
provider,
|
|
87
|
-
runtimeApiKey:
|
|
88
|
-
auth.apiKey && provider.auth.apiKey && !ctx.modelRegistry.isUsingOAuth(model) ? auth.apiKey : undefined,
|
|
130
|
+
runtimeApiKey: runtimeApiKeyFor(ctx, model, provider, auth),
|
|
89
131
|
thinkingLevel,
|
|
90
132
|
};
|
|
91
133
|
}
|
package/shared/model-effort.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { resolveCandidates } from "./model-fallback/index.ts";
|
|
4
4
|
import type { ModelCandidate } from "./model-fallback/types.ts";
|
|
5
5
|
|
|
6
|
-
export type ModelEffort = "
|
|
6
|
+
export type ModelEffort = "quick" | "standard" | "deep";
|
|
7
7
|
|
|
8
8
|
interface ModelPreference {
|
|
9
9
|
model: string;
|
|
@@ -25,42 +25,32 @@ export interface EffortProviderCandidates {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const MODEL_PREFERENCES: Record<ModelEffort, readonly ProviderPreference[]> = {
|
|
28
|
-
|
|
28
|
+
quick: [
|
|
29
29
|
{
|
|
30
30
|
provider: "openai-codex",
|
|
31
|
-
models: [
|
|
32
|
-
{ model: "gpt-5.6-luna", reasoning: "high" },
|
|
33
|
-
{ model: "gpt-5.5", reasoning: "low" },
|
|
34
|
-
],
|
|
31
|
+
models: [{ model: "gpt-5.6-luna", reasoning: "medium" }],
|
|
35
32
|
},
|
|
36
33
|
{ provider: "xai", models: [{ model: "grok-4.5", reasoning: "medium" }] },
|
|
37
34
|
{ provider: "anthropic", models: [{ model: "claude-haiku-4-5", reasoning: "high" }] },
|
|
38
35
|
],
|
|
39
|
-
|
|
36
|
+
standard: [
|
|
40
37
|
{
|
|
41
38
|
provider: "openai-codex",
|
|
42
|
-
models: [
|
|
43
|
-
{ model: "gpt-5.6-terra", reasoning: "high" },
|
|
44
|
-
{ model: "gpt-5.5", reasoning: "medium" },
|
|
45
|
-
],
|
|
39
|
+
models: [{ model: "gpt-5.6-luna", reasoning: "max" }],
|
|
46
40
|
},
|
|
47
41
|
{ provider: "xai", models: [{ model: "grok-4.5", reasoning: "high" }] },
|
|
48
42
|
{ provider: "anthropic", models: [{ model: "claude-sonnet-5", reasoning: "high" }] },
|
|
49
43
|
],
|
|
50
|
-
|
|
44
|
+
deep: [
|
|
51
45
|
{
|
|
52
46
|
provider: "openai-codex",
|
|
53
|
-
models: [
|
|
54
|
-
{ model: "gpt-5.6-sol", reasoning: "high" },
|
|
55
|
-
{ model: "gpt-5.5", reasoning: "high" },
|
|
56
|
-
],
|
|
47
|
+
models: [{ model: "gpt-5.6-sol", reasoning: "high" }],
|
|
57
48
|
},
|
|
58
|
-
{ provider: "xai", models: [{ model: "grok-4.5", reasoning: "high" }] },
|
|
59
49
|
{
|
|
60
50
|
provider: "anthropic",
|
|
61
51
|
models: [
|
|
62
52
|
{ model: "claude-opus-5", reasoning: "high" },
|
|
63
|
-
{ model: "claude-
|
|
53
|
+
{ model: "claude-fable-5", reasoning: "low" },
|
|
64
54
|
],
|
|
65
55
|
},
|
|
66
56
|
],
|
|
@@ -72,7 +62,7 @@ export function effortForSelection(
|
|
|
72
62
|
reasoning: string | undefined,
|
|
73
63
|
): ModelEffort | undefined {
|
|
74
64
|
if (!provider || !model || !reasoning) return undefined;
|
|
75
|
-
for (const effort of ["
|
|
65
|
+
for (const effort of ["deep", "standard", "quick"] as const) {
|
|
76
66
|
const preference = MODEL_PREFERENCES[effort].find((item) => item.provider === provider);
|
|
77
67
|
if (preference?.models.some((item) => item.model === model && item.reasoning === reasoning)) return effort;
|
|
78
68
|
}
|
|
@@ -98,6 +98,20 @@ export async function generateToolValidated<T>(
|
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
async function noteCandidateFailure(
|
|
102
|
+
ctx: GenerationContext,
|
|
103
|
+
options: ModelFallbackOptions | undefined,
|
|
104
|
+
label: string,
|
|
105
|
+
message: string,
|
|
106
|
+
hasMore: boolean,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
if (!hasMore) return;
|
|
109
|
+
await options?.onStatus?.(`Model failed (${label}); trying next model`);
|
|
110
|
+
if (options?.notifyOnFallback ?? false) {
|
|
111
|
+
ctx.ui.notify(`Model failed (${label}): ${message}\nTrying next model.`, "info");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
async function withModelFallback<T>(
|
|
102
116
|
ctx: GenerationContext,
|
|
103
117
|
candidates: readonly ModelCandidate[],
|
|
@@ -106,13 +120,11 @@ async function withModelFallback<T>(
|
|
|
106
120
|
): Promise<T> {
|
|
107
121
|
const failures: string[] = [];
|
|
108
122
|
const statusKey = options?.statusKey;
|
|
109
|
-
const notifyOnFallback = options?.notifyOnFallback ?? false;
|
|
110
123
|
|
|
111
124
|
for (const [index, candidate] of candidates.entries()) {
|
|
112
125
|
const label = `${candidate.model.provider}/${candidate.model.id}`;
|
|
113
126
|
if (statusKey) ctx.ui.setStatus(statusKey, `generating (${label})`);
|
|
114
127
|
await options?.onStatus?.(`Generating with ${label}`);
|
|
115
|
-
|
|
116
128
|
try {
|
|
117
129
|
return await request(candidate);
|
|
118
130
|
} catch (error) {
|
|
@@ -120,16 +132,42 @@ async function withModelFallback<T>(
|
|
|
120
132
|
if (shouldCooldownProvider(error)) await markProviderUnavailable(candidate.model.provider);
|
|
121
133
|
const message = errorText(error);
|
|
122
134
|
failures.push(`- ${label}: ${message}`);
|
|
123
|
-
|
|
124
|
-
if (index < candidates.length - 1 && notifyOnFallback) {
|
|
125
|
-
ctx.ui.notify(`Model failed (${label}): ${message}\nTrying next model.`, "info");
|
|
126
|
-
}
|
|
135
|
+
await noteCandidateFailure(ctx, options, label, message, index < candidates.length - 1);
|
|
127
136
|
}
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
throw new Error(["Model generation failed for all candidates:", ...failures].join("\n"));
|
|
131
140
|
}
|
|
132
141
|
|
|
142
|
+
function throwUnlessRetryableStopError(response: AssistantMessage, attempt: number, maxAttempts: number): void {
|
|
143
|
+
if (response.stopReason !== "error") return;
|
|
144
|
+
const error = new Error(response.errorMessage || "model returned an error");
|
|
145
|
+
if (attempt < maxAttempts && !shouldCooldownProvider(error)) return;
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function pushCorrection(
|
|
150
|
+
messages: Message[],
|
|
151
|
+
error: Error,
|
|
152
|
+
output: string,
|
|
153
|
+
correctionPrompt: (error: Error, output: string) => string,
|
|
154
|
+
): void {
|
|
155
|
+
messages.push({
|
|
156
|
+
role: "user",
|
|
157
|
+
content: [{ type: "text", text: correctionPrompt(error, truncAt(output, 4_000)) }],
|
|
158
|
+
timestamp: Date.now(),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function shouldRetryValidation(
|
|
163
|
+
error: unknown,
|
|
164
|
+
attempt: number,
|
|
165
|
+
maxAttempts: number,
|
|
166
|
+
correctionPrompt: ((error: Error, output: string) => string) | undefined,
|
|
167
|
+
): error is Error {
|
|
168
|
+
return error instanceof Error && attempt < maxAttempts && correctionPrompt !== undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
133
171
|
async function requestValidated<T>(
|
|
134
172
|
ctx: GenerationContext,
|
|
135
173
|
candidate: ModelCandidate,
|
|
@@ -137,37 +175,36 @@ async function requestValidated<T>(
|
|
|
137
175
|
validate: (text: string) => T,
|
|
138
176
|
correctionPrompt?: (error: Error, text: string) => string,
|
|
139
177
|
): Promise<T> {
|
|
140
|
-
const
|
|
141
|
-
const messages: Message[] = [userMessage];
|
|
178
|
+
const messages: Message[] = [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }];
|
|
142
179
|
const sessionId = randomUUID();
|
|
143
180
|
|
|
144
181
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
145
182
|
const response = await completeCandidate(ctx, candidate, messages, sessionId);
|
|
146
183
|
const text = responseText(response);
|
|
147
|
-
if (response.stopReason === "error") {
|
|
148
|
-
const error = new Error(response.errorMessage || "model returned an error");
|
|
149
|
-
if (attempt < MAX_ATTEMPTS && !shouldCooldownProvider(error)) continue;
|
|
150
|
-
throw error;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
184
|
try {
|
|
185
|
+
throwUnlessRetryableStopError(response, attempt, MAX_ATTEMPTS);
|
|
186
|
+
if (response.stopReason === "error") continue;
|
|
154
187
|
return validate(text);
|
|
155
188
|
} catch (error) {
|
|
156
|
-
if (!(error
|
|
157
|
-
|
|
158
|
-
|
|
189
|
+
if (!shouldRetryValidation(error, attempt, MAX_ATTEMPTS, correctionPrompt) || !correctionPrompt) {
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
159
192
|
if (text) messages.push({ ...response, content: [{ type: "text", text }] });
|
|
160
|
-
messages
|
|
161
|
-
role: "user",
|
|
162
|
-
content: [{ type: "text", text: correctionPrompt(error, truncAt(text, 4_000)) }],
|
|
163
|
-
timestamp: Date.now(),
|
|
164
|
-
});
|
|
193
|
+
pushCorrection(messages, error, text, correctionPrompt);
|
|
165
194
|
}
|
|
166
195
|
}
|
|
167
196
|
|
|
168
197
|
throw new Error("Model generation failed.");
|
|
169
198
|
}
|
|
170
199
|
|
|
200
|
+
function validateSingleToolCall(tool: Tool, toolCalls: readonly { name: string; arguments: unknown }[]): unknown {
|
|
201
|
+
if (toolCalls.length !== 1) throw new Error(`Model must call ${tool.name} exactly once.`);
|
|
202
|
+
const toolCall = toolCalls[0];
|
|
203
|
+
if (!toolCall) throw new Error(`Model must call ${tool.name}.`);
|
|
204
|
+
if (toolCall.name !== tool.name) throw new Error(`Model called ${toolCall.name}; expected ${tool.name}.`);
|
|
205
|
+
return toolCall.arguments;
|
|
206
|
+
}
|
|
207
|
+
|
|
171
208
|
async function requestToolValidated<T>(
|
|
172
209
|
ctx: GenerationContext,
|
|
173
210
|
candidate: ModelCandidate,
|
|
@@ -185,27 +222,15 @@ async function requestToolValidated<T>(
|
|
|
185
222
|
const text = responseText(response);
|
|
186
223
|
const toolCalls = response.content.flatMap((part) => (part.type === "toolCall" ? [part] : []));
|
|
187
224
|
const output = text || formatToolCalls(toolCalls);
|
|
188
|
-
if (response.stopReason === "error") {
|
|
189
|
-
const error = new Error(response.errorMessage || "model returned an error");
|
|
190
|
-
if (attempt < maxAttempts && !shouldCooldownProvider(error)) continue;
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
225
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (toolCall.name !== tool.name) throw new Error(`Model called ${toolCall.name}; expected ${tool.name}.`);
|
|
199
|
-
return validate(toolCall.arguments);
|
|
226
|
+
throwUnlessRetryableStopError(response, attempt, maxAttempts);
|
|
227
|
+
if (response.stopReason === "error") continue;
|
|
228
|
+
return validate(validateSingleToolCall(tool, toolCalls));
|
|
200
229
|
} catch (error) {
|
|
201
|
-
if (!(error
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
messages
|
|
205
|
-
role: "user",
|
|
206
|
-
content: [{ type: "text", text: correctionPrompt(error, truncAt(output, 4_000)) }],
|
|
207
|
-
timestamp: Date.now(),
|
|
208
|
-
});
|
|
230
|
+
if (!shouldRetryValidation(error, attempt, maxAttempts, correctionPrompt) || !correctionPrompt) {
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
pushCorrection(messages, error, output, correctionPrompt);
|
|
209
234
|
}
|
|
210
235
|
}
|
|
211
236
|
|
package/shared/settings/load.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { IsObject, type TSchema } from "typebox";
|
|
2
|
+
import { IsObject, type TObject, type TSchema } from "typebox";
|
|
3
3
|
import { Value } from "typebox/value";
|
|
4
4
|
import type { JsonObject, TauExtensionSettingsSpec } from "./define.ts";
|
|
5
5
|
import { asObject, readJsonStatus, writeJsonObject } from "./json.ts";
|
|
@@ -63,6 +63,47 @@ function extensionSection(root: JsonObject, key: string): JsonObject | undefined
|
|
|
63
63
|
|
|
64
64
|
const NO_DEFAULT = Symbol("no-default");
|
|
65
65
|
|
|
66
|
+
function repairKnownProperties(
|
|
67
|
+
schema: TObject,
|
|
68
|
+
valueObject: JsonObject,
|
|
69
|
+
defaultObject: JsonObject | undefined,
|
|
70
|
+
): JsonObject | typeof NO_DEFAULT {
|
|
71
|
+
const repaired: JsonObject = {};
|
|
72
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
73
|
+
for (const [key, propertySchema] of Object.entries(schema.properties)) {
|
|
74
|
+
if (!Object.hasOwn(valueObject, key)) {
|
|
75
|
+
if (!required.has(key)) continue;
|
|
76
|
+
const requiredDefault = propertyDefault(propertySchema, defaultObject, key);
|
|
77
|
+
if (requiredDefault === NO_DEFAULT) return NO_DEFAULT;
|
|
78
|
+
repaired[key] = Value.Clone(requiredDefault);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const propertyValue = replaceInvalidProperties(
|
|
83
|
+
propertySchema,
|
|
84
|
+
valueObject[key],
|
|
85
|
+
propertyDefault(propertySchema, defaultObject, key),
|
|
86
|
+
);
|
|
87
|
+
if (propertyValue !== NO_DEFAULT) repaired[key] = propertyValue;
|
|
88
|
+
else if (required.has(key)) return NO_DEFAULT;
|
|
89
|
+
}
|
|
90
|
+
return repaired;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function repairAdditionalProperties(schema: TObject, valueObject: JsonObject, repaired: JsonObject): void {
|
|
94
|
+
const additionalProperties = asObject(schema)?.additionalProperties;
|
|
95
|
+
if (additionalProperties === false) return;
|
|
96
|
+
for (const [key, additionalValue] of Object.entries(valueObject)) {
|
|
97
|
+
if (Object.hasOwn(schema.properties, key)) continue;
|
|
98
|
+
if (additionalProperties === undefined || additionalProperties === true) {
|
|
99
|
+
repaired[key] = additionalValue;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const repairedAdditional = replaceInvalidProperties(additionalProperties as TSchema, additionalValue, NO_DEFAULT);
|
|
103
|
+
if (repairedAdditional !== NO_DEFAULT) repaired[key] = repairedAdditional;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
66
107
|
function replaceInvalidProperties(
|
|
67
108
|
schema: TSchema,
|
|
68
109
|
value: unknown,
|
|
@@ -73,43 +114,9 @@ function replaceInvalidProperties(
|
|
|
73
114
|
const valueObject = asObject(value);
|
|
74
115
|
if (IsObject(schema) && valueObject) {
|
|
75
116
|
const defaultObject = documentedDefault === NO_DEFAULT ? undefined : asObject(documentedDefault);
|
|
76
|
-
const repaired
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!Object.hasOwn(valueObject, key)) {
|
|
80
|
-
if (!required.has(key)) continue;
|
|
81
|
-
const requiredDefault = propertyDefault(propertySchema, defaultObject, key);
|
|
82
|
-
if (requiredDefault === NO_DEFAULT) return validatedDefault(schema, documentedDefault);
|
|
83
|
-
repaired[key] = Value.Clone(requiredDefault);
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const propertyValue = replaceInvalidProperties(
|
|
88
|
-
propertySchema,
|
|
89
|
-
valueObject[key],
|
|
90
|
-
propertyDefault(propertySchema, defaultObject, key),
|
|
91
|
-
);
|
|
92
|
-
if (propertyValue !== NO_DEFAULT) repaired[key] = propertyValue;
|
|
93
|
-
else if (required.has(key)) return validatedDefault(schema, documentedDefault);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const schemaObject = asObject(schema);
|
|
97
|
-
const additionalProperties = schemaObject?.additionalProperties;
|
|
98
|
-
if (additionalProperties !== false) {
|
|
99
|
-
for (const [key, additionalValue] of Object.entries(valueObject)) {
|
|
100
|
-
if (Object.hasOwn(schema.properties, key)) continue;
|
|
101
|
-
if (additionalProperties === undefined || additionalProperties === true) repaired[key] = additionalValue;
|
|
102
|
-
else {
|
|
103
|
-
const repairedAdditional = replaceInvalidProperties(
|
|
104
|
-
additionalProperties as TSchema,
|
|
105
|
-
additionalValue,
|
|
106
|
-
NO_DEFAULT,
|
|
107
|
-
);
|
|
108
|
-
if (repairedAdditional !== NO_DEFAULT) repaired[key] = repairedAdditional;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
117
|
+
const repaired = repairKnownProperties(schema, valueObject, defaultObject);
|
|
118
|
+
if (repaired === NO_DEFAULT) return validatedDefault(schema, documentedDefault);
|
|
119
|
+
repairAdditionalProperties(schema, valueObject, repaired);
|
|
113
120
|
if (Value.Check(schema, repaired)) return repaired;
|
|
114
121
|
}
|
|
115
122
|
|
|
@@ -126,6 +126,7 @@ export class TemporaryOutputStore {
|
|
|
126
126
|
return writer;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// fallow-ignore-next-line unused-class-member -- called via createTemporaryOutputStore() from explore session lifecycle
|
|
129
130
|
async shutdown(): Promise<void> {
|
|
130
131
|
if (this.shutdownPromise) return this.shutdownPromise;
|
|
131
132
|
this.shutdownPromise = this.performShutdown();
|
|
@@ -177,6 +178,20 @@ export class TemporaryOutputStore {
|
|
|
177
178
|
this.active.delete(writer);
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
private async isRemovableOrphan(path: string): Promise<boolean> {
|
|
182
|
+
const metadata = await stat(path);
|
|
183
|
+
if (Date.now() - metadata.mtimeMs < this.orphanLifetimeMs) return false;
|
|
184
|
+
const marker = JSON.parse(await readFile(join(path, OWNER_FILE), "utf8")) as Partial<OwnerMarker>;
|
|
185
|
+
if (marker.kind !== "tau-tool-output" || marker.version !== 1 || typeof marker.pid !== "number") return false;
|
|
186
|
+
try {
|
|
187
|
+
process.kill(marker.pid, 0);
|
|
188
|
+
return false;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (!isErrorCode(error, "ESRCH")) return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
180
195
|
private async removeStaleOrphans(): Promise<void> {
|
|
181
196
|
let directory;
|
|
182
197
|
try {
|
|
@@ -188,16 +203,7 @@ export class TemporaryOutputStore {
|
|
|
188
203
|
if (!entry.isDirectory() || !entry.name.startsWith(DIRECTORY_PREFIX)) continue;
|
|
189
204
|
const path = join(this.baseDirectory, entry.name);
|
|
190
205
|
try {
|
|
191
|
-
|
|
192
|
-
if (Date.now() - metadata.mtimeMs < this.orphanLifetimeMs) continue;
|
|
193
|
-
const marker = JSON.parse(await readFile(join(path, OWNER_FILE), "utf8")) as Partial<OwnerMarker>;
|
|
194
|
-
if (marker.kind !== "tau-tool-output" || marker.version !== 1 || typeof marker.pid !== "number") continue;
|
|
195
|
-
try {
|
|
196
|
-
process.kill(marker.pid, 0);
|
|
197
|
-
continue;
|
|
198
|
-
} catch (error) {
|
|
199
|
-
if (!isErrorCode(error, "ESRCH")) continue;
|
|
200
|
-
}
|
|
206
|
+
if (!(await this.isRemovableOrphan(path))) continue;
|
|
201
207
|
await rm(path, { recursive: true, force: true });
|
|
202
208
|
} catch {
|
|
203
209
|
// An untrusted or concurrently changing directory is not ours to remove.
|