@zq-silk/yui 0.6.8 → 0.6.10
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/README.md +10 -3
- package/dist/cli/commandCatalog.js +1 -1
- package/dist/commands/taskCommands.js +63 -28
- package/dist/commands/taskContextCommand.js +27 -1
- package/dist/commands/taskRoleRuntimeStatus.js +17 -6
- package/dist/controller/agentRuntimeObserver.js +6 -3
- package/dist/controller/controller.js +29 -47
- package/dist/controller/fileSchedulerStoreAdapter.js +572 -258
- package/dist/controller/runtime.js +8 -1
- package/dist/controller/runtimeEventInbox.js +16 -5
- package/dist/controller/runtimeHookRunFence.js +51 -5
- package/dist/controller/runtimeObservationHook.js +8 -2
- package/dist/coordination/workMailbox.js +408 -28
- package/dist/coordination/workMailboxQueue.js +12 -10
- package/dist/executor/agentExecutor.js +101 -94
- package/dist/executor/executorRegistry.js +47 -2
- package/dist/executor/fileRoleLaunchPlanner.js +4 -2
- package/dist/lifecycle/exactRunTerminalization.js +1 -7
- package/dist/repository/taskWorkspaceCoordinator.js +9 -4
- package/dist/runtime/agentDriver.js +83 -4
- package/dist/runtime/agentDriverObservation.js +25 -10
- package/dist/runtime/builtinAgentDrivers.js +168 -18
- package/dist/runtime/codexAppServerRuntime.js +355 -0
- package/dist/runtime/continuationManager.js +117 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/lifecycleReservation.js +4 -3
- package/dist/runtime/promptEnvelope.js +14 -3
- package/dist/runtime/providerContinuation.js +225 -0
- package/dist/runtime/providerContinuationReconciliationService.js +172 -0
- package/dist/runtime/providerRuntimeIdentity.js +232 -0
- package/dist/runtime/providerRuntimeReconciler.js +166 -0
- package/dist/runtime/runtimeContinuationProjection.js +34 -0
- package/dist/runtime/runtimeObservation.js +217 -6
- package/dist/runtime/runtimeProjection.js +172 -11
- package/dist/scheduler/activeRoleRunDelivery.js +314 -1
- package/dist/scheduler/leaderWakeupProcessor.js +2 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
- package/dist/scheduler/roleRunLiveness.js +8 -7
- package/dist/scheduler/roleRunStall.js +4 -2
- package/dist/scheduler/taskExecutionProjection.js +2 -2
- package/dist/storage/migration/productionRegistry.js +474 -1
- package/dist/storage/sqliteSchema.js +102 -21
- package/dist/storage/sqliteStore.js +52 -110
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +40 -53
- package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
- package/dist/task/nextAction.js +1 -1
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/components.js +233 -4
- package/dist/web/assets/client/i18n.js +166 -2
- package/dist/web/assets/client/view.js +30 -13
- package/dist/web/assets/styles/cards.js +62 -0
- package/dist/web/assets/styles/widgets.js +1 -0
- package/dist/web/webSnapshot.js +11 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +33 -24
- package/skills/yui-operator/SKILL.md +7 -5
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
* the write transaction; conflict ->
|
|
13
13
|
* StorageConflictError (transactionWithRevisionCas).
|
|
14
14
|
* - Atomic durable write ........ WAL + synchronous=FULL; COMMIT == fsync.
|
|
15
|
-
* - Mailbox per-target ordering .
|
|
16
|
-
* (mailbox_id, sequence) primary key.
|
|
15
|
+
* - Mailbox per-target ordering . WorkMailbox v2 sequence/cursors in one row.
|
|
17
16
|
* - Exactly-once terminal state . conditional updates + UNIQUE(request_id)
|
|
18
|
-
* on
|
|
17
|
+
* on the durable outbox.
|
|
19
18
|
* - Crash recovery .............. WAL rollback of uncommitted transactions;
|
|
20
19
|
* outbox replay of committed-but-unacked effects.
|
|
21
20
|
* - Record family versioning .... full record (incl. schemaVersion) in payload.
|
|
@@ -39,8 +38,7 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
39
38
|
import { join } from "node:path";
|
|
40
39
|
import { isDeepStrictEqual } from "node:util";
|
|
41
40
|
import Database from "better-sqlite3";
|
|
42
|
-
import { mailboxTargetKey } from "../coordination/workMailbox.js";
|
|
43
|
-
import { validatePendingTurnCompletion } from "../executor/turnCompletion.js";
|
|
41
|
+
import { consumePendingBatch, mailboxTargetKey, pendingLane, validateWorkMailbox } from "../coordination/workMailbox.js";
|
|
44
42
|
import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
|
|
45
43
|
import { validateReviewFinding } from "../review/reviewFinding.js";
|
|
46
44
|
import { generateHomeIdentity, validateHomeIdentity } from "../repository/homeIdentity.js";
|
|
@@ -90,17 +88,18 @@ function isUniqueConstraint(error) {
|
|
|
90
88
|
}
|
|
91
89
|
/** Project a leader-role work mailbox's pending batch to a PendingWakeup (mirrors taskStore.ts). */
|
|
92
90
|
function pendingWakeupProjection(mailbox) {
|
|
91
|
+
const pending = mailbox === null ? null : pendingLane(mailbox, "normal");
|
|
93
92
|
if (mailbox === null || mailbox.target.kind !== "role" || mailbox.target.roleName !== "leader"
|
|
94
|
-
||
|
|
93
|
+
|| pending === null) {
|
|
95
94
|
return null;
|
|
96
95
|
}
|
|
97
96
|
return {
|
|
98
97
|
schemaVersion: CURRENT_PENDING_WAKEUP_SCHEMA_VERSION,
|
|
99
98
|
taskId: mailbox.target.taskId,
|
|
100
|
-
reasons: [...
|
|
101
|
-
requestCount:
|
|
102
|
-
firstRequestedAt:
|
|
103
|
-
lastRequestedAt:
|
|
99
|
+
reasons: [...pending.reasons],
|
|
100
|
+
requestCount: pending.requestCount,
|
|
101
|
+
firstRequestedAt: pending.firstQueuedAt,
|
|
102
|
+
lastRequestedAt: pending.lastQueuedAt
|
|
104
103
|
};
|
|
105
104
|
}
|
|
106
105
|
export class SqliteTaskStore {
|
|
@@ -915,7 +914,6 @@ export class SqliteTaskStore {
|
|
|
915
914
|
this.#db.prepare(`INSERT INTO role_session_sets (task_id, role_name, payload, updated_at) VALUES (?, ?, ?, ?)
|
|
916
915
|
ON CONFLICT(task_id, role_name) DO UPDATE SET payload = excluded.payload, updated_at = excluded.updated_at`).run(sessions.owner.taskId, sessions.owner.roleName, this.#json(sessions), this.#now());
|
|
917
916
|
this.#saveRuntimeSessionCandidate(sessions);
|
|
918
|
-
this.#savePendingRuntimeTurnCompletion(sessions);
|
|
919
917
|
});
|
|
920
918
|
}
|
|
921
919
|
removeTaskRole(taskId, name) {
|
|
@@ -1104,33 +1102,6 @@ export class SqliteTaskStore {
|
|
|
1104
1102
|
&& row.session_updated_at === candidate.sessionUpdatedAt
|
|
1105
1103
|
&& row.cleanup_required === (candidate.cleanupRequired ? 1 : 0);
|
|
1106
1104
|
}
|
|
1107
|
-
listPendingRuntimeTurnCompletions(taskIds) {
|
|
1108
|
-
const selectedTaskIds = taskIds === undefined
|
|
1109
|
-
? undefined
|
|
1110
|
-
: [...new Set(taskIds)].sort(numericCompare);
|
|
1111
|
-
if (selectedTaskIds?.length === 0)
|
|
1112
|
-
return [];
|
|
1113
|
-
const where = selectedTaskIds === undefined
|
|
1114
|
-
? ""
|
|
1115
|
-
: ` WHERE task_id IN (${selectedTaskIds.map(() => "?").join(", ")})`;
|
|
1116
|
-
const rows = this.#db.prepare(`SELECT task_id, role_name, schema_version, agent_id, native_session_id,
|
|
1117
|
-
turn_id, run_id, summary, observed_at, due_at
|
|
1118
|
-
FROM pending_runtime_turn_completions${where}`).all(...(selectedTaskIds ?? []));
|
|
1119
|
-
return rows.map((row) => validatePendingTurnCompletion({
|
|
1120
|
-
schemaVersion: row.schema_version,
|
|
1121
|
-
taskId: row.task_id,
|
|
1122
|
-
roleName: row.role_name,
|
|
1123
|
-
agentId: row.agent_id,
|
|
1124
|
-
nativeSessionId: row.native_session_id,
|
|
1125
|
-
turnId: row.turn_id,
|
|
1126
|
-
runId: row.run_id,
|
|
1127
|
-
summary: row.summary,
|
|
1128
|
-
observedAt: row.observed_at,
|
|
1129
|
-
dueAt: row.due_at
|
|
1130
|
-
})).sort((left, right) => (numericCompare(left.taskId, right.taskId)
|
|
1131
|
-
|| numericCompare(left.roleName, right.roleName)
|
|
1132
|
-
|| numericCompare(left.runId, right.runId)));
|
|
1133
|
-
}
|
|
1134
1105
|
saveRoleSessionSet(sessions) {
|
|
1135
1106
|
const taskId = sessions.owner.taskId;
|
|
1136
1107
|
this.#requireTask(taskId);
|
|
@@ -1138,7 +1109,6 @@ export class SqliteTaskStore {
|
|
|
1138
1109
|
this.#db.prepare(`INSERT INTO role_session_sets (task_id, role_name, payload, updated_at) VALUES (?, ?, ?, ?)
|
|
1139
1110
|
ON CONFLICT(task_id, role_name) DO UPDATE SET payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, sessions.owner.roleName, this.#json(sessions), this.#now());
|
|
1140
1111
|
this.#saveRuntimeSessionCandidate(sessions);
|
|
1141
|
-
this.#savePendingRuntimeTurnCompletion(sessions);
|
|
1142
1112
|
});
|
|
1143
1113
|
}
|
|
1144
1114
|
saveTaskRoleSessionSet(sessions) {
|
|
@@ -1175,32 +1145,6 @@ export class SqliteTaskStore {
|
|
|
1175
1145
|
this.#db.prepare(`DELETE FROM runtime_session_candidates
|
|
1176
1146
|
WHERE scope = ? AND task_id = ? AND role_name = ?`).run(owner.scope, owner.scope === "task" ? owner.taskId : "", owner.roleName);
|
|
1177
1147
|
}
|
|
1178
|
-
/** Maintain the independent pending-Turn projection in the same write txn. */
|
|
1179
|
-
#savePendingRuntimeTurnCompletion(sessions) {
|
|
1180
|
-
const owner = sessions.owner;
|
|
1181
|
-
const pending = sessions.pendingTurnCompletion;
|
|
1182
|
-
if (pending === null) {
|
|
1183
|
-
this.#db.prepare("DELETE FROM pending_runtime_turn_completions WHERE task_id = ? AND role_name = ?").run(owner.taskId, owner.roleName);
|
|
1184
|
-
return;
|
|
1185
|
-
}
|
|
1186
|
-
const normalized = validatePendingTurnCompletion(pending);
|
|
1187
|
-
if (normalized.taskId !== owner.taskId || normalized.roleName !== owner.roleName) {
|
|
1188
|
-
throw new StorageRecordError(`Pending Turn completion owner does not match Role session: ${owner.taskId}/${owner.roleName}.`);
|
|
1189
|
-
}
|
|
1190
|
-
this.#db.prepare(`INSERT INTO pending_runtime_turn_completions (
|
|
1191
|
-
task_id, role_name, schema_version, agent_id, native_session_id,
|
|
1192
|
-
turn_id, run_id, summary, observed_at, due_at
|
|
1193
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1194
|
-
ON CONFLICT(task_id, role_name) DO UPDATE SET
|
|
1195
|
-
schema_version = excluded.schema_version,
|
|
1196
|
-
agent_id = excluded.agent_id,
|
|
1197
|
-
native_session_id = excluded.native_session_id,
|
|
1198
|
-
turn_id = excluded.turn_id,
|
|
1199
|
-
run_id = excluded.run_id,
|
|
1200
|
-
summary = excluded.summary,
|
|
1201
|
-
observed_at = excluded.observed_at,
|
|
1202
|
-
due_at = excluded.due_at`).run(normalized.taskId, normalized.roleName, normalized.schemaVersion, normalized.agentId, normalized.nativeSessionId, normalized.turnId, normalized.runId, normalized.summary, normalized.observedAt, normalized.dueAt);
|
|
1203
|
-
}
|
|
1204
1148
|
// -- work items -------------------------------------------------------------
|
|
1205
1149
|
nextWorkItemId(taskId) { return this.#nextTaskRecordId(taskId, "workItem"); }
|
|
1206
1150
|
getWorkItem(taskId, workItemId) {
|
|
@@ -1776,13 +1720,14 @@ export class SqliteTaskStore {
|
|
|
1776
1720
|
}
|
|
1777
1721
|
#rowToMailbox(row) {
|
|
1778
1722
|
const target = this.#targetFromCols(row.target_kind, row.task_id, row.role_name);
|
|
1779
|
-
return {
|
|
1780
|
-
schemaVersion:
|
|
1723
|
+
return validateWorkMailbox({
|
|
1724
|
+
schemaVersion: CURRENT_WORK_MAILBOX_SCHEMA_VERSION,
|
|
1781
1725
|
target,
|
|
1782
1726
|
nextSequence: row.next_sequence,
|
|
1783
1727
|
processing: row.processing === null ? null : this.#parse(row.processing),
|
|
1784
|
-
pending:
|
|
1785
|
-
|
|
1728
|
+
pending: this.#parse(row.pending),
|
|
1729
|
+
inputDelivery: row.input_delivery === null ? null : this.#parse(row.input_delivery)
|
|
1730
|
+
});
|
|
1786
1731
|
}
|
|
1787
1732
|
#targetFromCols(kind, taskId, roleName) {
|
|
1788
1733
|
switch (kind) {
|
|
@@ -1796,27 +1741,31 @@ export class SqliteTaskStore {
|
|
|
1796
1741
|
}
|
|
1797
1742
|
getWorkMailbox(target) {
|
|
1798
1743
|
const cols = this.#mailboxCols(target);
|
|
1799
|
-
const row = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending FROM mailboxes WHERE target_key = ?").get(cols.targetKey);
|
|
1744
|
+
const row = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending, input_delivery FROM mailboxes WHERE target_key = ?").get(cols.targetKey);
|
|
1800
1745
|
return row === undefined ? null : this.#rowToMailbox(row);
|
|
1801
1746
|
}
|
|
1802
1747
|
listWorkMailboxes() {
|
|
1803
|
-
const rows = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending FROM mailboxes ORDER BY target_key").all();
|
|
1748
|
+
const rows = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending, input_delivery FROM mailboxes ORDER BY target_key").all();
|
|
1804
1749
|
return rows.map((row) => this.#rowToMailbox(row));
|
|
1805
1750
|
}
|
|
1806
1751
|
listReadyWorkMailboxes() {
|
|
1807
|
-
const rows = this.#db.prepare(`SELECT target_kind, task_id, role_name, next_sequence, processing, pending
|
|
1752
|
+
const rows = this.#db.prepare(`SELECT target_kind, task_id, role_name, next_sequence, processing, pending, input_delivery
|
|
1808
1753
|
FROM mailboxes
|
|
1809
|
-
WHERE processing IS NOT NULL
|
|
1754
|
+
WHERE processing IS NOT NULL
|
|
1755
|
+
OR input_delivery IS NOT NULL
|
|
1756
|
+
OR json_type(pending, '$.normal') <> 'null'
|
|
1757
|
+
OR json_type(pending, '$.userCorrection') <> 'null'
|
|
1810
1758
|
ORDER BY target_key`).all();
|
|
1811
1759
|
return rows.map((row) => this.#rowToMailbox(row));
|
|
1812
1760
|
}
|
|
1813
1761
|
saveWorkMailbox(mailbox) {
|
|
1814
1762
|
const cols = this.#mailboxCols(mailbox.target);
|
|
1815
1763
|
this.#mutate(() => {
|
|
1816
|
-
this.#db.prepare(`INSERT INTO mailboxes (target_kind, task_id, role_name, target_key, next_sequence, processing, pending)
|
|
1817
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1764
|
+
this.#db.prepare(`INSERT INTO mailboxes (target_kind, task_id, role_name, target_key, next_sequence, processing, pending, input_delivery)
|
|
1765
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1818
1766
|
ON CONFLICT(target_key) DO UPDATE SET next_sequence = excluded.next_sequence,
|
|
1819
|
-
processing = excluded.processing, pending = excluded.pending
|
|
1767
|
+
processing = excluded.processing, pending = excluded.pending,
|
|
1768
|
+
input_delivery = excluded.input_delivery`).run(cols.targetKind, cols.taskId, cols.roleName, cols.targetKey, mailbox.nextSequence, mailbox.processing === null ? null : this.#json(mailbox.processing), this.#json(mailbox.pending), mailbox.inputDelivery === null ? null : this.#json(mailbox.inputDelivery));
|
|
1820
1769
|
});
|
|
1821
1770
|
}
|
|
1822
1771
|
removeWorkMailbox(target) {
|
|
@@ -1832,28 +1781,6 @@ export class SqliteTaskStore {
|
|
|
1832
1781
|
* single writer connection serializes enqueues, so sequences stay gapless per
|
|
1833
1782
|
* mailbox. `(mailbox_id, sequence)` is the exactly-once key.
|
|
1834
1783
|
*/
|
|
1835
|
-
enqueueMailboxSignal(target, input) {
|
|
1836
|
-
return this.#mutate(() => {
|
|
1837
|
-
const cols = this.#mailboxCols(target);
|
|
1838
|
-
let mailboxId;
|
|
1839
|
-
let sequence;
|
|
1840
|
-
const existing = this.#db.prepare("SELECT mailbox_id, next_sequence FROM mailboxes WHERE target_key = ?").get(cols.targetKey);
|
|
1841
|
-
if (existing === undefined) {
|
|
1842
|
-
const result = this.#db.prepare(`INSERT INTO mailboxes (target_kind, task_id, role_name, target_key, next_sequence, processing, pending)
|
|
1843
|
-
VALUES (?, ?, ?, ?, 1, NULL, NULL)`).run(cols.targetKind, cols.taskId, cols.roleName, cols.targetKey);
|
|
1844
|
-
mailboxId = Number(result.lastInsertRowid);
|
|
1845
|
-
sequence = 1;
|
|
1846
|
-
}
|
|
1847
|
-
else {
|
|
1848
|
-
mailboxId = existing.mailbox_id;
|
|
1849
|
-
sequence = existing.next_sequence;
|
|
1850
|
-
}
|
|
1851
|
-
this.#db.prepare(`INSERT INTO mailbox_signals (mailbox_id, sequence, reason, ref_type, ref_task_id, ref_id, occurred_at, request_id)
|
|
1852
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(mailboxId, sequence, input.reason, input.ref?.type ?? null, input.ref && "taskId" in input.ref ? input.ref.taskId : null, input.ref?.id ?? null, this.#now(), input.requestId);
|
|
1853
|
-
this.#db.prepare("UPDATE mailboxes SET next_sequence = ? WHERE mailbox_id = ?").run(sequence + 1, mailboxId);
|
|
1854
|
-
return sequence;
|
|
1855
|
-
});
|
|
1856
|
-
}
|
|
1857
1784
|
// -- scheduler projections -----------------------------------------------------
|
|
1858
1785
|
#getProjection(taskId, kind) {
|
|
1859
1786
|
const row = this.#db.prepare("SELECT payload FROM task_projections WHERE task_id = ? AND kind = ?").get(taskId, kind);
|
|
@@ -1908,32 +1835,47 @@ export class SqliteTaskStore {
|
|
|
1908
1835
|
const target = { kind: "role", taskId: value.taskId, roleName: "leader" };
|
|
1909
1836
|
this.transaction((store) => {
|
|
1910
1837
|
const existing = store.getWorkMailbox(target);
|
|
1911
|
-
|
|
1912
|
-
|
|
1838
|
+
const existingPending = existing === null ? null : pendingLane(existing, "normal");
|
|
1839
|
+
if (existingPending !== null
|
|
1840
|
+
&& value.requestCount <= existingPending.requestCount) {
|
|
1913
1841
|
throw new StorageRecordError(`Pending wakeup is stale: ${value.taskId}`);
|
|
1914
1842
|
}
|
|
1915
|
-
const fromSequence =
|
|
1843
|
+
const fromSequence = existingPending?.fromSequence ?? existing?.nextSequence ?? 1;
|
|
1916
1844
|
const toSequence = fromSequence + value.requestCount - 1;
|
|
1917
1845
|
store.saveWorkMailbox({
|
|
1918
1846
|
schemaVersion: CURRENT_WORK_MAILBOX_SCHEMA_VERSION,
|
|
1919
1847
|
target,
|
|
1920
1848
|
nextSequence: Math.max(existing?.nextSequence ?? 1, toSequence + 1),
|
|
1921
1849
|
processing: existing?.processing ?? null,
|
|
1850
|
+
inputDelivery: existing?.inputDelivery ?? null,
|
|
1922
1851
|
pending: {
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1852
|
+
normal: {
|
|
1853
|
+
fromSequence,
|
|
1854
|
+
toSequence,
|
|
1855
|
+
reasons: [...value.reasons],
|
|
1856
|
+
refs: existingPending?.refs ?? [],
|
|
1857
|
+
requestCount: value.requestCount,
|
|
1858
|
+
firstQueuedAt: value.firstRequestedAt,
|
|
1859
|
+
lastQueuedAt: value.lastRequestedAt,
|
|
1860
|
+
sources: existingPending?.sources ?? ["pending-wakeup-projection"],
|
|
1861
|
+
dedupeKeys: existingPending?.dedupeKeys ?? [
|
|
1862
|
+
`pending-wakeup:${value.taskId}:${fromSequence}-${toSequence}`
|
|
1863
|
+
],
|
|
1864
|
+
deliveryModes: existingPending?.deliveryModes ?? ["followup"]
|
|
1865
|
+
},
|
|
1866
|
+
userCorrection: existing?.pending.userCorrection ?? null,
|
|
1867
|
+
cursors: existing?.pending.cursors ?? { normal: 0, userCorrection: 0 },
|
|
1868
|
+
recentDedupeKeys: existing?.pending.recentDedupeKeys ?? []
|
|
1931
1869
|
}
|
|
1932
1870
|
});
|
|
1933
1871
|
});
|
|
1934
1872
|
}
|
|
1935
1873
|
clearPendingWakeup(taskId) {
|
|
1936
|
-
|
|
1874
|
+
const target = { kind: "role", taskId, roleName: "leader" };
|
|
1875
|
+
const mailbox = this.getWorkMailbox(target);
|
|
1876
|
+
if (mailbox === null || mailbox.pending.normal === null)
|
|
1877
|
+
return;
|
|
1878
|
+
this.saveWorkMailbox(consumePendingBatch(mailbox, "normal"));
|
|
1937
1879
|
}
|
|
1938
1880
|
// -- telemetry (§4.4) -----------------------------------------------------------
|
|
1939
1881
|
/**
|
package/dist/storage/storeRpc.js
CHANGED
|
@@ -7,10 +7,9 @@ import { validateCapabilityGrant } from "../grant/capabilityGrant.js";
|
|
|
7
7
|
import { validateReleaseWorkflow } from "../release/releaseWorkflow.js";
|
|
8
8
|
import { reconciliationIntervalMilliseconds, resolveLeaderNextActionMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode } from "../config/yuiConfig.js";
|
|
9
9
|
import { resolveTimeZone } from "../output/timePresentation.js";
|
|
10
|
-
import { mailboxTargetKey, validateWorkMailbox } from "../coordination/workMailbox.js";
|
|
10
|
+
import { mailboxBatches, consumePendingBatch, mailboxHasWork, mailboxTargetKey, pendingLane, validateWorkMailbox } from "../coordination/workMailbox.js";
|
|
11
11
|
import { validateInputRequest } from "../input/inputRequest.js";
|
|
12
12
|
import { validateRoleSessionSet } from "../executor/agentExecutor.js";
|
|
13
|
-
import { validatePendingTurnCompletion } from "../executor/turnCompletion.js";
|
|
14
13
|
import { validateTaskMessage } from "../message/message.js";
|
|
15
14
|
import { validateAgentRun } from "../run/agentRun.js";
|
|
16
15
|
import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
|
|
@@ -71,7 +70,7 @@ export const CURRENT_MILESTONE_SCHEMA_VERSION = 1;
|
|
|
71
70
|
export const CURRENT_EVENT_SCHEMA_VERSION = 2;
|
|
72
71
|
export const CURRENT_CAPABILITY_GRANT_SCHEMA_VERSION = 1;
|
|
73
72
|
export const CURRENT_RELEASE_WORKFLOW_SCHEMA_VERSION = 1;
|
|
74
|
-
export const CURRENT_WORK_MAILBOX_SCHEMA_VERSION =
|
|
73
|
+
export const CURRENT_WORK_MAILBOX_SCHEMA_VERSION = 2;
|
|
75
74
|
export const CURRENT_ROLE_AGENT_SESSION_SCHEMA_VERSION = 3;
|
|
76
75
|
export const CURRENT_PENDING_WAKEUP_SCHEMA_VERSION = 1;
|
|
77
76
|
const STORAGE_LOCK_DIRECTORY = ".state.lock";
|
|
@@ -111,7 +110,7 @@ export const CURRENT_STORED_TASK_SCHEMA_VERSION = 16;
|
|
|
111
110
|
* Keep these named at the storage boundary so the upgrade record-axis map can
|
|
112
111
|
* assert it is classifying the same bytes the store reads and writes.
|
|
113
112
|
*/
|
|
114
|
-
export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION =
|
|
113
|
+
export const CURRENT_TASK_ROLE_SESSION_SET_SCHEMA_VERSION = 5;
|
|
115
114
|
/**
|
|
116
115
|
* v7 combines optional Issue 04 retry/receipt fields and Issue 05 Leader
|
|
117
116
|
* actionability fields. All are optional, so the v6→v7 migration is a
|
|
@@ -779,27 +778,6 @@ export class FileTaskStore {
|
|
|
779
778
|
.filter((candidate) => !query.cleanupRequiredOnly || candidate.cleanupRequired)
|
|
780
779
|
.sort(compareRuntimeSessionCandidates);
|
|
781
780
|
}
|
|
782
|
-
listPendingRuntimeTurnCompletions(taskIds) {
|
|
783
|
-
const selectedTaskIds = taskIds === undefined
|
|
784
|
-
? undefined
|
|
785
|
-
: [...new Set(taskIds)].sort(numericCompare);
|
|
786
|
-
if (selectedTaskIds?.length === 0)
|
|
787
|
-
return [];
|
|
788
|
-
const taskAggregates = selectedTaskIds === undefined
|
|
789
|
-
? Object.values(this.#state().tasks)
|
|
790
|
-
: selectedTaskIds.flatMap((taskId) => {
|
|
791
|
-
const aggregate = this.#state().tasks[taskId];
|
|
792
|
-
return aggregate === undefined ? [] : [aggregate];
|
|
793
|
-
});
|
|
794
|
-
return taskAggregates.flatMap((aggregate) => (Object.values(aggregate.roleSessionSets).flatMap((sessions) => {
|
|
795
|
-
const pending = sessions.pendingTurnCompletion;
|
|
796
|
-
return pending === null || pending === undefined
|
|
797
|
-
? []
|
|
798
|
-
: [validatePendingTurnCompletion(pending)];
|
|
799
|
-
}))).sort((left, right) => (numericCompare(left.taskId, right.taskId)
|
|
800
|
-
|| numericCompare(left.roleName, right.roleName)
|
|
801
|
-
|| numericCompare(left.runId, right.runId)));
|
|
802
|
-
}
|
|
803
781
|
saveRoleSessionSet(sessions) {
|
|
804
782
|
const stored = taskSessions(sessions);
|
|
805
783
|
const taskId = stored.owner.taskId;
|
|
@@ -1484,7 +1462,7 @@ export class FileTaskStore {
|
|
|
1484
1462
|
// in-memory aggregate while preserving the indexed SQLite contract's
|
|
1485
1463
|
// target-key order; production layout 7 uses the bounded SQLite query.
|
|
1486
1464
|
return Object.entries(this.#state().mailboxes)
|
|
1487
|
-
.filter(([, mailbox]) => mailbox
|
|
1465
|
+
.filter(([, mailbox]) => mailboxHasWork(mailbox))
|
|
1488
1466
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
1489
1467
|
.map(([, mailbox]) => clone(mailbox));
|
|
1490
1468
|
}
|
|
@@ -1520,32 +1498,47 @@ export class FileTaskStore {
|
|
|
1520
1498
|
const target = { kind: "role", taskId: wakeup.taskId, roleName: "leader" };
|
|
1521
1499
|
this.transaction(() => {
|
|
1522
1500
|
const existing = this.getWorkMailbox(target);
|
|
1523
|
-
|
|
1524
|
-
|
|
1501
|
+
const existingPending = existing === null ? null : pendingLane(existing, "normal");
|
|
1502
|
+
if (existingPending !== null
|
|
1503
|
+
&& wakeup.requestCount <= existingPending.requestCount) {
|
|
1525
1504
|
throw new StorageRecordError(`Pending wakeup is stale: ${wakeup.taskId}`);
|
|
1526
1505
|
}
|
|
1527
|
-
const fromSequence =
|
|
1506
|
+
const fromSequence = existingPending?.fromSequence ?? existing?.nextSequence ?? 1;
|
|
1528
1507
|
const toSequence = fromSequence + wakeup.requestCount - 1;
|
|
1529
1508
|
this.saveWorkMailbox({
|
|
1530
1509
|
schemaVersion: CURRENT_WORK_MAILBOX_SCHEMA_VERSION,
|
|
1531
1510
|
target,
|
|
1532
1511
|
nextSequence: Math.max(existing?.nextSequence ?? 1, toSequence + 1),
|
|
1533
1512
|
processing: existing?.processing ?? null,
|
|
1513
|
+
inputDelivery: existing?.inputDelivery ?? null,
|
|
1534
1514
|
pending: {
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1515
|
+
normal: {
|
|
1516
|
+
fromSequence,
|
|
1517
|
+
toSequence,
|
|
1518
|
+
reasons: [...wakeup.reasons],
|
|
1519
|
+
refs: existingPending?.refs ?? [],
|
|
1520
|
+
requestCount: wakeup.requestCount,
|
|
1521
|
+
firstQueuedAt: wakeup.firstRequestedAt,
|
|
1522
|
+
lastQueuedAt: wakeup.lastRequestedAt,
|
|
1523
|
+
sources: existingPending?.sources ?? ["pending-wakeup-projection"],
|
|
1524
|
+
dedupeKeys: existingPending?.dedupeKeys ?? [
|
|
1525
|
+
`pending-wakeup:${wakeup.taskId}:${fromSequence}-${toSequence}`
|
|
1526
|
+
],
|
|
1527
|
+
deliveryModes: existingPending?.deliveryModes ?? ["followup"]
|
|
1528
|
+
},
|
|
1529
|
+
userCorrection: existing?.pending.userCorrection ?? null,
|
|
1530
|
+
cursors: existing?.pending.cursors ?? { normal: 0, userCorrection: 0 },
|
|
1531
|
+
recentDedupeKeys: existing?.pending.recentDedupeKeys ?? []
|
|
1543
1532
|
}
|
|
1544
1533
|
});
|
|
1545
1534
|
});
|
|
1546
1535
|
}
|
|
1547
1536
|
clearPendingWakeup(taskId) {
|
|
1548
|
-
|
|
1537
|
+
const target = { kind: "role", taskId, roleName: "leader" };
|
|
1538
|
+
const mailbox = this.getWorkMailbox(target);
|
|
1539
|
+
if (mailbox === null || mailbox.pending.normal === null)
|
|
1540
|
+
return;
|
|
1541
|
+
this.saveWorkMailbox(consumePendingBatch(mailbox, "normal"));
|
|
1549
1542
|
}
|
|
1550
1543
|
getLeaderFailure(taskId) { return optional(this.#state().tasks[taskId]?.leaderFailure ?? undefined); }
|
|
1551
1544
|
saveLeaderFailure(value) { this.#saveSingleton(value.taskId, "leaderFailure", value, "Leader failure"); }
|
|
@@ -2912,17 +2905,18 @@ function values(records, identity) {
|
|
|
2912
2905
|
}
|
|
2913
2906
|
function numericCompare(left, right) { return left.localeCompare(right, undefined, { numeric: true }); }
|
|
2914
2907
|
export function pendingWakeupProjection(mailbox) {
|
|
2908
|
+
const pending = mailbox === null ? null : pendingLane(mailbox, "normal");
|
|
2915
2909
|
if (mailbox === null || mailbox.target.kind !== "role" || mailbox.target.roleName !== "leader"
|
|
2916
|
-
||
|
|
2910
|
+
|| pending === null) {
|
|
2917
2911
|
return null;
|
|
2918
2912
|
}
|
|
2919
2913
|
return {
|
|
2920
2914
|
schemaVersion: CURRENT_PENDING_WAKEUP_SCHEMA_VERSION,
|
|
2921
2915
|
taskId: mailbox.target.taskId,
|
|
2922
|
-
reasons: [...
|
|
2923
|
-
requestCount:
|
|
2924
|
-
firstRequestedAt:
|
|
2925
|
-
lastRequestedAt:
|
|
2916
|
+
reasons: [...pending.reasons],
|
|
2917
|
+
requestCount: pending.requestCount,
|
|
2918
|
+
firstRequestedAt: pending.firstQueuedAt,
|
|
2919
|
+
lastRequestedAt: pending.lastQueuedAt
|
|
2926
2920
|
};
|
|
2927
2921
|
}
|
|
2928
2922
|
function validateMailboxReferences(state, mailbox) {
|
|
@@ -2939,12 +2933,11 @@ function validateMailboxReferences(state, mailbox) {
|
|
|
2939
2933
|
}
|
|
2940
2934
|
const refs = [];
|
|
2941
2935
|
if (mailbox.processing !== null) {
|
|
2942
|
-
refs.push(...mailbox.processing.batch.refs);
|
|
2943
2936
|
if (mailbox.processing.executionRef !== undefined)
|
|
2944
2937
|
refs.push(mailbox.processing.executionRef);
|
|
2945
2938
|
}
|
|
2946
|
-
|
|
2947
|
-
refs.push(...
|
|
2939
|
+
for (const batch of mailboxBatches(mailbox))
|
|
2940
|
+
refs.push(...batch.refs);
|
|
2948
2941
|
for (const ref of refs) {
|
|
2949
2942
|
if (!mailboxReferenceExists(state, ref)) {
|
|
2950
2943
|
const identity = "taskId" in ref ? `${ref.taskId}/${ref.id}` : ref.id;
|
|
@@ -3012,13 +3005,6 @@ function validateCanonicalTaskReferences(state, aggregate) {
|
|
|
3012
3005
|
throw new StorageRecordError(`Task Role in-flight Run is invalid: ${taskId}/${roleName}.`);
|
|
3013
3006
|
}
|
|
3014
3007
|
}
|
|
3015
|
-
if (sessions.pendingTurnCompletion !== null) {
|
|
3016
|
-
const completion = sessions.pendingTurnCompletion;
|
|
3017
|
-
const run = aggregate.agentRuns[completion.runId];
|
|
3018
|
-
if (completion.taskId !== taskId || run === undefined || run.roleName !== roleName) {
|
|
3019
|
-
throw new StorageRecordError(`Task Role pending completion Run is invalid: ${taskId}/${roleName}.`);
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
3008
|
}
|
|
3023
3009
|
for (const run of Object.values(aggregate.agentRuns)) {
|
|
3024
3010
|
if (run.workItemId !== undefined && aggregate.workItems[run.workItemId] === undefined) {
|
|
@@ -3316,6 +3302,7 @@ function mailboxReferenceExists(state, ref) {
|
|
|
3316
3302
|
case "work-item": return aggregate.workItems[ref.id] !== undefined;
|
|
3317
3303
|
case "input": return aggregate.inputRequests[ref.id] !== undefined;
|
|
3318
3304
|
case "message": return aggregate.messages[ref.id] !== undefined;
|
|
3305
|
+
case "event": return aggregate.events[ref.id] !== undefined;
|
|
3319
3306
|
}
|
|
3320
3307
|
}
|
|
3321
3308
|
switch (ref.type) {
|
|
@@ -49,7 +49,6 @@ export function copySqlitePassthroughState(home, sourceDatabaseFilename, targetD
|
|
|
49
49
|
target.transaction(() => {
|
|
50
50
|
mergeGlobalSequences(source, target);
|
|
51
51
|
copyTableRows(source, target, "outbox");
|
|
52
|
-
copyMailboxSignals(source, target);
|
|
53
52
|
copyTableRows(source, target, "work_item_candidates");
|
|
54
53
|
copyTableRows(source, target, "review_findings");
|
|
55
54
|
copyTableRows(source, target, "telemetry");
|
|
@@ -78,26 +77,6 @@ function mergeGlobalSequences(source, target) {
|
|
|
78
77
|
for (const row of rows)
|
|
79
78
|
merge.run(row.name, row.high_water);
|
|
80
79
|
}
|
|
81
|
-
function copyMailboxSignals(source, target) {
|
|
82
|
-
if (!sqliteTableExists(source, "mailbox_signals"))
|
|
83
|
-
return;
|
|
84
|
-
const rows = source.prepare(`SELECT m.target_key, s.sequence, s.reason, s.ref_type, s.ref_task_id,
|
|
85
|
-
s.ref_id, s.occurred_at, s.request_id
|
|
86
|
-
FROM mailbox_signals s
|
|
87
|
-
JOIN mailboxes m ON m.mailbox_id = s.mailbox_id
|
|
88
|
-
ORDER BY m.target_key, s.sequence`).iterate();
|
|
89
|
-
const findMailbox = target.prepare("SELECT mailbox_id FROM mailboxes WHERE target_key = ?");
|
|
90
|
-
const insert = target.prepare(`INSERT INTO mailbox_signals
|
|
91
|
-
(mailbox_id, sequence, reason, ref_type, ref_task_id, ref_id, occurred_at, request_id)
|
|
92
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
93
|
-
for (const row of rows) {
|
|
94
|
-
const mailbox = findMailbox.get(row.target_key);
|
|
95
|
-
if (mailbox === undefined) {
|
|
96
|
-
throw new Error(`SQLite migration cannot preserve signals for missing mailbox ${row.target_key}.`);
|
|
97
|
-
}
|
|
98
|
-
insert.run(mailbox.mailbox_id, row.sequence, row.reason, row.ref_type, row.ref_task_id, row.ref_id, row.occurred_at, row.request_id);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
80
|
function copyTableRows(source, target, table) {
|
|
102
81
|
if (!sqliteTableExists(source, table) || !sqliteTableExists(target, table))
|
|
103
82
|
return;
|
package/dist/task/nextAction.js
CHANGED
|
@@ -244,7 +244,7 @@ export function projectNextAction(facts) {
|
|
|
244
244
|
},
|
|
245
245
|
{
|
|
246
246
|
kind: "native-subagent",
|
|
247
|
-
reason: "Use
|
|
247
|
+
reason: "Use native implementer subagents when bounded work benefits from specialist attention or parallel fan-out inside the Leader Session.",
|
|
248
248
|
refs
|
|
249
249
|
}
|
|
250
250
|
],
|
|
@@ -47,7 +47,7 @@ const state = {
|
|
|
47
47
|
detail: null,
|
|
48
48
|
detailKey: null
|
|
49
49
|
};
|
|
50
|
-
const VALID_FILTERS = ["all", "active", "draft", "completed", "archived"];
|
|
50
|
+
const VALID_FILTERS = ["all", "active", "draft", "completed", "retired", "archived"];
|
|
51
51
|
let terminalSession = null;
|
|
52
52
|
let terminalStateKey = "terminal.closed";
|
|
53
53
|
|