@agimon-ai/po-team 0.0.0-alpha.2 → 0.0.0-alpha.21
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/LICENSE +166 -5
- package/README.md +11 -12
- package/dist/{agentDiscovery-Whwzc4dP.cjs → agentDiscovery-DTTlM_LT.cjs} +246 -350
- package/dist/agentDiscovery-DTTlM_LT.cjs.map +1 -0
- package/dist/{agentDiscovery-ZQlFkKvB.mjs → agentDiscovery-ZNG8iZXV.mjs} +210 -279
- package/dist/agentDiscovery-ZNG8iZXV.mjs.map +1 -0
- package/dist/apiContracts.cjs +10 -3
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.d.mts.map +1 -1
- package/dist/apiContracts.mjs +10 -3
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/{externalProcessIpc-ojE0pvw0.cjs → atomicJson-DRT03Ql5.cjs} +155 -21
- package/dist/atomicJson-DRT03Ql5.cjs.map +1 -0
- package/dist/{externalProcessIpc-C-15lJrB.mjs → atomicJson-VVM1iAPu.mjs} +140 -12
- package/dist/atomicJson-VVM1iAPu.mjs.map +1 -0
- package/dist/{capabilityCeiling-CF7kAS18.mjs → capabilityCeiling-BaHT9v9S.mjs} +3 -3
- package/dist/capabilityCeiling-BaHT9v9S.mjs.map +1 -0
- package/dist/{capabilityCeiling-C-Y7H8oC.cjs → capabilityCeiling-CO3ZjYmY.cjs} +3 -3
- package/dist/capabilityCeiling-CO3ZjYmY.cjs.map +1 -0
- package/dist/capabilityCeiling.cjs +2 -2
- package/dist/capabilityCeiling.d.mts +2 -2
- package/dist/capabilityCeiling.mjs +2 -2
- package/dist/delegation.cjs +1 -1
- package/dist/delegation.cjs.map +1 -1
- package/dist/delegation.d.mts +1 -1
- package/dist/delegation.mjs +1 -1
- package/dist/delegation.mjs.map +1 -1
- package/dist/delegationApi.cjs +30 -30
- package/dist/delegationApi.d.mts +2 -2
- package/dist/delegationApi.mjs +1 -2
- package/dist/env.cjs +1 -1
- package/dist/env.d.mts +1 -1
- package/dist/env.mjs +1 -2
- package/dist/{environment-DrqhrxG_.mjs → environment-BtvJI2S-.mjs} +3 -3
- package/dist/environment-BtvJI2S-.mjs.map +1 -0
- package/dist/{environment-ma5oQ7uM.d.mts → environment-Bz1OlWIT.d.mts} +2 -2
- package/dist/{environment-ma5oQ7uM.d.mts.map → environment-Bz1OlWIT.d.mts.map} +1 -1
- package/dist/{environment-CaCW3SgT.cjs → environment-Cqs7vHJL.cjs} +3 -3
- package/dist/environment-Cqs7vHJL.cjs.map +1 -0
- package/dist/extensions/native.mjs +34 -12
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +990 -786
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts +2 -2
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +975 -772
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +994 -807
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/fablePlan.cjs +3 -3
- package/dist/fablePlan.d.mts +2 -2
- package/dist/fablePlan.mjs +1 -2
- package/dist/{fablePlanApi-CZ3uUcMi.d.mts → fablePlanApi-BTNKjLni.d.mts} +6 -6
- package/dist/{fablePlanApi-CZ3uUcMi.d.mts.map → fablePlanApi-BTNKjLni.d.mts.map} +1 -1
- package/dist/{fablePlanApi-wBu9bRXU.cjs → fablePlanApi-BhI4jnDP.cjs} +12 -12
- package/dist/fablePlanApi-BhI4jnDP.cjs.map +1 -0
- package/dist/{fablePlanApi-BsHhgVFV.mjs → fablePlanApi-DuMg43Hu.mjs} +5 -5
- package/dist/fablePlanApi-DuMg43Hu.mjs.map +1 -0
- package/dist/fablePlanApi.cjs +3 -3
- package/dist/fablePlanApi.d.mts +2 -2
- package/dist/fablePlanApi.mjs +1 -2
- package/dist/index.cjs +17 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +15 -5
- package/dist/index.mjs.map +1 -1
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/runs/background/cliRunnerEntry.cjs +26 -25
- package/dist/runs/background/cliRunnerEntry.cjs.map +1 -1
- package/dist/runs/background/cliRunnerEntry.mjs +14 -14
- package/dist/runs/background/cliRunnerEntry.mjs.map +1 -1
- package/dist/runtimeSubagentPolicy.cjs +3 -3
- package/dist/runtimeSubagentPolicy.d.mts +2 -2
- package/dist/runtimeSubagentPolicy.mjs +1 -2
- package/dist/runtimeSubagentTool.cjs +7 -2
- package/dist/runtimeSubagentTool.d.mts +1 -1
- package/dist/runtimeSubagentTool.mjs +1 -2
- package/dist/{subagentPolicy-ALJam4GN.d.mts → subagentPolicy-BE-OZoa-.d.mts} +6 -6
- package/dist/subagentPolicy-BE-OZoa-.d.mts.map +1 -0
- package/dist/{subagentPolicy-DQRyKDH-.cjs → subagentPolicy-DIpaKHO8.cjs} +8 -8
- package/dist/subagentPolicy-DIpaKHO8.cjs.map +1 -0
- package/dist/{subagentPolicy-DxjsKeyj.mjs → subagentPolicy-LfcXuqO5.mjs} +5 -5
- package/dist/subagentPolicy-LfcXuqO5.mjs.map +1 -0
- package/dist/subagentPolicy.cjs +3 -3
- package/dist/subagentPolicy.d.mts +2 -2
- package/dist/subagentPolicy.mjs +1 -2
- package/dist/{subagentTool-Ca2zQXKw.cjs → subagentTool-BueNPGHo.cjs} +5 -16
- package/dist/subagentTool-BueNPGHo.cjs.map +1 -0
- package/dist/{subagentTool-BTssGLw8.d.mts → subagentTool-UdB3s6Mn.d.mts} +2 -11
- package/dist/subagentTool-UdB3s6Mn.d.mts.map +1 -0
- package/dist/{subagentTool-WOYrjxgT.mjs → subagentTool-a2oh2v5B.mjs} +5 -16
- package/dist/subagentTool-a2oh2v5B.mjs.map +1 -0
- package/dist/subagentTool.cjs +7 -2
- package/dist/subagentTool.d.mts +1 -1
- package/dist/subagentTool.mjs +1 -2
- package/dist/teamSnapshot.d.mts +1 -0
- package/dist/teamSnapshot.d.mts.map +1 -1
- package/dist/teamSnapshot.mjs +1 -2
- package/package.json +25 -24
- package/dist/agentDiscovery-Whwzc4dP.cjs.map +0 -1
- package/dist/agentDiscovery-ZQlFkKvB.mjs.map +0 -1
- package/dist/atomicJson-D0346bt5.cjs +0 -178
- package/dist/atomicJson-D0346bt5.cjs.map +0 -1
- package/dist/atomicJson-D2t0CDh9.mjs +0 -137
- package/dist/atomicJson-D2t0CDh9.mjs.map +0 -1
- package/dist/capabilityCeiling-C-Y7H8oC.cjs.map +0 -1
- package/dist/capabilityCeiling-CF7kAS18.mjs.map +0 -1
- package/dist/environment-CaCW3SgT.cjs.map +0 -1
- package/dist/environment-DrqhrxG_.mjs.map +0 -1
- package/dist/externalProcessIpc-C-15lJrB.mjs.map +0 -1
- package/dist/externalProcessIpc-ojE0pvw0.cjs.map +0 -1
- package/dist/fablePlanApi-BsHhgVFV.mjs.map +0 -1
- package/dist/fablePlanApi-wBu9bRXU.cjs.map +0 -1
- package/dist/subagentPolicy-ALJam4GN.d.mts.map +0 -1
- package/dist/subagentPolicy-DQRyKDH-.cjs.map +0 -1
- package/dist/subagentPolicy-DxjsKeyj.mjs.map +0 -1
- package/dist/subagentTool-BTssGLw8.d.mts.map +0 -1
- package/dist/subagentTool-Ca2zQXKw.cjs.map +0 -1
- package/dist/subagentTool-WOYrjxgT.mjs.map +0 -1
|
@@ -1,88 +1,12 @@
|
|
|
1
|
-
const
|
|
2
|
-
let node_fs = require("node:fs");
|
|
3
|
-
node_fs = require_atomicJson.__toESM(node_fs, 1);
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
4
2
|
let node_path = require("node:path");
|
|
5
|
-
node_path =
|
|
6
|
-
let
|
|
3
|
+
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
4
|
+
let _agimon_ai_po_core_platform = require("@agimon-ai/po-core/platform");
|
|
5
|
+
let node_fs = require("node:fs");
|
|
6
|
+
node_fs = require_rolldown_runtime.__toESM(node_fs, 1);
|
|
7
7
|
let node_os = require("node:os");
|
|
8
|
-
node_os =
|
|
8
|
+
node_os = require_rolldown_runtime.__toESM(node_os, 1);
|
|
9
9
|
let _agimon_ai_po_core_piSettings = require("@agimon-ai/po-core/piSettings");
|
|
10
|
-
//#region src/schemas/team/packageConfig.ts
|
|
11
|
-
const PO_TEAM_PACKAGE = "@agimon-ai/po-team";
|
|
12
|
-
const TEAM_CONFIG_KEYS = ["models", "excludeTools"];
|
|
13
|
-
const TEAM_MODEL_KEYS = ["model", "thinking"];
|
|
14
|
-
const THINKING_VALUES = /* @__PURE__ */ new Set([
|
|
15
|
-
"off",
|
|
16
|
-
"minimal",
|
|
17
|
-
"low",
|
|
18
|
-
"medium",
|
|
19
|
-
"high",
|
|
20
|
-
"xhigh",
|
|
21
|
-
"max"
|
|
22
|
-
]);
|
|
23
|
-
function isRecord(value) {
|
|
24
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
-
}
|
|
26
|
-
function assertKnownKeys(value, keys, location) {
|
|
27
|
-
const unsupported = Object.keys(value).filter((key) => !keys.includes(key));
|
|
28
|
-
if (unsupported.length > 0) throw new Error(`${location} has unsupported field(s): ${unsupported.join(", ")}`);
|
|
29
|
-
}
|
|
30
|
-
/** Validate and normalize one Team-owned package configuration mapping. */
|
|
31
|
-
function parseTeamPackageConfig(value, location) {
|
|
32
|
-
if (!isRecord(value)) throw new Error(`${location} must be a mapping`);
|
|
33
|
-
assertKnownKeys(value, TEAM_CONFIG_KEYS, location);
|
|
34
|
-
const hasModels = Object.hasOwn(value, "models");
|
|
35
|
-
const hasExcludeTools = Object.hasOwn(value, "excludeTools");
|
|
36
|
-
if (!hasModels && !hasExcludeTools) throw new Error(`${location} must define models or excludeTools`);
|
|
37
|
-
let models;
|
|
38
|
-
if (hasModels) {
|
|
39
|
-
if (!Array.isArray(value.models) || value.models.length === 0) throw new Error(`${location}.models must be a non-empty array`);
|
|
40
|
-
models = value.models.map((entry, index) => {
|
|
41
|
-
const entryLocation = `${location}.models[${index}]`;
|
|
42
|
-
if (!isRecord(entry)) throw new Error(`${entryLocation} must be a mapping`);
|
|
43
|
-
assertKnownKeys(entry, TEAM_MODEL_KEYS, entryLocation);
|
|
44
|
-
if (typeof entry.model !== "string" || !entry.model.trim()) throw new Error(`${entryLocation}.model must be a non-empty string`);
|
|
45
|
-
if (entry.thinking !== void 0 && (typeof entry.thinking !== "string" || !THINKING_VALUES.has(entry.thinking))) throw new Error(`${entryLocation}.thinking must be one of: ${[...THINKING_VALUES].join(", ")}`);
|
|
46
|
-
return {
|
|
47
|
-
model: entry.model.trim(),
|
|
48
|
-
...entry.thinking ? { thinking: entry.thinking } : {}
|
|
49
|
-
};
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
let excludeTools;
|
|
53
|
-
if (hasExcludeTools) {
|
|
54
|
-
if (!Array.isArray(value.excludeTools) || value.excludeTools.length === 0) throw new Error(`${location}.excludeTools must be a non-empty array`);
|
|
55
|
-
excludeTools = [...new Set(value.excludeTools.map((entry, index) => {
|
|
56
|
-
if (typeof entry !== "string" || !entry.trim()) throw new Error(`${location}.excludeTools[${index}] must be a non-empty string`);
|
|
57
|
-
return entry.trim();
|
|
58
|
-
}))];
|
|
59
|
-
}
|
|
60
|
-
return {
|
|
61
|
-
...models ? { models } : {},
|
|
62
|
-
...excludeTools ? { excludeTools } : {}
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
/** Merge Team configurations contributed by selected layers in selection order. */
|
|
66
|
-
function mergeTeamPackageConfigurations(entries) {
|
|
67
|
-
let models;
|
|
68
|
-
const excludeTools = /* @__PURE__ */ new Set();
|
|
69
|
-
let hasExcludeTools = false;
|
|
70
|
-
for (const entry of entries) {
|
|
71
|
-
const location = `Package "${entry.specifier}" config in layer "${entry.layer}" of .po/modes.yaml`;
|
|
72
|
-
const next = parseTeamPackageConfig(entry.config, location);
|
|
73
|
-
if (next.models) models = next.models.map((model) => ({ ...model }));
|
|
74
|
-
if (next.excludeTools) {
|
|
75
|
-
hasExcludeTools = true;
|
|
76
|
-
for (const tool of next.excludeTools) excludeTools.add(tool);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (!models && !hasExcludeTools) return void 0;
|
|
80
|
-
return {
|
|
81
|
-
...models ? { models } : {},
|
|
82
|
-
...hasExcludeTools ? { excludeTools: [...excludeTools] } : {}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
10
|
//#region src/services/agentFrontmatter/index.ts
|
|
87
11
|
/**
|
|
88
12
|
* Parsing the YAML frontmatter block at the head of an agent or chain file.
|
|
@@ -312,29 +236,133 @@ function getConfigDirName(environment = process.env) {
|
|
|
312
236
|
function getProjectConfigDir(projectRoot, environment = process.env) {
|
|
313
237
|
return node_path.join(projectRoot, getConfigDirName(environment));
|
|
314
238
|
}
|
|
315
|
-
/**
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
239
|
+
/** The user-level agent directory Po runs with, from Po's own resolver. */
|
|
240
|
+
function getAgentDir(environment = process.env) {
|
|
241
|
+
return (0, _agimon_ai_po_core_piSettings.piAgentDirectory)(environment, environment.HOME ?? node_os.homedir());
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/services/agentProjectRoot/sync.ts
|
|
245
|
+
/** @deprecated Node-only shim. */
|
|
246
|
+
/**
|
|
247
|
+
* Locating the project root that agent configuration hangs off.
|
|
248
|
+
*
|
|
249
|
+
* A project root is any ancestor of the working directory that carries a config
|
|
250
|
+
* directory or a legacy `.agents` directory. There can be several: a package
|
|
251
|
+
* inside a monorepo and the monorepo itself both qualify. Which one wins is a
|
|
252
|
+
* setting, because both answers are legitimate. Teams that keep one shared set
|
|
253
|
+
* of agents at the repository root want `git-root`, teams that give each package
|
|
254
|
+
* its own want `nearest`, and neither can be inferred from the tree alone.
|
|
255
|
+
*
|
|
256
|
+
* DESIGN PATTERNS:
|
|
257
|
+
* - Candidates are collected once, nearest first, then a policy picks from them.
|
|
258
|
+
* Separating collection from policy is what lets `git-root` be answered
|
|
259
|
+
* without a second walk
|
|
260
|
+
* - Path derivation goes through `shared/configDir.ts`, so a rebranded build
|
|
261
|
+
* that renames its config directory needs no change here
|
|
262
|
+
*
|
|
263
|
+
* AVOID:
|
|
264
|
+
* - Hardcoding the config directory name; it is discovered, not assumed
|
|
265
|
+
* - Treating the nearest candidate as the project root without reading the
|
|
266
|
+
* setting first
|
|
267
|
+
*/
|
|
268
|
+
/** Stable cache identity for equivalent absolute, relative, and symlinked cwd spellings. */
|
|
269
|
+
function canonicalizeDiscoveryCwd$1(cwd) {
|
|
270
|
+
const resolved = node_path.resolve(cwd);
|
|
319
271
|
try {
|
|
320
|
-
|
|
321
|
-
} catch
|
|
322
|
-
|
|
323
|
-
throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });
|
|
272
|
+
return node_fs.realpathSync.native(resolved);
|
|
273
|
+
} catch {
|
|
274
|
+
return resolved;
|
|
324
275
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/services/agentMemory/index.ts
|
|
279
|
+
/**
|
|
280
|
+
* Per-agent persistent memory, injected into a child system prompt.
|
|
281
|
+
*
|
|
282
|
+
* An agent may opt into a durable, role-specific memory scope via its `memory`
|
|
283
|
+
* frontmatter. The opening lines of a `MEMORY.md` in the resolved directory are
|
|
284
|
+
* injected so a recurring custom agent can recall accumulated role notes:
|
|
285
|
+
* threat models, gotchas, verified commands, decisions. An agent with no write
|
|
286
|
+
* tools gets a read-only variant of the block instead.
|
|
287
|
+
*
|
|
288
|
+
* DESIGN PATTERNS:
|
|
289
|
+
* - The directory is derived from the agent's name, never from a user-supplied
|
|
290
|
+
* path, so no agent can name its way into another agent's notes
|
|
291
|
+
* - The file is opened once with O_NOFOLLOW and every subsequent check runs on
|
|
292
|
+
* that descriptor, so a symlink swapped in after the check cannot be followed
|
|
293
|
+
* - Reads are capped in both lines and bytes, and stop early. A memory file that
|
|
294
|
+
* grew unbounded must not be able to consume the child's whole context window
|
|
295
|
+
* - Every containment failure degrades to no injection. A prompt with no memory
|
|
296
|
+
* is correct; a prompt pointed at an unverified path is not
|
|
297
|
+
*
|
|
298
|
+
* WHY THE DIRECTORY IS NAMESPACED:
|
|
299
|
+
* Memory lives under a dedicated `agent-memory/` subdirectory so it can never
|
|
300
|
+
* collide with the coding agent's own `memory/{project}/` tree.
|
|
301
|
+
*
|
|
302
|
+
* AVOID:
|
|
303
|
+
* - Injecting memory contents without the boundary instruction; the file is data
|
|
304
|
+
* a previous run wrote, and a prompt-injection payload could have landed in it
|
|
305
|
+
* - Re-stat'ing by path between the open and the read
|
|
306
|
+
*/
|
|
307
|
+
/** Tools that let an agent append to its own memory file. */
|
|
308
|
+
const WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
309
|
+
"edit",
|
|
310
|
+
"write",
|
|
311
|
+
"bash"
|
|
312
|
+
]);
|
|
313
|
+
function unquoteFrontmatterValue(value) {
|
|
314
|
+
const trimmed = value.trim();
|
|
315
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
316
|
+
return trimmed;
|
|
317
|
+
}
|
|
318
|
+
function parseMemoryScope(value) {
|
|
319
|
+
return value === "project" || value === "user" ? value : void 0;
|
|
320
|
+
}
|
|
321
|
+
function parseMemoryBoolean(value) {
|
|
322
|
+
if (value === "true") return true;
|
|
323
|
+
if (value === "false") return false;
|
|
324
|
+
}
|
|
325
|
+
function parsePositiveInteger(value) {
|
|
326
|
+
if (value === void 0) return void 0;
|
|
327
|
+
const parsed = Number(value);
|
|
328
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
329
|
+
}
|
|
330
|
+
/** Split a `memory` frontmatter block into key/value pairs, either spelling. */
|
|
331
|
+
function readMemoryEntries(raw) {
|
|
332
|
+
const entries = /* @__PURE__ */ new Map();
|
|
333
|
+
const inlineObject = raw.trim().match(/^\{(.*)\}$/s);
|
|
334
|
+
const parts = inlineObject?.[1] !== void 0 ? inlineObject[1].split(",") : raw.split("\n");
|
|
335
|
+
for (const part of parts) {
|
|
336
|
+
const match = part.trim().match(/^([\w-]+)\s*:\s*(.*)$/);
|
|
337
|
+
if (!match?.[1] || match[2] === void 0) continue;
|
|
338
|
+
entries.set(match[1], unquoteFrontmatterValue(match[2]));
|
|
331
339
|
}
|
|
332
|
-
|
|
333
|
-
return parsed;
|
|
340
|
+
return entries;
|
|
334
341
|
}
|
|
335
|
-
/**
|
|
336
|
-
|
|
337
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Parse a `memory` frontmatter block, or undefined when it declares nothing.
|
|
344
|
+
*
|
|
345
|
+
* Accepts both the inline `{ scope: project }` form and the indented block form,
|
|
346
|
+
* because agent files in the wild use each.
|
|
347
|
+
*/
|
|
348
|
+
function parseMemoryFrontmatter(raw) {
|
|
349
|
+
if (!raw) return void 0;
|
|
350
|
+
const entries = readMemoryEntries(raw);
|
|
351
|
+
const enabled = parseMemoryBoolean(entries.get("enabled"));
|
|
352
|
+
const scope = parseMemoryScope(entries.get("scope"));
|
|
353
|
+
const maxEntries = parsePositiveInteger(entries.get("maxEntries"));
|
|
354
|
+
if (enabled === void 0 && scope === void 0 && maxEntries === void 0) return void 0;
|
|
355
|
+
const config = {};
|
|
356
|
+
if (enabled !== void 0) config.enabled = enabled;
|
|
357
|
+
if (scope !== void 0) config.scope = scope;
|
|
358
|
+
if (maxEntries !== void 0) config.maxEntries = maxEntries;
|
|
359
|
+
return config;
|
|
360
|
+
}
|
|
361
|
+
/** Whether an agent can write files this run. An unset `tools` inherits the builtins. */
|
|
362
|
+
function agentHasWriteTools(agent) {
|
|
363
|
+
const tools = agent.tools;
|
|
364
|
+
if (!tools) return true;
|
|
365
|
+
return tools.some((tool) => WRITE_TOOLS.has(tool));
|
|
338
366
|
}
|
|
339
367
|
//#endregion
|
|
340
368
|
//#region src/services/agentProjectRoot/index.ts
|
|
@@ -352,7 +380,7 @@ function getAgentDir(environment = process.env) {
|
|
|
352
380
|
* - Candidates are collected once, nearest first, then a policy picks from them.
|
|
353
381
|
* Separating collection from policy is what lets `git-root` be answered
|
|
354
382
|
* without a second walk
|
|
355
|
-
* - Path derivation
|
|
383
|
+
* - Path derivation uses injected directory settings, so a rebranded build
|
|
356
384
|
* that renames its config directory needs no change here
|
|
357
385
|
*
|
|
358
386
|
* AVOID:
|
|
@@ -360,6 +388,25 @@ function getAgentDir(environment = process.env) {
|
|
|
360
388
|
* - Treating the nearest candidate as the project root without reading the
|
|
361
389
|
* setting first
|
|
362
390
|
*/
|
|
391
|
+
async function readSettingsFile(io, filePath) {
|
|
392
|
+
let raw;
|
|
393
|
+
try {
|
|
394
|
+
raw = await (0, _agimon_ai_po_core_platform.readTextIfExists)(io.fs, filePath, io.context);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
397
|
+
throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error });
|
|
398
|
+
}
|
|
399
|
+
if (raw === void 0) return {};
|
|
400
|
+
let parsed;
|
|
401
|
+
try {
|
|
402
|
+
parsed = JSON.parse(raw);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
405
|
+
throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error });
|
|
406
|
+
}
|
|
407
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`Settings file '${filePath}' must contain a JSON object.`);
|
|
408
|
+
return parsed;
|
|
409
|
+
}
|
|
363
410
|
const SETTINGS_FILE_NAME = "settings.json";
|
|
364
411
|
const GIT_DIR_NAME$1 = ".git";
|
|
365
412
|
/**
|
|
@@ -373,32 +420,33 @@ const AGENTS_DIR_NAME = "agents";
|
|
|
373
420
|
const SUBAGENTS_SETTINGS_KEY$1 = "subagents";
|
|
374
421
|
const PROJECT_ROOT_RESOLUTION_KEY = "projectRootResolution";
|
|
375
422
|
/** Stable cache identity for equivalent absolute, relative, and symlinked cwd spellings. */
|
|
376
|
-
function canonicalizeDiscoveryCwd(cwd) {
|
|
423
|
+
async function canonicalizeDiscoveryCwd(io, cwd) {
|
|
377
424
|
const resolved = node_path.resolve(cwd);
|
|
378
425
|
try {
|
|
379
|
-
return
|
|
426
|
+
return (0, _agimon_ai_po_core_platform.getOrThrow)(await io.fs.canonicalPath(resolved, io.context));
|
|
380
427
|
} catch {
|
|
381
428
|
return resolved;
|
|
382
429
|
}
|
|
383
430
|
}
|
|
384
431
|
/** Whether a path exists and is a directory. Any failure reads as "no". */
|
|
385
|
-
function isDirectory
|
|
432
|
+
async function isDirectory(io, dirPath) {
|
|
386
433
|
try {
|
|
387
|
-
|
|
434
|
+
const canonical = (0, _agimon_ai_po_core_platform.getOrThrow)(await io.fs.canonicalPath(dirPath, io.context));
|
|
435
|
+
return (0, _agimon_ai_po_core_platform.getOrThrow)(await io.fs.fileInfo(canonical, io.context)).kind === "directory";
|
|
388
436
|
} catch {
|
|
389
437
|
return false;
|
|
390
438
|
}
|
|
391
439
|
}
|
|
392
440
|
/** True when `dir` carries either marker that makes it a project root. */
|
|
393
|
-
function isProjectRootCandidate(
|
|
394
|
-
return isDirectory
|
|
441
|
+
async function isProjectRootCandidate(io, dir) {
|
|
442
|
+
return await isDirectory(io, node_path.join(dir, io.configDirName)) || await isDirectory(io, node_path.join(dir, DOT_AGENTS_DIR_NAME));
|
|
395
443
|
}
|
|
396
444
|
/** Every project root above `cwd`, nearest first. */
|
|
397
|
-
function findProjectRootCandidates(
|
|
445
|
+
async function findProjectRootCandidates(io, cwd) {
|
|
398
446
|
const roots = [];
|
|
399
447
|
let currentDir = cwd;
|
|
400
448
|
while (true) {
|
|
401
|
-
if (isProjectRootCandidate(
|
|
449
|
+
if (await isProjectRootCandidate(io, currentDir)) roots.push(currentDir);
|
|
402
450
|
const parentDir = node_path.dirname(currentDir);
|
|
403
451
|
if (parentDir === currentDir) return roots;
|
|
404
452
|
currentDir = parentDir;
|
|
@@ -410,10 +458,11 @@ function findProjectRootCandidates(cwd, environment = process.env) {
|
|
|
410
458
|
* Existence is enough: a worktree or submodule has a `.git` file rather than a
|
|
411
459
|
* directory, and both are real repository roots.
|
|
412
460
|
*/
|
|
413
|
-
function findNearestGitRoot(cwd) {
|
|
461
|
+
async function findNearestGitRoot(io, cwd) {
|
|
414
462
|
let currentDir = cwd;
|
|
415
463
|
while (true) {
|
|
416
|
-
|
|
464
|
+
const gitMarker = await io.fs.exists(node_path.join(currentDir, GIT_DIR_NAME$1), io.context);
|
|
465
|
+
if (gitMarker.ok && gitMarker.value) return currentDir;
|
|
417
466
|
const parentDir = node_path.dirname(currentDir);
|
|
418
467
|
if (parentDir === currentDir) return null;
|
|
419
468
|
currentDir = parentDir;
|
|
@@ -425,10 +474,9 @@ function findNearestGitRoot(cwd) {
|
|
|
425
474
|
* Deliberately throws on an unrecognised value rather than falling back: a typo
|
|
426
475
|
* here silently changes which agents the whole session sees.
|
|
427
476
|
*/
|
|
428
|
-
function readProjectRootResolution(
|
|
429
|
-
const settingsPath = node_path.join(
|
|
430
|
-
|
|
431
|
-
const subagents = readSettingsFileStrict(settingsPath)[SUBAGENTS_SETTINGS_KEY$1];
|
|
477
|
+
async function readProjectRootResolution(io, projectRoot) {
|
|
478
|
+
const settingsPath = node_path.join(projectRoot, io.configDirName, SETTINGS_FILE_NAME);
|
|
479
|
+
const subagents = (await readSettingsFile(io, settingsPath))[SUBAGENTS_SETTINGS_KEY$1];
|
|
432
480
|
if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return void 0;
|
|
433
481
|
const value = subagents[PROJECT_ROOT_RESOLUTION_KEY];
|
|
434
482
|
if (value === void 0) return void 0;
|
|
@@ -442,15 +490,15 @@ function readProjectRootResolution(projectRoot, environment = process.env) {
|
|
|
442
490
|
* of its monorepo's shared agents, and from the git root second so a repository
|
|
443
491
|
* can claim its packages without editing each one.
|
|
444
492
|
*/
|
|
445
|
-
function findConfiguredProjectRoot(
|
|
446
|
-
const candidates = findProjectRootCandidates(
|
|
493
|
+
async function findConfiguredProjectRoot(io, cwd) {
|
|
494
|
+
const candidates = await findProjectRootCandidates(io, cwd);
|
|
447
495
|
const nearestRoot = candidates[0];
|
|
448
496
|
if (!nearestRoot) return null;
|
|
449
|
-
const nearestMode = readProjectRootResolution(
|
|
497
|
+
const nearestMode = await readProjectRootResolution(io, nearestRoot);
|
|
450
498
|
if (nearestMode === "nearest") return nearestRoot;
|
|
451
|
-
const gitRoot = findNearestGitRoot(cwd);
|
|
499
|
+
const gitRoot = await findNearestGitRoot(io, cwd);
|
|
452
500
|
const gitProjectRoot = gitRoot ? candidates.find((candidate) => node_path.resolve(candidate) === node_path.resolve(gitRoot)) : void 0;
|
|
453
|
-
if (gitProjectRoot && (nearestMode === "git-root" || readProjectRootResolution(
|
|
501
|
+
if (gitProjectRoot && (nearestMode === "git-root" || await readProjectRootResolution(io, gitProjectRoot) === "git-root")) return gitProjectRoot;
|
|
454
502
|
return nearestRoot;
|
|
455
503
|
}
|
|
456
504
|
/**
|
|
@@ -461,17 +509,17 @@ function findConfiguredProjectRoot(cwd, environment = process.env) {
|
|
|
461
509
|
* working after the move. Returned unfiltered because the caller reads each
|
|
462
510
|
* directory anyway and a missing one costs nothing.
|
|
463
511
|
*/
|
|
464
|
-
function userAgentDirs(
|
|
465
|
-
return [node_path.join(
|
|
512
|
+
function userAgentDirs(io) {
|
|
513
|
+
return [node_path.join((0, _agimon_ai_po_core_piSettings.piAgentDirectory)(io.environment, io.homeDirectory), AGENTS_DIR_NAME), node_path.join(io.homeDirectory, DOT_AGENTS_DIR_NAME)];
|
|
466
514
|
}
|
|
467
515
|
/** The user-level settings file. Always resolvable, unlike the project one. */
|
|
468
|
-
function getUserAgentSettingsPath(
|
|
469
|
-
return node_path.join(
|
|
516
|
+
function getUserAgentSettingsPath(io) {
|
|
517
|
+
return node_path.join((0, _agimon_ai_po_core_piSettings.piAgentDirectory)(io.environment, io.homeDirectory), SETTINGS_FILE_NAME);
|
|
470
518
|
}
|
|
471
519
|
/** The project settings file, or null when `cwd` sits under no project root. */
|
|
472
|
-
function getProjectAgentSettingsPath(
|
|
473
|
-
const projectRoot = findConfiguredProjectRoot(
|
|
474
|
-
return projectRoot ? node_path.join(
|
|
520
|
+
async function getProjectAgentSettingsPath(io, cwd) {
|
|
521
|
+
const projectRoot = await findConfiguredProjectRoot(io, cwd);
|
|
522
|
+
return projectRoot ? node_path.join(projectRoot, io.configDirName, SETTINGS_FILE_NAME) : null;
|
|
475
523
|
}
|
|
476
524
|
/**
|
|
477
525
|
* Project agent directories for `cwd`.
|
|
@@ -481,144 +529,24 @@ function getProjectAgentSettingsPath(cwd, environment = process.env) {
|
|
|
481
529
|
* `preferredDir` is where a newly created agent should be written, and is
|
|
482
530
|
* returned whether or not it exists yet.
|
|
483
531
|
*/
|
|
484
|
-
function resolveNearestProjectAgentDirs(
|
|
485
|
-
const projectRoot = findConfiguredProjectRoot(
|
|
532
|
+
async function resolveNearestProjectAgentDirs(io, cwd) {
|
|
533
|
+
const projectRoot = await findConfiguredProjectRoot(io, cwd);
|
|
486
534
|
if (!projectRoot) return {
|
|
487
535
|
readDirs: [],
|
|
488
536
|
preferredDir: null
|
|
489
537
|
};
|
|
490
538
|
const legacyDir = node_path.join(projectRoot, DOT_AGENTS_DIR_NAME);
|
|
491
|
-
const preferredDir = node_path.join(
|
|
539
|
+
const preferredDir = node_path.join(projectRoot, io.configDirName, AGENTS_DIR_NAME);
|
|
492
540
|
const readDirs = [];
|
|
493
|
-
if (isDirectory
|
|
494
|
-
if (isDirectory
|
|
541
|
+
if (await isDirectory(io, legacyDir)) readDirs.push(legacyDir);
|
|
542
|
+
if (await isDirectory(io, preferredDir)) readDirs.push(preferredDir);
|
|
495
543
|
return {
|
|
496
544
|
readDirs,
|
|
497
545
|
preferredDir
|
|
498
546
|
};
|
|
499
547
|
}
|
|
500
548
|
//#endregion
|
|
501
|
-
//#region src/services/agentMemory/index.ts
|
|
502
|
-
/**
|
|
503
|
-
* Per-agent persistent memory, injected into a child system prompt.
|
|
504
|
-
*
|
|
505
|
-
* An agent may opt into a durable, role-specific memory scope via its `memory`
|
|
506
|
-
* frontmatter. The opening lines of a `MEMORY.md` in the resolved directory are
|
|
507
|
-
* injected so a recurring custom agent can recall accumulated role notes:
|
|
508
|
-
* threat models, gotchas, verified commands, decisions. An agent with no write
|
|
509
|
-
* tools gets a read-only variant of the block instead.
|
|
510
|
-
*
|
|
511
|
-
* DESIGN PATTERNS:
|
|
512
|
-
* - The directory is derived from the agent's name, never from a user-supplied
|
|
513
|
-
* path, so no agent can name its way into another agent's notes
|
|
514
|
-
* - The file is opened once with O_NOFOLLOW and every subsequent check runs on
|
|
515
|
-
* that descriptor, so a symlink swapped in after the check cannot be followed
|
|
516
|
-
* - Reads are capped in both lines and bytes, and stop early. A memory file that
|
|
517
|
-
* grew unbounded must not be able to consume the child's whole context window
|
|
518
|
-
* - Every containment failure degrades to no injection. A prompt with no memory
|
|
519
|
-
* is correct; a prompt pointed at an unverified path is not
|
|
520
|
-
*
|
|
521
|
-
* WHY THE DIRECTORY IS NAMESPACED:
|
|
522
|
-
* Memory lives under a dedicated `agent-memory/` subdirectory so it can never
|
|
523
|
-
* collide with the coding agent's own `memory/{project}/` tree.
|
|
524
|
-
*
|
|
525
|
-
* AVOID:
|
|
526
|
-
* - Injecting memory contents without the boundary instruction; the file is data
|
|
527
|
-
* a previous run wrote, and a prompt-injection payload could have landed in it
|
|
528
|
-
* - Re-stat'ing by path between the open and the read
|
|
529
|
-
*/
|
|
530
|
-
/** Tools that let an agent append to its own memory file. */
|
|
531
|
-
const WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
532
|
-
"edit",
|
|
533
|
-
"write",
|
|
534
|
-
"bash"
|
|
535
|
-
]);
|
|
536
|
-
function unquoteFrontmatterValue(value) {
|
|
537
|
-
const trimmed = value.trim();
|
|
538
|
-
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
539
|
-
return trimmed;
|
|
540
|
-
}
|
|
541
|
-
function parseMemoryScope(value) {
|
|
542
|
-
return value === "project" || value === "user" ? value : void 0;
|
|
543
|
-
}
|
|
544
|
-
function parseMemoryBoolean(value) {
|
|
545
|
-
if (value === "true") return true;
|
|
546
|
-
if (value === "false") return false;
|
|
547
|
-
}
|
|
548
|
-
function parsePositiveInteger(value) {
|
|
549
|
-
if (value === void 0) return void 0;
|
|
550
|
-
const parsed = Number(value);
|
|
551
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
552
|
-
}
|
|
553
|
-
/** Split a `memory` frontmatter block into key/value pairs, either spelling. */
|
|
554
|
-
function readMemoryEntries(raw) {
|
|
555
|
-
const entries = /* @__PURE__ */ new Map();
|
|
556
|
-
const inlineObject = raw.trim().match(/^\{(.*)\}$/s);
|
|
557
|
-
const parts = inlineObject?.[1] !== void 0 ? inlineObject[1].split(",") : raw.split("\n");
|
|
558
|
-
for (const part of parts) {
|
|
559
|
-
const match = part.trim().match(/^([\w-]+)\s*:\s*(.*)$/);
|
|
560
|
-
if (!match?.[1] || match[2] === void 0) continue;
|
|
561
|
-
entries.set(match[1], unquoteFrontmatterValue(match[2]));
|
|
562
|
-
}
|
|
563
|
-
return entries;
|
|
564
|
-
}
|
|
565
|
-
/**
|
|
566
|
-
* Parse a `memory` frontmatter block, or undefined when it declares nothing.
|
|
567
|
-
*
|
|
568
|
-
* Accepts both the inline `{ scope: project }` form and the indented block form,
|
|
569
|
-
* because agent files in the wild use each.
|
|
570
|
-
*/
|
|
571
|
-
function parseMemoryFrontmatter(raw) {
|
|
572
|
-
if (!raw) return void 0;
|
|
573
|
-
const entries = readMemoryEntries(raw);
|
|
574
|
-
const enabled = parseMemoryBoolean(entries.get("enabled"));
|
|
575
|
-
const scope = parseMemoryScope(entries.get("scope"));
|
|
576
|
-
const maxEntries = parsePositiveInteger(entries.get("maxEntries"));
|
|
577
|
-
if (enabled === void 0 && scope === void 0 && maxEntries === void 0) return void 0;
|
|
578
|
-
const config = {};
|
|
579
|
-
if (enabled !== void 0) config.enabled = enabled;
|
|
580
|
-
if (scope !== void 0) config.scope = scope;
|
|
581
|
-
if (maxEntries !== void 0) config.maxEntries = maxEntries;
|
|
582
|
-
return config;
|
|
583
|
-
}
|
|
584
|
-
/** Whether an agent can write files this run. An unset `tools` inherits the builtins. */
|
|
585
|
-
function agentHasWriteTools(agent) {
|
|
586
|
-
const tools = agent.tools;
|
|
587
|
-
if (!tools) return true;
|
|
588
|
-
return tools.some((tool) => WRITE_TOOLS.has(tool));
|
|
589
|
-
}
|
|
590
|
-
//#endregion
|
|
591
549
|
//#region src/services/agentSettings/index.ts
|
|
592
|
-
/**
|
|
593
|
-
* Agent settings: reading them, and applying what they say to discovered agents.
|
|
594
|
-
*
|
|
595
|
-
* Two things live here because they are the same contract seen from both ends.
|
|
596
|
-
* Reading turns untyped JSON on disk into settings; applying folds those
|
|
597
|
-
* settings over the agents discovery found. The precedence is fixed: project
|
|
598
|
-
* settings beat user settings, an explicit per-agent override beats a bulk
|
|
599
|
-
* switch, and for a custom agent the agent's own frontmatter beats both.
|
|
600
|
-
*
|
|
601
|
-
* That last rule is why builtins and custom agents take different paths.
|
|
602
|
-
* Overriding a builtin replaces values outright, because a builtin has no file
|
|
603
|
-
* the user could have edited instead. Overriding a custom agent only fills
|
|
604
|
-
* fields its frontmatter left unset, so a settings default never silently
|
|
605
|
-
* contradicts what the author wrote in the file.
|
|
606
|
-
*
|
|
607
|
-
* DESIGN PATTERNS:
|
|
608
|
-
* - Reading is strict and throws with the offending path and field. A settings
|
|
609
|
-
* file that is quietly half-ignored is worse than one that refuses to load,
|
|
610
|
-
* because the user sees agents behaving in ways their config does not explain
|
|
611
|
-
* - Applying is pure and copy-on-write. Every `apply*` returns new configs and
|
|
612
|
-
* never mutates its input, so a cached discovery result cannot be corrupted
|
|
613
|
-
* by a later pass over it
|
|
614
|
-
* - Overriding a builtin snapshots the values it replaced into `override.base`,
|
|
615
|
-
* which is what makes removing the override restore the builtin exactly
|
|
616
|
-
*
|
|
617
|
-
* AVOID:
|
|
618
|
-
* - Trusting any shape read off disk without a real guard
|
|
619
|
-
* - Mutating an `AgentConfig` in place
|
|
620
|
-
* - Writing a settings file non-atomically; it holds the user's overrides
|
|
621
|
-
*/
|
|
622
550
|
const SUBAGENTS_SETTINGS_KEY = "subagents";
|
|
623
551
|
const AGENT_OVERRIDES_KEY = "agentOverrides";
|
|
624
552
|
/** Prefix marking a tool the child calls on an MCP server directly. */
|
|
@@ -809,9 +737,9 @@ function parseToolBudgetOverride(value, name, filePath) {
|
|
|
809
737
|
return budget;
|
|
810
738
|
}
|
|
811
739
|
/** Read and validate one settings file's `subagents` block. */
|
|
812
|
-
function readSubagentSettings(filePath) {
|
|
740
|
+
async function readSubagentSettings(io, filePath) {
|
|
813
741
|
if (!filePath) return EMPTY_SUBAGENT_SETTINGS;
|
|
814
|
-
const subagentsObject = readObjectField(
|
|
742
|
+
const subagentsObject = readObjectField(await readSettingsFile(io, filePath), SUBAGENTS_SETTINGS_KEY);
|
|
815
743
|
if (!subagentsObject) return EMPTY_SUBAGENT_SETTINGS;
|
|
816
744
|
let disableBuiltins;
|
|
817
745
|
if ("disableBuiltins" in subagentsObject) {
|
|
@@ -1177,16 +1105,9 @@ const LEGACY_AGENTS_DIR_NAME = ".agents";
|
|
|
1177
1105
|
const LEGACY_SKILLS_DIR_NAME = "skills";
|
|
1178
1106
|
const AGENT_FILE_EXTENSION = ".md";
|
|
1179
1107
|
const CHAIN_FILE_EXTENSION = ".chain.md";
|
|
1180
|
-
function isDirectory(target) {
|
|
1181
|
-
try {
|
|
1182
|
-
return node_fs.statSync(target).isDirectory();
|
|
1183
|
-
} catch {
|
|
1184
|
-
return false;
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
1108
|
/** A directory that owns its own agent configuration is a separate project. */
|
|
1188
|
-
function isDiscoveryNestedProjectRoot(
|
|
1189
|
-
return isDirectory(
|
|
1109
|
+
async function isDiscoveryNestedProjectRoot(io, dir) {
|
|
1110
|
+
return await isDirectory(io, node_path.join(dir, io.configDirName)) || await isDirectory(io, node_path.join(dir, LEGACY_AGENTS_DIR_NAME));
|
|
1190
1111
|
}
|
|
1191
1112
|
/**
|
|
1192
1113
|
* Decide whether the walk should skip a subdirectory.
|
|
@@ -1195,10 +1116,11 @@ function isDiscoveryNestedProjectRoot(dir, environment) {
|
|
|
1195
1116
|
* belong to that project, not to the one being scanned. The root itself is
|
|
1196
1117
|
* exempt: scanning a project's own agent directory must not prune it.
|
|
1197
1118
|
*/
|
|
1198
|
-
function shouldPruneDiscoveryDir(rootDir, dir, dirName
|
|
1119
|
+
async function shouldPruneDiscoveryDir(io, rootDir, dir, dirName) {
|
|
1199
1120
|
if (DISCOVERY_PRUNED_DIR_NAMES.has(dirName)) return true;
|
|
1200
|
-
|
|
1201
|
-
|
|
1121
|
+
const gitMarker = await io.fs.exists(node_path.join(dir, GIT_DIR_NAME), io.context);
|
|
1122
|
+
if (gitMarker.ok && gitMarker.value) return true;
|
|
1123
|
+
return node_path.resolve(dir) !== node_path.resolve(rootDir) && await isDiscoveryNestedProjectRoot(io, dir);
|
|
1202
1124
|
}
|
|
1203
1125
|
/**
|
|
1204
1126
|
* Collect matching files under `dir`, sorted so discovery order is stable.
|
|
@@ -1206,24 +1128,19 @@ function shouldPruneDiscoveryDir(rootDir, dir, dirName, environment) {
|
|
|
1206
1128
|
* Symlinked entries are followed as files, because staging an agent by symlink
|
|
1207
1129
|
* is a supported way to share one definition across projects.
|
|
1208
1130
|
*/
|
|
1209
|
-
function listFilesRecursive(dir, predicate, rootDir = dir
|
|
1131
|
+
async function listFilesRecursive(io, dir, predicate, rootDir = dir) {
|
|
1210
1132
|
const files = [];
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
entries = node_fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
1215
|
-
} catch {
|
|
1216
|
-
return files;
|
|
1217
|
-
}
|
|
1133
|
+
const result = await io.fs.listDir(dir, io.context);
|
|
1134
|
+
if (!result.ok) return files;
|
|
1135
|
+
const entries = result.value.sort((a, b) => a.name.localeCompare(b.name));
|
|
1218
1136
|
for (const entry of entries) {
|
|
1219
1137
|
const filePath = node_path.join(dir, entry.name);
|
|
1220
|
-
if (entry.
|
|
1221
|
-
if (!shouldPruneDiscoveryDir(rootDir, filePath, entry.name
|
|
1138
|
+
if (entry.kind === "directory") {
|
|
1139
|
+
if (!await shouldPruneDiscoveryDir(io, rootDir, filePath, entry.name)) files.push(...await listFilesRecursive(io, filePath, predicate, rootDir));
|
|
1222
1140
|
continue;
|
|
1223
1141
|
}
|
|
1224
|
-
if (
|
|
1225
|
-
if (
|
|
1226
|
-
files.push(filePath);
|
|
1142
|
+
if (entry.kind !== "file" && entry.kind !== "symlink") continue;
|
|
1143
|
+
if (predicate(entry.name)) files.push(filePath);
|
|
1227
1144
|
}
|
|
1228
1145
|
return files;
|
|
1229
1146
|
}
|
|
@@ -1267,13 +1184,15 @@ function isAgentFileName(fileName) {
|
|
|
1267
1184
|
* The returned configs carry only what the file itself declared. Settings
|
|
1268
1185
|
* defaults, builtin overrides, and cross-source precedence are the caller's job.
|
|
1269
1186
|
*/
|
|
1270
|
-
function loadAgentsFromDir(dir, source
|
|
1187
|
+
async function loadAgentsFromDir(io, dir, source) {
|
|
1271
1188
|
const agents = [];
|
|
1272
|
-
for (const filePath of listFilesRecursive(dir, isAgentFileName, dir
|
|
1189
|
+
for (const filePath of await listFilesRecursive(io, dir, isAgentFileName, dir)) {
|
|
1273
1190
|
if (isLegacyAgentSkillPath(dir, filePath)) continue;
|
|
1274
1191
|
let content;
|
|
1275
1192
|
try {
|
|
1276
|
-
|
|
1193
|
+
const text = await (0, _agimon_ai_po_core_platform.readTextIfExists)(io.fs, filePath, io.context);
|
|
1194
|
+
if (text === void 0) continue;
|
|
1195
|
+
content = text;
|
|
1277
1196
|
} catch {
|
|
1278
1197
|
continue;
|
|
1279
1198
|
}
|
|
@@ -1334,7 +1253,7 @@ function loadAgentsFromDir(dir, source, environment = process.env) {
|
|
|
1334
1253
|
* Read from the environment on every call rather than captured once, because a
|
|
1335
1254
|
* host can stage a plugin directory after this module is first imported.
|
|
1336
1255
|
*/
|
|
1337
|
-
function pluginAgentDirs(environment
|
|
1256
|
+
function pluginAgentDirs(environment) {
|
|
1338
1257
|
return (environment["PI_SUBAGENT_EXTRA_AGENT_DIRS"] ?? "").split(node_path.delimiter).map((directory) => directory.trim()).filter((directory) => directory.length > 0);
|
|
1339
1258
|
}
|
|
1340
1259
|
//#endregion
|
|
@@ -1442,8 +1361,6 @@ const MAX_CACHED_DISCOVERIES = 64;
|
|
|
1442
1361
|
const FINGERPRINT_FIELD_SEPARATOR = ":";
|
|
1443
1362
|
const FINGERPRINT_ENTRY_SEPARATOR = "|";
|
|
1444
1363
|
const MISSING_SOURCE_MARKER = "-";
|
|
1445
|
-
const MAJOR_MODES_CONFIG_RELATIVE_PATH = node_path.join(".po", "modes.yaml");
|
|
1446
|
-
const THINKING_SUFFIX = /:(?:off|minimal|low|medium|high|xhigh|max)$/;
|
|
1447
1364
|
/**
|
|
1448
1365
|
* Merge the sources into one list, later sources shadowing earlier ones.
|
|
1449
1366
|
*
|
|
@@ -1459,37 +1376,21 @@ function mergeAgentsForScope(scope, userAgents, projectAgents, pluginAgents = []
|
|
|
1459
1376
|
return [...byName.values()];
|
|
1460
1377
|
}
|
|
1461
1378
|
/** Size and mtime of one path, or a marker when it does not exist. */
|
|
1462
|
-
function stampOf(target) {
|
|
1379
|
+
async function stampOf(io, target) {
|
|
1463
1380
|
try {
|
|
1464
|
-
const
|
|
1381
|
+
const canonical = (0, _agimon_ai_po_core_platform.getOrThrow)(await io.fs.canonicalPath(target, io.context));
|
|
1382
|
+
const stat = (0, _agimon_ai_po_core_platform.getOrThrow)(await io.fs.fileInfo(canonical, io.context));
|
|
1465
1383
|
return `${target}${FINGERPRINT_FIELD_SEPARATOR}${stat.mtimeMs}${FINGERPRINT_FIELD_SEPARATOR}${stat.size}`;
|
|
1466
1384
|
} catch {
|
|
1467
1385
|
return `${target}${FINGERPRINT_FIELD_SEPARATOR}${MISSING_SOURCE_MARKER}`;
|
|
1468
1386
|
}
|
|
1469
1387
|
}
|
|
1470
|
-
/** Resolve Team-owned package configuration from the current parent harness state. */
|
|
1471
|
-
function resolveActiveTeamPackageConfig(environment = process.env) {
|
|
1472
|
-
const harness = (0, _agimon_ai_po_config.loadHarnessState)(environment).state;
|
|
1473
|
-
if (!harness.root) return void 0;
|
|
1474
|
-
const layers = (0, _agimon_ai_po_config.loadMajorModesConfig)(harness.root, environment.HOME);
|
|
1475
|
-
const config = mergeTeamPackageConfigurations((0, _agimon_ai_po_config.resolvePackageConfigurations)(layers, harness.layers, PO_TEAM_PACKAGE));
|
|
1476
|
-
return config ? {
|
|
1477
|
-
config,
|
|
1478
|
-
path: node_path.join(harness.root, MAJOR_MODES_CONFIG_RELATIVE_PATH)
|
|
1479
|
-
} : void 0;
|
|
1480
|
-
}
|
|
1481
|
-
function teamModelSpecs(config) {
|
|
1482
|
-
if (!config?.models) return void 0;
|
|
1483
|
-
return config.models.map(({ model, thinking }) => thinking ? `${model.replace(THINKING_SUFFIX, "")}:${thinking}` : model);
|
|
1484
|
-
}
|
|
1485
|
-
/** Resolve Team's ordered package model policy without merging it into an agent definition. */
|
|
1486
|
-
function resolveActiveTeamModelSpecs(environment = process.env) {
|
|
1487
|
-
return teamModelSpecs(resolveActiveTeamPackageConfig(environment)?.config);
|
|
1488
|
-
}
|
|
1489
1388
|
var AgentDiscoveryService = class {
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1389
|
+
io;
|
|
1390
|
+
agentConfig;
|
|
1391
|
+
constructor(io, agentConfig) {
|
|
1392
|
+
this.io = io;
|
|
1393
|
+
this.agentConfig = agentConfig;
|
|
1493
1394
|
}
|
|
1494
1395
|
cache = new LruCache(MAX_CACHED_DISCOVERIES);
|
|
1495
1396
|
/**
|
|
@@ -1502,23 +1403,25 @@ var AgentDiscoveryService = class {
|
|
|
1502
1403
|
now() {
|
|
1503
1404
|
return Date.now();
|
|
1504
1405
|
}
|
|
1505
|
-
discover(cwd, scope) {
|
|
1406
|
+
async discover(cwd, scope) {
|
|
1506
1407
|
const resolvedCwd = node_path.resolve(cwd);
|
|
1507
|
-
const canonicalCwd = canonicalizeDiscoveryCwd(resolvedCwd);
|
|
1408
|
+
const canonicalCwd = await canonicalizeDiscoveryCwd(this.io, resolvedCwd);
|
|
1508
1409
|
const key = `${scope}${FINGERPRINT_ENTRY_SEPARATOR}${canonicalCwd}`;
|
|
1509
1410
|
const cached = this.cache.get(key);
|
|
1510
1411
|
const timestamp = this.now();
|
|
1511
|
-
|
|
1512
|
-
|
|
1412
|
+
const team = await this.agentConfig?.currentOrUndefined();
|
|
1413
|
+
if (cached && cached.team === team && cached.expiresAt > timestamp && cached.fingerprint === await this.fingerprint(canonicalCwd)) return cloneResult(cached.result);
|
|
1414
|
+
const result = await this.load(resolvedCwd, scope, team);
|
|
1513
1415
|
this.cache.set(key, {
|
|
1514
1416
|
result,
|
|
1515
1417
|
expiresAt: timestamp + this.ttlMs,
|
|
1516
|
-
fingerprint: this.fingerprint(canonicalCwd)
|
|
1418
|
+
fingerprint: await this.fingerprint(canonicalCwd),
|
|
1419
|
+
team
|
|
1517
1420
|
});
|
|
1518
1421
|
return cloneResult(result);
|
|
1519
1422
|
}
|
|
1520
|
-
find(cwd, scope, name) {
|
|
1521
|
-
return this.discover(cwd, scope).agents.find((agent) => agent.name === name);
|
|
1423
|
+
async find(cwd, scope, name) {
|
|
1424
|
+
return (await this.discover(cwd, scope)).agents.find((agent) => agent.name === name);
|
|
1522
1425
|
}
|
|
1523
1426
|
invalidate() {
|
|
1524
1427
|
this.cache.clear();
|
|
@@ -1530,43 +1433,42 @@ var AgentDiscoveryService = class {
|
|
|
1530
1433
|
* from the same directory read overlapping sources, and computing one stamp
|
|
1531
1434
|
* keeps the stat count fixed regardless of how many scopes are queried.
|
|
1532
1435
|
*/
|
|
1533
|
-
fingerprint(cwd) {
|
|
1534
|
-
const harness = (0, _agimon_ai_po_config.loadHarnessState)(this.environment).state;
|
|
1436
|
+
async fingerprint(cwd) {
|
|
1535
1437
|
const sources = [
|
|
1536
|
-
getUserAgentSettingsPath(this.
|
|
1537
|
-
getProjectAgentSettingsPath(
|
|
1538
|
-
...userAgentDirs(this.
|
|
1539
|
-
...resolveNearestProjectAgentDirs(
|
|
1540
|
-
...pluginAgentDirs(this.environment)
|
|
1438
|
+
getUserAgentSettingsPath(this.io),
|
|
1439
|
+
await getProjectAgentSettingsPath(this.io, cwd) ?? MISSING_SOURCE_MARKER,
|
|
1440
|
+
...userAgentDirs(this.io),
|
|
1441
|
+
...(await resolveNearestProjectAgentDirs(this.io, cwd)).readDirs,
|
|
1442
|
+
...pluginAgentDirs(this.io.environment)
|
|
1541
1443
|
];
|
|
1542
|
-
|
|
1543
|
-
return [...sources.map(stampOf), `active-layers${FINGERPRINT_FIELD_SEPARATOR}${harness.layers.join(",")}`].join(FINGERPRINT_ENTRY_SEPARATOR);
|
|
1444
|
+
return (await Promise.all(sources.map((target) => stampOf(this.io, target)))).join(FINGERPRINT_ENTRY_SEPARATOR);
|
|
1544
1445
|
}
|
|
1545
1446
|
/**
|
|
1546
1447
|
* The uncached read. Protected so a test can count how often it actually runs,
|
|
1547
1448
|
* which is the only direct way to assert that the cache is doing its job.
|
|
1548
1449
|
*/
|
|
1549
|
-
load(cwd, scope) {
|
|
1550
|
-
const userSettingsPath = getUserAgentSettingsPath(this.
|
|
1551
|
-
const projectSettingsPath = getProjectAgentSettingsPath(
|
|
1552
|
-
const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath);
|
|
1553
|
-
const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath);
|
|
1450
|
+
async load(cwd, scope, team) {
|
|
1451
|
+
const userSettingsPath = getUserAgentSettingsPath(this.io);
|
|
1452
|
+
const projectSettingsPath = await getProjectAgentSettingsPath(this.io, cwd);
|
|
1453
|
+
const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : await readSubagentSettings(this.io, userSettingsPath);
|
|
1454
|
+
const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : await readSubagentSettings(this.io, projectSettingsPath);
|
|
1554
1455
|
const legacyDefaultModel = resolveSubagentDefaultModel(userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
1555
|
-
const
|
|
1556
|
-
const
|
|
1557
|
-
const defaultModel = packagePrimaryModel && teamPackage ? {
|
|
1456
|
+
const packagePrimaryModel = team?.models?.[0];
|
|
1457
|
+
const defaultModel = packagePrimaryModel && team ? {
|
|
1558
1458
|
type: "packages.team.defaultModel",
|
|
1559
1459
|
scope: "package",
|
|
1560
|
-
path:
|
|
1460
|
+
path: team.path ?? "",
|
|
1561
1461
|
model: packagePrimaryModel
|
|
1562
1462
|
} : legacyDefaultModel;
|
|
1563
1463
|
const defaultThinking = resolveSubagentDefaultThinking(userSettings, projectSettings, projectSettingsPath);
|
|
1564
1464
|
const defaultExtensions = resolveSubagentDefaultExtensions(userSettings, projectSettings, projectSettingsPath);
|
|
1565
1465
|
const resolve = (agents) => applyCustomAgentOverrides(applySubagentDefaults(agents, defaultModel, defaultThinking, defaultExtensions), userSettings, projectSettings, userSettingsPath, projectSettingsPath);
|
|
1566
|
-
const
|
|
1466
|
+
const projectDirs = await resolveNearestProjectAgentDirs(this.io, cwd);
|
|
1467
|
+
const loadDirs = async (dirs, source) => resolve((await Promise.all(dirs.map((dir) => loadAgentsFromDir(this.io, dir, source)))).flat());
|
|
1468
|
+
const plugin = await loadDirs(pluginAgentDirs(this.io.environment), "plugin");
|
|
1567
1469
|
return {
|
|
1568
|
-
agents: mergeAgentsForScope(scope, scope === "project" ? [] :
|
|
1569
|
-
projectAgentsDir:
|
|
1470
|
+
agents: mergeAgentsForScope(scope, scope === "project" ? [] : await loadDirs(userAgentDirs(this.io), "user"), scope === "user" ? [] : await loadDirs(projectDirs.readDirs, "project"), plugin).filter((agent) => agent.disabled !== true),
|
|
1471
|
+
projectAgentsDir: projectDirs.preferredDir,
|
|
1570
1472
|
modelScope: projectSettings.modelScope ?? userSettings.modelScope
|
|
1571
1473
|
};
|
|
1572
1474
|
}
|
|
@@ -1606,7 +1508,7 @@ Object.defineProperty(exports, "agentHasWriteTools", {
|
|
|
1606
1508
|
Object.defineProperty(exports, "canonicalizeDiscoveryCwd", {
|
|
1607
1509
|
enumerable: true,
|
|
1608
1510
|
get: function() {
|
|
1609
|
-
return canonicalizeDiscoveryCwd;
|
|
1511
|
+
return canonicalizeDiscoveryCwd$1;
|
|
1610
1512
|
}
|
|
1611
1513
|
});
|
|
1612
1514
|
Object.defineProperty(exports, "getAgentDir", {
|
|
@@ -1615,23 +1517,17 @@ Object.defineProperty(exports, "getAgentDir", {
|
|
|
1615
1517
|
return getAgentDir;
|
|
1616
1518
|
}
|
|
1617
1519
|
});
|
|
1618
|
-
Object.defineProperty(exports, "
|
|
1619
|
-
enumerable: true,
|
|
1620
|
-
get: function() {
|
|
1621
|
-
return getProjectConfigDir;
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
|
-
Object.defineProperty(exports, "resolveActiveTeamModelSpecs", {
|
|
1520
|
+
Object.defineProperty(exports, "getConfigDirName", {
|
|
1625
1521
|
enumerable: true,
|
|
1626
1522
|
get: function() {
|
|
1627
|
-
return
|
|
1523
|
+
return getConfigDirName;
|
|
1628
1524
|
}
|
|
1629
1525
|
});
|
|
1630
|
-
Object.defineProperty(exports, "
|
|
1526
|
+
Object.defineProperty(exports, "getProjectConfigDir", {
|
|
1631
1527
|
enumerable: true,
|
|
1632
1528
|
get: function() {
|
|
1633
|
-
return
|
|
1529
|
+
return getProjectConfigDir;
|
|
1634
1530
|
}
|
|
1635
1531
|
});
|
|
1636
1532
|
|
|
1637
|
-
//# sourceMappingURL=agentDiscovery-
|
|
1533
|
+
//# sourceMappingURL=agentDiscovery-DTTlM_LT.cjs.map
|