@itmetsam/samurai-framework 0.22.6 → 0.22.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/dist/agent/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AgentOptions, AgentSession, PlanOptions, PlanSession } from "./types.js";
|
|
2
2
|
export { DEFAULT_LIMITS, resolveLimits, type AgentLimits } from "./limits.js";
|
|
3
3
|
export { assertModelObject, type AgentModel } from "./model.js";
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
4
|
+
export { splitFrontMatter } from "./plan/front-matter.js";
|
|
5
|
+
export { formatPlan, MAX_PLAN_BYTES, parsePlan, type ParsedPlan, type PlanDocument, type PlanQuestion, } from "./plan/plan-file.js";
|
|
6
|
+
export { defaultPlanFile, listPlans } from "./plan/plan-path.js";
|
|
6
7
|
export { readPlan } from "./plan/read-plan.js";
|
|
7
8
|
export { defaultModelSpec, ModelSetupError, modelFromSpec, } from "./providers.js";
|
|
8
9
|
export type { AgentEndReason, AgentEvent, AgentOptions, AgentResult, AgentSession, PlanOptions, PlanResult, PlanSession, } from "./types.js";
|
package/dist/agent/index.js
CHANGED
|
@@ -2,8 +2,9 @@ import { startPlan } from "./plan/run.js";
|
|
|
2
2
|
import { startAgent } from "./run.js";
|
|
3
3
|
export { DEFAULT_LIMITS, resolveLimits } from "./limits.js";
|
|
4
4
|
export { assertModelObject } from "./model.js";
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
5
|
+
export { splitFrontMatter } from "./plan/front-matter.js";
|
|
6
|
+
export { formatPlan, MAX_PLAN_BYTES, parsePlan, } from "./plan/plan-file.js";
|
|
7
|
+
export { defaultPlanFile, listPlans } from "./plan/plan-path.js";
|
|
7
8
|
export { readPlan } from "./plan/read-plan.js";
|
|
8
9
|
export { defaultModelSpec, ModelSetupError, modelFromSpec, } from "./providers.js";
|
|
9
10
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** A plan file may not be larger than this (bytes), for the planner and for `generate --plan` */
|
|
2
2
|
export declare const MAX_PLAN_BYTES: number;
|
|
3
|
+
/** One step of a plan, with what to check after it */
|
|
3
4
|
interface PlanStep {
|
|
4
5
|
text: string;
|
|
5
6
|
expect?: string;
|
|
@@ -14,12 +15,35 @@ interface ParsedPlan {
|
|
|
14
15
|
notDone: string[];
|
|
15
16
|
/** Every open question, answered or not */
|
|
16
17
|
questions: string[];
|
|
18
|
+
/** Every open question with its answer, if it has one; what `formatPlan` takes back */
|
|
19
|
+
questionList: PlanQuestion[];
|
|
17
20
|
/** The questions that have an `Answer:` line, with the answer; a question with an empty answer is not here */
|
|
18
21
|
answers: {
|
|
19
22
|
question: string;
|
|
20
23
|
answer: string;
|
|
21
24
|
}[];
|
|
22
25
|
}
|
|
26
|
+
/** A plan as `formatPlan` writes it and `parsePlan` reads it back */
|
|
27
|
+
interface PlanDocument {
|
|
28
|
+
title: string;
|
|
29
|
+
goal: string;
|
|
30
|
+
start: string;
|
|
31
|
+
steps: PlanStep[];
|
|
32
|
+
seen: string[];
|
|
33
|
+
notDone: string[];
|
|
34
|
+
/** An answered question is written with an indented `Answer:` line below it */
|
|
35
|
+
questions: PlanQuestion[];
|
|
36
|
+
}
|
|
37
|
+
interface PlanQuestion {
|
|
38
|
+
text: string;
|
|
39
|
+
answer?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Writes a plan as Markdown, the format the planner writes and `parsePlan` reads. Every text is masked and made one
|
|
43
|
+
* line. `front` is a raw front matter block (ending with a new line, as `splitFrontMatter` returns it) placed first.
|
|
44
|
+
* Throws when the result is larger than `MAX_PLAN_BYTES`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatPlan(plan: PlanDocument, front?: string): string;
|
|
23
47
|
/**
|
|
24
48
|
* A plan file being written. The whole file is written again after every call, so it is valid Markdown with its
|
|
25
49
|
* four headings whenever the run stops. Texts are masked and made one line long.
|
|
@@ -52,7 +76,7 @@ declare class PlanFile {
|
|
|
52
76
|
/** Writes the first version of the file */
|
|
53
77
|
create(): void;
|
|
54
78
|
}
|
|
55
|
-
export type { PlanFile, ParsedPlan };
|
|
79
|
+
export type { PlanFile, ParsedPlan, PlanDocument, PlanQuestion };
|
|
56
80
|
/** Starts a plan file: the headings are written at once. An existing file is an error unless `force` is set */
|
|
57
81
|
export declare function createPlan(input: {
|
|
58
82
|
goal: string;
|
|
@@ -16,6 +16,48 @@ function bullets(items) {
|
|
|
16
16
|
? items.map((item) => `- ${item}`).join("\n")
|
|
17
17
|
: `- ${NONE}`;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Writes a plan as Markdown, the format the planner writes and `parsePlan` reads. Every text is masked and made one
|
|
21
|
+
* line. `front` is a raw front matter block (ending with a new line, as `splitFrontMatter` returns it) placed first.
|
|
22
|
+
* Throws when the result is larger than `MAX_PLAN_BYTES`.
|
|
23
|
+
*/
|
|
24
|
+
export function formatPlan(plan, front = "") {
|
|
25
|
+
const steps = plan.steps.map((step, i) => {
|
|
26
|
+
const expect = step.expect === undefined ? "" : oneLine(step.expect);
|
|
27
|
+
return `${i + 1}. ${oneLine(step.text)}${expect !== "" ? `\n Expect: ${expect}` : ""}`;
|
|
28
|
+
});
|
|
29
|
+
const questions = plan.questions.map((q) => {
|
|
30
|
+
const answer = q.answer === undefined ? "" : oneLine(q.answer);
|
|
31
|
+
return `- ${oneLine(q.text)}${answer !== "" ? `\n Answer: ${answer}` : ""}`;
|
|
32
|
+
});
|
|
33
|
+
const text = [
|
|
34
|
+
...(front === "" ? [] : [front.replace(/\n$/, "")]),
|
|
35
|
+
`# Plan: ${oneLine(plan.title)}`,
|
|
36
|
+
"",
|
|
37
|
+
`Goal: ${oneLine(plan.goal)}`,
|
|
38
|
+
`Start: ${oneLine(plan.start)}`,
|
|
39
|
+
"",
|
|
40
|
+
"## Steps",
|
|
41
|
+
"",
|
|
42
|
+
steps.length > 0 ? steps.join("\n") : `- ${NONE}`,
|
|
43
|
+
"",
|
|
44
|
+
"## Seen on the page",
|
|
45
|
+
"",
|
|
46
|
+
bullets(plan.seen.map(oneLine)),
|
|
47
|
+
"",
|
|
48
|
+
"## Not done",
|
|
49
|
+
"",
|
|
50
|
+
bullets(plan.notDone.map(oneLine)),
|
|
51
|
+
"",
|
|
52
|
+
"## Open questions",
|
|
53
|
+
"",
|
|
54
|
+
questions.length > 0 ? questions.join("\n") : `- ${NONE}`,
|
|
55
|
+
"",
|
|
56
|
+
].join("\n");
|
|
57
|
+
if (Buffer.byteLength(text) > MAX_PLAN_BYTES)
|
|
58
|
+
throw new Error(`the plan file is full (${MAX_PLAN_BYTES / 1024} KB); finish with what it holds, or shorten the next entry`);
|
|
59
|
+
return text;
|
|
60
|
+
}
|
|
19
61
|
/**
|
|
20
62
|
* A plan file being written. The whole file is written again after every call, so it is valid Markdown with its
|
|
21
63
|
* four headings whenever the run stops. Texts are masked and made one line long.
|
|
@@ -48,37 +90,19 @@ class PlanFile {
|
|
|
48
90
|
}
|
|
49
91
|
}
|
|
50
92
|
render(front) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"",
|
|
61
|
-
steps.length > 0 ? steps.join("\n") : `- ${NONE}`,
|
|
62
|
-
"",
|
|
63
|
-
"## Seen on the page",
|
|
64
|
-
"",
|
|
65
|
-
bullets(this.seenItems),
|
|
66
|
-
"",
|
|
67
|
-
"## Not done",
|
|
68
|
-
"",
|
|
69
|
-
bullets(this.notDoneItems),
|
|
70
|
-
"",
|
|
71
|
-
"## Open questions",
|
|
72
|
-
"",
|
|
73
|
-
bullets(this.questionItems),
|
|
74
|
-
"",
|
|
75
|
-
].join("\n");
|
|
93
|
+
return formatPlan({
|
|
94
|
+
title: this.title,
|
|
95
|
+
goal: this.goal,
|
|
96
|
+
start: this.start,
|
|
97
|
+
steps: this.steps,
|
|
98
|
+
seen: this.seenItems,
|
|
99
|
+
notDone: this.notDoneItems,
|
|
100
|
+
questions: this.questionItems.map((text) => ({ text })),
|
|
101
|
+
}, front);
|
|
76
102
|
}
|
|
77
103
|
/** Writes the file; refuses, and keeps the file as it was, when it would pass the size limit */
|
|
78
104
|
save() {
|
|
79
105
|
const text = this.render(this.frontMatter());
|
|
80
|
-
if (Buffer.byteLength(text) > MAX_PLAN_BYTES)
|
|
81
|
-
throw new Error(`the plan file is full (${MAX_PLAN_BYTES / 1024} KB); finish with what it holds, or shorten the next entry`);
|
|
82
106
|
mkdirSync(path.dirname(this.file), { recursive: true });
|
|
83
107
|
writeFileSync(this.file, text);
|
|
84
108
|
}
|
|
@@ -167,6 +191,7 @@ export function parsePlan(text) {
|
|
|
167
191
|
seen: [],
|
|
168
192
|
notDone: [],
|
|
169
193
|
questions: [],
|
|
194
|
+
questionList: [],
|
|
170
195
|
answers: [],
|
|
171
196
|
};
|
|
172
197
|
let section;
|
|
@@ -215,13 +240,18 @@ export function parsePlan(text) {
|
|
|
215
240
|
const asked = plan.questions[plan.questions.length - 1];
|
|
216
241
|
if (answer) {
|
|
217
242
|
const text = answer[1].trim();
|
|
218
|
-
if (asked !== undefined && text !== "")
|
|
243
|
+
if (asked !== undefined && text !== "") {
|
|
219
244
|
plan.answers.push({ question: asked, answer: text });
|
|
245
|
+
plan.questionList[plan.questionList.length - 1].answer = text;
|
|
246
|
+
}
|
|
220
247
|
continue;
|
|
221
248
|
}
|
|
222
249
|
const item = BULLET.exec(line)?.[1]?.trim();
|
|
223
|
-
if (item !== undefined && item !== NONE)
|
|
250
|
+
if (item !== undefined && item !== NONE) {
|
|
224
251
|
plan[section].push(item);
|
|
252
|
+
if (section === "questions")
|
|
253
|
+
plan.questionList.push({ text: item });
|
|
254
|
+
}
|
|
225
255
|
}
|
|
226
256
|
if (!hasSteps)
|
|
227
257
|
throw new Error('parsePlan(): the plan has no "## Steps" heading');
|
|
@@ -6,3 +6,8 @@ export declare function slugOf(goal: string): string;
|
|
|
6
6
|
* created. Returns the path relative to the project folder, with `/` separators.
|
|
7
7
|
*/
|
|
8
8
|
export declare function defaultPlanFile(goal: string): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* The plan files (`*.plan.md`) below the config's `plansDir`, searched in all sub-folders: paths relative to the
|
|
11
|
+
* project folder, with `/` separators, sorted. A missing folder gives an empty list.
|
|
12
|
+
*/
|
|
13
|
+
export declare function listPlans(): Promise<string[]>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { loadRawConfig, projectDir } from "../../config/config.js";
|
|
4
4
|
import { DEFAULT_PLANS_DIR } from "../../config/defaults.js";
|
|
@@ -44,3 +44,19 @@ export async function defaultPlanFile(goal) {
|
|
|
44
44
|
.join("/");
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The plan files (`*.plan.md`) below the config's `plansDir`, searched in all sub-folders: paths relative to the
|
|
49
|
+
* project folder, with `/` separators, sorted. A missing folder gives an empty list.
|
|
50
|
+
*/
|
|
51
|
+
export async function listPlans() {
|
|
52
|
+
const folder = path.resolve(projectDir(), await plansDirOf());
|
|
53
|
+
if (!existsSync(folder))
|
|
54
|
+
return [];
|
|
55
|
+
return readdirSync(folder, { recursive: true, encoding: "utf8" })
|
|
56
|
+
.filter((name) => name.endsWith(".plan.md"))
|
|
57
|
+
.map((name) => path
|
|
58
|
+
.relative(projectDir(), path.join(folder, name))
|
|
59
|
+
.split(path.sep)
|
|
60
|
+
.join("/"))
|
|
61
|
+
.sort();
|
|
62
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itmetsam/samurai-framework",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.7",
|
|
4
4
|
"description": "Browser test framework over WebDriver BiDi: auto-waiting locators, environments and secrets, a recorder, and a command line.",
|
|
5
5
|
"main": "dist/api.js",
|
|
6
6
|
"exports": {
|