@rowan-agent/agent 0.6.1 → 0.6.2
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 +8 -8
- package/dist/index.d.ts +3 -0
- package/dist/index.js +223 -120
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -136,9 +136,9 @@ dead-lettered.
|
|
|
136
136
|
|
|
137
137
|
### Process Recovery
|
|
138
138
|
|
|
139
|
-
Runtime startup
|
|
140
|
-
|
|
141
|
-
when it reconstructs an Agent:
|
|
139
|
+
Runtime startup and periodic recovery return expired Leases to durable queued
|
|
140
|
+
work without disturbing unexpired Leases owned by another process. The host
|
|
141
|
+
supplies its current executable resources when it reconstructs an Agent:
|
|
142
142
|
|
|
143
143
|
```ts
|
|
144
144
|
const runtime = await AgentRuntime.start({ stateStore, sessionProvider });
|
|
@@ -337,17 +337,17 @@ const tools = createCoreTools({
|
|
|
337
337
|
bashTimeoutMs?, // default: 30s
|
|
338
338
|
maxBashOutputBytes?, // default: 64KB
|
|
339
339
|
});
|
|
340
|
-
// Returns: read,
|
|
340
|
+
// Returns: read, bash, edit, write
|
|
341
341
|
```
|
|
342
342
|
|
|
343
343
|
### Built-in Tools
|
|
344
344
|
|
|
345
345
|
| Tool | Description | Parameters |
|
|
346
346
|
|------|-------------|------------|
|
|
347
|
-
| `read` | Reads a
|
|
348
|
-
| `
|
|
349
|
-
| `edit` |
|
|
350
|
-
| `
|
|
347
|
+
| `read` | Reads a file, optionally by line range | `path` (required), `offset?`, `limit?` |
|
|
348
|
+
| `bash` | Runs a bash command in the workspace | `command` (required), `timeout?` (seconds) |
|
|
349
|
+
| `edit` | Applies exact replacements, including multiple disjoint edits in one call | `path` (required), `edits[]` (each with `oldText` and `newText`) |
|
|
350
|
+
| `write` | Creates or overwrites a file | `path` (required), `content` (required) |
|
|
351
351
|
|
|
352
352
|
### Custom Tools
|
|
353
353
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1420,6 +1420,7 @@ declare class AgentRuntime {
|
|
|
1420
1420
|
private runningRuns;
|
|
1421
1421
|
private pumping;
|
|
1422
1422
|
private nextWorkerId;
|
|
1423
|
+
private leaseRecoveryTimer;
|
|
1423
1424
|
private stopped;
|
|
1424
1425
|
private constructor();
|
|
1425
1426
|
static start(options: AgentRuntimeOptions): Promise<AgentRuntime>;
|
|
@@ -1443,6 +1444,8 @@ declare class AgentRuntime {
|
|
|
1443
1444
|
private registerEventConsumer;
|
|
1444
1445
|
listEvents(cursor?: RuntimeEventCursor): Promise<RuntimeEvent[]>;
|
|
1445
1446
|
private recover;
|
|
1447
|
+
private startLeaseRecovery;
|
|
1448
|
+
private recoverExpiredLeases;
|
|
1446
1449
|
private scheduleQueuedRuns;
|
|
1447
1450
|
private attachAgent;
|
|
1448
1451
|
private submitAgentInput;
|
package/dist/index.js
CHANGED
|
@@ -776,34 +776,27 @@ var DEFAULT_MAX_READ_BYTES = 64e3;
|
|
|
776
776
|
var DEFAULT_BASH_TIMEOUT_MS = 3e4;
|
|
777
777
|
var DEFAULT_MAX_BASH_OUTPUT_BYTES = 64e3;
|
|
778
778
|
var ReadArgsSchema = Type2.Object({
|
|
779
|
-
path: Type2.String(),
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
Type2.Literal("skill"),
|
|
783
|
-
Type2.Literal("phase"),
|
|
784
|
-
Type2.Literal("markdown"),
|
|
785
|
-
Type2.Literal("code"),
|
|
786
|
-
Type2.Literal("file")
|
|
787
|
-
]))
|
|
779
|
+
path: Type2.String({ description: "Path to the file to read." }),
|
|
780
|
+
offset: Type2.Optional(Type2.Number({ description: "1-based line to start from." })),
|
|
781
|
+
limit: Type2.Optional(Type2.Number({ description: "Maximum number of lines." }))
|
|
788
782
|
});
|
|
789
783
|
var ReadArgsValidator = Schema.Compile(ReadArgsSchema);
|
|
790
784
|
var WriteArgsSchema = Type2.Object({
|
|
791
|
-
path: Type2.String(),
|
|
792
|
-
content: Type2.String()
|
|
785
|
+
path: Type2.String({ description: "Path to the file to write." }),
|
|
786
|
+
content: Type2.String({ description: "Complete file contents." })
|
|
793
787
|
});
|
|
794
788
|
var WriteArgsValidator = Schema.Compile(WriteArgsSchema);
|
|
795
789
|
var EditArgsSchema = Type2.Object({
|
|
796
|
-
path: Type2.String(),
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
790
|
+
path: Type2.String({ description: "Path to the file to edit." }),
|
|
791
|
+
edits: Type2.Array(Type2.Object({
|
|
792
|
+
oldText: Type2.String({ description: "Exact unique text to replace." }),
|
|
793
|
+
newText: Type2.String({ description: "Replacement text." })
|
|
794
|
+
}), { description: "One or more non-overlapping replacements." })
|
|
800
795
|
});
|
|
801
796
|
var EditArgsValidator = Schema.Compile(EditArgsSchema);
|
|
802
797
|
var BashArgsSchema = Type2.Object({
|
|
803
|
-
command: Type2.String(),
|
|
804
|
-
|
|
805
|
-
timeoutMs: Type2.Optional(Type2.Number()),
|
|
806
|
-
maxOutputBytes: Type2.Optional(Type2.Number())
|
|
798
|
+
command: Type2.String({ description: "Bash command to execute." }),
|
|
799
|
+
timeout: Type2.Optional(Type2.Number({ description: "Timeout in seconds." }))
|
|
807
800
|
});
|
|
808
801
|
var BashArgsValidator = Schema.Compile(BashArgsSchema);
|
|
809
802
|
var validatorCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -906,12 +899,7 @@ async function executeRuntimeToolCall(input) {
|
|
|
906
899
|
}
|
|
907
900
|
await input.observe?.({ type: "tool_start", tool, args });
|
|
908
901
|
try {
|
|
909
|
-
|
|
910
|
-
let result = {
|
|
911
|
-
...rawResult,
|
|
912
|
-
toolCallId: input.toolCall.id,
|
|
913
|
-
toolName: tool.name
|
|
914
|
-
};
|
|
902
|
+
let result = await tool.execute(args, input.toolContext, input.signal);
|
|
915
903
|
if (input.afterToolCall) {
|
|
916
904
|
await input.observe?.({
|
|
917
905
|
type: "result_review_requested",
|
|
@@ -945,6 +933,20 @@ function positiveNumber(value, name) {
|
|
|
945
933
|
}
|
|
946
934
|
return void 0;
|
|
947
935
|
}
|
|
936
|
+
function positiveInteger(value, name) {
|
|
937
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
938
|
+
return `${name} must be a positive integer.`;
|
|
939
|
+
}
|
|
940
|
+
return void 0;
|
|
941
|
+
}
|
|
942
|
+
function readTextLines(text, offset, limit) {
|
|
943
|
+
if (offset === void 0 && limit === void 0) {
|
|
944
|
+
return text;
|
|
945
|
+
}
|
|
946
|
+
const start = (offset ?? 1) - 1;
|
|
947
|
+
const lines = text.split(/\r\n|\n|\r/);
|
|
948
|
+
return lines.slice(start, limit === void 0 ? void 0 : start + limit).join("\n");
|
|
949
|
+
}
|
|
948
950
|
async function captureStream(stream, maxBytes) {
|
|
949
951
|
const reader = stream.getReader();
|
|
950
952
|
const chunks = [];
|
|
@@ -984,17 +986,25 @@ async function captureStream(stream, maxBytes) {
|
|
|
984
986
|
function createReadTool(context) {
|
|
985
987
|
return {
|
|
986
988
|
name: "read",
|
|
987
|
-
description: "
|
|
989
|
+
description: "Read a file in the workspace.",
|
|
988
990
|
parameters: ReadArgsSchema,
|
|
989
|
-
promptSnippet: "Read
|
|
990
|
-
promptGuidelines: [
|
|
991
|
-
"Always read a file before editing or writing to understand its current content.",
|
|
992
|
-
"Use maxBytes to limit output for large files."
|
|
993
|
-
],
|
|
991
|
+
promptSnippet: "Read file contents.",
|
|
992
|
+
promptGuidelines: ["Read files before editing them."],
|
|
994
993
|
async execute(args, toolContext) {
|
|
995
994
|
const parsed = ReadArgsValidator.Parse(args);
|
|
995
|
+
const offsetError = parsed.offset === void 0 ? void 0 : positiveInteger(parsed.offset, "offset");
|
|
996
|
+
const limitError = parsed.limit === void 0 ? void 0 : positiveInteger(parsed.limit, "limit");
|
|
997
|
+
if (offsetError || limitError) {
|
|
998
|
+
return toolResult({
|
|
999
|
+
context: toolContext,
|
|
1000
|
+
toolName: "read",
|
|
1001
|
+
ok: false,
|
|
1002
|
+
content: null,
|
|
1003
|
+
error: offsetError ?? limitError
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
996
1006
|
const resolved = resolveCoreToolPath(context, parsed.path);
|
|
997
|
-
const maxBytes =
|
|
1007
|
+
const maxBytes = context.maxReadBytes;
|
|
998
1008
|
const invalidLimit = positiveNumber(maxBytes, "maxBytes");
|
|
999
1009
|
if (invalidLimit) {
|
|
1000
1010
|
return toolResult({
|
|
@@ -1016,9 +1026,12 @@ function createReadTool(context) {
|
|
|
1016
1026
|
});
|
|
1017
1027
|
}
|
|
1018
1028
|
const bytes = await readFile2(resolved.absolutePath);
|
|
1019
|
-
const
|
|
1029
|
+
const source = new TextDecoder().decode(bytes);
|
|
1030
|
+
const selected = readTextLines(source, parsed.offset, parsed.limit);
|
|
1031
|
+
const selectedBytes = new TextEncoder().encode(selected);
|
|
1032
|
+
const sliced = selectedBytes.subarray(0, maxBytes);
|
|
1020
1033
|
const text = new TextDecoder().decode(sliced);
|
|
1021
|
-
const resourceType =
|
|
1034
|
+
const resourceType = detectResourceType(resolved.absolutePath);
|
|
1022
1035
|
let name;
|
|
1023
1036
|
let baseDir;
|
|
1024
1037
|
if (resourceType === "skill" || resourceType === "phase") {
|
|
@@ -1029,16 +1042,14 @@ function createReadTool(context) {
|
|
|
1029
1042
|
} else {
|
|
1030
1043
|
name = inferResourceName(resolved.absolutePath, basename2(resolved.absolutePath));
|
|
1031
1044
|
}
|
|
1045
|
+
const formatted = resourceType === "skill" || resourceType === "phase" ? formatResourceOutput({ type: resourceType, name, location: resolved.absolutePath, content: text, baseDir }) : text;
|
|
1046
|
+
const content = selectedBytes.byteLength > maxBytes ? `${formatted}
|
|
1047
|
+
[truncated]` : formatted;
|
|
1032
1048
|
return toolResult({
|
|
1033
1049
|
context: toolContext,
|
|
1034
1050
|
toolName: "read",
|
|
1035
1051
|
ok: true,
|
|
1036
|
-
content
|
|
1037
|
-
path: resolved.relativePath,
|
|
1038
|
-
content: formatResourceOutput({ type: resourceType, name, location: resolved.absolutePath, content: text, baseDir }),
|
|
1039
|
-
sizeBytes: bytes.byteLength,
|
|
1040
|
-
truncated: bytes.byteLength > maxBytes
|
|
1041
|
-
}
|
|
1052
|
+
content
|
|
1042
1053
|
});
|
|
1043
1054
|
}
|
|
1044
1055
|
};
|
|
@@ -1046,13 +1057,10 @@ function createReadTool(context) {
|
|
|
1046
1057
|
function createWriteTool(context) {
|
|
1047
1058
|
return {
|
|
1048
1059
|
name: "write",
|
|
1049
|
-
description: "
|
|
1060
|
+
description: "Create or overwrite a file in the workspace.",
|
|
1050
1061
|
parameters: WriteArgsSchema,
|
|
1051
|
-
promptSnippet: "
|
|
1052
|
-
promptGuidelines: [
|
|
1053
|
-
"Use write for new files or full file rewrites.",
|
|
1054
|
-
"For partial edits, prefer the edit tool to avoid overwriting unchanged content."
|
|
1055
|
-
],
|
|
1062
|
+
promptSnippet: "Create or overwrite files.",
|
|
1063
|
+
promptGuidelines: ["Use edit for partial changes."],
|
|
1056
1064
|
async execute(args, toolContext) {
|
|
1057
1065
|
const parsed = WriteArgsValidator.Parse(args);
|
|
1058
1066
|
const resolved = resolveCoreToolPath(context, parsed.path);
|
|
@@ -1062,69 +1070,74 @@ function createWriteTool(context) {
|
|
|
1062
1070
|
context: toolContext,
|
|
1063
1071
|
toolName: "write",
|
|
1064
1072
|
ok: true,
|
|
1065
|
-
content: {
|
|
1066
|
-
path: resolved.relativePath,
|
|
1067
|
-
bytesWritten: new TextEncoder().encode(parsed.content).byteLength
|
|
1068
|
-
}
|
|
1073
|
+
content: `Successfully wrote ${parsed.content.length} bytes to ${resolved.relativePath}.`
|
|
1069
1074
|
});
|
|
1070
1075
|
}
|
|
1071
1076
|
};
|
|
1072
1077
|
}
|
|
1078
|
+
function applyEditReplacements(current, edits) {
|
|
1079
|
+
if (edits.length === 0) {
|
|
1080
|
+
throw new Error("edits must contain at least one replacement.");
|
|
1081
|
+
}
|
|
1082
|
+
const matches = [];
|
|
1083
|
+
for (const edit of edits) {
|
|
1084
|
+
if (!edit.oldText) throw new Error("oldText must not be empty.");
|
|
1085
|
+
const start = current.indexOf(edit.oldText);
|
|
1086
|
+
if (start < 0) throw new Error(`oldText not found in file.`);
|
|
1087
|
+
const second = current.indexOf(edit.oldText, start + edit.oldText.length);
|
|
1088
|
+
if (second >= 0) {
|
|
1089
|
+
const count = current.split(edit.oldText).length - 1;
|
|
1090
|
+
throw new Error(`oldText appears ${count} times; provide more context.`);
|
|
1091
|
+
}
|
|
1092
|
+
matches.push({ start, end: start + edit.oldText.length, newText: edit.newText });
|
|
1093
|
+
}
|
|
1094
|
+
matches.sort((a, b) => a.start - b.start);
|
|
1095
|
+
for (let index = 1; index < matches.length; index += 1) {
|
|
1096
|
+
const previous = matches[index - 1];
|
|
1097
|
+
const currentMatch = matches[index];
|
|
1098
|
+
if (currentMatch.start < previous.end) {
|
|
1099
|
+
throw new Error("edits must not overlap.");
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
let content = current;
|
|
1103
|
+
for (let index = matches.length - 1; index >= 0; index -= 1) {
|
|
1104
|
+
const match = matches[index];
|
|
1105
|
+
content = content.slice(0, match.start) + match.newText + content.slice(match.end);
|
|
1106
|
+
}
|
|
1107
|
+
return { content, replacements: matches.length };
|
|
1108
|
+
}
|
|
1073
1109
|
function createEditTool(context) {
|
|
1074
1110
|
return {
|
|
1075
1111
|
name: "edit",
|
|
1076
|
-
description: "
|
|
1112
|
+
description: "Apply exact text replacements to a workspace file.",
|
|
1077
1113
|
parameters: EditArgsSchema,
|
|
1078
|
-
promptSnippet: "
|
|
1079
|
-
promptGuidelines: [
|
|
1080
|
-
"Read the file first to get the exact oldText to replace.",
|
|
1081
|
-
"oldText must be an exact match including whitespace and indentation.",
|
|
1082
|
-
"If oldText appears multiple times, set replaceAll=true or provide more surrounding context."
|
|
1083
|
-
],
|
|
1114
|
+
promptSnippet: "Apply exact text replacements.",
|
|
1115
|
+
promptGuidelines: ["Read the file first; each oldText must match exactly once."],
|
|
1084
1116
|
async execute(args, toolContext) {
|
|
1085
1117
|
const parsed = EditArgsValidator.Parse(args);
|
|
1086
|
-
if (!parsed.oldText) {
|
|
1087
|
-
return toolResult({
|
|
1088
|
-
context: toolContext,
|
|
1089
|
-
toolName: "edit",
|
|
1090
|
-
ok: false,
|
|
1091
|
-
content: null,
|
|
1092
|
-
error: "oldText must not be empty."
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
1118
|
const resolved = resolveCoreToolPath(context, parsed.path);
|
|
1096
1119
|
const current = await readFile2(resolved.absolutePath, "utf8");
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
const matches = current.split(parsed.oldText).length - 1;
|
|
1107
|
-
if (matches > 1 && !parsed.replaceAll) {
|
|
1120
|
+
let next;
|
|
1121
|
+
let replacements;
|
|
1122
|
+
try {
|
|
1123
|
+
const result = applyEditReplacements(current, parsed.edits);
|
|
1124
|
+
next = result.content;
|
|
1125
|
+
replacements = result.replacements;
|
|
1126
|
+
} catch (error) {
|
|
1108
1127
|
return toolResult({
|
|
1109
1128
|
context: toolContext,
|
|
1110
1129
|
toolName: "edit",
|
|
1111
1130
|
ok: false,
|
|
1112
1131
|
content: null,
|
|
1113
|
-
error:
|
|
1132
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1114
1133
|
});
|
|
1115
1134
|
}
|
|
1116
|
-
const replacements = parsed.replaceAll ? matches : 1;
|
|
1117
|
-
const next = parsed.replaceAll ? current.split(parsed.oldText).join(parsed.newText) : current.replace(parsed.oldText, parsed.newText);
|
|
1118
1135
|
await writeFile(resolved.absolutePath, next, "utf8");
|
|
1119
1136
|
return toolResult({
|
|
1120
1137
|
context: toolContext,
|
|
1121
1138
|
toolName: "edit",
|
|
1122
1139
|
ok: true,
|
|
1123
|
-
content: {
|
|
1124
|
-
path: resolved.relativePath,
|
|
1125
|
-
replacements,
|
|
1126
|
-
bytesWritten: new TextEncoder().encode(next).byteLength
|
|
1127
|
-
}
|
|
1140
|
+
content: `Successfully replaced ${replacements} block(s) in ${resolved.relativePath}.`
|
|
1128
1141
|
});
|
|
1129
1142
|
}
|
|
1130
1143
|
};
|
|
@@ -1132,19 +1145,15 @@ function createEditTool(context) {
|
|
|
1132
1145
|
function createBashTool(context) {
|
|
1133
1146
|
return {
|
|
1134
1147
|
name: "bash",
|
|
1135
|
-
description: "
|
|
1148
|
+
description: "Run a bash command in the workspace.",
|
|
1136
1149
|
parameters: BashArgsSchema,
|
|
1137
|
-
promptSnippet: "
|
|
1138
|
-
promptGuidelines: [
|
|
1139
|
-
"Use bash for build commands, tests, git operations, and system tools.",
|
|
1140
|
-
"Prefer dedicated tools (read/write/edit) for file operations.",
|
|
1141
|
-
"Set timeoutMs for long-running commands."
|
|
1142
|
-
],
|
|
1150
|
+
promptSnippet: "Run shell commands.",
|
|
1151
|
+
promptGuidelines: ["Use read/write/edit for file operations."],
|
|
1143
1152
|
async execute(args, toolContext, signal) {
|
|
1144
1153
|
const parsed = BashArgsValidator.Parse(args);
|
|
1145
|
-
const timeoutMs = parsed.
|
|
1146
|
-
const maxOutputBytes =
|
|
1147
|
-
const invalidTimeout = positiveNumber(timeoutMs, "
|
|
1154
|
+
const timeoutMs = parsed.timeout === void 0 ? context.bashTimeoutMs : parsed.timeout * 1e3;
|
|
1155
|
+
const maxOutputBytes = context.maxBashOutputBytes;
|
|
1156
|
+
const invalidTimeout = positiveNumber(timeoutMs, "timeout");
|
|
1148
1157
|
const invalidOutputLimit = positiveNumber(maxOutputBytes, "maxOutputBytes");
|
|
1149
1158
|
if (invalidTimeout || invalidOutputLimit) {
|
|
1150
1159
|
return toolResult({
|
|
@@ -1155,7 +1164,7 @@ function createBashTool(context) {
|
|
|
1155
1164
|
error: invalidTimeout ?? invalidOutputLimit
|
|
1156
1165
|
});
|
|
1157
1166
|
}
|
|
1158
|
-
const cwd = resolveCoreToolPath(context,
|
|
1167
|
+
const cwd = resolveCoreToolPath(context, ".");
|
|
1159
1168
|
let timedOut = false;
|
|
1160
1169
|
let aborted = false;
|
|
1161
1170
|
const proc = Bun.spawn(["bash", "-lc", parsed.command], {
|
|
@@ -1182,19 +1191,14 @@ function createBashTool(context) {
|
|
|
1182
1191
|
proc.exited
|
|
1183
1192
|
]);
|
|
1184
1193
|
const ok = exitCode === 0 && !timedOut && !aborted;
|
|
1194
|
+
const output = [stdout.text, stderr.text].filter(Boolean).join(stdout.text && stderr.text ? "\n" : "");
|
|
1195
|
+
const content = stdout.truncated || stderr.truncated ? `${output}
|
|
1196
|
+
[truncated]` : output;
|
|
1185
1197
|
return toolResult({
|
|
1186
1198
|
context: toolContext,
|
|
1187
1199
|
toolName: "bash",
|
|
1188
1200
|
ok,
|
|
1189
|
-
content
|
|
1190
|
-
command: parsed.command,
|
|
1191
|
-
cwd: cwd.relativePath,
|
|
1192
|
-
exitCode,
|
|
1193
|
-
stdout: stdout.text,
|
|
1194
|
-
stderr: stderr.text,
|
|
1195
|
-
stdoutTruncated: stdout.truncated,
|
|
1196
|
-
stderrTruncated: stderr.truncated
|
|
1197
|
-
},
|
|
1201
|
+
content,
|
|
1198
1202
|
...ok ? {} : {
|
|
1199
1203
|
error: timedOut ? `Command timed out after ${timeoutMs}ms.` : aborted ? "Command aborted." : `Command exited with ${exitCode}.`
|
|
1200
1204
|
}
|
|
@@ -1210,9 +1214,9 @@ function createCoreTools(input = {}) {
|
|
|
1210
1214
|
const context = createCoreToolContext(input);
|
|
1211
1215
|
return [
|
|
1212
1216
|
createReadTool(context),
|
|
1213
|
-
|
|
1217
|
+
createBashTool(context),
|
|
1214
1218
|
createEditTool(context),
|
|
1215
|
-
|
|
1219
|
+
createWriteTool(context)
|
|
1216
1220
|
];
|
|
1217
1221
|
}
|
|
1218
1222
|
|
|
@@ -1403,6 +1407,23 @@ function removePhaseMessage(messages, msgId) {
|
|
|
1403
1407
|
const idx = messages.findIndex((m) => m.id === msgId);
|
|
1404
1408
|
if (idx !== -1) messages.splice(idx, 1);
|
|
1405
1409
|
}
|
|
1410
|
+
function findLatestUserInputMessage(messages) {
|
|
1411
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1412
|
+
const message = messages[index];
|
|
1413
|
+
const kind = message.metadata?.kind;
|
|
1414
|
+
if (message.role === "user" && kind !== "phase_prompt" && kind !== "phase_input") {
|
|
1415
|
+
return message;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return void 0;
|
|
1419
|
+
}
|
|
1420
|
+
function moveMessageBefore(messages, messageId, targetId) {
|
|
1421
|
+
const messageIndex = messages.findIndex((message2) => message2.id === messageId);
|
|
1422
|
+
const targetIndex = messages.findIndex((message2) => message2.id === targetId);
|
|
1423
|
+
if (messageIndex === -1 || targetIndex === -1 || messageIndex < targetIndex) return;
|
|
1424
|
+
const [message] = messages.splice(messageIndex, 1);
|
|
1425
|
+
if (message) messages.splice(targetIndex, 0, message);
|
|
1426
|
+
}
|
|
1406
1427
|
function normalizePayload(payload) {
|
|
1407
1428
|
if (typeof payload === "string") {
|
|
1408
1429
|
try {
|
|
@@ -1539,6 +1560,15 @@ function createToolResultContent(result) {
|
|
|
1539
1560
|
}
|
|
1540
1561
|
];
|
|
1541
1562
|
}
|
|
1563
|
+
function createRouteToolResultContent(toolCall) {
|
|
1564
|
+
return [
|
|
1565
|
+
{
|
|
1566
|
+
type: "tool_result",
|
|
1567
|
+
toolUseId: toolCall.id,
|
|
1568
|
+
content: '{"ok": true}'
|
|
1569
|
+
}
|
|
1570
|
+
];
|
|
1571
|
+
}
|
|
1542
1572
|
async function executePhaseWithModel(ctx) {
|
|
1543
1573
|
const executableToolNames = new Set(
|
|
1544
1574
|
ctx.context.tools.filter((tool) => tool.name !== PhaseRouteTool).map((tool) => tool.name)
|
|
@@ -1561,6 +1591,13 @@ async function executePhaseWithModel(ctx) {
|
|
|
1561
1591
|
phase: ctx.phase.name,
|
|
1562
1592
|
toolCalls: collected.toolCalls
|
|
1563
1593
|
};
|
|
1594
|
+
for (const toolCall of collected.toolCalls) {
|
|
1595
|
+
if (toolCall.name !== PhaseRouteTool) continue;
|
|
1596
|
+
const messageId = ctx.messageManager.start("tool", createRouteToolResultContent(toolCall), {
|
|
1597
|
+
phase: ctx.phase.id
|
|
1598
|
+
});
|
|
1599
|
+
await ctx.messageManager.end(messageId);
|
|
1600
|
+
}
|
|
1564
1601
|
const executableToolCalls = collected.toolCalls.filter(
|
|
1565
1602
|
(toolCall) => executableToolNames.has(toolCall.name)
|
|
1566
1603
|
);
|
|
@@ -1603,6 +1640,7 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1603
1640
|
}
|
|
1604
1641
|
let previousPayload = resumingSuspendedRun ? state.continuation?.previousPayload : void 0;
|
|
1605
1642
|
let previousPhaseMsgId = resumingSuspendedRun ? state.continuation?.previousPhaseMessageId : void 0;
|
|
1643
|
+
let previousPhaseInputMsgId;
|
|
1606
1644
|
let previousResults = resumingSuspendedRun ? state.continuation?.previousResults?.map((result) => ({ ...result })) ?? [] : [];
|
|
1607
1645
|
let pendingInstruction = resumingSuspendedRun ? state.continuation?.pendingInstruction : void 0;
|
|
1608
1646
|
while (currentPhaseId) {
|
|
@@ -1613,7 +1651,9 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1613
1651
|
const abortResult = LoopGuard.checkAbort(config.signal);
|
|
1614
1652
|
if (abortResult.stopReason !== "none") {
|
|
1615
1653
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1654
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1616
1655
|
previousPhaseMsgId = void 0;
|
|
1656
|
+
previousPhaseInputMsgId = void 0;
|
|
1617
1657
|
return completeRun(config, state, createOutcome.aborted());
|
|
1618
1658
|
}
|
|
1619
1659
|
state.metrics.iterations++;
|
|
@@ -1672,13 +1712,17 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1672
1712
|
if (extBefore.abort) {
|
|
1673
1713
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1674
1714
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1715
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1675
1716
|
previousPhaseMsgId = void 0;
|
|
1717
|
+
previousPhaseInputMsgId = void 0;
|
|
1676
1718
|
return completeRun(config, state, extBefore.abort);
|
|
1677
1719
|
}
|
|
1678
1720
|
if (extBefore.skip) {
|
|
1679
1721
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1680
1722
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1723
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1681
1724
|
previousPhaseMsgId = void 0;
|
|
1725
|
+
previousPhaseInputMsgId = void 0;
|
|
1682
1726
|
if (extBefore.skip.route === "stop") {
|
|
1683
1727
|
return completeRun(config, state, {
|
|
1684
1728
|
id: "skip",
|
|
@@ -1696,13 +1740,32 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1696
1740
|
if (enteringNewPhase || !phaseMessageIsPresent) {
|
|
1697
1741
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1698
1742
|
removePhaseMessage(phaseContext.messages, previousPhaseMsgId);
|
|
1743
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1744
|
+
removePhaseMessage(phaseContext.messages, previousPhaseInputMsgId);
|
|
1699
1745
|
previousPhaseMsgId = void 0;
|
|
1746
|
+
previousPhaseInputMsgId = void 0;
|
|
1747
|
+
const latestUserInputMessage = findLatestUserInputMessage(config.context.messages);
|
|
1748
|
+
const lastMessageBeforePhase = config.context.messages.at(-1);
|
|
1700
1749
|
previousPhaseMsgId = injectPhaseContent(
|
|
1701
1750
|
phase,
|
|
1702
1751
|
{ results: previousResults, instruction: pendingInstruction },
|
|
1703
1752
|
config.context.messages,
|
|
1704
1753
|
phaseContext.messages
|
|
1705
1754
|
);
|
|
1755
|
+
if (previousPhaseMsgId) {
|
|
1756
|
+
if (lastMessageBeforePhase?.role === "user") {
|
|
1757
|
+
moveMessageBefore(config.context.messages, previousPhaseMsgId, lastMessageBeforePhase.id);
|
|
1758
|
+
moveMessageBefore(phaseContext.messages, previousPhaseMsgId, lastMessageBeforePhase.id);
|
|
1759
|
+
} else if (latestUserInputMessage) {
|
|
1760
|
+
const phaseInputMessage = createMessage("user", latestUserInputMessage.content, {
|
|
1761
|
+
kind: "phase_input",
|
|
1762
|
+
phase: phase.id
|
|
1763
|
+
});
|
|
1764
|
+
config.context.messages.push(phaseInputMessage);
|
|
1765
|
+
phaseContext.messages.push(phaseInputMessage);
|
|
1766
|
+
previousPhaseInputMsgId = phaseInputMessage.id;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1706
1769
|
previousResults = [];
|
|
1707
1770
|
pendingInstruction = void 0;
|
|
1708
1771
|
}
|
|
@@ -1720,7 +1783,9 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1720
1783
|
if (extAfter.abort) {
|
|
1721
1784
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1722
1785
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1786
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1723
1787
|
previousPhaseMsgId = void 0;
|
|
1788
|
+
previousPhaseInputMsgId = void 0;
|
|
1724
1789
|
return completeRun(config, state, extAfter.abort);
|
|
1725
1790
|
}
|
|
1726
1791
|
if (extAfter.retry && (phase.run || phase.factory)) {
|
|
@@ -1740,7 +1805,9 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1740
1805
|
if (preAbort.stopReason !== "none") {
|
|
1741
1806
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1742
1807
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1808
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1743
1809
|
previousPhaseMsgId = void 0;
|
|
1810
|
+
previousPhaseInputMsgId = void 0;
|
|
1744
1811
|
return completeRun(config, state, createOutcome.aborted());
|
|
1745
1812
|
}
|
|
1746
1813
|
const requestedAt = createTimestamp();
|
|
@@ -1768,7 +1835,9 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1768
1835
|
if (abortResult2.stopReason !== "none") {
|
|
1769
1836
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1770
1837
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1838
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1771
1839
|
previousPhaseMsgId = void 0;
|
|
1840
|
+
previousPhaseInputMsgId = void 0;
|
|
1772
1841
|
return completeRun(config, state, createOutcome.aborted());
|
|
1773
1842
|
}
|
|
1774
1843
|
for (const message of userMessages) {
|
|
@@ -1779,7 +1848,9 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1779
1848
|
}
|
|
1780
1849
|
config.emit?.({ type: "phase_end", phase: currentPhaseId, ts: createTimestamp() });
|
|
1781
1850
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1851
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1782
1852
|
previousPhaseMsgId = void 0;
|
|
1853
|
+
previousPhaseInputMsgId = void 0;
|
|
1783
1854
|
return completeRun(config, state, createOutcome.default(output, config.context.messages));
|
|
1784
1855
|
}
|
|
1785
1856
|
if (output.route === "continue") {
|
|
@@ -1822,7 +1893,9 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1822
1893
|
const entryPhaseId = phase.target ?? registry.entryPhaseId;
|
|
1823
1894
|
if (entryPhaseId === "stop") {
|
|
1824
1895
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1896
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1825
1897
|
previousPhaseMsgId = void 0;
|
|
1898
|
+
previousPhaseInputMsgId = void 0;
|
|
1826
1899
|
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
1827
1900
|
}
|
|
1828
1901
|
currentPhaseId = entryPhaseId;
|
|
@@ -1839,13 +1912,17 @@ async function runPhaseLoop(config, state, registry) {
|
|
|
1839
1912
|
}
|
|
1840
1913
|
if (nextRoute === "stop") {
|
|
1841
1914
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1915
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1842
1916
|
previousPhaseMsgId = void 0;
|
|
1917
|
+
previousPhaseInputMsgId = void 0;
|
|
1843
1918
|
return completeRun(config, state, createTerminalOutcome(phase, output, config.context.messages));
|
|
1844
1919
|
}
|
|
1845
1920
|
const targetPhaseId = nextRoute;
|
|
1846
1921
|
if (!registry.phases.has(targetPhaseId)) {
|
|
1847
1922
|
removePhaseMessage(config.context.messages, previousPhaseMsgId);
|
|
1923
|
+
removePhaseMessage(config.context.messages, previousPhaseInputMsgId);
|
|
1848
1924
|
previousPhaseMsgId = void 0;
|
|
1925
|
+
previousPhaseInputMsgId = void 0;
|
|
1849
1926
|
return completeRun(config, state, createOutcome.phaseNotFound(output));
|
|
1850
1927
|
}
|
|
1851
1928
|
state.metrics.phaseTransitions.push({
|
|
@@ -1961,24 +2038,31 @@ async function executePhase(ctx) {
|
|
|
1961
2038
|
return executePhaseWithModel(ctx);
|
|
1962
2039
|
}
|
|
1963
2040
|
async function executeToolCall(input) {
|
|
2041
|
+
let result;
|
|
1964
2042
|
if (input.config.runtime?.tools) {
|
|
1965
|
-
|
|
2043
|
+
result = await input.config.runtime.tools({
|
|
1966
2044
|
config: input.config,
|
|
1967
2045
|
toolCall: input.toolCall
|
|
1968
2046
|
});
|
|
2047
|
+
} else {
|
|
2048
|
+
const toolContext = {
|
|
2049
|
+
skills: input.config.context.skills,
|
|
2050
|
+
toolCallId: input.toolCall.id
|
|
2051
|
+
};
|
|
2052
|
+
result = await executeRuntimeToolCall({
|
|
2053
|
+
tools: input.tools,
|
|
2054
|
+
toolCall: input.toolCall,
|
|
2055
|
+
toolContext,
|
|
2056
|
+
beforeToolCall: input.config.beforeToolCall,
|
|
2057
|
+
afterToolCall: input.config.afterToolCall,
|
|
2058
|
+
signal: input.config.signal
|
|
2059
|
+
});
|
|
1969
2060
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
toolCallId: input.toolCall.id
|
|
2061
|
+
return {
|
|
2062
|
+
...result,
|
|
2063
|
+
toolCallId: input.toolCall.id,
|
|
2064
|
+
toolName: input.toolCall.name
|
|
1973
2065
|
};
|
|
1974
|
-
return executeRuntimeToolCall({
|
|
1975
|
-
tools: input.tools,
|
|
1976
|
-
toolCall: input.toolCall,
|
|
1977
|
-
toolContext,
|
|
1978
|
-
beforeToolCall: input.config.beforeToolCall,
|
|
1979
|
-
afterToolCall: input.config.afterToolCall,
|
|
1980
|
-
signal: input.config.signal
|
|
1981
|
-
});
|
|
1982
2066
|
}
|
|
1983
2067
|
function createPhaseExecution(config, state, allTools, phase, messageManager, toolExecutionManager, registry) {
|
|
1984
2068
|
return {
|
|
@@ -4689,6 +4773,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4689
4773
|
runningRuns = 0;
|
|
4690
4774
|
pumping = false;
|
|
4691
4775
|
nextWorkerId = 0;
|
|
4776
|
+
leaseRecoveryTimer;
|
|
4692
4777
|
stopped = false;
|
|
4693
4778
|
constructor(options) {
|
|
4694
4779
|
this.stateStore = options.stateStore;
|
|
@@ -4724,6 +4809,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4724
4809
|
activeRuntime = runtime;
|
|
4725
4810
|
try {
|
|
4726
4811
|
await runtime.recover();
|
|
4812
|
+
runtime.startLeaseRecovery();
|
|
4727
4813
|
return runtime;
|
|
4728
4814
|
} catch (error) {
|
|
4729
4815
|
await runtime.stop().catch(() => void 0);
|
|
@@ -4766,6 +4852,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4766
4852
|
async stop() {
|
|
4767
4853
|
if (this.stopped) return;
|
|
4768
4854
|
this.stopped = true;
|
|
4855
|
+
if (this.leaseRecoveryTimer) {
|
|
4856
|
+
clearInterval(this.leaseRecoveryTimer);
|
|
4857
|
+
this.leaseRecoveryTimer = void 0;
|
|
4858
|
+
}
|
|
4769
4859
|
let suspendedAgents;
|
|
4770
4860
|
try {
|
|
4771
4861
|
suspendedAgents = new Set(
|
|
@@ -4943,13 +5033,26 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
4943
5033
|
return this.stateStore.listEvents(cursor);
|
|
4944
5034
|
}
|
|
4945
5035
|
async recover() {
|
|
4946
|
-
await this.
|
|
5036
|
+
await this.recoverExpiredLeases();
|
|
4947
5037
|
const agents = await this.stateStore.listAgents();
|
|
4948
5038
|
for (const record of agents) {
|
|
4949
5039
|
if (record.state === "paused") this.pausedAgents.add(record.id);
|
|
4950
5040
|
}
|
|
4951
5041
|
this.publishEvents();
|
|
4952
5042
|
}
|
|
5043
|
+
startLeaseRecovery() {
|
|
5044
|
+
this.leaseRecoveryTimer = setInterval(() => {
|
|
5045
|
+
void this.recoverExpiredLeases().catch(() => void 0);
|
|
5046
|
+
}, this.leaseRenewalIntervalMs);
|
|
5047
|
+
}
|
|
5048
|
+
async recoverExpiredLeases() {
|
|
5049
|
+
const recovered = await this.stateStore.recoverExpiredLeases();
|
|
5050
|
+
for (const run of recovered) {
|
|
5051
|
+
this.notifyRun(run);
|
|
5052
|
+
if (this.bindings.has(run.agentId)) this.scheduleRun(run.agentId, run.id);
|
|
5053
|
+
}
|
|
5054
|
+
if (recovered.length > 0) this.publishEvents();
|
|
5055
|
+
}
|
|
4953
5056
|
async scheduleQueuedRuns(agentId) {
|
|
4954
5057
|
if (!this.bindings.has(agentId)) return;
|
|
4955
5058
|
const runs = await this.stateStore.listRuns({ agentId, states: ["queued"] });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowan-agent/agent",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build": "tsup && bun scripts/check-public-interface.ts"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@rowan-agent/models": "0.6.
|
|
26
|
+
"@rowan-agent/models": "0.6.2",
|
|
27
27
|
"jiti": "2.7.0",
|
|
28
28
|
"typebox": "^1.0.0"
|
|
29
29
|
},
|