@wrenautomation/raven 0.2.0 → 0.2.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 +3 -2
- package/dist/model.d.ts +3 -1
- package/dist/model.js +15 -2
- package/dist/plans/index.js +9 -6
- package/dist/review.js +2 -0
- package/dist/types.d.ts +23 -1
- package/dist/types.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Three calls, one record each:
|
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| `plan` | an ask (text, kind, what is known), proposals from people or other models, yield rows from earlier runs | a `Plan`: typed queries, each with `why`, `from` (ai or human) and `by` (model id, plan file or person), the sources it may use, the budget, and a snapshot of what raven was given |
|
|
14
14
|
| `triage` | hits as a source shows them for free (title, channel, length, date, snippet, which sources returned it) | read or skip per hit, with why; the read budget caps it |
|
|
15
|
-
| `review` | what a round came to (counts, found lines, notes) | the edge: `stop`, `again` (a new round from plan) or `back` (rerun one step), with why |
|
|
15
|
+
| `review` | what a round came to (counts, found lines, the extractor's verdict and gap per read, notes) | the edge: `stop`, `again` (a new round from plan) or `back` (rerun one step), with why |
|
|
16
16
|
|
|
17
17
|
Ask kinds: `web`, `video`, `channel`, `person`, `site`. Each has a default plan file
|
|
18
18
|
(prompts, sources, budget). Copy one, tune it, keep it beside your ledger: that is where a run
|
|
@@ -23,7 +23,8 @@ learns. Raven never loops; the runner takes the edge.
|
|
|
23
23
|
Needs Node 24+. The model is a command that reads the prompt on stdin and prints the answer.
|
|
24
24
|
Default `claude -p --output-format json`, so Claude Code is the model in any terminal and its
|
|
25
25
|
envelope gives the cost per call; `RAVEN_MODEL` or `--model` changes it (plain text out is fine);
|
|
26
|
-
`--no-model` plans from your own queries only.
|
|
26
|
+
`--no-model` plans from your own queries only. A command that says nothing for 3 minutes is
|
|
27
|
+
killed and the call fails with why.
|
|
27
28
|
|
|
28
29
|
```sh
|
|
29
30
|
npm i @wrenautomation/raven
|
package/dist/model.d.ts
CHANGED
|
@@ -31,7 +31,9 @@ export declare function readOutput(stdout: string): {
|
|
|
31
31
|
/**
|
|
32
32
|
* A model that is a shell command: the prompt on stdin, the answer on stdout. When stdout is
|
|
33
33
|
* Claude Code's JSON envelope (`claude -p --output-format json`) the usage comes with it.
|
|
34
|
+
* A command that says nothing for `timeoutMs` (3 minutes) is killed and the ask fails with
|
|
35
|
+
* why, so a hung model never hangs the run.
|
|
34
36
|
*/
|
|
35
|
-
export declare function commandModel(command: string, id?: string): Model;
|
|
37
|
+
export declare function commandModel(command: string, id?: string, timeoutMs?: number): Model;
|
|
36
38
|
/** A model that answers from a fixed list, in order: tests and dry runs. */
|
|
37
39
|
export declare function scriptedModel(answers: unknown[], id?: string): Model;
|
package/dist/model.js
CHANGED
|
@@ -72,17 +72,30 @@ export function readOutput(stdout) {
|
|
|
72
72
|
/**
|
|
73
73
|
* A model that is a shell command: the prompt on stdin, the answer on stdout. When stdout is
|
|
74
74
|
* Claude Code's JSON envelope (`claude -p --output-format json`) the usage comes with it.
|
|
75
|
+
* A command that says nothing for `timeoutMs` (3 minutes) is killed and the ask fails with
|
|
76
|
+
* why, so a hung model never hangs the run.
|
|
75
77
|
*/
|
|
76
|
-
export function commandModel(command, id = command) {
|
|
78
|
+
export function commandModel(command, id = command, timeoutMs = 180_000) {
|
|
77
79
|
return {
|
|
78
80
|
id,
|
|
79
81
|
ask: (prompt, shape) => new Promise((resolve, reject) => {
|
|
80
82
|
const child = spawn(command, { shell: true, stdio: ["pipe", "pipe", "inherit"] });
|
|
81
83
|
const out = [];
|
|
84
|
+
let timedOut = false;
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
timedOut = true;
|
|
87
|
+
child.kill();
|
|
88
|
+
}, timeoutMs);
|
|
82
89
|
child.stdout.on("data", (b) => out.push(b));
|
|
83
|
-
child.on("error",
|
|
90
|
+
child.on("error", (e) => {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
reject(e);
|
|
93
|
+
});
|
|
84
94
|
child.on("close", (code) => {
|
|
95
|
+
clearTimeout(timer);
|
|
85
96
|
const stdout = Buffer.concat(out).toString("utf8");
|
|
97
|
+
if (timedOut)
|
|
98
|
+
return reject(new Error(`${command}: no answer in ${Math.round(timeoutMs / 1000)}s`));
|
|
86
99
|
if (code !== 0)
|
|
87
100
|
return reject(new Error(`${command} exited ${code}: ${stdout.slice(0, 400)}`));
|
|
88
101
|
try {
|
package/dist/plans/index.js
CHANGED
|
@@ -25,7 +25,8 @@ ${SHAPE_EDGE}
|
|
|
25
25
|
|
|
26
26
|
Rules:
|
|
27
27
|
- "stop" when the found lines answer the ask, or when another round would likely add nothing.
|
|
28
|
-
- "again" when the ask is still open and new queries could find more.
|
|
28
|
+
- "again" when the ask is still open and new queries could find more. The gaps say what is missing; say what to look for next in "why".
|
|
29
|
+
- "again" too when the reads were mostly "thin" or "off": the queries found the wrong pages, so plan different ones.
|
|
29
30
|
- "back" when one step of this round failed for a reason a rerun fixes (a source was down, reads timed out). Name the step in "to".
|
|
30
31
|
- "why" is one short line a person reads to agree or not.
|
|
31
32
|
|
|
@@ -33,12 +34,14 @@ Ask: {ask}
|
|
|
33
34
|
Round {round} of at most {rounds}. Hits {hits}, reads {reads}, findings proposed {proposed}, kept {kept}.
|
|
34
35
|
Found:
|
|
35
36
|
{found}
|
|
37
|
+
Read (quality: rich, thin, off or empty; then the gap, what the ask still lacks):
|
|
38
|
+
{verdicts}
|
|
36
39
|
Notes:
|
|
37
40
|
{notes}`;
|
|
38
41
|
const web = {
|
|
39
42
|
id: "raven/web",
|
|
40
43
|
kind: "web",
|
|
41
|
-
version:
|
|
44
|
+
version: 2,
|
|
42
45
|
propose: `You plan web searches for one research ask.
|
|
43
46
|
|
|
44
47
|
${SHAPE_QUERIES}
|
|
@@ -69,7 +72,7 @@ Hits:
|
|
|
69
72
|
const video = {
|
|
70
73
|
id: "raven/video",
|
|
71
74
|
kind: "video",
|
|
72
|
-
version:
|
|
75
|
+
version: 2,
|
|
73
76
|
propose: `You plan video searches for one research ask: talks, walkthroughs, interviews and reviews on YouTube and podcast feeds.
|
|
74
77
|
|
|
75
78
|
${SHAPE_QUERIES}
|
|
@@ -100,7 +103,7 @@ Hits:
|
|
|
100
103
|
const channel = {
|
|
101
104
|
id: "raven/channel",
|
|
102
105
|
kind: "channel",
|
|
103
|
-
version:
|
|
106
|
+
version: 2,
|
|
104
107
|
propose: `You plan searches that find the people and channels worth following for one research ask: creators, newsletters, podcasts, forums.
|
|
105
108
|
|
|
106
109
|
${SHAPE_QUERIES}
|
|
@@ -129,7 +132,7 @@ Hits:
|
|
|
129
132
|
const person = {
|
|
130
133
|
id: "raven/person",
|
|
131
134
|
kind: "person",
|
|
132
|
-
version:
|
|
135
|
+
version: 2,
|
|
133
136
|
propose: `You plan searches about one person or company for one research ask: what they do, what they said, what others say about them.
|
|
134
137
|
|
|
135
138
|
${SHAPE_QUERIES}
|
|
@@ -160,7 +163,7 @@ Hits:
|
|
|
160
163
|
const site = {
|
|
161
164
|
id: "raven/site",
|
|
162
165
|
kind: "site",
|
|
163
|
-
version:
|
|
166
|
+
version: 2,
|
|
164
167
|
propose: `You plan searches inside one website for one research ask. Every query runs on {site} only.
|
|
165
168
|
|
|
166
169
|
${SHAPE_QUERIES}
|
package/dist/review.js
CHANGED
|
@@ -12,6 +12,7 @@ const EdgeAnswer = z.object({
|
|
|
12
12
|
to: Step.optional(),
|
|
13
13
|
why: z.string(),
|
|
14
14
|
});
|
|
15
|
+
const verdictLine = (v) => `${v.url}: ${v.quality}${v.gap ? `. ${v.gap}` : ""}`;
|
|
15
16
|
export function reviewPrompt(round, planFile) {
|
|
16
17
|
return fill(planFile.review, {
|
|
17
18
|
ask: round.ask.text,
|
|
@@ -23,6 +24,7 @@ export function reviewPrompt(round, planFile) {
|
|
|
23
24
|
kept: String(round.kept),
|
|
24
25
|
found: lines(round.found),
|
|
25
26
|
notes: lines(round.notes, "(none)"),
|
|
27
|
+
verdicts: lines(round.verdicts.map(verdictLine), "(none)"),
|
|
26
28
|
});
|
|
27
29
|
}
|
|
28
30
|
export async function review(round, model, options = {}) {
|
package/dist/types.d.ts
CHANGED
|
@@ -208,7 +208,19 @@ export declare const Triage: z.ZodObject<{
|
|
|
208
208
|
by: z.ZodString;
|
|
209
209
|
}, z.core.$strip>;
|
|
210
210
|
export type Triage = z.infer<typeof Triage>;
|
|
211
|
-
/**
|
|
211
|
+
/** The extractor's word on one read document: how much of the ask it served, what is still missing. */
|
|
212
|
+
export declare const Verdict: z.ZodObject<{
|
|
213
|
+
url: z.ZodString;
|
|
214
|
+
quality: z.ZodEnum<{
|
|
215
|
+
rich: "rich";
|
|
216
|
+
thin: "thin";
|
|
217
|
+
off: "off";
|
|
218
|
+
empty: "empty";
|
|
219
|
+
}>;
|
|
220
|
+
gap: z.ZodOptional<z.ZodString>;
|
|
221
|
+
}, z.core.$strip>;
|
|
222
|
+
export type Verdict = z.infer<typeof Verdict>;
|
|
223
|
+
/** What one round came to, as counts, verdicts and notes. The runner fills it; raven reads it. */
|
|
212
224
|
export declare const Round: z.ZodObject<{
|
|
213
225
|
ask: z.ZodObject<{
|
|
214
226
|
text: z.ZodString;
|
|
@@ -234,6 +246,16 @@ export declare const Round: z.ZodObject<{
|
|
|
234
246
|
kept: z.ZodNumber;
|
|
235
247
|
found: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
236
248
|
notes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
249
|
+
verdicts: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
250
|
+
url: z.ZodString;
|
|
251
|
+
quality: z.ZodEnum<{
|
|
252
|
+
rich: "rich";
|
|
253
|
+
thin: "thin";
|
|
254
|
+
off: "off";
|
|
255
|
+
empty: "empty";
|
|
256
|
+
}>;
|
|
257
|
+
gap: z.ZodOptional<z.ZodString>;
|
|
258
|
+
}, z.core.$strip>>>;
|
|
237
259
|
}, z.core.$strip>;
|
|
238
260
|
export type Round = z.infer<typeof Round>;
|
|
239
261
|
export declare const Step: z.ZodEnum<{
|
package/dist/types.js
CHANGED
|
@@ -107,7 +107,13 @@ export const Triage = z.object({
|
|
|
107
107
|
from: From,
|
|
108
108
|
by: z.string(),
|
|
109
109
|
});
|
|
110
|
-
/**
|
|
110
|
+
/** The extractor's word on one read document: how much of the ask it served, what is still missing. */
|
|
111
|
+
export const Verdict = z.object({
|
|
112
|
+
url: z.string(),
|
|
113
|
+
quality: z.enum(["rich", "thin", "off", "empty"]),
|
|
114
|
+
gap: z.string().optional(),
|
|
115
|
+
});
|
|
116
|
+
/** What one round came to, as counts, verdicts and notes. The runner fills it; raven reads it. */
|
|
111
117
|
export const Round = z.object({
|
|
112
118
|
ask: Ask,
|
|
113
119
|
round: z.number().int().positive(),
|
|
@@ -120,6 +126,8 @@ export const Round = z.object({
|
|
|
120
126
|
found: z.array(z.string()).default([]),
|
|
121
127
|
/** What went wrong or ran out, one line each. */
|
|
122
128
|
notes: z.array(z.string()).default([]),
|
|
129
|
+
/** The extractor's verdict on each document read this round: the planner sees what the reads were worth. */
|
|
130
|
+
verdicts: z.array(Verdict).default([]),
|
|
123
131
|
});
|
|
124
132
|
export const Step = z.enum(["plan", "fetch", "triage", "read", "extract"]);
|
|
125
133
|
/** The edge out of a round. `again` is a new round from `plan`; `back` reruns one step of this round. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrenautomation/raven",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Ask in, plan out. The planner of Wren's research engine: typed queries with who proposed them and why, proposals merged into one plan under a plan file per ask kind, read-or-skip on hits, and the edge to take after a round. Pure functions and prompts; the model is injected.",
|
|
5
5
|
"license": "PolyForm-Shield-1.0.0",
|
|
6
6
|
"repository": {
|