@zq-silk/yui 0.6.4 → 0.6.6
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 +11 -3
- package/dist/cli/updateOrchestrator.js +12 -6
- package/dist/cli.js +12 -0
- package/dist/controller/agentRuntimeObserver.js +76 -16
- package/dist/controller/controller.js +398 -76
- package/dist/controller/fileSchedulerStoreAdapter.js +60 -51
- package/dist/controller/jobSupervisor.js +2 -16
- package/dist/controller/resourceInventoryLinux.js +73 -20
- package/dist/controller/runtime.js +1 -0
- package/dist/controller/runtimeEventProcessor.js +171 -31
- package/dist/coordination/keyedWorkQueue.js +189 -0
- package/dist/runtime/runtimeSessionCandidate.js +43 -0
- package/dist/scheduler/activeRoleRunDelivery.js +2 -4
- package/dist/scheduler/activeTaskProgress.js +2 -4
- package/dist/scheduler/leaderWakeupProcessor.js +3 -1
- package/dist/scheduler/ports.js +35 -2
- package/dist/scheduler/roleRunLiveness.js +44 -22
- package/dist/scheduler/roleRunStall.js +49 -25
- package/dist/storage/sqliteSchema.js +363 -14
- package/dist/storage/sqliteStore.js +403 -30
- package/dist/storage/storeRpc.js +5 -0
- package/dist/storage/taskStore.js +104 -6
- package/i18n/README.zh-CN.md +7 -1
- package/package.json +1 -1
|
@@ -10,8 +10,10 @@ import { resolveTimeZone } from "../output/timePresentation.js";
|
|
|
10
10
|
import { mailboxTargetKey, 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";
|
|
13
14
|
import { validateTaskMessage } from "../message/message.js";
|
|
14
15
|
import { validateAgentRun } from "../run/agentRun.js";
|
|
16
|
+
import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
|
|
15
17
|
import { FileSessionOwnerRegistry } from "../runtime/sessionOwnerRegistry.js";
|
|
16
18
|
import { validateReviewConfig } from "../review/reviewConfig.js";
|
|
17
19
|
import { validateReviewRound } from "../review/reviewRound.js";
|
|
@@ -387,6 +389,13 @@ export class FileTaskStore {
|
|
|
387
389
|
.map((aggregate) => clone(aggregate.task))
|
|
388
390
|
.sort((left, right) => numericCompare(left.id, right.id));
|
|
389
391
|
}
|
|
392
|
+
listActiveTaskIds() {
|
|
393
|
+
// The file rollback backend has no catalog index, so it filters the loaded
|
|
394
|
+
// aggregate. Layout-7 Controller hot paths use SQLite's bounded selector.
|
|
395
|
+
return this.listTasks()
|
|
396
|
+
.filter((task) => task.status === "active")
|
|
397
|
+
.map((task) => task.id);
|
|
398
|
+
}
|
|
390
399
|
getStateRevision() { return this.#state().revision; }
|
|
391
400
|
getTask(id) { return optional(this.#state().tasks[id]?.task); }
|
|
392
401
|
readNextActionFacts(taskId) {
|
|
@@ -592,12 +601,21 @@ export class FileTaskStore {
|
|
|
592
601
|
.find((job) => job.idempotencyKey === key);
|
|
593
602
|
return optional(found);
|
|
594
603
|
}
|
|
595
|
-
|
|
604
|
+
listActiveDurableJobs() {
|
|
605
|
+
// The file rollback backend has no secondary indexes, so it filters its
|
|
606
|
+
// already-loaded aggregate here. Layout-7 Controller hot paths use the
|
|
607
|
+
// SQLite implementation below, whose status index avoids this history
|
|
608
|
+
// scan; the return contract and ordering stay identical across backends.
|
|
596
609
|
const all = [];
|
|
597
610
|
for (const aggregate of Object.values(this.#state().tasks)) {
|
|
598
|
-
|
|
611
|
+
for (const job of Object.values(aggregate.durableJobs)) {
|
|
612
|
+
if (job.status === "queued" || job.status === "running")
|
|
613
|
+
all.push(job);
|
|
614
|
+
}
|
|
599
615
|
}
|
|
600
|
-
return all
|
|
616
|
+
return all
|
|
617
|
+
.map((job) => clone(job))
|
|
618
|
+
.sort((left, right) => numericCompare(`${left.taskId}/${left.id}`, `${right.taskId}/${right.id}`));
|
|
601
619
|
}
|
|
602
620
|
hasActiveDurableJobs() {
|
|
603
621
|
for (const aggregate of Object.values(this.#state().tasks)) {
|
|
@@ -732,6 +750,56 @@ export class FileTaskStore {
|
|
|
732
750
|
listRoleSessionSets(taskId) {
|
|
733
751
|
return values(this.#requireTask(taskId).roleSessionSets, (set) => set.owner.roleName);
|
|
734
752
|
}
|
|
753
|
+
listRuntimeSessionCandidates(query = {}) {
|
|
754
|
+
const state = this.#state();
|
|
755
|
+
const selectedTaskIds = query.taskIds === undefined
|
|
756
|
+
? undefined
|
|
757
|
+
: [...new Set(query.taskIds)].sort(numericCompare);
|
|
758
|
+
const taskAggregates = query.scope === "global"
|
|
759
|
+
? []
|
|
760
|
+
: selectedTaskIds === undefined
|
|
761
|
+
? Object.values(state.tasks)
|
|
762
|
+
: selectedTaskIds.flatMap((taskId) => {
|
|
763
|
+
const aggregate = state.tasks[taskId];
|
|
764
|
+
return aggregate === undefined ? [] : [aggregate];
|
|
765
|
+
});
|
|
766
|
+
const candidates = [
|
|
767
|
+
...taskAggregates.flatMap((task) => (Object.values(task.roleSessionSets).flatMap((sessions) => {
|
|
768
|
+
const candidate = projectRuntimeSessionCandidate(sessions);
|
|
769
|
+
return candidate === null ? [] : [candidate];
|
|
770
|
+
}))),
|
|
771
|
+
...(query.scope === "task" || selectedTaskIds !== undefined
|
|
772
|
+
? []
|
|
773
|
+
: Object.values(state.globalRoleSessionSets).flatMap((sessions) => {
|
|
774
|
+
const candidate = projectRuntimeSessionCandidate(sessions);
|
|
775
|
+
return candidate === null ? [] : [candidate];
|
|
776
|
+
}))
|
|
777
|
+
];
|
|
778
|
+
return candidates
|
|
779
|
+
.filter((candidate) => !query.cleanupRequiredOnly || candidate.cleanupRequired)
|
|
780
|
+
.sort(compareRuntimeSessionCandidates);
|
|
781
|
+
}
|
|
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
|
+
}
|
|
735
803
|
saveRoleSessionSet(sessions) {
|
|
736
804
|
const stored = taskSessions(sessions);
|
|
737
805
|
const taskId = stored.owner.taskId;
|
|
@@ -836,11 +904,17 @@ export class FileTaskStore {
|
|
|
836
904
|
}
|
|
837
905
|
getAgentRun(taskId, id) { return optional(this.#state().tasks[taskId]?.agentRuns[id]); }
|
|
838
906
|
listAgentRuns(taskId) { return values(this.#requireTask(taskId).agentRuns, "id"); }
|
|
839
|
-
listPendingProviderRetries() {
|
|
907
|
+
listPendingProviderRetries(taskIds) {
|
|
840
908
|
// The legacy File store can answer the empty case without a scan fallback.
|
|
841
909
|
// If durable retry state exists, the db-only capability must fail closed
|
|
842
910
|
// instead of silently losing the Controller's wake deadline.
|
|
843
|
-
|
|
911
|
+
const tasks = taskIds === undefined
|
|
912
|
+
? this.listTasks()
|
|
913
|
+
: [...new Set(taskIds)].sort(numericCompare).flatMap((taskId) => {
|
|
914
|
+
const task = this.getTask(taskId);
|
|
915
|
+
return task === null ? [] : [task];
|
|
916
|
+
});
|
|
917
|
+
for (const task of tasks) {
|
|
844
918
|
if (task.status !== "active")
|
|
845
919
|
continue;
|
|
846
920
|
for (const run of this.listAgentRuns(task.id)) {
|
|
@@ -1104,6 +1178,21 @@ export class FileTaskStore {
|
|
|
1104
1178
|
listInputRequests(taskId) {
|
|
1105
1179
|
return values(this.#requireTask(taskId).inputRequests, "id");
|
|
1106
1180
|
}
|
|
1181
|
+
listOpenInputRequests(taskIds) {
|
|
1182
|
+
const state = this.#state();
|
|
1183
|
+
const selected = taskIds === undefined
|
|
1184
|
+
? Object.values(state.tasks)
|
|
1185
|
+
: [...new Set(taskIds)].sort(numericCompare).flatMap((taskId) => {
|
|
1186
|
+
const aggregate = state.tasks[taskId];
|
|
1187
|
+
return aggregate === undefined ? [] : [aggregate];
|
|
1188
|
+
});
|
|
1189
|
+
return selected
|
|
1190
|
+
.flatMap((aggregate) => Object.values(aggregate.inputRequests))
|
|
1191
|
+
.filter((request) => request.status === "open")
|
|
1192
|
+
.map(clone)
|
|
1193
|
+
.sort((left, right) => (numericCompare(left.taskId, right.taskId)
|
|
1194
|
+
|| numericCompare(left.id, right.id)));
|
|
1195
|
+
}
|
|
1107
1196
|
listAllInputRequests() {
|
|
1108
1197
|
return Object.values(this.#state().tasks)
|
|
1109
1198
|
.flatMap((aggregate) => Object.values(aggregate.inputRequests).map(clone))
|
|
@@ -1390,6 +1479,15 @@ export class FileTaskStore {
|
|
|
1390
1479
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
1391
1480
|
.map(([, mailbox]) => clone(mailbox));
|
|
1392
1481
|
}
|
|
1482
|
+
listReadyWorkMailboxes() {
|
|
1483
|
+
// The file rollback backend has no secondary indexes. It filters the
|
|
1484
|
+
// in-memory aggregate while preserving the indexed SQLite contract's
|
|
1485
|
+
// target-key order; production layout 7 uses the bounded SQLite query.
|
|
1486
|
+
return Object.entries(this.#state().mailboxes)
|
|
1487
|
+
.filter(([, mailbox]) => mailbox.processing !== null || mailbox.pending !== null)
|
|
1488
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
1489
|
+
.map(([, mailbox]) => clone(mailbox));
|
|
1490
|
+
}
|
|
1393
1491
|
saveWorkMailbox(value) {
|
|
1394
1492
|
let mailbox;
|
|
1395
1493
|
try {
|
|
@@ -1410,7 +1508,7 @@ export class FileTaskStore {
|
|
|
1410
1508
|
return pendingWakeupProjection(this.getWorkMailbox({ kind: "role", taskId, roleName: "leader" }));
|
|
1411
1509
|
}
|
|
1412
1510
|
listPendingWakeups() {
|
|
1413
|
-
return this.
|
|
1511
|
+
return this.listReadyWorkMailboxes()
|
|
1414
1512
|
.flatMap((mailbox) => {
|
|
1415
1513
|
const wakeup = pendingWakeupProjection(mailbox);
|
|
1416
1514
|
return wakeup === null ? [] : [wakeup];
|
package/i18n/README.zh-CN.md
CHANGED
|
@@ -52,7 +52,8 @@ Leader 和 Operator,再说明全局 Worker 配置会复制到新建的 Task Ro
|
|
|
52
52
|
支持的思考强度。随后 setup 会确认位于 Yui home 外部的 Project workspace,
|
|
53
53
|
并询问 shell completion。选择器同时提供原生 CLI 默认值和自定义值入口。
|
|
54
54
|
再次运行不会删除已有 Task/Role,也不会改变当前安装的 Project workspace,
|
|
55
|
-
可用于安全地调整配置。
|
|
55
|
+
可用于安全地调整配置。setup 成功返回前会确保当前 Home 的后台 Controller
|
|
56
|
+
已经启动。
|
|
56
57
|
|
|
57
58
|
模型与思考强度属于 Agent binding 设置,因此 Operator、Leader 和全局
|
|
58
59
|
Worker 即使使用同一个 Agent CLI,也可以采用不同配置。Profile 中的
|
|
@@ -477,6 +478,11 @@ yui controller restart
|
|
|
477
478
|
|
|
478
479
|
`controller restart` 会用当前安装的 Yui 版本替换 Controller 进程及其调度循环、socket 服务,不会停止或重启已受管的 tmux/Agent 会话。
|
|
479
480
|
|
|
481
|
+
成功的 `setup`、`upgrade` 和 `update` 都会确保当前 Home 有一个运行中的
|
|
482
|
+
Controller;如果之前没有运行,会在完成后启动。只读命令和
|
|
483
|
+
`upgrade --dry-run` 不会启动 Controller。`update` 只有在新二进制健康检查通过后,
|
|
484
|
+
才会替换或启动 Controller。
|
|
485
|
+
|
|
480
486
|
恢复 reconciliation 默认每 120 秒执行一次。普通持久状态变化只会将 Task、Role 或 Operator key 放入队列并立即返回;固定 100ms 窗口内到达的 key 会合并触发一次不重叠的定向处理。Operator 呈现使用独立 lane,不会被 Task 的 Git/worktree 操作阻塞;周期 Git/worktree 处理只覆盖仍有持久 Task mailbox 工作的 Task,活动 Role 的存活检查合并为一次 tmux inventory。Codex turn-complete Hook 直接写入存储,不启动或等待 Controller,并给合法的 yield、输入请求或完成动作保留 2 秒竞争窗口;到期后才关闭被 Agent 遗忘的活动 Role Run。持久 WorkMailbox 会冻结当前 processing 批次,期间的新事件合并到下一 pending 批次;失败会释放当前批次供恢复。推荐输入与 pending Turn 共用最近 deadline 选择器,不依赖恢复扫描间隔;显式 `task reconcile` 仍会立即请求恢复扫描。保留的闭环为:
|
|
481
487
|
|
|
482
488
|
1. 准备 active Project Task 的主 worktree;
|