@rowan-agent/agent 0.8.5 → 0.8.7
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 +124 -8
- package/dist/index.d.ts +457 -21
- package/dist/index.js +1462 -611
- 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 },
|
|
@@ -1020,14 +1045,17 @@ import { dirname as dirname3, join, resolve as resolve3 } from "path";
|
|
|
1020
1045
|
import { parseModelRef } from "@rowan-agent/models";
|
|
1021
1046
|
function assertAgentDefinition(value) {
|
|
1022
1047
|
if (!isRecord(value)) throw new TypeError("config.definition is invalid");
|
|
1048
|
+
if ("content" in value) throw new TypeError("definition.content is not supported; use definition.prompt");
|
|
1049
|
+
if ("context" in value) throw new TypeError("definition.context is not supported; use definition.contexts");
|
|
1023
1050
|
requiredString(value.name, "definition.name");
|
|
1024
1051
|
requiredString(value.description, "definition.description");
|
|
1025
|
-
requiredString(value.
|
|
1052
|
+
requiredString(value.prompt, "definition.prompt");
|
|
1026
1053
|
optionalStringList(value.tools, "definition.tools");
|
|
1027
1054
|
optionalStringList(value.skills, "definition.skills");
|
|
1028
|
-
|
|
1029
|
-
optionalStringList(value.
|
|
1030
|
-
|
|
1055
|
+
optionalPhaseRegistrySelection(value.phases, "definition.phases");
|
|
1056
|
+
optionalStringList(value.contexts, "definition.contexts");
|
|
1057
|
+
if ("extensions" in value) throw new TypeError("definition.extensions is not supported; Extensions are Runtime-global");
|
|
1058
|
+
if ("entryPhase" in value) throw new TypeError("definition.entryPhase is not supported; use definition.phases.entryPhaseId");
|
|
1031
1059
|
if (value.model !== void 0) {
|
|
1032
1060
|
if (!isRecord(value.model) || typeof value.model.provider !== "string" || value.model.provider.trim() === "" || typeof value.model.id !== "string" || value.model.id.trim() === "") {
|
|
1033
1061
|
throw new TypeError("definition.model must be a valid model reference");
|
|
@@ -1038,25 +1066,31 @@ function parseAgentDefinition(raw) {
|
|
|
1038
1066
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
1039
1067
|
return normalizeAgentDefinition(frontmatter, body);
|
|
1040
1068
|
}
|
|
1041
|
-
function normalizeAgentDefinition(frontmatter,
|
|
1069
|
+
function normalizeAgentDefinition(frontmatter, prompt, options = {}) {
|
|
1070
|
+
if (frontmatter.content !== void 0) throw new TypeError("content is not supported; use prompt and the document body");
|
|
1071
|
+
if (frontmatter.context !== void 0) throw new TypeError("context is not supported; use contexts");
|
|
1042
1072
|
const name = frontmatter.name === void 0 && options.fallbackName ? options.fallbackName : requiredString(frontmatter.name, "name");
|
|
1043
1073
|
const description = requiredString(frontmatter.description, "description");
|
|
1044
|
-
const
|
|
1074
|
+
const normalizedPrompt = requiredString(prompt, "prompt");
|
|
1045
1075
|
const tools = optionalStringList(frontmatter.tools, "tools");
|
|
1046
1076
|
const skills = optionalStringList(frontmatter.skills, "skills");
|
|
1047
|
-
const phases =
|
|
1048
|
-
|
|
1049
|
-
|
|
1077
|
+
const phases = optionalPhaseRegistrySelection(frontmatter.phases, "phases");
|
|
1078
|
+
if (frontmatter.extensions !== void 0) {
|
|
1079
|
+
throw new TypeError("extensions is not supported; Extensions are Runtime-global");
|
|
1080
|
+
}
|
|
1081
|
+
const contexts = optionalStringList(frontmatter.contexts, "contexts");
|
|
1082
|
+
if (frontmatter.entryPhase !== void 0) {
|
|
1083
|
+
throw new TypeError("entryPhase is not supported; use phases.entryPhaseId");
|
|
1084
|
+
}
|
|
1050
1085
|
const model = optionalModel(frontmatter.model);
|
|
1051
1086
|
return {
|
|
1052
1087
|
name,
|
|
1053
1088
|
description,
|
|
1054
|
-
|
|
1089
|
+
prompt: normalizedPrompt,
|
|
1055
1090
|
...tools ? { tools } : {},
|
|
1056
1091
|
...skills ? { skills } : {},
|
|
1057
1092
|
...phases ? { phases } : {},
|
|
1058
|
-
...
|
|
1059
|
-
...entryPhase ? { entryPhase } : {},
|
|
1093
|
+
...contexts ? { contexts } : {},
|
|
1060
1094
|
...model ? { model } : {}
|
|
1061
1095
|
};
|
|
1062
1096
|
}
|
|
@@ -1089,6 +1123,22 @@ function optionalStringList(value, field) {
|
|
|
1089
1123
|
}
|
|
1090
1124
|
return names;
|
|
1091
1125
|
}
|
|
1126
|
+
function optionalPhaseRegistrySelection(value, field) {
|
|
1127
|
+
if (value === void 0) return void 0;
|
|
1128
|
+
if (!isRecord(value)) throw new TypeError(`${field} must be an object`);
|
|
1129
|
+
if (!("entryPhaseId" in value)) throw new TypeError(`${field}.entryPhaseId is required`);
|
|
1130
|
+
if (!("phaseIds" in value)) throw new TypeError(`${field}.phaseIds is required`);
|
|
1131
|
+
const entryPhaseId = value.entryPhaseId;
|
|
1132
|
+
if (entryPhaseId !== null && (typeof entryPhaseId !== "string" || entryPhaseId.trim() === "")) {
|
|
1133
|
+
throw new TypeError(`${field}.entryPhaseId must be a non-empty string or null`);
|
|
1134
|
+
}
|
|
1135
|
+
const phaseIds = optionalStringList(value.phaseIds, `${field}.phaseIds`);
|
|
1136
|
+
if (!phaseIds) throw new TypeError(`${field}.phaseIds is required`);
|
|
1137
|
+
return {
|
|
1138
|
+
entryPhaseId: entryPhaseId === null ? null : entryPhaseId.trim(),
|
|
1139
|
+
phaseIds
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1092
1142
|
function optionalModel(value) {
|
|
1093
1143
|
const source = optionalString(value, "model");
|
|
1094
1144
|
if (!source) return void 0;
|
|
@@ -1227,7 +1277,7 @@ async function loadPhase(targetPath) {
|
|
|
1227
1277
|
isolated: metadata.isolated,
|
|
1228
1278
|
filePath: resolved,
|
|
1229
1279
|
baseDir,
|
|
1230
|
-
content: definition.
|
|
1280
|
+
content: definition.prompt,
|
|
1231
1281
|
model: definition.model
|
|
1232
1282
|
};
|
|
1233
1283
|
const codePath = await discoverPhaseCode(baseDir);
|
|
@@ -1693,7 +1743,7 @@ function createTerminalOutcome(phase, output, transcript) {
|
|
|
1693
1743
|
return phase.run || phase.factory ? createOutcome.phase(output, transcript) : createOutcome.default(output, transcript);
|
|
1694
1744
|
}
|
|
1695
1745
|
function buildToolsWithRouting(config, availablePhases) {
|
|
1696
|
-
const tools =
|
|
1746
|
+
const tools = config.context.tools.filter((tool) => tool.name !== PhaseRouteTool);
|
|
1697
1747
|
if (availablePhases.length > 0) {
|
|
1698
1748
|
tools.push(createRouteTool(availablePhases));
|
|
1699
1749
|
}
|
|
@@ -2158,7 +2208,7 @@ async function withRetry(fn, options = {}) {
|
|
|
2158
2208
|
}
|
|
2159
2209
|
const delayMs = getRetryDelay(attempt, baseDelayMs, maxDelayMs);
|
|
2160
2210
|
options.onRetry?.(attempt, error, delayMs);
|
|
2161
|
-
await new Promise((
|
|
2211
|
+
await new Promise((resolve7) => setTimeout(resolve7, delayMs));
|
|
2162
2212
|
if (options.signal?.aborted) {
|
|
2163
2213
|
throw lastError;
|
|
2164
2214
|
}
|
|
@@ -2663,6 +2713,9 @@ function isContinuation(value) {
|
|
|
2663
2713
|
}
|
|
2664
2714
|
|
|
2665
2715
|
// src/runtime/contracts.ts
|
|
2716
|
+
function isAgentConfiguration(config) {
|
|
2717
|
+
return !("resources" in config);
|
|
2718
|
+
}
|
|
2666
2719
|
var METADATA_LIMIT = 64 * 1024;
|
|
2667
2720
|
var IDENTITY_LIMIT = 256;
|
|
2668
2721
|
function isRecord3(value) {
|
|
@@ -2715,6 +2768,42 @@ function assertAgentConfig(config) {
|
|
|
2715
2768
|
throw new TypeError("config.resources is invalid");
|
|
2716
2769
|
}
|
|
2717
2770
|
for (const tool of config.resources.tools) projectToolDefinition(tool);
|
|
2771
|
+
const names = /* @__PURE__ */ new Set();
|
|
2772
|
+
for (const context of config.resources.contexts ?? []) {
|
|
2773
|
+
if (typeof context.name !== "string" || context.name.trim() === "") {
|
|
2774
|
+
throw new TypeError("Context candidate name must be non-empty");
|
|
2775
|
+
}
|
|
2776
|
+
if (names.has(context.name)) throw new TypeError(`Duplicate Context candidate "${context.name}".`);
|
|
2777
|
+
names.add(context.name);
|
|
2778
|
+
assertJsonValue(context.value, `Context candidate "${context.name}" value`);
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
function assertAgentConfigRequest(config) {
|
|
2782
|
+
if (!isAgentConfiguration(config)) {
|
|
2783
|
+
assertAgentConfig(config);
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
|
|
2787
|
+
assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
|
|
2788
|
+
if (!config.definition || typeof config.definition.name !== "string" || config.definition.name.trim() === "") {
|
|
2789
|
+
throw new TypeError("config.definition.name must be non-empty");
|
|
2790
|
+
}
|
|
2791
|
+
if (config.definition.layer && "content" in config.definition.layer) {
|
|
2792
|
+
throw new TypeError("definition.layer.content is not supported; use definition.layer.prompt");
|
|
2793
|
+
}
|
|
2794
|
+
const view = config.resourceView;
|
|
2795
|
+
if (!view || !Array.isArray(view.agents) || !Array.isArray(view.tools) || !Array.isArray(view.skills) || !Array.isArray(view.phases)) {
|
|
2796
|
+
throw new TypeError("config.resourceView is invalid");
|
|
2797
|
+
}
|
|
2798
|
+
for (const [kind, ids] of Object.entries(view)) {
|
|
2799
|
+
for (const sourceId of ids) {
|
|
2800
|
+
if (typeof sourceId !== "string" || sourceId.trim() === "") throw new TypeError(`config.resourceView.${kind} contains an invalid Source ID`);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
for (const context of config.contexts ?? []) {
|
|
2804
|
+
if (typeof context.name !== "string" || context.name.trim() === "") throw new TypeError("Context candidate name must be non-empty");
|
|
2805
|
+
assertJsonValue(context.value, `Context candidate "${context.name}" value`);
|
|
2806
|
+
}
|
|
2718
2807
|
}
|
|
2719
2808
|
function assertToolExecutionResult(value) {
|
|
2720
2809
|
if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
|
|
@@ -2808,21 +2897,92 @@ function validateConfigResolution(agentId, value) {
|
|
|
2808
2897
|
throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
|
|
2809
2898
|
}
|
|
2810
2899
|
function snapshotConfig(config) {
|
|
2900
|
+
if (isAgentConfiguration(config)) return snapshotConfiguration(config);
|
|
2811
2901
|
const definition = Object.freeze({
|
|
2812
2902
|
...config.definition,
|
|
2813
2903
|
...config.definition.tools ? { tools: Object.freeze([...config.definition.tools]) } : {},
|
|
2814
2904
|
...config.definition.skills ? { skills: Object.freeze([...config.definition.skills]) } : {},
|
|
2815
|
-
...config.definition.phases ? {
|
|
2816
|
-
|
|
2905
|
+
...config.definition.phases ? {
|
|
2906
|
+
phases: Object.freeze({
|
|
2907
|
+
entryPhaseId: config.definition.phases.entryPhaseId,
|
|
2908
|
+
phaseIds: Object.freeze([...config.definition.phases.phaseIds])
|
|
2909
|
+
})
|
|
2910
|
+
} : {},
|
|
2911
|
+
...config.definition.contexts ? { contexts: Object.freeze([...config.definition.contexts]) } : {}
|
|
2817
2912
|
});
|
|
2818
2913
|
const resources = Object.freeze({
|
|
2819
2914
|
...config.resources,
|
|
2820
2915
|
tools: Object.freeze([...config.resources.tools]),
|
|
2821
2916
|
skills: Object.freeze([...config.resources.skills]),
|
|
2822
|
-
...config.resources.
|
|
2917
|
+
...config.resources.resourceView ? {
|
|
2918
|
+
resourceView: Object.freeze({
|
|
2919
|
+
agents: Object.freeze([...config.resources.resourceView.agents]),
|
|
2920
|
+
tools: Object.freeze([...config.resources.resourceView.tools]),
|
|
2921
|
+
skills: Object.freeze([...config.resources.resourceView.skills]),
|
|
2922
|
+
phases: Object.freeze([...config.resources.resourceView.phases])
|
|
2923
|
+
})
|
|
2924
|
+
} : {},
|
|
2925
|
+
...config.resources.resourceRefs ? { resourceRefs: Object.freeze(config.resources.resourceRefs.map((ref) => Object.freeze({ ...ref }))) } : {},
|
|
2926
|
+
...config.resources.resourceRevisions ? {
|
|
2927
|
+
resourceRevisions: Object.freeze(Object.fromEntries(
|
|
2928
|
+
Object.entries(config.resources.resourceRevisions).map(([kind, revisions]) => [kind, Object.freeze([...revisions])])
|
|
2929
|
+
))
|
|
2930
|
+
} : {},
|
|
2931
|
+
...config.resources.contexts ? {
|
|
2932
|
+
contexts: Object.freeze(config.resources.contexts.map((context) => Object.freeze({
|
|
2933
|
+
name: context.name,
|
|
2934
|
+
value: snapshotJsonValue(context.value)
|
|
2935
|
+
})))
|
|
2936
|
+
} : {}
|
|
2823
2937
|
});
|
|
2824
2938
|
return Object.freeze({ ...config, definition, resources });
|
|
2825
2939
|
}
|
|
2940
|
+
function snapshotConfiguration(config) {
|
|
2941
|
+
const layer = config.definition.layer;
|
|
2942
|
+
const definition = Object.freeze({
|
|
2943
|
+
name: config.definition.name,
|
|
2944
|
+
...layer === void 0 ? {} : {
|
|
2945
|
+
layer: Object.freeze({
|
|
2946
|
+
...layer,
|
|
2947
|
+
...layer.tools ? { tools: Object.freeze([...layer.tools]) } : {},
|
|
2948
|
+
...layer.skills ? { skills: Object.freeze([...layer.skills]) } : {},
|
|
2949
|
+
...layer.phases ? {
|
|
2950
|
+
phases: Object.freeze({
|
|
2951
|
+
entryPhaseId: layer.phases.entryPhaseId,
|
|
2952
|
+
phaseIds: Object.freeze([...layer.phases.phaseIds])
|
|
2953
|
+
})
|
|
2954
|
+
} : {}
|
|
2955
|
+
})
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
const resourceView = Object.freeze({
|
|
2959
|
+
agents: Object.freeze([...config.resourceView.agents]),
|
|
2960
|
+
tools: Object.freeze([...config.resourceView.tools]),
|
|
2961
|
+
skills: Object.freeze([...config.resourceView.skills]),
|
|
2962
|
+
phases: Object.freeze([...config.resourceView.phases])
|
|
2963
|
+
});
|
|
2964
|
+
return Object.freeze({
|
|
2965
|
+
...config,
|
|
2966
|
+
definition,
|
|
2967
|
+
resourceView,
|
|
2968
|
+
...config.contexts ? {
|
|
2969
|
+
contexts: Object.freeze(config.contexts.map((context) => Object.freeze({
|
|
2970
|
+
name: context.name,
|
|
2971
|
+
value: snapshotJsonValue(context.value)
|
|
2972
|
+
})))
|
|
2973
|
+
} : {}
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
function snapshotJsonValue(value) {
|
|
2977
|
+
return deepFreeze(JSON.parse(canonicalJson(value)));
|
|
2978
|
+
}
|
|
2979
|
+
function deepFreeze(value) {
|
|
2980
|
+
if (value && typeof value === "object") {
|
|
2981
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
2982
|
+
Object.freeze(value);
|
|
2983
|
+
}
|
|
2984
|
+
return value;
|
|
2985
|
+
}
|
|
2826
2986
|
function assertConfigIdentity(identity) {
|
|
2827
2987
|
if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
|
|
2828
2988
|
assertUtf8ByteLimit(identity, 256, "config.identity");
|
|
@@ -2867,7 +3027,7 @@ var ConfigCommandService = class {
|
|
|
2867
3027
|
configs;
|
|
2868
3028
|
storeIncarnation;
|
|
2869
3029
|
async createAgent(input) {
|
|
2870
|
-
|
|
3030
|
+
assertAgentConfigRequest(input.config);
|
|
2871
3031
|
assertIdentity(input.config.identity);
|
|
2872
3032
|
const reserved = await this.store.reserveAgent({
|
|
2873
3033
|
idempotencyKey: input.idempotencyKey,
|
|
@@ -2889,7 +3049,7 @@ var ConfigCommandService = class {
|
|
|
2889
3049
|
return reserved.id;
|
|
2890
3050
|
}
|
|
2891
3051
|
async updateAgentConfig(input) {
|
|
2892
|
-
|
|
3052
|
+
assertAgentConfigRequest(input.config);
|
|
2893
3053
|
assertIdentity(input.config.identity);
|
|
2894
3054
|
const agent = await this.findAgent(input.agentId);
|
|
2895
3055
|
const operationId = this.operationId("update_agent_config", input.agentId, input.idempotencyKey);
|
|
@@ -2916,6 +3076,23 @@ var ConfigCommandService = class {
|
|
|
2916
3076
|
const resolution = validateConfigResolution(input.agent.id, result);
|
|
2917
3077
|
return resolution;
|
|
2918
3078
|
}
|
|
3079
|
+
/** Persist one resolved Configuration Snapshot under a Run-specific token.
|
|
3080
|
+
* The durable Run pins this token at claim time, so later Source replacement
|
|
3081
|
+
* cannot mutate an active or input-waiting Run. */
|
|
3082
|
+
async storeSnapshot(input) {
|
|
3083
|
+
assertAgentConfigRequest(input.config);
|
|
3084
|
+
const result = await this.put({
|
|
3085
|
+
agentId: input.agent.id,
|
|
3086
|
+
agentMetadata: input.agent.metadata,
|
|
3087
|
+
config: input.config,
|
|
3088
|
+
operationId: input.operationId,
|
|
3089
|
+
signal: input.signal
|
|
3090
|
+
});
|
|
3091
|
+
if (result.kind === "identity_conflict") {
|
|
3092
|
+
throw idempotencyConflict("update_agent_config", input.operationId);
|
|
3093
|
+
}
|
|
3094
|
+
return brandConfigToken(result.token);
|
|
3095
|
+
}
|
|
2919
3096
|
async findAgent(agentId) {
|
|
2920
3097
|
const agent = (await this.store.listAgents()).find((candidate) => candidate.id === agentId);
|
|
2921
3098
|
if (!agent) throw new RuntimeError("agent_not_found", { agentId });
|
|
@@ -3149,156 +3326,400 @@ function jsonText(value) {
|
|
|
3149
3326
|
return typeof value === "string" ? value : JSON.stringify(value) ?? "null";
|
|
3150
3327
|
}
|
|
3151
3328
|
|
|
3152
|
-
// src/extensions
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
var HooksManager = class {
|
|
3162
|
-
handlers = /* @__PURE__ */ new Map();
|
|
3163
|
-
on(eventType, handler) {
|
|
3164
|
-
const handlers = this.handlers.get(eventType) ?? [];
|
|
3165
|
-
handlers.push(handler);
|
|
3166
|
-
this.handlers.set(eventType, handlers);
|
|
3167
|
-
}
|
|
3168
|
-
off(eventType, handler) {
|
|
3169
|
-
const handlers = this.handlers.get(eventType);
|
|
3170
|
-
if (!handlers) return;
|
|
3171
|
-
const index = handlers.indexOf(handler);
|
|
3172
|
-
if (index >= 0) handlers.splice(index, 1);
|
|
3173
|
-
}
|
|
3174
|
-
async emit(eventType, event) {
|
|
3175
|
-
const handlers = this.handlers.get(eventType);
|
|
3176
|
-
if (!handlers?.length) return;
|
|
3177
|
-
const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
|
|
3178
|
-
const failure = results.find((result) => result.status === "rejected");
|
|
3179
|
-
if (failure) {
|
|
3180
|
-
throw new HookError(
|
|
3181
|
-
eventType,
|
|
3182
|
-
`${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
|
|
3183
|
-
failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
|
|
3184
|
-
);
|
|
3185
|
-
}
|
|
3329
|
+
// src/runtime/extensions.ts
|
|
3330
|
+
function assembleRegisteredExtensions(config, runner) {
|
|
3331
|
+
const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
|
|
3332
|
+
const tools = [...config.resources.tools];
|
|
3333
|
+
const names = new Set(tools.map((tool) => tool.name));
|
|
3334
|
+
for (const tool of extensionTools) {
|
|
3335
|
+
if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
|
|
3336
|
+
names.add(tool.name);
|
|
3337
|
+
tools.push(tool);
|
|
3186
3338
|
}
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3339
|
+
const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
|
|
3340
|
+
const phases = mergePhases(config.resources.phases, extensionPhases);
|
|
3341
|
+
const context = resolveDefinitionContext(config, { tools, phases });
|
|
3342
|
+
return {
|
|
3343
|
+
context,
|
|
3344
|
+
beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
|
|
3345
|
+
afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
|
|
3346
|
+
beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
|
|
3347
|
+
beforeToolCall: async (input) => {
|
|
3348
|
+
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3349
|
+
const decision = await runner.emitBeforeToolCall(tool, input.args);
|
|
3350
|
+
return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
|
|
3351
|
+
},
|
|
3352
|
+
afterToolCall: async (input) => {
|
|
3353
|
+
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3354
|
+
const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
|
|
3355
|
+
return fromLoopResult(result);
|
|
3356
|
+
},
|
|
3357
|
+
setContext: (context2) => {
|
|
3358
|
+
runner.currentContext = context2;
|
|
3201
3359
|
}
|
|
3202
|
-
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
function resolveDefinitionContext(config, assembled = {}) {
|
|
3363
|
+
const tools = selectNamedResources(
|
|
3364
|
+
assembled.tools ?? config.resources.tools,
|
|
3365
|
+
config.definition.tools,
|
|
3366
|
+
"Tool"
|
|
3367
|
+
);
|
|
3368
|
+
const skills = selectNamedResources(
|
|
3369
|
+
config.resources.skills,
|
|
3370
|
+
config.definition.skills,
|
|
3371
|
+
"Skill"
|
|
3372
|
+
);
|
|
3373
|
+
const contexts = selectNamedResources(
|
|
3374
|
+
config.resources.contexts ?? [],
|
|
3375
|
+
config.definition.contexts,
|
|
3376
|
+
"Context"
|
|
3377
|
+
);
|
|
3378
|
+
const candidateRegistry = assembled.phases ?? config.resources.phases;
|
|
3379
|
+
const selectedPhases = selectNamedResources(
|
|
3380
|
+
[...candidateRegistry?.phases.values() ?? []],
|
|
3381
|
+
config.definition.phases?.phaseIds,
|
|
3382
|
+
"Phase"
|
|
3383
|
+
);
|
|
3384
|
+
const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
|
|
3385
|
+
const requestedEntry = config.definition.phases ? config.definition.phases.entryPhaseId : candidateRegistry?.entryPhaseId ?? null;
|
|
3386
|
+
const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
|
|
3387
|
+
if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
|
|
3388
|
+
console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
|
|
3203
3389
|
}
|
|
3204
|
-
};
|
|
3205
|
-
|
|
3206
|
-
// src/extensions/runner.ts
|
|
3207
|
-
import { execFile } from "child_process";
|
|
3208
|
-
import {
|
|
3209
|
-
registerModel,
|
|
3210
|
-
unregisterProviderModels,
|
|
3211
|
-
registerApiProvider
|
|
3212
|
-
} from "@rowan-agent/models";
|
|
3213
|
-
|
|
3214
|
-
// src/extensions/types.ts
|
|
3215
|
-
function createSourceInfo(extensionPath, options = {}) {
|
|
3216
|
-
const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
|
|
3217
|
-
const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
|
|
3218
3390
|
return {
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3391
|
+
systemPrompt: [config.definition.prompt, buildContextDescription(contexts)].filter((section) => section.length > 0).join("\n\n"),
|
|
3392
|
+
tools,
|
|
3393
|
+
skills,
|
|
3394
|
+
phases: { phases, entryPhaseId }
|
|
3222
3395
|
};
|
|
3223
3396
|
}
|
|
3224
|
-
function
|
|
3225
|
-
|
|
3226
|
-
const
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3397
|
+
function mergePhases(base, extension) {
|
|
3398
|
+
if (!base && extension.phases.size === 0) return void 0;
|
|
3399
|
+
const phases = new Map(base?.phases ?? []);
|
|
3400
|
+
for (const [name, phase] of extension.phases) {
|
|
3401
|
+
if (phases.has(name)) throw new TypeError(`Extension Phase collides with Context Phase "${name}"`);
|
|
3402
|
+
phases.set(name, phase);
|
|
3403
|
+
}
|
|
3404
|
+
return {
|
|
3405
|
+
phases,
|
|
3406
|
+
entryPhaseId: base?.entryPhaseId ?? extension.entryPhaseId
|
|
3230
3407
|
};
|
|
3408
|
+
}
|
|
3409
|
+
function adaptExtensionTool(input) {
|
|
3410
|
+
const definition = input.definition;
|
|
3231
3411
|
return {
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3412
|
+
name: definition.name,
|
|
3413
|
+
description: definition.description,
|
|
3414
|
+
parameters: definition.parameters,
|
|
3415
|
+
execute: async (args, _context, signal) => {
|
|
3416
|
+
const result = await definition.execute(args, signal);
|
|
3417
|
+
const content = JSON.parse(JSON.stringify(result.content));
|
|
3418
|
+
return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
|
|
3235
3419
|
}
|
|
3236
3420
|
};
|
|
3237
3421
|
}
|
|
3238
|
-
|
|
3239
|
-
// src/extensions/runner.ts
|
|
3240
|
-
import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
|
|
3241
|
-
|
|
3242
|
-
// src/extensions/event-bus.ts
|
|
3243
|
-
function createEventBus() {
|
|
3244
|
-
const listeners = /* @__PURE__ */ new Map();
|
|
3422
|
+
function toLoopResult(result, toolCallId, toolName) {
|
|
3245
3423
|
return {
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
set.delete(listener);
|
|
3252
|
-
if (set.size === 0) listeners.delete(event);
|
|
3253
|
-
};
|
|
3254
|
-
},
|
|
3255
|
-
emit(event, ...args) {
|
|
3256
|
-
const set = listeners.get(event);
|
|
3257
|
-
if (!set) return;
|
|
3258
|
-
for (const listener of set) {
|
|
3259
|
-
try {
|
|
3260
|
-
listener(...args);
|
|
3261
|
-
} catch (err) {
|
|
3262
|
-
console.error(`[event-bus] Listener error for "${event}":`, err);
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
},
|
|
3266
|
-
off(event) {
|
|
3267
|
-
if (event) {
|
|
3268
|
-
listeners.delete(event);
|
|
3269
|
-
} else {
|
|
3270
|
-
listeners.clear();
|
|
3271
|
-
}
|
|
3272
|
-
},
|
|
3273
|
-
has(event) {
|
|
3274
|
-
const set = listeners.get(event);
|
|
3275
|
-
return set !== void 0 && set.size > 0;
|
|
3276
|
-
},
|
|
3277
|
-
count(event) {
|
|
3278
|
-
return listeners.get(event)?.size ?? 0;
|
|
3279
|
-
}
|
|
3424
|
+
toolCallId,
|
|
3425
|
+
toolName,
|
|
3426
|
+
ok: result.ok,
|
|
3427
|
+
content: result.content,
|
|
3428
|
+
...!result.ok ? { error: result.error } : {}
|
|
3280
3429
|
};
|
|
3281
3430
|
}
|
|
3431
|
+
function fromLoopResult(result) {
|
|
3432
|
+
return result.ok ? { ok: true, content: result.content } : { ok: false, content: result.content, error: result.error ?? "Extension hook rejected the Tool result." };
|
|
3433
|
+
}
|
|
3282
3434
|
|
|
3283
|
-
// src/
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3435
|
+
// src/runtime/transient-run-events.ts
|
|
3436
|
+
var MAX_BUFFERED_EVENTS = 128;
|
|
3437
|
+
var MAX_BUFFERED_TEXT = 64 * 1024;
|
|
3438
|
+
function deferred() {
|
|
3439
|
+
let resolve7;
|
|
3440
|
+
const promise = new Promise((done) => {
|
|
3441
|
+
resolve7 = done;
|
|
3442
|
+
});
|
|
3443
|
+
return { promise, resolve: resolve7 };
|
|
3444
|
+
}
|
|
3445
|
+
var TransientRunEventSubscription = class {
|
|
3446
|
+
queue = [];
|
|
3447
|
+
wake = deferred();
|
|
3448
|
+
version = 0;
|
|
3449
|
+
stopped = false;
|
|
3450
|
+
push(event) {
|
|
3451
|
+
if (this.stopped) return;
|
|
3452
|
+
const previous = this.queue.at(-1);
|
|
3453
|
+
if (previous?.kind === "message_delta" && event.kind === "message_delta" && previous.executionId === event.executionId && previous.messageId === event.messageId && previous.offset + previous.text.length === event.offset) {
|
|
3454
|
+
this.queue[this.queue.length - 1] = {
|
|
3455
|
+
...previous,
|
|
3456
|
+
text: previous.text + event.text
|
|
3457
|
+
};
|
|
3458
|
+
} else {
|
|
3459
|
+
if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
|
|
3460
|
+
this.queue.push(event);
|
|
3461
|
+
}
|
|
3462
|
+
this.trimText();
|
|
3463
|
+
this.notify();
|
|
3464
|
+
}
|
|
3465
|
+
shift() {
|
|
3466
|
+
return this.queue.shift();
|
|
3467
|
+
}
|
|
3468
|
+
clear(executionId) {
|
|
3469
|
+
if (executionId === void 0) this.queue.length = 0;
|
|
3470
|
+
else {
|
|
3471
|
+
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3472
|
+
if (this.queue[index]?.executionId === executionId) this.queue.splice(index, 1);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
this.notify();
|
|
3476
|
+
}
|
|
3477
|
+
clearMessage(messageId) {
|
|
3478
|
+
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3479
|
+
const event = this.queue[index];
|
|
3480
|
+
if (event?.kind === "message_delta" && event.messageId === messageId) this.queue.splice(index, 1);
|
|
3481
|
+
}
|
|
3482
|
+
this.notify();
|
|
3483
|
+
}
|
|
3484
|
+
clearTool(toolCallId) {
|
|
3485
|
+
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
3486
|
+
const event = this.queue[index];
|
|
3487
|
+
if (event?.kind === "tool_progress" && event.toolCallId === toolCallId) this.queue.splice(index, 1);
|
|
3488
|
+
}
|
|
3489
|
+
this.notify();
|
|
3490
|
+
}
|
|
3491
|
+
checkpoint() {
|
|
3492
|
+
return this.version;
|
|
3493
|
+
}
|
|
3494
|
+
changed(since) {
|
|
3495
|
+
if (this.version !== since || this.queue.length > 0 || this.stopped) return Promise.resolve();
|
|
3496
|
+
return this.wake.promise;
|
|
3497
|
+
}
|
|
3498
|
+
stop() {
|
|
3499
|
+
if (this.stopped) return;
|
|
3500
|
+
this.stopped = true;
|
|
3501
|
+
this.queue.length = 0;
|
|
3502
|
+
this.notify();
|
|
3503
|
+
}
|
|
3504
|
+
notify() {
|
|
3505
|
+
const wake = this.wake;
|
|
3506
|
+
this.wake = deferred();
|
|
3507
|
+
this.version += 1;
|
|
3508
|
+
wake.resolve();
|
|
3509
|
+
}
|
|
3510
|
+
trimText() {
|
|
3511
|
+
let buffered = this.queue.reduce(
|
|
3512
|
+
(length, event) => length + (event.kind === "message_delta" ? event.text.length : 0),
|
|
3513
|
+
0
|
|
3514
|
+
);
|
|
3515
|
+
while (buffered > MAX_BUFFERED_TEXT) {
|
|
3516
|
+
const index = this.queue.findIndex((event2) => event2.kind === "message_delta");
|
|
3517
|
+
if (index < 0) return;
|
|
3518
|
+
const event = this.queue[index];
|
|
3519
|
+
const overflow = buffered - MAX_BUFFERED_TEXT;
|
|
3520
|
+
if (event.text.length <= overflow) {
|
|
3521
|
+
this.queue.splice(index, 1);
|
|
3522
|
+
buffered -= event.text.length;
|
|
3523
|
+
} else {
|
|
3524
|
+
this.queue[index] = {
|
|
3525
|
+
...event,
|
|
3526
|
+
offset: event.offset + overflow,
|
|
3527
|
+
text: event.text.slice(overflow)
|
|
3528
|
+
};
|
|
3529
|
+
buffered -= overflow;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
};
|
|
3534
|
+
var TransientRunEventHub = class {
|
|
3535
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
3536
|
+
subscribe(runId) {
|
|
3537
|
+
const subscription = new TransientRunEventSubscription();
|
|
3538
|
+
const current = this.subscriptions.get(runId) ?? /* @__PURE__ */ new Set();
|
|
3539
|
+
current.add(subscription);
|
|
3540
|
+
this.subscriptions.set(runId, current);
|
|
3541
|
+
return subscription;
|
|
3542
|
+
}
|
|
3543
|
+
unsubscribe(runId, subscription) {
|
|
3544
|
+
subscription.stop();
|
|
3545
|
+
const current = this.subscriptions.get(runId);
|
|
3546
|
+
current?.delete(subscription);
|
|
3547
|
+
if (current?.size === 0) this.subscriptions.delete(runId);
|
|
3548
|
+
}
|
|
3549
|
+
publish(event) {
|
|
3550
|
+
for (const subscription of this.subscriptions.get(event.runId) ?? []) {
|
|
3551
|
+
subscription.push(event);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
clear(runId, executionId) {
|
|
3555
|
+
for (const subscription of this.subscriptions.get(runId) ?? []) {
|
|
3556
|
+
subscription.clear(executionId);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
clearMessage(runId, messageId) {
|
|
3560
|
+
for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearMessage(messageId);
|
|
3561
|
+
}
|
|
3562
|
+
clearTool(runId, toolCallId) {
|
|
3563
|
+
for (const subscription of this.subscriptions.get(runId) ?? []) subscription.clearTool(toolCallId);
|
|
3564
|
+
}
|
|
3565
|
+
close() {
|
|
3566
|
+
for (const subscriptions of this.subscriptions.values()) {
|
|
3567
|
+
for (const subscription of subscriptions) subscription.stop();
|
|
3568
|
+
}
|
|
3569
|
+
this.subscriptions.clear();
|
|
3570
|
+
}
|
|
3571
|
+
};
|
|
3572
|
+
|
|
3573
|
+
// src/extensions/hooks.ts
|
|
3574
|
+
var HookError = class extends Error {
|
|
3575
|
+
constructor(eventType, message, cause) {
|
|
3576
|
+
super(message, cause === void 0 ? void 0 : { cause });
|
|
3577
|
+
this.eventType = eventType;
|
|
3578
|
+
this.name = "HookError";
|
|
3579
|
+
}
|
|
3580
|
+
eventType;
|
|
3581
|
+
};
|
|
3582
|
+
var HooksManager = class {
|
|
3583
|
+
handlers = /* @__PURE__ */ new Map();
|
|
3584
|
+
on(eventType, handler) {
|
|
3585
|
+
const handlers = this.handlers.get(eventType) ?? [];
|
|
3586
|
+
handlers.push(handler);
|
|
3587
|
+
this.handlers.set(eventType, handlers);
|
|
3588
|
+
}
|
|
3589
|
+
off(eventType, handler) {
|
|
3590
|
+
const handlers = this.handlers.get(eventType);
|
|
3591
|
+
if (!handlers) return;
|
|
3592
|
+
const index = handlers.indexOf(handler);
|
|
3593
|
+
if (index >= 0) handlers.splice(index, 1);
|
|
3594
|
+
}
|
|
3595
|
+
async emit(eventType, event) {
|
|
3596
|
+
const handlers = this.handlers.get(eventType);
|
|
3597
|
+
if (!handlers?.length) return;
|
|
3598
|
+
const results = await Promise.allSettled(handlers.map((handler) => handler(event)));
|
|
3599
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
3600
|
+
if (failure) {
|
|
3601
|
+
throw new HookError(
|
|
3602
|
+
eventType,
|
|
3603
|
+
`${results.filter((result) => result.status === "rejected").length} handler(s) failed for "${eventType}"`,
|
|
3604
|
+
failure.reason instanceof Error ? failure.reason : new Error(String(failure.reason))
|
|
3605
|
+
);
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
async emitFirst(eventType, event) {
|
|
3609
|
+
const handlers = this.handlers.get(eventType);
|
|
3610
|
+
if (!handlers?.length) return void 0;
|
|
3611
|
+
for (const handler of handlers) {
|
|
3612
|
+
try {
|
|
3613
|
+
const result = await handler(event);
|
|
3614
|
+
if (result !== void 0) return result;
|
|
3615
|
+
} catch (error) {
|
|
3616
|
+
throw new HookError(
|
|
3617
|
+
eventType,
|
|
3618
|
+
`Handler failed for "${eventType}"`,
|
|
3619
|
+
error instanceof Error ? error : new Error(String(error))
|
|
3620
|
+
);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
return void 0;
|
|
3624
|
+
}
|
|
3625
|
+
};
|
|
3626
|
+
|
|
3627
|
+
// src/extensions/runner.ts
|
|
3628
|
+
import { execFile } from "child_process";
|
|
3629
|
+
import {
|
|
3630
|
+
registerModel,
|
|
3631
|
+
unregisterProviderModels,
|
|
3632
|
+
registerApiProvider
|
|
3633
|
+
} from "@rowan-agent/models";
|
|
3634
|
+
|
|
3635
|
+
// src/extensions/types.ts
|
|
3636
|
+
function createSourceInfo(extensionPath, options = {}) {
|
|
3637
|
+
const source = options.source ?? (extensionPath.startsWith("<") ? "synthetic" : "local");
|
|
3638
|
+
const displayName = extensionPath.startsWith("<") ? extensionPath.slice(1, -1) : extensionPath.split("/").pop() ?? extensionPath;
|
|
3639
|
+
return {
|
|
3640
|
+
source,
|
|
3641
|
+
baseDir: options.baseDir,
|
|
3642
|
+
displayName
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
function createExtensionRuntime() {
|
|
3646
|
+
const state = {};
|
|
3647
|
+
const assertActive = () => {
|
|
3648
|
+
if (state.staleMessage) {
|
|
3649
|
+
throw new Error(state.staleMessage);
|
|
3650
|
+
}
|
|
3651
|
+
};
|
|
3652
|
+
return {
|
|
3653
|
+
assertActive,
|
|
3654
|
+
invalidate: (message) => {
|
|
3655
|
+
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.";
|
|
3656
|
+
}
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
// src/extensions/runner.ts
|
|
3661
|
+
import { parseModelRef as parseModelRef2 } from "@rowan-agent/models";
|
|
3662
|
+
|
|
3663
|
+
// src/extensions/event-bus.ts
|
|
3664
|
+
function createEventBus() {
|
|
3665
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
3666
|
+
return {
|
|
3667
|
+
on(event, listener) {
|
|
3668
|
+
const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
3669
|
+
set.add(listener);
|
|
3670
|
+
listeners.set(event, set);
|
|
3671
|
+
return () => {
|
|
3672
|
+
set.delete(listener);
|
|
3673
|
+
if (set.size === 0) listeners.delete(event);
|
|
3674
|
+
};
|
|
3675
|
+
},
|
|
3676
|
+
emit(event, ...args) {
|
|
3677
|
+
const set = listeners.get(event);
|
|
3678
|
+
if (!set) return;
|
|
3679
|
+
for (const listener of set) {
|
|
3680
|
+
try {
|
|
3681
|
+
listener(...args);
|
|
3682
|
+
} catch (err) {
|
|
3683
|
+
console.error(`[event-bus] Listener error for "${event}":`, err);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
},
|
|
3687
|
+
off(event) {
|
|
3688
|
+
if (event) {
|
|
3689
|
+
listeners.delete(event);
|
|
3690
|
+
} else {
|
|
3691
|
+
listeners.clear();
|
|
3692
|
+
}
|
|
3693
|
+
},
|
|
3694
|
+
has(event) {
|
|
3695
|
+
const set = listeners.get(event);
|
|
3696
|
+
return set !== void 0 && set.size > 0;
|
|
3697
|
+
},
|
|
3698
|
+
count(event) {
|
|
3699
|
+
return listeners.get(event)?.size ?? 0;
|
|
3700
|
+
}
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
// src/extensions/runner.ts
|
|
3705
|
+
async function execCommand(command, args, cwd, options) {
|
|
3706
|
+
return new Promise((resolve7, reject) => {
|
|
3707
|
+
const child = execFile(
|
|
3708
|
+
command,
|
|
3709
|
+
args,
|
|
3710
|
+
{
|
|
3711
|
+
cwd: options?.cwd ?? cwd,
|
|
3712
|
+
env: options?.env ? { ...process.env, ...options.env } : void 0,
|
|
3713
|
+
timeout: options?.timeout,
|
|
3714
|
+
maxBuffer: 10 * 1024 * 1024
|
|
3715
|
+
},
|
|
3716
|
+
(error, stdout, stderr) => {
|
|
3717
|
+
if (error && error.killed && options?.signal?.aborted) {
|
|
3718
|
+
reject(new Error("Command was aborted"));
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
resolve7({
|
|
3722
|
+
exitCode: typeof error?.code === "number" ? error.code : error ? 1 : 0,
|
|
3302
3723
|
stdout: stdout ?? "",
|
|
3303
3724
|
stderr: stderr ?? ""
|
|
3304
3725
|
});
|
|
@@ -3416,6 +3837,7 @@ var ExtensionRunner = class {
|
|
|
3416
3837
|
*/
|
|
3417
3838
|
invalidate(message) {
|
|
3418
3839
|
this.runtime.invalidate(message);
|
|
3840
|
+
for (const extension of this.extensions) extension.runtime.invalidate(message);
|
|
3419
3841
|
}
|
|
3420
3842
|
// ---------------------------------------------------------------------------
|
|
3421
3843
|
// Direct hook API
|
|
@@ -3445,13 +3867,21 @@ var ExtensionRunner = class {
|
|
|
3445
3867
|
*/
|
|
3446
3868
|
async loadExtensions(extensions) {
|
|
3447
3869
|
for (const ext of extensions) {
|
|
3870
|
+
const extension = {
|
|
3871
|
+
path: ext.path,
|
|
3872
|
+
tools: /* @__PURE__ */ new Map(),
|
|
3873
|
+
phases: /* @__PURE__ */ new Set(),
|
|
3874
|
+
cleanup: [],
|
|
3875
|
+
runtime: createExtensionRuntime()
|
|
3876
|
+
};
|
|
3448
3877
|
try {
|
|
3449
|
-
const extension = { path: ext.path, tools: /* @__PURE__ */ new Map() };
|
|
3450
3878
|
const api = this.createExtensionAPI(extension, ext.manifest);
|
|
3451
|
-
await ext.factory(api);
|
|
3879
|
+
const disposer = await ext.factory(api);
|
|
3880
|
+
if (typeof disposer === "function") extension.disposer = disposer;
|
|
3452
3881
|
this.extensions.push(extension);
|
|
3453
3882
|
this._phaseCache = null;
|
|
3454
3883
|
} catch (error) {
|
|
3884
|
+
await this.rollbackExtension(extension);
|
|
3455
3885
|
const message = error instanceof Error ? error.message : String(error);
|
|
3456
3886
|
this.emitError({
|
|
3457
3887
|
extensionPath: ext.path,
|
|
@@ -3463,6 +3893,17 @@ var ExtensionRunner = class {
|
|
|
3463
3893
|
}
|
|
3464
3894
|
}
|
|
3465
3895
|
}
|
|
3896
|
+
/** Dispose all active Extensions in reverse activation order. */
|
|
3897
|
+
async close() {
|
|
3898
|
+
this.abortController.abort();
|
|
3899
|
+
for (const extension of [...this.extensions].reverse()) {
|
|
3900
|
+
await this.disposeExtension(extension, "This Extension Runtime has been closed.");
|
|
3901
|
+
}
|
|
3902
|
+
this.extensions.length = 0;
|
|
3903
|
+
this.phases.clear();
|
|
3904
|
+
this._phaseCache = null;
|
|
3905
|
+
this.runtime.invalidate("This Extension Runtime has been closed.");
|
|
3906
|
+
}
|
|
3466
3907
|
// ---------------------------------------------------------------------------
|
|
3467
3908
|
// Tool management
|
|
3468
3909
|
// ---------------------------------------------------------------------------
|
|
@@ -3660,8 +4101,9 @@ var ExtensionRunner = class {
|
|
|
3660
4101
|
unregisterProvider: (name) => this.unregisterProvider(name),
|
|
3661
4102
|
registerTool: (tool) => this.registerTool(extension, tool),
|
|
3662
4103
|
context: extContext,
|
|
3663
|
-
manifest
|
|
3664
|
-
|
|
4104
|
+
manifest,
|
|
4105
|
+
trackCleanup: (cleanup) => extension.cleanup.push(cleanup)
|
|
4106
|
+
}, extension.runtime, this.events);
|
|
3665
4107
|
}
|
|
3666
4108
|
registerTool(extension, tool) {
|
|
3667
4109
|
if (extension.tools.has(tool.name)) {
|
|
@@ -3718,6 +4160,7 @@ var ExtensionRunner = class {
|
|
|
3718
4160
|
source: { extensionPath: extension.path }
|
|
3719
4161
|
};
|
|
3720
4162
|
this.phases.set(name, registered);
|
|
4163
|
+
extension.phases.add(name);
|
|
3721
4164
|
this._phaseCache = null;
|
|
3722
4165
|
}
|
|
3723
4166
|
registerProvider(config) {
|
|
@@ -3752,260 +4195,835 @@ var ExtensionRunner = class {
|
|
|
3752
4195
|
this._phaseCache = new Map(this.phases);
|
|
3753
4196
|
return this._phaseCache;
|
|
3754
4197
|
}
|
|
4198
|
+
async rollbackExtension(extension) {
|
|
4199
|
+
await this.disposeExtension(extension, "This Extension activation was rolled back.");
|
|
4200
|
+
}
|
|
4201
|
+
async disposeExtension(extension, message) {
|
|
4202
|
+
extension.runtime.invalidate(message);
|
|
4203
|
+
for (const name of extension.phases) this.phases.delete(name);
|
|
4204
|
+
for (const cleanup of [...extension.cleanup].reverse()) {
|
|
4205
|
+
await Promise.resolve().then(() => cleanup()).catch(() => void 0);
|
|
4206
|
+
}
|
|
4207
|
+
if (extension.disposer) {
|
|
4208
|
+
await Promise.resolve().then(() => extension.disposer()).catch(() => void 0);
|
|
4209
|
+
}
|
|
4210
|
+
this._phaseCache = null;
|
|
4211
|
+
}
|
|
3755
4212
|
};
|
|
3756
4213
|
function createExtensionRunner(options) {
|
|
3757
4214
|
return new ExtensionRunner(options);
|
|
3758
4215
|
}
|
|
3759
4216
|
|
|
3760
|
-
// src/
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
4217
|
+
// src/extensions/loader.ts
|
|
4218
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
4219
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
4220
|
+
import { fileURLToPath } from "url";
|
|
4221
|
+
import { basename as basename3, dirname as dirname4, extname as extname2, join as join2, resolve as resolve4 } from "path";
|
|
4222
|
+
import { createJiti } from "jiti";
|
|
4223
|
+
function isExtensionFile(path) {
|
|
4224
|
+
const extension = extname2(path).toLowerCase();
|
|
4225
|
+
return extension === ".ts" || extension === ".js";
|
|
4226
|
+
}
|
|
4227
|
+
function isSyntheticPath(path) {
|
|
4228
|
+
return path.startsWith("<") && path.endsWith(">");
|
|
4229
|
+
}
|
|
4230
|
+
function readManifestSync(dir) {
|
|
4231
|
+
const manifestPath = join2(dir, "package.json");
|
|
4232
|
+
if (!existsSync2(manifestPath)) {
|
|
4233
|
+
return void 0;
|
|
3769
4234
|
}
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
4235
|
+
try {
|
|
4236
|
+
const content = readFileSync(manifestPath, "utf8");
|
|
4237
|
+
const pkg = JSON.parse(content);
|
|
4238
|
+
const rowan = pkg.rowan;
|
|
4239
|
+
if (!rowan) return void 0;
|
|
4240
|
+
return {
|
|
4241
|
+
entry: rowan.extensions?.[0],
|
|
4242
|
+
phase: rowan.phase
|
|
4243
|
+
};
|
|
4244
|
+
} catch {
|
|
4245
|
+
return void 0;
|
|
3780
4246
|
}
|
|
3781
|
-
const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
|
|
3782
|
-
const phases = mergePhases(config.resources.phases, extensionPhases);
|
|
3783
|
-
const context = resolveDefinitionContext(config, { tools, phases });
|
|
3784
|
-
return {
|
|
3785
|
-
context,
|
|
3786
|
-
beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
|
|
3787
|
-
afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
|
|
3788
|
-
beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
|
|
3789
|
-
beforeToolCall: async (input) => {
|
|
3790
|
-
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3791
|
-
const decision = await runner.emitBeforeToolCall(tool, input.args);
|
|
3792
|
-
return decision.allow ? { allow: true } : { allow: false, reason: decision.reason ?? "Extension hook rejected the Tool." };
|
|
3793
|
-
},
|
|
3794
|
-
afterToolCall: async (input) => {
|
|
3795
|
-
const tool = projectTool(input.tool, input.context.agentId, input.context.runId);
|
|
3796
|
-
const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
|
|
3797
|
-
return fromLoopResult(result);
|
|
3798
|
-
},
|
|
3799
|
-
setContext: (context2) => {
|
|
3800
|
-
runner.currentContext = context2;
|
|
3801
|
-
}
|
|
3802
|
-
};
|
|
3803
4247
|
}
|
|
3804
|
-
function
|
|
3805
|
-
const
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
"Tool"
|
|
3809
|
-
);
|
|
3810
|
-
const skills = selectNamedResources(
|
|
3811
|
-
config.resources.skills,
|
|
3812
|
-
config.definition.skills,
|
|
3813
|
-
"Skill"
|
|
3814
|
-
);
|
|
3815
|
-
const candidateRegistry = assembled.phases ?? config.resources.phases;
|
|
3816
|
-
const selectedPhases = selectNamedResources(
|
|
3817
|
-
[...candidateRegistry?.phases.values() ?? []],
|
|
3818
|
-
config.definition.phases,
|
|
3819
|
-
"Phase"
|
|
3820
|
-
);
|
|
3821
|
-
const phases = new Map(selectedPhases.map((phase) => [phase.name, phase]));
|
|
3822
|
-
const requestedEntry = config.definition.entryPhase ?? candidateRegistry?.entryPhaseId ?? null;
|
|
3823
|
-
const entryPhaseId = requestedEntry === DEFAULT_PHASE_ID ? DEFAULT_PHASE_ID : requestedEntry && phases.has(requestedEntry) ? requestedEntry : null;
|
|
3824
|
-
if (requestedEntry && requestedEntry !== DEFAULT_PHASE_ID && !phases.has(requestedEntry)) {
|
|
3825
|
-
console.warn(`Phase entry "${requestedEntry}" is not available; Rowan will use "default".`);
|
|
4248
|
+
function jitiAliases() {
|
|
4249
|
+
const moduleUrl = import.meta.url;
|
|
4250
|
+
if (!moduleUrl) {
|
|
4251
|
+
return {};
|
|
3826
4252
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
};
|
|
4253
|
+
const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
|
|
4254
|
+
if (!existsSync2(agentSourcePath)) {
|
|
4255
|
+
return {};
|
|
4256
|
+
}
|
|
4257
|
+
return { "@rowan-agent/agent": agentSourcePath };
|
|
3833
4258
|
}
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
4259
|
+
var sharedJiti;
|
|
4260
|
+
function getJiti() {
|
|
4261
|
+
sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
|
|
4262
|
+
fsCache: false,
|
|
4263
|
+
moduleCache: false,
|
|
4264
|
+
tryNative: false,
|
|
4265
|
+
alias: jitiAliases()
|
|
4266
|
+
});
|
|
4267
|
+
return sharedJiti;
|
|
4268
|
+
}
|
|
4269
|
+
async function loadExtensionModule(extensionPath) {
|
|
4270
|
+
const jiti = getJiti();
|
|
4271
|
+
const module = await jiti.import(extensionPath, { default: true });
|
|
4272
|
+
return typeof module === "function" ? module : void 0;
|
|
4273
|
+
}
|
|
4274
|
+
async function readPackageManifest(path) {
|
|
4275
|
+
if (!existsSync2(path)) {
|
|
4276
|
+
return void 0;
|
|
4277
|
+
}
|
|
4278
|
+
const content = await readFile3(path, "utf8");
|
|
4279
|
+
return JSON.parse(content);
|
|
4280
|
+
}
|
|
4281
|
+
async function resolveExtensionEntries(dir) {
|
|
4282
|
+
const manifest = await readPackageManifest(join2(dir, "package.json"));
|
|
4283
|
+
const declared = manifest?.rowan?.extensions;
|
|
4284
|
+
if (declared && declared.length > 0) {
|
|
4285
|
+
return declared.map((entry) => resolve4(dir, entry));
|
|
4286
|
+
}
|
|
4287
|
+
for (const name of ["index.ts", "index.js"]) {
|
|
4288
|
+
const entry = join2(dir, name);
|
|
4289
|
+
if (existsSync2(entry)) {
|
|
4290
|
+
return [entry];
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
return void 0;
|
|
4294
|
+
}
|
|
4295
|
+
async function discoverExtensionsInDir(dir) {
|
|
4296
|
+
const entries = await readdir2(dir, { withFileTypes: true }).catch((error) => {
|
|
4297
|
+
if (error.code === "ENOENT") {
|
|
4298
|
+
return [];
|
|
4299
|
+
}
|
|
4300
|
+
throw error;
|
|
4301
|
+
});
|
|
4302
|
+
const paths = [];
|
|
4303
|
+
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4304
|
+
const entryPath = join2(dir, entry.name);
|
|
4305
|
+
if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
|
|
4306
|
+
paths.push(entryPath);
|
|
4307
|
+
continue;
|
|
4308
|
+
}
|
|
4309
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
4310
|
+
const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
|
|
4311
|
+
if (entry.isDirectory() || info?.isDirectory()) {
|
|
4312
|
+
const resolvedEntries = await resolveExtensionEntries(entryPath);
|
|
4313
|
+
if (resolvedEntries) {
|
|
4314
|
+
paths.push(...resolvedEntries);
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
3840
4318
|
}
|
|
4319
|
+
return paths;
|
|
4320
|
+
}
|
|
4321
|
+
function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
|
|
4322
|
+
const resolvedCwd = resolve4(cwd);
|
|
4323
|
+
const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve4(resolvedCwd, extensionPath);
|
|
4324
|
+
const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname4(resolvedPath)) || "unknown";
|
|
4325
|
+
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname4(resolvedPath);
|
|
4326
|
+
const manifest = readManifestSync(manifestDir);
|
|
3841
4327
|
return {
|
|
3842
|
-
|
|
3843
|
-
|
|
4328
|
+
path: resolvedPath,
|
|
4329
|
+
name,
|
|
4330
|
+
factory,
|
|
4331
|
+
manifest
|
|
3844
4332
|
};
|
|
3845
4333
|
}
|
|
3846
|
-
function
|
|
3847
|
-
const
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
const
|
|
3854
|
-
|
|
3855
|
-
|
|
4334
|
+
async function loadExtensions(paths, cwd) {
|
|
4335
|
+
const extensions = [];
|
|
4336
|
+
const errors = [];
|
|
4337
|
+
const resolvedCwd = resolve4(cwd);
|
|
4338
|
+
for (const path of paths) {
|
|
4339
|
+
const resolvedPath = resolve4(resolvedCwd, path);
|
|
4340
|
+
try {
|
|
4341
|
+
const factory = await loadExtensionModule(resolvedPath);
|
|
4342
|
+
if (!factory) {
|
|
4343
|
+
errors.push({
|
|
4344
|
+
path: resolvedPath,
|
|
4345
|
+
error: `Extension does not export a valid factory function: ${path}`
|
|
4346
|
+
});
|
|
4347
|
+
continue;
|
|
4348
|
+
}
|
|
4349
|
+
extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
|
|
4350
|
+
} catch (error) {
|
|
4351
|
+
errors.push({
|
|
4352
|
+
path: resolvedPath,
|
|
4353
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4354
|
+
});
|
|
3856
4355
|
}
|
|
3857
|
-
}
|
|
4356
|
+
}
|
|
4357
|
+
return { extensions, errors };
|
|
3858
4358
|
}
|
|
3859
|
-
function
|
|
4359
|
+
async function loadExtensionsFromPath(path) {
|
|
4360
|
+
const resolvedPath = resolve4(path);
|
|
4361
|
+
const info = await stat2(resolvedPath);
|
|
4362
|
+
if (!info.isDirectory()) {
|
|
4363
|
+
return loadExtensions([resolvedPath], dirname4(resolvedPath));
|
|
4364
|
+
}
|
|
4365
|
+
const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
|
|
4366
|
+
return loadExtensions(entries, resolvedPath);
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
// src/runtime/resource-registry.ts
|
|
4370
|
+
import { stat as stat3, readdir as readdir4 } from "fs/promises";
|
|
4371
|
+
import { join as join4, resolve as resolve6 } from "path";
|
|
4372
|
+
|
|
4373
|
+
// src/harness/skills.ts
|
|
4374
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
4375
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
4376
|
+
import { dirname as dirname5, join as join3, resolve as resolve5 } from "path";
|
|
4377
|
+
var SKILL_MARKER = "SKILL.md";
|
|
4378
|
+
function resolveSkillPath(input) {
|
|
4379
|
+
const resolved = resolve5(input);
|
|
4380
|
+
return existsSync3(resolved) && statSync2(resolved).isDirectory() ? join3(resolved, SKILL_MARKER) : resolved;
|
|
4381
|
+
}
|
|
4382
|
+
async function loadSkill(path) {
|
|
4383
|
+
const resolved = resolveSkillPath(path);
|
|
4384
|
+
let loaded;
|
|
4385
|
+
try {
|
|
4386
|
+
loaded = await loadMarkdown(resolved);
|
|
4387
|
+
} catch (error) {
|
|
4388
|
+
if (!(error instanceof FrontmatterParseError)) throw error;
|
|
4389
|
+
warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
|
|
4390
|
+
throw new ResourceMetadataError("parse_failed", error.message);
|
|
4391
|
+
}
|
|
4392
|
+
const { frontmatter, body } = loaded;
|
|
4393
|
+
const metadata = frontmatter;
|
|
4394
|
+
const id = inferResourceName(resolved, SKILL_MARKER);
|
|
4395
|
+
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
4396
|
+
const name = frontmatterName || id;
|
|
4397
|
+
const description = validateDescription(metadata.description);
|
|
4398
|
+
const diagnostics = validateResourceId(id);
|
|
4399
|
+
if (frontmatterName && frontmatterName !== id) {
|
|
4400
|
+
diagnostics.push(...validateResourceName(frontmatterName, id));
|
|
4401
|
+
}
|
|
4402
|
+
diagnostics.push(...description.warnings);
|
|
4403
|
+
warnResourceDiagnostics("skill", resolved, diagnostics);
|
|
4404
|
+
if (description.missing) {
|
|
4405
|
+
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
4406
|
+
}
|
|
3860
4407
|
return {
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
4408
|
+
name,
|
|
4409
|
+
description: description.description,
|
|
4410
|
+
filePath: resolved,
|
|
4411
|
+
baseDir: dirname5(resolved),
|
|
4412
|
+
content: body,
|
|
4413
|
+
disableModelInvocation: metadata["disable-model-invocation"] === true
|
|
3866
4414
|
};
|
|
3867
4415
|
}
|
|
3868
|
-
function
|
|
3869
|
-
|
|
4416
|
+
async function loadSkills(targetPath) {
|
|
4417
|
+
const skillsDir = resolve5(targetPath);
|
|
4418
|
+
if (existsSync3(skillsDir) && statSync2(skillsDir).isFile()) {
|
|
4419
|
+
try {
|
|
4420
|
+
return [await loadSkill(skillsDir)];
|
|
4421
|
+
} catch (error) {
|
|
4422
|
+
if (error instanceof ResourceMetadataError) return [];
|
|
4423
|
+
throw error;
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
if (existsSync3(join3(skillsDir, SKILL_MARKER))) {
|
|
4427
|
+
try {
|
|
4428
|
+
return [await loadSkill(skillsDir)];
|
|
4429
|
+
} catch (error) {
|
|
4430
|
+
if (error instanceof ResourceMetadataError) return [];
|
|
4431
|
+
throw error;
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
const entries = await readdir3(skillsDir, { withFileTypes: true });
|
|
4435
|
+
const skills = [];
|
|
4436
|
+
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4437
|
+
if (!entry.isDirectory()) continue;
|
|
4438
|
+
const skillFile = join3(skillsDir, entry.name, SKILL_MARKER);
|
|
4439
|
+
if (!existsSync3(skillFile)) continue;
|
|
4440
|
+
try {
|
|
4441
|
+
const skill = await loadSkill(skillFile);
|
|
4442
|
+
skills.push(skill);
|
|
4443
|
+
} catch (error) {
|
|
4444
|
+
if (error instanceof ResourceMetadataError) continue;
|
|
4445
|
+
console.warn(`Failed to load skill "${entry.name}":`, error);
|
|
4446
|
+
}
|
|
4447
|
+
}
|
|
4448
|
+
return skills;
|
|
3870
4449
|
}
|
|
3871
4450
|
|
|
3872
|
-
// src/runtime/
|
|
3873
|
-
var
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
}
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
var
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4451
|
+
// src/runtime/resource-registry.ts
|
|
4452
|
+
var ResourceRegistryError = class extends Error {
|
|
4453
|
+
code;
|
|
4454
|
+
constructor(code, message) {
|
|
4455
|
+
super(message);
|
|
4456
|
+
this.name = "ResourceRegistryError";
|
|
4457
|
+
this.code = code;
|
|
4458
|
+
}
|
|
4459
|
+
};
|
|
4460
|
+
var RESOURCE_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
4461
|
+
var SOURCE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
4462
|
+
var ResourceRegistry = class {
|
|
4463
|
+
sources = /* @__PURE__ */ new Map();
|
|
4464
|
+
/** Sources registered by the bootstrap boundary (currently Extensions). */
|
|
4465
|
+
implicitSources = /* @__PURE__ */ new Map();
|
|
4466
|
+
revisionSequence = 0;
|
|
4467
|
+
async loadAgents(input) {
|
|
4468
|
+
assertSourceId(input.sourceId);
|
|
4469
|
+
const collected = await this.collect("agent", input);
|
|
4470
|
+
return this.commit("agent", input.sourceId, collected.values, collected.skipped);
|
|
4471
|
+
}
|
|
4472
|
+
async loadSkills(input) {
|
|
4473
|
+
assertSourceId(input.sourceId);
|
|
4474
|
+
const collected = await this.collect("skill", input);
|
|
4475
|
+
return this.commit("skill", input.sourceId, collected.values, collected.skipped);
|
|
4476
|
+
}
|
|
4477
|
+
async loadPhases(input) {
|
|
4478
|
+
assertSourceId(input.sourceId);
|
|
4479
|
+
const collected = await this.collect("phase", input);
|
|
4480
|
+
return this.commit("phase", input.sourceId, collected.values, collected.skipped);
|
|
4481
|
+
}
|
|
4482
|
+
async loadTools(input) {
|
|
4483
|
+
assertSourceId(input.sourceId);
|
|
4484
|
+
return this.commit("tool", input.sourceId, input.values, []);
|
|
4485
|
+
}
|
|
4486
|
+
async unload(input) {
|
|
4487
|
+
assertSourceId(input.sourceId);
|
|
4488
|
+
if (this.hasImplicit(input.kind, input.sourceId)) {
|
|
4489
|
+
throw new ResourceRegistryError(
|
|
4490
|
+
"implicit_source_protected",
|
|
4491
|
+
`Implicit ${input.kind} source "${input.sourceId}" is managed by Runtime bootstrap.`
|
|
4492
|
+
);
|
|
4493
|
+
}
|
|
4494
|
+
const bySource = this.sources.get(input.kind);
|
|
4495
|
+
const existing = bySource?.get(input.sourceId);
|
|
4496
|
+
if (!existing) {
|
|
4497
|
+
return {
|
|
4498
|
+
revision: this.nextRevision(input.kind, input.sourceId),
|
|
4499
|
+
registered: [],
|
|
4500
|
+
skipped: []
|
|
3894
4501
|
};
|
|
3895
|
-
} else {
|
|
3896
|
-
if (this.queue.length >= MAX_BUFFERED_EVENTS) this.queue.shift();
|
|
3897
|
-
this.queue.push(event);
|
|
3898
4502
|
}
|
|
3899
|
-
|
|
3900
|
-
|
|
4503
|
+
bySource.delete(input.sourceId);
|
|
4504
|
+
return {
|
|
4505
|
+
revision: this.nextRevision(input.kind, input.sourceId),
|
|
4506
|
+
registered: [],
|
|
4507
|
+
skipped: []
|
|
4508
|
+
};
|
|
3901
4509
|
}
|
|
3902
|
-
|
|
3903
|
-
|
|
4510
|
+
resolveView(view) {
|
|
4511
|
+
const sourceIds = {
|
|
4512
|
+
agents: this.withImplicit("agent", view.agents),
|
|
4513
|
+
tools: this.withImplicit("tool", view.tools),
|
|
4514
|
+
skills: this.withImplicit("skill", view.skills),
|
|
4515
|
+
phases: this.withImplicit("phase", view.phases)
|
|
4516
|
+
};
|
|
4517
|
+
return {
|
|
4518
|
+
agents: this.resolveKind("agent", sourceIds.agents),
|
|
4519
|
+
tools: this.resolveKind("tool", sourceIds.tools),
|
|
4520
|
+
skills: this.resolveKind("skill", sourceIds.skills),
|
|
4521
|
+
phases: this.resolveKind("phase", sourceIds.phases),
|
|
4522
|
+
revisions: {
|
|
4523
|
+
agent: this.resolveRevisions("agent", sourceIds.agents),
|
|
4524
|
+
tool: this.resolveRevisions("tool", sourceIds.tools),
|
|
4525
|
+
skill: this.resolveRevisions("skill", sourceIds.skills),
|
|
4526
|
+
phase: this.resolveRevisions("phase", sourceIds.phases)
|
|
4527
|
+
},
|
|
4528
|
+
refs: {
|
|
4529
|
+
agent: this.resolveRefs("agent", sourceIds.agents),
|
|
4530
|
+
tool: this.resolveRefs("tool", sourceIds.tools),
|
|
4531
|
+
skill: this.resolveRefs("skill", sourceIds.skills),
|
|
4532
|
+
phase: this.resolveRefs("phase", sourceIds.phases)
|
|
4533
|
+
}
|
|
4534
|
+
};
|
|
3904
4535
|
}
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
4536
|
+
/** Mark one already-loaded source as implicit for every Resource View. */
|
|
4537
|
+
markImplicit(kind, sourceId) {
|
|
4538
|
+
assertSourceId(sourceId);
|
|
4539
|
+
const entries = this.implicitSources.get(kind) ?? [];
|
|
4540
|
+
if (!entries.includes(sourceId)) entries.push(sourceId);
|
|
4541
|
+
this.implicitSources.set(kind, entries);
|
|
4542
|
+
}
|
|
4543
|
+
/** Bootstrap-only helper for replacing an implicit contribution source. */
|
|
4544
|
+
async replaceImplicit(kind, sourceId, values) {
|
|
4545
|
+
const result = await this.commit(kind, sourceId, values, []);
|
|
4546
|
+
this.markImplicit(kind, sourceId);
|
|
4547
|
+
return result;
|
|
4548
|
+
}
|
|
4549
|
+
hasImplicit(kind, sourceId) {
|
|
4550
|
+
return this.implicitSources.get(kind)?.includes(sourceId) ?? false;
|
|
4551
|
+
}
|
|
4552
|
+
withImplicit(kind, sourceIds) {
|
|
4553
|
+
const combined = [...sourceIds];
|
|
4554
|
+
for (const sourceId of this.implicitSources.get(kind) ?? []) {
|
|
4555
|
+
if (!combined.includes(sourceId)) combined.push(sourceId);
|
|
4556
|
+
}
|
|
4557
|
+
return combined;
|
|
4558
|
+
}
|
|
4559
|
+
async collect(kind, input) {
|
|
4560
|
+
const hasDirectory = input.directory !== void 0;
|
|
4561
|
+
const hasValues = input.values !== void 0;
|
|
4562
|
+
if (!hasDirectory && !hasValues) {
|
|
4563
|
+
throw new ResourceRegistryError(
|
|
4564
|
+
"resource_input_missing",
|
|
4565
|
+
`Resource source "${input.sourceId}" requires directory or values.`
|
|
4566
|
+
);
|
|
4567
|
+
}
|
|
4568
|
+
const directoryValues = hasDirectory ? await loadDirectoryValues(kind, input.directory, input.sourceId) : { values: [], skipped: [] };
|
|
4569
|
+
return {
|
|
4570
|
+
values: [...directoryValues.values, ...input.values ?? []],
|
|
4571
|
+
skipped: directoryValues.skipped
|
|
4572
|
+
};
|
|
4573
|
+
}
|
|
4574
|
+
async commit(kind, sourceId, values, skipped) {
|
|
4575
|
+
assertSourceId(sourceId);
|
|
4576
|
+
const names = /* @__PURE__ */ new Set();
|
|
4577
|
+
for (const value of values) {
|
|
4578
|
+
if (kind === "agent") assertAgentDefinition(value);
|
|
4579
|
+
assertNamedResource(value, kind, sourceId);
|
|
4580
|
+
if (isImplicitCoreName(kind, value.name) && sourceId !== "rowan.core") {
|
|
4581
|
+
throw new ResourceRegistryError(
|
|
4582
|
+
"resource_collision",
|
|
4583
|
+
`${capitalize(kind)} resource "${value.name}" is reserved by Rowan core.`
|
|
4584
|
+
);
|
|
4585
|
+
}
|
|
4586
|
+
if (names.has(value.name)) {
|
|
4587
|
+
throw new ResourceRegistryError(
|
|
4588
|
+
"resource_collision",
|
|
4589
|
+
`Duplicate ${capitalize(kind)} resource "${value.name}" in source "${sourceId}".`
|
|
4590
|
+
);
|
|
3910
4591
|
}
|
|
4592
|
+
names.add(value.name);
|
|
4593
|
+
}
|
|
4594
|
+
const revision = this.nextRevision(kind, sourceId);
|
|
4595
|
+
const record = {
|
|
4596
|
+
kind,
|
|
4597
|
+
sourceId,
|
|
4598
|
+
revision,
|
|
4599
|
+
values: Object.freeze([...values])
|
|
4600
|
+
};
|
|
4601
|
+
let bySource = this.sources.get(kind);
|
|
4602
|
+
if (!bySource) {
|
|
4603
|
+
bySource = /* @__PURE__ */ new Map();
|
|
4604
|
+
this.sources.set(kind, bySource);
|
|
3911
4605
|
}
|
|
3912
|
-
|
|
4606
|
+
bySource.set(sourceId, record);
|
|
4607
|
+
return {
|
|
4608
|
+
revision,
|
|
4609
|
+
registered: values.map((value) => ({ kind, sourceId, name: value.name })),
|
|
4610
|
+
skipped
|
|
4611
|
+
};
|
|
3913
4612
|
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
4613
|
+
resolveKind(kind, sourceIds) {
|
|
4614
|
+
const names = /* @__PURE__ */ new Set();
|
|
4615
|
+
const values = [];
|
|
4616
|
+
for (const source of this.sourcesFor(kind, sourceIds)) {
|
|
4617
|
+
for (const value of source.values) {
|
|
4618
|
+
if (names.has(value.name)) {
|
|
4619
|
+
throw new ResourceRegistryError(
|
|
4620
|
+
"resource_collision",
|
|
4621
|
+
`Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`
|
|
4622
|
+
);
|
|
4623
|
+
}
|
|
4624
|
+
names.add(value.name);
|
|
4625
|
+
values.push(value);
|
|
4626
|
+
}
|
|
3918
4627
|
}
|
|
3919
|
-
|
|
4628
|
+
return values;
|
|
3920
4629
|
}
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
4630
|
+
resolveRevisions(kind, sourceIds) {
|
|
4631
|
+
return this.sourcesFor(kind, sourceIds).map(({ revision }) => revision);
|
|
4632
|
+
}
|
|
4633
|
+
resolveRefs(kind, sourceIds) {
|
|
4634
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
4635
|
+
const refs = [];
|
|
4636
|
+
for (const source of this.sourcesFor(kind, sourceIds)) {
|
|
4637
|
+
for (const value of source.values) {
|
|
4638
|
+
if (seenNames.has(value.name)) throw new ResourceRegistryError("resource_collision", `Duplicate ${capitalize(kind)} resource "${value.name}" in Resource View.`);
|
|
4639
|
+
seenNames.add(value.name);
|
|
4640
|
+
refs.push({ kind, sourceId: source.sourceId, name: value.name });
|
|
4641
|
+
}
|
|
3925
4642
|
}
|
|
3926
|
-
|
|
4643
|
+
return refs;
|
|
4644
|
+
}
|
|
4645
|
+
sourcesFor(kind, sourceIds) {
|
|
4646
|
+
const bySource = this.sources.get(kind);
|
|
4647
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4648
|
+
const sources = [];
|
|
4649
|
+
for (const sourceId of sourceIds) {
|
|
4650
|
+
assertSourceId(sourceId);
|
|
4651
|
+
if (seen.has(sourceId)) continue;
|
|
4652
|
+
seen.add(sourceId);
|
|
4653
|
+
const source = bySource?.get(sourceId);
|
|
4654
|
+
if (!source) {
|
|
4655
|
+
throw new ResourceRegistryError(
|
|
4656
|
+
"unknown_resource_source",
|
|
4657
|
+
`Unknown resource source "${sourceId}" for ${kind}.`
|
|
4658
|
+
);
|
|
4659
|
+
}
|
|
4660
|
+
sources.push(source);
|
|
4661
|
+
}
|
|
4662
|
+
return sources;
|
|
3927
4663
|
}
|
|
3928
|
-
|
|
3929
|
-
|
|
4664
|
+
nextRevision(kind, sourceId) {
|
|
4665
|
+
this.revisionSequence += 1;
|
|
4666
|
+
return `${kind}:${sourceId}:${this.revisionSequence}`;
|
|
3930
4667
|
}
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
4668
|
+
};
|
|
4669
|
+
function assertSourceId(sourceId) {
|
|
4670
|
+
if (typeof sourceId !== "string" || !SOURCE_ID.test(sourceId)) {
|
|
4671
|
+
throw new ResourceRegistryError("invalid_source_id", `Invalid resource source "${String(sourceId)}".`);
|
|
3934
4672
|
}
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
this.notify();
|
|
4673
|
+
}
|
|
4674
|
+
function assertNamedResource(value, kind, sourceId) {
|
|
4675
|
+
if (!value || typeof value !== "object" || typeof value.name !== "string") {
|
|
4676
|
+
throw new ResourceRegistryError("invalid_resource_name", `Invalid ${kind} resource in source "${sourceId}".`);
|
|
3940
4677
|
}
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
this.version += 1;
|
|
3945
|
-
wake.resolve();
|
|
4678
|
+
const name = value.name;
|
|
4679
|
+
if (!RESOURCE_NAME.test(name)) {
|
|
4680
|
+
throw new ResourceRegistryError("invalid_resource_name", `Invalid ${capitalize(kind)} resource name "${name}".`);
|
|
3946
4681
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
4682
|
+
}
|
|
4683
|
+
function capitalize(value) {
|
|
4684
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
4685
|
+
}
|
|
4686
|
+
function isImplicitCoreName(kind, name) {
|
|
4687
|
+
return kind === "tool" && name === "route" || kind === "phase" && name === "default";
|
|
4688
|
+
}
|
|
4689
|
+
async function loadDirectoryValues(kind, directory, sourceId) {
|
|
4690
|
+
const root = resolve6(directory);
|
|
4691
|
+
let rootInfo;
|
|
4692
|
+
try {
|
|
4693
|
+
rootInfo = await stat3(root);
|
|
4694
|
+
} catch (error) {
|
|
4695
|
+
if (isMissingPath(error)) {
|
|
4696
|
+
return {
|
|
4697
|
+
values: [],
|
|
4698
|
+
skipped: [{ kind: "missing", sourceId, path: root, message: `Resource directory "${root}" does not exist.` }]
|
|
4699
|
+
};
|
|
4700
|
+
}
|
|
4701
|
+
return {
|
|
4702
|
+
values: [],
|
|
4703
|
+
skipped: [{
|
|
4704
|
+
kind: "invalid",
|
|
4705
|
+
sourceId,
|
|
4706
|
+
path: root,
|
|
4707
|
+
message: `Resource directory "${root}" could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
4708
|
+
}]
|
|
4709
|
+
};
|
|
4710
|
+
}
|
|
4711
|
+
const marker = markerFor(kind);
|
|
4712
|
+
const targets = [];
|
|
4713
|
+
if (rootInfo.isFile()) {
|
|
4714
|
+
targets.push(root);
|
|
4715
|
+
} else if (rootInfo.isDirectory()) {
|
|
4716
|
+
const markerAtRoot = join4(root, marker);
|
|
4717
|
+
try {
|
|
4718
|
+
const markerInfo = await stat3(markerAtRoot);
|
|
4719
|
+
if (markerInfo.isFile()) targets.push(markerAtRoot);
|
|
4720
|
+
} catch (error) {
|
|
4721
|
+
if (!isMissingPath(error)) throw error;
|
|
4722
|
+
let entries;
|
|
4723
|
+
try {
|
|
4724
|
+
entries = await readdir4(root, { withFileTypes: true });
|
|
4725
|
+
} catch (error2) {
|
|
4726
|
+
return {
|
|
4727
|
+
values: [],
|
|
4728
|
+
skipped: [{
|
|
4729
|
+
kind: "invalid",
|
|
4730
|
+
sourceId,
|
|
4731
|
+
path: root,
|
|
4732
|
+
message: `Resource directory "${root}" could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
4733
|
+
}]
|
|
3965
4734
|
};
|
|
3966
|
-
|
|
4735
|
+
}
|
|
4736
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
4737
|
+
if (!entry.isDirectory()) continue;
|
|
4738
|
+
const target = join4(root, entry.name, marker);
|
|
4739
|
+
try {
|
|
4740
|
+
const targetInfo = await stat3(target);
|
|
4741
|
+
if (targetInfo.isFile()) targets.push(target);
|
|
4742
|
+
} catch (targetError) {
|
|
4743
|
+
if (!isMissingPath(targetError)) throw targetError;
|
|
4744
|
+
}
|
|
3967
4745
|
}
|
|
3968
4746
|
}
|
|
3969
4747
|
}
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
4748
|
+
const values = [];
|
|
4749
|
+
const skipped = [];
|
|
4750
|
+
for (const target of targets) {
|
|
4751
|
+
try {
|
|
4752
|
+
const value = await loadDirectoryValue(kind, target);
|
|
4753
|
+
values.push(value);
|
|
4754
|
+
} catch (error) {
|
|
4755
|
+
skipped.push({
|
|
4756
|
+
kind: "invalid",
|
|
4757
|
+
sourceId,
|
|
4758
|
+
path: target,
|
|
4759
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4760
|
+
});
|
|
4761
|
+
}
|
|
3979
4762
|
}
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4763
|
+
return { values, skipped };
|
|
4764
|
+
}
|
|
4765
|
+
async function loadDirectoryValue(kind, target) {
|
|
4766
|
+
switch (kind) {
|
|
4767
|
+
case "agent": {
|
|
4768
|
+
let loaded;
|
|
4769
|
+
try {
|
|
4770
|
+
loaded = await loadMarkdown(target);
|
|
4771
|
+
} catch (error) {
|
|
4772
|
+
if (error instanceof FrontmatterParseError) {
|
|
4773
|
+
throw new TypeError(`frontmatter could not be parsed: ${error.message}`);
|
|
4774
|
+
}
|
|
4775
|
+
throw error;
|
|
4776
|
+
}
|
|
4777
|
+
return normalizeAgentDefinition(
|
|
4778
|
+
loaded.frontmatter,
|
|
4779
|
+
loaded.body,
|
|
4780
|
+
{ fallbackName: inferResourceName(target, "AGENT.md") }
|
|
4781
|
+
);
|
|
4782
|
+
}
|
|
4783
|
+
case "skill":
|
|
4784
|
+
return await loadSkill(target);
|
|
4785
|
+
case "phase":
|
|
4786
|
+
return await loadPhase(target);
|
|
4787
|
+
case "tool":
|
|
4788
|
+
throw new ResourceRegistryError(
|
|
4789
|
+
"resource_directory_not_supported",
|
|
4790
|
+
"Tool sources accept inline values only."
|
|
4791
|
+
);
|
|
3985
4792
|
}
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
4793
|
+
}
|
|
4794
|
+
function markerFor(kind) {
|
|
4795
|
+
switch (kind) {
|
|
4796
|
+
case "agent":
|
|
4797
|
+
return "AGENT.md";
|
|
4798
|
+
case "skill":
|
|
4799
|
+
return "SKILL.md";
|
|
4800
|
+
case "phase":
|
|
4801
|
+
return "PHASE.md";
|
|
4802
|
+
case "tool":
|
|
4803
|
+
return "";
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
function isMissingPath(error) {
|
|
4807
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4810
|
+
// src/runtime/extension-lifetime.ts
|
|
4811
|
+
var ExtensionLifetimeError = class extends Error {
|
|
4812
|
+
code;
|
|
4813
|
+
constructor(code, message) {
|
|
4814
|
+
super(message);
|
|
4815
|
+
this.name = "ExtensionLifetimeError";
|
|
4816
|
+
this.code = code;
|
|
4817
|
+
}
|
|
4818
|
+
};
|
|
4819
|
+
var RuntimeExtensionLifetime = class {
|
|
4820
|
+
runner;
|
|
4821
|
+
frozen = false;
|
|
4822
|
+
closed = false;
|
|
4823
|
+
constructor(options = {}) {
|
|
4824
|
+
this.runner = createExtensionRunner(options);
|
|
4825
|
+
}
|
|
4826
|
+
async activate(extensions) {
|
|
4827
|
+
if (this.closed) throw new ExtensionLifetimeError("extensions_closed", "The Extension Runtime has been closed.");
|
|
4828
|
+
if (this.frozen) throw new ExtensionLifetimeError("extensions_frozen", "Extensions are frozen until Runtime restart.");
|
|
4829
|
+
const active = [];
|
|
4830
|
+
const errors = [];
|
|
4831
|
+
for (const extension of extensions) {
|
|
4832
|
+
try {
|
|
4833
|
+
await this.runner.loadExtensions([extension]);
|
|
4834
|
+
active.push(extension.name);
|
|
4835
|
+
} catch (error) {
|
|
4836
|
+
errors.push({
|
|
4837
|
+
path: extension.path,
|
|
4838
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
3989
4841
|
}
|
|
4842
|
+
this.runner.bind();
|
|
4843
|
+
this.frozen = true;
|
|
4844
|
+
return { active, errors };
|
|
3990
4845
|
}
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
4846
|
+
tools() {
|
|
4847
|
+
return this.runner.getAllRegisteredTools();
|
|
4848
|
+
}
|
|
4849
|
+
phases() {
|
|
4850
|
+
return this.runner.getPhases();
|
|
4851
|
+
}
|
|
4852
|
+
get extensionRunner() {
|
|
4853
|
+
return this.runner;
|
|
4854
|
+
}
|
|
4855
|
+
async close() {
|
|
4856
|
+
if (this.closed) return;
|
|
4857
|
+
this.closed = true;
|
|
4858
|
+
await this.runner.close();
|
|
4859
|
+
}
|
|
4860
|
+
};
|
|
4861
|
+
var RuntimeBootstrapRegistry = class extends ResourceRegistry {
|
|
4862
|
+
constructor(extensions = new RuntimeExtensionLifetime()) {
|
|
4863
|
+
super();
|
|
4864
|
+
this.extensions = extensions;
|
|
4865
|
+
}
|
|
4866
|
+
extensions;
|
|
4867
|
+
extensionSourceId = "rowan.extensions";
|
|
4868
|
+
/** Install Rowan-owned implicit resources before any host source is read. */
|
|
4869
|
+
async ensureCoreResources() {
|
|
4870
|
+
await this.replaceImplicit("tool", "rowan.core", [createRouteTool([])]);
|
|
4871
|
+
await this.replaceImplicit("phase", "rowan.core", [createDefaultPhase()]);
|
|
4872
|
+
}
|
|
4873
|
+
async loadExtensions(input) {
|
|
4874
|
+
const loaded = Array.isArray(input) ? { extensions: [...input], errors: [] } : (() => {
|
|
4875
|
+
const descriptor = input;
|
|
4876
|
+
return descriptor.directory ? loadExtensionsFromPath(descriptor.directory) : Promise.resolve({ extensions: [...descriptor.values ?? []], errors: [] });
|
|
4877
|
+
})();
|
|
4878
|
+
const normalized = await loaded;
|
|
4879
|
+
const result = await this.extensions.activate(normalized.extensions);
|
|
4880
|
+
await this.replaceImplicit("tool", this.extensionSourceId, this.extensions.tools().map(adaptExtensionTool2));
|
|
4881
|
+
await this.replaceImplicit("phase", this.extensionSourceId, [...this.extensions.phases()]);
|
|
4882
|
+
return {
|
|
4883
|
+
...result,
|
|
4884
|
+
revision: `extensions:${Date.now()}`,
|
|
4885
|
+
registered: result.active,
|
|
4886
|
+
skipped: normalized.errors.map((error) => ({
|
|
4887
|
+
kind: "invalid",
|
|
4888
|
+
sourceId: "rowan.extensions",
|
|
4889
|
+
path: error.path,
|
|
4890
|
+
message: error.error
|
|
4891
|
+
}))
|
|
4892
|
+
};
|
|
4893
|
+
}
|
|
4894
|
+
get extensionRunner() {
|
|
4895
|
+
return this.extensions.extensionRunner;
|
|
4896
|
+
}
|
|
4897
|
+
get extensionTools() {
|
|
4898
|
+
return this.extensions.tools();
|
|
4899
|
+
}
|
|
4900
|
+
get extensionPhases() {
|
|
4901
|
+
return this.extensions.phases();
|
|
4902
|
+
}
|
|
4903
|
+
async closeExtensions() {
|
|
4904
|
+
await this.extensions.close();
|
|
4905
|
+
}
|
|
4906
|
+
};
|
|
4907
|
+
function adaptExtensionTool2(input) {
|
|
4908
|
+
const definition = input.definition;
|
|
4909
|
+
return {
|
|
4910
|
+
name: definition.name,
|
|
4911
|
+
description: definition.description,
|
|
4912
|
+
parameters: definition.parameters,
|
|
4913
|
+
execute: async (args, context, signal) => {
|
|
4914
|
+
const result = await definition.execute(args, signal);
|
|
4915
|
+
const content = JSON.parse(JSON.stringify(result.content));
|
|
4916
|
+
return result.isError ? { ok: false, content, error: "Extension Tool failed." } : { ok: true, content };
|
|
3994
4917
|
}
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
// src/runtime/configuration-snapshot.ts
|
|
4922
|
+
function resolveConfigurationSnapshot(registry, input) {
|
|
4923
|
+
const resolved = registry.resolveView(input.resourceView);
|
|
4924
|
+
const base = resolved.agents.find(({ name }) => name === input.definition.name);
|
|
4925
|
+
if (!base) {
|
|
4926
|
+
throw new Error(`Agent Definition "${input.definition.name}" is not available in the Resource View.`);
|
|
4927
|
+
}
|
|
4928
|
+
const layer = input.definition.layer;
|
|
4929
|
+
const definition = applyDefinitionLayer(base, layer);
|
|
4930
|
+
const tools = selectNamedResources(resolved.tools, definition.tools, "Tool");
|
|
4931
|
+
const skills = selectNamedResources(resolved.skills, definition.skills, "Skill");
|
|
4932
|
+
const phases = resolvePhases(resolved.phases, definition.phases);
|
|
4933
|
+
const contexts = selectNamedResources(input.contexts ?? [], base.contexts, "Context");
|
|
4934
|
+
return {
|
|
4935
|
+
identity: input.identity,
|
|
4936
|
+
definition,
|
|
4937
|
+
resources: {
|
|
4938
|
+
tools,
|
|
4939
|
+
skills,
|
|
4940
|
+
...phases ? { phases } : {},
|
|
4941
|
+
revisions: resolved.revisions,
|
|
4942
|
+
refs: {
|
|
4943
|
+
agent: selectedRefs(resolved.refs.agent, [base.name]),
|
|
4944
|
+
tool: selectedRefs(resolved.refs.tool, tools.map(({ name }) => name)),
|
|
4945
|
+
skill: selectedRefs(resolved.refs.skill, skills.map(({ name }) => name)),
|
|
4946
|
+
phase: selectedRefs(resolved.refs.phase, [...phases?.phases.keys() ?? []])
|
|
4947
|
+
}
|
|
4948
|
+
},
|
|
4949
|
+
contexts,
|
|
4950
|
+
resourceView: input.resourceView,
|
|
4951
|
+
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
4952
|
+
...input.maxAttempts === void 0 ? {} : { maxAttempts: input.maxAttempts },
|
|
4953
|
+
..."stream" in input && input.stream ? { model: layer?.model ?? input.model, stream: input.stream } : { model: layer?.model ?? input.model }
|
|
4954
|
+
};
|
|
4955
|
+
}
|
|
4956
|
+
function materializeConfigurationSnapshot(snapshot) {
|
|
4957
|
+
return {
|
|
4958
|
+
identity: snapshot.identity,
|
|
4959
|
+
definition: snapshot.definition,
|
|
4960
|
+
resources: {
|
|
4961
|
+
tools: snapshot.resources.tools,
|
|
4962
|
+
skills: snapshot.resources.skills,
|
|
4963
|
+
...snapshot.resources.phases ? { phases: snapshot.resources.phases } : {},
|
|
4964
|
+
...snapshot.contexts.length > 0 ? { contexts: snapshot.contexts } : {},
|
|
4965
|
+
resourceView: snapshot.resourceView,
|
|
4966
|
+
resourceRefs: [
|
|
4967
|
+
...snapshot.resources.refs.agent,
|
|
4968
|
+
...snapshot.resources.refs.tool,
|
|
4969
|
+
...snapshot.resources.refs.skill,
|
|
4970
|
+
...snapshot.resources.refs.phase
|
|
4971
|
+
],
|
|
4972
|
+
resourceRevisions: snapshot.resources.revisions
|
|
4973
|
+
},
|
|
4974
|
+
...snapshot.cwd === void 0 ? {} : { cwd: snapshot.cwd },
|
|
4975
|
+
...snapshot.maxAttempts === void 0 ? {} : { maxAttempts: snapshot.maxAttempts },
|
|
4976
|
+
..."stream" in snapshot && snapshot.stream ? { model: snapshot.model, stream: snapshot.stream } : { model: snapshot.model }
|
|
4977
|
+
};
|
|
4978
|
+
}
|
|
4979
|
+
function applyDefinitionLayer(base, layer) {
|
|
4980
|
+
if (!layer) return { ...base };
|
|
4981
|
+
return {
|
|
4982
|
+
...base,
|
|
4983
|
+
...layer.description === void 0 ? {} : { description: layer.description },
|
|
4984
|
+
...layer.prompt === void 0 ? {} : { prompt: layer.prompt },
|
|
4985
|
+
...layer.model === void 0 ? {} : { model: layer.model },
|
|
4986
|
+
...layer.tools === void 0 ? {} : { tools: intersectNames(base.tools, layer.tools, "Tool") },
|
|
4987
|
+
...layer.skills === void 0 ? {} : { skills: intersectNames(base.skills, layer.skills, "Skill") },
|
|
4988
|
+
...layer.phases === void 0 ? {} : { phases: intersectPhaseSelection(base.phases, layer.phases) }
|
|
4989
|
+
};
|
|
4990
|
+
}
|
|
4991
|
+
function intersectNames(parent, layer, kind) {
|
|
4992
|
+
if (parent === void 0) return [...layer];
|
|
4993
|
+
const parentNames = new Set(parent);
|
|
4994
|
+
for (const name of layer) {
|
|
4995
|
+
if (!parentNames.has(name)) console.warn(`${kind} "${name}" is not available and will be skipped.`);
|
|
3995
4996
|
}
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4997
|
+
return layer.filter((name) => parentNames.has(name));
|
|
4998
|
+
}
|
|
4999
|
+
function intersectPhaseSelection(parent, layer) {
|
|
5000
|
+
if (!parent) return layer;
|
|
5001
|
+
const parentNames = new Set(parent.phaseIds);
|
|
5002
|
+
for (const name of layer.phaseIds) {
|
|
5003
|
+
if (!parentNames.has(name)) console.warn(`Phase "${name}" is not available and will be skipped.`);
|
|
4001
5004
|
}
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
5005
|
+
return {
|
|
5006
|
+
entryPhaseId: layer.entryPhaseId,
|
|
5007
|
+
phaseIds: layer.phaseIds.filter((name) => parentNames.has(name))
|
|
5008
|
+
};
|
|
5009
|
+
}
|
|
5010
|
+
function resolvePhases(candidates, selection) {
|
|
5011
|
+
const selected = selectNamedResources(candidates, selection?.phaseIds, "Phase");
|
|
5012
|
+
if (selected.length === 0 && selection === void 0) return void 0;
|
|
5013
|
+
const entryPhaseId = selection?.entryPhaseId ?? null;
|
|
5014
|
+
if (entryPhaseId !== null && !selected.some(({ name }) => name === entryPhaseId)) {
|
|
5015
|
+
console.warn(`Phase entry "${entryPhaseId}" is not available and will be skipped.`);
|
|
5016
|
+
return { phases: new Map(selected.map((phase) => [phase.name, phase])), entryPhaseId: null };
|
|
4007
5017
|
}
|
|
4008
|
-
|
|
5018
|
+
return {
|
|
5019
|
+
phases: new Map(selected.map((phase) => [phase.name, phase])),
|
|
5020
|
+
entryPhaseId
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
function selectedRefs(refs, names) {
|
|
5024
|
+
const selected = new Set(names);
|
|
5025
|
+
return refs.filter(({ name }) => selected.has(name));
|
|
5026
|
+
}
|
|
4009
5027
|
|
|
4010
5028
|
// src/runtime/durable-runtime.ts
|
|
4011
5029
|
var DEFAULT_CONCURRENCY = 10;
|
|
@@ -4022,14 +5040,16 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4022
5040
|
executions = /* @__PURE__ */ new Map();
|
|
4023
5041
|
consumers = /* @__PURE__ */ new Map();
|
|
4024
5042
|
transientEvents = new TransientRunEventHub();
|
|
5043
|
+
resources;
|
|
4025
5044
|
heartbeat;
|
|
4026
5045
|
pumping = false;
|
|
4027
5046
|
closed = false;
|
|
4028
|
-
constructor(options, owned) {
|
|
5047
|
+
constructor(options, owned, resources) {
|
|
4029
5048
|
this.owned = owned;
|
|
4030
5049
|
this.commands = new ConfigCommandService(owned, options.configs, String(owned.lease.token).split(":")[0]);
|
|
4031
5050
|
this.storeIncarnation = String(owned.lease.token).split(":")[0];
|
|
4032
5051
|
this.concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
5052
|
+
this.resources = resources;
|
|
4033
5053
|
}
|
|
4034
5054
|
static async init(options) {
|
|
4035
5055
|
if (!options.store) throw new TypeError("AgentRuntime requires a DurableStore");
|
|
@@ -4037,10 +5057,38 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4037
5057
|
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
4038
5058
|
if (!Number.isInteger(concurrency) || concurrency <= 0) throw new TypeError("concurrency must be a positive integer");
|
|
4039
5059
|
const owned = await options.store.openOwner({ ownerId: createId("owner"), leaseMs: OWNER_LEASE_MS });
|
|
4040
|
-
const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned);
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
5060
|
+
const runtime = new _AgentRuntime({ ...options, configs, concurrency }, owned, new RuntimeBootstrapRegistry());
|
|
5061
|
+
try {
|
|
5062
|
+
await runtime.resources.ensureCoreResources();
|
|
5063
|
+
await options.bootstrap?.(runtime.resources);
|
|
5064
|
+
runtime.startHeartbeat();
|
|
5065
|
+
void runtime.pump();
|
|
5066
|
+
return runtime;
|
|
5067
|
+
} catch (error) {
|
|
5068
|
+
await runtime.resources.closeExtensions().catch(() => void 0);
|
|
5069
|
+
await owned.sealAndReleaseOwner().catch(() => void 0);
|
|
5070
|
+
throw error;
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
async loadAgents(input) {
|
|
5074
|
+
this.assertOpen();
|
|
5075
|
+
return this.resources.loadAgents(input);
|
|
5076
|
+
}
|
|
5077
|
+
async loadSkills(input) {
|
|
5078
|
+
this.assertOpen();
|
|
5079
|
+
return this.resources.loadSkills(input);
|
|
5080
|
+
}
|
|
5081
|
+
async loadPhases(input) {
|
|
5082
|
+
this.assertOpen();
|
|
5083
|
+
return this.resources.loadPhases(input);
|
|
5084
|
+
}
|
|
5085
|
+
async loadTools(input) {
|
|
5086
|
+
this.assertOpen();
|
|
5087
|
+
return this.resources.loadTools(input);
|
|
5088
|
+
}
|
|
5089
|
+
async unload(input) {
|
|
5090
|
+
this.assertOpen();
|
|
5091
|
+
return this.resources.unload(input);
|
|
4044
5092
|
}
|
|
4045
5093
|
async createAgent(config, options = {}) {
|
|
4046
5094
|
this.assertOpen();
|
|
@@ -4100,6 +5148,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4100
5148
|
this.executions.clear();
|
|
4101
5149
|
this.transientEvents.close();
|
|
4102
5150
|
for (const subscription of this.consumers.values()) subscription.controller.abort();
|
|
5151
|
+
await this.resources.closeExtensions();
|
|
4103
5152
|
await this.owned.sealAndReleaseOwner();
|
|
4104
5153
|
}
|
|
4105
5154
|
async snapshot(runId) {
|
|
@@ -4157,7 +5206,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4157
5206
|
let executionRevision = run.revision;
|
|
4158
5207
|
try {
|
|
4159
5208
|
const agent = await this.requireAgent(run.agentId);
|
|
4160
|
-
|
|
5209
|
+
let token = run.pinnedConfigToken ?? agent.currentConfigToken;
|
|
4161
5210
|
if (!token) {
|
|
4162
5211
|
await this.failQueued(run, "Agent has no Config Token.");
|
|
4163
5212
|
return;
|
|
@@ -4177,13 +5226,28 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4177
5226
|
await this.failQueued(run, resolution.reason);
|
|
4178
5227
|
return;
|
|
4179
5228
|
}
|
|
5229
|
+
let resolvedConfig = resolution.config;
|
|
5230
|
+
if (isAgentConfiguration(resolvedConfig)) {
|
|
5231
|
+
try {
|
|
5232
|
+
const snapshot = this.materializeConfig(resolvedConfig);
|
|
5233
|
+
token = await this.commands.storeSnapshot({
|
|
5234
|
+
agent,
|
|
5235
|
+
config: snapshot,
|
|
5236
|
+
operationId: `run-snapshot:${run.id}`
|
|
5237
|
+
});
|
|
5238
|
+
resolvedConfig = snapshot;
|
|
5239
|
+
} catch (error) {
|
|
5240
|
+
await this.failQueued(run, error instanceof Error ? error.message : "Configuration Snapshot failed.");
|
|
5241
|
+
return;
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
4180
5244
|
const executionId = createId("exec");
|
|
4181
5245
|
claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
|
|
4182
5246
|
executionRevision = claim.run.revision;
|
|
4183
5247
|
const controller = new AbortController();
|
|
4184
5248
|
this.executions.set(run.id, { controller, executionId });
|
|
4185
|
-
const config =
|
|
4186
|
-
const assembly =
|
|
5249
|
+
const config = resolvedConfig;
|
|
5250
|
+
const assembly = assembleRegisteredExtensions(config, this.resources.extensionRunner);
|
|
4187
5251
|
let toolQueue = Promise.resolve();
|
|
4188
5252
|
const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
|
|
4189
5253
|
const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
|
|
@@ -4207,7 +5271,9 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4207
5271
|
execution: {
|
|
4208
5272
|
agentId: run.agentId,
|
|
4209
5273
|
runId: run.id,
|
|
4210
|
-
executionId: claim.execution.executionId
|
|
5274
|
+
executionId: claim.execution.executionId,
|
|
5275
|
+
...agent.metadata === void 0 ? {} : { agentMetadata: agent.metadata },
|
|
5276
|
+
...run.metadata === void 0 ? {} : { runMetadata: run.metadata }
|
|
4211
5277
|
},
|
|
4212
5278
|
model,
|
|
4213
5279
|
stream,
|
|
@@ -4236,6 +5302,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4236
5302
|
const task = toolQueue.then(async () => {
|
|
4237
5303
|
const execution = await this.executeToolBatch({
|
|
4238
5304
|
run,
|
|
5305
|
+
agentMetadata: agent.metadata,
|
|
4239
5306
|
execution: claim.execution,
|
|
4240
5307
|
expectedRevision: executionRevision,
|
|
4241
5308
|
toolConfig: executionTools,
|
|
@@ -4252,6 +5319,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4252
5319
|
const task = toolQueue.then(async () => {
|
|
4253
5320
|
const execution = await this.executeToolBatch({
|
|
4254
5321
|
run,
|
|
5322
|
+
agentMetadata: agent.metadata,
|
|
4255
5323
|
execution: claim.execution,
|
|
4256
5324
|
expectedRevision: executionRevision,
|
|
4257
5325
|
toolConfig: executionTools,
|
|
@@ -4346,6 +5414,8 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4346
5414
|
const context = {
|
|
4347
5415
|
agentId: input.run.agentId,
|
|
4348
5416
|
runId: input.run.id,
|
|
5417
|
+
...input.agentMetadata === void 0 ? {} : { agentMetadata: input.agentMetadata },
|
|
5418
|
+
...input.run.metadata === void 0 ? {} : { runMetadata: input.run.metadata },
|
|
4349
5419
|
toolCallId,
|
|
4350
5420
|
reportProgress: (progress) => {
|
|
4351
5421
|
const active = this.executions.get(input.run.id);
|
|
@@ -4489,6 +5559,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4489
5559
|
assertOpen() {
|
|
4490
5560
|
if (this.closed) throw new RuntimeError("runtime_closed", null);
|
|
4491
5561
|
}
|
|
5562
|
+
materializeConfig(config) {
|
|
5563
|
+
if (!isAgentConfiguration(config)) return config;
|
|
5564
|
+
return materializeConfigurationSnapshot(resolveConfigurationSnapshot(this.resources, config));
|
|
5565
|
+
}
|
|
4492
5566
|
startHeartbeat() {
|
|
4493
5567
|
this.heartbeat = setInterval(() => {
|
|
4494
5568
|
void this.owned.renewOwner(OWNER_LEASE_MS).catch(() => {
|
|
@@ -4552,10 +5626,10 @@ function boundaryFromSnapshot(snapshot) {
|
|
|
4552
5626
|
}
|
|
4553
5627
|
}
|
|
4554
5628
|
function delay(ms) {
|
|
4555
|
-
return new Promise((
|
|
5629
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
4556
5630
|
}
|
|
4557
5631
|
function normalizePhaseRegistry(registry) {
|
|
4558
|
-
if (registry?.phases.has(DEFAULT_PHASE_ID)) {
|
|
5632
|
+
if (registry?.phases.has(DEFAULT_PHASE_ID) && registry.phases.get(DEFAULT_PHASE_ID)?.filePath !== "") {
|
|
4559
5633
|
throw new TypeError(`Configured Phase collides with Rowan built-in Phase "${DEFAULT_PHASE_ID}".`);
|
|
4560
5634
|
}
|
|
4561
5635
|
return {
|
|
@@ -4579,13 +5653,13 @@ function toJsonValue(value) {
|
|
|
4579
5653
|
return value;
|
|
4580
5654
|
}
|
|
4581
5655
|
function deferred2() {
|
|
4582
|
-
let
|
|
5656
|
+
let resolve7;
|
|
4583
5657
|
let reject;
|
|
4584
5658
|
const promise = new Promise((res, rej) => {
|
|
4585
|
-
|
|
5659
|
+
resolve7 = (value) => res(value);
|
|
4586
5660
|
reject = rej;
|
|
4587
5661
|
});
|
|
4588
|
-
return { promise, resolve:
|
|
5662
|
+
return { promise, resolve: resolve7, reject };
|
|
4589
5663
|
}
|
|
4590
5664
|
|
|
4591
5665
|
// src/runtime/durable-store.ts
|
|
@@ -6036,236 +7110,6 @@ function assertOwnerInput(ownerId, leaseMs) {
|
|
|
6036
7110
|
if (!Number.isFinite(leaseMs) || leaseMs <= 0) throw new TypeError("leaseMs must be a positive finite number");
|
|
6037
7111
|
}
|
|
6038
7112
|
|
|
6039
|
-
// src/harness/skills.ts
|
|
6040
|
-
import { existsSync as existsSync2, statSync as statSync2 } from "fs";
|
|
6041
|
-
import { readdir as readdir2 } from "fs/promises";
|
|
6042
|
-
import { dirname as dirname4, join as join2, resolve as resolve4 } from "path";
|
|
6043
|
-
var SKILL_MARKER = "SKILL.md";
|
|
6044
|
-
function resolveSkillPath(input) {
|
|
6045
|
-
const resolved = resolve4(input);
|
|
6046
|
-
return existsSync2(resolved) && statSync2(resolved).isDirectory() ? join2(resolved, SKILL_MARKER) : resolved;
|
|
6047
|
-
}
|
|
6048
|
-
async function loadSkill(path) {
|
|
6049
|
-
const resolved = resolveSkillPath(path);
|
|
6050
|
-
let loaded;
|
|
6051
|
-
try {
|
|
6052
|
-
loaded = await loadMarkdown(resolved);
|
|
6053
|
-
} catch (error) {
|
|
6054
|
-
if (!(error instanceof FrontmatterParseError)) throw error;
|
|
6055
|
-
warnResourceDiagnostics("skill", resolved, [`frontmatter could not be parsed: ${error.message}`]);
|
|
6056
|
-
throw new ResourceMetadataError("parse_failed", error.message);
|
|
6057
|
-
}
|
|
6058
|
-
const { frontmatter, body } = loaded;
|
|
6059
|
-
const metadata = frontmatter;
|
|
6060
|
-
const id = inferResourceName(resolved, SKILL_MARKER);
|
|
6061
|
-
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
6062
|
-
const name = frontmatterName || id;
|
|
6063
|
-
const description = validateDescription(metadata.description);
|
|
6064
|
-
const diagnostics = validateResourceId(id);
|
|
6065
|
-
if (frontmatterName && frontmatterName !== id) {
|
|
6066
|
-
diagnostics.push(...validateResourceName(frontmatterName, id));
|
|
6067
|
-
}
|
|
6068
|
-
diagnostics.push(...description.warnings);
|
|
6069
|
-
warnResourceDiagnostics("skill", resolved, diagnostics);
|
|
6070
|
-
if (description.missing) {
|
|
6071
|
-
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
6072
|
-
}
|
|
6073
|
-
return {
|
|
6074
|
-
name,
|
|
6075
|
-
description: description.description,
|
|
6076
|
-
filePath: resolved,
|
|
6077
|
-
baseDir: dirname4(resolved),
|
|
6078
|
-
content: body,
|
|
6079
|
-
disableModelInvocation: metadata["disable-model-invocation"] === true
|
|
6080
|
-
};
|
|
6081
|
-
}
|
|
6082
|
-
async function loadSkills(targetPath) {
|
|
6083
|
-
const skillsDir = resolve4(targetPath);
|
|
6084
|
-
if (existsSync2(skillsDir) && statSync2(skillsDir).isFile()) {
|
|
6085
|
-
try {
|
|
6086
|
-
return [await loadSkill(skillsDir)];
|
|
6087
|
-
} catch (error) {
|
|
6088
|
-
if (error instanceof ResourceMetadataError) return [];
|
|
6089
|
-
throw error;
|
|
6090
|
-
}
|
|
6091
|
-
}
|
|
6092
|
-
if (existsSync2(join2(skillsDir, SKILL_MARKER))) {
|
|
6093
|
-
try {
|
|
6094
|
-
return [await loadSkill(skillsDir)];
|
|
6095
|
-
} catch (error) {
|
|
6096
|
-
if (error instanceof ResourceMetadataError) return [];
|
|
6097
|
-
throw error;
|
|
6098
|
-
}
|
|
6099
|
-
}
|
|
6100
|
-
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
6101
|
-
const skills = [];
|
|
6102
|
-
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6103
|
-
if (!entry.isDirectory()) continue;
|
|
6104
|
-
const skillFile = join2(skillsDir, entry.name, SKILL_MARKER);
|
|
6105
|
-
if (!existsSync2(skillFile)) continue;
|
|
6106
|
-
try {
|
|
6107
|
-
const skill = await loadSkill(skillFile);
|
|
6108
|
-
skills.push(skill);
|
|
6109
|
-
} catch (error) {
|
|
6110
|
-
if (error instanceof ResourceMetadataError) continue;
|
|
6111
|
-
console.warn(`Failed to load skill "${entry.name}":`, error);
|
|
6112
|
-
}
|
|
6113
|
-
}
|
|
6114
|
-
return skills;
|
|
6115
|
-
}
|
|
6116
|
-
|
|
6117
|
-
// src/extensions/loader.ts
|
|
6118
|
-
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
6119
|
-
import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
6120
|
-
import { fileURLToPath } from "url";
|
|
6121
|
-
import { basename as basename3, dirname as dirname5, extname as extname2, join as join3, resolve as resolve5 } from "path";
|
|
6122
|
-
import { createJiti } from "jiti";
|
|
6123
|
-
function isExtensionFile(path) {
|
|
6124
|
-
const extension = extname2(path).toLowerCase();
|
|
6125
|
-
return extension === ".ts" || extension === ".js";
|
|
6126
|
-
}
|
|
6127
|
-
function isSyntheticPath(path) {
|
|
6128
|
-
return path.startsWith("<") && path.endsWith(">");
|
|
6129
|
-
}
|
|
6130
|
-
function readManifestSync(dir) {
|
|
6131
|
-
const manifestPath = join3(dir, "package.json");
|
|
6132
|
-
if (!existsSync3(manifestPath)) {
|
|
6133
|
-
return void 0;
|
|
6134
|
-
}
|
|
6135
|
-
try {
|
|
6136
|
-
const content = readFileSync(manifestPath, "utf8");
|
|
6137
|
-
const pkg = JSON.parse(content);
|
|
6138
|
-
const rowan = pkg.rowan;
|
|
6139
|
-
if (!rowan) return void 0;
|
|
6140
|
-
return {
|
|
6141
|
-
entry: rowan.extensions?.[0],
|
|
6142
|
-
phase: rowan.phase
|
|
6143
|
-
};
|
|
6144
|
-
} catch {
|
|
6145
|
-
return void 0;
|
|
6146
|
-
}
|
|
6147
|
-
}
|
|
6148
|
-
function jitiAliases() {
|
|
6149
|
-
const moduleUrl = import.meta.url;
|
|
6150
|
-
if (!moduleUrl) {
|
|
6151
|
-
return {};
|
|
6152
|
-
}
|
|
6153
|
-
const agentSourcePath = fileURLToPath(new URL("../index.ts", moduleUrl));
|
|
6154
|
-
if (!existsSync3(agentSourcePath)) {
|
|
6155
|
-
return {};
|
|
6156
|
-
}
|
|
6157
|
-
return { "@rowan-agent/agent": agentSourcePath };
|
|
6158
|
-
}
|
|
6159
|
-
var sharedJiti;
|
|
6160
|
-
function getJiti() {
|
|
6161
|
-
sharedJiti ??= createJiti(import.meta.url || process.cwd(), {
|
|
6162
|
-
fsCache: false,
|
|
6163
|
-
moduleCache: false,
|
|
6164
|
-
tryNative: false,
|
|
6165
|
-
alias: jitiAliases()
|
|
6166
|
-
});
|
|
6167
|
-
return sharedJiti;
|
|
6168
|
-
}
|
|
6169
|
-
async function loadExtensionModule(extensionPath) {
|
|
6170
|
-
const jiti = getJiti();
|
|
6171
|
-
const module = await jiti.import(extensionPath, { default: true });
|
|
6172
|
-
return typeof module === "function" ? module : void 0;
|
|
6173
|
-
}
|
|
6174
|
-
async function readPackageManifest(path) {
|
|
6175
|
-
if (!existsSync3(path)) {
|
|
6176
|
-
return void 0;
|
|
6177
|
-
}
|
|
6178
|
-
const content = await readFile3(path, "utf8");
|
|
6179
|
-
return JSON.parse(content);
|
|
6180
|
-
}
|
|
6181
|
-
async function resolveExtensionEntries(dir) {
|
|
6182
|
-
const manifest = await readPackageManifest(join3(dir, "package.json"));
|
|
6183
|
-
const declared = manifest?.rowan?.extensions;
|
|
6184
|
-
if (declared && declared.length > 0) {
|
|
6185
|
-
return declared.map((entry) => resolve5(dir, entry));
|
|
6186
|
-
}
|
|
6187
|
-
for (const name of ["index.ts", "index.js"]) {
|
|
6188
|
-
const entry = join3(dir, name);
|
|
6189
|
-
if (existsSync3(entry)) {
|
|
6190
|
-
return [entry];
|
|
6191
|
-
}
|
|
6192
|
-
}
|
|
6193
|
-
return void 0;
|
|
6194
|
-
}
|
|
6195
|
-
async function discoverExtensionsInDir(dir) {
|
|
6196
|
-
const entries = await readdir3(dir, { withFileTypes: true }).catch((error) => {
|
|
6197
|
-
if (error.code === "ENOENT") {
|
|
6198
|
-
return [];
|
|
6199
|
-
}
|
|
6200
|
-
throw error;
|
|
6201
|
-
});
|
|
6202
|
-
const paths = [];
|
|
6203
|
-
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
6204
|
-
const entryPath = join3(dir, entry.name);
|
|
6205
|
-
if ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {
|
|
6206
|
-
paths.push(entryPath);
|
|
6207
|
-
continue;
|
|
6208
|
-
}
|
|
6209
|
-
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
6210
|
-
const info = entry.isSymbolicLink() ? await stat2(entryPath).catch(() => void 0) : void 0;
|
|
6211
|
-
if (entry.isDirectory() || info?.isDirectory()) {
|
|
6212
|
-
const resolvedEntries = await resolveExtensionEntries(entryPath);
|
|
6213
|
-
if (resolvedEntries) {
|
|
6214
|
-
paths.push(...resolvedEntries);
|
|
6215
|
-
}
|
|
6216
|
-
}
|
|
6217
|
-
}
|
|
6218
|
-
}
|
|
6219
|
-
return paths;
|
|
6220
|
-
}
|
|
6221
|
-
function loadExtensionFromFactory(factory, cwd, extensionPath = "<inline>") {
|
|
6222
|
-
const resolvedCwd = resolve5(cwd);
|
|
6223
|
-
const resolvedPath = isSyntheticPath(extensionPath) ? extensionPath : resolve5(resolvedCwd, extensionPath);
|
|
6224
|
-
const name = isSyntheticPath(extensionPath) ? extensionPath : basename3(dirname5(resolvedPath)) || "unknown";
|
|
6225
|
-
const manifestDir = isSyntheticPath(extensionPath) ? resolvedCwd : dirname5(resolvedPath);
|
|
6226
|
-
const manifest = readManifestSync(manifestDir);
|
|
6227
|
-
return {
|
|
6228
|
-
path: resolvedPath,
|
|
6229
|
-
name,
|
|
6230
|
-
factory,
|
|
6231
|
-
manifest
|
|
6232
|
-
};
|
|
6233
|
-
}
|
|
6234
|
-
async function loadExtensions(paths, cwd) {
|
|
6235
|
-
const extensions = [];
|
|
6236
|
-
const errors = [];
|
|
6237
|
-
const resolvedCwd = resolve5(cwd);
|
|
6238
|
-
for (const path of paths) {
|
|
6239
|
-
const resolvedPath = resolve5(resolvedCwd, path);
|
|
6240
|
-
try {
|
|
6241
|
-
const factory = await loadExtensionModule(resolvedPath);
|
|
6242
|
-
if (!factory) {
|
|
6243
|
-
errors.push({
|
|
6244
|
-
path: resolvedPath,
|
|
6245
|
-
error: `Extension does not export a valid factory function: ${path}`
|
|
6246
|
-
});
|
|
6247
|
-
continue;
|
|
6248
|
-
}
|
|
6249
|
-
extensions.push(loadExtensionFromFactory(factory, resolvedCwd, resolvedPath));
|
|
6250
|
-
} catch (error) {
|
|
6251
|
-
errors.push({
|
|
6252
|
-
path: resolvedPath,
|
|
6253
|
-
error: error instanceof Error ? error.message : String(error)
|
|
6254
|
-
});
|
|
6255
|
-
}
|
|
6256
|
-
}
|
|
6257
|
-
return { extensions, errors };
|
|
6258
|
-
}
|
|
6259
|
-
async function loadExtensionsFromPath(path) {
|
|
6260
|
-
const resolvedPath = resolve5(path);
|
|
6261
|
-
const info = await stat2(resolvedPath);
|
|
6262
|
-
if (!info.isDirectory()) {
|
|
6263
|
-
return loadExtensions([resolvedPath], dirname5(resolvedPath));
|
|
6264
|
-
}
|
|
6265
|
-
const entries = await resolveExtensionEntries(resolvedPath) ?? await discoverExtensionsInDir(resolvedPath);
|
|
6266
|
-
return loadExtensions(entries, resolvedPath);
|
|
6267
|
-
}
|
|
6268
|
-
|
|
6269
7113
|
// src/index.ts
|
|
6270
7114
|
function createCoreTools2(input = {}) {
|
|
6271
7115
|
return createCoreTools(input).map((tool) => ({
|
|
@@ -6280,9 +7124,14 @@ function createCoreTools2(input = {}) {
|
|
|
6280
7124
|
}
|
|
6281
7125
|
export {
|
|
6282
7126
|
AgentRuntime,
|
|
7127
|
+
ExtensionLifetimeError,
|
|
6283
7128
|
InMemoryConfigProvider,
|
|
6284
7129
|
InMemoryStore,
|
|
7130
|
+
ResourceRegistry,
|
|
7131
|
+
ResourceRegistryError,
|
|
7132
|
+
RuntimeBootstrapRegistry,
|
|
6285
7133
|
RuntimeError,
|
|
7134
|
+
RuntimeExtensionLifetime,
|
|
6286
7135
|
SqliteStore,
|
|
6287
7136
|
brandConfigToken,
|
|
6288
7137
|
createCoreTools2 as createCoreTools,
|
|
@@ -6290,6 +7139,8 @@ export {
|
|
|
6290
7139
|
loadExtensionsFromPath as loadExtensions,
|
|
6291
7140
|
loadPhases,
|
|
6292
7141
|
loadSkills,
|
|
7142
|
+
materializeConfigurationSnapshot,
|
|
6293
7143
|
parseAgentDefinition,
|
|
6294
|
-
parseFrontmatter
|
|
7144
|
+
parseFrontmatter,
|
|
7145
|
+
resolveConfigurationSnapshot
|
|
6295
7146
|
};
|