agents-relay 1.0.101 → 1.0.102

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.
@@ -86,6 +86,27 @@ export class ReconcilerRuntimeBase {
86
86
  }
87
87
  async idle() { while (this.live.size > 0 || this.plannerLive.size > 0 || this.continuationLive.size > 0 || this.reconciling)
88
88
  await new Promise(resolve => setTimeout(resolve, 25)); }
89
+ async close() {
90
+ for (const live of this.live.values()) {
91
+ live.controller.abort();
92
+ live.execution.cancel();
93
+ }
94
+ this.live.clear();
95
+ for (const timer of this.terminalTimers.values())
96
+ clearTimeout(timer);
97
+ this.terminalTimers.clear();
98
+ for (const timer of this.terminalExitGraceTimers.values())
99
+ clearTimeout(timer);
100
+ this.terminalExitGraceTimers.clear();
101
+ for (const planner of this.plannerLive.values()) {
102
+ clearTimeout(planner.timer);
103
+ planner.controller.abort();
104
+ }
105
+ this.plannerLive.clear();
106
+ for (const waiter of this.terminalWaiters.values())
107
+ waiter.resolve();
108
+ this.terminalWaiters.clear();
109
+ }
89
110
  async wake(jobId) { return this.reconcile(jobId); }
90
111
  async startAck(jobId, taskId, sourceId, launchBarrier, timeoutMs = this.options.startAckTimeoutMs ?? 60000) {
91
112
  if (!this.options.eventBus)
@@ -146,8 +146,13 @@ export class Reconciler extends ReconcilerReviewBase {
146
146
  this.plannerLive.get(task.id)?.controller.abort();
147
147
  }
148
148
  const beforeSchedule = await this.store.load(jobId);
149
+ const taskStatesBeforeSchedule = new Map(beforeSchedule.tasks.map(task => [task.id, task.state]));
149
150
  const scheduleBefore = JSON.stringify(beforeSchedule.schedule ?? null);
150
151
  let scheduled = schedule(beforeSchedule);
152
+ for (const task of scheduled.tasks) {
153
+ if (taskStatesBeforeSchedule.get(task.id) !== task.state)
154
+ await this.store.saveTask(task);
155
+ }
151
156
  if (scheduled.state !== previousState || JSON.stringify(scheduled.schedule ?? null) !== scheduleBefore)
152
157
  await this.store.saveJob(scheduled);
153
158
  if (prState === 'OPEN') {
@@ -1,18 +1,26 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { parseExecutableMermaid, WorkflowEngine } from './mermaid.js';
3
- import { WORKFLOW_EFFECT_NAMES, WORKFLOW_EVENT_NAMES, WORKFLOW_MUTATION_NAMES } from './contracts.js';
3
+ import { WORKFLOW_EFFECT_NAMES, WORKFLOW_EVENT_NAMES, WORKFLOW_FACT_NAMES, WORKFLOW_MUTATION_NAMES, classifyWorkflowActions } from './contracts.js';
4
4
  function declarativeActions() {
5
5
  return Object.fromEntries([...WORKFLOW_MUTATION_NAMES, ...WORKFLOW_EFFECT_NAMES].map(name => [name, { declarative: true }]));
6
6
  }
7
- function load(name, guards = {}) {
8
- const url = new URL(`../../workflow/${name}.mmd`, import.meta.url);
9
- const graph = parseExecutableMermaid(readFileSync(url, 'utf8'));
7
+ export function validateCanonicalWorkflowGraph(graph) {
10
8
  const canonicalEvents = new Set(WORKFLOW_EVENT_NAMES);
9
+ const canonicalFacts = new Set(WORKFLOW_FACT_NAMES);
11
10
  for (const transition of graph.transitions) {
12
11
  if (!canonicalEvents.has(transition.event)) {
13
12
  throw new Error(`Unknown canonical workflow event ${transition.event} referenced at line ${transition.line}`);
14
13
  }
14
+ if (transition.guard && !canonicalFacts.has(transition.guard)) {
15
+ throw new Error(`Unknown canonical workflow fact ${transition.guard} referenced at line ${transition.line}`);
16
+ }
17
+ classifyWorkflowActions(transition.actions);
15
18
  }
19
+ }
20
+ function load(name, guards = {}) {
21
+ const url = new URL(`../../workflow/${name}.mmd`, import.meta.url);
22
+ const graph = parseExecutableMermaid(readFileSync(url, 'utf8'));
23
+ validateCanonicalWorkflowGraph(graph);
16
24
  return new WorkflowEngine(graph, guards, declarativeActions());
17
25
  }
18
26
  const booleanGuard = (name) => context => context[name] === true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "1.0.101",
3
+ "version": "1.0.102",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc -p tsconfig.json && chmod +x dist/cli.js dist/relayd.js",
12
- "test": "npm run build && node --test --test-force-exit test/*.test.js",
12
+ "test": "npm run build && node --test test/*.test.js",
13
13
  "test:chatgpt-temp-quality": "AGENTS_RELAY_RUN_CHATGPT_TEMP_QUALITY=1 AGENTS_RELAY_CHATGPT_BROWSER_WORKER_AGENT=/Users/chengli/Workspace/neo/skills/chatgpt-browser-worker/agents/browser-worker.agent.md sh -c \"npm run build && node test/quality/run-temporary-chat.mjs\"",
14
14
  "dev": "tsx src/cli.ts"
15
15
  },