taskflow-core 0.1.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/dist/agents/analyst.md +30 -0
- package/dist/agents/critic.md +31 -0
- package/dist/agents/doc-writer.md +43 -0
- package/dist/agents/executor-code.md +36 -0
- package/dist/agents/executor-fast.md +26 -0
- package/dist/agents/executor-ui.md +35 -0
- package/dist/agents/executor.md +29 -0
- package/dist/agents/final-arbiter.md +29 -0
- package/dist/agents/plan-arbiter.md +35 -0
- package/dist/agents/planner.md +30 -0
- package/dist/agents/recover.md +28 -0
- package/dist/agents/reviewer.md +37 -0
- package/dist/agents/risk-reviewer.md +37 -0
- package/dist/agents/scout.md +51 -0
- package/dist/agents/security-reviewer.md +39 -0
- package/dist/agents/test-engineer.md +31 -0
- package/dist/agents/verifier.md +29 -0
- package/dist/agents/visual-explorer.md +32 -0
- package/dist/agents.d.ts +55 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +265 -0
- package/dist/agents.js.map +1 -0
- package/dist/cache.d.ts +57 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +256 -0
- package/dist/cache.js.map +1 -0
- package/dist/compile.d.ts +37 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +324 -0
- package/dist/compile.js.map +1 -0
- package/dist/context-store.d.ts +142 -0
- package/dist/context-store.d.ts.map +1 -0
- package/dist/context-store.js +383 -0
- package/dist/context-store.js.map +1 -0
- package/dist/detached-runner.d.ts +12 -0
- package/dist/detached-runner.d.ts.map +1 -0
- package/dist/detached-runner.js +68 -0
- package/dist/detached-runner.js.map +1 -0
- package/dist/flowir/hash.d.ts +51 -0
- package/dist/flowir/hash.d.ts.map +1 -0
- package/dist/flowir/hash.js +90 -0
- package/dist/flowir/hash.js.map +1 -0
- package/dist/flowir/index.d.ts +43 -0
- package/dist/flowir/index.d.ts.map +1 -0
- package/dist/flowir/index.js +62 -0
- package/dist/flowir/index.js.map +1 -0
- package/dist/flowir/meta.d.ts +103 -0
- package/dist/flowir/meta.d.ts.map +1 -0
- package/dist/flowir/meta.js +19 -0
- package/dist/flowir/meta.js.map +1 -0
- package/dist/flowir/phasefp.d.ts +55 -0
- package/dist/flowir/phasefp.d.ts.map +1 -0
- package/dist/flowir/phasefp.js +123 -0
- package/dist/flowir/phasefp.js.map +1 -0
- package/dist/flowir/translate.d.ts +37 -0
- package/dist/flowir/translate.d.ts.map +1 -0
- package/dist/flowir/translate.js +137 -0
- package/dist/flowir/translate.js.map +1 -0
- package/dist/frontmatter.d.ts +21 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +107 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/host/runner-types.d.ts +98 -0
- package/dist/host/runner-types.d.ts.map +1 -0
- package/dist/host/runner-types.js +21 -0
- package/dist/host/runner-types.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate.d.ts +52 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/interpolate.js +418 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +43 -0
- package/dist/paths.js.map +1 -0
- package/dist/runner-core.d.ts +49 -0
- package/dist/runner-core.d.ts.map +1 -0
- package/dist/runner-core.js +208 -0
- package/dist/runner-core.js.map +1 -0
- package/dist/runtime.d.ts +234 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +2284 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +257 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +795 -0
- package/dist/schema.js.map +1 -0
- package/dist/stale.d.ts +72 -0
- package/dist/stale.d.ts.map +1 -0
- package/dist/stale.js +179 -0
- package/dist/stale.js.map +1 -0
- package/dist/store.d.ts +237 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +761 -0
- package/dist/store.js.map +1 -0
- package/dist/typebox-helpers.d.ts +15 -0
- package/dist/typebox-helpers.d.ts.map +1 -0
- package/dist/typebox-helpers.js +19 -0
- package/dist/typebox-helpers.js.map +1 -0
- package/dist/usage.d.ts +21 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +33 -0
- package/dist/usage.js.map +1 -0
- package/dist/verify.d.ts +38 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +324 -0
- package/dist/verify.js.map +1 -0
- package/dist/workspace.d.ts +64 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +178 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +40 -0
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent discovery and configuration.
|
|
3
|
+
* Adapted from the pi subagent extension so taskflow shares the same agent
|
|
4
|
+
* pool (~/.pi/agent/agents/*.md, .pi/agents/*.md) and settings overrides.
|
|
5
|
+
*/
|
|
6
|
+
export type AgentScope = "user" | "project" | "both";
|
|
7
|
+
export interface TaskflowSettings {
|
|
8
|
+
/** Whether taskflow's package-local built-in agents are available to flows. */
|
|
9
|
+
builtInAgents: boolean;
|
|
10
|
+
/** Whether package-local built-ins are copied into the current project's .pi/agents/. */
|
|
11
|
+
syncBuiltinAgentsToProject: boolean;
|
|
12
|
+
/** Maximum completed/failed runs to keep. 0 disables cleanup. */
|
|
13
|
+
maxKeptRuns: number;
|
|
14
|
+
/** Maximum age (days) for completed/failed runs. 0 disables age cleanup. */
|
|
15
|
+
maxRunAgeDays: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_TASKFLOW_SETTINGS: TaskflowSettings;
|
|
18
|
+
export declare function normalizeTaskflowSettings(raw: unknown): TaskflowSettings;
|
|
19
|
+
export declare function shouldLoadBuiltinAgents(settings?: TaskflowSettings): boolean;
|
|
20
|
+
export declare function shouldSyncBuiltinAgentsToProject(settings?: TaskflowSettings): boolean;
|
|
21
|
+
export interface AgentConfig {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
tools?: string[];
|
|
25
|
+
model?: string;
|
|
26
|
+
thinking?: string;
|
|
27
|
+
systemPrompt: string;
|
|
28
|
+
source: "user" | "project" | "built-in";
|
|
29
|
+
filePath: string;
|
|
30
|
+
}
|
|
31
|
+
/** @internal */
|
|
32
|
+
export interface AgentDiscoveryResult {
|
|
33
|
+
agents: AgentConfig[];
|
|
34
|
+
projectAgentsDir: string | null;
|
|
35
|
+
}
|
|
36
|
+
export declare function discoverAgents(cwd: string, scope: AgentScope, modelRoles?: Record<string, string>, taskflowSettings?: TaskflowSettings): AgentDiscoveryResult;
|
|
37
|
+
export interface SubagentSettings {
|
|
38
|
+
globalThinking?: string;
|
|
39
|
+
modelRoles?: Record<string, string>;
|
|
40
|
+
taskflow: TaskflowSettings;
|
|
41
|
+
}
|
|
42
|
+
/** Read subagent overrides from ~/.pi/agent/settings.json (shared with the subagent extension). */
|
|
43
|
+
export declare function readSubagentSettings(): SubagentSettings;
|
|
44
|
+
/**
|
|
45
|
+
* Copy the 18 built-in agents from extensions/agents/*.md into the project's
|
|
46
|
+
* .pi/agents/ directory so Pi's native subagent tool (and any other extension)
|
|
47
|
+
* can discover them. taskflow's own discoverAgents() already reads from this
|
|
48
|
+
* directory with lower priority than built-in, so the copy is a no-op for
|
|
49
|
+
* taskflow phases — it only matters for Pi's native agent discovery.
|
|
50
|
+
*
|
|
51
|
+
* Idempotent: only copies agents whose built-in source is newer than the
|
|
52
|
+
* project copy (or that don't exist yet).
|
|
53
|
+
*/
|
|
54
|
+
export declare function syncBuiltinAgentsToProject(cwd: string): void;
|
|
55
|
+
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,gBAAgB;IAChC,+EAA+E;IAC/E,aAAa,EAAE,OAAO,CAAC;IACvB,yFAAyF;IACzF,0BAA0B,EAAE,OAAO,CAAC;IACpC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,aAAa,EAAE,MAAM,CAAC;CACtB;AAID,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAuBxE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,GAAE,gBAA4C,GAAG,OAAO,CAEvG;AAED,wBAAgB,gCAAgC,CAAC,QAAQ,GAAE,gBAA4C,GAAG,OAAO,CAEhH;AAED,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,gBAAgB;AAChB,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAyFD,wBAAgB,cAAc,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,UAAU,EACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,gBAAgB,GAAE,gBAA4C,GAC5D,oBAAoB,CAwCtB;AAED,MAAM,WAAW,gBAAgB;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,QAAQ,EAAE,gBAAgB,CAAC;CAC3B;AAoBD,mGAAmG;AACnG,wBAAgB,oBAAoB,IAAI,gBAAgB,CAavD;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAmC5D"}
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent discovery and configuration.
|
|
3
|
+
* Adapted from the pi subagent extension so taskflow shares the same agent
|
|
4
|
+
* pool (~/.pi/agent/agents/*.md, .pi/agents/*.md) and settings overrides.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { getAgentDir } from "./paths.js";
|
|
9
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
10
|
+
import { DEFAULT_KEPT_RUNS, DEFAULT_RUN_AGE_DAYS, writeFileAtomic } from "./store.js";
|
|
11
|
+
export const DEFAULT_TASKFLOW_SETTINGS = {
|
|
12
|
+
builtInAgents: true,
|
|
13
|
+
syncBuiltinAgentsToProject: false,
|
|
14
|
+
maxKeptRuns: DEFAULT_KEPT_RUNS,
|
|
15
|
+
maxRunAgeDays: DEFAULT_RUN_AGE_DAYS,
|
|
16
|
+
};
|
|
17
|
+
export function normalizeTaskflowSettings(raw) {
|
|
18
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
19
|
+
return { ...DEFAULT_TASKFLOW_SETTINGS };
|
|
20
|
+
}
|
|
21
|
+
const rec = raw;
|
|
22
|
+
return {
|
|
23
|
+
builtInAgents: typeof rec.builtInAgents === "boolean"
|
|
24
|
+
? rec.builtInAgents
|
|
25
|
+
: DEFAULT_TASKFLOW_SETTINGS.builtInAgents,
|
|
26
|
+
syncBuiltinAgentsToProject: typeof rec.syncBuiltinAgentsToProject === "boolean"
|
|
27
|
+
? rec.syncBuiltinAgentsToProject
|
|
28
|
+
: DEFAULT_TASKFLOW_SETTINGS.syncBuiltinAgentsToProject,
|
|
29
|
+
maxKeptRuns: typeof rec.maxKeptRuns === "number" && rec.maxKeptRuns >= 0 && Number.isInteger(rec.maxKeptRuns)
|
|
30
|
+
? rec.maxKeptRuns
|
|
31
|
+
: DEFAULT_TASKFLOW_SETTINGS.maxKeptRuns,
|
|
32
|
+
maxRunAgeDays: typeof rec.maxRunAgeDays === "number" && rec.maxRunAgeDays >= 0 && Number.isInteger(rec.maxRunAgeDays)
|
|
33
|
+
? rec.maxRunAgeDays
|
|
34
|
+
: DEFAULT_TASKFLOW_SETTINGS.maxRunAgeDays,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function shouldLoadBuiltinAgents(settings = DEFAULT_TASKFLOW_SETTINGS) {
|
|
38
|
+
return settings.builtInAgents;
|
|
39
|
+
}
|
|
40
|
+
export function shouldSyncBuiltinAgentsToProject(settings = DEFAULT_TASKFLOW_SETTINGS) {
|
|
41
|
+
return settings.builtInAgents && settings.syncBuiltinAgentsToProject;
|
|
42
|
+
}
|
|
43
|
+
function loadAgentsFromDir(dir, source) {
|
|
44
|
+
const agents = [];
|
|
45
|
+
if (!fs.existsSync(dir))
|
|
46
|
+
return agents;
|
|
47
|
+
let entries;
|
|
48
|
+
try {
|
|
49
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return agents;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
try {
|
|
56
|
+
if (!entry.name.endsWith(".md"))
|
|
57
|
+
continue;
|
|
58
|
+
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
59
|
+
continue;
|
|
60
|
+
const filePath = path.join(dir, entry.name);
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const { frontmatter, body } = (() => {
|
|
69
|
+
try {
|
|
70
|
+
return parseFrontmatter(content);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// A single malformed agent file must not break discovery for every flow.
|
|
74
|
+
return { frontmatter: {}, body: "" };
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
if (!frontmatter.name || !frontmatter.description)
|
|
78
|
+
continue;
|
|
79
|
+
// frontmatter is YAML-parsed: tools may be a comma-separated string ("a, b")
|
|
80
|
+
// OR a YAML sequence ([a, b]). Handle both forms; reject other types to
|
|
81
|
+
// prevent garbage output from malformed YAML (e.g. boolean, number).
|
|
82
|
+
const rawTools = frontmatter.tools;
|
|
83
|
+
let tools;
|
|
84
|
+
if (Array.isArray(rawTools)) {
|
|
85
|
+
tools = rawTools.map((t) => String(t).trim()).filter(Boolean);
|
|
86
|
+
}
|
|
87
|
+
else if (typeof rawTools === "string") {
|
|
88
|
+
tools = rawTools.split(",").map((t) => t.trim()).filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
else if (rawTools !== undefined && rawTools !== null) {
|
|
91
|
+
console.warn(`[taskflow] Agent '${String(frontmatter.name)}': 'tools' must be a string or array, got ${typeof rawTools}. Ignoring.`);
|
|
92
|
+
tools = undefined;
|
|
93
|
+
}
|
|
94
|
+
agents.push({
|
|
95
|
+
name: String(frontmatter.name),
|
|
96
|
+
description: String(frontmatter.description),
|
|
97
|
+
tools: tools && tools.length > 0 ? tools : undefined,
|
|
98
|
+
model: frontmatter.model === undefined ? undefined : String(frontmatter.model),
|
|
99
|
+
thinking: frontmatter.thinking === undefined ? undefined : String(frontmatter.thinking),
|
|
100
|
+
systemPrompt: body,
|
|
101
|
+
source,
|
|
102
|
+
filePath,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Defense-in-depth: a single bad agent file must not break discovery
|
|
107
|
+
// for the entire flow (e.g. exotic YAML shapes, runtime errors in
|
|
108
|
+
// field access, symlink races, etc.).
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return agents;
|
|
113
|
+
}
|
|
114
|
+
function isDirectory(p) {
|
|
115
|
+
try {
|
|
116
|
+
return fs.statSync(p).isDirectory();
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function findNearestProjectAgentsDir(cwd) {
|
|
123
|
+
let currentDir = cwd;
|
|
124
|
+
while (true) {
|
|
125
|
+
const candidate = path.join(currentDir, ".pi", "agents");
|
|
126
|
+
if (isDirectory(candidate))
|
|
127
|
+
return candidate;
|
|
128
|
+
const parentDir = path.dirname(currentDir);
|
|
129
|
+
if (parentDir === currentDir)
|
|
130
|
+
return null;
|
|
131
|
+
currentDir = parentDir;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function discoverAgents(cwd, scope, modelRoles, taskflowSettings = DEFAULT_TASKFLOW_SETTINGS) {
|
|
135
|
+
// Built-in agents ship with the package (extensions/agents/*.md).
|
|
136
|
+
// PI_TASKFLOW_BUILTIN_AGENTS_DIR is kept as a test hook only; user-facing
|
|
137
|
+
// enable/disable lives in settings.json under `taskflow.builtInAgents`.
|
|
138
|
+
const builtInDirEnv = process.env.PI_TASKFLOW_BUILTIN_AGENTS_DIR;
|
|
139
|
+
const builtInDir = builtInDirEnv ? builtInDirEnv : builtInDirEnv === undefined ? path.resolve(import.meta.dirname, "agents") : "";
|
|
140
|
+
const builtInAgents = shouldLoadBuiltinAgents(taskflowSettings) && builtInDir ? loadAgentsFromDir(builtInDir, "built-in") : [];
|
|
141
|
+
const userDir = path.join(getAgentDir(), "agents");
|
|
142
|
+
const projectAgentsDir = findNearestProjectAgentsDir(cwd);
|
|
143
|
+
const userAgents = scope === "project" ? [] : loadAgentsFromDir(userDir, "user");
|
|
144
|
+
const projectAgents = scope === "user" || !projectAgentsDir ? [] : loadAgentsFromDir(projectAgentsDir, "project");
|
|
145
|
+
// Layer order: built-in → user → project (later layers override earlier)
|
|
146
|
+
const agentMap = new Map();
|
|
147
|
+
for (const a of builtInAgents)
|
|
148
|
+
agentMap.set(a.name, a);
|
|
149
|
+
if (scope === "both") {
|
|
150
|
+
for (const a of userAgents)
|
|
151
|
+
agentMap.set(a.name, a);
|
|
152
|
+
for (const a of projectAgents)
|
|
153
|
+
agentMap.set(a.name, a);
|
|
154
|
+
}
|
|
155
|
+
else if (scope === "user") {
|
|
156
|
+
for (const a of userAgents)
|
|
157
|
+
agentMap.set(a.name, a);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
for (const a of projectAgents)
|
|
161
|
+
agentMap.set(a.name, a);
|
|
162
|
+
}
|
|
163
|
+
// Resolve {{role}} model references (e.g. {{fast}} → openrouter/deepseek/v4-flash)
|
|
164
|
+
// Clone before mutating, consistent with the overrides block above.
|
|
165
|
+
if (modelRoles) {
|
|
166
|
+
for (const [name, agent] of agentMap.entries()) {
|
|
167
|
+
const resolved = resolveModelRole(agent.model, modelRoles);
|
|
168
|
+
if (resolved !== agent.model) {
|
|
169
|
+
const mutated = { ...agent };
|
|
170
|
+
mutated.model = resolved;
|
|
171
|
+
agentMap.set(name, mutated);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { agents: Array.from(agentMap.values()), projectAgentsDir };
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Resolve `{{roleName}}` model references against a role→model mapping.
|
|
179
|
+
* E.g. `{{fast}}` → `openrouter/deepseek/deepseek-v4-flash` if modelRoles.fast is set.
|
|
180
|
+
* Returns undefined if the value is not a role reference or the role is unmapped.
|
|
181
|
+
*/
|
|
182
|
+
/**
|
|
183
|
+
* Resolve `{{roleName}}` model references against a role→model mapping.
|
|
184
|
+
* E.g. `{{fast}}` → `openrouter/deepseek/deepseek-v4-flash` if modelRoles.fast is set.
|
|
185
|
+
* Returns undefined if the value is not a role reference or the role is unmapped.
|
|
186
|
+
* @internal
|
|
187
|
+
*/
|
|
188
|
+
function resolveModelRole(model, roles) {
|
|
189
|
+
if (!model || !roles)
|
|
190
|
+
return model;
|
|
191
|
+
const match = model.match(/^\{\{(\w+)\}\}$/);
|
|
192
|
+
if (!match)
|
|
193
|
+
return model;
|
|
194
|
+
return roles[match[1]] ?? undefined;
|
|
195
|
+
}
|
|
196
|
+
/** Read subagent overrides from ~/.pi/agent/settings.json (shared with the subagent extension). */
|
|
197
|
+
export function readSubagentSettings() {
|
|
198
|
+
try {
|
|
199
|
+
const settingsPath = path.join(getAgentDir(), "settings.json");
|
|
200
|
+
if (!fs.existsSync(settingsPath))
|
|
201
|
+
return { taskflow: { ...DEFAULT_TASKFLOW_SETTINGS } };
|
|
202
|
+
const raw = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
203
|
+
return {
|
|
204
|
+
globalThinking: raw.subagents?.globalThinking ?? raw.defaultThinkingLevel,
|
|
205
|
+
modelRoles: raw.modelRoles,
|
|
206
|
+
taskflow: normalizeTaskflowSettings(raw.taskflow),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return { taskflow: { ...DEFAULT_TASKFLOW_SETTINGS } };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Copy the 18 built-in agents from extensions/agents/*.md into the project's
|
|
215
|
+
* .pi/agents/ directory so Pi's native subagent tool (and any other extension)
|
|
216
|
+
* can discover them. taskflow's own discoverAgents() already reads from this
|
|
217
|
+
* directory with lower priority than built-in, so the copy is a no-op for
|
|
218
|
+
* taskflow phases — it only matters for Pi's native agent discovery.
|
|
219
|
+
*
|
|
220
|
+
* Idempotent: only copies agents whose built-in source is newer than the
|
|
221
|
+
* project copy (or that don't exist yet).
|
|
222
|
+
*/
|
|
223
|
+
export function syncBuiltinAgentsToProject(cwd) {
|
|
224
|
+
const builtInDir = path.resolve(import.meta.dirname, "agents");
|
|
225
|
+
if (!fs.existsSync(builtInDir))
|
|
226
|
+
return;
|
|
227
|
+
const projectAgentsDir = path.join(cwd, ".pi", "agents");
|
|
228
|
+
fs.mkdirSync(projectAgentsDir, { recursive: true });
|
|
229
|
+
let entries;
|
|
230
|
+
try {
|
|
231
|
+
entries = fs.readdirSync(builtInDir, { withFileTypes: true });
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
238
|
+
continue;
|
|
239
|
+
const src = path.join(builtInDir, entry.name);
|
|
240
|
+
const dst = path.join(projectAgentsDir, entry.name);
|
|
241
|
+
let srcMtime = 0;
|
|
242
|
+
try {
|
|
243
|
+
srcMtime = fs.statSync(src).mtimeMs;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
let dstMtime = 0;
|
|
249
|
+
try {
|
|
250
|
+
dstMtime = fs.statSync(dst).mtimeMs;
|
|
251
|
+
}
|
|
252
|
+
catch { /* dst doesn't exist yet */ }
|
|
253
|
+
// Only copy when the source is newer (or the destination is missing).
|
|
254
|
+
if (srcMtime <= dstMtime)
|
|
255
|
+
continue;
|
|
256
|
+
try {
|
|
257
|
+
const content = fs.readFileSync(src, "utf-8");
|
|
258
|
+
writeFileAtomic(dst, content);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// Best-effort: a locked file must not block the sync.
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-run memoization: fingerprint resolver + persistent phase-result cache.
|
|
3
|
+
*
|
|
4
|
+
* See docs/rfc-cross-run-memoization.md. The cache lets a phase reuse the result
|
|
5
|
+
* of an identical-input phase from ANY prior run (scope: "cross-run"), for $0.00.
|
|
6
|
+
* Freshness is guarded by:
|
|
7
|
+
* - the existing content-addressed inputHash (declared inputs)
|
|
8
|
+
* - optional `fingerprint` entries folded into the key (git/glob/file/env)
|
|
9
|
+
* - optional TTL
|
|
10
|
+
* - default `run-only` scope (this module is only consulted for cross-run)
|
|
11
|
+
*
|
|
12
|
+
* Zero runtime dependencies: Node built-ins only (fs.globSync requires Node >=22,
|
|
13
|
+
* which the project already targets).
|
|
14
|
+
*/
|
|
15
|
+
import { type PhaseState } from "./store.ts";
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a phase's `fingerprint` list into a single deterministic string to be
|
|
18
|
+
* folded into the cache key. Returns "" when there are no entries (so the key is
|
|
19
|
+
* unchanged for phases that declare no fingerprint).
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveFingerprint(entries: string[] | undefined, cwd: string): string;
|
|
22
|
+
/** @internal */
|
|
23
|
+
export interface CacheEntry {
|
|
24
|
+
/** The full cache key (== phase inputHash incl. fingerprint). */
|
|
25
|
+
key: string;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
/** Trimmed phase result surface that downstream phases consume. */
|
|
28
|
+
output?: string;
|
|
29
|
+
json?: unknown;
|
|
30
|
+
model?: string;
|
|
31
|
+
/** Full PhaseState payload preserved so cross-run reuse is semantically
|
|
32
|
+
* equivalent to within-run resume. Storing only output/json would drop
|
|
33
|
+
* `gate`, `approval`, `reads`, `loop`, `tournament`, `warnings`, etc.,
|
|
34
|
+
* breaking recompute soundness and gate-block detection. */
|
|
35
|
+
state?: PhaseState;
|
|
36
|
+
/** Provenance for audit / cleanup. */
|
|
37
|
+
flowName?: string;
|
|
38
|
+
phaseId?: string;
|
|
39
|
+
runId?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The cross-run cache, scoped to a working directory. Cheap to construct; all IO
|
|
43
|
+
* is lazy and failure-tolerant (a broken cache must never break a run).
|
|
44
|
+
*/
|
|
45
|
+
export declare class CacheStore {
|
|
46
|
+
private dir;
|
|
47
|
+
constructor(cwd: string);
|
|
48
|
+
/** Look up a fresh entry. Returns null on miss, malformed key, or TTL expiry. */
|
|
49
|
+
get(key: string, ttlMs?: number): CacheEntry | null;
|
|
50
|
+
/** Store an entry (best-effort; never throws into the run). */
|
|
51
|
+
put(entry: CacheEntry): void;
|
|
52
|
+
/** Remove all cache entries. Returns the number removed. */
|
|
53
|
+
clear(): number;
|
|
54
|
+
/** Opportunistic eviction: drop expired/oversized entries. Best-effort. */
|
|
55
|
+
private cleanup;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAsGlF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKrF;AAMD,gBAAgB;AAChB,MAAM,WAAW,UAAU;IAC1B,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;iEAG6D;IAC7D,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAgBD;;;GAGG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,GAAG,CAAS;gBAER,GAAG,EAAE,MAAM;IAIvB,iFAAiF;IACjF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAgBnD,+DAA+D;IAC/D,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAc5B,4DAA4D;IAC5D,KAAK,IAAI,MAAM;IAmBf,2EAA2E;IAC3E,OAAO,CAAC,OAAO;CAqCf"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-run memoization: fingerprint resolver + persistent phase-result cache.
|
|
3
|
+
*
|
|
4
|
+
* See docs/rfc-cross-run-memoization.md. The cache lets a phase reuse the result
|
|
5
|
+
* of an identical-input phase from ANY prior run (scope: "cross-run"), for $0.00.
|
|
6
|
+
* Freshness is guarded by:
|
|
7
|
+
* - the existing content-addressed inputHash (declared inputs)
|
|
8
|
+
* - optional `fingerprint` entries folded into the key (git/glob/file/env)
|
|
9
|
+
* - optional TTL
|
|
10
|
+
* - default `run-only` scope (this module is only consulted for cross-run)
|
|
11
|
+
*
|
|
12
|
+
* Zero runtime dependencies: Node built-ins only (fs.globSync requires Node >=22,
|
|
13
|
+
* which the project already targets).
|
|
14
|
+
*/
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import * as crypto from "node:crypto";
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { cacheDir, withLock, writeFileAtomic } from "./store.js";
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Fingerprint resolution
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/** Per-file byte cap when content-hashing (mirrors store/context limits). */
|
|
24
|
+
const FINGERPRINT_MAX_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
25
|
+
/** Cap on glob match count folded into a single fingerprint (defensive). */
|
|
26
|
+
const FINGERPRINT_MAX_GLOB_MATCHES = 5000;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a single fingerprint entry to a deterministic string. Never throws:
|
|
29
|
+
* missing files / non-git repos / unreadable paths resolve to a stable sentinel
|
|
30
|
+
* so the key stays deterministic (and a later appearance of the resource simply
|
|
31
|
+
* changes the key → cache miss, which is the safe direction).
|
|
32
|
+
*/
|
|
33
|
+
function resolveOne(entry, cwd) {
|
|
34
|
+
try {
|
|
35
|
+
if (entry === "git:HEAD" || entry.startsWith("git:")) {
|
|
36
|
+
const ref = entry.slice("git:".length) || "HEAD";
|
|
37
|
+
// Reject refs that could be interpreted as git options (e.g. "--exec=...").
|
|
38
|
+
// The ref comes from a taskflow definition; refuse flag-like values so a
|
|
39
|
+
// crafted definition can't smuggle arguments into git.
|
|
40
|
+
if (ref.startsWith("-"))
|
|
41
|
+
return `git:${ref}=<invalid-ref>`;
|
|
42
|
+
try {
|
|
43
|
+
const sha = execFileSync("git", ["rev-parse", ref], {
|
|
44
|
+
cwd,
|
|
45
|
+
encoding: "utf-8",
|
|
46
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
47
|
+
timeout: 30_000,
|
|
48
|
+
}).trim();
|
|
49
|
+
return `git:${ref}=${sha}`;
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
if (e.code === "ETIMEDOUT") {
|
|
53
|
+
return `git:${ref}=<timeout>`;
|
|
54
|
+
}
|
|
55
|
+
return `git:${ref}=<no-git>`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (entry.startsWith("glob:") || entry.startsWith("glob!:")) {
|
|
59
|
+
const contentMode = entry.startsWith("glob!:");
|
|
60
|
+
const pattern = entry.slice(contentMode ? "glob!:".length : "glob:".length);
|
|
61
|
+
let matches;
|
|
62
|
+
try {
|
|
63
|
+
// fs.globSync (Node >=22) — cwd-relative, returns posix-ish paths.
|
|
64
|
+
matches = fs.globSync(pattern, { cwd }).slice().sort();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return `${entry}=<glob-error>`;
|
|
68
|
+
}
|
|
69
|
+
if (matches.length > FINGERPRINT_MAX_GLOB_MATCHES) {
|
|
70
|
+
matches = matches.slice(0, FINGERPRINT_MAX_GLOB_MATCHES);
|
|
71
|
+
}
|
|
72
|
+
const parts = [];
|
|
73
|
+
for (const rel of matches) {
|
|
74
|
+
const abs = path.resolve(cwd, rel);
|
|
75
|
+
try {
|
|
76
|
+
if (contentMode) {
|
|
77
|
+
const st = fs.statSync(abs);
|
|
78
|
+
if (st.isFile() && st.size <= FINGERPRINT_MAX_FILE_BYTES) {
|
|
79
|
+
const buf = fs.readFileSync(abs);
|
|
80
|
+
parts.push(`${rel}:${crypto.createHash("sha256").update(buf).digest("hex").slice(0, 16)}`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
parts.push(`${rel}:<skip>`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const st = fs.statSync(abs);
|
|
88
|
+
parts.push(`${rel}:${st.size}:${Math.floor(st.mtimeMs)}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
parts.push(`${rel}:<stat-error>`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const digest = crypto.createHash("sha256").update(parts.join("\u0000")).digest("hex").slice(0, 16);
|
|
96
|
+
return `${entry}=${digest}`;
|
|
97
|
+
}
|
|
98
|
+
if (entry.startsWith("file:")) {
|
|
99
|
+
const rel = entry.slice("file:".length);
|
|
100
|
+
const abs = path.resolve(cwd, rel);
|
|
101
|
+
try {
|
|
102
|
+
const st = fs.statSync(abs);
|
|
103
|
+
if (!st.isFile() || st.size > FINGERPRINT_MAX_FILE_BYTES)
|
|
104
|
+
return `file:${rel}=<skip>`;
|
|
105
|
+
const buf = fs.readFileSync(abs);
|
|
106
|
+
return `file:${rel}=${crypto.createHash("sha256").update(buf).digest("hex").slice(0, 16)}`;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return `file:${rel}=<missing>`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (entry.startsWith("env:")) {
|
|
113
|
+
const name = entry.slice("env:".length);
|
|
114
|
+
return `env:${name}=${process.env[name] ?? ""}`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Fall through to sentinel below.
|
|
119
|
+
}
|
|
120
|
+
// Unknown prefixes are rejected at validation time; defensively encode.
|
|
121
|
+
return `${entry}=<unknown>`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a phase's `fingerprint` list into a single deterministic string to be
|
|
125
|
+
* folded into the cache key. Returns "" when there are no entries (so the key is
|
|
126
|
+
* unchanged for phases that declare no fingerprint).
|
|
127
|
+
*/
|
|
128
|
+
export function resolveFingerprint(entries, cwd) {
|
|
129
|
+
if (!entries || entries.length === 0)
|
|
130
|
+
return "";
|
|
131
|
+
// Preserve author order (it's part of the declared key) but resolve each.
|
|
132
|
+
const resolved = entries.map((e) => resolveOne(e, cwd));
|
|
133
|
+
return crypto.createHash("sha256").update(resolved.join("\u0000")).digest("hex").slice(0, 16);
|
|
134
|
+
}
|
|
135
|
+
/** Keep at most this many cache entries; LRU-ish eviction by createdAt. */
|
|
136
|
+
const DEFAULT_MAX_ENTRIES = 1000;
|
|
137
|
+
/** Drop entries older than this regardless of TTL (hard backstop). */
|
|
138
|
+
const DEFAULT_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1000; // 90 days
|
|
139
|
+
/** A cache key is a 16-hex inputHash; constrain to that to prevent traversal. */
|
|
140
|
+
function isValidKey(key) {
|
|
141
|
+
return /^[0-9a-f]{8,64}$/.test(key);
|
|
142
|
+
}
|
|
143
|
+
function entryPath(dir, key) {
|
|
144
|
+
return path.join(dir, `${key}.json`);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The cross-run cache, scoped to a working directory. Cheap to construct; all IO
|
|
148
|
+
* is lazy and failure-tolerant (a broken cache must never break a run).
|
|
149
|
+
*/
|
|
150
|
+
export class CacheStore {
|
|
151
|
+
dir;
|
|
152
|
+
constructor(cwd) {
|
|
153
|
+
this.dir = cacheDir(cwd);
|
|
154
|
+
}
|
|
155
|
+
/** Look up a fresh entry. Returns null on miss, malformed key, or TTL expiry. */
|
|
156
|
+
get(key, ttlMs) {
|
|
157
|
+
if (!isValidKey(key))
|
|
158
|
+
return null;
|
|
159
|
+
let entry;
|
|
160
|
+
try {
|
|
161
|
+
const raw = fs.readFileSync(entryPath(this.dir, key), "utf-8");
|
|
162
|
+
entry = JSON.parse(raw);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
if (typeof entry?.createdAt !== "number")
|
|
168
|
+
return null;
|
|
169
|
+
const age = Date.now() - entry.createdAt;
|
|
170
|
+
if (age > DEFAULT_MAX_AGE_MS)
|
|
171
|
+
return null;
|
|
172
|
+
if (ttlMs !== undefined && age > ttlMs)
|
|
173
|
+
return null;
|
|
174
|
+
return entry;
|
|
175
|
+
}
|
|
176
|
+
/** Store an entry (best-effort; never throws into the run). */
|
|
177
|
+
put(entry) {
|
|
178
|
+
if (!isValidKey(entry.key))
|
|
179
|
+
return;
|
|
180
|
+
try {
|
|
181
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
182
|
+
const lock = path.join(this.dir, `${entry.key}.json.lock`);
|
|
183
|
+
withLock(lock, () => {
|
|
184
|
+
writeFileAtomic(entryPath(this.dir, entry.key), JSON.stringify(entry, null, 2));
|
|
185
|
+
});
|
|
186
|
+
this.cleanup();
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
/* cache write failures are non-fatal */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Remove all cache entries. Returns the number removed. */
|
|
193
|
+
clear() {
|
|
194
|
+
let n = 0;
|
|
195
|
+
try {
|
|
196
|
+
for (const f of fs.readdirSync(this.dir)) {
|
|
197
|
+
if (f.endsWith(".json")) {
|
|
198
|
+
try {
|
|
199
|
+
fs.unlinkSync(path.join(this.dir, f));
|
|
200
|
+
n++;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
/* ignore */
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
/* no dir → nothing to clear */
|
|
210
|
+
}
|
|
211
|
+
return n;
|
|
212
|
+
}
|
|
213
|
+
/** Opportunistic eviction: drop expired/oversized entries. Best-effort. */
|
|
214
|
+
cleanup() {
|
|
215
|
+
let files;
|
|
216
|
+
try {
|
|
217
|
+
files = fs.readdirSync(this.dir).filter((f) => f.endsWith(".json"));
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
const live = [];
|
|
224
|
+
for (const f of files) {
|
|
225
|
+
const abs = path.join(this.dir, f);
|
|
226
|
+
try {
|
|
227
|
+
const e = JSON.parse(fs.readFileSync(abs, "utf-8"));
|
|
228
|
+
if (typeof e?.createdAt !== "number" || now - e.createdAt > DEFAULT_MAX_AGE_MS) {
|
|
229
|
+
fs.unlinkSync(abs);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
live.push({ file: abs, createdAt: e.createdAt });
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
try {
|
|
236
|
+
fs.unlinkSync(abs);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
/* ignore */
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (live.length > DEFAULT_MAX_ENTRIES) {
|
|
244
|
+
live.sort((a, b) => a.createdAt - b.createdAt); // oldest first
|
|
245
|
+
for (const victim of live.slice(0, live.length - DEFAULT_MAX_ENTRIES)) {
|
|
246
|
+
try {
|
|
247
|
+
fs.unlinkSync(victim.file);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
/* ignore */
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=cache.js.map
|