cornfield 0.8.0 → 0.9.0
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/core/agent-contract.js +5 -2
- package/core/agent-contract.schema.json +9 -9
- package/core/config.js +59 -5
- package/explorer/explorer.css +3 -1
- package/explorer/forge-panel.js +203 -5
- package/explorer/index.html +28 -0
- package/explorer/review-tokens-model.js +5 -1
- package/explorer/review-tokens.js +11 -7
- package/explorer/runs-model.js +107 -0
- package/package.json +1 -1
- package/server/agent-api.js +227 -0
- package/server/agent-runs.js +769 -0
- package/server/db.js +53 -0
- package/server/forge-forgejo.js +7 -0
- package/server/forge-github.js +7 -0
- package/server/forge.js +5 -1
- package/server/hooks.js +19 -0
- package/server/review-api.js +26 -12
- package/server/review-tokens.js +42 -25
- package/server/server.js +29 -7
- package/server/spec-poll.js +41 -15
package/server/spec-poll.js
CHANGED
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
* token is only ever asked those two questions, never used to list. With no such session the tick does nothing and says so
|
|
25
25
|
* once for the stretch (and once more when a session is back). Whose sign-in it looks with is logged each time that changes.
|
|
26
26
|
*
|
|
27
|
+
* **It also follows agent runs** (issue #80, server/agent-runs.js): when there is a run to look at (`runs.hasWork()`, a question of the
|
|
28
|
+
* database alone, so an idle repo costs the forge nothing), the same tick hands the same maintainer's forge to `runs.follow`, which reads the
|
|
29
|
+
* runs' comments and pull requests. A server with no `spec.ready` hook still polls for runs (`specs: false`) and then does nothing else.
|
|
30
|
+
*
|
|
27
31
|
* **Never in the way.** One tick runs at a time (the next is scheduled when one ends, so a slow forge cannot pile them up).
|
|
28
32
|
* A forge error is logged and never thrown; the next tick waits twice the interval until one succeeds. The timer is
|
|
29
33
|
* unref'd and `close()` clears it.
|
|
@@ -48,14 +52,17 @@ const words = (err) => (err instanceof Error ? err.message : String(err));
|
|
|
48
52
|
* config: any,
|
|
49
53
|
* where: { proposals: string },
|
|
50
54
|
* auth: { store: () => Promise<import('node:sqlite').DatabaseSync>, forgeForUser: (userId: string) => Promise<import('./forge.js').Forge> },
|
|
51
|
-
* hooks: import('./hooks.js').Hooks,
|
|
55
|
+
* hooks: import('./hooks.js').Hooks | null,
|
|
56
|
+
* runs?: { hasWork: () => Promise<boolean>, follow: (forge: import('./forge.js').Forge) => Promise<unknown> } | null,
|
|
57
|
+
* specs?: boolean,
|
|
52
58
|
* intervalMs: number,
|
|
53
59
|
* now?: () => number,
|
|
54
60
|
* timer?: { setTimeout: Function, clearTimeout: Function },
|
|
55
61
|
* firstMs?: number,
|
|
56
|
-
* }} opts `now`, `timer` and `firstMs` are the tests' clock
|
|
62
|
+
* }} opts `now`, `timer` and `firstMs` are the tests' clock; `runs` is the agent runs engine, whose runs are followed on the same tick, and
|
|
63
|
+
* `specs: false` leaves the spec PRs alone (no hook wants `spec.ready`), so only the runs are looked at
|
|
57
64
|
*/
|
|
58
|
-
export function createSpecPoll({ config, where, auth, hooks, intervalMs, now = Date.now, timer = { setTimeout, clearTimeout }, firstMs = FIRST_MS }) {
|
|
65
|
+
export function createSpecPoll({ config, where, auth, hooks, runs = null, specs = true, intervalMs, now = Date.now, timer = { setTimeout, clearTimeout }, firstMs = FIRST_MS }) {
|
|
59
66
|
const repo = config.forge.repo;
|
|
60
67
|
const log = (what) => console.error(`[cornfield] spec.ready poll: ${what}`);
|
|
61
68
|
let handle = null;
|
|
@@ -101,6 +108,14 @@ export function createSpecPoll({ config, where, auth, hooks, intervalMs, now = D
|
|
|
101
108
|
|
|
102
109
|
/** One look. Resolves to what happened; never rejects. */
|
|
103
110
|
async function look() {
|
|
111
|
+
// Nothing to look at: no forge call is made (an idle repo with only runs to follow costs nothing)
|
|
112
|
+
let followRuns = false;
|
|
113
|
+
try {
|
|
114
|
+
followRuns = Boolean(runs) && await /** @type {NonNullable<typeof runs>} */ (runs).hasWork();
|
|
115
|
+
} catch (err) {
|
|
116
|
+
log(`the runs could not be counted: ${words(err)}`);
|
|
117
|
+
}
|
|
118
|
+
if (!specs && !followRuns) return 'ok';
|
|
104
119
|
let found;
|
|
105
120
|
try {
|
|
106
121
|
found = await maintainerForge();
|
|
@@ -120,20 +135,31 @@ export function createSpecPoll({ config, where, auth, hooks, intervalMs, now = D
|
|
|
120
135
|
noSession = false;
|
|
121
136
|
if (found.login !== lookingAs) log(`looking at the open spec PRs${threadsOn(config) && hookWants(config, THREAD_EVENTS) ? ' and threads' : ''} with the sign-in of @${found.login}, a maintainer`);
|
|
122
137
|
lookingAs = found.login;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
let out = 'ok';
|
|
139
|
+
if (specs) {
|
|
140
|
+
try {
|
|
141
|
+
const specReady = hookWants(config, ['spec.ready']);
|
|
142
|
+
const threads = threadsOn(config) && hookWants(config, THREAD_EVENTS);
|
|
143
|
+
// One listing of the open PRs serves both: the threads' look makes it itself when it runs
|
|
144
|
+
const problem = threads ? await lookAtThreads(found.forge, specReady) : (await listPullProposals(found.forge, where, { hooks })).problem;
|
|
145
|
+
if (problem) {
|
|
146
|
+
log(`the forge could not be asked: ${problem}`);
|
|
147
|
+
out = 'error';
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
log(`failed: ${words(err)}`);
|
|
151
|
+
out = 'error';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (followRuns && !closed) {
|
|
155
|
+
try {
|
|
156
|
+
await /** @type {NonNullable<typeof runs>} */ (runs).follow(found.forge);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
log(`the runs could not be followed: ${words(err)}`);
|
|
159
|
+
out = 'error';
|
|
131
160
|
}
|
|
132
|
-
return 'ok';
|
|
133
|
-
} catch (err) {
|
|
134
|
-
log(`failed: ${words(err)}`);
|
|
135
|
-
return 'error';
|
|
136
161
|
}
|
|
162
|
+
return out;
|
|
137
163
|
}
|
|
138
164
|
|
|
139
165
|
function schedule(ms) {
|