@rowan-agent/agent 0.8.4 → 0.8.6
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/README.md +59 -5
- package/dist/index.d.ts +475 -14
- package/dist/index.js +2030 -1017
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,18 @@ function buildSkillsDescription(skills) {
|
|
|
33
33
|
lines.push("</available_skills>");
|
|
34
34
|
return lines.join("\n");
|
|
35
35
|
}
|
|
36
|
+
function buildContextDescription(contexts) {
|
|
37
|
+
if (contexts.length === 0) return "";
|
|
38
|
+
const lines = ["<agent_context>"];
|
|
39
|
+
for (const context of contexts) {
|
|
40
|
+
lines.push(` <context name="${escapeXml(context.name)}">`);
|
|
41
|
+
const value = jsonToXml(context.value, 2);
|
|
42
|
+
if (value) lines.push(value);
|
|
43
|
+
lines.push(" </context>");
|
|
44
|
+
}
|
|
45
|
+
lines.push("</agent_context>");
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
36
48
|
function formatResourceOutput(resource) {
|
|
37
49
|
const parts = [`<${resource.type} name="${escapeXml(resource.name)}" location="${escapeXml(resource.location)}">`];
|
|
38
50
|
if (resource.baseDir) {
|
|
@@ -955,6 +967,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
|
|
|
955
967
|
};
|
|
956
968
|
const ctx = options?.context ?? noopContext;
|
|
957
969
|
const phaseIn = options?.phase;
|
|
970
|
+
const trackCleanup = options?.trackCleanup;
|
|
958
971
|
let outputPayload = phaseIn?.state?.payload;
|
|
959
972
|
let nextPhase;
|
|
960
973
|
let outputMessage;
|
|
@@ -962,6 +975,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
|
|
|
962
975
|
on: (eventType, handler) => {
|
|
963
976
|
assertActive();
|
|
964
977
|
hooks?.on(eventType, handler);
|
|
978
|
+
trackCleanup?.(() => hooks?.off(eventType, handler));
|
|
965
979
|
},
|
|
966
980
|
off: (eventType, handler) => {
|
|
967
981
|
assertActive();
|
|
@@ -978,6 +992,7 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
|
|
|
978
992
|
registerProvider: (config) => {
|
|
979
993
|
assertActive();
|
|
980
994
|
options?.registerProvider?.(config);
|
|
995
|
+
trackCleanup?.(() => options?.unregisterProvider?.(config.id));
|
|
981
996
|
},
|
|
982
997
|
unregisterProvider: (name) => {
|
|
983
998
|
assertActive();
|
|
@@ -989,7 +1004,17 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
|
|
|
989
1004
|
formatJson
|
|
990
1005
|
},
|
|
991
1006
|
context: ctx,
|
|
992
|
-
events: eventBus
|
|
1007
|
+
events: eventBus ? {
|
|
1008
|
+
on: (event, listener) => {
|
|
1009
|
+
const unsubscribe = eventBus.on(event, listener);
|
|
1010
|
+
trackCleanup?.(unsubscribe);
|
|
1011
|
+
return unsubscribe;
|
|
1012
|
+
},
|
|
1013
|
+
off: (event) => eventBus.off(event),
|
|
1014
|
+
emit: (event, ...args) => eventBus.emit(event, ...args),
|
|
1015
|
+
has: (event) => eventBus.has(event),
|
|
1016
|
+
count: (event) => eventBus.count(event)
|
|
1017
|
+
} : { on: () => () => {
|
|
993
1018
|
}, off: () => {
|
|
994
1019
|
}, emit: () => {
|
|
995
1020
|
}, has: () => false, count: () => 0 },
|
|
@@ -1015,7 +1040,113 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
|
|
|
1015
1040
|
import { existsSync, statSync } from "fs";
|
|
1016
1041
|
import { readdir } from "fs/promises";
|
|
1017
1042
|
import { dirname as dirname3, join, resolve as resolve3 } from "path";
|
|
1043
|
+
|
|
1044
|
+
// src/harness/definitions.ts
|
|
1018
1045
|
import { parseModelRef } from "@rowan-agent/models";
|
|
1046
|
+
function assertAgentDefinition(value) {
|
|
1047
|
+
if (!isRecord(value)) throw new TypeError("config.definition is invalid");
|
|
1048
|
+
requiredString(value.name, "definition.name");
|
|
1049
|
+
requiredString(value.description, "definition.description");
|
|
1050
|
+
requiredString(value.content, "definition.content");
|
|
1051
|
+
optionalStringList(value.tools, "definition.tools");
|
|
1052
|
+
optionalStringList(value.skills, "definition.skills");
|
|
1053
|
+
optionalPhaseRegistrySelection(value.phases, "definition.phases");
|
|
1054
|
+
optionalStringList(value.context, "definition.context");
|
|
1055
|
+
if ("extensions" in value) throw new TypeError("definition.extensions is not supported; Extensions are Runtime-global");
|
|
1056
|
+
if ("entryPhase" in value) throw new TypeError("definition.entryPhase is not supported; use definition.phases.entryPhaseId");
|
|
1057
|
+
if (value.model !== void 0) {
|
|
1058
|
+
if (!isRecord(value.model) || typeof value.model.provider !== "string" || value.model.provider.trim() === "" || typeof value.model.id !== "string" || value.model.id.trim() === "") {
|
|
1059
|
+
throw new TypeError("definition.model must be a valid model reference");
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function parseAgentDefinition(raw) {
|
|
1064
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
1065
|
+
return normalizeAgentDefinition(frontmatter, body);
|
|
1066
|
+
}
|
|
1067
|
+
function normalizeAgentDefinition(frontmatter, content, options = {}) {
|
|
1068
|
+
const name = frontmatter.name === void 0 && options.fallbackName ? options.fallbackName : requiredString(frontmatter.name, "name");
|
|
1069
|
+
const description = requiredString(frontmatter.description, "description");
|
|
1070
|
+
const normalizedContent = requiredString(content, "content");
|
|
1071
|
+
const tools = optionalStringList(frontmatter.tools, "tools");
|
|
1072
|
+
const skills = optionalStringList(frontmatter.skills, "skills");
|
|
1073
|
+
const phases = optionalPhaseRegistrySelection(frontmatter.phases, "phases");
|
|
1074
|
+
if (frontmatter.extensions !== void 0) {
|
|
1075
|
+
throw new TypeError("extensions is not supported; Extensions are Runtime-global");
|
|
1076
|
+
}
|
|
1077
|
+
const context = optionalStringList(frontmatter.context, "context");
|
|
1078
|
+
if (frontmatter.entryPhase !== void 0) {
|
|
1079
|
+
throw new TypeError("entryPhase is not supported; use phases.entryPhaseId");
|
|
1080
|
+
}
|
|
1081
|
+
const model = optionalModel(frontmatter.model);
|
|
1082
|
+
return {
|
|
1083
|
+
name,
|
|
1084
|
+
description,
|
|
1085
|
+
content: normalizedContent,
|
|
1086
|
+
...tools ? { tools } : {},
|
|
1087
|
+
...skills ? { skills } : {},
|
|
1088
|
+
...phases ? { phases } : {},
|
|
1089
|
+
...context ? { context } : {},
|
|
1090
|
+
...model ? { model } : {}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function requiredString(value, field) {
|
|
1094
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1095
|
+
throw new TypeError(`${field} is required`);
|
|
1096
|
+
}
|
|
1097
|
+
return value.trim();
|
|
1098
|
+
}
|
|
1099
|
+
function optionalString(value, field) {
|
|
1100
|
+
if (value === void 0) return void 0;
|
|
1101
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1102
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
1103
|
+
}
|
|
1104
|
+
return value.trim();
|
|
1105
|
+
}
|
|
1106
|
+
function optionalStringList(value, field) {
|
|
1107
|
+
if (value === void 0) return void 0;
|
|
1108
|
+
if (!Array.isArray(value)) throw new TypeError(`${field} must be an array of strings`);
|
|
1109
|
+
const names = [];
|
|
1110
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1111
|
+
for (const [index, item] of value.entries()) {
|
|
1112
|
+
if (typeof item !== "string" || item.trim() === "") {
|
|
1113
|
+
throw new TypeError(`${field}[${index}] must be a non-empty string`);
|
|
1114
|
+
}
|
|
1115
|
+
const name = item.trim();
|
|
1116
|
+
if (seen.has(name)) continue;
|
|
1117
|
+
seen.add(name);
|
|
1118
|
+
names.push(name);
|
|
1119
|
+
}
|
|
1120
|
+
return names;
|
|
1121
|
+
}
|
|
1122
|
+
function optionalPhaseRegistrySelection(value, field) {
|
|
1123
|
+
if (value === void 0) return void 0;
|
|
1124
|
+
if (!isRecord(value)) throw new TypeError(`${field} must be an object`);
|
|
1125
|
+
if (!("entryPhaseId" in value)) throw new TypeError(`${field}.entryPhaseId is required`);
|
|
1126
|
+
if (!("phaseIds" in value)) throw new TypeError(`${field}.phaseIds is required`);
|
|
1127
|
+
const entryPhaseId = value.entryPhaseId;
|
|
1128
|
+
if (entryPhaseId !== null && (typeof entryPhaseId !== "string" || entryPhaseId.trim() === "")) {
|
|
1129
|
+
throw new TypeError(`${field}.entryPhaseId must be a non-empty string or null`);
|
|
1130
|
+
}
|
|
1131
|
+
const phaseIds = optionalStringList(value.phaseIds, `${field}.phaseIds`);
|
|
1132
|
+
if (!phaseIds) throw new TypeError(`${field}.phaseIds is required`);
|
|
1133
|
+
return {
|
|
1134
|
+
entryPhaseId: entryPhaseId === null ? null : entryPhaseId.trim(),
|
|
1135
|
+
phaseIds
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
function optionalModel(value) {
|
|
1139
|
+
const source = optionalString(value, "model");
|
|
1140
|
+
if (!source) return void 0;
|
|
1141
|
+
const model = parseModelRef(source);
|
|
1142
|
+
if (!model || model.provider.trim() === "" || model.id.trim() === "") {
|
|
1143
|
+
throw new TypeError("model must be a valid model reference");
|
|
1144
|
+
}
|
|
1145
|
+
return model;
|
|
1146
|
+
}
|
|
1147
|
+
function isRecord(value) {
|
|
1148
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1149
|
+
}
|
|
1019
1150
|
|
|
1020
1151
|
// src/harness/resource-validation.ts
|
|
1021
1152
|
var MAX_RESOURCE_ID_LENGTH = 64;
|
|
@@ -1113,8 +1244,16 @@ async function loadPhase(targetPath) {
|
|
|
1113
1244
|
const { frontmatter, body } = loaded;
|
|
1114
1245
|
const metadata = frontmatter;
|
|
1115
1246
|
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
1116
|
-
|
|
1117
|
-
|
|
1247
|
+
let definition;
|
|
1248
|
+
try {
|
|
1249
|
+
definition = normalizeAgentDefinition(metadata, body, { fallbackName: directoryName });
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1252
|
+
warnResourceDiagnostics("phase", resolved, [message]);
|
|
1253
|
+
throw new ResourceMetadataError("invalid_metadata", message);
|
|
1254
|
+
}
|
|
1255
|
+
const name = definition.name;
|
|
1256
|
+
const description = validateDescription(definition.description);
|
|
1118
1257
|
const diagnostics = validateResourceId(directoryName);
|
|
1119
1258
|
if (frontmatterName && frontmatterName !== directoryName) {
|
|
1120
1259
|
diagnostics.push(...validateResourceName(frontmatterName, directoryName));
|
|
@@ -1123,22 +1262,19 @@ async function loadPhase(targetPath) {
|
|
|
1123
1262
|
diagnostics.push(...validateSkillReferences(metadata.skills));
|
|
1124
1263
|
diagnostics.push(...validatePhaseTarget(metadata.target));
|
|
1125
1264
|
warnResourceDiagnostics("phase", resolved, diagnostics);
|
|
1126
|
-
if (description.missing) {
|
|
1127
|
-
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
1128
|
-
}
|
|
1129
1265
|
const baseDir = dirname3(resolved);
|
|
1130
1266
|
const phase = {
|
|
1131
1267
|
name,
|
|
1132
1268
|
description: description.description,
|
|
1133
|
-
tools:
|
|
1134
|
-
skills:
|
|
1269
|
+
tools: definition.tools ? [...definition.tools] : void 0,
|
|
1270
|
+
skills: definition.skills ? [...definition.skills] : void 0,
|
|
1135
1271
|
target: metadata.target,
|
|
1136
1272
|
input: metadata.input,
|
|
1137
1273
|
isolated: metadata.isolated,
|
|
1138
1274
|
filePath: resolved,
|
|
1139
1275
|
baseDir,
|
|
1140
|
-
content:
|
|
1141
|
-
model:
|
|
1276
|
+
content: definition.content,
|
|
1277
|
+
model: definition.model
|
|
1142
1278
|
};
|
|
1143
1279
|
const codePath = await discoverPhaseCode(baseDir);
|
|
1144
1280
|
if (codePath) {
|
|
@@ -1236,6 +1372,31 @@ function createDefaultPhase() {
|
|
|
1236
1372
|
};
|
|
1237
1373
|
}
|
|
1238
1374
|
|
|
1375
|
+
// src/harness/resource-selection.ts
|
|
1376
|
+
function selectNamedResources(candidates, names, kind) {
|
|
1377
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1378
|
+
for (const candidate of candidates) {
|
|
1379
|
+
if (byName.has(candidate.name)) {
|
|
1380
|
+
throw new TypeError(`Duplicate ${kind} candidate "${candidate.name}".`);
|
|
1381
|
+
}
|
|
1382
|
+
byName.set(candidate.name, candidate);
|
|
1383
|
+
}
|
|
1384
|
+
if (names === void 0) return [...candidates];
|
|
1385
|
+
const selected = [];
|
|
1386
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1387
|
+
for (const name of names) {
|
|
1388
|
+
if (seen.has(name)) continue;
|
|
1389
|
+
seen.add(name);
|
|
1390
|
+
const candidate = byName.get(name);
|
|
1391
|
+
if (!candidate) {
|
|
1392
|
+
console.warn(`${kind} "${name}" is not available and will be skipped.`);
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
selected.push(candidate);
|
|
1396
|
+
}
|
|
1397
|
+
return selected;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1239
1400
|
// src/harness/context/system-prompt.ts
|
|
1240
1401
|
function buildSystemPrompt(options) {
|
|
1241
1402
|
const { systemPrompt, promptGuidelines, appendSystemPrompt, tools, skills } = options;
|
|
@@ -1578,7 +1739,7 @@ function createTerminalOutcome(phase, output, transcript) {
|
|
|
1578
1739
|
return phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
|
|
1579
1740
|
}
|
|
1580
1741
|
function buildToolsWithRouting(config, availablePhases) {
|
|
1581
|
-
const tools =
|
|
1742
|
+
const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
|
|
1582
1743
|
if (availablePhases.length > 0) {
|
|
1583
1744
|
tools.push(createRouteTool(availablePhases));
|
|
1584
1745
|
}
|
|
@@ -1763,8 +1924,8 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1763
1924
|
const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
|
|
1764
1925
|
const toolExecutionManager = createToolExecutionManager();
|
|
1765
1926
|
const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
|
|
1766
|
-
const phaseTools =
|
|
1767
|
-
const phaseSkills =
|
|
1927
|
+
const phaseTools = selectPhaseTools(allTools, phase.tools);
|
|
1928
|
+
const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
|
|
1768
1929
|
let phaseContext = {
|
|
1769
1930
|
systemPrompt: config.context.systemPrompt,
|
|
1770
1931
|
messages: messageManager.visible(),
|
|
@@ -2043,7 +2204,7 @@ async function withRetry(fn, options = {}) {
|
|
|
2043
2204
|
}
|
|
2044
2205
|
const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
|
|
2045
2206
|
options.onRetry?.(attempt, error, delayMs);
|
|
2046
|
-
await new Promise((
|
|
2207
|
+
await new Promise((resolve7) => setTimeout(resolve7, delayMs));
|
|
2047
2208
|
if (options.signal?.aborted) {
|
|
2048
2209
|
throw lastError;
|
|
2049
2210
|
}
|
|
@@ -2254,8 +2415,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2254
2415
|
async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
|
|
2255
2416
|
const messages = [...context];
|
|
2256
2417
|
const allTools = buildToolsWithRouting(config, availablePhases);
|
|
2257
|
-
const phaseTools =
|
|
2258
|
-
const phaseSkills =
|
|
2418
|
+
const phaseTools = selectPhaseTools(allTools, phase.tools);
|
|
2419
|
+
const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
|
|
2259
2420
|
const systemPrompt = config.context.systemPrompt;
|
|
2260
2421
|
const phaseContext = {
|
|
2261
2422
|
systemPrompt,
|
|
@@ -2293,6 +2454,11 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2293
2454
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2294
2455
|
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
|
|
2295
2456
|
}
|
|
2457
|
+
function selectPhaseTools(candidates, names) {
|
|
2458
|
+
const routing = candidates.filter(({ name }) => name === PhaseRouteTool);
|
|
2459
|
+
const ordinary = candidates.filter(({ name }) => name !== PhaseRouteTool);
|
|
2460
|
+
return [...selectNamedResources(ordinary, names, "Tool"), ...routing];
|
|
2461
|
+
}
|
|
2296
2462
|
async function waitForBackgroundTasks(backgroundTasks) {
|
|
2297
2463
|
const entries = Array.from(backgroundTasks.entries());
|
|
2298
2464
|
const results = await Promise.allSettled(entries.map(([, t]) => t.promise));
|
|
@@ -2523,29 +2689,32 @@ function createMetrics() {
|
|
|
2523
2689
|
startedAtMs: Date.now()
|
|
2524
2690
|
};
|
|
2525
2691
|
}
|
|
2526
|
-
function
|
|
2692
|
+
function isRecord2(value) {
|
|
2527
2693
|
return value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
2528
2694
|
}
|
|
2529
2695
|
function isCheckpointData(value) {
|
|
2530
|
-
if (!
|
|
2696
|
+
if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
|
|
2531
2697
|
if (!isMetrics(value.metrics)) return false;
|
|
2532
2698
|
if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
|
|
2533
2699
|
return true;
|
|
2534
2700
|
}
|
|
2535
2701
|
function isMetrics(value) {
|
|
2536
|
-
if (!
|
|
2537
|
-
return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) =>
|
|
2702
|
+
if (!isRecord2(value)) return false;
|
|
2703
|
+
return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord2(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
|
|
2538
2704
|
}
|
|
2539
2705
|
function isContinuation(value) {
|
|
2540
|
-
if (!
|
|
2706
|
+
if (!isRecord2(value) || typeof value.isContinuing !== "boolean" || !Array.isArray(value.previousResults)) return false;
|
|
2541
2707
|
if (value.previousPayload !== void 0 && !isJsonValue(value.previousPayload)) return false;
|
|
2542
|
-
return value.previousResults.every((result) =>
|
|
2708
|
+
return value.previousResults.every((result) => isRecord2(result) && typeof result.name === "string" && (result.output === void 0 || isJsonValue(result.output))) && (value.pendingInstruction === void 0 || typeof value.pendingInstruction === "string") && (value.previousPhaseMessageId === void 0 || typeof value.previousPhaseMessageId === "string");
|
|
2543
2709
|
}
|
|
2544
2710
|
|
|
2545
2711
|
// src/runtime/contracts.ts
|
|
2712
|
+
function isAgentConfiguration(config) {
|
|
2713
|
+
return !("resources" in config);
|
|
2714
|
+
}
|
|
2546
2715
|
var METADATA_LIMIT = 64 * 1024;
|
|
2547
2716
|
var IDENTITY_LIMIT = 256;
|
|
2548
|
-
function
|
|
2717
|
+
function isRecord3(value) {
|
|
2549
2718
|
if (value === null || typeof value !== "object") return false;
|
|
2550
2719
|
const prototype = Object.getPrototypeOf(value);
|
|
2551
2720
|
return prototype === Object.prototype || prototype === null;
|
|
@@ -2554,7 +2723,7 @@ function hasOnlyKeys(value, allowed) {
|
|
|
2554
2723
|
return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.includes(key));
|
|
2555
2724
|
}
|
|
2556
2725
|
function isMetadata(value) {
|
|
2557
|
-
if (!
|
|
2726
|
+
if (!isRecord3(value) || !isJsonValue(value)) return false;
|
|
2558
2727
|
try {
|
|
2559
2728
|
assertUtf8ByteLimit(canonicalJson(value), METADATA_LIMIT, "metadata");
|
|
2560
2729
|
return true;
|
|
@@ -2566,23 +2735,23 @@ function assertMetadata(value, argument) {
|
|
|
2566
2735
|
if (!isMetadata(value)) throw new TypeError(`${argument} must be a JSON-safe metadata object`);
|
|
2567
2736
|
}
|
|
2568
2737
|
function isText(value) {
|
|
2569
|
-
return
|
|
2738
|
+
return isRecord3(value) && value.type === "text" && typeof value.text === "string";
|
|
2570
2739
|
}
|
|
2571
2740
|
function isImage(value) {
|
|
2572
|
-
return
|
|
2741
|
+
return isRecord3(value) && value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string";
|
|
2573
2742
|
}
|
|
2574
2743
|
function isUserContent(value) {
|
|
2575
2744
|
return typeof value === "string" || Array.isArray(value) && value.every((part) => isText(part) || isImage(part));
|
|
2576
2745
|
}
|
|
2577
2746
|
function normalizeUserInput(input) {
|
|
2578
2747
|
const normalized = typeof input === "string" ? { content: input } : input;
|
|
2579
|
-
if (!
|
|
2748
|
+
if (!isRecord3(normalized) || !hasOnlyKeys(normalized, ["content", "metadata"]) || !isUserContent(normalized.content)) throw new TypeError("input must contain only valid UserContent");
|
|
2580
2749
|
if (normalized.metadata !== void 0) assertMetadata(normalized.metadata, "input.metadata");
|
|
2581
2750
|
assertJsonValue(normalized, "input");
|
|
2582
2751
|
return normalized;
|
|
2583
2752
|
}
|
|
2584
2753
|
function isToolResult(value) {
|
|
2585
|
-
if (!
|
|
2754
|
+
if (!isRecord3(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
|
|
2586
2755
|
const expected = value.ok ? ["content", "ok"] : ["content", "error", "ok"];
|
|
2587
2756
|
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) return false;
|
|
2588
2757
|
return value.ok || typeof value.error === "string";
|
|
@@ -2590,8 +2759,44 @@ function isToolResult(value) {
|
|
|
2590
2759
|
function assertAgentConfig(config) {
|
|
2591
2760
|
if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
|
|
2592
2761
|
assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
|
|
2593
|
-
|
|
2594
|
-
|
|
2762
|
+
assertAgentDefinition(config.definition);
|
|
2763
|
+
if (!config.resources || !Array.isArray(config.resources.tools) || !Array.isArray(config.resources.skills)) {
|
|
2764
|
+
throw new TypeError("config.resources is invalid");
|
|
2765
|
+
}
|
|
2766
|
+
for (const tool of config.resources.tools) projectToolDefinition(tool);
|
|
2767
|
+
const names = /* @__PURE__ */ new Set();
|
|
2768
|
+
for (const context of config.resources.contexts ?? []) {
|
|
2769
|
+
if (typeof context.name !== "string" || context.name.trim() === "") {
|
|
2770
|
+
throw new TypeError("Context candidate name must be non-empty");
|
|
2771
|
+
}
|
|
2772
|
+
if (names.has(context.name)) throw new TypeError(`Duplicate Context candidate "${context.name}".`);
|
|
2773
|
+
names.add(context.name);
|
|
2774
|
+
assertJsonValue(context.value, `Context candidate "${context.name}" value`);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
function assertAgentConfigRequest(config) {
|
|
2778
|
+
if (!isAgentConfiguration(config)) {
|
|
2779
|
+
assertAgentConfig(config);
|
|
2780
|
+
return;
|
|
2781
|
+
}
|
|
2782
|
+
if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
|
|
2783
|
+
assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
|
|
2784
|
+
if (!config.definition || typeof config.definition.name !== "string" || config.definition.name.trim() === "") {
|
|
2785
|
+
throw new TypeError("config.definition.name must be non-empty");
|
|
2786
|
+
}
|
|
2787
|
+
const view = config.resourceView;
|
|
2788
|
+
if (!view || !Array.isArray(view.agents) || !Array.isArray(view.tools) || !Array.isArray(view.skills) || !Array.isArray(view.phases)) {
|
|
2789
|
+
throw new TypeError("config.resourceView is invalid");
|
|
2790
|
+
}
|
|
2791
|
+
for (const [kind, ids] of Object.entries(view)) {
|
|
2792
|
+
for (const sourceId of ids) {
|
|
2793
|
+
if (typeof sourceId !== "string" || sourceId.trim() === "") throw new TypeError(`config.resourceView.${kind} contains an invalid Source ID`);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
for (const context of config.contexts ?? []) {
|
|
2797
|
+
if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Context candidate name must be non-empty");
|
|
2798
|
+
assertJsonValue(context.value, `Context candidate "${context.name}" value`);
|
|
2799
|
+
}
|
|
2595
2800
|
}
|
|
2596
2801
|
function assertToolExecutionResult(value) {
|
|
2597
2802
|
if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
|
|
@@ -2599,7 +2804,7 @@ function assertToolExecutionResult(value) {
|
|
|
2599
2804
|
function projectToolDefinition(tool) {
|
|
2600
2805
|
if (typeof tool.name !== "string" || tool.name.length === 0 || typeof tool.description !== "string") throw new TypeError("Tool definition is invalid");
|
|
2601
2806
|
assertJsonValue(tool.parameters, "tool.parameters");
|
|
2602
|
-
if (!
|
|
2807
|
+
if (!isRecord3(tool.parameters)) throw new TypeError("tool.parameters must be a JSON object");
|
|
2603
2808
|
return { name: tool.name, description: tool.description, parameters: JSON.parse(canonicalJson(tool.parameters)) };
|
|
2604
2809
|
}
|
|
2605
2810
|
|
|
@@ -2666,7 +2871,7 @@ function brandConfigToken(raw) {
|
|
|
2666
2871
|
return raw;
|
|
2667
2872
|
}
|
|
2668
2873
|
function validateConfigResolution(agentId, value) {
|
|
2669
|
-
if (!
|
|
2874
|
+
if (!isRecord4(value) || typeof value.kind !== "string") {
|
|
2670
2875
|
throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
|
|
2671
2876
|
}
|
|
2672
2877
|
if (value.kind === "deferred") {
|
|
@@ -2679,18 +2884,97 @@ function validateConfigResolution(agentId, value) {
|
|
|
2679
2884
|
};
|
|
2680
2885
|
}
|
|
2681
2886
|
if (value.kind === "unavailable" && typeof value.reason === "string") return { kind: "unavailable", reason: value.reason };
|
|
2682
|
-
if (value.kind === "available" &&
|
|
2887
|
+
if (value.kind === "available" && isRecord4(value.config) && typeof value.config.identity === "string") {
|
|
2683
2888
|
return { kind: "available", config: value.config };
|
|
2684
2889
|
}
|
|
2685
2890
|
throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
|
|
2686
2891
|
}
|
|
2687
2892
|
function snapshotConfig(config) {
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2893
|
+
if (isAgentConfiguration(config)) return snapshotConfiguration(config);
|
|
2894
|
+
const definition = Object.freeze({
|
|
2895
|
+
...config.definition,
|
|
2896
|
+
...config.definition.tools ? { tools: Object.freeze([...config.definition.tools]) } : {},
|
|
2897
|
+
...config.definition.skills ? { skills: Object.freeze([...config.definition.skills]) } : {},
|
|
2898
|
+
...config.definition.phases ? {
|
|
2899
|
+
phases: Object.freeze({
|
|
2900
|
+
entryPhaseId: config.definition.phases.entryPhaseId,
|
|
2901
|
+
phaseIds: Object.freeze([...config.definition.phases.phaseIds])
|
|
2902
|
+
})
|
|
2903
|
+
} : {},
|
|
2904
|
+
...config.definition.context ? { context: Object.freeze([...config.definition.context]) } : {}
|
|
2905
|
+
});
|
|
2906
|
+
const resources = Object.freeze({
|
|
2907
|
+
...config.resources,
|
|
2908
|
+
tools: Object.freeze([...config.resources.tools]),
|
|
2909
|
+
skills: Object.freeze([...config.resources.skills]),
|
|
2910
|
+
...config.resources.resourceView ? {
|
|
2911
|
+
resourceView: Object.freeze({
|
|
2912
|
+
agents: Object.freeze([...config.resources.resourceView.agents]),
|
|
2913
|
+
tools: Object.freeze([...config.resources.resourceView.tools]),
|
|
2914
|
+
skills: Object.freeze([...config.resources.resourceView.skills]),
|
|
2915
|
+
phases: Object.freeze([...config.resources.resourceView.phases])
|
|
2916
|
+
})
|
|
2917
|
+
} : {},
|
|
2918
|
+
...config.resources.resourceRefs ? { resourceRefs: Object.freeze(config.resources.resourceRefs.map((ref) => Object.freeze({ ...ref }))) } : {},
|
|
2919
|
+
...config.resources.resourceRevisions ? {
|
|
2920
|
+
resourceRevisions: Object.freeze(Object.fromEntries(
|
|
2921
|
+
Object.entries(config.resources.resourceRevisions).map(([kind, revisions]) => [kind, Object.freeze([...revisions])])
|
|
2922
|
+
))
|
|
2923
|
+
} : {},
|
|
2924
|
+
...config.resources.contexts ? {
|
|
2925
|
+
contexts: Object.freeze(config.resources.contexts.map((context) => Object.freeze({
|
|
2926
|
+
name: context.name,
|
|
2927
|
+
value: snapshotJsonValue(context.value)
|
|
2928
|
+
})))
|
|
2929
|
+
} : {}
|
|
2930
|
+
});
|
|
2931
|
+
return Object.freeze({ ...config, definition, resources });
|
|
2932
|
+
}
|
|
2933
|
+
function snapshotConfiguration(config) {
|
|
2934
|
+
const layer = config.definition.layer;
|
|
2935
|
+
const definition = Object.freeze({
|
|
2936
|
+
name: config.definition.name,
|
|
2937
|
+
...layer === void 0 ? {} : {
|
|
2938
|
+
layer: Object.freeze({
|
|
2939
|
+
...layer,
|
|
2940
|
+
...layer.tools ? { tools: Object.freeze([...layer.tools]) } : {},
|
|
2941
|
+
...layer.skills ? { skills: Object.freeze([...layer.skills]) } : {},
|
|
2942
|
+
...layer.phases ? {
|
|
2943
|
+
phases: Object.freeze({
|
|
2944
|
+
entryPhaseId: layer.phases.entryPhaseId,
|
|
2945
|
+
phaseIds: Object.freeze([...layer.phases.phaseIds])
|
|
2946
|
+
})
|
|
2947
|
+
} : {}
|
|
2948
|
+
})
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2951
|
+
const resourceView = Object.freeze({
|
|
2952
|
+
agents: Object.freeze([...config.resourceView.agents]),
|
|
2953
|
+
tools: Object.freeze([...config.resourceView.tools]),
|
|
2954
|
+
skills: Object.freeze([...config.resourceView.skills]),
|
|
2955
|
+
phases: Object.freeze([...config.resourceView.phases])
|
|
2956
|
+
});
|
|
2957
|
+
return Object.freeze({
|
|
2958
|
+
...config,
|
|
2959
|
+
definition,
|
|
2960
|
+
resourceView,
|
|
2961
|
+
...config.contexts ? {
|
|
2962
|
+
contexts: Object.freeze(config.contexts.map((context) => Object.freeze({
|
|
2963
|
+
name: context.name,
|
|
2964
|
+
value: snapshotJsonValue(context.value)
|
|
2965
|
+
})))
|
|
2966
|
+
} : {}
|
|
2692
2967
|
});
|
|
2693
|
-
|
|
2968
|
+
}
|
|
2969
|
+
function snapshotJsonValue(value) {
|
|
2970
|
+
return deepFreeze(JSON.parse(canonicalJson(value)));
|
|
2971
|
+
}
|
|
2972
|
+
function deepFreeze(value) {
|
|
2973
|
+
if (value && typeof value === "object") {
|
|
2974
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
2975
|
+
Object.freeze(value);
|
|
2976
|
+
}
|
|
2977
|
+
return value;
|
|
2694
2978
|
}
|
|
2695
2979
|
function assertConfigIdentity(identity) {
|
|
2696
2980
|
if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
|
|
@@ -2705,7 +2989,7 @@ function throwIfAborted(signal) {
|
|
|
2705
2989
|
error.name = "AbortError";
|
|
2706
2990
|
throw error;
|
|
2707
2991
|
}
|
|
2708
|
-
function
|
|
2992
|
+
function isRecord4(value) {
|
|
2709
2993
|
return value !== null && typeof value === "object";
|
|
2710
2994
|
}
|
|
2711
2995
|
|
|
@@ -2736,7 +3020,7 @@ var ConfigCommandService = class {
|
|
|
2736
3020
|
configs;
|
|
2737
3021
|
storeIncarnation;
|
|
2738
3022
|
async createAgent(input) {
|
|
2739
|
-
|
|
3023
|
+
assertAgentConfigRequest(input.config);
|
|
2740
3024
|
assertIdentity(input.config.identity);
|
|
2741
3025
|
const reserved = await this.store.reserveAgent({
|
|
2742
3026
|
idempotencyKey: input.idempotencyKey,
|
|
@@ -2758,7 +3042,7 @@ var ConfigCommandService = class {
|
|
|
2758
3042
|
return reserved.id;
|
|
2759
3043
|
}
|
|
2760
3044
|
async updateAgentConfig(input) {
|
|
2761
|
-
|
|
3045
|
+
assertAgentConfigRequest(input.config);
|
|
2762
3046
|
assertIdentity(input.config.identity);
|
|
2763
3047
|
const agent = await this.findAgent(input.agentId);
|
|
2764
3048
|
const operationId = this.operationId("update_agent_config", input.agentId, input.idempotencyKey);
|
|
@@ -2785,6 +3069,23 @@ var ConfigCommandService = class {
|
|
|
2785
3069
|
const resolution = validateConfigResolution(input.agent.id, result);
|
|
2786
3070
|
return resolution;
|
|
2787
3071
|
}
|
|
3072
|
+
/** Persist one resolved Configuration Snapshot under a Run-specific token.
|
|
3073
|
+
* The durable Run pins this token at claim time, so later Source replacement
|
|
3074
|
+
* cannot mutate an active or input-waiting Run. */
|
|
3075
|
+
async storeSnapshot(input) {
|
|
3076
|
+
assertAgentConfigRequest(input.config);
|
|
3077
|
+
const result = await this.put({
|
|
3078
|
+
agentId: input.agent.id,
|
|
3079
|
+
agentMetadata: input.agent.metadata,
|
|
3080
|
+
config: input.config,
|
|
3081
|
+
operationId: input.operationId,
|
|
3082
|
+
signal: input.signal
|
|
3083
|
+
});
|
|
3084
|
+
if (result.kind === "identity_conflict") {
|
|
3085
|
+
throw idempotencyConflict("update_agent_config", input.operationId);
|
|
3086
|
+
}
|
|
3087
|
+
return brandConfigToken(result.token);
|
|
3088
|
+
}
|
|
2788
3089
|
async findAgent(agentId) {
|
|
2789
3090
|
const agent = (await this.store.listAgents()).find((candidate) => candidate.id === agentId);
|
|
2790
3091
|
if (!agent) throw new RuntimeError("agent_not_found", { agentId });
|
|
@@ -3018,326 +3319,590 @@ function jsonText(value) {
|
|
|
3018
3319
|
return typeof value === "string" ? value : JSON.stringify(value) ?? "null";
|
|
3019
3320
|
}
|
|
3020
3321
|
|
|
3021
|
-
// src/extensions
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
var HooksManager = class {
|
|
3031
|
-
handlers = /* @__PURE__ */ new Map();
|
|
3032
|
-
on(eventType, handler) {
|
|
3033
|
-
const handlers = this.handlers.get(eventType) ?? [];
|
|
3034
|
-
handlers.push(handler);
|
|
3035
|
-
this.handlers.set(eventType, handlers);
|
|
3036
|
-
}
|
|
3037
|
-
off(eventType, handler) {
|
|
3038
|
-
const handlers = this.handlers.get(eventType);
|
|
3039
|
-
if (!handlers) return;
|
|
3040
|
-
const index = handlers.indexOf(handler);
|
|
3041
|
-
if (index >= 0) handlers.splice(index, 1);
|
|
3042
|
-
}
|
|
3043
|
-
async emit(eventType, event) {
|
|
3044
|
-
const handlers = this.handlers.get(eventType);
|
|
3045
|
-
if (!handlers?.length) return;
|
|
3046
|
-
const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
|
|
3047
|
-
const failure = results.find((result) => result.status === "rejected");
|
|
3048
|
-
if (failure) {
|
|
3049
|
-
throw new HookError(
|
|
3050
|
-
eventType,
|
|
3051
|
-
`${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
|
|
3052
|
-
failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
|
|
3053
|
-
);
|
|
3054
|
-
}
|
|
3322
|
+
// src/runtime/extensions.ts
|
|
3323
|
+
function assembleRegisteredExtensions(config, runner) {
|
|
3324
|
+
const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
|
|
3325
|
+
const tools = [...config.resources.tools];
|
|
3326
|
+
const names = new Set(tools.map((tool) => tool.name));
|
|
3327
|
+
for (const tool of extensionTools) {
|
|
3328
|
+
if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
|
|
3329
|
+
names.add(tool.name);
|
|
3330
|
+
tools.push(tool);
|
|
3055
3331
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3332
|
+
const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
|
|
3333
|
+
const phases = mergePhases(config.resources.phases, extensionPhases);
|
|
3334
|
+
const context = resolveDefinitionContext(config, { tools, phases });
|
|
3335
|
+
return {
|
|
3336
|
+
context,
|
|
3337
|
+
beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
|
|
3338
|
+
afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
|
|
3339
|
+
beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
|
|
3340
|
+
beforeToolCall: async (input) => {
|
|
3341
|
+
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3342
|
+
const decision = await runner.emitBeforeToolCall(tool, input.args);
|
|
3343
|
+
return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
|
|
3344
|
+
},
|
|
3345
|
+
afterToolCall: async (input) => {
|
|
3346
|
+
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3347
|
+
const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
|
|
3348
|
+
return fromLoopResult(result);
|
|
3349
|
+
},
|
|
3350
|
+
setContext: (context2) => {
|
|
3351
|
+
runner.currentContext = context2;
|
|
3070
3352
|
}
|
|
3071
|
-
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
function resolveDefinitionContext(config, assembled = {}) {
|
|
3356
|
+
const tools = selectNamedResources(
|
|
3357
|
+
assembled.tools ?? config.resources.tools,
|
|
3358
|
+
config.definition.tools,
|
|
3359
|
+
"Tool"
|
|
3360
|
+
);
|
|
3361
|
+
const skills = selectNamedResources(
|
|
3362
|
+
config.resources.skills,
|
|
3363
|
+
config.definition.skills,
|
|
3364
|
+
"Skill"
|
|
3365
|
+
);
|
|
3366
|
+
const contexts = selectNamedResources(
|
|
3367
|
+
config.resources.contexts ?? [],
|
|
3368
|
+
config.definition.context,
|
|
3369
|
+
"Context"
|
|
3370
|
+
);
|
|
3371
|
+
const candidateRegistry = assembled.phases ?? config.resources.phases;
|
|
3372
|
+
const selectedPhases = selectNamedResources(
|
|
3373
|
+
[...candidateRegistry?.phases.values() ?? []],
|
|
3374
|
+
config.definition.phases?.phaseIds,
|
|
3375
|
+
"Phase"
|
|
3376
|
+
);
|
|
3377
|
+
const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
|
|
3378
|
+
const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
|
|
3379
|
+
const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
|
|
3380
|
+
if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
|
|
3381
|
+
console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
|
|
3072
3382
|
}
|
|
3073
|
-
};
|
|
3074
|
-
|
|
3075
|
-
// src/extensions/runner.ts
|
|
3076
|
-
import { execFile } from "child_process";
|
|
3077
|
-
import {
|
|
3078
|
-
registerModel,
|
|
3079
|
-
unregisterProviderModels,
|
|
3080
|
-
registerApiProvider
|
|
3081
|
-
} from "@rowan-agent/models";
|
|
3082
|
-
|
|
3083
|
-
// src/extensions/types.ts
|
|
3084
|
-
function createSourceInfo(extensionPath, options = {}) {
|
|
3085
|
-
const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
|
|
3086
|
-
const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
|
|
3087
3383
|
return {
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3384
|
+
systemPrompt: [config.definition.content, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
|
|
3385
|
+
tools,
|
|
3386
|
+
skills,
|
|
3387
|
+
phases: { phases, entryPhaseId }
|
|
3091
3388
|
};
|
|
3092
3389
|
}
|
|
3093
|
-
function
|
|
3094
|
-
|
|
3095
|
-
const
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3390
|
+
function mergePhases(base, extension) {
|
|
3391
|
+
if (!base && extension.phases.size === 0) return void 0;
|
|
3392
|
+
const phases = new Map(base?.phases ?? []);
|
|
3393
|
+
for (const [name, phase] of extension.phases) {
|
|
3394
|
+
if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
|
|
3395
|
+
phases.set(name, phase);
|
|
3396
|
+
}
|
|
3397
|
+
return {
|
|
3398
|
+
phases,
|
|
3399
|
+
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
|
|
3099
3400
|
};
|
|
3401
|
+
}
|
|
3402
|
+
function adaptExtensionTool(input) {
|
|
3403
|
+
const definition = input.definition;
|
|
3100
3404
|
return {
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3405
|
+
name: definition.name,
|
|
3406
|
+
description: definition.description,
|
|
3407
|
+
parameters: definition.parameters,
|
|
3408
|
+
execute: async (args, _context, signal) => {
|
|
3409
|
+
const result = await definition.execute(args, signal);
|
|
3410
|
+
const content = JSON.parse(JSON.stringify(result.content));
|
|
3411
|
+
return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
|
|
3104
3412
|
}
|
|
3105
3413
|
};
|
|
3106
3414
|
}
|
|
3107
|
-
|
|
3108
|
-
// src/extensions/runner.ts
|
|
3109
|
-
import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
|
|
3110
|
-
|
|
3111
|
-
// src/extensions/event-bus.ts
|
|
3112
|
-
function createEventBus() {
|
|
3113
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
3415
|
+
function toLoopResult(result, toolCallId, toolName) {
|
|
3114
3416
|
return {
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
set.delete(listener);
|
|
3121
|
-
if (set.size === 0) listeners.delete(event);
|
|
3122
|
-
};
|
|
3123
|
-
},
|
|
3124
|
-
emit(event, ...args) {
|
|
3125
|
-
const set = listeners.get(event);
|
|
3126
|
-
if (!set) return;
|
|
3127
|
-
for (const listener of set) {
|
|
3128
|
-
try {
|
|
3129
|
-
listener(...args);
|
|
3130
|
-
} catch (err) {
|
|
3131
|
-
console.error(`[event-bus] Listener error for "${event}":`, err);
|
|
3132
|
-
}
|
|
3133
|
-
}
|
|
3134
|
-
},
|
|
3135
|
-
off(event) {
|
|
3136
|
-
if (event) {
|
|
3137
|
-
listeners.delete(event);
|
|
3138
|
-
} else {
|
|
3139
|
-
listeners.clear();
|
|
3140
|
-
}
|
|
3141
|
-
},
|
|
3142
|
-
has(event) {
|
|
3143
|
-
const set = listeners.get(event);
|
|
3144
|
-
return set !== void 0 && set.size > 0;
|
|
3145
|
-
},
|
|
3146
|
-
count(event) {
|
|
3147
|
-
return listeners.get(event)?.size ?? 0;
|
|
3148
|
-
}
|
|
3417
|
+
toolCallId,
|
|
3418
|
+
toolName,
|
|
3419
|
+
ok: result.ok,
|
|
3420
|
+
content: result.content,
|
|
3421
|
+
...!result.ok ? { error: result.error } : {}
|
|
3149
3422
|
};
|
|
3150
3423
|
}
|
|
3424
|
+
function fromLoopResult(result) {
|
|
3425
|
+
return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
|
|
3426
|
+
}
|
|
3151
3427
|
|
|
3152
|
-
// src/
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
cwd: options?.cwd ?? cwd,
|
|
3160
|
-
env: options?.env ? { ...process.env, ...options.env } : void 0,
|
|
3161
|
-
timeout: options?.timeout,
|
|
3162
|
-
maxBuffer: 10 * 1024 * 1024
|
|
3163
|
-
},
|
|
3164
|
-
(error, stdout, stderr) => {
|
|
3165
|
-
if (error && error.killed && options?.signal?.aborted) {
|
|
3166
|
-
reject(new Error("Command was aborted"));
|
|
3167
|
-
return;
|
|
3168
|
-
}
|
|
3169
|
-
resolve6({
|
|
3170
|
-
exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
3171
|
-
stdout: stdout ?? "",
|
|
3172
|
-
stderr: stderr ?? ""
|
|
3173
|
-
});
|
|
3174
|
-
}
|
|
3175
|
-
);
|
|
3176
|
-
if (options?.signal) {
|
|
3177
|
-
options.signal.addEventListener(
|
|
3178
|
-
"abort",
|
|
3179
|
-
() => {
|
|
3180
|
-
child.kill("SIGTERM");
|
|
3181
|
-
},
|
|
3182
|
-
{ once: true }
|
|
3183
|
-
);
|
|
3184
|
-
}
|
|
3428
|
+
// src/runtime/transient-run-events.ts
|
|
3429
|
+
var MAX_BUFFERED_EVENTS = 128;
|
|
3430
|
+
var MAX_BUFFERED_TEXT = 64 * 1024;
|
|
3431
|
+
function deferred() {
|
|
3432
|
+
let resolve7;
|
|
3433
|
+
const promise = new Promise((done) => {
|
|
3434
|
+
resolve7 = done;
|
|
3185
3435
|
});
|
|
3436
|
+
return { promise, resolve: resolve7 };
|
|
3186
3437
|
}
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3438
|
+
var TransientRunEventSubscription = class {
|
|
3439
|
+
queue = [];
|
|
3440
|
+
wake = deferred();
|
|
3441
|
+
version = 0;
|
|
3442
|
+
stopped = false;
|
|
3443
|
+
push(event) {
|
|
3444
|
+
if (this.stopped) return;
|
|
3445
|
+
const previous = this.queue.at(-1);
|
|
3446
|
+
if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
|
|
3447
|
+
this.queue[this.queue.length - 1] = {
|
|
3448
|
+
...previous,
|
|
3449
|
+
text: previous.text + event.text
|
|
3450
|
+
};
|
|
3451
|
+
} else {
|
|
3452
|
+
if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
|
|
3453
|
+
this.queue.push(event);
|
|
3454
|
+
}
|
|
3455
|
+
this.trimText();
|
|
3456
|
+
this.notify();
|
|
3190
3457
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
id: modelConfig.id,
|
|
3194
|
-
name: modelConfig.name,
|
|
3195
|
-
protocol: config.protocol,
|
|
3196
|
-
provider: config.id,
|
|
3197
|
-
baseUrl: config.baseUrl,
|
|
3198
|
-
apiKey: config.apiKey,
|
|
3199
|
-
reasoning: modelConfig.reasoning,
|
|
3200
|
-
input: modelConfig.input,
|
|
3201
|
-
cost: modelConfig.cost,
|
|
3202
|
-
contextWindow: modelConfig.contextWindow,
|
|
3203
|
-
maxTokens: modelConfig.maxTokens,
|
|
3204
|
-
...config.headers ? { headers: config.headers } : {},
|
|
3205
|
-
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
|
|
3206
|
-
...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {},
|
|
3207
|
-
...config.retryDelayMs !== void 0 ? { retryDelayMs: config.retryDelayMs } : {}
|
|
3208
|
-
});
|
|
3458
|
+
shift() {
|
|
3459
|
+
return this.queue.shift();
|
|
3209
3460
|
}
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
cwd;
|
|
3219
|
-
abortController = new AbortController();
|
|
3220
|
-
_idle = true;
|
|
3221
|
-
// Per-extension tracking
|
|
3222
|
-
extensions = [];
|
|
3223
|
-
// Phase management
|
|
3224
|
-
phases = /* @__PURE__ */ new Map();
|
|
3225
|
-
_phaseCache = null;
|
|
3226
|
-
// Provider management
|
|
3227
|
-
pendingProviders = [];
|
|
3228
|
-
bound = false;
|
|
3229
|
-
// Error listeners
|
|
3230
|
-
errorListeners = /* @__PURE__ */ new Set();
|
|
3231
|
-
/** Current agent context — set by the agent before each phase */
|
|
3232
|
-
currentContext;
|
|
3233
|
-
constructor(options) {
|
|
3234
|
-
this.hooks = new HooksManager();
|
|
3235
|
-
this.runtime = createExtensionRuntime();
|
|
3236
|
-
this.events = createEventBus();
|
|
3237
|
-
this.cwd = options?.cwd ?? process.cwd();
|
|
3461
|
+
clear(executionId) {
|
|
3462
|
+
if (executionId === void 0) this.queue.length = 0;
|
|
3463
|
+
else {
|
|
3464
|
+
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3465
|
+
if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
this.notify();
|
|
3238
3469
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3470
|
+
clearMessage(messageId) {
|
|
3471
|
+
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3472
|
+
const event = this.queue[index];
|
|
3473
|
+
if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
|
|
3474
|
+
}
|
|
3475
|
+
this.notify();
|
|
3242
3476
|
}
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3477
|
+
clearTool(toolCallId) {
|
|
3478
|
+
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3479
|
+
const event = this.queue[index];
|
|
3480
|
+
if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
|
|
3481
|
+
}
|
|
3482
|
+
this.notify();
|
|
3246
3483
|
}
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
return this.abortController.signal;
|
|
3484
|
+
checkpoint() {
|
|
3485
|
+
return this.version;
|
|
3250
3486
|
}
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
this.
|
|
3487
|
+
changed(since) {
|
|
3488
|
+
if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
|
|
3489
|
+
return this.wake.promise;
|
|
3254
3490
|
}
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
* Returns an unsubscribe function.
|
|
3261
|
-
*/
|
|
3262
|
-
onError(listener) {
|
|
3263
|
-
this.errorListeners.add(listener);
|
|
3264
|
-
return () => this.errorListeners.delete(listener);
|
|
3491
|
+
stop() {
|
|
3492
|
+
if (this.stopped) return;
|
|
3493
|
+
this.stopped = true;
|
|
3494
|
+
this.queue.length = 0;
|
|
3495
|
+
this.notify();
|
|
3265
3496
|
}
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3497
|
+
notify() {
|
|
3498
|
+
const wake = this.wake;
|
|
3499
|
+
this.wake = deferred();
|
|
3500
|
+
this.version += 1;
|
|
3501
|
+
wake.resolve();
|
|
3502
|
+
}
|
|
3503
|
+
trimText() {
|
|
3504
|
+
let buffered = this.queue.reduce(
|
|
3505
|
+
(length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
|
|
3506
|
+
0
|
|
3507
|
+
);
|
|
3508
|
+
while (buffered > MAX_BUFFERED_TEXT) {
|
|
3509
|
+
const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
|
|
3510
|
+
if (index < 0) return;
|
|
3511
|
+
const event = this.queue[index];
|
|
3512
|
+
const overflow = buffered - MAX_BUFFERED_TEXT;
|
|
3513
|
+
if (event.text.length <= overflow) {
|
|
3514
|
+
this.queue.splice(index, 1);
|
|
3515
|
+
buffered -= event.text.length;
|
|
3516
|
+
} else {
|
|
3517
|
+
this.queue[index] = {
|
|
3518
|
+
...event,
|
|
3519
|
+
offset: event.offset + overflow,
|
|
3520
|
+
text: event.text.slice(overflow)
|
|
3521
|
+
};
|
|
3522
|
+
buffered -= overflow;
|
|
3275
3523
|
}
|
|
3276
3524
|
}
|
|
3277
3525
|
}
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
this.runtime.invalidate(message);
|
|
3526
|
+
};
|
|
3527
|
+
var TransientRunEventHub = class {
|
|
3528
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
3529
|
+
subscribe(runId) {
|
|
3530
|
+
const subscription = new TransientRunEventSubscription();
|
|
3531
|
+
const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
|
|
3532
|
+
current.add(subscription);
|
|
3533
|
+
this.subscriptions.set(runId, current);
|
|
3534
|
+
return subscription;
|
|
3288
3535
|
}
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
* Returns an unsubscribe function.
|
|
3295
|
-
*
|
|
3296
|
-
* @example
|
|
3297
|
-
* ```ts
|
|
3298
|
-
* const unsub = runner.on("before_tool_call", (event) => {
|
|
3299
|
-
* return { allow: false, reason: "Blocked" };
|
|
3300
|
-
* });
|
|
3301
|
-
* unsub(); // Cancel subscription
|
|
3302
|
-
* ```
|
|
3303
|
-
*/
|
|
3304
|
-
on(type, handler) {
|
|
3305
|
-
this.hooks.on(type, handler);
|
|
3306
|
-
return () => this.hooks.off(type, handler);
|
|
3536
|
+
unsubscribe(runId, subscription) {
|
|
3537
|
+
subscription.stop();
|
|
3538
|
+
const current = this.subscriptions.get(runId);
|
|
3539
|
+
current?.delete(subscription);
|
|
3540
|
+
if (current?.size === 0) this.subscriptions.delete(runId);
|
|
3307
3541
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
/**
|
|
3312
|
-
* Load and initialize extensions.
|
|
3313
|
-
* Creates Extension tracking objects and calls each factory with an ExtensionAPI.
|
|
3314
|
-
*/
|
|
3315
|
-
async loadExtensions(extensions) {
|
|
3316
|
-
for (const ext of extensions) {
|
|
3317
|
-
try {
|
|
3318
|
-
const extension = { path: ext.path, tools: /* @__PURE__ */ new Map() };
|
|
3319
|
-
const api = this.createExtensionAPI(extension, ext.manifest);
|
|
3320
|
-
await ext.factory(api);
|
|
3321
|
-
this.extensions.push(extension);
|
|
3322
|
-
this._phaseCache = null;
|
|
3323
|
-
} catch (error) {
|
|
3324
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
3325
|
-
this.emitError({
|
|
3326
|
-
extensionPath: ext.path,
|
|
3327
|
-
event: "load",
|
|
3328
|
-
error: message,
|
|
3329
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
3330
|
-
});
|
|
3331
|
-
throw error;
|
|
3332
|
-
}
|
|
3542
|
+
publish(event) {
|
|
3543
|
+
for (const subscription of this.subscriptions.get(event.runId) ?? []) {
|
|
3544
|
+
subscription.push(event);
|
|
3333
3545
|
}
|
|
3334
3546
|
}
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3547
|
+
clear(runId, executionId) {
|
|
3548
|
+
for (const subscription of this.subscriptions.get(runId) ?? []) {
|
|
3549
|
+
subscription.clear(executionId);
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
clearMessage(runId, messageId) {
|
|
3553
|
+
for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
|
|
3554
|
+
}
|
|
3555
|
+
clearTool(runId, toolCallId) {
|
|
3556
|
+
for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
|
|
3557
|
+
}
|
|
3558
|
+
close() {
|
|
3559
|
+
for (const subscriptions of this.subscriptions.values()) {
|
|
3560
|
+
for (const subscription of subscriptions) subscription.stop();
|
|
3561
|
+
}
|
|
3562
|
+
this.subscriptions.clear();
|
|
3563
|
+
}
|
|
3564
|
+
};
|
|
3565
|
+
|
|
3566
|
+
// src/extensions/hooks.ts
|
|
3567
|
+
var HookError = class extends Error {
|
|
3568
|
+
constructor(eventType, message, cause) {
|
|
3569
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
3570
|
+
this.eventType = eventType;
|
|
3571
|
+
this.name = "HookError";
|
|
3572
|
+
}
|
|
3573
|
+
eventType;
|
|
3574
|
+
};
|
|
3575
|
+
var HooksManager = class {
|
|
3576
|
+
handlers = /* @__PURE__ */ new Map();
|
|
3577
|
+
on(eventType, handler) {
|
|
3578
|
+
const handlers = this.handlers.get(eventType) ?? [];
|
|
3579
|
+
handlers.push(handler);
|
|
3580
|
+
this.handlers.set(eventType, handlers);
|
|
3581
|
+
}
|
|
3582
|
+
off(eventType, handler) {
|
|
3583
|
+
const handlers = this.handlers.get(eventType);
|
|
3584
|
+
if (!handlers) return;
|
|
3585
|
+
const index = handlers.indexOf(handler);
|
|
3586
|
+
if (index >= 0) handlers.splice(index, 1);
|
|
3587
|
+
}
|
|
3588
|
+
async emit(eventType, event) {
|
|
3589
|
+
const handlers = this.handlers.get(eventType);
|
|
3590
|
+
if (!handlers?.length) return;
|
|
3591
|
+
const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
|
|
3592
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
3593
|
+
if (failure) {
|
|
3594
|
+
throw new HookError(
|
|
3595
|
+
eventType,
|
|
3596
|
+
`${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
|
|
3597
|
+
failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
async emitFirst(eventType, event) {
|
|
3602
|
+
const handlers = this.handlers.get(eventType);
|
|
3603
|
+
if (!handlers?.length) return void 0;
|
|
3604
|
+
for (const handler of handlers) {
|
|
3605
|
+
try {
|
|
3606
|
+
const result = await handler(event);
|
|
3607
|
+
if (result !== void 0) return result;
|
|
3608
|
+
} catch (error) {
|
|
3609
|
+
throw new HookError(
|
|
3610
|
+
eventType,
|
|
3611
|
+
`Handler failed for "${eventType}"`,
|
|
3612
|
+
error instanceof Error ? error : new Error(String(error))
|
|
3613
|
+
);
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
return void 0;
|
|
3617
|
+
}
|
|
3618
|
+
};
|
|
3619
|
+
|
|
3620
|
+
// src/extensions/runner.ts
|
|
3621
|
+
import { execFile } from "child_process";
|
|
3622
|
+
import {
|
|
3623
|
+
registerModel,
|
|
3624
|
+
unregisterProviderModels,
|
|
3625
|
+
registerApiProvider
|
|
3626
|
+
} from "@rowan-agent/models";
|
|
3627
|
+
|
|
3628
|
+
// src/extensions/types.ts
|
|
3629
|
+
function createSourceInfo(extensionPath, options = {}) {
|
|
3630
|
+
const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
|
|
3631
|
+
const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
|
|
3632
|
+
return {
|
|
3633
|
+
source,
|
|
3634
|
+
baseDir: options.baseDir,
|
|
3635
|
+
displayName
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
function createExtensionRuntime() {
|
|
3639
|
+
const state = {};
|
|
3640
|
+
const assertActive = () => {
|
|
3641
|
+
if (state.staleMessage) {
|
|
3642
|
+
throw new Error(state.staleMessage);
|
|
3643
|
+
}
|
|
3644
|
+
};
|
|
3645
|
+
return {
|
|
3646
|
+
assertActive,
|
|
3647
|
+
invalidate: (message) => {
|
|
3648
|
+
state.staleMessage ??= message ?? "This extension context is stale after runtime replacement or reload. Do not use a captured extension API after the runner has been replaced.";
|
|
3649
|
+
}
|
|
3650
|
+
};
|
|
3651
|
+
}
|
|
3652
|
+
|
|
3653
|
+
// src/extensions/runner.ts
|
|
3654
|
+
import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
|
|
3655
|
+
|
|
3656
|
+
// src/extensions/event-bus.ts
|
|
3657
|
+
function createEventBus() {
|
|
3658
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
3659
|
+
return {
|
|
3660
|
+
on(event, listener) {
|
|
3661
|
+
const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
3662
|
+
set.add(listener);
|
|
3663
|
+
listeners.set(event, set);
|
|
3664
|
+
return () => {
|
|
3665
|
+
set.delete(listener);
|
|
3666
|
+
if (set.size === 0) listeners.delete(event);
|
|
3667
|
+
};
|
|
3668
|
+
},
|
|
3669
|
+
emit(event, ...args) {
|
|
3670
|
+
const set = listeners.get(event);
|
|
3671
|
+
if (!set) return;
|
|
3672
|
+
for (const listener of set) {
|
|
3673
|
+
try {
|
|
3674
|
+
listener(...args);
|
|
3675
|
+
} catch (err) {
|
|
3676
|
+
console.error(`[event-bus] Listener error for "${event}":`, err);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
},
|
|
3680
|
+
off(event) {
|
|
3681
|
+
if (event) {
|
|
3682
|
+
listeners.delete(event);
|
|
3683
|
+
} else {
|
|
3684
|
+
listeners.clear();
|
|
3685
|
+
}
|
|
3686
|
+
},
|
|
3687
|
+
has(event) {
|
|
3688
|
+
const set = listeners.get(event);
|
|
3689
|
+
return set !== void 0 && set.size > 0;
|
|
3690
|
+
},
|
|
3691
|
+
count(event) {
|
|
3692
|
+
return listeners.get(event)?.size ?? 0;
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
// src/extensions/runner.ts
|
|
3698
|
+
async function execCommand(command, args, cwd, options) {
|
|
3699
|
+
return new Promise((resolve7, reject) => {
|
|
3700
|
+
const child = execFile(
|
|
3701
|
+
command,
|
|
3702
|
+
args,
|
|
3703
|
+
{
|
|
3704
|
+
cwd: options?.cwd ?? cwd,
|
|
3705
|
+
env: options?.env ? { ...process.env, ...options.env } : void 0,
|
|
3706
|
+
timeout: options?.timeout,
|
|
3707
|
+
maxBuffer: 10 * 1024 * 1024
|
|
3708
|
+
},
|
|
3709
|
+
(error, stdout, stderr) => {
|
|
3710
|
+
if (error && error.killed && options?.signal?.aborted) {
|
|
3711
|
+
reject(new Error("Command was aborted"));
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
resolve7({
|
|
3715
|
+
exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
3716
|
+
stdout: stdout ?? "",
|
|
3717
|
+
stderr: stderr ?? ""
|
|
3718
|
+
});
|
|
3719
|
+
}
|
|
3720
|
+
);
|
|
3721
|
+
if (options?.signal) {
|
|
3722
|
+
options.signal.addEventListener(
|
|
3723
|
+
"abort",
|
|
3724
|
+
() => {
|
|
3725
|
+
child.kill("SIGTERM");
|
|
3726
|
+
},
|
|
3727
|
+
{ once: true }
|
|
3728
|
+
);
|
|
3729
|
+
}
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
function applyProviderRegistration(config) {
|
|
3733
|
+
if (config.streamSimple) {
|
|
3734
|
+
registerApiProvider({ protocol: config.protocol, stream: config.streamSimple });
|
|
3735
|
+
}
|
|
3736
|
+
for (const modelConfig of config.models) {
|
|
3737
|
+
registerModel({
|
|
3738
|
+
id: modelConfig.id,
|
|
3739
|
+
name: modelConfig.name,
|
|
3740
|
+
protocol: config.protocol,
|
|
3741
|
+
provider: config.id,
|
|
3742
|
+
baseUrl: config.baseUrl,
|
|
3743
|
+
apiKey: config.apiKey,
|
|
3744
|
+
reasoning: modelConfig.reasoning,
|
|
3745
|
+
input: modelConfig.input,
|
|
3746
|
+
cost: modelConfig.cost,
|
|
3747
|
+
contextWindow: modelConfig.contextWindow,
|
|
3748
|
+
maxTokens: modelConfig.maxTokens,
|
|
3749
|
+
...config.headers ? { headers: config.headers } : {},
|
|
3750
|
+
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
|
|
3751
|
+
...config.maxRetries !== void 0 ? { maxRetries: config.maxRetries } : {},
|
|
3752
|
+
...config.retryDelayMs !== void 0 ? { retryDelayMs: config.retryDelayMs } : {}
|
|
3753
|
+
});
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
function applyProviderUnregistration(name) {
|
|
3757
|
+
unregisterProviderModels(name);
|
|
3758
|
+
}
|
|
3759
|
+
var ExtensionRunner = class {
|
|
3760
|
+
hooks;
|
|
3761
|
+
runtime;
|
|
3762
|
+
events;
|
|
3763
|
+
cwd;
|
|
3764
|
+
abortController = new AbortController();
|
|
3765
|
+
_idle = true;
|
|
3766
|
+
// Per-extension tracking
|
|
3767
|
+
extensions = [];
|
|
3768
|
+
// Phase management
|
|
3769
|
+
phases = /* @__PURE__ */ new Map();
|
|
3770
|
+
_phaseCache = null;
|
|
3771
|
+
// Provider management
|
|
3772
|
+
pendingProviders = [];
|
|
3773
|
+
bound = false;
|
|
3774
|
+
// Error listeners
|
|
3775
|
+
errorListeners = /* @__PURE__ */ new Set();
|
|
3776
|
+
/** Current agent context — set by the agent before each phase */
|
|
3777
|
+
currentContext;
|
|
3778
|
+
constructor(options) {
|
|
3779
|
+
this.hooks = new HooksManager();
|
|
3780
|
+
this.runtime = createExtensionRuntime();
|
|
3781
|
+
this.events = createEventBus();
|
|
3782
|
+
this.cwd = options?.cwd ?? process.cwd();
|
|
3783
|
+
}
|
|
3784
|
+
/** Whether the agent is currently idle (not streaming). */
|
|
3785
|
+
get isIdle() {
|
|
3786
|
+
return this._idle;
|
|
3787
|
+
}
|
|
3788
|
+
/** Set idle state — called by the agent loop. */
|
|
3789
|
+
setIdle(idle) {
|
|
3790
|
+
this._idle = idle;
|
|
3791
|
+
}
|
|
3792
|
+
/** Abort signal for the current runner instance. */
|
|
3793
|
+
get signal() {
|
|
3794
|
+
return this.abortController.signal;
|
|
3795
|
+
}
|
|
3796
|
+
/** Abort the current runner operation. */
|
|
3797
|
+
abort() {
|
|
3798
|
+
this.abortController.abort();
|
|
3799
|
+
}
|
|
3800
|
+
// ---------------------------------------------------------------------------
|
|
3801
|
+
// Error handling
|
|
3802
|
+
// ---------------------------------------------------------------------------
|
|
3803
|
+
/**
|
|
3804
|
+
* Register an error listener.
|
|
3805
|
+
* Returns an unsubscribe function.
|
|
3806
|
+
*/
|
|
3807
|
+
onError(listener) {
|
|
3808
|
+
this.errorListeners.add(listener);
|
|
3809
|
+
return () => this.errorListeners.delete(listener);
|
|
3810
|
+
}
|
|
3811
|
+
/**
|
|
3812
|
+
* Emit a structured extension error to all listeners.
|
|
3813
|
+
*/
|
|
3814
|
+
emitError(error) {
|
|
3815
|
+
for (const listener of this.errorListeners) {
|
|
3816
|
+
try {
|
|
3817
|
+
listener(error);
|
|
3818
|
+
} catch (err) {
|
|
3819
|
+
console.error("[extension-runner] Error listener failed:", err);
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
// ---------------------------------------------------------------------------
|
|
3824
|
+
// Lifecycle: invalidate / assertActive
|
|
3825
|
+
// ---------------------------------------------------------------------------
|
|
3826
|
+
/**
|
|
3827
|
+
* Mark all extension contexts as stale.
|
|
3828
|
+
* After calling this, any captured ExtensionAPI or ExtensionContext will throw
|
|
3829
|
+
* on use. Used during runtime replacement or reload.
|
|
3830
|
+
*/
|
|
3831
|
+
invalidate(message) {
|
|
3832
|
+
this.runtime.invalidate(message);
|
|
3833
|
+
for (const extension of this.extensions) extension.runtime.invalidate(message);
|
|
3834
|
+
}
|
|
3835
|
+
// ---------------------------------------------------------------------------
|
|
3836
|
+
// Direct hook API
|
|
3837
|
+
// ---------------------------------------------------------------------------
|
|
3838
|
+
/**
|
|
3839
|
+
* Subscribe to a specific hook event type.
|
|
3840
|
+
* Returns an unsubscribe function.
|
|
3841
|
+
*
|
|
3842
|
+
* @example
|
|
3843
|
+
* ```ts
|
|
3844
|
+
* const unsub = runner.on("before_tool_call", (event) => {
|
|
3845
|
+
* return { allow: false, reason: "Blocked" };
|
|
3846
|
+
* });
|
|
3847
|
+
* unsub(); // Cancel subscription
|
|
3848
|
+
* ```
|
|
3849
|
+
*/
|
|
3850
|
+
on(type, handler) {
|
|
3851
|
+
this.hooks.on(type, handler);
|
|
3852
|
+
return () => this.hooks.off(type, handler);
|
|
3853
|
+
}
|
|
3854
|
+
// ---------------------------------------------------------------------------
|
|
3855
|
+
// Extension loading
|
|
3856
|
+
// ---------------------------------------------------------------------------
|
|
3857
|
+
/**
|
|
3858
|
+
* Load and initialize extensions.
|
|
3859
|
+
* Creates Extension tracking objects and calls each factory with an ExtensionAPI.
|
|
3860
|
+
*/
|
|
3861
|
+
async loadExtensions(extensions) {
|
|
3862
|
+
for (const ext of extensions) {
|
|
3863
|
+
const extension = {
|
|
3864
|
+
path: ext.path,
|
|
3865
|
+
tools: /* @__PURE__ */ new Map(),
|
|
3866
|
+
phases: /* @__PURE__ */ new Set(),
|
|
3867
|
+
cleanup: [],
|
|
3868
|
+
runtime: createExtensionRuntime()
|
|
3869
|
+
};
|
|
3870
|
+
try {
|
|
3871
|
+
const api = this.createExtensionAPI(extension, ext.manifest);
|
|
3872
|
+
const disposer = await ext.factory(api);
|
|
3873
|
+
if (typeof disposer === "function") extension.disposer = disposer;
|
|
3874
|
+
this.extensions.push(extension);
|
|
3875
|
+
this._phaseCache = null;
|
|
3876
|
+
} catch (error) {
|
|
3877
|
+
await this.rollbackExtension(extension);
|
|
3878
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3879
|
+
this.emitError({
|
|
3880
|
+
extensionPath: ext.path,
|
|
3881
|
+
event: "load",
|
|
3882
|
+
error: message,
|
|
3883
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
3884
|
+
});
|
|
3885
|
+
throw error;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
/** Dispose all active Extensions in reverse activation order. */
|
|
3890
|
+
async close() {
|
|
3891
|
+
this.abortController.abort();
|
|
3892
|
+
for (const extension of [...this.extensions].reverse()) {
|
|
3893
|
+
await this.disposeExtension(extension, "This Extension Runtime has been closed.");
|
|
3894
|
+
}
|
|
3895
|
+
this.extensions.length = 0;
|
|
3896
|
+
this.phases.clear();
|
|
3897
|
+
this._phaseCache = null;
|
|
3898
|
+
this.runtime.invalidate("This Extension Runtime has been closed.");
|
|
3899
|
+
}
|
|
3900
|
+
// ---------------------------------------------------------------------------
|
|
3901
|
+
// Tool management
|
|
3902
|
+
// ---------------------------------------------------------------------------
|
|
3903
|
+
/** Get all registered tools from all extensions. */
|
|
3904
|
+
getAllRegisteredTools() {
|
|
3905
|
+
const toolsByName = /* @__PURE__ */ new Map();
|
|
3341
3906
|
for (const ext of this.extensions) {
|
|
3342
3907
|
for (const tool of ext.tools.values()) {
|
|
3343
3908
|
if (!toolsByName.has(tool.definition.name)) {
|
|
@@ -3345,498 +3910,1112 @@ var ExtensionRunner = class {
|
|
|
3345
3910
|
}
|
|
3346
3911
|
}
|
|
3347
3912
|
}
|
|
3348
|
-
return Array.from(toolsByName.values());
|
|
3913
|
+
return Array.from(toolsByName.values());
|
|
3914
|
+
}
|
|
3915
|
+
/**
|
|
3916
|
+
* Get a tool definition by name. Returns undefined if not found.
|
|
3917
|
+
*/
|
|
3918
|
+
getToolDefinition(toolName) {
|
|
3919
|
+
for (const ext of this.extensions) {
|
|
3920
|
+
const tool = ext.tools.get(toolName);
|
|
3921
|
+
if (tool) return tool.definition;
|
|
3922
|
+
}
|
|
3923
|
+
return void 0;
|
|
3924
|
+
}
|
|
3925
|
+
// ---------------------------------------------------------------------------
|
|
3926
|
+
// Phase management
|
|
3927
|
+
// ---------------------------------------------------------------------------
|
|
3928
|
+
getPhase(name) {
|
|
3929
|
+
const reg = this.getRegisteredPhase(name);
|
|
3930
|
+
if (!reg) return void 0;
|
|
3931
|
+
return this.adaptToPhase(reg);
|
|
3932
|
+
}
|
|
3933
|
+
getPhases() {
|
|
3934
|
+
return [...this.collectRegisteredPhases().values()].map(
|
|
3935
|
+
(p) => this.adaptToPhase(p)
|
|
3936
|
+
);
|
|
3937
|
+
}
|
|
3938
|
+
createPhaseRegistry(input = {}) {
|
|
3939
|
+
const registered = this.collectRegisteredPhases();
|
|
3940
|
+
const phases = /* @__PURE__ */ new Map();
|
|
3941
|
+
for (const [name, reg] of registered) {
|
|
3942
|
+
phases.set(name, this.adaptToPhase(reg));
|
|
3943
|
+
}
|
|
3944
|
+
const entryPhaseId = input.entryPhaseId ?? null;
|
|
3945
|
+
return { phases, entryPhaseId };
|
|
3946
|
+
}
|
|
3947
|
+
/** Adapt an extension RegisteredPhase to the core Phase type. */
|
|
3948
|
+
adaptToPhase(reg) {
|
|
3949
|
+
const def = reg.definition;
|
|
3950
|
+
return {
|
|
3951
|
+
name: def.name,
|
|
3952
|
+
description: def.description,
|
|
3953
|
+
tools: def.tools,
|
|
3954
|
+
skills: def.skills,
|
|
3955
|
+
target: def.target,
|
|
3956
|
+
input: def.input,
|
|
3957
|
+
run: def.run,
|
|
3958
|
+
filePath: "",
|
|
3959
|
+
baseDir: "",
|
|
3960
|
+
content: "",
|
|
3961
|
+
...def.model ? { model: parseModelRef2(def.model) } : {}
|
|
3962
|
+
};
|
|
3963
|
+
}
|
|
3964
|
+
// ---------------------------------------------------------------------------
|
|
3965
|
+
// Lifecycle
|
|
3966
|
+
// ---------------------------------------------------------------------------
|
|
3967
|
+
/**
|
|
3968
|
+
* Bind the runner — flushes pending provider registrations and
|
|
3969
|
+
* replaces runtime stubs with real implementations.
|
|
3970
|
+
*/
|
|
3971
|
+
bind() {
|
|
3972
|
+
if (this.bound) return;
|
|
3973
|
+
this.bound = true;
|
|
3974
|
+
this.flushPendingProviders();
|
|
3975
|
+
}
|
|
3976
|
+
// ---------------------------------------------------------------------------
|
|
3977
|
+
// Unified hook emission
|
|
3978
|
+
// ---------------------------------------------------------------------------
|
|
3979
|
+
/**
|
|
3980
|
+
* Generic emit — fire-and-forget for any event type.
|
|
3981
|
+
*/
|
|
3982
|
+
async emit(eventType, event) {
|
|
3983
|
+
await this.hooks.emit(eventType, event);
|
|
3984
|
+
}
|
|
3985
|
+
/**
|
|
3986
|
+
* Unified hook emission — returns the first non-undefined result.
|
|
3987
|
+
*/
|
|
3988
|
+
async emitHook(type, event) {
|
|
3989
|
+
return this.hooks.emitFirst(type, event);
|
|
3990
|
+
}
|
|
3991
|
+
// ---------------------------------------------------------------------------
|
|
3992
|
+
// Phase hooks (with inline processing)
|
|
3993
|
+
// ---------------------------------------------------------------------------
|
|
3994
|
+
async emitBeforePhase(phaseId, input) {
|
|
3995
|
+
const result = await this.emitHook("before_phase", {
|
|
3996
|
+
type: "before_phase",
|
|
3997
|
+
phaseId,
|
|
3998
|
+
input
|
|
3999
|
+
});
|
|
4000
|
+
return result ?? {};
|
|
4001
|
+
}
|
|
4002
|
+
async emitAfterPhase(phaseId, output) {
|
|
4003
|
+
const result = await this.emitHook("after_phase", {
|
|
4004
|
+
type: "after_phase",
|
|
4005
|
+
phaseId,
|
|
4006
|
+
output
|
|
4007
|
+
});
|
|
4008
|
+
return result ?? {};
|
|
4009
|
+
}
|
|
4010
|
+
async emitBeforePrompt(phaseId, input) {
|
|
4011
|
+
const result = await this.emitHook("before_prompt", {
|
|
4012
|
+
type: "before_prompt",
|
|
4013
|
+
phaseId,
|
|
4014
|
+
input
|
|
4015
|
+
});
|
|
4016
|
+
return result?.input ?? input;
|
|
4017
|
+
}
|
|
4018
|
+
async emitBeforeToolCall(tool, args) {
|
|
4019
|
+
const result = await this.emitHook("before_tool_call", {
|
|
4020
|
+
type: "before_tool_call",
|
|
4021
|
+
tool,
|
|
4022
|
+
args
|
|
4023
|
+
});
|
|
4024
|
+
return result ?? { allow: true };
|
|
4025
|
+
}
|
|
4026
|
+
async emitAfterToolCall(tool, result) {
|
|
4027
|
+
const hookResult = await this.emitHook("after_tool_call", {
|
|
4028
|
+
type: "after_tool_call",
|
|
4029
|
+
tool,
|
|
4030
|
+
result
|
|
4031
|
+
});
|
|
4032
|
+
return hookResult?.result ?? result;
|
|
4033
|
+
}
|
|
4034
|
+
// ---------------------------------------------------------------------------
|
|
4035
|
+
// Internal helpers
|
|
4036
|
+
// ---------------------------------------------------------------------------
|
|
4037
|
+
/**
|
|
4038
|
+
* Create an ExtensionAPI for a specific extension.
|
|
4039
|
+
* Registration methods write to the extension tracking object.
|
|
4040
|
+
* Action methods delegate to the shared runtime.
|
|
4041
|
+
*/
|
|
4042
|
+
createExtensionAPI(extension, manifest) {
|
|
4043
|
+
const runner = this;
|
|
4044
|
+
const extContext = {
|
|
4045
|
+
get cwd() {
|
|
4046
|
+
return runner.cwd;
|
|
4047
|
+
},
|
|
4048
|
+
get signal() {
|
|
4049
|
+
return runner.abortController.signal;
|
|
4050
|
+
},
|
|
4051
|
+
isIdle() {
|
|
4052
|
+
return runner._idle;
|
|
4053
|
+
},
|
|
4054
|
+
abort() {
|
|
4055
|
+
runner.abortController.abort();
|
|
4056
|
+
},
|
|
4057
|
+
exec(command, args, options) {
|
|
4058
|
+
return execCommand(command, args, runner.cwd, options);
|
|
4059
|
+
},
|
|
4060
|
+
manifest,
|
|
4061
|
+
getSystemPrompt() {
|
|
4062
|
+
return runner.currentContext?.systemPrompt ?? "";
|
|
4063
|
+
},
|
|
4064
|
+
setSystemPrompt(prompt) {
|
|
4065
|
+
if (runner.currentContext) runner.currentContext.systemPrompt = prompt;
|
|
4066
|
+
},
|
|
4067
|
+
getMessages() {
|
|
4068
|
+
return runner.currentContext?.messages ?? [];
|
|
4069
|
+
},
|
|
4070
|
+
addMessage(role, content) {
|
|
4071
|
+
runner.currentContext?.messages.push({ role, content });
|
|
4072
|
+
},
|
|
4073
|
+
getAvailableTools() {
|
|
4074
|
+
return (runner.currentContext?.tools ?? []).map((t) => ({ name: t.name, description: t.description }));
|
|
4075
|
+
},
|
|
4076
|
+
getAvailableSkills() {
|
|
4077
|
+
return (runner.currentContext?.skills ?? []).map((s) => ({ name: s.name, description: s.description }));
|
|
4078
|
+
},
|
|
4079
|
+
getSkillContent(skillName) {
|
|
4080
|
+
const skill = runner.currentContext?.skills.find((s) => s.name === skillName);
|
|
4081
|
+
return skill?.content ?? "";
|
|
4082
|
+
},
|
|
4083
|
+
getAvailablePhases() {
|
|
4084
|
+
return [...runner.currentContext?.phases?.phases.keys() ?? []];
|
|
4085
|
+
},
|
|
4086
|
+
getPhaseContent(phaseId) {
|
|
4087
|
+
const phase = runner.currentContext?.phases?.phases.get(phaseId);
|
|
4088
|
+
return phase?.content || phase?.description || "";
|
|
4089
|
+
}
|
|
4090
|
+
};
|
|
4091
|
+
return createExtensionAPI(this.hooks, {
|
|
4092
|
+
registerPhase: (registration) => this.registerPhase(extension, registration),
|
|
4093
|
+
registerProvider: (config) => this.registerProvider(config),
|
|
4094
|
+
unregisterProvider: (name) => this.unregisterProvider(name),
|
|
4095
|
+
registerTool: (tool) => this.registerTool(extension, tool),
|
|
4096
|
+
context: extContext,
|
|
4097
|
+
manifest,
|
|
4098
|
+
trackCleanup: (cleanup) => extension.cleanup.push(cleanup)
|
|
4099
|
+
}, extension.runtime, this.events);
|
|
4100
|
+
}
|
|
4101
|
+
registerTool(extension, tool) {
|
|
4102
|
+
if (extension.tools.has(tool.name)) {
|
|
4103
|
+
throw new Error(`Duplicate Tool name "${tool.name}" in extension ${extension.path}.`);
|
|
4104
|
+
}
|
|
4105
|
+
for (const ext of this.extensions) {
|
|
4106
|
+
if (ext.tools.has(tool.name)) {
|
|
4107
|
+
const message = `Duplicate Tool name "${tool.name}" from extensions ${ext.path} and ${extension.path}.`;
|
|
4108
|
+
this.emitError({
|
|
4109
|
+
extensionPath: extension.path,
|
|
4110
|
+
event: "register_tool",
|
|
4111
|
+
error: message
|
|
4112
|
+
});
|
|
4113
|
+
throw new Error(message);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
const sourceInfo = createSourceInfo(extension.path);
|
|
4117
|
+
extension.tools.set(tool.name, {
|
|
4118
|
+
definition: tool,
|
|
4119
|
+
sourceInfo
|
|
4120
|
+
});
|
|
3349
4121
|
}
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
getToolDefinition(toolName) {
|
|
3354
|
-
for (const ext of this.extensions) {
|
|
3355
|
-
const tool = ext.tools.get(toolName);
|
|
3356
|
-
if (tool) return tool.definition;
|
|
4122
|
+
registerPhase(extension, registration) {
|
|
4123
|
+
if (typeof registration.name !== "string" || registration.name.length === 0) {
|
|
4124
|
+
throw new Error(`Phase registration requires a "name" field.`);
|
|
3357
4125
|
}
|
|
3358
|
-
|
|
4126
|
+
const name = registration.name;
|
|
4127
|
+
const description = validateDescription(registration.description);
|
|
4128
|
+
const errors = validateResourceId(name, "name");
|
|
4129
|
+
if (description.missing) errors.push(...description.warnings);
|
|
4130
|
+
errors.push(...validateSkillReferences(registration.skills));
|
|
4131
|
+
errors.push(...validatePhaseTarget(registration.target));
|
|
4132
|
+
if (errors.length > 0) {
|
|
4133
|
+
throw new Error(`Invalid phase registration "${name}": ${errors.join("; ")}`);
|
|
4134
|
+
}
|
|
4135
|
+
if (description.warnings.length > 0) {
|
|
4136
|
+
warnResourceDiagnostics("phase", `extension ${extension.path}`, description.warnings);
|
|
4137
|
+
}
|
|
4138
|
+
if (this.phases.has(name)) {
|
|
4139
|
+
throw new Error(`Duplicate phase name: ${name}`);
|
|
4140
|
+
}
|
|
4141
|
+
const definition = {
|
|
4142
|
+
name,
|
|
4143
|
+
description: description.description,
|
|
4144
|
+
run: registration.run,
|
|
4145
|
+
...registration.tools ? { tools: registration.tools.slice() } : {},
|
|
4146
|
+
...registration.skills ? { skills: registration.skills.slice() } : {},
|
|
4147
|
+
...registration.target ? { target: registration.target } : {},
|
|
4148
|
+
...registration.input ? { input: { ...registration.input } } : {},
|
|
4149
|
+
...registration.model ? { model: registration.model } : {}
|
|
4150
|
+
};
|
|
4151
|
+
const registered = {
|
|
4152
|
+
definition,
|
|
4153
|
+
source: { extensionPath: extension.path }
|
|
4154
|
+
};
|
|
4155
|
+
this.phases.set(name, registered);
|
|
4156
|
+
extension.phases.add(name);
|
|
4157
|
+
this._phaseCache = null;
|
|
3359
4158
|
}
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
return this.adaptToPhase(reg);
|
|
4159
|
+
registerProvider(config) {
|
|
4160
|
+
if (this.bound) {
|
|
4161
|
+
applyProviderRegistration(config);
|
|
4162
|
+
} else {
|
|
4163
|
+
this.pendingProviders.push({ kind: "register", config });
|
|
4164
|
+
}
|
|
3367
4165
|
}
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
(
|
|
3371
|
-
|
|
4166
|
+
unregisterProvider(name) {
|
|
4167
|
+
if (this.bound) {
|
|
4168
|
+
applyProviderUnregistration(name);
|
|
4169
|
+
} else {
|
|
4170
|
+
this.pendingProviders.push({ kind: "unregister", name });
|
|
4171
|
+
}
|
|
3372
4172
|
}
|
|
3373
|
-
|
|
3374
|
-
const
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
4173
|
+
flushPendingProviders() {
|
|
4174
|
+
for (const action of this.pendingProviders) {
|
|
4175
|
+
if (action.kind === "register") {
|
|
4176
|
+
applyProviderRegistration(action.config);
|
|
4177
|
+
} else {
|
|
4178
|
+
applyProviderUnregistration(action.name);
|
|
4179
|
+
}
|
|
3378
4180
|
}
|
|
3379
|
-
|
|
3380
|
-
return { phases, entryPhaseId };
|
|
4181
|
+
this.pendingProviders.length = 0;
|
|
3381
4182
|
}
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
4183
|
+
getRegisteredPhase(name) {
|
|
4184
|
+
return this.collectRegisteredPhases().get(name);
|
|
4185
|
+
}
|
|
4186
|
+
collectRegisteredPhases() {
|
|
4187
|
+
if (this._phaseCache) return this._phaseCache;
|
|
4188
|
+
this._phaseCache = new Map(this.phases);
|
|
4189
|
+
return this._phaseCache;
|
|
4190
|
+
}
|
|
4191
|
+
async rollbackExtension(extension) {
|
|
4192
|
+
await this.disposeExtension(extension, "This Extension activation was rolled back.");
|
|
4193
|
+
}
|
|
4194
|
+
async disposeExtension(extension, message) {
|
|
4195
|
+
extension.runtime.invalidate(message);
|
|
4196
|
+
for (const name of extension.phases) this.phases.delete(name);
|
|
4197
|
+
for (const cleanup of [...extension.cleanup].reverse()) {
|
|
4198
|
+
await Promise.resolve().then(() => cleanup()).catch(() => void 0);
|
|
4199
|
+
}
|
|
4200
|
+
if (extension.disposer) {
|
|
4201
|
+
await Promise.resolve().then(() => extension.disposer()).catch(() => void 0);
|
|
4202
|
+
}
|
|
4203
|
+
this._phaseCache = null;
|
|
4204
|
+
}
|
|
4205
|
+
};
|
|
4206
|
+
function createExtensionRunner(options) {
|
|
4207
|
+
return new ExtensionRunner(options);
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
// src/extensions/loader.ts
|
|
4211
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
4212
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
4213
|
+
import { fileURLToPath } from "url";
|
|
4214
|
+
import { basename as basename3, dirname as dirname4, extname as extname2, join as join2, resolve as resolve4 } from "path";
|
|
4215
|
+
import { createJiti } from "jiti";
|
|
4216
|
+
function isExtensionFile(path) {
|
|
4217
|
+
const extension = extname2(path).toLowerCase();
|
|
4218
|
+
return extension === ".ts" || extension === ".js";
|
|
4219
|
+
}
|
|
4220
|
+
function isSyntheticPath(path) {
|
|
4221
|
+
return path.startsWith("<") && path.endsWith(">");
|
|
4222
|
+
}
|
|
4223
|
+
function readManifestSync(dir) {
|
|
4224
|
+
const manifestPath = join2(dir, "package.json");
|
|
4225
|
+
if (!existsSync2(manifestPath)) {
|
|
4226
|
+
return void 0;
|
|
4227
|
+
}
|
|
4228
|
+
try {
|
|
4229
|
+
const content = readFileSync(manifestPath, "utf8");
|
|
4230
|
+
const pkg = JSON.parse(content);
|
|
4231
|
+
const rowan = pkg.rowan;
|
|
4232
|
+
if (!rowan) return void 0;
|
|
3385
4233
|
return {
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
tools: def.tools,
|
|
3389
|
-
skills: def.skills,
|
|
3390
|
-
target: def.target,
|
|
3391
|
-
input: def.input,
|
|
3392
|
-
run: def.run,
|
|
3393
|
-
filePath: "",
|
|
3394
|
-
baseDir: "",
|
|
3395
|
-
content: "",
|
|
3396
|
-
...def.model ? { model: parseModelRef2(def.model) } : {}
|
|
4234
|
+
entry: rowan.extensions?.[0],
|
|
4235
|
+
phase: rowan.phase
|
|
3397
4236
|
};
|
|
4237
|
+
} catch {
|
|
4238
|
+
return void 0;
|
|
3398
4239
|
}
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
4240
|
+
}
|
|
4241
|
+
function jitiAliases() {
|
|
4242
|
+
const moduleUrl = import.meta.url;
|
|
4243
|
+
if (!moduleUrl) {
|
|
4244
|
+
return {};
|
|
4245
|
+
}
|
|
4246
|
+
const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
|
|
4247
|
+
if (!existsSync2(agentSourcePath)) {
|
|
4248
|
+
return {};
|
|
4249
|
+
}
|
|
4250
|
+
return { "@rowan-agent/agent": agentSourcePath };
|
|
4251
|
+
}
|
|
4252
|
+
var sharedJiti;
|
|
4253
|
+
function getJiti() {
|
|
4254
|
+
sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
|
|
4255
|
+
fsCache: false,
|
|
4256
|
+
moduleCache: false,
|
|
4257
|
+
tryNative: false,
|
|
4258
|
+
alias: jitiAliases()
|
|
4259
|
+
});
|
|
4260
|
+
return sharedJiti;
|
|
4261
|
+
}
|
|
4262
|
+
async function loadExtensionModule(extensionPath) {
|
|
4263
|
+
const jiti = getJiti();
|
|
4264
|
+
const module = await jiti.import(extensionPath, { default: true });
|
|
4265
|
+
return typeof module === "function" ? module : void 0;
|
|
4266
|
+
}
|
|
4267
|
+
async function readPackageManifest(path) {
|
|
4268
|
+
if (!existsSync2(path)) {
|
|
4269
|
+
return void 0;
|
|
4270
|
+
}
|
|
4271
|
+
const content = await readFile3(path, "utf8");
|
|
4272
|
+
return JSON.parse(content);
|
|
4273
|
+
}
|
|
4274
|
+
async function resolveExtensionEntries(dir) {
|
|
4275
|
+
const manifest = await readPackageManifest(join2(dir, "package.json"));
|
|
4276
|
+
const declared = manifest?.rowan?.extensions;
|
|
4277
|
+
if (declared && declared.length > 0) {
|
|
4278
|
+
return declared.map((entry) => resolve4(dir, entry));
|
|
4279
|
+
}
|
|
4280
|
+
for (const name of ["index.ts", "index.js"]) {
|
|
4281
|
+
const entry = join2(dir, name);
|
|
4282
|
+
if (existsSync2(entry)) {
|
|
4283
|
+
return [entry];
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
return void 0;
|
|
4287
|
+
}
|
|
4288
|
+
async function discoverExtensionsInDir(dir) {
|
|
4289
|
+
const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
|
|
4290
|
+
if (error.code === "ENOENT") {
|
|
4291
|
+
return [];
|
|
4292
|
+
}
|
|
4293
|
+
throw error;
|
|
4294
|
+
});
|
|
4295
|
+
const paths = [];
|
|
4296
|
+
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4297
|
+
const entryPath = join2(dir, entry.name);
|
|
4298
|
+
if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
|
|
4299
|
+
paths.push(entryPath);
|
|
4300
|
+
continue;
|
|
4301
|
+
}
|
|
4302
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
4303
|
+
const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
|
|
4304
|
+
if (entry.isDirectory() || info?.isDirectory()) {
|
|
4305
|
+
const resolvedEntries = await resolveExtensionEntries(entryPath);
|
|
4306
|
+
if (resolvedEntries) {
|
|
4307
|
+
paths.push(...resolvedEntries);
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
return paths;
|
|
4313
|
+
}
|
|
4314
|
+
function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
|
|
4315
|
+
const resolvedCwd = resolve4(cwd);
|
|
4316
|
+
const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve4(resolvedCwd, extensionPath);
|
|
4317
|
+
const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname4(resolvedPath)) || "unknown";
|
|
4318
|
+
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname4(resolvedPath);
|
|
4319
|
+
const manifest = readManifestSync(manifestDir);
|
|
4320
|
+
return {
|
|
4321
|
+
path: resolvedPath,
|
|
4322
|
+
name,
|
|
4323
|
+
factory,
|
|
4324
|
+
manifest
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
async function loadExtensions(paths, cwd) {
|
|
4328
|
+
const extensions = [];
|
|
4329
|
+
const errors = [];
|
|
4330
|
+
const resolvedCwd = resolve4(cwd);
|
|
4331
|
+
for (const path of paths) {
|
|
4332
|
+
const resolvedPath = resolve4(resolvedCwd, path);
|
|
4333
|
+
try {
|
|
4334
|
+
const factory = await loadExtensionModule(resolvedPath);
|
|
4335
|
+
if (!factory) {
|
|
4336
|
+
errors.push({
|
|
4337
|
+
path: resolvedPath,
|
|
4338
|
+
error: `Extension does not export a valid factory function: ${path}`
|
|
4339
|
+
});
|
|
4340
|
+
continue;
|
|
4341
|
+
}
|
|
4342
|
+
extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
|
|
4343
|
+
} catch (error) {
|
|
4344
|
+
errors.push({
|
|
4345
|
+
path: resolvedPath,
|
|
4346
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4347
|
+
});
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
return { extensions, errors };
|
|
4351
|
+
}
|
|
4352
|
+
async function loadExtensionsFromPath(path) {
|
|
4353
|
+
const resolvedPath = resolve4(path);
|
|
4354
|
+
const info = await stat2(resolvedPath);
|
|
4355
|
+
if (!info.isDirectory()) {
|
|
4356
|
+
return loadExtensions([resolvedPath], dirname4(resolvedPath));
|
|
3410
4357
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
4358
|
+
const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
|
|
4359
|
+
return loadExtensions(entries, resolvedPath);
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
// src/runtime/resource-registry.ts
|
|
4363
|
+
import { stat as stat3, readdir as readdir4 } from "fs/promises";
|
|
4364
|
+
import { join as join4, resolve as resolve6 } from "path";
|
|
4365
|
+
|
|
4366
|
+
// src/harness/skills.ts
|
|
4367
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
4368
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
4369
|
+
import { dirname as dirname5, join as join3, resolve as resolve5 } from "path";
|
|
4370
|
+
var SKILL_MARKER = "SKILL.md";
|
|
4371
|
+
function resolveSkillPath(input) {
|
|
4372
|
+
const resolved = resolve5(input);
|
|
4373
|
+
return existsSync3(resolved) && statSync2(resolved).isDirectory() ? join3(resolved, SKILL_MARKER) : resolved;
|
|
4374
|
+
}
|
|
4375
|
+
async function loadSkill(path) {
|
|
4376
|
+
const resolved = resolveSkillPath(path);
|
|
4377
|
+
let loaded;
|
|
4378
|
+
try {
|
|
4379
|
+
loaded = await loadMarkdown(resolved);
|
|
4380
|
+
} catch (error) {
|
|
4381
|
+
if (!(error instanceof FrontmatterParseError)) throw error;
|
|
4382
|
+
warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
|
|
4383
|
+
throw new ResourceMetadataError("parse_failed", error.message);
|
|
3419
4384
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
4385
|
+
const { frontmatter, body } = loaded;
|
|
4386
|
+
const metadata = frontmatter;
|
|
4387
|
+
const id = inferResourceName(resolved, SKILL_MARKER);
|
|
4388
|
+
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
4389
|
+
const name = frontmatterName || id;
|
|
4390
|
+
const description = validateDescription(metadata.description);
|
|
4391
|
+
const diagnostics = validateResourceId(id);
|
|
4392
|
+
if (frontmatterName && frontmatterName !== id) {
|
|
4393
|
+
diagnostics.push(...validateResourceName(frontmatterName, id));
|
|
3425
4394
|
}
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
const result = await this.emitHook("before_phase", {
|
|
3431
|
-
type: "before_phase",
|
|
3432
|
-
phaseId,
|
|
3433
|
-
input
|
|
3434
|
-
});
|
|
3435
|
-
return result ?? {};
|
|
4395
|
+
diagnostics.push(...description.warnings);
|
|
4396
|
+
warnResourceDiagnostics("skill", resolved, diagnostics);
|
|
4397
|
+
if (description.missing) {
|
|
4398
|
+
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
3436
4399
|
}
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
4400
|
+
return {
|
|
4401
|
+
name,
|
|
4402
|
+
description: description.description,
|
|
4403
|
+
filePath: resolved,
|
|
4404
|
+
baseDir: dirname5(resolved),
|
|
4405
|
+
content: body,
|
|
4406
|
+
disableModelInvocation: metadata["disable-model-invocation"] === true
|
|
4407
|
+
};
|
|
4408
|
+
}
|
|
4409
|
+
async function loadSkills(targetPath) {
|
|
4410
|
+
const skillsDir = resolve5(targetPath);
|
|
4411
|
+
if (existsSync3(skillsDir) && statSync2(skillsDir).isFile()) {
|
|
4412
|
+
try {
|
|
4413
|
+
return [await loadSkill(skillsDir)];
|
|
4414
|
+
} catch (error) {
|
|
4415
|
+
if (error instanceof ResourceMetadataError) return [];
|
|
4416
|
+
throw error;
|
|
4417
|
+
}
|
|
3444
4418
|
}
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
4419
|
+
if (existsSync3(join3(skillsDir, SKILL_MARKER))) {
|
|
4420
|
+
try {
|
|
4421
|
+
return [await loadSkill(skillsDir)];
|
|
4422
|
+
} catch (error) {
|
|
4423
|
+
if (error instanceof ResourceMetadataError) return [];
|
|
4424
|
+
throw error;
|
|
4425
|
+
}
|
|
3452
4426
|
}
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
4427
|
+
const entries = await readdir3(skillsDir, { withFileTypes: true });
|
|
4428
|
+
const skills = [];
|
|
4429
|
+
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4430
|
+
if (!entry.isDirectory()) continue;
|
|
4431
|
+
const skillFile = join3(skillsDir, entry.name, SKILL_MARKER);
|
|
4432
|
+
if (!existsSync3(skillFile)) continue;
|
|
4433
|
+
try {
|
|
4434
|
+
const skill = await loadSkill(skillFile);
|
|
4435
|
+
skills.push(skill);
|
|
4436
|
+
} catch (error) {
|
|
4437
|
+
if (error instanceof ResourceMetadataError) continue;
|
|
4438
|
+
console.warn(`Failed to load skill "${entry.name}":`, error);
|
|
4439
|
+
}
|
|
3460
4440
|
}
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
4441
|
+
return skills;
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
// src/runtime/resource-registry.ts
|
|
4445
|
+
var ResourceRegistryError = class extends Error {
|
|
4446
|
+
code;
|
|
4447
|
+
constructor(code, message) {
|
|
4448
|
+
super(message);
|
|
4449
|
+
this.name = "ResourceRegistryError";
|
|
4450
|
+
this.code = code;
|
|
3468
4451
|
}
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
const
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
4452
|
+
};
|
|
4453
|
+
var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
4454
|
+
var SOURCE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
4455
|
+
var ResourceRegistry = class {
|
|
4456
|
+
sources = /* @__PURE__ */ new Map();
|
|
4457
|
+
/** Sources registered by the bootstrap boundary (currently Extensions). */
|
|
4458
|
+
implicitSources = /* @__PURE__ */ new Map();
|
|
4459
|
+
revisionSequence = 0;
|
|
4460
|
+
async loadAgents(input) {
|
|
4461
|
+
assertSourceId(input.sourceId);
|
|
4462
|
+
const collected = await this.collect("agent", input);
|
|
4463
|
+
return this.commit("agent", input.sourceId, collected.values, collected.skipped);
|
|
4464
|
+
}
|
|
4465
|
+
async loadSkills(input) {
|
|
4466
|
+
assertSourceId(input.sourceId);
|
|
4467
|
+
const collected = await this.collect("skill", input);
|
|
4468
|
+
return this.commit("skill", input.sourceId, collected.values, collected.skipped);
|
|
4469
|
+
}
|
|
4470
|
+
async loadPhases(input) {
|
|
4471
|
+
assertSourceId(input.sourceId);
|
|
4472
|
+
const collected = await this.collect("phase", input);
|
|
4473
|
+
return this.commit("phase", input.sourceId, collected.values, collected.skipped);
|
|
4474
|
+
}
|
|
4475
|
+
async loadTools(input) {
|
|
4476
|
+
assertSourceId(input.sourceId);
|
|
4477
|
+
return this.commit("tool", input.sourceId, input.values, []);
|
|
4478
|
+
}
|
|
4479
|
+
async unload(input) {
|
|
4480
|
+
assertSourceId(input.sourceId);
|
|
4481
|
+
if (this.hasImplicit(input.kind, input.sourceId)) {
|
|
4482
|
+
throw new ResourceRegistryError(
|
|
4483
|
+
"implicit_source_protected",
|
|
4484
|
+
`Implicit ${input.kind} source "${input.sourceId}" is managed by Runtime bootstrap.`
|
|
4485
|
+
);
|
|
4486
|
+
}
|
|
4487
|
+
const bySource = this.sources.get(input.kind);
|
|
4488
|
+
const existing = bySource?.get(input.sourceId);
|
|
4489
|
+
if (!existing) {
|
|
4490
|
+
return {
|
|
4491
|
+
revision: this.nextRevision(input.kind, input.sourceId),
|
|
4492
|
+
registered: [],
|
|
4493
|
+
skipped: []
|
|
4494
|
+
};
|
|
4495
|
+
}
|
|
4496
|
+
bySource.delete(input.sourceId);
|
|
4497
|
+
return {
|
|
4498
|
+
revision: this.nextRevision(input.kind, input.sourceId),
|
|
4499
|
+
registered: [],
|
|
4500
|
+
skipped: []
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
resolveView(view) {
|
|
4504
|
+
const sourceIds = {
|
|
4505
|
+
agents: this.withImplicit("agent", view.agents),
|
|
4506
|
+
tools: this.withImplicit("tool", view.tools),
|
|
4507
|
+
skills: this.withImplicit("skill", view.skills),
|
|
4508
|
+
phases: this.withImplicit("phase", view.phases)
|
|
4509
|
+
};
|
|
4510
|
+
return {
|
|
4511
|
+
agents: this.resolveKind("agent", sourceIds.agents),
|
|
4512
|
+
tools: this.resolveKind("tool", sourceIds.tools),
|
|
4513
|
+
skills: this.resolveKind("skill", sourceIds.skills),
|
|
4514
|
+
phases: this.resolveKind("phase", sourceIds.phases),
|
|
4515
|
+
revisions: {
|
|
4516
|
+
agent: this.resolveRevisions("agent", sourceIds.agents),
|
|
4517
|
+
tool: this.resolveRevisions("tool", sourceIds.tools),
|
|
4518
|
+
skill: this.resolveRevisions("skill", sourceIds.skills),
|
|
4519
|
+
phase: this.resolveRevisions("phase", sourceIds.phases)
|
|
3520
4520
|
},
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
4521
|
+
refs: {
|
|
4522
|
+
agent: this.resolveRefs("agent", sourceIds.agents),
|
|
4523
|
+
tool: this.resolveRefs("tool", sourceIds.tools),
|
|
4524
|
+
skill: this.resolveRefs("skill", sourceIds.skills),
|
|
4525
|
+
phase: this.resolveRefs("phase", sourceIds.phases)
|
|
3524
4526
|
}
|
|
3525
4527
|
};
|
|
3526
|
-
return createExtensionAPI(this.hooks, {
|
|
3527
|
-
registerPhase: (registration) => this.registerPhase(extension, registration),
|
|
3528
|
-
registerProvider: (config) => this.registerProvider(config),
|
|
3529
|
-
unregisterProvider: (name) => this.unregisterProvider(name),
|
|
3530
|
-
registerTool: (tool) => this.registerTool(extension, tool),
|
|
3531
|
-
context: extContext,
|
|
3532
|
-
manifest
|
|
3533
|
-
}, this.runtime, this.events);
|
|
3534
4528
|
}
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
throw new Error(message);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
const sourceInfo = createSourceInfo(extension.path);
|
|
3551
|
-
extension.tools.set(tool.name, {
|
|
3552
|
-
definition: tool,
|
|
3553
|
-
sourceInfo
|
|
3554
|
-
});
|
|
4529
|
+
/** Mark one already-loaded source as implicit for every Resource View. */
|
|
4530
|
+
markImplicit(kind, sourceId) {
|
|
4531
|
+
assertSourceId(sourceId);
|
|
4532
|
+
const entries = this.implicitSources.get(kind) ?? [];
|
|
4533
|
+
if (!entries.includes(sourceId)) entries.push(sourceId);
|
|
4534
|
+
this.implicitSources.set(kind, entries);
|
|
4535
|
+
}
|
|
4536
|
+
/** Bootstrap-only helper for replacing an implicit contribution source. */
|
|
4537
|
+
async replaceImplicit(kind, sourceId, values) {
|
|
4538
|
+
const result = await this.commit(kind, sourceId, values, []);
|
|
4539
|
+
this.markImplicit(kind, sourceId);
|
|
4540
|
+
return result;
|
|
3555
4541
|
}
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
const
|
|
3561
|
-
const
|
|
3562
|
-
|
|
3563
|
-
if (description.missing) errors.push(...description.warnings);
|
|
3564
|
-
errors.push(...validateSkillReferences(registration.skills));
|
|
3565
|
-
errors.push(...validatePhaseTarget(registration.target));
|
|
3566
|
-
if (errors.length > 0) {
|
|
3567
|
-
throw new Error(`Invalid phase registration "${name}": ${errors.join("; ")}`);
|
|
3568
|
-
}
|
|
3569
|
-
if (description.warnings.length > 0) {
|
|
3570
|
-
warnResourceDiagnostics("phase", `extension ${extension.path}`, description.warnings);
|
|
4542
|
+
hasImplicit(kind, sourceId) {
|
|
4543
|
+
return this.implicitSources.get(kind)?.includes(sourceId) ?? false;
|
|
4544
|
+
}
|
|
4545
|
+
withImplicit(kind, sourceIds) {
|
|
4546
|
+
const combined = [...sourceIds];
|
|
4547
|
+
for (const sourceId of this.implicitSources.get(kind) ?? []) {
|
|
4548
|
+
if (!combined.includes(sourceId)) combined.push(sourceId);
|
|
3571
4549
|
}
|
|
3572
|
-
|
|
3573
|
-
|
|
4550
|
+
return combined;
|
|
4551
|
+
}
|
|
4552
|
+
async collect(kind, input) {
|
|
4553
|
+
const hasDirectory = input.directory !== void 0;
|
|
4554
|
+
const hasValues = input.values !== void 0;
|
|
4555
|
+
if (!hasDirectory && !hasValues) {
|
|
4556
|
+
throw new ResourceRegistryError(
|
|
4557
|
+
"resource_input_missing",
|
|
4558
|
+
`Resource source "${input.sourceId}" requires directory or values.`
|
|
4559
|
+
);
|
|
3574
4560
|
}
|
|
3575
|
-
const
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
...registration.tools ? { tools: registration.tools.slice() } : {},
|
|
3580
|
-
...registration.skills ? { skills: registration.skills.slice() } : {},
|
|
3581
|
-
...registration.target ? { target: registration.target } : {},
|
|
3582
|
-
...registration.input ? { input: { ...registration.input } } : {},
|
|
3583
|
-
...registration.model ? { model: registration.model } : {}
|
|
3584
|
-
};
|
|
3585
|
-
const registered = {
|
|
3586
|
-
definition,
|
|
3587
|
-
source: { extensionPath: extension.path }
|
|
4561
|
+
const directoryValues = hasDirectory ? await loadDirectoryValues(kind, input.directory, input.sourceId) : { values: [], skipped: [] };
|
|
4562
|
+
return {
|
|
4563
|
+
values: [...directoryValues.values, ...input.values ?? []],
|
|
4564
|
+
skipped: directoryValues.skipped
|
|
3588
4565
|
};
|
|
3589
|
-
this.phases.set(name, registered);
|
|
3590
|
-
this._phaseCache = null;
|
|
3591
4566
|
}
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
4567
|
+
async commit(kind, sourceId, values, skipped) {
|
|
4568
|
+
assertSourceId(sourceId);
|
|
4569
|
+
const names = /* @__PURE__ */ new Set();
|
|
4570
|
+
for (const value of values) {
|
|
4571
|
+
assertNamedResource(value, kind, sourceId);
|
|
4572
|
+
if (isImplicitCoreName(kind, value.name) && sourceId !== "rowan.core") {
|
|
4573
|
+
throw new ResourceRegistryError(
|
|
4574
|
+
"resource_collision",
|
|
4575
|
+
`${capitalize(kind)} resource "${value.name}" is reserved by Rowan core.`
|
|
4576
|
+
);
|
|
4577
|
+
}
|
|
4578
|
+
if (names.has(value.name)) {
|
|
4579
|
+
throw new ResourceRegistryError(
|
|
4580
|
+
"resource_collision",
|
|
4581
|
+
`Duplicate ${capitalize(kind)} resource "${value.name}" in source "${sourceId}".`
|
|
4582
|
+
);
|
|
4583
|
+
}
|
|
4584
|
+
names.add(value.name);
|
|
4585
|
+
}
|
|
4586
|
+
const revision = this.nextRevision(kind, sourceId);
|
|
4587
|
+
const record = {
|
|
4588
|
+
kind,
|
|
4589
|
+
sourceId,
|
|
4590
|
+
revision,
|
|
4591
|
+
values: Object.freeze([...values])
|
|
4592
|
+
};
|
|
4593
|
+
let bySource = this.sources.get(kind);
|
|
4594
|
+
if (!bySource) {
|
|
4595
|
+
bySource = /* @__PURE__ */ new Map();
|
|
4596
|
+
this.sources.set(kind, bySource);
|
|
3597
4597
|
}
|
|
4598
|
+
bySource.set(sourceId, record);
|
|
4599
|
+
return {
|
|
4600
|
+
revision,
|
|
4601
|
+
registered: values.map((value) => ({ kind, sourceId, name: value.name })),
|
|
4602
|
+
skipped
|
|
4603
|
+
};
|
|
3598
4604
|
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
4605
|
+
resolveKind(kind, sourceIds) {
|
|
4606
|
+
const names = /* @__PURE__ */ new Set();
|
|
4607
|
+
const values = [];
|
|
4608
|
+
for (const source of this.sourcesFor(kind, sourceIds)) {
|
|
4609
|
+
for (const value of source.values) {
|
|
4610
|
+
if (names.has(value.name)) {
|
|
4611
|
+
throw new ResourceRegistryError(
|
|
4612
|
+
"resource_collision",
|
|
4613
|
+
`Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
|
|
4614
|
+
);
|
|
4615
|
+
}
|
|
4616
|
+
names.add(value.name);
|
|
4617
|
+
values.push(value);
|
|
4618
|
+
}
|
|
3604
4619
|
}
|
|
4620
|
+
return values;
|
|
3605
4621
|
}
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
4622
|
+
resolveRevisions(kind, sourceIds) {
|
|
4623
|
+
return this.sourcesFor(kind, sourceIds).map(({ revision }) => revision);
|
|
4624
|
+
}
|
|
4625
|
+
resolveRefs(kind, sourceIds) {
|
|
4626
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
4627
|
+
const refs = [];
|
|
4628
|
+
for (const source of this.sourcesFor(kind, sourceIds)) {
|
|
4629
|
+
for (const value of source.values) {
|
|
4630
|
+
if (seenNames.has(value.name)) throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
|
|
4631
|
+
seenNames.add(value.name);
|
|
4632
|
+
refs.push({ kind, sourceId: source.sourceId, name: value.name });
|
|
3612
4633
|
}
|
|
3613
4634
|
}
|
|
3614
|
-
|
|
3615
|
-
}
|
|
3616
|
-
|
|
3617
|
-
|
|
4635
|
+
return refs;
|
|
4636
|
+
}
|
|
4637
|
+
sourcesFor(kind, sourceIds) {
|
|
4638
|
+
const bySource = this.sources.get(kind);
|
|
4639
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4640
|
+
const sources = [];
|
|
4641
|
+
for (const sourceId of sourceIds) {
|
|
4642
|
+
assertSourceId(sourceId);
|
|
4643
|
+
if (seen.has(sourceId)) continue;
|
|
4644
|
+
seen.add(sourceId);
|
|
4645
|
+
const source = bySource?.get(sourceId);
|
|
4646
|
+
if (!source) {
|
|
4647
|
+
throw new ResourceRegistryError(
|
|
4648
|
+
"unknown_resource_source",
|
|
4649
|
+
`Unknown resource source "${sourceId}" for ${kind}.`
|
|
4650
|
+
);
|
|
4651
|
+
}
|
|
4652
|
+
sources.push(source);
|
|
4653
|
+
}
|
|
4654
|
+
return sources;
|
|
3618
4655
|
}
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
return this._phaseCache;
|
|
4656
|
+
nextRevision(kind, sourceId) {
|
|
4657
|
+
this.revisionSequence += 1;
|
|
4658
|
+
return `${kind}:${sourceId}:${this.revisionSequence}`;
|
|
3623
4659
|
}
|
|
3624
4660
|
};
|
|
3625
|
-
function
|
|
3626
|
-
|
|
3627
|
-
}
|
|
3628
|
-
|
|
3629
|
-
// src/runtime/extensions.ts
|
|
3630
|
-
async function assembleExtensions(config) {
|
|
3631
|
-
if (!config.extensions?.length) return { context: config.context };
|
|
3632
|
-
const runner = createExtensionRunner({ cwd: config.cwd });
|
|
3633
|
-
await runner.loadExtensions([...config.extensions]);
|
|
3634
|
-
runner.bind();
|
|
3635
|
-
const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
|
|
3636
|
-
const tools = [...config.context.tools];
|
|
3637
|
-
const names = new Set(tools.map((tool) => tool.name));
|
|
3638
|
-
for (const tool of extensionTools) {
|
|
3639
|
-
if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
|
|
3640
|
-
names.add(tool.name);
|
|
3641
|
-
tools.push(tool);
|
|
4661
|
+
function assertSourceId(sourceId) {
|
|
4662
|
+
if (typeof sourceId !== "string" || !SOURCE_ID.test(sourceId)) {
|
|
4663
|
+
throw new ResourceRegistryError("invalid_source_id", `Invalid resource source "${String(sourceId)}".`);
|
|
3642
4664
|
}
|
|
3643
|
-
const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
|
|
3644
|
-
const phases = mergePhases(config.context.phases, extensionPhases);
|
|
3645
|
-
return {
|
|
3646
|
-
context: { ...config.context, tools, ...phases ? { phases } : {} },
|
|
3647
|
-
beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
|
|
3648
|
-
afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
|
|
3649
|
-
beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
|
|
3650
|
-
beforeToolCall: async (input) => {
|
|
3651
|
-
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3652
|
-
const decision = await runner.emitBeforeToolCall(tool, input.args);
|
|
3653
|
-
return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
|
|
3654
|
-
},
|
|
3655
|
-
afterToolCall: async (input) => {
|
|
3656
|
-
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3657
|
-
const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
|
|
3658
|
-
return fromLoopResult(result);
|
|
3659
|
-
},
|
|
3660
|
-
setContext: (context) => {
|
|
3661
|
-
runner.currentContext = context;
|
|
3662
|
-
}
|
|
3663
|
-
};
|
|
3664
4665
|
}
|
|
3665
|
-
function
|
|
3666
|
-
if (!
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
4666
|
+
function assertNamedResource(value, kind, sourceId) {
|
|
4667
|
+
if (!value || typeof value !== "object" || typeof value.name !== "string") {
|
|
4668
|
+
throw new ResourceRegistryError("invalid_resource_name", `Invalid ${kind} resource in source "${sourceId}".`);
|
|
4669
|
+
}
|
|
4670
|
+
const name = value.name;
|
|
4671
|
+
if (!RESOURCE_NAME.test(name)) {
|
|
4672
|
+
throw new ResourceRegistryError("invalid_resource_name", `Invalid ${capitalize(kind)} resource name "${name}".`);
|
|
3671
4673
|
}
|
|
3672
|
-
return {
|
|
3673
|
-
phases,
|
|
3674
|
-
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
|
|
3675
|
-
};
|
|
3676
|
-
}
|
|
3677
|
-
function adaptExtensionTool(input) {
|
|
3678
|
-
const definition = input.definition;
|
|
3679
|
-
return {
|
|
3680
|
-
name: definition.name,
|
|
3681
|
-
description: definition.description,
|
|
3682
|
-
parameters: definition.parameters,
|
|
3683
|
-
execute: async (args, _context, signal) => {
|
|
3684
|
-
const result = await definition.execute(args, signal);
|
|
3685
|
-
const content = JSON.parse(JSON.stringify(result.content));
|
|
3686
|
-
return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
|
|
3687
|
-
}
|
|
3688
|
-
};
|
|
3689
|
-
}
|
|
3690
|
-
function toLoopResult(result, toolCallId, toolName) {
|
|
3691
|
-
return {
|
|
3692
|
-
toolCallId,
|
|
3693
|
-
toolName,
|
|
3694
|
-
ok: result.ok,
|
|
3695
|
-
content: result.content,
|
|
3696
|
-
...!result.ok ? { error: result.error } : {}
|
|
3697
|
-
};
|
|
3698
4674
|
}
|
|
3699
|
-
function
|
|
3700
|
-
return
|
|
4675
|
+
function capitalize(value) {
|
|
4676
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
3701
4677
|
}
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
var MAX_BUFFERED_EVENTS = 128;
|
|
3705
|
-
var MAX_BUFFERED_TEXT = 64 * 1024;
|
|
3706
|
-
function deferred() {
|
|
3707
|
-
let resolve6;
|
|
3708
|
-
const promise = new Promise((done) => {
|
|
3709
|
-
resolve6 = done;
|
|
3710
|
-
});
|
|
3711
|
-
return { promise, resolve: resolve6 };
|
|
4678
|
+
function isImplicitCoreName(kind, name) {
|
|
4679
|
+
return kind === "tool" && name === "route" || kind === "phase" && name === "default";
|
|
3712
4680
|
}
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
if (
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
...previous,
|
|
3724
|
-
text: previous.text + event.text
|
|
4681
|
+
async function loadDirectoryValues(kind, directory, sourceId) {
|
|
4682
|
+
const root = resolve6(directory);
|
|
4683
|
+
let rootInfo;
|
|
4684
|
+
try {
|
|
4685
|
+
rootInfo = await stat3(root);
|
|
4686
|
+
} catch (error) {
|
|
4687
|
+
if (isMissingPath(error)) {
|
|
4688
|
+
return {
|
|
4689
|
+
values: [],
|
|
4690
|
+
skipped: [{ kind: "missing", sourceId, path: root, message: `Resource directory "${root}" does not exist.` }]
|
|
3725
4691
|
};
|
|
3726
|
-
} else {
|
|
3727
|
-
if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
|
|
3728
|
-
this.queue.push(event);
|
|
3729
4692
|
}
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
4693
|
+
return {
|
|
4694
|
+
values: [],
|
|
4695
|
+
skipped: [{
|
|
4696
|
+
kind: "invalid",
|
|
4697
|
+
sourceId,
|
|
4698
|
+
path: root,
|
|
4699
|
+
message: `Resource directory "${root}" could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
4700
|
+
}]
|
|
4701
|
+
};
|
|
3735
4702
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
4703
|
+
const marker = markerFor(kind);
|
|
4704
|
+
const targets = [];
|
|
4705
|
+
if (rootInfo.isFile()) {
|
|
4706
|
+
targets.push(root);
|
|
4707
|
+
} else if (rootInfo.isDirectory()) {
|
|
4708
|
+
const markerAtRoot = join4(root, marker);
|
|
4709
|
+
try {
|
|
4710
|
+
const markerInfo = await stat3(markerAtRoot);
|
|
4711
|
+
if (markerInfo.isFile()) targets.push(markerAtRoot);
|
|
4712
|
+
} catch (error) {
|
|
4713
|
+
if (!isMissingPath(error)) throw error;
|
|
4714
|
+
let entries;
|
|
4715
|
+
try {
|
|
4716
|
+
entries = await readdir4(root, { withFileTypes: true });
|
|
4717
|
+
} catch (error2) {
|
|
4718
|
+
return {
|
|
4719
|
+
values: [],
|
|
4720
|
+
skipped: [{
|
|
4721
|
+
kind: "invalid",
|
|
4722
|
+
sourceId,
|
|
4723
|
+
path: root,
|
|
4724
|
+
message: `Resource directory "${root}" could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
4725
|
+
}]
|
|
4726
|
+
};
|
|
4727
|
+
}
|
|
4728
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
4729
|
+
if (!entry.isDirectory()) continue;
|
|
4730
|
+
const target = join4(root, entry.name, marker);
|
|
4731
|
+
try {
|
|
4732
|
+
const targetInfo = await stat3(target);
|
|
4733
|
+
if (targetInfo.isFile()) targets.push(target);
|
|
4734
|
+
} catch (targetError) {
|
|
4735
|
+
if (!isMissingPath(targetError)) throw targetError;
|
|
4736
|
+
}
|
|
3741
4737
|
}
|
|
3742
4738
|
}
|
|
3743
|
-
this.notify();
|
|
3744
4739
|
}
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
4740
|
+
const values = [];
|
|
4741
|
+
const skipped = [];
|
|
4742
|
+
for (const target of targets) {
|
|
4743
|
+
try {
|
|
4744
|
+
const value = await loadDirectoryValue(kind, target);
|
|
4745
|
+
values.push(value);
|
|
4746
|
+
} catch (error) {
|
|
4747
|
+
skipped.push({
|
|
4748
|
+
kind: "invalid",
|
|
4749
|
+
sourceId,
|
|
4750
|
+
path: target,
|
|
4751
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4752
|
+
});
|
|
3749
4753
|
}
|
|
3750
|
-
this.notify();
|
|
3751
4754
|
}
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
4755
|
+
return { values, skipped };
|
|
4756
|
+
}
|
|
4757
|
+
async function loadDirectoryValue(kind, target) {
|
|
4758
|
+
switch (kind) {
|
|
4759
|
+
case "agent": {
|
|
4760
|
+
let loaded;
|
|
4761
|
+
try {
|
|
4762
|
+
loaded = await loadMarkdown(target);
|
|
4763
|
+
} catch (error) {
|
|
4764
|
+
if (error instanceof FrontmatterParseError) {
|
|
4765
|
+
throw new TypeError(`frontmatter could not be parsed: ${error.message}`);
|
|
4766
|
+
}
|
|
4767
|
+
throw error;
|
|
4768
|
+
}
|
|
4769
|
+
return normalizeAgentDefinition(
|
|
4770
|
+
loaded.frontmatter,
|
|
4771
|
+
loaded.body,
|
|
4772
|
+
{ fallbackName: inferResourceName(target, "AGENT.md") }
|
|
4773
|
+
);
|
|
3756
4774
|
}
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
4775
|
+
case "skill":
|
|
4776
|
+
return await loadSkill(target);
|
|
4777
|
+
case "phase":
|
|
4778
|
+
return await loadPhase(target);
|
|
4779
|
+
case "tool":
|
|
4780
|
+
throw new ResourceRegistryError(
|
|
4781
|
+
"resource_directory_not_supported",
|
|
4782
|
+
"Tool sources accept inline values only."
|
|
4783
|
+
);
|
|
3765
4784
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
4785
|
+
}
|
|
4786
|
+
function markerFor(kind) {
|
|
4787
|
+
switch (kind) {
|
|
4788
|
+
case "agent":
|
|
4789
|
+
return "AGENT.md";
|
|
4790
|
+
case "skill":
|
|
4791
|
+
return "SKILL.md";
|
|
4792
|
+
case "phase":
|
|
4793
|
+
return "PHASE.md";
|
|
4794
|
+
case "tool":
|
|
4795
|
+
return "";
|
|
3771
4796
|
}
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
4797
|
+
}
|
|
4798
|
+
function isMissingPath(error) {
|
|
4799
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// src/runtime/extension-lifetime.ts
|
|
4803
|
+
var ExtensionLifetimeError = class extends Error {
|
|
4804
|
+
code;
|
|
4805
|
+
constructor(code, message) {
|
|
4806
|
+
super(message);
|
|
4807
|
+
this.name = "ExtensionLifetimeError";
|
|
4808
|
+
this.code = code;
|
|
3777
4809
|
}
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
4810
|
+
};
|
|
4811
|
+
var RuntimeExtensionLifetime = class {
|
|
4812
|
+
runner;
|
|
4813
|
+
frozen = false;
|
|
4814
|
+
closed = false;
|
|
4815
|
+
constructor(options = {}) {
|
|
4816
|
+
this.runner = createExtensionRunner(options);
|
|
4817
|
+
}
|
|
4818
|
+
async activate(extensions) {
|
|
4819
|
+
if (this.closed) throw new ExtensionLifetimeError("extensions_closed", "The Extension Runtime has been closed.");
|
|
4820
|
+
if (this.frozen) throw new ExtensionLifetimeError("extensions_frozen", "Extensions are frozen until Runtime restart.");
|
|
4821
|
+
const active = [];
|
|
4822
|
+
const errors = [];
|
|
4823
|
+
for (const extension of extensions) {
|
|
4824
|
+
try {
|
|
4825
|
+
await this.runner.loadExtensions([extension]);
|
|
4826
|
+
active.push(extension.name);
|
|
4827
|
+
} catch (error) {
|
|
4828
|
+
errors.push({
|
|
4829
|
+
path: extension.path,
|
|
4830
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4831
|
+
});
|
|
3798
4832
|
}
|
|
3799
4833
|
}
|
|
4834
|
+
this.runner.bind();
|
|
4835
|
+
this.frozen = true;
|
|
4836
|
+
return { active, errors };
|
|
4837
|
+
}
|
|
4838
|
+
tools() {
|
|
4839
|
+
return this.runner.getAllRegisteredTools();
|
|
4840
|
+
}
|
|
4841
|
+
phases() {
|
|
4842
|
+
return this.runner.getPhases();
|
|
4843
|
+
}
|
|
4844
|
+
get extensionRunner() {
|
|
4845
|
+
return this.runner;
|
|
4846
|
+
}
|
|
4847
|
+
async close() {
|
|
4848
|
+
if (this.closed) return;
|
|
4849
|
+
this.closed = true;
|
|
4850
|
+
await this.runner.close();
|
|
3800
4851
|
}
|
|
3801
4852
|
};
|
|
3802
|
-
var
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
4853
|
+
var RuntimeBootstrapRegistry = class extends ResourceRegistry {
|
|
4854
|
+
constructor(extensions = new RuntimeExtensionLifetime()) {
|
|
4855
|
+
super();
|
|
4856
|
+
this.extensions = extensions;
|
|
4857
|
+
}
|
|
4858
|
+
extensions;
|
|
4859
|
+
extensionSourceId = "rowan.extensions";
|
|
4860
|
+
/** Install Rowan-owned implicit resources before any host source is read. */
|
|
4861
|
+
async ensureCoreResources() {
|
|
4862
|
+
await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
|
|
4863
|
+
await this.replaceImplicit("phase", "rowan.core", [createDefaultPhase()]);
|
|
4864
|
+
}
|
|
4865
|
+
async loadExtensions(input) {
|
|
4866
|
+
const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
|
|
4867
|
+
const descriptor = input;
|
|
4868
|
+
return descriptor.directory ? loadExtensionsFromPath(descriptor.directory) : Promise.resolve({ extensions: [...descriptor.values ?? []], errors: [] });
|
|
4869
|
+
})();
|
|
4870
|
+
const normalized = await loaded;
|
|
4871
|
+
const result = await this.extensions.activate(normalized.extensions);
|
|
4872
|
+
await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool2));
|
|
4873
|
+
await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
|
|
4874
|
+
return {
|
|
4875
|
+
...result,
|
|
4876
|
+
revision: `extensions:${Date.now()}`,
|
|
4877
|
+
registered: result.active,
|
|
4878
|
+
skipped: normalized.errors.map((error) => ({
|
|
4879
|
+
kind: "invalid",
|
|
4880
|
+
sourceId: "rowan.extensions",
|
|
4881
|
+
path: error.path,
|
|
4882
|
+
message: error.error
|
|
4883
|
+
}))
|
|
4884
|
+
};
|
|
3810
4885
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
const current = this.subscriptions.get(runId);
|
|
3814
|
-
current?.delete(subscription);
|
|
3815
|
-
if (current?.size === 0) this.subscriptions.delete(runId);
|
|
4886
|
+
get extensionRunner() {
|
|
4887
|
+
return this.extensions.extensionRunner;
|
|
3816
4888
|
}
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
subscription.push(event);
|
|
3820
|
-
}
|
|
4889
|
+
get extensionTools() {
|
|
4890
|
+
return this.extensions.tools();
|
|
3821
4891
|
}
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
4892
|
+
get extensionPhases() {
|
|
4893
|
+
return this.extensions.phases();
|
|
4894
|
+
}
|
|
4895
|
+
async closeExtensions() {
|
|
4896
|
+
await this.extensions.close();
|
|
4897
|
+
}
|
|
4898
|
+
};
|
|
4899
|
+
function adaptExtensionTool2(input) {
|
|
4900
|
+
const definition = input.definition;
|
|
4901
|
+
return {
|
|
4902
|
+
name: definition.name,
|
|
4903
|
+
description: definition.description,
|
|
4904
|
+
parameters: definition.parameters,
|
|
4905
|
+
execute: async (args, context, signal) => {
|
|
4906
|
+
const result = await definition.execute(args, signal);
|
|
4907
|
+
const content = JSON.parse(JSON.stringify(result.content));
|
|
4908
|
+
return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
|
|
3825
4909
|
}
|
|
4910
|
+
};
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4913
|
+
// src/runtime/configuration-snapshot.ts
|
|
4914
|
+
function resolveConfigurationSnapshot(registry, input) {
|
|
4915
|
+
const resolved = registry.resolveView(input.resourceView);
|
|
4916
|
+
const base = resolved.agents.find(({ name }) => name === input.definition.name);
|
|
4917
|
+
if (!base) {
|
|
4918
|
+
throw new Error(`Agent Definition "${input.definition.name}" is not available in the Resource View.`);
|
|
4919
|
+
}
|
|
4920
|
+
const layer = input.definition.layer;
|
|
4921
|
+
const definition = applyDefinitionLayer(base, layer);
|
|
4922
|
+
const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
|
|
4923
|
+
const skills = selectNamedResources(resolved.skills, definition.skills, "Skill");
|
|
4924
|
+
const phases = resolvePhases(resolved.phases, definition.phases);
|
|
4925
|
+
const contexts = selectNamedResources(input.contexts ?? [], base.context, "Context");
|
|
4926
|
+
return {
|
|
4927
|
+
identity: input.identity,
|
|
4928
|
+
definition,
|
|
4929
|
+
resources: {
|
|
4930
|
+
tools,
|
|
4931
|
+
skills,
|
|
4932
|
+
...phases ? { phases } : {},
|
|
4933
|
+
revisions: resolved.revisions,
|
|
4934
|
+
refs: {
|
|
4935
|
+
agent: selectedRefs(resolved.refs.agent, [base.name]),
|
|
4936
|
+
tool: selectedRefs(resolved.refs.tool, tools.map(({ name }) => name)),
|
|
4937
|
+
skill: selectedRefs(resolved.refs.skill, skills.map(({ name }) => name)),
|
|
4938
|
+
phase: selectedRefs(resolved.refs.phase, [...phases?.phases.keys() ?? []])
|
|
4939
|
+
}
|
|
4940
|
+
},
|
|
4941
|
+
contexts,
|
|
4942
|
+
resourceView: input.resourceView,
|
|
4943
|
+
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
4944
|
+
...input.maxAttempts === void 0 ? {} : { maxAttempts: input.maxAttempts },
|
|
4945
|
+
..."stream" in input && input.stream ? { model: layer?.model ?? input.model, stream: input.stream } : { model: layer?.model ?? input.model }
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
function materializeConfigurationSnapshot(snapshot) {
|
|
4949
|
+
return {
|
|
4950
|
+
identity: snapshot.identity,
|
|
4951
|
+
definition: snapshot.definition,
|
|
4952
|
+
resources: {
|
|
4953
|
+
tools: snapshot.resources.tools,
|
|
4954
|
+
skills: snapshot.resources.skills,
|
|
4955
|
+
...snapshot.resources.phases ? { phases: snapshot.resources.phases } : {},
|
|
4956
|
+
...snapshot.contexts.length > 0 ? { contexts: snapshot.contexts } : {},
|
|
4957
|
+
resourceView: snapshot.resourceView,
|
|
4958
|
+
resourceRefs: [
|
|
4959
|
+
...snapshot.resources.refs.agent,
|
|
4960
|
+
...snapshot.resources.refs.tool,
|
|
4961
|
+
...snapshot.resources.refs.skill,
|
|
4962
|
+
...snapshot.resources.refs.phase
|
|
4963
|
+
],
|
|
4964
|
+
resourceRevisions: snapshot.resources.revisions
|
|
4965
|
+
},
|
|
4966
|
+
...snapshot.cwd === void 0 ? {} : { cwd: snapshot.cwd },
|
|
4967
|
+
...snapshot.maxAttempts === void 0 ? {} : { maxAttempts: snapshot.maxAttempts },
|
|
4968
|
+
..."stream" in snapshot && snapshot.stream ? { model: snapshot.model, stream: snapshot.stream } : { model: snapshot.model }
|
|
4969
|
+
};
|
|
4970
|
+
}
|
|
4971
|
+
function applyDefinitionLayer(base, layer) {
|
|
4972
|
+
if (!layer) return { ...base };
|
|
4973
|
+
return {
|
|
4974
|
+
...base,
|
|
4975
|
+
...layer.description === void 0 ? {} : { description: layer.description },
|
|
4976
|
+
...layer.content === void 0 ? {} : { content: layer.content },
|
|
4977
|
+
...layer.model === void 0 ? {} : { model: layer.model },
|
|
4978
|
+
...layer.tools === void 0 ? {} : { tools: intersectNames(base.tools, layer.tools, "Tool") },
|
|
4979
|
+
...layer.skills === void 0 ? {} : { skills: intersectNames(base.skills, layer.skills, "Skill") },
|
|
4980
|
+
...layer.phases === void 0 ? {} : { phases: intersectPhaseSelection(base.phases, layer.phases) }
|
|
4981
|
+
};
|
|
4982
|
+
}
|
|
4983
|
+
function intersectNames(parent, layer, kind) {
|
|
4984
|
+
if (parent === void 0) return [...layer];
|
|
4985
|
+
const parentNames = new Set(parent);
|
|
4986
|
+
for (const name of layer) {
|
|
4987
|
+
if (!parentNames.has(name)) console.warn(`${kind} "${name}" is not available and will be skipped.`);
|
|
3826
4988
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
4989
|
+
return layer.filter((name) => parentNames.has(name));
|
|
4990
|
+
}
|
|
4991
|
+
function intersectPhaseSelection(parent, layer) {
|
|
4992
|
+
if (!parent) return layer;
|
|
4993
|
+
const parentNames = new Set(parent.phaseIds);
|
|
4994
|
+
for (const name of layer.phaseIds) {
|
|
4995
|
+
if (!parentNames.has(name)) console.warn(`Phase "${name}" is not available and will be skipped.`);
|
|
3832
4996
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
4997
|
+
return {
|
|
4998
|
+
entryPhaseId: layer.entryPhaseId,
|
|
4999
|
+
phaseIds: layer.phaseIds.filter((name) => parentNames.has(name))
|
|
5000
|
+
};
|
|
5001
|
+
}
|
|
5002
|
+
function resolvePhases(candidates, selection) {
|
|
5003
|
+
const selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
|
|
5004
|
+
if (selected.length === 0 && selection === void 0) return void 0;
|
|
5005
|
+
const entryPhaseId = selection?.entryPhaseId ?? null;
|
|
5006
|
+
if (entryPhaseId !== null && !selected.some(({ name }) => name === entryPhaseId)) {
|
|
5007
|
+
console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
|
|
5008
|
+
return { phases: new Map(selected.map((phase) => [phase.name, phase])), entryPhaseId: null };
|
|
3838
5009
|
}
|
|
3839
|
-
|
|
5010
|
+
return {
|
|
5011
|
+
phases: new Map(selected.map((phase) => [phase.name, phase])),
|
|
5012
|
+
entryPhaseId
|
|
5013
|
+
};
|
|
5014
|
+
}
|
|
5015
|
+
function selectedRefs(refs, names) {
|
|
5016
|
+
const selected = new Set(names);
|
|
5017
|
+
return refs.filter(({ name }) => selected.has(name));
|
|
5018
|
+
}
|
|
3840
5019
|
|
|
3841
5020
|
// src/runtime/durable-runtime.ts
|
|
3842
5021
|
var DEFAULT_CONCURRENCY = 10;
|
|
@@ -3853,14 +5032,16 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
3853
5032
|
executions = /* @__PURE__ */ new Map();
|
|
3854
5033
|
consumers = /* @__PURE__ */ new Map();
|
|
3855
5034
|
transientEvents = new TransientRunEventHub();
|
|
5035
|
+
resources;
|
|
3856
5036
|
heartbeat;
|
|
3857
5037
|
pumping = false;
|
|
3858
5038
|
closed = false;
|
|
3859
|
-
constructor(options, owned) {
|
|
5039
|
+
constructor(options, owned, resources) {
|
|
3860
5040
|
this.owned = owned;
|
|
3861
5041
|
this.commands = new ConfigCommandService(owned, options.configs, String(owned.lease.token).split(":")[0]);
|
|
3862
5042
|
this.storeIncarnation = String(owned.lease.token).split(":")[0];
|
|
3863
5043
|
this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
5044
|
+
this.resources = resources;
|
|
3864
5045
|
}
|
|
3865
5046
|
static async init(options) {
|
|
3866
5047
|
if (!options.store) throw new TypeError("AgentRuntime requires a DurableStore");
|
|
@@ -3868,10 +5049,38 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
3868
5049
|
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
3869
5050
|
if (!Number.isInteger(concurrency) || concurrency <= 0) throw new TypeError("concurrency must be a positive integer");
|
|
3870
5051
|
const owned = await options.store.openOwner({ ownerId: createId("owner"), leaseMs: OWNER_LEASE_MS });
|
|
3871
|
-
const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned);
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
5052
|
+
const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned, new RuntimeBootstrapRegistry());
|
|
5053
|
+
try {
|
|
5054
|
+
await runtime.resources.ensureCoreResources();
|
|
5055
|
+
await options.bootstrap?.(runtime.resources);
|
|
5056
|
+
runtime.startHeartbeat();
|
|
5057
|
+
void runtime.pump();
|
|
5058
|
+
return runtime;
|
|
5059
|
+
} catch (error) {
|
|
5060
|
+
await runtime.resources.closeExtensions().catch(() => void 0);
|
|
5061
|
+
await owned.sealAndReleaseOwner().catch(() => void 0);
|
|
5062
|
+
throw error;
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
async loadAgents(input) {
|
|
5066
|
+
this.assertOpen();
|
|
5067
|
+
return this.resources.loadAgents(input);
|
|
5068
|
+
}
|
|
5069
|
+
async loadSkills(input) {
|
|
5070
|
+
this.assertOpen();
|
|
5071
|
+
return this.resources.loadSkills(input);
|
|
5072
|
+
}
|
|
5073
|
+
async loadPhases(input) {
|
|
5074
|
+
this.assertOpen();
|
|
5075
|
+
return this.resources.loadPhases(input);
|
|
5076
|
+
}
|
|
5077
|
+
async loadTools(input) {
|
|
5078
|
+
this.assertOpen();
|
|
5079
|
+
return this.resources.loadTools(input);
|
|
5080
|
+
}
|
|
5081
|
+
async unload(input) {
|
|
5082
|
+
this.assertOpen();
|
|
5083
|
+
return this.resources.unload(input);
|
|
3875
5084
|
}
|
|
3876
5085
|
async createAgent(config, options = {}) {
|
|
3877
5086
|
this.assertOpen();
|
|
@@ -3931,6 +5140,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
3931
5140
|
this.executions.clear();
|
|
3932
5141
|
this.transientEvents.close();
|
|
3933
5142
|
for (const subscription of this.consumers.values()) subscription.controller.abort();
|
|
5143
|
+
await this.resources.closeExtensions();
|
|
3934
5144
|
await this.owned.sealAndReleaseOwner();
|
|
3935
5145
|
}
|
|
3936
5146
|
async snapshot(runId) {
|
|
@@ -3988,7 +5198,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
3988
5198
|
let executionRevision = run.revision;
|
|
3989
5199
|
try {
|
|
3990
5200
|
const agent = await this.requireAgent(run.agentId);
|
|
3991
|
-
|
|
5201
|
+
let token = run.pinnedConfigToken ?? agent.currentConfigToken;
|
|
3992
5202
|
if (!token) {
|
|
3993
5203
|
await this.failQueued(run, "Agent has no Config Token.");
|
|
3994
5204
|
return;
|
|
@@ -4008,42 +5218,58 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4008
5218
|
await this.failQueued(run, resolution.reason);
|
|
4009
5219
|
return;
|
|
4010
5220
|
}
|
|
5221
|
+
let resolvedConfig = resolution.config;
|
|
5222
|
+
if (isAgentConfiguration(resolvedConfig)) {
|
|
5223
|
+
try {
|
|
5224
|
+
const snapshot = this.materializeConfig(resolvedConfig);
|
|
5225
|
+
token = await this.commands.storeSnapshot({
|
|
5226
|
+
agent,
|
|
5227
|
+
config: snapshot,
|
|
5228
|
+
operationId: `run-snapshot:${run.id}`
|
|
5229
|
+
});
|
|
5230
|
+
resolvedConfig = snapshot;
|
|
5231
|
+
} catch (error) {
|
|
5232
|
+
await this.failQueued(run, error instanceof Error ? error.message : "Configuration Snapshot failed.");
|
|
5233
|
+
return;
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
4011
5236
|
const executionId = createId("exec");
|
|
4012
5237
|
claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
|
|
5238
|
+
executionRevision = claim.run.revision;
|
|
4013
5239
|
const controller = new AbortController();
|
|
4014
5240
|
this.executions.set(run.id, { controller, executionId });
|
|
4015
|
-
const config =
|
|
4016
|
-
const assembly =
|
|
4017
|
-
const executionConfig = {
|
|
4018
|
-
...config,
|
|
4019
|
-
context: assembly.context,
|
|
4020
|
-
...assembly.beforeToolCall ? { beforeToolCall: assembly.beforeToolCall } : {},
|
|
4021
|
-
...assembly.afterToolCall ? { afterToolCall: assembly.afterToolCall } : {}
|
|
4022
|
-
};
|
|
4023
|
-
executionRevision = claim.run.revision;
|
|
5241
|
+
const config = resolvedConfig;
|
|
5242
|
+
const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner);
|
|
4024
5243
|
let toolQueue = Promise.resolve();
|
|
4025
5244
|
const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
|
|
4026
5245
|
const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
|
|
4027
5246
|
const executionContext = projectModelContext({
|
|
4028
5247
|
context: {
|
|
4029
|
-
...
|
|
4030
|
-
phases: normalizePhaseRegistry(
|
|
5248
|
+
...assembly.context,
|
|
5249
|
+
phases: normalizePhaseRegistry(assembly.context.phases)
|
|
4031
5250
|
},
|
|
4032
5251
|
messages: claim.history,
|
|
4033
5252
|
agentId: run.agentId,
|
|
4034
5253
|
runId: run.id
|
|
4035
5254
|
});
|
|
5255
|
+
const executionTools = {
|
|
5256
|
+
tools: assembly.context.tools,
|
|
5257
|
+
beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
|
|
5258
|
+
afterToolCall: assembly.afterToolCall ?? config.afterToolCall
|
|
5259
|
+
};
|
|
4036
5260
|
const result = await executeOnce({
|
|
4037
5261
|
canonicalMessages: executionContext.messages,
|
|
4038
5262
|
context: executionContext,
|
|
4039
5263
|
execution: {
|
|
4040
5264
|
agentId: run.agentId,
|
|
4041
5265
|
runId: run.id,
|
|
4042
|
-
executionId: claim.execution.executionId
|
|
5266
|
+
executionId: claim.execution.executionId,
|
|
5267
|
+
...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
|
|
5268
|
+
...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
|
|
4043
5269
|
},
|
|
4044
5270
|
model,
|
|
4045
5271
|
stream,
|
|
4046
|
-
maxAttempts:
|
|
5272
|
+
maxAttempts: config.maxAttempts,
|
|
4047
5273
|
checkpoint: claim.run.checkpoint,
|
|
4048
5274
|
signal: controller.signal,
|
|
4049
5275
|
beforePhase: assembly.beforePhase,
|
|
@@ -4068,9 +5294,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4068
5294
|
const task = toolQueue.then(async () => {
|
|
4069
5295
|
const execution = await this.executeToolBatch({
|
|
4070
5296
|
run,
|
|
5297
|
+
agentMetadata: agent.metadata,
|
|
4071
5298
|
execution: claim.execution,
|
|
4072
5299
|
expectedRevision: executionRevision,
|
|
4073
|
-
|
|
5300
|
+
toolConfig: executionTools,
|
|
4074
5301
|
toolCalls: [toolCall],
|
|
4075
5302
|
signal: controller.signal
|
|
4076
5303
|
});
|
|
@@ -4084,9 +5311,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4084
5311
|
const task = toolQueue.then(async () => {
|
|
4085
5312
|
const execution = await this.executeToolBatch({
|
|
4086
5313
|
run,
|
|
5314
|
+
agentMetadata: agent.metadata,
|
|
4087
5315
|
execution: claim.execution,
|
|
4088
5316
|
expectedRevision: executionRevision,
|
|
4089
|
-
|
|
5317
|
+
toolConfig: executionTools,
|
|
4090
5318
|
toolCalls,
|
|
4091
5319
|
signal: controller.signal
|
|
4092
5320
|
});
|
|
@@ -4165,7 +5393,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4165
5393
|
return { results, revision };
|
|
4166
5394
|
}
|
|
4167
5395
|
async executeReservedTool(input) {
|
|
4168
|
-
const durableTool = input.
|
|
5396
|
+
const durableTool = input.toolConfig.tools.find((candidate) => candidate.name === input.toolCall.name);
|
|
4169
5397
|
const providerToolCallId = input.toolCall.id;
|
|
4170
5398
|
const toolCallId = input.tool.id;
|
|
4171
5399
|
let revision = input.expectedRevision;
|
|
@@ -4178,6 +5406,8 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4178
5406
|
const context = {
|
|
4179
5407
|
agentId: input.run.agentId,
|
|
4180
5408
|
runId: input.run.id,
|
|
5409
|
+
...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
|
|
5410
|
+
...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
|
|
4181
5411
|
toolCallId,
|
|
4182
5412
|
reportProgress: (progress) => {
|
|
4183
5413
|
const active = this.executions.get(input.run.id);
|
|
@@ -4200,8 +5430,8 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4200
5430
|
}
|
|
4201
5431
|
};
|
|
4202
5432
|
try {
|
|
4203
|
-
if (input.
|
|
4204
|
-
const decision = await input.
|
|
5433
|
+
if (input.toolConfig.beforeToolCall) {
|
|
5434
|
+
const decision = await input.toolConfig.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
|
|
4205
5435
|
if (!decision.allow) {
|
|
4206
5436
|
const failed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "failed", result: { ok: false, content: null, error: decision.reason } });
|
|
4207
5437
|
return { result: protocolFailure(decision.reason), revision: failed.run.revision };
|
|
@@ -4218,7 +5448,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4218
5448
|
try {
|
|
4219
5449
|
let result = await durableTool.execute(toJsonValue(input.toolCall.args), context, input.signal);
|
|
4220
5450
|
assertToolExecutionResult(result);
|
|
4221
|
-
if (input.
|
|
5451
|
+
if (input.toolConfig.afterToolCall) result = await input.toolConfig.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
|
|
4222
5452
|
assertToolExecutionResult(result);
|
|
4223
5453
|
const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
|
|
4224
5454
|
return {
|
|
@@ -4321,6 +5551,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4321
5551
|
assertOpen() {
|
|
4322
5552
|
if (this.closed) throw new RuntimeError("runtime_closed", null);
|
|
4323
5553
|
}
|
|
5554
|
+
materializeConfig(config) {
|
|
5555
|
+
if (!isAgentConfiguration(config)) return config;
|
|
5556
|
+
return materializeConfigurationSnapshot(resolveConfigurationSnapshot(this.resources, config));
|
|
5557
|
+
}
|
|
4324
5558
|
startHeartbeat() {
|
|
4325
5559
|
this.heartbeat = setInterval(() => {
|
|
4326
5560
|
void this.owned.renewOwner(OWNER_LEASE_MS).catch(() => {
|
|
@@ -4384,10 +5618,10 @@ function boundaryFromSnapshot(snapshot) {
|
|
|
4384
5618
|
}
|
|
4385
5619
|
}
|
|
4386
5620
|
function delay(ms) {
|
|
4387
|
-
return new Promise((
|
|
5621
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
4388
5622
|
}
|
|
4389
5623
|
function normalizePhaseRegistry(registry) {
|
|
4390
|
-
if (registry?.phases.has(DEFAULT_PHASE_ID)) {
|
|
5624
|
+
if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
|
|
4391
5625
|
throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
|
|
4392
5626
|
}
|
|
4393
5627
|
return {
|
|
@@ -4411,13 +5645,13 @@ function toJsonValue(value) {
|
|
|
4411
5645
|
return value;
|
|
4412
5646
|
}
|
|
4413
5647
|
function deferred2() {
|
|
4414
|
-
let
|
|
5648
|
+
let resolve7;
|
|
4415
5649
|
let reject;
|
|
4416
5650
|
const promise = new Promise((res, rej) => {
|
|
4417
|
-
|
|
5651
|
+
resolve7 = (value) => res(value);
|
|
4418
5652
|
reject = rej;
|
|
4419
5653
|
});
|
|
4420
|
-
return { promise, resolve:
|
|
5654
|
+
return { promise, resolve: resolve7, reject };
|
|
4421
5655
|
}
|
|
4422
5656
|
|
|
4423
5657
|
// src/runtime/durable-store.ts
|
|
@@ -5868,236 +7102,6 @@ function assertOwnerInput(ownerId, leaseMs) {
|
|
|
5868
7102
|
if (!Number.isFinite(leaseMs) || leaseMs <= 0) throw new TypeError("leaseMs must be a positive finite number");
|
|
5869
7103
|
}
|
|
5870
7104
|
|
|
5871
|
-
// src/harness/skills.ts
|
|
5872
|
-
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
5873
|
-
import { readdir as readdir2 } from "fs/promises";
|
|
5874
|
-
import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
|
|
5875
|
-
var SKILL_MARKER = "SKILL.md";
|
|
5876
|
-
function resolveSkillPath(input) {
|
|
5877
|
-
const resolved = resolve4(input);
|
|
5878
|
-
return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, SKILL_MARKER) : resolved;
|
|
5879
|
-
}
|
|
5880
|
-
async function loadSkill(path) {
|
|
5881
|
-
const resolved = resolveSkillPath(path);
|
|
5882
|
-
let loaded;
|
|
5883
|
-
try {
|
|
5884
|
-
loaded = await loadMarkdown(resolved);
|
|
5885
|
-
} catch (error) {
|
|
5886
|
-
if (!(error instanceof FrontmatterParseError)) throw error;
|
|
5887
|
-
warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
|
|
5888
|
-
throw new ResourceMetadataError("parse_failed", error.message);
|
|
5889
|
-
}
|
|
5890
|
-
const { frontmatter, body } = loaded;
|
|
5891
|
-
const metadata = frontmatter;
|
|
5892
|
-
const id = inferResourceName(resolved, SKILL_MARKER);
|
|
5893
|
-
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
5894
|
-
const name = frontmatterName || id;
|
|
5895
|
-
const description = validateDescription(metadata.description);
|
|
5896
|
-
const diagnostics = validateResourceId(id);
|
|
5897
|
-
if (frontmatterName && frontmatterName !== id) {
|
|
5898
|
-
diagnostics.push(...validateResourceName(frontmatterName, id));
|
|
5899
|
-
}
|
|
5900
|
-
diagnostics.push(...description.warnings);
|
|
5901
|
-
warnResourceDiagnostics("skill", resolved, diagnostics);
|
|
5902
|
-
if (description.missing) {
|
|
5903
|
-
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
5904
|
-
}
|
|
5905
|
-
return {
|
|
5906
|
-
name,
|
|
5907
|
-
description: description.description,
|
|
5908
|
-
filePath: resolved,
|
|
5909
|
-
baseDir: dirname4(resolved),
|
|
5910
|
-
content: body,
|
|
5911
|
-
disableModelInvocation: metadata["disable-model-invocation"] === true
|
|
5912
|
-
};
|
|
5913
|
-
}
|
|
5914
|
-
async function loadSkills(targetPath) {
|
|
5915
|
-
const skillsDir = resolve4(targetPath);
|
|
5916
|
-
if (existsSync2(skillsDir) && statSync2(skillsDir).isFile()) {
|
|
5917
|
-
try {
|
|
5918
|
-
return [await loadSkill(skillsDir)];
|
|
5919
|
-
} catch (error) {
|
|
5920
|
-
if (error instanceof ResourceMetadataError) return [];
|
|
5921
|
-
throw error;
|
|
5922
|
-
}
|
|
5923
|
-
}
|
|
5924
|
-
if (existsSync2(join2(skillsDir, SKILL_MARKER))) {
|
|
5925
|
-
try {
|
|
5926
|
-
return [await loadSkill(skillsDir)];
|
|
5927
|
-
} catch (error) {
|
|
5928
|
-
if (error instanceof ResourceMetadataError) return [];
|
|
5929
|
-
throw error;
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
5933
|
-
const skills = [];
|
|
5934
|
-
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5935
|
-
if (!entry.isDirectory()) continue;
|
|
5936
|
-
const skillFile = join2(skillsDir, entry.name, SKILL_MARKER);
|
|
5937
|
-
if (!existsSync2(skillFile)) continue;
|
|
5938
|
-
try {
|
|
5939
|
-
const skill = await loadSkill(skillFile);
|
|
5940
|
-
skills.push(skill);
|
|
5941
|
-
} catch (error) {
|
|
5942
|
-
if (error instanceof ResourceMetadataError) continue;
|
|
5943
|
-
console.warn(`Failed to load skill "${entry.name}":`, error);
|
|
5944
|
-
}
|
|
5945
|
-
}
|
|
5946
|
-
return skills;
|
|
5947
|
-
}
|
|
5948
|
-
|
|
5949
|
-
// src/extensions/loader.ts
|
|
5950
|
-
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
5951
|
-
import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
5952
|
-
import { fileURLToPath } from "url";
|
|
5953
|
-
import { basename as basename3, dirname as dirname5, extname as extname2, join as join3, resolve as resolve5 } from "path";
|
|
5954
|
-
import { createJiti } from "jiti";
|
|
5955
|
-
function isExtensionFile(path) {
|
|
5956
|
-
const extension = extname2(path).toLowerCase();
|
|
5957
|
-
return extension === ".ts" || extension === ".js";
|
|
5958
|
-
}
|
|
5959
|
-
function isSyntheticPath(path) {
|
|
5960
|
-
return path.startsWith("<") && path.endsWith(">");
|
|
5961
|
-
}
|
|
5962
|
-
function readManifestSync(dir) {
|
|
5963
|
-
const manifestPath = join3(dir, "package.json");
|
|
5964
|
-
if (!existsSync3(manifestPath)) {
|
|
5965
|
-
return void 0;
|
|
5966
|
-
}
|
|
5967
|
-
try {
|
|
5968
|
-
const content = readFileSync(manifestPath, "utf8");
|
|
5969
|
-
const pkg = JSON.parse(content);
|
|
5970
|
-
const rowan = pkg.rowan;
|
|
5971
|
-
if (!rowan) return void 0;
|
|
5972
|
-
return {
|
|
5973
|
-
entry: rowan.extensions?.[0],
|
|
5974
|
-
phase: rowan.phase
|
|
5975
|
-
};
|
|
5976
|
-
} catch {
|
|
5977
|
-
return void 0;
|
|
5978
|
-
}
|
|
5979
|
-
}
|
|
5980
|
-
function jitiAliases() {
|
|
5981
|
-
const moduleUrl = import.meta.url;
|
|
5982
|
-
if (!moduleUrl) {
|
|
5983
|
-
return {};
|
|
5984
|
-
}
|
|
5985
|
-
const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
|
|
5986
|
-
if (!existsSync3(agentSourcePath)) {
|
|
5987
|
-
return {};
|
|
5988
|
-
}
|
|
5989
|
-
return { "@rowan-agent/agent": agentSourcePath };
|
|
5990
|
-
}
|
|
5991
|
-
var sharedJiti;
|
|
5992
|
-
function getJiti() {
|
|
5993
|
-
sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
|
|
5994
|
-
fsCache: false,
|
|
5995
|
-
moduleCache: false,
|
|
5996
|
-
tryNative: false,
|
|
5997
|
-
alias: jitiAliases()
|
|
5998
|
-
});
|
|
5999
|
-
return sharedJiti;
|
|
6000
|
-
}
|
|
6001
|
-
async function loadExtensionModule(extensionPath) {
|
|
6002
|
-
const jiti = getJiti();
|
|
6003
|
-
const module = await jiti.import(extensionPath, { default: true });
|
|
6004
|
-
return typeof module === "function" ? module : void 0;
|
|
6005
|
-
}
|
|
6006
|
-
async function readPackageManifest(path) {
|
|
6007
|
-
if (!existsSync3(path)) {
|
|
6008
|
-
return void 0;
|
|
6009
|
-
}
|
|
6010
|
-
const content = await readFile3(path, "utf8");
|
|
6011
|
-
return JSON.parse(content);
|
|
6012
|
-
}
|
|
6013
|
-
async function resolveExtensionEntries(dir) {
|
|
6014
|
-
const manifest = await readPackageManifest(join3(dir, "package.json"));
|
|
6015
|
-
const declared = manifest?.rowan?.extensions;
|
|
6016
|
-
if (declared && declared.length > 0) {
|
|
6017
|
-
return declared.map((entry) => resolve5(dir, entry));
|
|
6018
|
-
}
|
|
6019
|
-
for (const name of ["index.ts", "index.js"]) {
|
|
6020
|
-
const entry = join3(dir, name);
|
|
6021
|
-
if (existsSync3(entry)) {
|
|
6022
|
-
return [entry];
|
|
6023
|
-
}
|
|
6024
|
-
}
|
|
6025
|
-
return void 0;
|
|
6026
|
-
}
|
|
6027
|
-
async function discoverExtensionsInDir(dir) {
|
|
6028
|
-
const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
|
|
6029
|
-
if (error.code === "ENOENT") {
|
|
6030
|
-
return [];
|
|
6031
|
-
}
|
|
6032
|
-
throw error;
|
|
6033
|
-
});
|
|
6034
|
-
const paths = [];
|
|
6035
|
-
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6036
|
-
const entryPath = join3(dir, entry.name);
|
|
6037
|
-
if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
|
|
6038
|
-
paths.push(entryPath);
|
|
6039
|
-
continue;
|
|
6040
|
-
}
|
|
6041
|
-
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
6042
|
-
const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
|
|
6043
|
-
if (entry.isDirectory() || info?.isDirectory()) {
|
|
6044
|
-
const resolvedEntries = await resolveExtensionEntries(entryPath);
|
|
6045
|
-
if (resolvedEntries) {
|
|
6046
|
-
paths.push(...resolvedEntries);
|
|
6047
|
-
}
|
|
6048
|
-
}
|
|
6049
|
-
}
|
|
6050
|
-
}
|
|
6051
|
-
return paths;
|
|
6052
|
-
}
|
|
6053
|
-
function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
|
|
6054
|
-
const resolvedCwd = resolve5(cwd);
|
|
6055
|
-
const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve5(resolvedCwd, extensionPath);
|
|
6056
|
-
const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname5(resolvedPath)) || "unknown";
|
|
6057
|
-
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname5(resolvedPath);
|
|
6058
|
-
const manifest = readManifestSync(manifestDir);
|
|
6059
|
-
return {
|
|
6060
|
-
path: resolvedPath,
|
|
6061
|
-
name,
|
|
6062
|
-
factory,
|
|
6063
|
-
manifest
|
|
6064
|
-
};
|
|
6065
|
-
}
|
|
6066
|
-
async function loadExtensions(paths, cwd) {
|
|
6067
|
-
const extensions = [];
|
|
6068
|
-
const errors = [];
|
|
6069
|
-
const resolvedCwd = resolve5(cwd);
|
|
6070
|
-
for (const path of paths) {
|
|
6071
|
-
const resolvedPath = resolve5(resolvedCwd, path);
|
|
6072
|
-
try {
|
|
6073
|
-
const factory = await loadExtensionModule(resolvedPath);
|
|
6074
|
-
if (!factory) {
|
|
6075
|
-
errors.push({
|
|
6076
|
-
path: resolvedPath,
|
|
6077
|
-
error: `Extension does not export a valid factory function: ${path}`
|
|
6078
|
-
});
|
|
6079
|
-
continue;
|
|
6080
|
-
}
|
|
6081
|
-
extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
|
|
6082
|
-
} catch (error) {
|
|
6083
|
-
errors.push({
|
|
6084
|
-
path: resolvedPath,
|
|
6085
|
-
error: error instanceof Error ? error.message : String(error)
|
|
6086
|
-
});
|
|
6087
|
-
}
|
|
6088
|
-
}
|
|
6089
|
-
return { extensions, errors };
|
|
6090
|
-
}
|
|
6091
|
-
async function loadExtensionsFromPath(path) {
|
|
6092
|
-
const resolvedPath = resolve5(path);
|
|
6093
|
-
const info = await stat2(resolvedPath);
|
|
6094
|
-
if (!info.isDirectory()) {
|
|
6095
|
-
return loadExtensions([resolvedPath], dirname5(resolvedPath));
|
|
6096
|
-
}
|
|
6097
|
-
const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
|
|
6098
|
-
return loadExtensions(entries, resolvedPath);
|
|
6099
|
-
}
|
|
6100
|
-
|
|
6101
7105
|
// src/index.ts
|
|
6102
7106
|
function createCoreTools2(input = {}) {
|
|
6103
7107
|
return createCoreTools(input).map((tool) => ({
|
|
@@ -6112,14 +7116,23 @@ function createCoreTools2(input = {}) {
|
|
|
6112
7116
|
}
|
|
6113
7117
|
export {
|
|
6114
7118
|
AgentRuntime,
|
|
7119
|
+
ExtensionLifetimeError,
|
|
6115
7120
|
InMemoryConfigProvider,
|
|
6116
7121
|
InMemoryStore,
|
|
7122
|
+
ResourceRegistry,
|
|
7123
|
+
ResourceRegistryError,
|
|
7124
|
+
RuntimeBootstrapRegistry,
|
|
6117
7125
|
RuntimeError,
|
|
7126
|
+
RuntimeExtensionLifetime,
|
|
6118
7127
|
SqliteStore,
|
|
6119
7128
|
brandConfigToken,
|
|
6120
7129
|
createCoreTools2 as createCoreTools,
|
|
6121
7130
|
isRuntimeError,
|
|
6122
7131
|
loadExtensionsFromPath as loadExtensions,
|
|
6123
7132
|
loadPhases,
|
|
6124
|
-
loadSkills
|
|
7133
|
+
loadSkills,
|
|
7134
|
+
materializeConfigurationSnapshot,
|
|
7135
|
+
parseAgentDefinition,
|
|
7136
|
+
parseFrontmatter,
|
|
7137
|
+
resolveConfigurationSnapshot
|
|
6125
7138
|
};
|