@rowan-agent/agent 0.8.4 → 0.8.5
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 +12 -5
- package/dist/index.d.ts +30 -5
- package/dist/index.js +230 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,8 +21,12 @@ const agentId = await runtime.createAgent({
|
|
|
21
21
|
identity: "example:v1", // Stable config snapshot identity, not the Agent ID
|
|
22
22
|
model: { provider: "openai", id: "gpt-4o" },
|
|
23
23
|
stream,
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
definition: {
|
|
25
|
+
name: "workspace-assistant",
|
|
26
|
+
description: "Assist with the current workspace.",
|
|
27
|
+
content: "You are helpful.",
|
|
28
|
+
},
|
|
29
|
+
resources: {
|
|
26
30
|
tools: createCoreTools({ root: process.cwd() }),
|
|
27
31
|
skills: [],
|
|
28
32
|
},
|
|
@@ -68,7 +72,8 @@ lifecycles, and Owner fencing.
|
|
|
68
72
|
|
|
69
73
|
## Tool lifecycle
|
|
70
74
|
|
|
71
|
-
Tools are supplied
|
|
75
|
+
Tools are supplied as `AgentConfig.resources.tools`, selected by the
|
|
76
|
+
Definition, and persist through:
|
|
72
77
|
|
|
73
78
|
`pending → running → completed | failed | indeterminate`
|
|
74
79
|
|
|
@@ -101,5 +106,7 @@ checkpoints; transient events never enter the Durable Store.
|
|
|
101
106
|
|
|
102
107
|
## Resources
|
|
103
108
|
|
|
104
|
-
`loadSkills()`, `loadPhases()`, and `loadExtensions()` load
|
|
105
|
-
Pass
|
|
109
|
+
`loadSkills()`, `loadPhases()`, and `loadExtensions()` load Resource Candidates.
|
|
110
|
+
Pass them through `AgentConfig.resources`; the Definition's optional name lists
|
|
111
|
+
select from them after Extension assembly. Omitted lists inherit all candidates,
|
|
112
|
+
`[]` selects none, and missing names warn and are skipped.
|
package/dist/index.d.ts
CHANGED
|
@@ -865,6 +865,20 @@ type ToolProgress = Readonly<{
|
|
|
865
865
|
}>;
|
|
866
866
|
type RunEvent = DurableRunEvent | MessageDelta | ToolProgress;
|
|
867
867
|
|
|
868
|
+
type AgentDefinition = Readonly<{
|
|
869
|
+
name: string;
|
|
870
|
+
description: string;
|
|
871
|
+
content: string;
|
|
872
|
+
tools?: readonly string[];
|
|
873
|
+
skills?: readonly string[];
|
|
874
|
+
phases?: readonly string[];
|
|
875
|
+
extensions?: readonly string[];
|
|
876
|
+
entryPhase?: string;
|
|
877
|
+
model?: ModelRef;
|
|
878
|
+
}>;
|
|
879
|
+
/** Parse one Markdown definition using the same frontmatter vocabulary as Phase resources. */
|
|
880
|
+
declare function parseAgentDefinition(raw: string): AgentDefinition;
|
|
881
|
+
|
|
868
882
|
type UserInput = string | Readonly<{
|
|
869
883
|
content: UserContent;
|
|
870
884
|
metadata?: Metadata;
|
|
@@ -903,17 +917,17 @@ type AfterToolCall = (input: Readonly<{
|
|
|
903
917
|
context: ToolInvocationContext;
|
|
904
918
|
signal: AbortSignal;
|
|
905
919
|
}>) => ToolExecutionResult | Promise<ToolExecutionResult>;
|
|
906
|
-
type
|
|
907
|
-
systemPrompt: string;
|
|
920
|
+
type AgentResources = Readonly<{
|
|
908
921
|
tools: readonly Tool[];
|
|
909
922
|
skills: readonly Skill[];
|
|
910
923
|
phases?: PhaseRegistry;
|
|
924
|
+
extensions?: readonly LoadedExtension[];
|
|
911
925
|
}>;
|
|
912
926
|
type AgentConfig = Readonly<{
|
|
913
927
|
identity: string;
|
|
914
|
-
|
|
928
|
+
definition: AgentDefinition;
|
|
929
|
+
resources: AgentResources;
|
|
915
930
|
cwd?: string;
|
|
916
|
-
extensions?: readonly LoadedExtension[];
|
|
917
931
|
maxAttempts?: number;
|
|
918
932
|
beforeToolCall?: BeforeToolCall;
|
|
919
933
|
afterToolCall?: AfterToolCall;
|
|
@@ -1806,6 +1820,17 @@ declare function loadExtensionsFromPath(path: string): Promise<{
|
|
|
1806
1820
|
}>;
|
|
1807
1821
|
}>;
|
|
1808
1822
|
|
|
1823
|
+
/** Parsed frontmatter result */
|
|
1824
|
+
interface FrontmatterResult<T = Record<string, unknown>> {
|
|
1825
|
+
frontmatter: T;
|
|
1826
|
+
body: string;
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Parse YAML frontmatter from markdown content.
|
|
1830
|
+
* Parse YAML frontmatter and return the markdown body after the closing delimiter.
|
|
1831
|
+
*/
|
|
1832
|
+
declare function parseFrontmatter<T = Record<string, unknown>>(raw: string): FrontmatterResult<T>;
|
|
1833
|
+
|
|
1809
1834
|
declare function createCoreTools(input?: CoreToolContext): Tool[];
|
|
1810
1835
|
|
|
1811
|
-
export { type AfterToolCall, type AgentConfig, type
|
|
1836
|
+
export { type AfterToolCall, type AgentConfig, type AgentDefinition, type AgentId, type AgentListCursor, type AgentRecord, type AgentResources, type AgentRun, AgentRuntime, type AgentRuntimeOptions, type AgentSummary, type AnyRuntimeError, type AssistantContent, type AssistantMessage, type BeforeToolCall, type ConfigProvider, type ConfigPutResult, type ConfigResolution, type ConfigToken, type CoreToolContext, type DurableConsumer, type DurableRunEvent, type DurableStore, type DurableToolResult, type EventCursor, type EventId, type ExecutionCheckpoint, type ExecutionId, type ExecutionToken, type ExtensionAPI, type ExtensionFactory, type FrontmatterResult, type HookEvent, type HookEventType, type HookHandler, InMemoryConfigProvider, InMemoryStore, type InputRequest, type InputRequestId, type InputRequiredCommit, type JsonObject, type JsonPrimitive, type JsonValue, type LoadExtensionsResult, type LoadedExtension, type Message, type MessageBase, type MessageCommitted, type MessageContent, type MessageDelta, type MessageId, type Metadata, type OpaqueId, type Outcome, type OwnerLease, type OwnerToken, type Page, type Phase, type PhaseContext, type PhaseExecutionIdentity, type PhaseInvocation, type PhaseOutput, type PhaseRegistry, type PhaseState, type RunBoundary, type RunClaim, type RunEvent, type RunFailure, type RunId, type RunListCursor, type RunRecord, type RunSnapshot, type RunState, type RunStateChanged, type RunSummary, RuntimeError, type RuntimeErrorCode, type RuntimeErrorDetails, type Skill, SqliteStore, type TextContent, type ThinkingContent, type Tool, type ToolCallId, type ToolCallSnapshot, type ToolCallState, type ToolDefinition, type ToolExecutionResult, type ToolInvocationContext, type ToolMessage, type ToolMessageContent, type ToolProgress, type ToolResultContent, type ToolStateChanged, type ToolUseContent, type UserContent, type UserInput, type UserMessage, brandConfigToken, createCoreTools, isRuntimeError, loadExtensionsFromPath as loadExtensions, loadPhases, loadSkills, parseAgentDefinition, parseFrontmatter };
|
package/dist/index.js
CHANGED
|
@@ -1015,7 +1015,92 @@ function createExtensionAPI(hooks, options, runtime, eventBus) {
|
|
|
1015
1015
|
import { existsSync, statSync } from "fs";
|
|
1016
1016
|
import { readdir } from "fs/promises";
|
|
1017
1017
|
import { dirname as dirname3, join, resolve as resolve3 } from "path";
|
|
1018
|
+
|
|
1019
|
+
// src/harness/definitions.ts
|
|
1018
1020
|
import { parseModelRef } from "@rowan-agent/models";
|
|
1021
|
+
function assertAgentDefinition(value) {
|
|
1022
|
+
if (!isRecord(value)) throw new TypeError("config.definition is invalid");
|
|
1023
|
+
requiredString(value.name, "definition.name");
|
|
1024
|
+
requiredString(value.description, "definition.description");
|
|
1025
|
+
requiredString(value.content, "definition.content");
|
|
1026
|
+
optionalStringList(value.tools, "definition.tools");
|
|
1027
|
+
optionalStringList(value.skills, "definition.skills");
|
|
1028
|
+
optionalStringList(value.phases, "definition.phases");
|
|
1029
|
+
optionalStringList(value.extensions, "definition.extensions");
|
|
1030
|
+
optionalString(value.entryPhase, "definition.entryPhase");
|
|
1031
|
+
if (value.model !== void 0) {
|
|
1032
|
+
if (!isRecord(value.model) || typeof value.model.provider !== "string" || value.model.provider.trim() === "" || typeof value.model.id !== "string" || value.model.id.trim() === "") {
|
|
1033
|
+
throw new TypeError("definition.model must be a valid model reference");
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function parseAgentDefinition(raw) {
|
|
1038
|
+
const { frontmatter, body } = parseFrontmatter(raw);
|
|
1039
|
+
return normalizeAgentDefinition(frontmatter, body);
|
|
1040
|
+
}
|
|
1041
|
+
function normalizeAgentDefinition(frontmatter, content, options = {}) {
|
|
1042
|
+
const name = frontmatter.name === void 0 && options.fallbackName ? options.fallbackName : requiredString(frontmatter.name, "name");
|
|
1043
|
+
const description = requiredString(frontmatter.description, "description");
|
|
1044
|
+
const normalizedContent = requiredString(content, "content");
|
|
1045
|
+
const tools = optionalStringList(frontmatter.tools, "tools");
|
|
1046
|
+
const skills = optionalStringList(frontmatter.skills, "skills");
|
|
1047
|
+
const phases = optionalStringList(frontmatter.phases, "phases");
|
|
1048
|
+
const extensions = optionalStringList(frontmatter.extensions, "extensions");
|
|
1049
|
+
const entryPhase = optionalString(frontmatter.entryPhase, "entryPhase");
|
|
1050
|
+
const model = optionalModel(frontmatter.model);
|
|
1051
|
+
return {
|
|
1052
|
+
name,
|
|
1053
|
+
description,
|
|
1054
|
+
content: normalizedContent,
|
|
1055
|
+
...tools ? { tools } : {},
|
|
1056
|
+
...skills ? { skills } : {},
|
|
1057
|
+
...phases ? { phases } : {},
|
|
1058
|
+
...extensions ? { extensions } : {},
|
|
1059
|
+
...entryPhase ? { entryPhase } : {},
|
|
1060
|
+
...model ? { model } : {}
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
function requiredString(value, field) {
|
|
1064
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1065
|
+
throw new TypeError(`${field} is required`);
|
|
1066
|
+
}
|
|
1067
|
+
return value.trim();
|
|
1068
|
+
}
|
|
1069
|
+
function optionalString(value, field) {
|
|
1070
|
+
if (value === void 0) return void 0;
|
|
1071
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1072
|
+
throw new TypeError(`${field} must be a non-empty string`);
|
|
1073
|
+
}
|
|
1074
|
+
return value.trim();
|
|
1075
|
+
}
|
|
1076
|
+
function optionalStringList(value, field) {
|
|
1077
|
+
if (value === void 0) return void 0;
|
|
1078
|
+
if (!Array.isArray(value)) throw new TypeError(`${field} must be an array of strings`);
|
|
1079
|
+
const names = [];
|
|
1080
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1081
|
+
for (const [index, item] of value.entries()) {
|
|
1082
|
+
if (typeof item !== "string" || item.trim() === "") {
|
|
1083
|
+
throw new TypeError(`${field}[${index}] must be a non-empty string`);
|
|
1084
|
+
}
|
|
1085
|
+
const name = item.trim();
|
|
1086
|
+
if (seen.has(name)) continue;
|
|
1087
|
+
seen.add(name);
|
|
1088
|
+
names.push(name);
|
|
1089
|
+
}
|
|
1090
|
+
return names;
|
|
1091
|
+
}
|
|
1092
|
+
function optionalModel(value) {
|
|
1093
|
+
const source = optionalString(value, "model");
|
|
1094
|
+
if (!source) return void 0;
|
|
1095
|
+
const model = parseModelRef(source);
|
|
1096
|
+
if (!model || model.provider.trim() === "" || model.id.trim() === "") {
|
|
1097
|
+
throw new TypeError("model must be a valid model reference");
|
|
1098
|
+
}
|
|
1099
|
+
return model;
|
|
1100
|
+
}
|
|
1101
|
+
function isRecord(value) {
|
|
1102
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1103
|
+
}
|
|
1019
1104
|
|
|
1020
1105
|
// src/harness/resource-validation.ts
|
|
1021
1106
|
var MAX_RESOURCE_ID_LENGTH = 64;
|
|
@@ -1113,8 +1198,16 @@ async function loadPhase(targetPath) {
|
|
|
1113
1198
|
const { frontmatter, body } = loaded;
|
|
1114
1199
|
const metadata = frontmatter;
|
|
1115
1200
|
const frontmatterName = typeof metadata.name === "string" ? metadata.name : void 0;
|
|
1116
|
-
|
|
1117
|
-
|
|
1201
|
+
let definition;
|
|
1202
|
+
try {
|
|
1203
|
+
definition = normalizeAgentDefinition(metadata, body, { fallbackName: directoryName });
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1206
|
+
warnResourceDiagnostics("phase", resolved, [message]);
|
|
1207
|
+
throw new ResourceMetadataError("invalid_metadata", message);
|
|
1208
|
+
}
|
|
1209
|
+
const name = definition.name;
|
|
1210
|
+
const description = validateDescription(definition.description);
|
|
1118
1211
|
const diagnostics = validateResourceId(directoryName);
|
|
1119
1212
|
if (frontmatterName && frontmatterName !== directoryName) {
|
|
1120
1213
|
diagnostics.push(...validateResourceName(frontmatterName, directoryName));
|
|
@@ -1123,22 +1216,19 @@ async function loadPhase(targetPath) {
|
|
|
1123
1216
|
diagnostics.push(...validateSkillReferences(metadata.skills));
|
|
1124
1217
|
diagnostics.push(...validatePhaseTarget(metadata.target));
|
|
1125
1218
|
warnResourceDiagnostics("phase", resolved, diagnostics);
|
|
1126
|
-
if (description.missing) {
|
|
1127
|
-
throw new ResourceMetadataError("invalid_metadata", "description is required");
|
|
1128
|
-
}
|
|
1129
1219
|
const baseDir = dirname3(resolved);
|
|
1130
1220
|
const phase = {
|
|
1131
1221
|
name,
|
|
1132
1222
|
description: description.description,
|
|
1133
|
-
tools:
|
|
1134
|
-
skills:
|
|
1223
|
+
tools: definition.tools ? [...definition.tools] : void 0,
|
|
1224
|
+
skills: definition.skills ? [...definition.skills] : void 0,
|
|
1135
1225
|
target: metadata.target,
|
|
1136
1226
|
input: metadata.input,
|
|
1137
1227
|
isolated: metadata.isolated,
|
|
1138
1228
|
filePath: resolved,
|
|
1139
1229
|
baseDir,
|
|
1140
|
-
content:
|
|
1141
|
-
model:
|
|
1230
|
+
content: definition.content,
|
|
1231
|
+
model: definition.model
|
|
1142
1232
|
};
|
|
1143
1233
|
const codePath = await discoverPhaseCode(baseDir);
|
|
1144
1234
|
if (codePath) {
|
|
@@ -1236,6 +1326,31 @@ function createDefaultPhase() {
|
|
|
1236
1326
|
};
|
|
1237
1327
|
}
|
|
1238
1328
|
|
|
1329
|
+
// src/harness/resource-selection.ts
|
|
1330
|
+
function selectNamedResources(candidates, names, kind) {
|
|
1331
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1332
|
+
for (const candidate of candidates) {
|
|
1333
|
+
if (byName.has(candidate.name)) {
|
|
1334
|
+
throw new TypeError(`Duplicate ${kind} candidate "${candidate.name}".`);
|
|
1335
|
+
}
|
|
1336
|
+
byName.set(candidate.name, candidate);
|
|
1337
|
+
}
|
|
1338
|
+
if (names === void 0) return [...candidates];
|
|
1339
|
+
const selected = [];
|
|
1340
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1341
|
+
for (const name of names) {
|
|
1342
|
+
if (seen.has(name)) continue;
|
|
1343
|
+
seen.add(name);
|
|
1344
|
+
const candidate = byName.get(name);
|
|
1345
|
+
if (!candidate) {
|
|
1346
|
+
console.warn(`${kind} "${name}" is not available and will be skipped.`);
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
selected.push(candidate);
|
|
1350
|
+
}
|
|
1351
|
+
return selected;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1239
1354
|
// src/harness/context/system-prompt.ts
|
|
1240
1355
|
function buildSystemPrompt(options) {
|
|
1241
1356
|
const { systemPrompt, promptGuidelines, appendSystemPrompt, tools, skills } = options;
|
|
@@ -1763,8 +1878,8 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1763
1878
|
const messageManager = createMessageManager(config.context, config.onMessage, config.onMessageDelta);
|
|
1764
1879
|
const toolExecutionManager = createToolExecutionManager();
|
|
1765
1880
|
const execution = createPhaseExecution(config, state, phase, messageManager, toolExecutionManager, registry);
|
|
1766
|
-
const phaseTools =
|
|
1767
|
-
const phaseSkills =
|
|
1881
|
+
const phaseTools = selectPhaseTools(allTools, phase.tools);
|
|
1882
|
+
const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
|
|
1768
1883
|
let phaseContext = {
|
|
1769
1884
|
systemPrompt: config.context.systemPrompt,
|
|
1770
1885
|
messages: messageManager.visible(),
|
|
@@ -2254,8 +2369,8 @@ function createPhaseExecution(config, state, phase, messageManager, toolExecutio
|
|
|
2254
2369
|
async function executeParallelPhase(config, state, registry, phase, payload, context, availablePhases, instanceId, groupId, index, count, sourcePhaseId) {
|
|
2255
2370
|
const messages = [...context];
|
|
2256
2371
|
const allTools = buildToolsWithRouting(config, availablePhases);
|
|
2257
|
-
const phaseTools =
|
|
2258
|
-
const phaseSkills =
|
|
2372
|
+
const phaseTools = selectPhaseTools(allTools, phase.tools);
|
|
2373
|
+
const phaseSkills = selectNamedResources(config.context.skills, phase.skills, "Skill");
|
|
2259
2374
|
const systemPrompt = config.context.systemPrompt;
|
|
2260
2375
|
const phaseContext = {
|
|
2261
2376
|
systemPrompt,
|
|
@@ -2293,6 +2408,11 @@ async function executeParallelPhase(config, state, registry, phase, payload, con
|
|
|
2293
2408
|
const resultPayload = decision?.decision[0]?.payload !== void 0 ? normalizePayload(decision.decision[0].payload) : output.payload;
|
|
2294
2409
|
return { instanceId, phaseId: phase.name, payload: resultPayload, content: output.message };
|
|
2295
2410
|
}
|
|
2411
|
+
function selectPhaseTools(candidates, names) {
|
|
2412
|
+
const routing = candidates.filter(({ name }) => name === PhaseRouteTool);
|
|
2413
|
+
const ordinary = candidates.filter(({ name }) => name !== PhaseRouteTool);
|
|
2414
|
+
return [...selectNamedResources(ordinary, names, "Tool"), ...routing];
|
|
2415
|
+
}
|
|
2296
2416
|
async function waitForBackgroundTasks(backgroundTasks) {
|
|
2297
2417
|
const entries = Array.from(backgroundTasks.entries());
|
|
2298
2418
|
const results = await Promise.allSettled(entries.map(([, t]) => t.promise));
|
|
@@ -2523,29 +2643,29 @@ function createMetrics() {
|
|
|
2523
2643
|
startedAtMs: Date.now()
|
|
2524
2644
|
};
|
|
2525
2645
|
}
|
|
2526
|
-
function
|
|
2646
|
+
function isRecord2(value) {
|
|
2527
2647
|
return value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
2528
2648
|
}
|
|
2529
2649
|
function isCheckpointData(value) {
|
|
2530
|
-
if (!
|
|
2650
|
+
if (!isRecord2(value) || typeof value.currentPhase !== "string" || !Number.isInteger(value.attempt)) return false;
|
|
2531
2651
|
if (!isMetrics(value.metrics)) return false;
|
|
2532
2652
|
if (value.continuation !== void 0 && !isContinuation(value.continuation)) return false;
|
|
2533
2653
|
return true;
|
|
2534
2654
|
}
|
|
2535
2655
|
function isMetrics(value) {
|
|
2536
|
-
if (!
|
|
2537
|
-
return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) =>
|
|
2656
|
+
if (!isRecord2(value)) return false;
|
|
2657
|
+
return Number.isInteger(value.iterations) && Array.isArray(value.phaseTransitions) && value.phaseTransitions.every((transition) => isRecord2(transition) && typeof transition.from === "string" && typeof transition.to === "string" && typeof transition.ts === "string") && Number.isInteger(value.compactionCount) && Number.isInteger(value.retryCount) && typeof value.startedAt === "string" && Number.isFinite(value.startedAtMs) && (value.endedAt === void 0 || typeof value.endedAt === "string") && (value.durationMs === void 0 || Number.isFinite(value.durationMs));
|
|
2538
2658
|
}
|
|
2539
2659
|
function isContinuation(value) {
|
|
2540
|
-
if (!
|
|
2660
|
+
if (!isRecord2(value) || typeof value.isContinuing !== "boolean" || !Array.isArray(value.previousResults)) return false;
|
|
2541
2661
|
if (value.previousPayload !== void 0 && !isJsonValue(value.previousPayload)) return false;
|
|
2542
|
-
return value.previousResults.every((result) =>
|
|
2662
|
+
return value.previousResults.every((result) => isRecord2(result) && typeof result.name === "string" && (result.output === void 0 || isJsonValue(result.output))) && (value.pendingInstruction === void 0 || typeof value.pendingInstruction === "string") && (value.previousPhaseMessageId === void 0 || typeof value.previousPhaseMessageId === "string");
|
|
2543
2663
|
}
|
|
2544
2664
|
|
|
2545
2665
|
// src/runtime/contracts.ts
|
|
2546
2666
|
var METADATA_LIMIT = 64 * 1024;
|
|
2547
2667
|
var IDENTITY_LIMIT = 256;
|
|
2548
|
-
function
|
|
2668
|
+
function isRecord3(value) {
|
|
2549
2669
|
if (value === null || typeof value !== "object") return false;
|
|
2550
2670
|
const prototype = Object.getPrototypeOf(value);
|
|
2551
2671
|
return prototype === Object.prototype || prototype === null;
|
|
@@ -2554,7 +2674,7 @@ function hasOnlyKeys(value, allowed) {
|
|
|
2554
2674
|
return Reflect.ownKeys(value).every((key) => typeof key === "string" && allowed.includes(key));
|
|
2555
2675
|
}
|
|
2556
2676
|
function isMetadata(value) {
|
|
2557
|
-
if (!
|
|
2677
|
+
if (!isRecord3(value) || !isJsonValue(value)) return false;
|
|
2558
2678
|
try {
|
|
2559
2679
|
assertUtf8ByteLimit(canonicalJson(value), METADATA_LIMIT, "metadata");
|
|
2560
2680
|
return true;
|
|
@@ -2566,23 +2686,23 @@ function assertMetadata(value, argument) {
|
|
|
2566
2686
|
if (!isMetadata(value)) throw new TypeError(`${argument} must be a JSON-safe metadata object`);
|
|
2567
2687
|
}
|
|
2568
2688
|
function isText(value) {
|
|
2569
|
-
return
|
|
2689
|
+
return isRecord3(value) && value.type === "text" && typeof value.text === "string";
|
|
2570
2690
|
}
|
|
2571
2691
|
function isImage(value) {
|
|
2572
|
-
return
|
|
2692
|
+
return isRecord3(value) && value.type === "image" && typeof value.data === "string" && typeof value.mimeType === "string";
|
|
2573
2693
|
}
|
|
2574
2694
|
function isUserContent(value) {
|
|
2575
2695
|
return typeof value === "string" || Array.isArray(value) && value.every((part) => isText(part) || isImage(part));
|
|
2576
2696
|
}
|
|
2577
2697
|
function normalizeUserInput(input) {
|
|
2578
2698
|
const normalized = typeof input === "string" ? { content: input } : input;
|
|
2579
|
-
if (!
|
|
2699
|
+
if (!isRecord3(normalized) || !hasOnlyKeys(normalized, ["content", "metadata"]) || !isUserContent(normalized.content)) throw new TypeError("input must contain only valid UserContent");
|
|
2580
2700
|
if (normalized.metadata !== void 0) assertMetadata(normalized.metadata, "input.metadata");
|
|
2581
2701
|
assertJsonValue(normalized, "input");
|
|
2582
2702
|
return normalized;
|
|
2583
2703
|
}
|
|
2584
2704
|
function isToolResult(value) {
|
|
2585
|
-
if (!
|
|
2705
|
+
if (!isRecord3(value) || !isJsonValue(value.content) || typeof value.ok !== "boolean") return false;
|
|
2586
2706
|
const expected = value.ok ? ["content", "ok"] : ["content", "error", "ok"];
|
|
2587
2707
|
if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(expected)) return false;
|
|
2588
2708
|
return value.ok || typeof value.error === "string";
|
|
@@ -2590,8 +2710,11 @@ function isToolResult(value) {
|
|
|
2590
2710
|
function assertAgentConfig(config) {
|
|
2591
2711
|
if (typeof config.identity !== "string" || config.identity.length === 0) throw new TypeError("config.identity must be non-empty");
|
|
2592
2712
|
assertUtf8ByteLimit(config.identity, IDENTITY_LIMIT, "config.identity");
|
|
2593
|
-
|
|
2594
|
-
|
|
2713
|
+
assertAgentDefinition(config.definition);
|
|
2714
|
+
if (!config.resources || !Array.isArray(config.resources.tools) || !Array.isArray(config.resources.skills)) {
|
|
2715
|
+
throw new TypeError("config.resources is invalid");
|
|
2716
|
+
}
|
|
2717
|
+
for (const tool of config.resources.tools) projectToolDefinition(tool);
|
|
2595
2718
|
}
|
|
2596
2719
|
function assertToolExecutionResult(value) {
|
|
2597
2720
|
if (!isToolResult(value)) throw new TypeError("Tool result must be JSON-safe and contain no Runtime identity");
|
|
@@ -2599,7 +2722,7 @@ function assertToolExecutionResult(value) {
|
|
|
2599
2722
|
function projectToolDefinition(tool) {
|
|
2600
2723
|
if (typeof tool.name !== "string" || tool.name.length === 0 || typeof tool.description !== "string") throw new TypeError("Tool definition is invalid");
|
|
2601
2724
|
assertJsonValue(tool.parameters, "tool.parameters");
|
|
2602
|
-
if (!
|
|
2725
|
+
if (!isRecord3(tool.parameters)) throw new TypeError("tool.parameters must be a JSON object");
|
|
2603
2726
|
return { name: tool.name, description: tool.description, parameters: JSON.parse(canonicalJson(tool.parameters)) };
|
|
2604
2727
|
}
|
|
2605
2728
|
|
|
@@ -2666,7 +2789,7 @@ function brandConfigToken(raw) {
|
|
|
2666
2789
|
return raw;
|
|
2667
2790
|
}
|
|
2668
2791
|
function validateConfigResolution(agentId, value) {
|
|
2669
|
-
if (!
|
|
2792
|
+
if (!isRecord4(value) || typeof value.kind !== "string") {
|
|
2670
2793
|
throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
|
|
2671
2794
|
}
|
|
2672
2795
|
if (value.kind === "deferred") {
|
|
@@ -2679,18 +2802,26 @@ function validateConfigResolution(agentId, value) {
|
|
|
2679
2802
|
};
|
|
2680
2803
|
}
|
|
2681
2804
|
if (value.kind === "unavailable" && typeof value.reason === "string") return { kind: "unavailable", reason: value.reason };
|
|
2682
|
-
if (value.kind === "available" &&
|
|
2805
|
+
if (value.kind === "available" && isRecord4(value.config) && typeof value.config.identity === "string") {
|
|
2683
2806
|
return { kind: "available", config: value.config };
|
|
2684
2807
|
}
|
|
2685
2808
|
throw new Error(`Config Provider returned an invalid resolution for Agent ${agentId}.`);
|
|
2686
2809
|
}
|
|
2687
2810
|
function snapshotConfig(config) {
|
|
2688
|
-
const
|
|
2689
|
-
...config.
|
|
2690
|
-
tools: Object.freeze([...config.
|
|
2691
|
-
skills: Object.freeze([...config.
|
|
2811
|
+
const definition = Object.freeze({
|
|
2812
|
+
...config.definition,
|
|
2813
|
+
...config.definition.tools ? { tools: Object.freeze([...config.definition.tools]) } : {},
|
|
2814
|
+
...config.definition.skills ? { skills: Object.freeze([...config.definition.skills]) } : {},
|
|
2815
|
+
...config.definition.phases ? { phases: Object.freeze([...config.definition.phases]) } : {},
|
|
2816
|
+
...config.definition.extensions ? { extensions: Object.freeze([...config.definition.extensions]) } : {}
|
|
2692
2817
|
});
|
|
2693
|
-
|
|
2818
|
+
const resources = Object.freeze({
|
|
2819
|
+
...config.resources,
|
|
2820
|
+
tools: Object.freeze([...config.resources.tools]),
|
|
2821
|
+
skills: Object.freeze([...config.resources.skills]),
|
|
2822
|
+
...config.resources.extensions ? { extensions: Object.freeze([...config.resources.extensions]) } : {}
|
|
2823
|
+
});
|
|
2824
|
+
return Object.freeze({ ...config, definition, resources });
|
|
2694
2825
|
}
|
|
2695
2826
|
function assertConfigIdentity(identity) {
|
|
2696
2827
|
if (typeof identity !== "string" || identity.length === 0) throw new TypeError("config.identity must be non-empty");
|
|
@@ -2705,7 +2836,7 @@ function throwIfAborted(signal) {
|
|
|
2705
2836
|
error.name = "AbortError";
|
|
2706
2837
|
throw error;
|
|
2707
2838
|
}
|
|
2708
|
-
function
|
|
2839
|
+
function isRecord4(value) {
|
|
2709
2840
|
return value !== null && typeof value === "object";
|
|
2710
2841
|
}
|
|
2711
2842
|
|
|
@@ -3628,12 +3759,19 @@ function createExtensionRunner(options) {
|
|
|
3628
3759
|
|
|
3629
3760
|
// src/runtime/extensions.ts
|
|
3630
3761
|
async function assembleExtensions(config) {
|
|
3631
|
-
|
|
3762
|
+
const selectedExtensions = selectNamedResources(
|
|
3763
|
+
config.resources.extensions ?? [],
|
|
3764
|
+
config.definition.extensions,
|
|
3765
|
+
"Extension"
|
|
3766
|
+
);
|
|
3767
|
+
if (selectedExtensions.length === 0) {
|
|
3768
|
+
return { context: resolveDefinitionContext(config) };
|
|
3769
|
+
}
|
|
3632
3770
|
const runner = createExtensionRunner({ cwd: config.cwd });
|
|
3633
|
-
await runner.loadExtensions(
|
|
3771
|
+
await runner.loadExtensions(selectedExtensions);
|
|
3634
3772
|
runner.bind();
|
|
3635
3773
|
const extensionTools = runner.getAllRegisteredTools().map(adaptExtensionTool);
|
|
3636
|
-
const tools = [...config.
|
|
3774
|
+
const tools = [...config.resources.tools];
|
|
3637
3775
|
const names = new Set(tools.map((tool) => tool.name));
|
|
3638
3776
|
for (const tool of extensionTools) {
|
|
3639
3777
|
if (names.has(tool.name)) throw new TypeError(`Extension Tool collides with Context Tool "${tool.name}"`);
|
|
@@ -3641,9 +3779,10 @@ async function assembleExtensions(config) {
|
|
|
3641
3779
|
tools.push(tool);
|
|
3642
3780
|
}
|
|
3643
3781
|
const extensionPhases = runner.createPhaseRegistry({ entryPhaseId: null });
|
|
3644
|
-
const phases = mergePhases(config.
|
|
3782
|
+
const phases = mergePhases(config.resources.phases, extensionPhases);
|
|
3783
|
+
const context = resolveDefinitionContext(config, { tools, phases });
|
|
3645
3784
|
return {
|
|
3646
|
-
context
|
|
3785
|
+
context,
|
|
3647
3786
|
beforePhase: (phaseId, input) => runner.emitBeforePhase(phaseId, input),
|
|
3648
3787
|
afterPhase: (phaseId, output) => runner.emitAfterPhase(phaseId, output),
|
|
3649
3788
|
beforePrompt: (phaseId, input) => runner.emitBeforePrompt(phaseId, input),
|
|
@@ -3657,11 +3796,41 @@ async function assembleExtensions(config) {
|
|
|
3657
3796
|
const result = await runner.emitAfterToolCall(tool, toLoopResult(input.result, input.context.toolCallId, input.tool.name));
|
|
3658
3797
|
return fromLoopResult(result);
|
|
3659
3798
|
},
|
|
3660
|
-
setContext: (
|
|
3661
|
-
runner.currentContext =
|
|
3799
|
+
setContext: (context2) => {
|
|
3800
|
+
runner.currentContext = context2;
|
|
3662
3801
|
}
|
|
3663
3802
|
};
|
|
3664
3803
|
}
|
|
3804
|
+
function resolveDefinitionContext(config, assembled = {}) {
|
|
3805
|
+
const tools = selectNamedResources(
|
|
3806
|
+
assembled.tools ?? config.resources.tools,
|
|
3807
|
+
config.definition.tools,
|
|
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".`);
|
|
3826
|
+
}
|
|
3827
|
+
return {
|
|
3828
|
+
systemPrompt: config.definition.content,
|
|
3829
|
+
tools,
|
|
3830
|
+
skills,
|
|
3831
|
+
phases: { phases, entryPhaseId }
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3665
3834
|
function mergePhases(base, extension) {
|
|
3666
3835
|
if (!base && extension.phases.size === 0) return void 0;
|
|
3667
3836
|
const phases = new Map(base?.phases ?? []);
|
|
@@ -4010,29 +4179,28 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4010
4179
|
}
|
|
4011
4180
|
const executionId = createId("exec");
|
|
4012
4181
|
claim = await this.owned.claimRun({ runId: run.id, expectedRevision: run.revision, executionId, configToken: token });
|
|
4182
|
+
executionRevision = claim.run.revision;
|
|
4013
4183
|
const controller = new AbortController();
|
|
4014
4184
|
this.executions.set(run.id, { controller, executionId });
|
|
4015
4185
|
const config = resolution.config;
|
|
4016
4186
|
const assembly = await assembleExtensions(config);
|
|
4017
|
-
const executionConfig = {
|
|
4018
|
-
...config,
|
|
4019
|
-
context: assembly.context,
|
|
4020
|
-
...assembly.beforeToolCall ? { beforeToolCall: assembly.beforeToolCall } : {},
|
|
4021
|
-
...assembly.afterToolCall ? { afterToolCall: assembly.afterToolCall } : {}
|
|
4022
|
-
};
|
|
4023
|
-
executionRevision = claim.run.revision;
|
|
4024
4187
|
let toolQueue = Promise.resolve();
|
|
4025
4188
|
const model = "stream" in config && config.stream ? config.model : { provider: config.model.provider, id: config.model.id };
|
|
4026
4189
|
const stream = "stream" in config && config.stream ? config.stream : createModelStream(config.model);
|
|
4027
4190
|
const executionContext = projectModelContext({
|
|
4028
4191
|
context: {
|
|
4029
|
-
...
|
|
4030
|
-
phases: normalizePhaseRegistry(
|
|
4192
|
+
...assembly.context,
|
|
4193
|
+
phases: normalizePhaseRegistry(assembly.context.phases)
|
|
4031
4194
|
},
|
|
4032
4195
|
messages: claim.history,
|
|
4033
4196
|
agentId: run.agentId,
|
|
4034
4197
|
runId: run.id
|
|
4035
4198
|
});
|
|
4199
|
+
const executionTools = {
|
|
4200
|
+
tools: assembly.context.tools,
|
|
4201
|
+
beforeToolCall: assembly.beforeToolCall ?? config.beforeToolCall,
|
|
4202
|
+
afterToolCall: assembly.afterToolCall ?? config.afterToolCall
|
|
4203
|
+
};
|
|
4036
4204
|
const result = await executeOnce({
|
|
4037
4205
|
canonicalMessages: executionContext.messages,
|
|
4038
4206
|
context: executionContext,
|
|
@@ -4043,7 +4211,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4043
4211
|
},
|
|
4044
4212
|
model,
|
|
4045
4213
|
stream,
|
|
4046
|
-
maxAttempts:
|
|
4214
|
+
maxAttempts: config.maxAttempts,
|
|
4047
4215
|
checkpoint: claim.run.checkpoint,
|
|
4048
4216
|
signal: controller.signal,
|
|
4049
4217
|
beforePhase: assembly.beforePhase,
|
|
@@ -4070,7 +4238,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4070
4238
|
run,
|
|
4071
4239
|
execution: claim.execution,
|
|
4072
4240
|
expectedRevision: executionRevision,
|
|
4073
|
-
|
|
4241
|
+
toolConfig: executionTools,
|
|
4074
4242
|
toolCalls: [toolCall],
|
|
4075
4243
|
signal: controller.signal
|
|
4076
4244
|
});
|
|
@@ -4086,7 +4254,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4086
4254
|
run,
|
|
4087
4255
|
execution: claim.execution,
|
|
4088
4256
|
expectedRevision: executionRevision,
|
|
4089
|
-
|
|
4257
|
+
toolConfig: executionTools,
|
|
4090
4258
|
toolCalls,
|
|
4091
4259
|
signal: controller.signal
|
|
4092
4260
|
});
|
|
@@ -4165,7 +4333,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4165
4333
|
return { results, revision };
|
|
4166
4334
|
}
|
|
4167
4335
|
async executeReservedTool(input) {
|
|
4168
|
-
const durableTool = input.
|
|
4336
|
+
const durableTool = input.toolConfig.tools.find((candidate) => candidate.name === input.toolCall.name);
|
|
4169
4337
|
const providerToolCallId = input.toolCall.id;
|
|
4170
4338
|
const toolCallId = input.tool.id;
|
|
4171
4339
|
let revision = input.expectedRevision;
|
|
@@ -4200,8 +4368,8 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4200
4368
|
}
|
|
4201
4369
|
};
|
|
4202
4370
|
try {
|
|
4203
|
-
if (input.
|
|
4204
|
-
const decision = await input.
|
|
4371
|
+
if (input.toolConfig.beforeToolCall) {
|
|
4372
|
+
const decision = await input.toolConfig.beforeToolCall({ tool: durableTool, args: toJsonValue(input.toolCall.args), context, signal: input.signal });
|
|
4205
4373
|
if (!decision.allow) {
|
|
4206
4374
|
const failed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: "failed", result: { ok: false, content: null, error: decision.reason } });
|
|
4207
4375
|
return { result: protocolFailure(decision.reason), revision: failed.run.revision };
|
|
@@ -4218,7 +4386,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4218
4386
|
try {
|
|
4219
4387
|
let result = await durableTool.execute(toJsonValue(input.toolCall.args), context, input.signal);
|
|
4220
4388
|
assertToolExecutionResult(result);
|
|
4221
|
-
if (input.
|
|
4389
|
+
if (input.toolConfig.afterToolCall) result = await input.toolConfig.afterToolCall({ tool: durableTool, result, context, signal: input.signal });
|
|
4222
4390
|
assertToolExecutionResult(result);
|
|
4223
4391
|
const committed = await this.owned.commitToolResult({ runId: input.run.id, execution: input.execution, expectedRevision: revision, toolCallId, state: result.ok ? "completed" : "failed", result });
|
|
4224
4392
|
return {
|
|
@@ -6121,5 +6289,7 @@ export {
|
|
|
6121
6289
|
isRuntimeError,
|
|
6122
6290
|
loadExtensionsFromPath as loadExtensions,
|
|
6123
6291
|
loadPhases,
|
|
6124
|
-
loadSkills
|
|
6292
|
+
loadSkills,
|
|
6293
|
+
parseAgentDefinition,
|
|
6294
|
+
parseFrontmatter
|
|
6125
6295
|
};
|