@swirls/looms 0.2.0 → 0.3.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/CHANGELOG.md +22 -0
- package/dist/actor/host.js +4 -6
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/routing.d.ts +5 -0
- package/dist/actor/routing.js +14 -0
- package/dist/agent/agent-session.js +1 -0
- package/dist/agent/debugger/agent-chat.js +45 -11
- package/dist/agent/debugger/summarize.js +2 -0
- package/dist/agent/effects.d.ts +30 -0
- package/dist/agent/events.d.ts +2 -0
- package/dist/agent/events.js +2 -0
- package/dist/agent/llm.js +1 -8
- package/dist/agent/module.d.ts +604 -534
- package/dist/agent/module.js +11 -7
- package/dist/agent/projections.d.ts +5 -0
- package/dist/agent/projections.js +43 -2
- package/dist/agent/scope.d.ts +2 -0
- package/dist/agent/signals.d.ts +5 -0
- package/dist/agent/signals.js +7 -0
- package/dist/agent/threads.js +35 -21
- package/dist/agent/transcript.d.ts +7 -0
- package/dist/agent/transcript.js +19 -0
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.js +4 -0
- package/dist/approval/debugger/approvals-view.js +5 -3
- package/dist/approval/effects.d.ts +12 -0
- package/dist/approval/module.d.ts +24 -0
- package/dist/client/client.d.ts +292 -0
- package/dist/client/client.js +17 -1
- package/dist/cloudflare/router.d.ts +1 -1
- package/dist/cloudflare/router.js +6 -8
- package/dist/core/effects.d.ts +7 -0
- package/dist/core/effects.js +1 -0
- package/dist/core/fold.js +12 -6
- package/dist/core/fork.d.ts +35 -0
- package/dist/core/fork.js +129 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/module-scope.d.ts +1 -0
- package/dist/core/module.d.ts +2 -2
- package/dist/core/projection.js +1 -0
- package/dist/core/protocol.d.ts +10 -1
- package/dist/core/protocol.js +11 -0
- package/dist/core/replay.d.ts +8 -0
- package/dist/core/snapshots.js +3 -17
- package/dist/core/state.d.ts +14 -0
- package/dist/core/state.js +25 -2
- package/dist/core/store.d.ts +1 -0
- package/dist/debugger/app/assets/{index-Qb371t_O.js → index-BevQYa-U.js} +2 -2
- package/dist/debugger/app/assets/index-CgDzWJg7.css +2 -0
- package/dist/debugger/app/assets/routes-B17Om93i.js +47 -0
- package/dist/debugger/app/index.html +2 -2
- package/dist/debugger/components/composer.d.ts +5 -2
- package/dist/debugger/components/composer.js +5 -4
- package/dist/debugger/components/run-tree.js +2 -5
- package/dist/debugger/events/catalog.js +1 -3
- package/dist/debugger/events/protocol-summarize.js +7 -0
- package/dist/debugger/lib/event-counts.d.ts +2 -2
- package/dist/debugger/lib/event-counts.js +5 -9
- package/dist/debugger/shell/debugger-shell.js +1 -1
- package/dist/debugger/shell/run-controls.d.ts +2 -1
- package/dist/debugger/shell/run-controls.js +40 -13
- package/dist/debugger/shell/run-search.js +1 -3
- package/dist/evaluator/effects.d.ts +12 -0
- package/dist/evaluator/module.d.ts +24 -0
- package/dist/projectors/index-model.d.ts +13 -7
- package/dist/projectors/index-model.js +21 -26
- package/dist/projectors/memory.js +19 -11
- package/dist/projectors/postgres.js +20 -19
- package/dist/projectors/schema.js +20 -18
- package/dist/projectors/sqlite.js +32 -27
- package/dist/react/materialize.js +16 -11
- package/dist/react/react.d.ts +6 -2
- package/dist/react/react.js +5 -2
- package/dist/react/tables.d.ts +3 -2
- package/dist/runtime/cancellation.js +14 -27
- package/dist/runtime/errors.d.ts +39 -0
- package/dist/runtime/errors.js +32 -0
- package/dist/runtime/helpers.d.ts +2 -0
- package/dist/runtime/helpers.js +23 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/looms.d.ts +4 -0
- package/dist/runtime/looms.js +1 -0
- package/dist/runtime/run-start.js +1 -3
- package/dist/runtime/runtime.d.ts +1 -1
- package/dist/runtime/runtime.js +45 -18
- package/dist/runtime/server/http.d.ts +2 -0
- package/dist/runtime/server/http.js +17 -2
- package/dist/runtime/server/routes/runs.js +35 -2
- package/dist/runtime/types.d.ts +8 -0
- package/dist/runtime/wake-engine.js +5 -2
- package/dist/s2/snapshot-store.js +2 -6
- package/dist/workflow/effects.d.ts +24 -0
- package/dist/workflow/effects.js +1 -0
- package/dist/workflow/module.d.ts +48 -0
- package/package.json +1 -1
- package/dist/debugger/app/assets/index-B3Ah8WAE.css +0 -2
- package/dist/debugger/app/assets/routes-CjtEHgPQ.js +0 -47
|
@@ -12,9 +12,9 @@ function applyOp(sql, op) {
|
|
|
12
12
|
switch (op.type) {
|
|
13
13
|
case 'upsertActor':
|
|
14
14
|
return sql `
|
|
15
|
-
INSERT INTO
|
|
16
|
-
VALUES (${op.actorId}, ${op.kind}, ${op.status}, ${op.definitionName}, ${op.parentActorId}, ${date})
|
|
17
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
15
|
+
INSERT INTO looms_run_actors (run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at)
|
|
16
|
+
VALUES (${op.runId}, ${op.actorId}, ${op.kind}, ${op.status}, ${op.definitionName}, ${op.parentActorId}, ${date})
|
|
17
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
18
18
|
kind = EXCLUDED.kind,
|
|
19
19
|
status = EXCLUDED.status,
|
|
20
20
|
definition_name = EXCLUDED.definition_name,
|
|
@@ -23,24 +23,24 @@ function applyOp(sql, op) {
|
|
|
23
23
|
`;
|
|
24
24
|
case 'setActorStatus':
|
|
25
25
|
return sql `
|
|
26
|
-
INSERT INTO
|
|
27
|
-
VALUES (${op.actorId}, ${op.status}, ${date})
|
|
28
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
26
|
+
INSERT INTO looms_run_actors (run_id, actor_id, status, updated_at)
|
|
27
|
+
VALUES (${op.runId}, ${op.actorId}, ${op.status}, ${date})
|
|
28
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
29
29
|
status = EXCLUDED.status,
|
|
30
30
|
updated_at = EXCLUDED.updated_at
|
|
31
31
|
`;
|
|
32
32
|
case 'upsertReview':
|
|
33
33
|
return sql `
|
|
34
|
-
INSERT INTO
|
|
35
|
-
VALUES (${op.
|
|
36
|
-
ON CONFLICT (review_id) DO UPDATE SET
|
|
34
|
+
INSERT INTO looms_run_reviews (run_id, review_id, status, title, updated_at)
|
|
35
|
+
VALUES (${op.runId}, ${op.reviewId}, ${op.status}, ${op.title}, ${date})
|
|
36
|
+
ON CONFLICT (run_id, review_id) DO UPDATE SET
|
|
37
37
|
status = EXCLUDED.status,
|
|
38
38
|
updated_at = EXCLUDED.updated_at
|
|
39
39
|
`;
|
|
40
40
|
case 'setReviewStatus':
|
|
41
41
|
return sql `
|
|
42
|
-
UPDATE
|
|
43
|
-
WHERE review_id = ${op.reviewId}
|
|
42
|
+
UPDATE looms_run_reviews SET status = ${op.status}, updated_at = ${date}
|
|
43
|
+
WHERE run_id = ${op.runId} AND review_id = ${op.reviewId}
|
|
44
44
|
`;
|
|
45
45
|
default: {
|
|
46
46
|
const exhaustiveCheck = op;
|
|
@@ -56,13 +56,14 @@ export function postgres(options) {
|
|
|
56
56
|
applyOps: (ops) => runProjectorPromise('postgres', () => sql.begin((tx) => Effect.runPromise(Effect.forEach(ops, (op) => Effect.promise(() => Promise.resolve(applyOp(tx, op))), {
|
|
57
57
|
discard: true,
|
|
58
58
|
})))).then(() => undefined),
|
|
59
|
-
getActor: (actorId) => runProjectorPromise('postgres', () => sql `
|
|
60
|
-
SELECT actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
61
|
-
FROM
|
|
59
|
+
getActor: (runId, actorId) => runProjectorPromise('postgres', () => sql `
|
|
60
|
+
SELECT run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
61
|
+
FROM looms_run_actors WHERE run_id = ${runId} AND actor_id = ${actorId}
|
|
62
62
|
`).then((rows) => {
|
|
63
63
|
const row = rows[0];
|
|
64
64
|
return row
|
|
65
65
|
? {
|
|
66
|
+
runId: row.run_id,
|
|
66
67
|
actorId: row.actor_id,
|
|
67
68
|
kind: row.kind,
|
|
68
69
|
status: row.status,
|
|
@@ -72,16 +73,16 @@ export function postgres(options) {
|
|
|
72
73
|
}
|
|
73
74
|
: null;
|
|
74
75
|
}),
|
|
75
|
-
listReviews: (
|
|
76
|
+
listReviews: (runId) => runProjectorPromise('postgres', () => runId
|
|
76
77
|
? sql `
|
|
77
|
-
SELECT
|
|
78
|
-
FROM
|
|
78
|
+
SELECT run_id, review_id, status, title, updated_at
|
|
79
|
+
FROM looms_run_reviews WHERE run_id = ${runId}
|
|
79
80
|
`
|
|
80
81
|
: sql `
|
|
81
|
-
SELECT
|
|
82
|
+
SELECT run_id, review_id, status, title, updated_at FROM looms_run_reviews
|
|
82
83
|
`).then((rows) => rows.map((row) => ({
|
|
84
|
+
runId: row.run_id,
|
|
83
85
|
reviewId: row.review_id,
|
|
84
|
-
actorId: row.actor_id,
|
|
85
86
|
status: row.status,
|
|
86
87
|
title: row.title,
|
|
87
88
|
updatedAt: row.updated_at.getTime(),
|
|
@@ -2,42 +2,44 @@
|
|
|
2
2
|
* SQL schema for the optional Looms secondary index.
|
|
3
3
|
*/
|
|
4
4
|
export const POSTGRES_SCHEMA_SQL = `
|
|
5
|
-
CREATE TABLE IF NOT EXISTS
|
|
6
|
-
|
|
5
|
+
CREATE TABLE IF NOT EXISTS looms_run_actors (
|
|
6
|
+
run_id TEXT NOT NULL,
|
|
7
|
+
actor_id TEXT NOT NULL,
|
|
7
8
|
kind TEXT,
|
|
8
9
|
status TEXT,
|
|
9
10
|
definition_name TEXT,
|
|
10
11
|
parent_actor_id TEXT,
|
|
11
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
12
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
13
|
+
PRIMARY KEY (run_id, actor_id)
|
|
12
14
|
);
|
|
13
15
|
|
|
14
|
-
CREATE TABLE IF NOT EXISTS
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
CREATE TABLE IF NOT EXISTS looms_run_reviews (
|
|
17
|
+
run_id TEXT NOT NULL,
|
|
18
|
+
review_id TEXT NOT NULL,
|
|
17
19
|
status TEXT NOT NULL,
|
|
18
20
|
title TEXT NOT NULL,
|
|
19
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
21
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
22
|
+
PRIMARY KEY (run_id, review_id)
|
|
20
23
|
);
|
|
21
|
-
|
|
22
|
-
CREATE INDEX IF NOT EXISTS looms_reviews_actor_id_idx ON looms_reviews (actor_id);
|
|
23
24
|
`.trim();
|
|
24
25
|
export const SQLITE_SCHEMA_SQL = `
|
|
25
|
-
CREATE TABLE IF NOT EXISTS
|
|
26
|
-
|
|
26
|
+
CREATE TABLE IF NOT EXISTS looms_run_actors (
|
|
27
|
+
run_id TEXT NOT NULL,
|
|
28
|
+
actor_id TEXT NOT NULL,
|
|
27
29
|
kind TEXT,
|
|
28
30
|
status TEXT,
|
|
29
31
|
definition_name TEXT,
|
|
30
32
|
parent_actor_id TEXT,
|
|
31
|
-
updated_at INTEGER NOT NULL
|
|
33
|
+
updated_at INTEGER NOT NULL,
|
|
34
|
+
PRIMARY KEY (run_id, actor_id)
|
|
32
35
|
);
|
|
33
36
|
|
|
34
|
-
CREATE TABLE IF NOT EXISTS
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
CREATE TABLE IF NOT EXISTS looms_run_reviews (
|
|
38
|
+
run_id TEXT NOT NULL,
|
|
39
|
+
review_id TEXT NOT NULL,
|
|
37
40
|
status TEXT NOT NULL,
|
|
38
41
|
title TEXT NOT NULL,
|
|
39
|
-
updated_at INTEGER NOT NULL
|
|
42
|
+
updated_at INTEGER NOT NULL,
|
|
43
|
+
PRIMARY KEY (run_id, review_id)
|
|
40
44
|
);
|
|
41
|
-
|
|
42
|
-
CREATE INDEX IF NOT EXISTS looms_reviews_actor_id_idx ON looms_reviews (actor_id);
|
|
43
45
|
`.trim();
|
|
@@ -9,35 +9,39 @@ function hasDb(options) {
|
|
|
9
9
|
function applyOp(db, op) {
|
|
10
10
|
switch (op.type) {
|
|
11
11
|
case 'upsertActor':
|
|
12
|
-
db.run(`INSERT INTO
|
|
13
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
14
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
12
|
+
db.run(`INSERT INTO looms_run_actors (run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at)
|
|
13
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
14
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
15
15
|
kind = excluded.kind,
|
|
16
16
|
status = excluded.status,
|
|
17
17
|
definition_name = excluded.definition_name,
|
|
18
18
|
parent_actor_id = excluded.parent_actor_id,
|
|
19
|
-
updated_at = excluded.updated_at`, [
|
|
19
|
+
updated_at = excluded.updated_at`, [
|
|
20
|
+
op.runId,
|
|
21
|
+
op.actorId,
|
|
22
|
+
op.kind,
|
|
23
|
+
op.status,
|
|
24
|
+
op.definitionName,
|
|
25
|
+
op.parentActorId,
|
|
26
|
+
op.updatedAt,
|
|
27
|
+
]);
|
|
20
28
|
return;
|
|
21
29
|
case 'setActorStatus':
|
|
22
|
-
db.run(`INSERT INTO
|
|
23
|
-
VALUES (?, ?, ?)
|
|
24
|
-
ON CONFLICT (actor_id) DO UPDATE SET
|
|
30
|
+
db.run(`INSERT INTO looms_run_actors (run_id, actor_id, status, updated_at)
|
|
31
|
+
VALUES (?, ?, ?, ?)
|
|
32
|
+
ON CONFLICT (run_id, actor_id) DO UPDATE SET
|
|
25
33
|
status = excluded.status,
|
|
26
|
-
updated_at = excluded.updated_at`, [op.actorId, op.status, op.updatedAt]);
|
|
34
|
+
updated_at = excluded.updated_at`, [op.runId, op.actorId, op.status, op.updatedAt]);
|
|
27
35
|
return;
|
|
28
36
|
case 'upsertReview':
|
|
29
|
-
db.run(`INSERT INTO
|
|
37
|
+
db.run(`INSERT INTO looms_run_reviews (run_id, review_id, status, title, updated_at)
|
|
30
38
|
VALUES (?, ?, ?, ?, ?)
|
|
31
|
-
ON CONFLICT (review_id) DO UPDATE SET
|
|
39
|
+
ON CONFLICT (run_id, review_id) DO UPDATE SET
|
|
32
40
|
status = excluded.status,
|
|
33
|
-
updated_at = excluded.updated_at`, [op.
|
|
41
|
+
updated_at = excluded.updated_at`, [op.runId, op.reviewId, op.status, op.title, op.updatedAt]);
|
|
34
42
|
return;
|
|
35
43
|
case 'setReviewStatus':
|
|
36
|
-
db.run(`UPDATE
|
|
37
|
-
op.status,
|
|
38
|
-
op.updatedAt,
|
|
39
|
-
op.reviewId,
|
|
40
|
-
]);
|
|
44
|
+
db.run(`UPDATE looms_run_reviews SET status = ?, updated_at = ? WHERE run_id = ? AND review_id = ?`, [op.status, op.updatedAt, op.runId, op.reviewId]);
|
|
41
45
|
return;
|
|
42
46
|
default: {
|
|
43
47
|
const exhaustiveCheck = op;
|
|
@@ -60,13 +64,14 @@ export function sqlite(options = {}) {
|
|
|
60
64
|
});
|
|
61
65
|
apply(ops);
|
|
62
66
|
}),
|
|
63
|
-
getActor: (actorId) => runProjectorSync('sqlite', () => {
|
|
67
|
+
getActor: (runId, actorId) => runProjectorSync('sqlite', () => {
|
|
64
68
|
const row = db
|
|
65
|
-
.query(`SELECT actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
66
|
-
FROM
|
|
67
|
-
.get(actorId);
|
|
69
|
+
.query(`SELECT run_id, actor_id, kind, status, definition_name, parent_actor_id, updated_at
|
|
70
|
+
FROM looms_run_actors WHERE run_id = ? AND actor_id = ?`)
|
|
71
|
+
.get(runId, actorId);
|
|
68
72
|
return row
|
|
69
73
|
? {
|
|
74
|
+
runId: row.run_id,
|
|
70
75
|
actorId: row.actor_id,
|
|
71
76
|
kind: row.kind,
|
|
72
77
|
status: row.status,
|
|
@@ -76,18 +81,18 @@ export function sqlite(options = {}) {
|
|
|
76
81
|
}
|
|
77
82
|
: null;
|
|
78
83
|
}),
|
|
79
|
-
listReviews: (
|
|
80
|
-
const rows =
|
|
84
|
+
listReviews: (runId) => runProjectorSync('sqlite', () => {
|
|
85
|
+
const rows = runId
|
|
81
86
|
? db
|
|
82
|
-
.query(`SELECT
|
|
83
|
-
FROM
|
|
84
|
-
.all(
|
|
87
|
+
.query(`SELECT run_id, review_id, status, title, updated_at
|
|
88
|
+
FROM looms_run_reviews WHERE run_id = ?`)
|
|
89
|
+
.all(runId)
|
|
85
90
|
: db
|
|
86
|
-
.query(`SELECT
|
|
91
|
+
.query(`SELECT run_id, review_id, status, title, updated_at FROM looms_run_reviews`)
|
|
87
92
|
.all();
|
|
88
93
|
return rows.map((row) => ({
|
|
94
|
+
runId: row.run_id,
|
|
89
95
|
reviewId: row.review_id,
|
|
90
|
-
actorId: row.actor_id,
|
|
91
96
|
status: row.status,
|
|
92
97
|
title: row.title,
|
|
93
98
|
updatedAt: row.updated_at,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isJsonObject, isJsonString } from '@swirls/looms/core';
|
|
1
|
+
import { completedRunStatus, isJsonObject, isJsonString, } from '@swirls/looms/core';
|
|
2
2
|
import { emptyTables } from './tables.js';
|
|
3
3
|
function payloadObject(event) {
|
|
4
4
|
return isJsonObject(event.payload) ? event.payload : {};
|
|
@@ -7,6 +7,16 @@ function readString(obj, key) {
|
|
|
7
7
|
const value = obj[key];
|
|
8
8
|
return isJsonString(value) ? value : undefined;
|
|
9
9
|
}
|
|
10
|
+
function setRunStatus(tables, runId, status) {
|
|
11
|
+
const existing = tables.runs.get(runId);
|
|
12
|
+
tables.runs.set(runId, {
|
|
13
|
+
runId,
|
|
14
|
+
status,
|
|
15
|
+
rootThreadId: existing?.rootThreadId ?? null,
|
|
16
|
+
kind: existing?.kind ?? null,
|
|
17
|
+
definitionName: existing?.definitionName ?? null,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
10
20
|
export function materializeEvents(events, from = emptyTables()) {
|
|
11
21
|
const tables = {
|
|
12
22
|
runs: from.runs,
|
|
@@ -28,17 +38,12 @@ export function materializeEvents(events, from = emptyTables()) {
|
|
|
28
38
|
tables.runs.set(event.runId, next);
|
|
29
39
|
break;
|
|
30
40
|
}
|
|
31
|
-
case 'runtime.run.completed':
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
rootThreadId: existing?.rootThreadId ?? null,
|
|
37
|
-
kind: existing?.kind ?? null,
|
|
38
|
-
definitionName: existing?.definitionName ?? null,
|
|
39
|
-
});
|
|
41
|
+
case 'runtime.run.completed':
|
|
42
|
+
setRunStatus(tables, event.runId, completedRunStatus(payload));
|
|
43
|
+
break;
|
|
44
|
+
case 'runtime.run.cancelled':
|
|
45
|
+
setRunStatus(tables, event.runId, 'cancelled');
|
|
40
46
|
break;
|
|
41
|
-
}
|
|
42
47
|
case 'runtime.thread.started': {
|
|
43
48
|
const threadId = readString(payload, 'threadId') ?? event.threadId;
|
|
44
49
|
const kind = readString(payload, 'kind');
|
package/dist/react/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type ProjectionDefinition, type ThreadTree } from '@swirls/looms/core';
|
|
2
|
+
import { type ProjectionDefinition, type RunStatus, type ThreadTree } from '@swirls/looms/core';
|
|
3
3
|
import { createFold, foldEvents, type EventFoldDefinition } from './derived';
|
|
4
4
|
import type { AnyEventEnvelope, RegisteredEvent } from './register';
|
|
5
5
|
import { createLoomsStore, type CoalesceOption, type LoomsClientStore, type LoomsConnectionStatus } from './store';
|
|
@@ -30,7 +30,11 @@ export declare function useProjection<S, TEvent extends AnyEventEnvelope = Regis
|
|
|
30
30
|
/** Subscribe to arbitrary store derivations with an equality check. */
|
|
31
31
|
export declare function useRunSelector<Selected, TEvent extends AnyEventEnvelope = RegisteredEvent>(store: LoomsClientStore<TEvent>, selector: (store: LoomsClientStore<TEvent>) => Selected, isEqual?: (a: Selected, b: Selected) => boolean): Selected;
|
|
32
32
|
export interface RunSummary {
|
|
33
|
-
readonly status:
|
|
33
|
+
readonly status: RunStatus;
|
|
34
|
+
/** True once the run is completed, failed, or cancelled and accepts no new signals. */
|
|
35
|
+
readonly finished: boolean;
|
|
36
|
+
/** True when the run failed or was cancelled, so a fork can continue it from where it stopped. */
|
|
37
|
+
readonly resumable: boolean;
|
|
34
38
|
readonly kind: string | undefined;
|
|
35
39
|
readonly definitionName: string | undefined;
|
|
36
40
|
readonly rootThreadId: string | undefined;
|
package/dist/react/react.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Fiber } from 'effect';
|
|
2
2
|
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore, } from 'react';
|
|
3
|
-
import { threadTree, toThreadTree } from '@swirls/looms/core';
|
|
3
|
+
import { isTerminalStatus, threadTree, toThreadTree, } from '@swirls/looms/core';
|
|
4
4
|
import { createFold, foldEvents } from './derived.js';
|
|
5
5
|
import { createLoomsStore, } from './store.js';
|
|
6
6
|
const LoomsContext = createContext({ endpoint: '' });
|
|
@@ -125,8 +125,11 @@ function shallowEqualSummary(a, b) {
|
|
|
125
125
|
export function useRunSummary(store) {
|
|
126
126
|
const selector = useCallback((s) => {
|
|
127
127
|
const run = s.getState().runs.get(s.storeId);
|
|
128
|
+
const status = run?.status ?? 'running';
|
|
128
129
|
return {
|
|
129
|
-
status
|
|
130
|
+
status,
|
|
131
|
+
finished: isTerminalStatus(status),
|
|
132
|
+
resumable: status === 'failed' || status === 'cancelled',
|
|
130
133
|
kind: run?.kind ?? undefined,
|
|
131
134
|
definitionName: run?.definitionName ?? undefined,
|
|
132
135
|
rootThreadId: run?.rootThreadId ?? undefined,
|
package/dist/react/tables.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { RunStatus, ThreadStatus } from '@swirls/looms/core';
|
|
1
2
|
export interface RunRow {
|
|
2
3
|
runId: string;
|
|
3
|
-
status:
|
|
4
|
+
status: RunStatus;
|
|
4
5
|
rootThreadId: string | null;
|
|
5
6
|
kind: string | null;
|
|
6
7
|
definitionName: string | null;
|
|
@@ -11,7 +12,7 @@ export interface ThreadRow {
|
|
|
11
12
|
kind: string;
|
|
12
13
|
definitionName: string;
|
|
13
14
|
parentThreadId: string | null;
|
|
14
|
-
status:
|
|
15
|
+
status: ThreadStatus;
|
|
15
16
|
}
|
|
16
17
|
/** Materialized run/thread rows. Event log lives only in `store.events()` / EventIndex. */
|
|
17
18
|
export interface MaterializedTables {
|
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
import { Clock, Effect } from 'effect';
|
|
2
|
-
import { createEvent, EventStoreTag, foldRun, } from '@swirls/looms/core';
|
|
2
|
+
import { createEvent, descendantThreadIds, EventStoreTag, foldRun, } from '@swirls/looms/core';
|
|
3
3
|
import { RuntimeExecutionError } from './errors.js';
|
|
4
4
|
import { waitSatisfiedEvents, workerTask } from './helpers.js';
|
|
5
5
|
import { notifyObserver } from './observer.js';
|
|
6
6
|
import { isWorkerEffect } from './worker-effect-dispatch.js';
|
|
7
|
-
function descendantThreadIds(state, targetThreadId) {
|
|
8
|
-
return Object.values(state.threads)
|
|
9
|
-
.filter((record) => {
|
|
10
|
-
let parentThreadId = record.parentThreadId;
|
|
11
|
-
while (parentThreadId) {
|
|
12
|
-
if (parentThreadId === targetThreadId) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
parentThreadId = state.threads[parentThreadId]?.parentThreadId ?? null;
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
})
|
|
19
|
-
.map((record) => record.threadId);
|
|
20
|
-
}
|
|
21
7
|
/**
|
|
22
8
|
* Cancel a thread and its descendants. Worker attempts in flight are asked to stop and must
|
|
23
9
|
* acknowledge; attempts waiting to retry were never handed out and are cancelled outright.
|
|
24
10
|
*/
|
|
25
11
|
function planCancellation(input) {
|
|
26
|
-
const { runId, state, registry, now } = input;
|
|
12
|
+
const { runId, seq, state, registry, now } = input;
|
|
27
13
|
const targetThreadId = input.threadId ?? state.rootThreadId ?? '';
|
|
28
14
|
const idempotencyKey = `cancel:${targetThreadId}`;
|
|
29
15
|
if (state.processedIdempotencyKeys.includes(idempotencyKey)) {
|
|
@@ -65,18 +51,18 @@ function planCancellation(input) {
|
|
|
65
51
|
idempotencyKey,
|
|
66
52
|
}));
|
|
67
53
|
}
|
|
68
|
-
const threadEvents = [
|
|
69
|
-
...descendants.map((_, index) => descendants[descendants.length - 1 - index]),
|
|
70
|
-
targetThreadId,
|
|
71
|
-
]
|
|
54
|
+
const threadEvents = [...descendants.toReversed(), targetThreadId]
|
|
72
55
|
.filter((threadId) => threadId.length > 0)
|
|
73
|
-
.map((threadId) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
56
|
+
.map((threadId) => {
|
|
57
|
+
const payload = { threadId, reason: 'cancelled' };
|
|
58
|
+
return createEvent(runId, {
|
|
59
|
+
type: 'runtime.thread.cancelled',
|
|
60
|
+
payload: threadId === targetThreadId ? { ...payload, resumeSeq: seq } : payload,
|
|
61
|
+
threadId,
|
|
62
|
+
origin: { type: 'external' },
|
|
63
|
+
idempotencyKey,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
80
66
|
const batch = [...effectEvents, ...threadEvents];
|
|
81
67
|
const folded = foldRun(batch, registry, { runId, initial: state });
|
|
82
68
|
const satisfied = waitSatisfiedEvents(folded, batch).map((event) => createEvent(runId, event));
|
|
@@ -91,6 +77,7 @@ export function createCancellation(options) {
|
|
|
91
77
|
return yield* repository.commit(store, runId, (current) => {
|
|
92
78
|
const plan = planCancellation({
|
|
93
79
|
runId,
|
|
80
|
+
seq: current.seq,
|
|
94
81
|
state: current.state,
|
|
95
82
|
threadId,
|
|
96
83
|
registry,
|
package/dist/runtime/errors.d.ts
CHANGED
|
@@ -19,6 +19,45 @@ export declare class StartRunConflictError extends StartRunConflictError_base<{
|
|
|
19
19
|
}> {
|
|
20
20
|
constructor(runId: string, requested: RunStartIdentity, existing: DurableRunStartIdentity, mismatches: readonly (keyof RunStartIdentity)[]);
|
|
21
21
|
}
|
|
22
|
+
declare const RunTerminalError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
23
|
+
readonly _tag: "RunTerminalError";
|
|
24
|
+
} & Readonly<A>;
|
|
25
|
+
export declare class RunTerminalError extends RunTerminalError_base<{
|
|
26
|
+
readonly runId: string;
|
|
27
|
+
readonly status: string;
|
|
28
|
+
readonly message: string;
|
|
29
|
+
}> {
|
|
30
|
+
constructor(runId: string, status: string);
|
|
31
|
+
}
|
|
32
|
+
declare const RunNotFoundError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
33
|
+
readonly _tag: "RunNotFoundError";
|
|
34
|
+
} & Readonly<A>;
|
|
35
|
+
export declare class RunNotFoundError extends RunNotFoundError_base<{
|
|
36
|
+
readonly runId: string;
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}> {
|
|
39
|
+
constructor(runId: string);
|
|
40
|
+
}
|
|
41
|
+
declare const RunNotFinishedError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
42
|
+
readonly _tag: "RunNotFinishedError";
|
|
43
|
+
} & Readonly<A>;
|
|
44
|
+
export declare class RunNotFinishedError extends RunNotFinishedError_base<{
|
|
45
|
+
readonly runId: string;
|
|
46
|
+
readonly status: string;
|
|
47
|
+
readonly message: string;
|
|
48
|
+
}> {
|
|
49
|
+
constructor(runId: string, status: string);
|
|
50
|
+
}
|
|
51
|
+
declare const ForkConflictError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
52
|
+
readonly _tag: "ForkConflictError";
|
|
53
|
+
} & Readonly<A>;
|
|
54
|
+
export declare class ForkConflictError extends ForkConflictError_base<{
|
|
55
|
+
readonly runId: string;
|
|
56
|
+
readonly sourceRunId: string;
|
|
57
|
+
readonly message: string;
|
|
58
|
+
}> {
|
|
59
|
+
constructor(runId: string, sourceRunId: string);
|
|
60
|
+
}
|
|
22
61
|
declare const DuplicateEffectDispatchError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
23
62
|
readonly _tag: "DuplicateEffectDispatchError";
|
|
24
63
|
} & Readonly<A>;
|
package/dist/runtime/errors.js
CHANGED
|
@@ -11,6 +11,38 @@ export class StartRunConflictError extends Data.TaggedError('StartRunConflictErr
|
|
|
11
11
|
this.name = 'StartRunConflictError';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
export class RunTerminalError extends Data.TaggedError('RunTerminalError') {
|
|
15
|
+
constructor(runId, status) {
|
|
16
|
+
super({ runId, status, message: `Run "${runId}" is ${status} and accepts no new events` });
|
|
17
|
+
this.name = 'RunTerminalError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class RunNotFoundError extends Data.TaggedError('RunNotFoundError') {
|
|
21
|
+
constructor(runId) {
|
|
22
|
+
super({ runId, message: `Run "${runId}" does not exist` });
|
|
23
|
+
this.name = 'RunNotFoundError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class RunNotFinishedError extends Data.TaggedError('RunNotFinishedError') {
|
|
27
|
+
constructor(runId, status) {
|
|
28
|
+
super({
|
|
29
|
+
runId,
|
|
30
|
+
status,
|
|
31
|
+
message: `Run "${runId}" is ${status}; only finished runs can be forked`,
|
|
32
|
+
});
|
|
33
|
+
this.name = 'RunNotFinishedError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class ForkConflictError extends Data.TaggedError('ForkConflictError') {
|
|
37
|
+
constructor(runId, sourceRunId) {
|
|
38
|
+
super({
|
|
39
|
+
runId,
|
|
40
|
+
sourceRunId,
|
|
41
|
+
message: `Run "${runId}" already exists and is not a fork of "${sourceRunId}"`,
|
|
42
|
+
});
|
|
43
|
+
this.name = 'ForkConflictError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
14
46
|
export class DuplicateEffectDispatchError extends Data.TaggedError('DuplicateEffectDispatchError') {
|
|
15
47
|
constructor(runId, effectId, threadId) {
|
|
16
48
|
super({
|
|
@@ -43,6 +43,8 @@ export declare function threadStartedEvents(definitions: ReadonlyMap<string, Reg
|
|
|
43
43
|
parentThreadId: string | null;
|
|
44
44
|
}): Effect.Effect<ReadonlyArray<EventInput>, UnknownDefinitionError>;
|
|
45
45
|
export declare function synthesizedThreadFailed(before: RunState, after: RunState, written: readonly EventEnvelope[]): EventInput[];
|
|
46
|
+
/** Cancel whatever is still live below each thread `written` cancels, deepest first. */
|
|
47
|
+
export declare function cascadedCancellations(state: RunState, written: readonly EventEnvelope[]): EventInput[];
|
|
46
48
|
export declare function waitSatisfiedEvents(state: RunState, events: readonly EventEnvelope[]): EventInput[];
|
|
47
49
|
export declare function createEffectFailedEvent(runId: string, item: {
|
|
48
50
|
effectId: string;
|
package/dist/runtime/helpers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Data, Effect, Layer, Predicate } from 'effect';
|
|
2
|
-
import { asJson, createEvent, definitionKey, makeMemorySnapshotStore, matchingWaits, snapshotStoreOf, validateEventInput, validateEventsEffect, validateInputEffect, withSnapshotStore, InvalidEventError, } from '@swirls/looms/core';
|
|
2
|
+
import { asJson, createEvent, definitionKey, descendantThreadIds, isTerminalStatus, makeMemorySnapshotStore, matchingWaits, snapshotStoreOf, validateEventInput, validateEventsEffect, validateInputEffect, withSnapshotStore, InvalidEventError, } from '@swirls/looms/core';
|
|
3
3
|
export function workerTask(runId, item, attempt) {
|
|
4
4
|
return {
|
|
5
5
|
runId,
|
|
@@ -180,6 +180,28 @@ export function synthesizedThreadFailed(before, after, written) {
|
|
|
180
180
|
}
|
|
181
181
|
return out;
|
|
182
182
|
}
|
|
183
|
+
/** Cancel whatever is still live below each thread `written` cancels, deepest first. */
|
|
184
|
+
export function cascadedCancellations(state, written) {
|
|
185
|
+
const cancelled = written.flatMap((event) => {
|
|
186
|
+
if (event.type !== 'runtime.thread.cancelled') {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const payload = Predicate.isObject(event.payload) ? event.payload : {};
|
|
190
|
+
const threadId = Predicate.isString(payload.threadId) ? payload.threadId : event.threadId;
|
|
191
|
+
return threadId ? [threadId] : [];
|
|
192
|
+
});
|
|
193
|
+
const live = new Set(cancelled
|
|
194
|
+
.flatMap((threadId) => descendantThreadIds(state, threadId))
|
|
195
|
+
.filter((threadId) => {
|
|
196
|
+
const record = state.threads[threadId];
|
|
197
|
+
return record !== undefined && !isTerminalStatus(record.status);
|
|
198
|
+
}));
|
|
199
|
+
return [...live].toReversed().map((threadId) => ({
|
|
200
|
+
type: 'runtime.thread.cancelled',
|
|
201
|
+
payload: { threadId, reason: 'cancelled' },
|
|
202
|
+
threadId,
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
183
205
|
export function waitSatisfiedEvents(state, events) {
|
|
184
206
|
const produced = [];
|
|
185
207
|
const remaining = { ...state.waits };
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, MaxWakeIterationsError, RunOverloadedError, StartRunConflictError, type CreateRuntimeOptions, type EffectWorker, type EffectWorkerTask, type LoomsRuntime, type RunOperationalStatus, type RegisteredDefinition, type StartRunArgs, type WakeError, type WakeScheduler, type WorkerCallback, type WorkerCallbackInput, } from './runtime';
|
|
1
|
+
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, type CreateRuntimeOptions, type EffectWorker, type EffectWorkerTask, type LoomsRuntime, type RunOperationalStatus, type RegisteredDefinition, type StartRunArgs, type WakeError, type WakeScheduler, type WorkerCallback, type WorkerCallbackInput, } from './runtime';
|
|
2
2
|
export { createLooms, type Looms, type CreateLoomsOptions, type StartResult } from './looms';
|
|
3
3
|
export { serveHttp, createFetchHandler, isLoomsApiPath, type FetchHandlerOptions, authAllowed, authDenied, bearerAuth, defaultAuthorize, type AuthDecision, type AuthorizedAccess, type Authorize, type BearerTokens, type RouteAccess, type RouteInfo, type RunningServer, type LoomsFetchResult, } from './server';
|
|
4
4
|
export { createEventStreamResponse, type EventStreamOptions } from './sse';
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, MaxWakeIterationsError, RunOverloadedError, StartRunConflictError, } from './runtime.js';
|
|
1
|
+
export { createRuntime, createTimeoutScheduler, DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunOverloadedError, RunTerminalError, StartRunConflictError, } from './runtime.js';
|
|
2
2
|
export { createLooms } from './looms.js';
|
|
3
3
|
export { serveHttp, createFetchHandler, isLoomsApiPath, authAllowed, authDenied, bearerAuth, defaultAuthorize, } from './server.js';
|
|
4
4
|
export { createEventStreamResponse } from './sse.js';
|
package/dist/runtime/looms.d.ts
CHANGED
|
@@ -89,6 +89,10 @@ export interface Looms {
|
|
|
89
89
|
project<S>(runId: string, definition: ProjectionDefinition<S>): Promise<S>;
|
|
90
90
|
replayTo(runId: string, seq: number): Promise<ReplayStep | null>;
|
|
91
91
|
cancel(runId: string, threadId?: string): Promise<RunState>;
|
|
92
|
+
/** Start a new run that continues where a finished run ended. */
|
|
93
|
+
fork(sourceRunId: string, options?: {
|
|
94
|
+
runId?: string;
|
|
95
|
+
}): Promise<StartResult>;
|
|
92
96
|
workerCallback(runId: string, input: WorkerCallbackInput): Promise<RunState>;
|
|
93
97
|
fetch(req: Request): Promise<Response | null>;
|
|
94
98
|
serve(options?: {
|
package/dist/runtime/looms.js
CHANGED
|
@@ -98,6 +98,7 @@ export function createLooms(options = {}) {
|
|
|
98
98
|
project: (runId, definition) => runEffect((i) => i.runtime.project(runId, definition)),
|
|
99
99
|
replayTo: (runId, seq) => runEffect((i) => i.runtime.replayTo(runId, seq)),
|
|
100
100
|
cancel: (runId, threadId) => runEffect((i) => i.runtime.cancel(runId, threadId)),
|
|
101
|
+
fork: (sourceRunId, forkOpts) => runEffect((i) => i.runtime.fork(sourceRunId, forkOpts)),
|
|
101
102
|
workerCallback: (runId, input) => runEffect((i) => i.runtime.workerCallback(runId, input)),
|
|
102
103
|
fetch: (req) => {
|
|
103
104
|
if (!handlesPath(new URL(req.url).pathname, debuggerMount)) {
|
|
@@ -5,9 +5,7 @@ function canonicalJson(value) {
|
|
|
5
5
|
return `[${value.map(canonicalJson).join(',')}]`;
|
|
6
6
|
}
|
|
7
7
|
if (Predicate.isObject(value)) {
|
|
8
|
-
const keys = Object.keys(value);
|
|
9
|
-
// oxlint-disable-next-line unicorn/no-array-sort
|
|
10
|
-
keys.sort();
|
|
8
|
+
const keys = Object.keys(value).toSorted();
|
|
11
9
|
return `{${keys
|
|
12
10
|
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key] ?? null)}`)
|
|
13
11
|
.join(',')}}`;
|
|
@@ -2,7 +2,7 @@ import { type AnyRuntimeModule } from '@swirls/looms/core';
|
|
|
2
2
|
export type { RegisteredDefinition } from '@swirls/looms/core';
|
|
3
3
|
export type { WakeScheduler } from './wake-scheduler';
|
|
4
4
|
export { createTimeoutScheduler } from './wake-scheduler';
|
|
5
|
-
export { DuplicateEffectDispatchError, MaxWakeIterationsError, StartRunConflictError, } from './errors';
|
|
5
|
+
export { DuplicateEffectDispatchError, ForkConflictError, MaxWakeIterationsError, RunNotFinishedError, RunNotFoundError, RunTerminalError, StartRunConflictError, } from './errors';
|
|
6
6
|
export { RunOverloadedError } from './ingress';
|
|
7
7
|
export type { CreateRuntimeOptions, EffectWorker, EffectWorkerTask, LoomsRuntime, RunOperationalStatus, StartResult, StartRunArgs, WakeError, } from './types';
|
|
8
8
|
export type { WorkerCallback, WorkerCallbackInput } from '@swirls/looms/core';
|