octaflow 0.37.0 → 0.38.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/dist/document/index.js +21 -2
- package/dist/postgres/index.d.ts +12 -6
- package/dist/postgres/index.js +14 -12
- package/package.json +1 -2
- package/skills/add-a-case-loop/SKILL.md +5 -5
package/dist/document/index.js
CHANGED
|
@@ -189,7 +189,7 @@ function checkSchemaSubset(schema, path) {
|
|
|
189
189
|
return issues;
|
|
190
190
|
}
|
|
191
191
|
for (const keyword of Object.keys(s)) {
|
|
192
|
-
if (keyword === "type" || ANNOTATIONS.has(keyword) || BY_TYPE[type].has(keyword)) continue;
|
|
192
|
+
if (keyword === "type" || ANNOTATIONS.has(keyword) || keyword.startsWith("x-") || BY_TYPE[type].has(keyword)) continue;
|
|
193
193
|
at(`${path}.${keyword}`, `"${keyword}" is not part of the schema subset for type ${type}`);
|
|
194
194
|
}
|
|
195
195
|
if (type === "string" && s.format !== void 0 && (typeof s.format !== "string" || !FORMATS.has(s.format))) at(`${path}.format`, `format must be one of ${[...FORMATS].join(", ")}`);
|
|
@@ -1586,8 +1586,27 @@ const humanApproveKind = defineKind({
|
|
|
1586
1586
|
subject: z.unknown(),
|
|
1587
1587
|
instructions: z.string().optional(),
|
|
1588
1588
|
/** What the person may decide; `approve` among them. Default approve and reject — add `revise`, or `expired` for a timeout's answer. */
|
|
1589
|
-
decisions: z.array(z.string().min(1)).min(2).refine((d) => d.includes("approve"), "the decisions include \"approve\"").optional()
|
|
1589
|
+
decisions: z.array(z.string().min(1)).min(2).refine((d) => d.includes("approve"), "the decisions include \"approve\"").optional(),
|
|
1590
|
+
/**
|
|
1591
|
+
* Approvals of the same run this one must come from someone else than — four eyes. The compiler holds
|
|
1592
|
+
* each to a step upstream that says who answered it (`output.by`, which an approval does); the host
|
|
1593
|
+
* holds an answer to it, since only it knows who answers.
|
|
1594
|
+
*/
|
|
1595
|
+
distinctFrom: z.array(z.string().min(1)).optional()
|
|
1590
1596
|
},
|
|
1597
|
+
check: ({ params, checkExpr }) => {
|
|
1598
|
+
const others = params.distinctFrom;
|
|
1599
|
+
if (others === void 0) return [];
|
|
1600
|
+
if (!Array.isArray(others) || !others.every((k) => typeof k === "string")) return [{
|
|
1601
|
+
path: "distinctFrom",
|
|
1602
|
+
message: "names the approval steps by key"
|
|
1603
|
+
}];
|
|
1604
|
+
return others.flatMap((k, i) => checkExpr(`steps.${k}.output.by`).errors.length ? [{
|
|
1605
|
+
path: `distinctFrom.${i}`,
|
|
1606
|
+
message: `"${k}" is no approval upstream of this one`
|
|
1607
|
+
}] : []);
|
|
1608
|
+
},
|
|
1609
|
+
reads: (params) => Array.isArray(params.distinctFrom) ? params.distinctFrom.map((k) => `steps.${k}.output.by`) : [],
|
|
1591
1610
|
actor: "person",
|
|
1592
1611
|
approval: true,
|
|
1593
1612
|
wait: true,
|
package/dist/postgres/index.d.ts
CHANGED
|
@@ -44,16 +44,22 @@ export interface SqlExecutor {
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function poolExecutor(pool: Pool): SqlExecutor;
|
|
46
46
|
/**
|
|
47
|
-
* The slice of a postgres.js `sql`
|
|
48
|
-
*
|
|
49
|
-
* list with the affected-row `count` on it; `sql.begin` runs its callback on one
|
|
50
|
-
* reserved connection inside `BEGIN`/`COMMIT` and hands it a `sql` of the same shape.
|
|
47
|
+
* The slice of a postgres.js transaction `sql` {@link postgresJsExecutor} uses: `unsafe` alone. postgres.js hands
|
|
48
|
+
* `begin`'s callback a `TransactionSql`, which has no `begin` of its own, and the executor never nests.
|
|
51
49
|
*/
|
|
52
|
-
export interface
|
|
50
|
+
export interface PostgresJsTransactionLike {
|
|
53
51
|
unsafe(query: string, params?: unknown[]): PromiseLike<unknown[] & {
|
|
54
52
|
count?: number | null;
|
|
55
53
|
}>;
|
|
56
|
-
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The slice of a postgres.js `sql` instance {@link postgresJsExecutor} uses —
|
|
57
|
+
* structural, so the driver is never imported. `sql.unsafe` resolves to the row
|
|
58
|
+
* list with the affected-row `count` on it; `sql.begin` runs its callback on one
|
|
59
|
+
* reserved connection inside `BEGIN`/`COMMIT` and hands it a transaction `sql`.
|
|
60
|
+
*/
|
|
61
|
+
export interface PostgresJsSqlLike extends PostgresJsTransactionLike {
|
|
62
|
+
begin<T>(fn: (tx: PostgresJsTransactionLike) => Promise<T>): PromiseLike<T>;
|
|
57
63
|
}
|
|
58
64
|
/**
|
|
59
65
|
* Build a {@link SqlExecutor} over a postgres.js `sql` instance: top-level
|
package/dist/postgres/index.js
CHANGED
|
@@ -150,19 +150,21 @@ const isoDates = (params) => params.map((p) => p instanceof Date ? p.toISOString
|
|
|
150
150
|
* {@link poolExecutor}.
|
|
151
151
|
*/
|
|
152
152
|
function postgresJsExecutor(sql) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
153
|
+
const query = (conn) => async function(text, params) {
|
|
154
|
+
const rows = await conn.unsafe(text, isoDates(params ?? []));
|
|
155
|
+
return {
|
|
156
|
+
rows: Array.from(rows),
|
|
157
|
+
rowCount: rows.count ?? rows.length
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
const inTransaction = (tx) => ({
|
|
161
|
+
query: query(tx),
|
|
162
|
+
transaction: (fn) => fn(inTransaction(tx))
|
|
164
163
|
});
|
|
165
|
-
return
|
|
164
|
+
return {
|
|
165
|
+
query: query(sql),
|
|
166
|
+
transaction: (fn) => Promise.resolve(sql.begin((tx) => fn(inTransaction(tx))))
|
|
167
|
+
};
|
|
166
168
|
}
|
|
167
169
|
/**
|
|
168
170
|
* Build a {@link SqlExecutor} over a PGlite instance — the embedded Postgres a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octaflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "Durable DAG workflow engine: Zod-typed steps over Postgres + pg-boss. The model layer is @octabits-io/models.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -66,7 +66,6 @@
|
|
|
66
66
|
"drizzle-orm": ">=0.36.0 <1",
|
|
67
67
|
"pg": "^8.0.0",
|
|
68
68
|
"pg-boss": "^12.21.0",
|
|
69
|
-
"typescript": "^5 || ^6 || ^7",
|
|
70
69
|
"zod": "^4.6.0"
|
|
71
70
|
},
|
|
72
71
|
"peerDependenciesMeta": {
|
|
@@ -19,10 +19,10 @@ the case.
|
|
|
19
19
|
… body … ─► result ─► decide (manager) ─► next ─► move N+1 | case closed
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Shown end to end in `apps/demo-
|
|
23
|
-
that answers by resuming the move that waits
|
|
24
|
-
`apps/demo-
|
|
25
|
-
`packages/octaflow/src/ai/case-loop.test.ts`.
|
|
22
|
+
Shown end to end, written as a case document, in the office: `apps/demo-office/src/features/office/case/`
|
|
23
|
+
(the document, the office's kinds, a customer agent that answers by resuming the move that waits
|
|
24
|
+
for it), with whole cases on the scripted model in `apps/demo-office/src/overdue-invoice.test.ts`.
|
|
25
|
+
The loop's own tests are `packages/octaflow/src/ai/case-loop.test.ts`.
|
|
26
26
|
|
|
27
27
|
**Which pattern?**
|
|
28
28
|
|
|
@@ -110,7 +110,7 @@ means the move stopped waiting — keep the answer where the next `wait` move fi
|
|
|
110
110
|
## Prove it works
|
|
111
111
|
|
|
112
112
|
A test on the in-memory engine with one scripted model whose lines are routed by the system
|
|
113
|
-
prompt — `apps/demo-
|
|
113
|
+
prompt — `apps/demo-office/src/overdue-invoice.test.ts` is the template — that asserts:
|
|
114
114
|
|
|
115
115
|
- a whole case: the moves in order, the outcome the host recorded;
|
|
116
116
|
- a refused choice is in `decide`'s `refusals` with its reason, and the next choice is taken;
|