@popcorn245/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.
@@ -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
- try {
124
- const specs = hookWants(config, ['spec.ready']);
125
- const threads = threadsOn(config) && hookWants(config, THREAD_EVENTS);
126
- // One listing of the open PRs serves both: the threads' look makes it itself when it runs
127
- const problem = threads ? await lookAtThreads(found.forge, specs) : (await listPullProposals(found.forge, where, { hooks })).problem;
128
- if (problem) {
129
- log(`the forge could not be asked: ${problem}`);
130
- return 'error';
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) {