@noetic-tools/core 0.3.0 → 1.1.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.
@@ -101,7 +101,13 @@ export class EventBroadcaster {
101
101
  //#region BroadcastIterator
102
102
  class BroadcastIterator {
103
103
  cursor = 0;
104
- waiter;
104
+ /**
105
+ * Parked `next()` calls, FIFO. The AsyncIterator protocol allows pipelined
106
+ * `next()` calls (calling again before the previous promise settles), so a
107
+ * single waiter slot would orphan all but the latest call — the overwritten
108
+ * promises would never settle. `notify()` drains in arrival order.
109
+ */
110
+ waiters = [];
105
111
  returned = false;
106
112
  broadcaster;
107
113
  constructor(broadcaster) {
@@ -113,20 +119,22 @@ class BroadcastIterator {
113
119
  }
114
120
  /** Called by the broadcaster when new data or completion is available. */
115
121
  notify() {
116
- if (!this.waiter) {
117
- return;
118
- }
119
- const w = this.waiter;
120
- this.waiter = undefined;
121
- try {
122
- const result = this.tryRead();
123
- if (result) {
124
- w.resolve(result);
122
+ while (this.waiters.length > 0) {
123
+ let result;
124
+ try {
125
+ result = this.tryRead();
125
126
  }
126
- }
127
- catch (err) {
128
- // Reject the waiter's promise so the error propagates through the async iterator
129
- w.reject(err);
127
+ catch (err) {
128
+ // Reject so the error propagates through the async iterator. The
129
+ // broadcaster stays finished-with-error, so the loop drains every
130
+ // remaining waiter with the same rejection.
131
+ this.waiters.shift().reject(err);
132
+ continue;
133
+ }
134
+ if (!result) {
135
+ return;
136
+ }
137
+ this.waiters.shift().resolve(result);
130
138
  }
131
139
  }
132
140
  async next() {
@@ -136,25 +144,41 @@ class BroadcastIterator {
136
144
  value: undefined,
137
145
  };
138
146
  }
139
- const result = this.tryRead();
140
- if (result) {
141
- return result;
147
+ // Only read ahead when no one is parked — a pipelined next() must queue
148
+ // BEHIND earlier calls so events are handed out in FIFO order.
149
+ if (this.waiters.length === 0) {
150
+ const result = this.tryRead();
151
+ if (result) {
152
+ return result;
153
+ }
142
154
  }
143
155
  return new Promise((resolve, reject) => {
144
- this.waiter = {
156
+ this.waiters.push({
145
157
  resolve,
146
158
  reject,
147
- };
159
+ });
148
160
  });
149
161
  }
162
+ /** Settle every parked waiter with `{ done: true }` — used on early exit. */
163
+ settleWaiters() {
164
+ const drained = this.waiters.splice(0, this.waiters.length);
165
+ for (const waiter of drained) {
166
+ waiter.resolve({
167
+ done: true,
168
+ value: undefined,
169
+ });
170
+ }
171
+ }
150
172
  async throw(err) {
151
173
  this.returned = true;
152
174
  this.broadcaster.removeIterator(this);
175
+ this.settleWaiters();
153
176
  throw err;
154
177
  }
155
178
  async return() {
156
179
  this.returned = true;
157
180
  this.broadcaster.removeIterator(this);
181
+ this.settleWaiters();
158
182
  return {
159
183
  done: true,
160
184
  value: undefined,
@@ -46,7 +46,8 @@ const AllPredicateSchema = z.object({
46
46
  predicates: z.array(UntilPredicateRef).min(1),
47
47
  });
48
48
  /** @public Zod schema validating a named `until` predicate (including `any`/`all` combinators). */
49
- export const UntilPredicateSchema = z.union([
49
+ export const UntilPredicateSchema = z
50
+ .union([
50
51
  MaxStepsPredicateSchema,
51
52
  MaxCostPredicateSchema,
52
53
  MaxDurationPredicateSchema,
@@ -56,7 +57,11 @@ export const UntilPredicateSchema = z.union([
56
57
  ConvergedPredicateSchema,
57
58
  AnyPredicateSchema,
58
59
  AllPredicateSchema,
59
- ]);
60
+ ])
61
+ .meta({
62
+ id: 'UntilPredicate',
63
+ title: 'UntilPredicate',
64
+ });
60
65
  //#endregion
61
66
  //#region Merge Strategy
62
67
  /** @public ESM literal enum for merge strategies. */
@@ -158,7 +163,8 @@ const EveryNodeSchema = z.object({
158
163
  .optional(),
159
164
  });
160
165
  /** @public Zod schema validating a single `WorkflowNode` (any of the 9 JSON-safe kinds). */
161
- export const WorkflowNodeSchema = z.discriminatedUnion('kind', [
166
+ export const WorkflowNodeSchema = z
167
+ .discriminatedUnion('kind', [
162
168
  LlmNodeSchema,
163
169
  ToolNodeSchema,
164
170
  BranchNodeSchema,
@@ -168,11 +174,21 @@ export const WorkflowNodeSchema = z.discriminatedUnion('kind', [
168
174
  LoopNodeSchema,
169
175
  SequenceNodeSchema,
170
176
  EveryNodeSchema,
171
- ]);
177
+ ])
178
+ .meta({
179
+ id: 'WorkflowNode',
180
+ title: 'WorkflowNode',
181
+ });
172
182
  /** @public Zod schema validating a complete `WorkflowDocument`. */
173
- export const WorkflowDocumentSchema = z.object({
183
+ export const WorkflowDocumentSchema = z
184
+ .object({
174
185
  version: z.literal(1),
175
186
  root: WorkflowNodeSchema,
187
+ })
188
+ .meta({
189
+ id: 'WorkflowDocument',
190
+ title: 'WorkflowDocument',
191
+ description: 'A portable, JSON-safe representation of a Noetic step tree.',
176
192
  });
177
193
  //#endregion
178
194
  //#region Helpers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noetic-tools/core",
3
- "version": "0.3.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Step primitives, interpreter, runtime, and memory layer contract for Noetic agents.",
6
6
  "license": "Apache-2.0",
@@ -40,10 +40,12 @@
40
40
  "types": "./dist/internal-test.d.ts",
41
41
  "default": "./dist/internal-test.js"
42
42
  },
43
+ "./schema": "./schema/noetic-workflow.schema.json",
43
44
  "./package.json": "./package.json"
44
45
  },
45
46
  "files": [
46
47
  "dist",
48
+ "schema",
47
49
  "README.md",
48
50
  "LICENSE"
49
51
  ],
@@ -64,15 +66,16 @@
64
66
  }
65
67
  },
66
68
  "dependencies": {
67
- "@noetic-tools/memory": "^0.2.0",
68
- "@noetic-tools/types": "^0.2.0",
69
+ "@noetic-tools/memory": "^1.0.1",
70
+ "@noetic-tools/types": "^1.0.0",
69
71
  "@openrouter/agent": "^0.6.0",
70
72
  "zod": "^4.0.0"
71
73
  },
72
74
  "scripts": {
73
75
  "build": "bunx tsc -p tsconfig.build.json && bun scripts/add-js-extensions.ts",
76
+ "gen:schema": "bun scripts/generate-workflow-schema.ts",
74
77
  "verify:esm": "bun scripts/verify-node-esm.ts",
75
- "prepublishOnly": "rm -rf dist && bun run build && bun run verify:esm && bun scripts/pin-workspace-deps.ts",
78
+ "prepublishOnly": "rm -rf dist && bun run gen:schema && bun run build && bun run verify:esm && bun scripts/pin-workspace-deps.ts && bun scripts/strip-dev-conditions.ts",
76
79
  "test": "bun test",
77
80
  "test:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov",
78
81
  "test:ci": "bun test --coverage --coverage-reporter=lcov",