@nestos/kanban 0.5.1 → 0.5.2
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/LICENSE +674 -0
- package/README.md +73 -29
- package/dist/{activity-C6xDM-cg.d.cts → activity-BbC4sgKL.d.cts} +28 -2
- package/dist/{activity-Dw8DOw0L.d.ts → activity-Czlan5xW.d.ts} +28 -2
- package/dist/activity-module-DaO1Rry0.d.cts +22 -0
- package/dist/activity-module-T22wliZu.d.ts +22 -0
- package/dist/activity-ports-Dqxa86zT.d.cts +12 -0
- package/dist/activity-ports-cxfrelaL.d.ts +12 -0
- package/dist/activity.cjs +10 -282
- package/dist/activity.cjs.map +1 -1
- package/dist/activity.d.cts +8 -30
- package/dist/activity.d.ts +8 -30
- package/dist/activity.js +9 -281
- package/dist/activity.js.map +1 -1
- package/dist/{chunk-O4OGAAV4.js → chunk-3JYLJPE5.js} +29 -5
- package/dist/chunk-3JYLJPE5.js.map +1 -0
- package/dist/{chunk-ATUFFNLV.js → chunk-3LEIHB7Y.js} +5 -5
- package/dist/chunk-4XF6LQSU.cjs.map +1 -1
- package/dist/chunk-5Q7WB63A.cjs +282 -0
- package/dist/chunk-5Q7WB63A.cjs.map +1 -0
- package/dist/{chunk-PSKQAFDF.cjs → chunk-7KDOTBRX.cjs} +1 -1
- package/dist/chunk-7KDOTBRX.cjs.map +1 -0
- package/dist/{chunk-5DLZWW5Y.js → chunk-AHHEGTUA.js} +3 -3
- package/dist/chunk-AOHQCX7K.cjs +95 -0
- package/dist/chunk-AOHQCX7K.cjs.map +1 -0
- package/dist/{chunk-IUI5CI2S.cjs → chunk-DINLR6E6.cjs} +96 -92
- package/dist/chunk-DINLR6E6.cjs.map +1 -0
- package/dist/{chunk-L44TM4EE.cjs → chunk-EYTVC2H5.cjs} +568 -454
- package/dist/chunk-EYTVC2H5.cjs.map +1 -0
- package/dist/chunk-FEE2YBZ7.js +1972 -0
- package/dist/chunk-FEE2YBZ7.js.map +1 -0
- package/dist/chunk-FNO5K3A7.js +282 -0
- package/dist/chunk-FNO5K3A7.js.map +1 -0
- package/dist/chunk-IHHDXZPZ.js +382 -0
- package/dist/chunk-IHHDXZPZ.js.map +1 -0
- package/dist/chunk-J3EAIVA3.js +280 -0
- package/dist/chunk-J3EAIVA3.js.map +1 -0
- package/dist/{chunk-DFTOTBGF.js → chunk-KH3WF3K4.js} +20 -16
- package/dist/chunk-KH3WF3K4.js.map +1 -0
- package/dist/{chunk-TB2RZGDV.js → chunk-M35X5PHO.js} +14 -1
- package/dist/chunk-M35X5PHO.js.map +1 -0
- package/dist/chunk-QRGZD2JU.cjs +1972 -0
- package/dist/chunk-QRGZD2JU.cjs.map +1 -0
- package/dist/{chunk-4KF7KUAI.cjs → chunk-RBOER6EW.cjs} +6 -6
- package/dist/chunk-RBOER6EW.cjs.map +1 -0
- package/dist/{chunk-6MN3H3YF.cjs → chunk-RSH6QSEC.cjs} +16 -3
- package/dist/chunk-RSH6QSEC.cjs.map +1 -0
- package/dist/{chunk-T7P5EIC4.cjs → chunk-VMRDXOLX.cjs} +33 -33
- package/dist/chunk-VMRDXOLX.cjs.map +1 -0
- package/dist/{chunk-5XKOCKY2.js → chunk-X4UHZX6F.js} +1 -1
- package/dist/chunk-XBSGUVCY.cjs +382 -0
- package/dist/chunk-XBSGUVCY.cjs.map +1 -0
- package/dist/{chunk-OHQTKGQN.js → chunk-XKDZHAUE.js} +242 -128
- package/dist/chunk-XKDZHAUE.js.map +1 -0
- package/dist/chunk-ZUZGAO6G.cjs +280 -0
- package/dist/chunk-ZUZGAO6G.cjs.map +1 -0
- package/dist/{content-CAIp1njn.d.cts → content-C5aJZFlw.d.cts} +1 -1
- package/dist/{content-CGMJdyH_.d.ts → content-hLH5BNud.d.ts} +1 -1
- package/dist/contracts/index.cjs +13 -3
- package/dist/contracts/index.cjs.map +1 -1
- package/dist/contracts/index.d.cts +5 -4
- package/dist/contracts/index.d.ts +5 -4
- package/dist/contracts/index.js +14 -4
- package/dist/{discovery-DxpirOz3.d.cts → discovery-9E-LfwIw.d.cts} +1 -1
- package/dist/{discovery-gSqWpuRm.d.ts → discovery-D60yrjej.d.ts} +1 -1
- package/dist/discovery.cjs +63 -65
- package/dist/discovery.cjs.map +1 -1
- package/dist/discovery.d.cts +4 -4
- package/dist/discovery.d.ts +4 -4
- package/dist/discovery.js +5 -7
- package/dist/discovery.js.map +1 -1
- package/dist/execution-bridge-fpCnH6z5.d.cts +52 -0
- package/dist/execution-bridge-rk0uVy_n.d.ts +52 -0
- package/dist/execution.cjs +6 -356
- package/dist/execution.cjs.map +1 -1
- package/dist/execution.d.cts +4 -51
- package/dist/execution.d.ts +4 -51
- package/dist/execution.js +9 -359
- package/dist/execution.js.map +1 -1
- package/dist/index.cjs +10 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -16
- package/dist/index.d.ts +9 -16
- package/dist/index.js +10 -12
- package/dist/index.js.map +1 -1
- package/dist/{module-CWNK2BMj.d.ts → module-CYfE7YhM.d.ts} +2 -2
- package/dist/{module-BbvwdCDF.d.cts → module-Ca9L3qCn.d.cts} +2 -2
- package/dist/{planning-CcFZYdIL.d.cts → planning-CHrOqQ6b.d.cts} +45 -10
- package/dist/{planning-CcFZYdIL.d.ts → planning-CHrOqQ6b.d.ts} +45 -10
- package/dist/{ports-Bn6ro2FB.d.ts → ports-B0McVg7B.d.ts} +4 -3
- package/dist/ports-BeGO1VRI.d.cts +49 -0
- package/dist/{ports-CaL6geqR.d.cts → ports-GQy0x-RI.d.cts} +4 -3
- package/dist/ports-ykGiNUSx.d.ts +49 -0
- package/dist/runtime/index.cjs +205 -0
- package/dist/runtime/index.cjs.map +1 -0
- package/dist/runtime/index.d.cts +75 -0
- package/dist/runtime/index.d.ts +75 -0
- package/dist/runtime/index.js +205 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/integration.cjs +423 -0
- package/dist/runtime/integration.cjs.map +1 -0
- package/dist/runtime/integration.d.cts +658 -0
- package/dist/runtime/integration.d.ts +658 -0
- package/dist/runtime/integration.js +423 -0
- package/dist/runtime/integration.js.map +1 -0
- package/dist/storage.cjs +9 -277
- package/dist/storage.cjs.map +1 -1
- package/dist/storage.d.cts +6 -6
- package/dist/storage.d.ts +6 -6
- package/dist/storage.js +11 -279
- package/dist/storage.js.map +1 -1
- package/dist/{store-q63eSMoT.d.cts → store-DPwHzGiz.d.cts} +2 -2
- package/dist/{store-Ct0gOAAg.d.ts → store-DgdrPCV9.d.ts} +2 -2
- package/dist/task-projection-CSorlMws.d.ts +11 -0
- package/dist/task-projection-DKKB_ueb.d.cts +11 -0
- package/examples/standalone.ts +22 -0
- package/package.json +117 -87
- package/dist/chunk-2YFPV5GF.cjs +0 -16
- package/dist/chunk-2YFPV5GF.cjs.map +0 -1
- package/dist/chunk-4KF7KUAI.cjs.map +0 -1
- package/dist/chunk-6MN3H3YF.cjs.map +0 -1
- package/dist/chunk-DFTOTBGF.js.map +0 -1
- package/dist/chunk-G6FCDLDF.js +0 -16
- package/dist/chunk-G6FCDLDF.js.map +0 -1
- package/dist/chunk-IUI5CI2S.cjs.map +0 -1
- package/dist/chunk-KYNYPKKH.cjs +0 -71
- package/dist/chunk-KYNYPKKH.cjs.map +0 -1
- package/dist/chunk-L44TM4EE.cjs.map +0 -1
- package/dist/chunk-LSE6JG5I.cjs +0 -20
- package/dist/chunk-LSE6JG5I.cjs.map +0 -1
- package/dist/chunk-O4OGAAV4.js.map +0 -1
- package/dist/chunk-OHQTKGQN.js.map +0 -1
- package/dist/chunk-PSKQAFDF.cjs.map +0 -1
- package/dist/chunk-QTMTBEBA.js +0 -20
- package/dist/chunk-QTMTBEBA.js.map +0 -1
- package/dist/chunk-T7P5EIC4.cjs.map +0 -1
- package/dist/chunk-TB2RZGDV.js.map +0 -1
- package/dist/ui/index.cjs +0 -1961
- package/dist/ui/index.cjs.map +0 -1
- package/dist/ui/index.d.cts +0 -209
- package/dist/ui/index.d.ts +0 -209
- package/dist/ui/index.js +0 -1961
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/kanban.css +0 -194
- /package/dist/{chunk-ATUFFNLV.js.map → chunk-3LEIHB7Y.js.map} +0 -0
- /package/dist/{chunk-5DLZWW5Y.js.map → chunk-AHHEGTUA.js.map} +0 -0
- /package/dist/{chunk-5XKOCKY2.js.map → chunk-X4UHZX6F.js.map} +0 -0
package/dist/execution.cjs
CHANGED
|
@@ -1,368 +1,18 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var _chunk4XF6LQSUcjs = require('./chunk-4XF6LQSU.cjs');
|
|
4
3
|
|
|
5
4
|
|
|
6
|
-
var _chunk2YFPV5GFcjs = require('./chunk-2YFPV5GF.cjs');
|
|
7
5
|
|
|
8
6
|
|
|
9
|
-
var
|
|
7
|
+
var _chunkXBSGUVCYcjs = require('./chunk-XBSGUVCY.cjs');
|
|
8
|
+
require('./chunk-4XF6LQSU.cjs');
|
|
9
|
+
require('./chunk-RSH6QSEC.cjs');
|
|
10
|
+
require('./chunk-AOHQCX7K.cjs');
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
var _chunk6MN3H3YFcjs = require('./chunk-6MN3H3YF.cjs');
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
var _chunkLSE6JG5Icjs = require('./chunk-LSE6JG5I.cjs');
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
async function createKanbanExecutionBridge(options) {
|
|
20
|
-
const path = `${options.directory}/executions.seq`;
|
|
21
|
-
try {
|
|
22
|
-
await options.fs.driver.createFile({ parentPath: options.directory, name: "executions.seq", type: "seqfile", recursive: true });
|
|
23
|
-
} catch (error) {
|
|
24
|
-
if (_optionalChain([error, 'optionalAccess', _ => _.code]) !== "EEXIST") throw error;
|
|
25
|
-
}
|
|
26
|
-
if (!_optionalChain([options, 'access', _2 => _2.fs, 'access', _3 => _3.meta, 'access', _4 => _4.seq, 'optionalAccess', _5 => _5.transaction])) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("TRANSACTION_REQUIRED");
|
|
27
|
-
let stopped = false, again = false, pending;
|
|
28
|
-
const writes = /* @__PURE__ */ new Set();
|
|
29
|
-
const failures = /* @__PURE__ */ new Map();
|
|
30
|
-
const track = (operation) => {
|
|
31
|
-
const tracked = operation.finally(() => {
|
|
32
|
-
writes.delete(tracked);
|
|
33
|
-
});
|
|
34
|
-
writes.add(tracked);
|
|
35
|
-
return tracked;
|
|
36
|
-
};
|
|
37
|
-
const active = () => !stopped;
|
|
38
|
-
const scan = async () => {
|
|
39
|
-
if (stopped) return;
|
|
40
|
-
if (pending) {
|
|
41
|
-
again = true;
|
|
42
|
-
return pending;
|
|
43
|
-
}
|
|
44
|
-
const started = Date.now();
|
|
45
|
-
pending = runBridge(options, path, active, track, failures).catch((error) => {
|
|
46
|
-
try {
|
|
47
|
-
options.log({ operationId: _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), stage: "bridge.scan", result: "failure", code: safeCode(error), durationMs: Date.now() - started });
|
|
48
|
-
} catch (e) {
|
|
49
|
-
}
|
|
50
|
-
throw error;
|
|
51
|
-
}).finally(() => {
|
|
52
|
-
pending = void 0;
|
|
53
|
-
if (again && !stopped) {
|
|
54
|
-
again = false;
|
|
55
|
-
void scan().catch(() => {
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
return pending;
|
|
60
|
-
};
|
|
61
|
-
const unsubscribe = _optionalChain([options, 'access', _6 => _6.subscribe, 'optionalCall', _7 => _7(() => {
|
|
62
|
-
void scan().catch(() => {
|
|
63
|
-
});
|
|
64
|
-
})]);
|
|
65
|
-
return { scan, tick: scan, async quiesce() {
|
|
66
|
-
stopped = true;
|
|
67
|
-
_optionalChain([unsubscribe, 'optionalCall', _8 => _8()]);
|
|
68
|
-
if (!pending) return;
|
|
69
|
-
let timer;
|
|
70
|
-
try {
|
|
71
|
-
await Promise.race([pending, new Promise((resolve) => {
|
|
72
|
-
timer = setTimeout(resolve, _nullishCoalesce(options.shutdownTimeoutMs, () => ( 5e3)));
|
|
73
|
-
})]);
|
|
74
|
-
} finally {
|
|
75
|
-
if (timer) clearTimeout(timer);
|
|
76
|
-
await Promise.allSettled([...writes]);
|
|
77
|
-
}
|
|
78
|
-
} };
|
|
79
|
-
}
|
|
80
|
-
async function runBridge(options, path, active, track, failures) {
|
|
81
|
-
const workflow = await options.service.workflow(options.boardId);
|
|
82
|
-
const live = new Set(Object.values(workflow.dispatches).filter((dispatch) => ["prepared", "running", "unknown"].includes(dispatch.outcome)).map((dispatch) => dispatch.id));
|
|
83
|
-
for (const id of failures.keys()) if (!live.has(id)) failures.delete(id);
|
|
84
|
-
for (const dispatch of Object.values(workflow.dispatches)) {
|
|
85
|
-
if (!active()) return;
|
|
86
|
-
if (live.has(dispatch.id) && (_nullishCoalesce(_optionalChain([failures, 'access', _9 => _9.get, 'call', _10 => _10(dispatch.id), 'optionalAccess', _11 => _11.retryAt]), () => ( 0))) <= Date.now()) await runDispatch(options, path, dispatch, active, track, failures);
|
|
87
|
-
}
|
|
88
|
-
const automatic = typeof options.autoDispatch === "function" ? await options.autoDispatch() : options.autoDispatch;
|
|
89
|
-
if (!automatic || !active()) return;
|
|
90
|
-
for (const id of (await options.service.workflow(options.boardId)).ready) {
|
|
91
|
-
if (!active()) return;
|
|
92
|
-
const { item } = await options.service.item(options.boardId, id), owner = _optionalChain([item, 'access', _12 => _12.eligiblePrincipals, 'optionalAccess', _13 => _13[0]]);
|
|
93
|
-
if (!owner || !_chunkKYNYPKKHcjs.taskDue.call(void 0, item, (_nullishCoalesce(options.now, () => ( Date.now)))())) continue;
|
|
94
|
-
const worker = await options.worker(owner), board = await worker.read(options.boardId);
|
|
95
|
-
if (!active()) return;
|
|
96
|
-
if (!_chunkLSE6JG5Icjs.boardAllowsStart.call(void 0, board, item)) continue;
|
|
97
|
-
try {
|
|
98
|
-
const dispatchId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, );
|
|
99
|
-
const estimate = await _optionalChain([options, 'access', _14 => _14.estimate, 'optionalCall', _15 => _15(item, owner)]);
|
|
100
|
-
if (!active()) return;
|
|
101
|
-
await worker.execute(board.id, { operationId: "dispatch:" + dispatchId, expectedRevision: board.revision, command: { type: "prepare-dispatch", itemId: id, dispatchId, estimate, trigger: "automatic" } });
|
|
102
|
-
const dispatch = (await options.service.workflow(board.id)).dispatches[dispatchId];
|
|
103
|
-
if (active()) await runDispatch(options, path, dispatch, active, track, failures);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
if (!["WIP_LIMIT", "ASSIGNEE_WIP_LIMIT", "READY_LIMIT", "REVISION_CONFLICT"].includes(safeCode(error))) throw error;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async function runDispatch(options, path, dispatch, active, track, failures) {
|
|
110
|
-
const start = Date.now();
|
|
111
|
-
let stage = "inspect", result;
|
|
112
|
-
const previousFailure = failures.has(dispatch.id);
|
|
113
|
-
try {
|
|
114
|
-
let record = await loadRecord(options, path, dispatch);
|
|
115
|
-
if (!active()) return;
|
|
116
|
-
if ((!record || record.phase === "prepared") && await _asyncOptionalChain([(await options.service.read(options.boardId)), 'access', async _16 => _16.executionControl, 'optionalAccess', async _17 => _17.paused])) return;
|
|
117
|
-
if (!record) {
|
|
118
|
-
stage = "prepare";
|
|
119
|
-
await options.authorize(stage, dispatch);
|
|
120
|
-
if (!active()) return;
|
|
121
|
-
const item = (await options.service.item(options.boardId, dispatch.itemId)).item;
|
|
122
|
-
if (!active()) return;
|
|
123
|
-
const request = await prepareRequest(options, path, dispatch, item);
|
|
124
|
-
if (!active()) return;
|
|
125
|
-
record = await track(savePrepared(options, path, dispatch, request));
|
|
126
|
-
}
|
|
127
|
-
if (!active()) return;
|
|
128
|
-
const submit = record.phase === "prepared" && await track(claimSubmission(options, path, dispatch));
|
|
129
|
-
if (!active()) return;
|
|
130
|
-
stage = submit ? "start" : "inspect";
|
|
131
|
-
await options.authorize(stage, dispatch);
|
|
132
|
-
if (!active()) return;
|
|
133
|
-
result = await (submit ? options.executor.start(record.request) : options.executor.inspect(record.request));
|
|
134
|
-
failures.delete(dispatch.id);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
const notStarted = _optionalChain([error, 'optionalAccess', _18 => _18.executionOutcome]) === "not-started" && error.retryable === true;
|
|
137
|
-
if (notStarted && stage === "start") await track(releaseUnstartedSubmission(options, path, dispatch));
|
|
138
|
-
result = { outcome: stage === "prepare" && !notStarted ? "failed" : "unknown", code: safeCode(error) };
|
|
139
|
-
if (stage === "inspect" || notStarted) {
|
|
140
|
-
const previous = failures.get(dispatch.id), count = previous && previous.code === result.code ? previous.count + 1 : 1;
|
|
141
|
-
failures.set(dispatch.id, { code: result.code, count, retryAt: Date.now() + Math.min(6e4, 1e3 * 2 ** Math.min(count - 1, 6)) });
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (stage !== "inspect" || _chunk4XF6LQSUcjs.executionObservationChanged.call(void 0, dispatch, result) || previousFailure && !failures.has(dispatch.id))
|
|
145
|
-
try {
|
|
146
|
-
options.log({ operationId: dispatch.id, stage: `bridge.${stage}`, result: ["unknown", "failed", "cancelled"].includes(result.outcome) ? "failure" : "success", code: result.code, ...failures.has(dispatch.id) ? { retryAfterMs: Math.max(0, failures.get(dispatch.id).retryAt - Date.now()) } : {}, durationMs: Date.now() - start });
|
|
147
|
-
} catch (e2) {
|
|
148
|
-
}
|
|
149
|
-
if (active()) try {
|
|
150
|
-
await linkSession(options, dispatch, result.executionSessionId);
|
|
151
|
-
if (active()) await settle(options, dispatch, result);
|
|
152
|
-
} catch (error) {
|
|
153
|
-
if (safeCode(error) !== "REVISION_CONFLICT") throw error;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
async function releaseUnstartedSubmission(options, path, dispatch) {
|
|
157
|
-
await options.fs.meta.seq.transaction(async (tx) => {
|
|
158
|
-
const raw = await tx.getEntry(path, dispatch.id);
|
|
159
|
-
if (!raw) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("EXECUTION_JOURNAL_MISSING");
|
|
160
|
-
const record = decodeRecord(raw, dispatch);
|
|
161
|
-
if (record.phase === "submitting") await tx.setEntry(path, dispatch.id, JSON.stringify({ ...record, phase: "prepared" }));
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
async function prepareRequest(options, path, dispatch, item) {
|
|
165
|
-
if (!dispatch.continuation) return options.executor.prepare(dispatch, item);
|
|
166
|
-
if (!options.executor.prepareContinuation) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("CONTINUATION_UNSUPPORTED");
|
|
167
|
-
const previous = (await options.service.workflow(options.boardId)).dispatches[dispatch.continuation.previousDispatchId];
|
|
168
|
-
if (!previous || previous.itemId !== dispatch.itemId) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("CONTINUATION_SCOPE_MISMATCH");
|
|
169
|
-
const record = await loadRecord(options, path, previous);
|
|
170
|
-
if (!record) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("EXECUTION_JOURNAL_MISSING");
|
|
171
|
-
return options.executor.prepareContinuation(dispatch, item, record.request);
|
|
172
|
-
}
|
|
173
|
-
async function loadRecord(options, path, dispatch) {
|
|
174
|
-
const raw = await options.fs.meta.seq.getEntry(path, dispatch.id);
|
|
175
|
-
if (!raw) return;
|
|
176
|
-
return decodeRecord(raw, dispatch);
|
|
177
|
-
}
|
|
178
|
-
async function savePrepared(options, path, dispatch, request) {
|
|
179
|
-
const rawRequest = JSON.stringify(request);
|
|
180
|
-
if (typeof rawRequest !== "string" || new TextEncoder().encode(rawRequest).byteLength > 1024 * 1024) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("INVALID_EXECUTION_REQUEST");
|
|
181
|
-
return options.fs.meta.seq.transaction(async (tx) => {
|
|
182
|
-
const raw = await tx.getEntry(path, dispatch.id);
|
|
183
|
-
if (raw) return decodeRecord(raw, dispatch);
|
|
184
|
-
const record = { schema: 1, fence: dispatch.fence, request: JSON.parse(rawRequest), digest: _chunk6MN3H3YFcjs.sha256HexSync.call(void 0, rawRequest), phase: "prepared" };
|
|
185
|
-
await tx.setEntry(path, dispatch.id, JSON.stringify(record));
|
|
186
|
-
return record;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
async function claimSubmission(options, path, dispatch) {
|
|
190
|
-
return options.fs.meta.seq.transaction(async (tx) => {
|
|
191
|
-
const raw = await tx.getEntry(path, dispatch.id);
|
|
192
|
-
if (!raw) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("EXECUTION_JOURNAL_MISSING");
|
|
193
|
-
const record = decodeRecord(raw, dispatch);
|
|
194
|
-
if (record.phase !== "prepared") return false;
|
|
195
|
-
await tx.setEntry(path, dispatch.id, JSON.stringify({ ...record, phase: "submitting" }));
|
|
196
|
-
return true;
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
function decodeRecord(raw, dispatch) {
|
|
200
|
-
const record = JSON.parse(raw);
|
|
201
|
-
if (record.schema !== 1 || record.fence !== dispatch.fence || !["prepared", "submitting"].includes(record.phase) || record.digest !== _chunk6MN3H3YFcjs.sha256HexSync.call(void 0, JSON.stringify(record.request))) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("INVALID_EXECUTION_JOURNAL");
|
|
202
|
-
return record;
|
|
203
|
-
}
|
|
204
|
-
async function settle(options, dispatch, result) {
|
|
205
|
-
const current = (await options.service.workflow(options.boardId)).dispatches[dispatch.id];
|
|
206
|
-
if (!current || !["prepared", "running", "unknown"].includes(current.outcome)) return;
|
|
207
|
-
const board = await options.service.read(options.boardId), item = board.items.find((item2) => item2.id === dispatch.itemId);
|
|
208
|
-
const renew = ["running", "unknown"].includes(result.outcome) && (_nullishCoalesce(_optionalChain([item, 'optionalAccess', _19 => _19.owner, 'optionalAccess', _20 => _20.leaseUntil]), () => ( 0))) <= Date.now() + board.policy.leaseMs / 2;
|
|
209
|
-
if (!renew && !_chunk4XF6LQSUcjs.executionObservationChanged.call(void 0, current, result)) return;
|
|
210
|
-
const { outcome, outputs, code, executionSessionId, controlCapabilities } = result, observation = { outcome, ...controlCapabilities ? { controlCapabilities } : {}, ...executionSessionId ? { executionSessionId } : {}, ...outputs ? { outputs } : {}, ...code ? { code } : {} };
|
|
211
|
-
const digest = _chunk6MN3H3YFcjs.sha256HexSync.call(void 0, JSON.stringify(observation));
|
|
212
|
-
await options.service.execute(board.id, {
|
|
213
|
-
operationId: `settle:${dispatch.id}:${digest}:${board.revision}`,
|
|
214
|
-
expectedRevision: board.revision,
|
|
215
|
-
command: { type: "observe-execution", dispatchId: dispatch.id, fence: dispatch.fence, ...observation }
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
async function linkSession(options, dispatch, sessionId) {
|
|
219
|
-
if (!sessionId || !options.executionReference) return;
|
|
220
|
-
const reference = options.executionReference(sessionId, dispatch.id);
|
|
221
|
-
const board = await options.service.read(options.boardId), item = board.items.find((item2) => item2.id === dispatch.itemId);
|
|
222
|
-
if (_optionalChain([item, 'optionalAccess', _21 => _21.owner, 'optionalAccess', _22 => _22.token]) !== dispatch.fence) throw new (0, _chunk2YFPV5GFcjs.KanbanError)("STALE_EXECUTION_RESULT");
|
|
223
|
-
const current = _optionalChain([item, 'access', _23 => _23.externalRefs, 'optionalAccess', _24 => _24.filter, 'call', _25 => _25((ref) => ref.system === reference.system), 'access', _26 => _26.at, 'call', _27 => _27(-1)]);
|
|
224
|
-
if (_optionalChain([current, 'optionalAccess', _28 => _28.id]) === reference.id && current.revision === reference.revision) return;
|
|
225
|
-
await options.service.execute(board.id, {
|
|
226
|
-
operationId: `execution-session:${dispatch.id}:${_chunk6MN3H3YFcjs.sha256HexSync.call(void 0, sessionId)}:${board.revision}`,
|
|
227
|
-
expectedRevision: board.revision,
|
|
228
|
-
command: { type: "link-reference", itemId: dispatch.itemId, reference }
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
function safeCode(error) {
|
|
232
|
-
const code = _nullishCoalesce(_optionalChain([error, 'optionalAccess', _29 => _29.reasonCode]), () => ( _optionalChain([error, 'optionalAccess', _30 => _30.code])));
|
|
233
|
-
return typeof code === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : "EXECUTION_OBSERVATION_FAILED";
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// src/monitor.ts
|
|
237
|
-
function startKanbanMonitor(options) {
|
|
238
|
-
let stopped = false, pending;
|
|
239
|
-
const tick = () => {
|
|
240
|
-
if (stopped || pending) return;
|
|
241
|
-
pending = Promise.resolve().then(() => stopped ? void 0 : options.service.scan()).then(() => stopped ? void 0 : _optionalChain([options, 'access', _31 => _31.onSuccess, 'optionalCall', _32 => _32()])).catch(async (error) => {
|
|
242
|
-
if (stopped) return;
|
|
243
|
-
try {
|
|
244
|
-
await options.onError(error);
|
|
245
|
-
} catch (e3) {
|
|
246
|
-
}
|
|
247
|
-
}).finally(() => {
|
|
248
|
-
pending = void 0;
|
|
249
|
-
});
|
|
250
|
-
};
|
|
251
|
-
const unsubscribe = options.subscribeTick(tick), unwatch = _optionalChain([options, 'access', _33 => _33.subscribeChanges, 'optionalCall', _34 => _34(tick)]);
|
|
252
|
-
if (options.initialScan !== false) tick();
|
|
253
|
-
return { async dispose() {
|
|
254
|
-
if (stopped) {
|
|
255
|
-
await pending;
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
stopped = true;
|
|
259
|
-
try {
|
|
260
|
-
unsubscribe();
|
|
261
|
-
_optionalChain([unwatch, 'optionalCall', _35 => _35()]);
|
|
262
|
-
} finally {
|
|
263
|
-
await pending;
|
|
264
|
-
}
|
|
265
|
-
} };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// src/work-messages.ts
|
|
269
|
-
var _vfscore = require('@nestos/vfs-core');
|
|
270
|
-
async function consumeWorkMessages(fs, directory, scope, journal, deliver, decode, publishReply) {
|
|
271
|
-
const publications = await readPublications(fs, directory, scope, decode), indexKey = "workspace-message-index:" + scope.attemptId;
|
|
272
|
-
const known = await _asyncNullishCoalesce(await journal.get(indexKey), async () => ( []));
|
|
273
|
-
if (known.some((id) => !publications.some((p) => p.message.id === id))) throw protocolError("WORK_MESSAGE_MISSING", "Published workspace message is missing", "ECONFLICT");
|
|
274
|
-
unresolvedWorkIssues(publications.map((p) => p.message));
|
|
275
|
-
publications.sort((a, b) => Number(a.message.type === "resolved") - Number(b.message.type === "resolved"));
|
|
276
|
-
for (const { message, bytes } of publications) {
|
|
277
|
-
const digest = _chunk6MN3H3YFcjs.sha256HexSync.call(void 0, bytes);
|
|
278
|
-
const delivered = await deliverPublication(journal, indexKey, message, digest, deliver);
|
|
279
|
-
if (delivered) await publishReply(directory + "/outbox", "ack-" + message.id + ".yaml", JSON.stringify({ schema: "work-reply/1", ...scope, id: "ack-" + message.id, type: "accepted", replyTo: message.id, content: "Accepted", digest }));
|
|
280
|
-
}
|
|
281
|
-
return publications.map((p) => p.message);
|
|
282
|
-
}
|
|
283
|
-
async function readPublications(fs, directory, scope, decode) {
|
|
284
|
-
const files = await fs.driver.getChildren(directory + "/inbox", { fields: "entry" }), publications = [];
|
|
285
|
-
if (files.length > 200) throw protocolError("WORK_MESSAGE_COUNT_LIMIT", "Workspace inbox exceeds 200 entries");
|
|
286
|
-
for (const file of files.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
287
|
-
if (file.type !== "file" || !/^[a-zA-Z0-9_-]{1,128}\.(yaml|yml|json)$/.test(file.name)) continue;
|
|
288
|
-
const bytes = await readMessageBytes(fs, directory + "/inbox/" + file.name);
|
|
289
|
-
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes), envelope = decode(text);
|
|
290
|
-
if (_optionalChain([envelope, 'optionalAccess', _36 => _36.schema]) === "work-message/2" && Object.hasOwn(envelope, "scopeId")) continue;
|
|
291
|
-
const message = parseWorkMessage(text, scope, decode);
|
|
292
|
-
if (file.name.slice(0, file.name.lastIndexOf(".")) !== message.id) throw protocolError("WORK_MESSAGE_FILENAME_MISMATCH", "Workspace message filename does not match its identity");
|
|
293
|
-
publications.push({ message, bytes });
|
|
294
|
-
}
|
|
295
|
-
return publications;
|
|
296
|
-
}
|
|
297
|
-
async function deliverPublication(journal, indexKey, message, digest, deliver) {
|
|
298
|
-
const key = "workspace-message:" + message.attemptId + ":" + message.id;
|
|
299
|
-
const receipt = await journal.update(key, (old) => {
|
|
300
|
-
if (old && old.digest !== digest) throw protocolError("WORK_MESSAGE_CHANGED", "Published workspace message changed", "ECONFLICT");
|
|
301
|
-
return _nullishCoalesce(old, () => ( { digest, message, delivered: false }));
|
|
302
|
-
});
|
|
303
|
-
await journal.update(indexKey, (old) => _optionalChain([old, 'optionalAccess', _37 => _37.includes, 'call', _38 => _38(message.id)]) ? old : [..._nullishCoalesce(old, () => ( [])), message.id]);
|
|
304
|
-
if (receipt.delivered) return true;
|
|
305
|
-
const outcome = await deliver(receipt.message);
|
|
306
|
-
if (_optionalChain([outcome, 'optionalAccess', _39 => _39.pending])) return false;
|
|
307
|
-
await journal.update(key, (old) => ({ ...old, delivered: true }));
|
|
308
|
-
return true;
|
|
309
|
-
}
|
|
310
|
-
function parseWorkMessage(text, scope, decode) {
|
|
311
|
-
let value;
|
|
312
|
-
try {
|
|
313
|
-
value = decode(text);
|
|
314
|
-
} catch (e4) {
|
|
315
|
-
throw protocolError("WORK_MESSAGE_YAML_INVALID", "Workspace message is not valid bounded YAML");
|
|
316
|
-
}
|
|
317
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw protocolError("WORK_MESSAGE_SCHEMA_INVALID", "Workspace message schema is invalid");
|
|
318
|
-
if ("body" in value) {
|
|
319
|
-
if (Object.hasOwn(value, "content")) throw protocolError("WORK_MESSAGE_FIELDS_INVALID", "Workspace message must not contain both body and content");
|
|
320
|
-
value = { ...value, content: value.body };
|
|
321
|
-
}
|
|
322
|
-
if (Object.keys(value).some((key) => !["schema", "runId", "itemId", "attemptId", "id", "type", "content", "body", "replyTo"].includes(key)))
|
|
323
|
-
throw protocolError("WORK_MESSAGE_FIELDS_INVALID", "Workspace message contains undeclared fields");
|
|
324
|
-
validateEnvelope(value, scope);
|
|
325
|
-
return value;
|
|
326
|
-
}
|
|
327
|
-
function validateEnvelope(value, scope) {
|
|
328
|
-
if (!["work-message/1", "work-message/2"].includes(value.schema)) throw protocolError("WORK_MESSAGE_SCHEMA_INVALID", "Workspace message schema is unsupported");
|
|
329
|
-
if (!["progress", "clarification", "error", "result", ...value.schema === "work-message/2" ? ["resolved"] : []].includes(value.type))
|
|
330
|
-
throw protocolError("WORK_MESSAGE_TYPE_INVALID", "Workspace message schema does not permit this type");
|
|
331
|
-
if (!validId(value.id)) throw protocolError("WORK_MESSAGE_ID_INVALID", "Workspace message identity is invalid");
|
|
332
|
-
if (["runId", "itemId", "attemptId"].some((key) => value[key] !== scope[key]))
|
|
333
|
-
throw protocolError("WORK_MESSAGE_SCOPE_MISMATCH", "Workspace message execution scope is invalid");
|
|
334
|
-
if (typeof value.content !== "string" || !value.content.trim() || value.content.length > 8e3) throw protocolError("WORK_MESSAGE_CONTENT_INVALID", "Workspace message content is invalid");
|
|
335
|
-
if (value.replyTo !== void 0 && !validId(value.replyTo) || value.type === "resolved" && !value.replyTo)
|
|
336
|
-
throw protocolError("WORK_MESSAGE_REPLY_INVALID", "Workspace message reply target is invalid");
|
|
337
|
-
}
|
|
338
|
-
function validId(value) {
|
|
339
|
-
return typeof value === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value);
|
|
340
|
-
}
|
|
341
|
-
function protocolError(reasonCode, message, code = "EINVAL") {
|
|
342
|
-
return Object.assign(new (0, _vfscore.FSError)(code, message), { reasonCode });
|
|
343
|
-
}
|
|
344
|
-
async function readMessageBytes(fs, path) {
|
|
345
|
-
const before = await fs.driver.getNode(path);
|
|
346
|
-
if (_optionalChain([before, 'optionalAccess', _40 => _40.type]) !== "file" || !Number.isSafeInteger(before.size) || before.size < 1) throw protocolError("WORK_MESSAGE_FILE_INVALID", "Workspace message is not a nonempty regular file");
|
|
347
|
-
if (before.size > 65536) throw protocolError("WORK_MESSAGE_SIZE_LIMIT", "Workspace message exceeds 64 KiB");
|
|
348
|
-
const bytes = await fs.driver.readContent(path, { encoding: "binary", length: before.size });
|
|
349
|
-
const after = await fs.driver.getNode(path);
|
|
350
|
-
if (bytes.byteLength !== before.size || _optionalChain([after, 'optionalAccess', _41 => _41.type]) !== "file" || after.size !== before.size || after.revision !== before.revision || after.modifiedAt !== before.modifiedAt)
|
|
351
|
-
throw protocolError("WORK_MESSAGE_READ_CONFLICT", "Workspace message changed while reading", "ECONFLICT");
|
|
352
|
-
return bytes;
|
|
353
|
-
}
|
|
354
|
-
function unresolvedWorkIssues(messages) {
|
|
355
|
-
const issues = messages.filter((m) => m.type === "clarification" || m.type === "error");
|
|
356
|
-
for (const resolution of messages.filter((m) => m.type === "resolved")) {
|
|
357
|
-
if (!issues.some((issue) => issue.id === resolution.replyTo)) throw Object.assign(new Error("Resolution target is not an issue in this attempt"), { code: "WORKSPACE_RESOLUTION_INVALID" });
|
|
358
|
-
}
|
|
359
|
-
return issues.filter((issue) => !messages.some((m) => m.type === "resolved" && m.replyTo === issue.id));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
exports.consumeWorkMessages = consumeWorkMessages; exports.createKanbanExecutionBridge = createKanbanExecutionBridge; exports.parseWorkMessage = parseWorkMessage; exports.startKanbanMonitor = startKanbanMonitor; exports.unresolvedWorkIssues = unresolvedWorkIssues;
|
|
17
|
+
exports.consumeWorkMessages = _chunkXBSGUVCYcjs.consumeWorkMessages; exports.createKanbanExecutionBridge = _chunkXBSGUVCYcjs.createKanbanExecutionBridge; exports.parseWorkMessage = _chunkXBSGUVCYcjs.parseWorkMessage; exports.startKanbanMonitor = _chunkXBSGUVCYcjs.startKanbanMonitor; exports.unresolvedWorkIssues = _chunkXBSGUVCYcjs.unresolvedWorkIssues;
|
|
368
18
|
//# sourceMappingURL=execution.cjs.map
|
package/dist/execution.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/li/share/prj/x1/release/local-libraries/work/1ca933a0-6fe7-4e9c-b13b-76d7f318e0fb/source/libraries/kanban/dist/execution.cjs","../src/execution-bridge.ts","../src/monitor.ts","../src/work-messages.ts"],"names":[],"mappings":"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","file":"/home/li/share/prj/x1/release/local-libraries/work/1ca933a0-6fe7-4e9c-b13b-76d7f318e0fb/source/libraries/kanban/dist/execution.cjs","sourcesContent":[null,"import {boardAllowsStart} from './contracts/board-control';\nimport {taskDue} from './contracts/task-projection';\nimport {sha256HexSync, randomUUID} from './utils';\nimport type {IFileSystem} from '@nestos/vfs-core';\nimport type {KanbanDispatch, KanbanService, KanbanItem, KanbanArtifactRef, KanbanPrincipal, KanbanEstimate} from './contracts';\nimport {KanbanError} from './ports';\nimport {executionObservationChanged} from './execution-observation';\n\nexport interface KanbanExecutionObservation {\n controlCapabilities?: {pause:'boundary'|'none';resume:boolean};\n executionSessionId?: string;\n outcome: 'running' | 'unknown' | 'succeeded' | 'failed' | 'cancelled';\n outputs?: KanbanArtifactRef[]; code?: string;\n}\nexport interface KanbanExecutionPort {\n prepareContinuation?(dispatch: KanbanDispatch, item: KanbanItem, previousRequest: unknown): Promise<unknown>;\n /** Persistable request; preparing cannot submit agent work. */\n prepare(dispatch: KanbanDispatch, item: KanbanItem): Promise<unknown>;\n start(request: unknown): Promise<KanbanExecutionObservation>;\n /** Observe the original admission only. Missing receipts mean unknown. */\n inspect(request: unknown): Promise<KanbanExecutionObservation>;\n}\nexport interface KanbanExecutionBridgeOptions {\n fs: IFileSystem; directory: string; boardId: string; service: KanbanService;\n worker(principal: KanbanPrincipal): Promise<KanbanService>;\n executor: KanbanExecutionPort;\n authorize(stage: 'prepare' | 'start' | 'inspect', dispatch: KanbanDispatch): Promise<void>;\n log(input: {operationId: string; stage: string; result: 'success' | 'failure'; code?: string; retryAfterMs?: number; durationMs: number}): void;\n executionReference?(sessionId: string, dispatchId: string): import('./contracts').KanbanExternalReference;\n subscribe?(wake: () => void): () => void;\n now?:()=>number;\n autoDispatch?: boolean | (() => Promise<boolean>); shutdownTimeoutMs?: number;\n estimate?(item: KanbanItem, principal: KanbanPrincipal): Promise<KanbanEstimate | undefined>;\n}\ninterface ExecutionRecord {schema: 1; fence: string; request: unknown; digest: string; phase: 'prepared' | 'submitting'}\ninterface ReadFailure {code: string; count: number; retryAt: number}\ntype TrackWrite = <T>(operation: Promise<T>) => Promise<T>;\n\n/** The host owns execution. Prepared/submitting identities survive response loss and host shutdown. */\nexport async function createKanbanExecutionBridge(options: KanbanExecutionBridgeOptions) {\n const path = `${options.directory}/executions.seq`;\n try {await options.fs.driver.createFile({parentPath:options.directory,name:'executions.seq',type:'seqfile',recursive:true});}\n catch(error) {if((error as {code?:string})?.code!=='EEXIST') throw error;}\n if(!options.fs.meta.seq?.transaction) throw new KanbanError('TRANSACTION_REQUIRED');\n let stopped=false, again=false, pending: Promise<void>|undefined;\n const writes=new Set<Promise<unknown>>();\n const failures = new Map<string, ReadFailure>();\n const track:TrackWrite=operation=>{const tracked=operation.finally(()=>{writes.delete(tracked);});writes.add(tracked);return tracked;};\n const active=()=>!stopped;\n const scan=async()=>{if(stopped) return; if(pending) {again=true; return pending;} const started=Date.now();\n pending=runBridge(options,path,active,track,failures).catch(error=>{try {options.log({operationId:randomUUID(),stage:'bridge.scan',result:'failure',code:safeCode(error),durationMs:Date.now()-started});} catch {/* Diagnostics cannot replay work. */} throw error;}).finally(()=>{pending=undefined;if(again&&!stopped) {again=false;void scan().catch(()=>{});}});\n return pending;\n };\n const unsubscribe=options.subscribe?.(()=>{void scan().catch(()=>{});});\n return {scan, tick: scan, async quiesce() {\n stopped=true;unsubscribe?.(); if(!pending) return;\n let timer: ReturnType<typeof setTimeout>|undefined;\n try {await Promise.race([pending,new Promise<void>(resolve=>{timer=setTimeout(resolve,options.shutdownTimeoutMs ?? 5000);})]);}\n finally {if(timer) clearTimeout(timer);await Promise.allSettled([...writes]);}\n }};\n}\nasync function runBridge(options: KanbanExecutionBridgeOptions, path: string, active: () => boolean, track: TrackWrite, failures: Map<string, ReadFailure>): Promise<void> {\n const workflow=await options.service.workflow(options.boardId);\n const live = new Set(Object.values(workflow.dispatches).filter(dispatch => ['prepared', 'running', 'unknown'].includes(dispatch.outcome)).map(dispatch => dispatch.id));\n for (const id of failures.keys()) if (!live.has(id)) failures.delete(id);\n for(const dispatch of Object.values(workflow.dispatches)) {\n if(!active()) return;\n if(live.has(dispatch.id) && (failures.get(dispatch.id)?.retryAt ?? 0) <= Date.now()) await runDispatch(options,path,dispatch,active,track,failures);\n }\n const automatic = typeof options.autoDispatch === 'function' ? await options.autoDispatch() : options.autoDispatch;\n if(!automatic||!active()) return;\n for(const id of (await options.service.workflow(options.boardId)).ready) {\n if(!active()) return;\n const {item}=await options.service.item(options.boardId,id), owner=item.eligiblePrincipals?.[0]; if(!owner || !taskDue(item, (options.now??Date.now)())) continue;\n const worker=await options.worker(owner), board=await worker.read(options.boardId);if(!active()) return;\n if (!boardAllowsStart(board, item)) continue;\n try {\n const dispatchId=randomUUID();\n const estimate=await options.estimate?.(item,owner);if(!active()) return;\n await worker.execute(board.id,{operationId:'dispatch:'+dispatchId,expectedRevision:board.revision,command:{type:'prepare-dispatch',itemId:id,dispatchId,estimate,trigger:'automatic'}});\n const dispatch=(await options.service.workflow(board.id)).dispatches[dispatchId];\n if(active()) await runDispatch(options,path,dispatch,active,track,failures);\n }\n catch(error) {if(!['WIP_LIMIT','ASSIGNEE_WIP_LIMIT','READY_LIMIT','REVISION_CONFLICT'].includes(safeCode(error))) throw error;}\n }\n}\nasync function runDispatch(options: KanbanExecutionBridgeOptions, path: string, dispatch: KanbanDispatch, active: () => boolean, track: TrackWrite, failures: Map<string, ReadFailure>): Promise<void> {\n const start=Date.now(); let stage: 'inspect' | 'prepare' | 'start'='inspect', result: KanbanExecutionObservation;\n const previousFailure = failures.has(dispatch.id);\n try {\n let record=await loadRecord(options,path,dispatch);if(!active()) return;\n if ((!record || record.phase === 'prepared') && (await options.service.read(options.boardId)).executionControl?.paused) return;\n if(!record) {\n stage='prepare';await options.authorize(stage,dispatch);if(!active()) return;\n const item=(await options.service.item(options.boardId,dispatch.itemId)).item;if(!active()) return;\n const request=await prepareRequest(options, path, dispatch, item);if(!active()) return;\n record=await track(savePrepared(options,path,dispatch,request));\n }\n if(!active()) return;\n const submit=record.phase==='prepared'&&await track(claimSubmission(options,path,dispatch));\n if(!active()) return; stage=submit?'start':'inspect';await options.authorize(stage,dispatch);if(!active()) return;\n result=await (submit?options.executor.start(record.request):options.executor.inspect(record.request));\n failures.delete(dispatch.id);\n } catch(error) {\n const notStarted = (error as {executionOutcome?: string; retryable?: boolean})?.executionOutcome === 'not-started'\n && (error as {retryable?: boolean}).retryable === true;\n if (notStarted && stage === 'start') await track(releaseUnstartedSubmission(options, path, dispatch));\n result={outcome:stage==='prepare'&&!notStarted?'failed':'unknown',code:safeCode(error)};\n if (stage === 'inspect' || notStarted) {\n const previous = failures.get(dispatch.id), count = previous && previous.code === result.code ? previous.count + 1 : 1;\n failures.set(dispatch.id, {code: result.code!, count, retryAt: Date.now() + Math.min(60000, 1000 * 2 ** Math.min(count - 1, 6))});\n }\n }\n if (stage !== 'inspect' || executionObservationChanged(dispatch, result) || previousFailure && !failures.has(dispatch.id))\n try {options.log({operationId:dispatch.id,stage:`bridge.${stage}`,result:['unknown','failed','cancelled'].includes(result.outcome)?'failure':'success',code:result.code,...(failures.has(dispatch.id)?{retryAfterMs:Math.max(0,failures.get(dispatch.id)!.retryAt-Date.now())}:{}),durationMs:Date.now()-start});} catch {/* Metadata-only diagnostics. */}\n if(active()) try {await linkSession(options,dispatch,result.executionSessionId);if(active()) await settle(options,dispatch,result);} catch(error) {if(safeCode(error)!=='REVISION_CONFLICT') throw error;}\n}\nasync function releaseUnstartedSubmission(options: KanbanExecutionBridgeOptions, path: string, dispatch: KanbanDispatch): Promise<void> {\n await options.fs.meta.seq!.transaction!(async tx => {\n const raw = await tx.getEntry(path, dispatch.id);\n if (!raw) throw new KanbanError('EXECUTION_JOURNAL_MISSING');\n const record = decodeRecord(raw, dispatch);\n if (record.phase === 'submitting') await tx.setEntry(path, dispatch.id, JSON.stringify({...record, phase: 'prepared'}));\n });\n}\nasync function prepareRequest(options: KanbanExecutionBridgeOptions, path: string, dispatch: KanbanDispatch, item: KanbanItem): Promise<unknown> {\n if (!dispatch.continuation) return options.executor.prepare(dispatch, item);\n if (!options.executor.prepareContinuation) throw new KanbanError('CONTINUATION_UNSUPPORTED');\n const previous = (await options.service.workflow(options.boardId)).dispatches[dispatch.continuation.previousDispatchId];\n if (!previous || previous.itemId !== dispatch.itemId) throw new KanbanError('CONTINUATION_SCOPE_MISMATCH');\n const record = await loadRecord(options, path, previous);\n if (!record) throw new KanbanError('EXECUTION_JOURNAL_MISSING');\n return options.executor.prepareContinuation(dispatch, item, record.request);\n}\nasync function loadRecord(options: KanbanExecutionBridgeOptions, path: string, dispatch: KanbanDispatch): Promise<ExecutionRecord|undefined> {\n const raw=await options.fs.meta.seq!.getEntry(path,dispatch.id); if(!raw) return;\n return decodeRecord(raw,dispatch);\n}\nasync function savePrepared(options: KanbanExecutionBridgeOptions, path: string, dispatch: KanbanDispatch, request: unknown): Promise<ExecutionRecord> {\n const rawRequest=JSON.stringify(request);\n if(typeof rawRequest!=='string'||new TextEncoder().encode(rawRequest).byteLength>1024*1024) throw new KanbanError('INVALID_EXECUTION_REQUEST');\n return options.fs.meta.seq!.transaction!(async tx=>{\n const raw=await tx.getEntry(path,dispatch.id);if(raw) return decodeRecord(raw,dispatch);\n const record: ExecutionRecord={schema:1,fence:dispatch.fence,request:JSON.parse(rawRequest),digest:sha256HexSync(rawRequest),phase:'prepared'};\n await tx.setEntry(path,dispatch.id,JSON.stringify(record));return record;\n });\n}\nasync function claimSubmission(options: KanbanExecutionBridgeOptions, path: string, dispatch: KanbanDispatch): Promise<boolean> {\n return options.fs.meta.seq!.transaction!(async tx=>{\n const raw=await tx.getEntry(path,dispatch.id);if(!raw) throw new KanbanError('EXECUTION_JOURNAL_MISSING');\n const record=decodeRecord(raw,dispatch);\n if(record.phase!=='prepared') return false;\n await tx.setEntry(path,dispatch.id,JSON.stringify({...record,phase:'submitting'}));return true;\n });\n}\nfunction decodeRecord(raw: string, dispatch: KanbanDispatch): ExecutionRecord {\n const record=JSON.parse(raw) as ExecutionRecord;\n if(record.schema!==1||record.fence!==dispatch.fence||!['prepared','submitting'].includes(record.phase)||record.digest!==sha256HexSync(JSON.stringify(record.request))) throw new KanbanError('INVALID_EXECUTION_JOURNAL');return record;\n}\nasync function settle(options: KanbanExecutionBridgeOptions, dispatch: KanbanDispatch, result: KanbanExecutionObservation): Promise<void> {\n const current=(await options.service.workflow(options.boardId)).dispatches[dispatch.id];\n if(!current||!['prepared','running','unknown'].includes(current.outcome)) return;\n const board=await options.service.read(options.boardId), item=board.items.find(item=>item.id===dispatch.itemId);\n const renew=['running','unknown'].includes(result.outcome) && (item?.owner?.leaseUntil ?? 0) <= Date.now() + board.policy.leaseMs / 2;\n if(!renew&&!executionObservationChanged(current,result)) return;\n const {outcome,outputs,code,executionSessionId,controlCapabilities}=result, observation={outcome,...(controlCapabilities?{controlCapabilities}:{}),...(executionSessionId?{executionSessionId}:{}),...(outputs?{outputs}:{}),...(code?{code}:{})};\n const digest=sha256HexSync(JSON.stringify(observation));\n await options.service.execute(board.id,{operationId:`settle:${dispatch.id}:${digest}:${board.revision}`,expectedRevision:board.revision,\n command:{type:'observe-execution',dispatchId:dispatch.id,fence:dispatch.fence,...observation}});\n}\nasync function linkSession(options: KanbanExecutionBridgeOptions, dispatch: KanbanDispatch, sessionId?: string): Promise<void> {\n if(!sessionId || !options.executionReference) return;\n const reference = options.executionReference(sessionId, dispatch.id);\n const board=await options.service.read(options.boardId),item=board.items.find(item=>item.id===dispatch.itemId);\n if(item?.owner?.token!==dispatch.fence) throw new KanbanError('STALE_EXECUTION_RESULT');\n const current=item.externalRefs?.filter(ref=>ref.system===reference.system).at(-1);\n if(current?.id===reference.id&¤t.revision===reference.revision) return;\n await options.service.execute(board.id,{operationId:`execution-session:${dispatch.id}:${sha256HexSync(sessionId)}:${board.revision}`,expectedRevision:board.revision,\n command:{type:'link-reference',itemId:dispatch.itemId,reference}});\n}\nfunction safeCode(error: unknown): string {\n const code=(error as {reasonCode?:unknown})?.reasonCode??(error as {code?:unknown})?.code;return typeof code==='string'&&/^[A-Z][A-Z0-9_]{0,79}$/.test(code)?code:'EXECUTION_OBSERVATION_FAILED';\n}\n","import type {KanbanClient} from './contracts';\n\n/** Host supplies its scheduler; disposal prevents new scans and awaits in-flight work. */\nexport function startKanbanMonitor(options: {\n service: Pick<KanbanClient, 'scan'>;\n subscribeTick: (tick: () => void) => () => void;\n onError: (error: unknown) => void | Promise<void>;\n onSuccess?: () => void | Promise<void>;\n subscribeChanges?: (tick: () => void) => () => void;\n initialScan?: boolean;\n}): { dispose(): Promise<void> } {\n let stopped = false, pending: Promise<void> | undefined;\n const tick = () => {\n if (stopped || pending) return;\n pending = Promise.resolve().then(() => stopped ? undefined : options.service.scan()).then(()=>stopped ? undefined : options.onSuccess?.()).catch(async error => {\n if (stopped) return;\n try { await options.onError(error); } catch { /* Reporting must not leave an unhandled rejection. */ }\n }).finally(() => { pending = undefined; });\n };\n const unsubscribe = options.subscribeTick(tick), unwatch = options.subscribeChanges?.(tick);\n if (options.initialScan !== false) tick();\n return { async dispose() {\n if (stopped) { await pending; return; }\n stopped = true; try { unsubscribe(); unwatch?.(); } finally { await pending; }\n } };\n}\n","import {FSError, type IFileSystem} from '@nestos/vfs-core';\nimport {sha256HexSync} from './utils';\nexport interface WorkMessageScope {runId:string;itemId:string;attemptId:string}\nexport interface WorkMessage extends WorkMessageScope {schema:'work-message/1'|'work-message/2';id:string;type:'progress'|'clarification'|'error'|'result'|'resolved';content:string;replyTo?:string}\ninterface Journal {get<T>(key:string):Promise<T|undefined>;update<T>(key:string,change:(old:T|undefined)=>T):Promise<T>}\ninterface Receipt {digest:string;message:WorkMessage;delivered:boolean}\n/** Directory events are wakeups; immutable files plus durable receipts are authoritative. */\nexport async function consumeWorkMessages(fs:IFileSystem,directory:string,scope:WorkMessageScope,journal:Journal,\n deliver:(message:WorkMessage)=>Promise<void|{pending:true}>,decode:(text:string)=>unknown,publishReply:(directory:string,name:string,content:string)=>Promise<void>):Promise<WorkMessage[]> {\n const publications=await readPublications(fs,directory,scope,decode),indexKey='workspace-message-index:'+scope.attemptId;\n const known=await journal.get<string[]>(indexKey)??[];\n if(known.some(id=>!publications.some(p=>p.message.id===id)))throw protocolError('WORK_MESSAGE_MISSING','Published workspace message is missing','ECONFLICT');\n unresolvedWorkIssues(publications.map(p=>p.message));\n publications.sort((a,b)=>Number(a.message.type==='resolved')-Number(b.message.type==='resolved'));\n for(const {message,bytes} of publications) {\n const digest=sha256HexSync(bytes);\n const delivered=await deliverPublication(journal,indexKey,message,digest,deliver);\n if(delivered)await publishReply(directory+'/outbox','ack-'+message.id+'.yaml',JSON.stringify({schema:'work-reply/1',...scope,id:'ack-'+message.id,type:'accepted',replyTo:message.id,content:'Accepted',digest}));\n }\n return publications.map(p=>p.message);\n}\nasync function readPublications(fs:IFileSystem,directory:string,scope:WorkMessageScope,decode:(text:string)=>unknown) {\n const files=await fs.driver.getChildren(directory+'/inbox',{fields:'entry'}),publications:Array<{message:WorkMessage;bytes:ArrayBuffer}>=[];\n if(files.length>200)throw protocolError('WORK_MESSAGE_COUNT_LIMIT','Workspace inbox exceeds 200 entries');\n for(const file of files.sort((a,b)=>a.name.localeCompare(b.name))) {\n if(file.type!=='file'||!/^[a-zA-Z0-9_-]{1,128}\\.(yaml|yml|json)$/.test(file.name))continue;\n const bytes=await readMessageBytes(fs,directory+'/inbox/'+file.name);\n const text = new TextDecoder('utf-8',{fatal:true}).decode(bytes), envelope = decode(text) as {schema?: string; scopeId?: string};\n if (envelope?.schema === 'work-message/2' && Object.hasOwn(envelope, 'scopeId')) continue;\n const message=parseWorkMessage(text,scope,decode);\n if(file.name.slice(0,file.name.lastIndexOf('.'))!==message.id)throw protocolError('WORK_MESSAGE_FILENAME_MISMATCH','Workspace message filename does not match its identity');\n publications.push({message,bytes});\n }\n return publications;\n}\nasync function deliverPublication(journal:Journal,indexKey:string,message:WorkMessage,digest:string,deliver:(message:WorkMessage)=>Promise<void|{pending:true}>):Promise<boolean> {\n const key='workspace-message:'+message.attemptId+':'+message.id;\n const receipt=await journal.update<Receipt>(key,old=>{\n if(old&&old.digest!==digest)throw protocolError('WORK_MESSAGE_CHANGED','Published workspace message changed','ECONFLICT');\n return old??{digest,message,delivered:false};\n });\n await journal.update<string[]>(indexKey,old=>old?.includes(message.id)?old:[...old??[],message.id]);\n if(receipt.delivered)return true;\n const outcome=await deliver(receipt.message);if(outcome?.pending)return false;\n await journal.update<Receipt>(key,old=>({...old!,delivered:true}));return true;\n}\n\nexport function parseWorkMessage(text:string,scope:WorkMessageScope,decode:(text:string)=>unknown):WorkMessage {\n let value:WorkMessage;\n try {value=decode(text) as WorkMessage;} catch {throw protocolError('WORK_MESSAGE_YAML_INVALID','Workspace message is not valid bounded YAML');}\n if(!value||typeof value!=='object'||Array.isArray(value))throw protocolError('WORK_MESSAGE_SCHEMA_INVALID','Workspace message schema is invalid');\n if('body' in value) {\n if(Object.hasOwn(value,'content'))throw protocolError('WORK_MESSAGE_FIELDS_INVALID','Workspace message must not contain both body and content');\n value={...value,content:(value as WorkMessage&{body:string}).body};\n }\n if(Object.keys(value).some(key=>!['schema','runId','itemId','attemptId','id','type','content','body','replyTo'].includes(key)))\n throw protocolError('WORK_MESSAGE_FIELDS_INVALID','Workspace message contains undeclared fields');\n validateEnvelope(value,scope);return value;\n}\nfunction validateEnvelope(value:WorkMessage,scope:WorkMessageScope):void {\n if(!['work-message/1','work-message/2'].includes(value.schema))throw protocolError('WORK_MESSAGE_SCHEMA_INVALID','Workspace message schema is unsupported');\n if(!['progress','clarification','error','result',...(value.schema==='work-message/2'?['resolved']:[])].includes(value.type))\n throw protocolError('WORK_MESSAGE_TYPE_INVALID','Workspace message schema does not permit this type');\n if(!validId(value.id))throw protocolError('WORK_MESSAGE_ID_INVALID','Workspace message identity is invalid');\n if(['runId','itemId','attemptId'].some(key=>value[key as keyof WorkMessageScope]!==scope[key as keyof WorkMessageScope]))\n throw protocolError('WORK_MESSAGE_SCOPE_MISMATCH','Workspace message execution scope is invalid');\n if(typeof value.content!=='string'||!value.content.trim()||value.content.length>8000)throw protocolError('WORK_MESSAGE_CONTENT_INVALID','Workspace message content is invalid');\n if(value.replyTo!==undefined&&!validId(value.replyTo)||value.type==='resolved'&&!value.replyTo)\n throw protocolError('WORK_MESSAGE_REPLY_INVALID','Workspace message reply target is invalid');\n}\nfunction validId(value:unknown):value is string {return typeof value==='string'&&/^[a-zA-Z0-9_-]{1,128}$/.test(value);}\nfunction protocolError(reasonCode:string,message:string,code:'EINVAL'|'ECONFLICT'='EINVAL'):Error {\n return Object.assign(new FSError(code,message),{reasonCode});\n}\n\n/** Range reads must fit the advertised size; remote backends reject short ranges. */\nasync function readMessageBytes(fs: IFileSystem, path: string): Promise<ArrayBuffer> {\n const before = await fs.driver.getNode(path);\n if (before?.type !== 'file' || !Number.isSafeInteger(before.size) || before.size < 1) throw protocolError('WORK_MESSAGE_FILE_INVALID','Workspace message is not a nonempty regular file');\n if (before.size > 65536) throw protocolError('WORK_MESSAGE_SIZE_LIMIT','Workspace message exceeds 64 KiB');\n const bytes = await fs.driver.readContent(path, {encoding: 'binary', length: before.size});\n const after = await fs.driver.getNode(path);\n if (bytes.byteLength !== before.size || after?.type !== 'file' || after.size !== before.size\n || after.revision !== before.revision || after.modifiedAt !== before.modifiedAt)\n throw protocolError('WORK_MESSAGE_READ_CONFLICT','Workspace message changed while reading','ECONFLICT');\n return bytes;\n}\n\n/** Only explicit linked resolutions close issues; unrelated progress cannot erase them. */\nexport function unresolvedWorkIssues(messages: WorkMessage[]): WorkMessage[] {\n const issues = messages.filter(m => m.type === 'clarification' || m.type === 'error');\n for (const resolution of messages.filter(m => m.type === 'resolved')) {\n if (!issues.some(issue => issue.id === resolution.replyTo)) throw Object.assign(new Error('Resolution target is not an issue in this attempt'), {code:'WORKSPACE_RESOLUTION_INVALID'});\n }\n return issues.filter(issue => !messages.some(m => m.type === 'resolved' && m.replyTo === issue.id));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/li/share/prj/x1/libraries/kanban/dist/execution.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACF,kWAAC","file":"/home/li/share/prj/x1/libraries/kanban/dist/execution.cjs"}
|
package/dist/execution.d.cts
CHANGED
|
@@ -1,54 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
export { K as KanbanExecutionBridgeOptions, a as KanbanExecutionObservation, b as KanbanExecutionPort, c as createKanbanExecutionBridge } from './execution-bridge-fpCnH6z5.cjs';
|
|
2
|
+
import { K as KanbanClient } from './activity-BbC4sgKL.cjs';
|
|
2
3
|
import { IFileSystem } from '@nestos/vfs-core';
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
interface KanbanExecutionObservation {
|
|
6
|
-
controlCapabilities?: {
|
|
7
|
-
pause: 'boundary' | 'none';
|
|
8
|
-
resume: boolean;
|
|
9
|
-
};
|
|
10
|
-
executionSessionId?: string;
|
|
11
|
-
outcome: 'running' | 'unknown' | 'succeeded' | 'failed' | 'cancelled';
|
|
12
|
-
outputs?: KanbanArtifactRef[];
|
|
13
|
-
code?: string;
|
|
14
|
-
}
|
|
15
|
-
interface KanbanExecutionPort {
|
|
16
|
-
prepareContinuation?(dispatch: KanbanDispatch, item: KanbanItem, previousRequest: unknown): Promise<unknown>;
|
|
17
|
-
/** Persistable request; preparing cannot submit agent work. */
|
|
18
|
-
prepare(dispatch: KanbanDispatch, item: KanbanItem): Promise<unknown>;
|
|
19
|
-
start(request: unknown): Promise<KanbanExecutionObservation>;
|
|
20
|
-
/** Observe the original admission only. Missing receipts mean unknown. */
|
|
21
|
-
inspect(request: unknown): Promise<KanbanExecutionObservation>;
|
|
22
|
-
}
|
|
23
|
-
interface KanbanExecutionBridgeOptions {
|
|
24
|
-
fs: IFileSystem;
|
|
25
|
-
directory: string;
|
|
26
|
-
boardId: string;
|
|
27
|
-
service: KanbanService;
|
|
28
|
-
worker(principal: KanbanPrincipal): Promise<KanbanService>;
|
|
29
|
-
executor: KanbanExecutionPort;
|
|
30
|
-
authorize(stage: 'prepare' | 'start' | 'inspect', dispatch: KanbanDispatch): Promise<void>;
|
|
31
|
-
log(input: {
|
|
32
|
-
operationId: string;
|
|
33
|
-
stage: string;
|
|
34
|
-
result: 'success' | 'failure';
|
|
35
|
-
code?: string;
|
|
36
|
-
retryAfterMs?: number;
|
|
37
|
-
durationMs: number;
|
|
38
|
-
}): void;
|
|
39
|
-
executionReference?(sessionId: string, dispatchId: string): KanbanExternalReference;
|
|
40
|
-
subscribe?(wake: () => void): () => void;
|
|
41
|
-
now?: () => number;
|
|
42
|
-
autoDispatch?: boolean | (() => Promise<boolean>);
|
|
43
|
-
shutdownTimeoutMs?: number;
|
|
44
|
-
estimate?(item: KanbanItem, principal: KanbanPrincipal): Promise<KanbanEstimate | undefined>;
|
|
45
|
-
}
|
|
46
|
-
/** The host owns execution. Prepared/submitting identities survive response loss and host shutdown. */
|
|
47
|
-
declare function createKanbanExecutionBridge(options: KanbanExecutionBridgeOptions): Promise<{
|
|
48
|
-
scan: () => Promise<void>;
|
|
49
|
-
tick: () => Promise<void>;
|
|
50
|
-
quiesce(): Promise<void>;
|
|
51
|
-
}>;
|
|
4
|
+
import './planning-CHrOqQ6b.cjs';
|
|
52
5
|
|
|
53
6
|
/** Host supplies its scheduler; disposal prevents new scans and awaits in-flight work. */
|
|
54
7
|
declare function startKanbanMonitor(options: {
|
|
@@ -86,4 +39,4 @@ declare function parseWorkMessage(text: string, scope: WorkMessageScope, decode:
|
|
|
86
39
|
/** Only explicit linked resolutions close issues; unrelated progress cannot erase them. */
|
|
87
40
|
declare function unresolvedWorkIssues(messages: WorkMessage[]): WorkMessage[];
|
|
88
41
|
|
|
89
|
-
export { type
|
|
42
|
+
export { type WorkMessage, type WorkMessageScope, consumeWorkMessages, parseWorkMessage, startKanbanMonitor, unresolvedWorkIssues };
|
package/dist/execution.d.ts
CHANGED
|
@@ -1,54 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
export { K as KanbanExecutionBridgeOptions, a as KanbanExecutionObservation, b as KanbanExecutionPort, c as createKanbanExecutionBridge } from './execution-bridge-rk0uVy_n.js';
|
|
2
|
+
import { K as KanbanClient } from './activity-Czlan5xW.js';
|
|
2
3
|
import { IFileSystem } from '@nestos/vfs-core';
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
interface KanbanExecutionObservation {
|
|
6
|
-
controlCapabilities?: {
|
|
7
|
-
pause: 'boundary' | 'none';
|
|
8
|
-
resume: boolean;
|
|
9
|
-
};
|
|
10
|
-
executionSessionId?: string;
|
|
11
|
-
outcome: 'running' | 'unknown' | 'succeeded' | 'failed' | 'cancelled';
|
|
12
|
-
outputs?: KanbanArtifactRef[];
|
|
13
|
-
code?: string;
|
|
14
|
-
}
|
|
15
|
-
interface KanbanExecutionPort {
|
|
16
|
-
prepareContinuation?(dispatch: KanbanDispatch, item: KanbanItem, previousRequest: unknown): Promise<unknown>;
|
|
17
|
-
/** Persistable request; preparing cannot submit agent work. */
|
|
18
|
-
prepare(dispatch: KanbanDispatch, item: KanbanItem): Promise<unknown>;
|
|
19
|
-
start(request: unknown): Promise<KanbanExecutionObservation>;
|
|
20
|
-
/** Observe the original admission only. Missing receipts mean unknown. */
|
|
21
|
-
inspect(request: unknown): Promise<KanbanExecutionObservation>;
|
|
22
|
-
}
|
|
23
|
-
interface KanbanExecutionBridgeOptions {
|
|
24
|
-
fs: IFileSystem;
|
|
25
|
-
directory: string;
|
|
26
|
-
boardId: string;
|
|
27
|
-
service: KanbanService;
|
|
28
|
-
worker(principal: KanbanPrincipal): Promise<KanbanService>;
|
|
29
|
-
executor: KanbanExecutionPort;
|
|
30
|
-
authorize(stage: 'prepare' | 'start' | 'inspect', dispatch: KanbanDispatch): Promise<void>;
|
|
31
|
-
log(input: {
|
|
32
|
-
operationId: string;
|
|
33
|
-
stage: string;
|
|
34
|
-
result: 'success' | 'failure';
|
|
35
|
-
code?: string;
|
|
36
|
-
retryAfterMs?: number;
|
|
37
|
-
durationMs: number;
|
|
38
|
-
}): void;
|
|
39
|
-
executionReference?(sessionId: string, dispatchId: string): KanbanExternalReference;
|
|
40
|
-
subscribe?(wake: () => void): () => void;
|
|
41
|
-
now?: () => number;
|
|
42
|
-
autoDispatch?: boolean | (() => Promise<boolean>);
|
|
43
|
-
shutdownTimeoutMs?: number;
|
|
44
|
-
estimate?(item: KanbanItem, principal: KanbanPrincipal): Promise<KanbanEstimate | undefined>;
|
|
45
|
-
}
|
|
46
|
-
/** The host owns execution. Prepared/submitting identities survive response loss and host shutdown. */
|
|
47
|
-
declare function createKanbanExecutionBridge(options: KanbanExecutionBridgeOptions): Promise<{
|
|
48
|
-
scan: () => Promise<void>;
|
|
49
|
-
tick: () => Promise<void>;
|
|
50
|
-
quiesce(): Promise<void>;
|
|
51
|
-
}>;
|
|
4
|
+
import './planning-CHrOqQ6b.js';
|
|
52
5
|
|
|
53
6
|
/** Host supplies its scheduler; disposal prevents new scans and awaits in-flight work. */
|
|
54
7
|
declare function startKanbanMonitor(options: {
|
|
@@ -86,4 +39,4 @@ declare function parseWorkMessage(text: string, scope: WorkMessageScope, decode:
|
|
|
86
39
|
/** Only explicit linked resolutions close issues; unrelated progress cannot erase them. */
|
|
87
40
|
declare function unresolvedWorkIssues(messages: WorkMessage[]): WorkMessage[];
|
|
88
41
|
|
|
89
|
-
export { type
|
|
42
|
+
export { type WorkMessage, type WorkMessageScope, consumeWorkMessages, parseWorkMessage, startKanbanMonitor, unresolvedWorkIssues };
|