@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
|
@@ -282,6 +282,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
282
282
|
return { timeZone: this.store.getConfig().timeZone };
|
|
283
283
|
}
|
|
284
284
|
listTasks() { return this.store.listTasks(); }
|
|
285
|
+
listActiveTaskIds() {
|
|
286
|
+
return [...this.store.listActiveTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
287
|
+
}
|
|
285
288
|
getTask(taskId) { return this.store.getTask(taskId); }
|
|
286
289
|
getTaskWorkspace(taskId) { return this.store.getTaskWorkspace(taskId); }
|
|
287
290
|
getTaskBrief(taskId) { return this.store.getTaskBrief(taskId); }
|
|
@@ -298,10 +301,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
298
301
|
return this.store.getActiveAgentRun(taskId, roleName);
|
|
299
302
|
}
|
|
300
303
|
hasOpenInputRequest(taskId) {
|
|
301
|
-
return this.store.
|
|
304
|
+
return this.store.listOpenInputRequests([taskId]).length > 0;
|
|
302
305
|
}
|
|
303
|
-
listOpenInputRequests() {
|
|
304
|
-
return this.store.
|
|
306
|
+
listOpenInputRequests(taskIds) {
|
|
307
|
+
return this.store.listOpenInputRequests(taskIds);
|
|
305
308
|
}
|
|
306
309
|
getInputRequest(taskId, inputRequestId) {
|
|
307
310
|
return this.store.getInputRequest(taskId, inputRequestId);
|
|
@@ -321,10 +324,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
321
324
|
}
|
|
322
325
|
resolveExpiredInputRecommendations(now, taskIds) {
|
|
323
326
|
return this.store.transaction((store) => {
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
327
|
+
const selectedTaskIds = taskIds === undefined
|
|
328
|
+
? undefined
|
|
329
|
+
: [...taskIds].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
330
|
+
const expired = store.listOpenInputRequests(selectedTaskIds).filter((request) => (request.policy.kind === "recommended"
|
|
331
|
+
&& Date.parse(request.policy.timeoutAt) <= now.getTime()));
|
|
328
332
|
const resolved = [];
|
|
329
333
|
for (const request of expired) {
|
|
330
334
|
const task = store.getTask(request.taskId);
|
|
@@ -513,6 +517,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
513
517
|
}
|
|
514
518
|
getWorkMailbox(target) { return this.store.getWorkMailbox(target); }
|
|
515
519
|
listWorkMailboxes() { return this.store.listWorkMailboxes(); }
|
|
520
|
+
listReadyWorkMailboxes() { return this.store.listReadyWorkMailboxes(); }
|
|
516
521
|
queueTaskProgress(taskId, reason, now) {
|
|
517
522
|
this.store.transaction((store) => {
|
|
518
523
|
enqueueWork(store, { kind: "task", taskId }, reason, now, [
|
|
@@ -523,8 +528,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
523
528
|
enqueueLeaderWakeup(taskId, reason, now) {
|
|
524
529
|
return this.store.transaction((store) => (queueLeaderWakeup(store, taskId, reason, now)));
|
|
525
530
|
}
|
|
526
|
-
|
|
527
|
-
return this.store.
|
|
531
|
+
listActiveDurableJobs() {
|
|
532
|
+
return this.store.listActiveDurableJobs();
|
|
528
533
|
}
|
|
529
534
|
/**
|
|
530
535
|
* Apply one durable-job transition and, in the SAME transaction, enqueue
|
|
@@ -647,38 +652,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
647
652
|
});
|
|
648
653
|
}
|
|
649
654
|
listDormantRuntimeOwners() {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
: [];
|
|
665
|
-
})));
|
|
666
|
-
const globalOwners = this.store.listGlobalRoleSessionSets().flatMap((sessions) => {
|
|
667
|
-
const active = sessions.sessions[sessions.activeAgentId];
|
|
668
|
-
return active !== undefined
|
|
669
|
-
&& active.status !== "stopped"
|
|
670
|
-
&& !hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget(sessions.owner)))
|
|
671
|
-
? [{
|
|
672
|
-
owner: sessions.owner,
|
|
673
|
-
agentId: active.agentId,
|
|
674
|
-
adapterId: active.adapterId,
|
|
675
|
-
nativeSessionId: active.nativeSessionId,
|
|
676
|
-
...(active.launchId === undefined ? {} : { launchId: active.launchId }),
|
|
677
|
-
sessionUpdatedAt: active.updatedAt
|
|
678
|
-
}]
|
|
679
|
-
: [];
|
|
655
|
+
return this.listRuntimeSessionCandidates().flatMap((candidate) => {
|
|
656
|
+
if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(runtimeLifecycleTarget(candidate.owner)))
|
|
657
|
+
|| (candidate.owner.scope === "task"
|
|
658
|
+
&& this.store.getActiveAgentRun(candidate.owner.taskId, candidate.owner.roleName) !== null)) {
|
|
659
|
+
return [];
|
|
660
|
+
}
|
|
661
|
+
return [{
|
|
662
|
+
owner: candidate.owner,
|
|
663
|
+
agentId: candidate.agentId,
|
|
664
|
+
adapterId: candidate.adapterId,
|
|
665
|
+
nativeSessionId: candidate.nativeSessionId,
|
|
666
|
+
...(candidate.launchId === undefined ? {} : { launchId: candidate.launchId }),
|
|
667
|
+
sessionUpdatedAt: candidate.sessionUpdatedAt
|
|
668
|
+
}];
|
|
680
669
|
});
|
|
681
|
-
|
|
670
|
+
}
|
|
671
|
+
listRuntimeSessionCandidates(query = {}) {
|
|
672
|
+
return this.store.listRuntimeSessionCandidates(query);
|
|
682
673
|
}
|
|
683
674
|
markRuntimeOwnerStopped(candidate, now) {
|
|
684
675
|
return this.store.transaction((store) => {
|
|
@@ -755,6 +746,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
755
746
|
owner: "controller",
|
|
756
747
|
startedAt: input.now.toISOString()
|
|
757
748
|
}), batchId, { type: "run", taskId: input.task.id, id: input.run.id });
|
|
749
|
+
// SQLite stores the durable Run row and its active pointer separately;
|
|
750
|
+
// FileTaskStore happens to persist both from saveActiveAgentRun. Keep
|
|
751
|
+
// the adapter contract backend-neutral and make the two writes atomic.
|
|
752
|
+
store.saveAgentRun(input.run);
|
|
758
753
|
store.saveActiveAgentRun(input.run);
|
|
759
754
|
store.saveWorkMailbox(claimed);
|
|
760
755
|
store.saveRole(input.task.id, updateRoleStatus(role, "running", input.now));
|
|
@@ -1380,7 +1375,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1380
1375
|
nativeSessionId: input.nativeSessionId,
|
|
1381
1376
|
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
1382
1377
|
policy: "fixed",
|
|
1383
|
-
status:
|
|
1378
|
+
status: idleStatus === "stopped" || idleStatus === "broken"
|
|
1379
|
+
? idleStatus
|
|
1380
|
+
: sessions.inFlight === null ? idleStatus : "running",
|
|
1384
1381
|
effective
|
|
1385
1382
|
}, now);
|
|
1386
1383
|
if (sessions.inFlight === null) {
|
|
@@ -1416,7 +1413,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
1416
1413
|
runId: fence.runId,
|
|
1417
1414
|
turnId: input.turnId
|
|
1418
1415
|
}, now);
|
|
1419
|
-
|
|
1416
|
+
const settledStatus = sessions.sessions[input.agentId]?.status;
|
|
1417
|
+
if (settledStatus !== "stopped" && settledStatus !== "broken") {
|
|
1418
|
+
sessions = updateRoleAgentSessionStatus(sessions, input.agentId, "ready", now);
|
|
1419
|
+
}
|
|
1420
1420
|
store.saveTaskRoleSessionSet(sessions);
|
|
1421
1421
|
completeRuntimeHookReservation(store, owner, input.launchId);
|
|
1422
1422
|
return { session: sessions.sessions[input.agentId], duplicate: false };
|
|
@@ -1430,13 +1430,15 @@ export class FileSchedulerStoreAdapter {
|
|
|
1430
1430
|
};
|
|
1431
1431
|
});
|
|
1432
1432
|
}
|
|
1433
|
-
listPendingRuntimeTurnCompletions() {
|
|
1434
|
-
return this.store.
|
|
1433
|
+
listPendingRuntimeTurnCompletions(taskIds) {
|
|
1434
|
+
return this.store.listPendingRuntimeTurnCompletions(taskIds);
|
|
1435
1435
|
}
|
|
1436
1436
|
/** Resolves only completions whose grace deadline has elapsed. */
|
|
1437
1437
|
resolveDueRuntimeTurnCompletions(now, taskIds) {
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1438
|
+
const selectedTaskIds = taskIds === undefined
|
|
1439
|
+
? undefined
|
|
1440
|
+
: [...taskIds].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
1441
|
+
const due = this.listPendingRuntimeTurnCompletions(selectedTaskIds).filter((completion) => Date.parse(completion.dueAt) <= now.getTime());
|
|
1440
1442
|
const finalized = [];
|
|
1441
1443
|
for (const completion of due) {
|
|
1442
1444
|
this.store.transaction((store) => {
|
|
@@ -1472,7 +1474,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
1472
1474
|
runId: completion.runId,
|
|
1473
1475
|
turnId: completion.turnId
|
|
1474
1476
|
}, now);
|
|
1475
|
-
|
|
1477
|
+
const settledStatus = settled.sessions[completion.agentId]?.status;
|
|
1478
|
+
if (settledStatus !== "stopped" && settledStatus !== "broken") {
|
|
1479
|
+
settled = updateRoleAgentSessionStatus(settled, completion.agentId, "ready", now);
|
|
1480
|
+
}
|
|
1476
1481
|
store.saveTaskRoleSessionSet(settled);
|
|
1477
1482
|
});
|
|
1478
1483
|
}
|
|
@@ -1506,6 +1511,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1506
1511
|
throw new Error("Runtime turn completion does not match the effective launch identity.");
|
|
1507
1512
|
}
|
|
1508
1513
|
const sessions = effectiveExisting?.status === "ready"
|
|
1514
|
+
|| effectiveExisting?.status === "stopped"
|
|
1515
|
+
|| effectiveExisting?.status === "broken"
|
|
1509
1516
|
? current
|
|
1510
1517
|
: recordRoleAgentSession(current, {
|
|
1511
1518
|
agentId: input.agentId,
|
|
@@ -1816,8 +1823,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1816
1823
|
* due. The Controller arms its deadline timer from this projection, so a
|
|
1817
1824
|
* Controller restart resumes the same attempt lineage.
|
|
1818
1825
|
*/
|
|
1819
|
-
listPendingProviderRetries() {
|
|
1820
|
-
return this.store.listPendingProviderRetries();
|
|
1826
|
+
listPendingProviderRetries(taskIds) {
|
|
1827
|
+
return this.store.listPendingProviderRetries(taskIds);
|
|
1821
1828
|
}
|
|
1822
1829
|
/**
|
|
1823
1830
|
* Issue 04: reopens each due retry on its original Native Session. A Run
|
|
@@ -1826,8 +1833,10 @@ export class FileSchedulerStoreAdapter {
|
|
|
1826
1833
|
* re-pushes the exact same input in the same pass.
|
|
1827
1834
|
*/
|
|
1828
1835
|
resolveDueProviderRetries(now, taskIds) {
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1836
|
+
const selectedTaskIds = taskIds === undefined
|
|
1837
|
+
? undefined
|
|
1838
|
+
: [...taskIds].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
1839
|
+
const due = this.listPendingProviderRetries(selectedTaskIds).filter((entry) => Date.parse(entry.nextAttemptAt) <= now.getTime());
|
|
1831
1840
|
const reopened = [];
|
|
1832
1841
|
for (const entry of due) {
|
|
1833
1842
|
this.store.transaction((store) => {
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The supervisor is pure logic over injected ports. It is invoked from the
|
|
5
5
|
* Controller scheduler pass and on Controller startup. Each pass reconciles
|
|
6
|
-
*
|
|
6
|
+
* only jobs that can still change under supervision:
|
|
7
7
|
*
|
|
8
8
|
* queued -> write spec, spawn detached runner, transition to running
|
|
9
9
|
* running -> harvest exit.json / liveness via pid+startIdentity /
|
|
10
10
|
* evidence ladder (exit.json -> checkpoint -> unknown) /
|
|
11
11
|
* stale heartbeat SIGTERM then SIGKILL escalation
|
|
12
|
-
* terminal + !wakeupNotified -> set flag + enqueue Leader wakeup
|
|
13
12
|
* cancelRequestedAt -> write cancel fence + SIGTERM
|
|
14
13
|
*/
|
|
15
14
|
import { spawn } from "node:child_process";
|
|
@@ -41,7 +40,7 @@ export class DurableJobSupervisor {
|
|
|
41
40
|
this.#onError = options.onError ?? (() => undefined);
|
|
42
41
|
}
|
|
43
42
|
reconcile(now) {
|
|
44
|
-
const jobs = this.#store.
|
|
43
|
+
const jobs = this.#store.listActiveDurableJobs();
|
|
45
44
|
for (const job of jobs) {
|
|
46
45
|
try {
|
|
47
46
|
this.#reconcileJob(job, now);
|
|
@@ -62,9 +61,6 @@ export class DurableJobSupervisor {
|
|
|
62
61
|
this.#superviseRunning(job, now);
|
|
63
62
|
return;
|
|
64
63
|
}
|
|
65
|
-
if (isDurableJobTerminal(job.status) && job.wakeupNotified !== true) {
|
|
66
|
-
this.#notifyWakeup(job, now);
|
|
67
|
-
}
|
|
68
64
|
}
|
|
69
65
|
/** Composite key for the SIGKILL deadline map. */
|
|
70
66
|
#sigkillKey(job) {
|
|
@@ -335,16 +331,6 @@ export class DurableJobSupervisor {
|
|
|
335
331
|
outcome: job.result?.outcome ?? job.status
|
|
336
332
|
});
|
|
337
333
|
}
|
|
338
|
-
/**
|
|
339
|
-
* Recovery fallback for terminal jobs that reached their terminal state
|
|
340
|
-
* before the atomic wakeup composition existed (or whose wakeup was lost).
|
|
341
|
-
* The normal path (#harvestExit / #handleDeadProcess) already composes
|
|
342
|
-
* complete + wakeupNotified + enqueue in one transaction; this pass only
|
|
343
|
-
* fires for jobs that somehow slipped through without wakeupNotified.
|
|
344
|
-
*/
|
|
345
|
-
#notifyWakeup(job, now) {
|
|
346
|
-
this.#store.transitionDurableJob(job.taskId, job.id, (current) => markDurableJobWakeupNotified(current, now), now, { reason: "job-finished", refs: wakeupRefs(job) });
|
|
347
|
-
}
|
|
348
334
|
}
|
|
349
335
|
function wakeupRefs(job) {
|
|
350
336
|
const refs = [{ type: "task", id: job.taskId }];
|
|
@@ -574,7 +574,9 @@ function loadHomeState(home, warnings, options) {
|
|
|
574
574
|
}
|
|
575
575
|
try {
|
|
576
576
|
const store = (options.openCompatibleStore ?? openCompatibleFileTaskStore)(home);
|
|
577
|
-
const roles = store.listGlobalRoles()
|
|
577
|
+
const roles = [...store.listGlobalRoles()]
|
|
578
|
+
.sort((left, right) => numericCompare(left.name, right.name))
|
|
579
|
+
.map((role) => {
|
|
578
580
|
const session = activeLiveRoleAgentSession(store.getGlobalRoleSessionSet(role.name));
|
|
579
581
|
const binding = role.agentBindings[role.activeAgentId];
|
|
580
582
|
const agentId = session?.effective.agentId ?? role.activeAgentId;
|
|
@@ -588,25 +590,73 @@ function loadHomeState(home, warnings, options) {
|
|
|
588
590
|
...(session?.launchId === undefined ? {} : { launchId: session.launchId })
|
|
589
591
|
};
|
|
590
592
|
});
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
593
|
+
const appendTaskRole = (task, role) => {
|
|
594
|
+
const session = activeLiveRoleAgentSession(store.getRoleSessionSet(task.id, role.name));
|
|
595
|
+
const binding = role.agentBindings[role.activeAgentId];
|
|
596
|
+
const run = store.getActiveAgentRun(task.id, role.name);
|
|
597
|
+
const agentId = session?.effective.agentId ?? role.activeAgentId;
|
|
598
|
+
const adapterId = session?.effective.adapterId ?? binding?.adapterId;
|
|
599
|
+
roles.push({
|
|
600
|
+
ownerKind: "task-role",
|
|
601
|
+
taskId: task.id,
|
|
602
|
+
taskTitle: task.title,
|
|
603
|
+
taskStatus: task.status,
|
|
604
|
+
roleName: role.name,
|
|
605
|
+
agentId,
|
|
606
|
+
...(adapterId === undefined ? {} : { adapterId }),
|
|
607
|
+
...(session === null ? {} : { nativeSessionId: session.nativeSessionId }),
|
|
608
|
+
...(session?.launchId === undefined ? {} : { launchId: session.launchId }),
|
|
609
|
+
...(run === null ? {} : { runId: run.id })
|
|
610
|
+
});
|
|
611
|
+
};
|
|
612
|
+
// Layout-7 SQLite owns bounded active-Task and current-Session indexes.
|
|
613
|
+
// Use those indexes only as selectors, then point-read the authoritative
|
|
614
|
+
// Task/Role/Session/Run records so an index row cannot bypass ownership
|
|
615
|
+
// fences. The legacy File store intentionally retains its complete
|
|
616
|
+
// listTasks fallback because it has no equivalent active selector.
|
|
617
|
+
const hotStore = store;
|
|
618
|
+
const indexedTaskIds = hotStore.listActiveTaskIds === undefined
|
|
619
|
+
? undefined
|
|
620
|
+
: [...new Set(hotStore.listActiveTaskIds())].sort(numericCompare);
|
|
621
|
+
if (indexedTaskIds === undefined) {
|
|
622
|
+
for (const task of store.listTasks()) {
|
|
623
|
+
for (const role of [...store.listRoles(task.id)].sort((left, right) => (numericCompare(left.name, right.name)))) {
|
|
624
|
+
appendTaskRole(task, role);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
const sessionCandidates = typeof store.listRuntimeSessionCandidates === "function"
|
|
630
|
+
? store.listRuntimeSessionCandidates({ scope: "task" })
|
|
631
|
+
: [];
|
|
632
|
+
const candidateRoles = new Map();
|
|
633
|
+
for (const candidate of sessionCandidates) {
|
|
634
|
+
if (candidate.owner.scope !== "task")
|
|
635
|
+
continue;
|
|
636
|
+
const roleNames = candidateRoles.get(candidate.owner.taskId) ?? new Set();
|
|
637
|
+
roleNames.add(candidate.owner.roleName);
|
|
638
|
+
candidateRoles.set(candidate.owner.taskId, roleNames);
|
|
639
|
+
}
|
|
640
|
+
const activeTaskSet = new Set(indexedTaskIds);
|
|
641
|
+
const taskIds = [...new Set([
|
|
642
|
+
...indexedTaskIds,
|
|
643
|
+
...candidateRoles.keys()
|
|
644
|
+
])].sort(numericCompare);
|
|
645
|
+
for (const taskId of taskIds) {
|
|
646
|
+
const task = store.getTask(taskId);
|
|
647
|
+
if (task === null)
|
|
648
|
+
continue;
|
|
649
|
+
const rolesForTask = activeTaskSet.has(taskId) && task.status === "active"
|
|
650
|
+
? store.listRoles(taskId)
|
|
651
|
+
: [...(candidateRoles.get(taskId) ?? [])]
|
|
652
|
+
.sort(numericCompare)
|
|
653
|
+
.flatMap((roleName) => {
|
|
654
|
+
const role = store.getRole(taskId, roleName);
|
|
655
|
+
return role === null ? [] : [role];
|
|
656
|
+
});
|
|
657
|
+
for (const role of [...rolesForTask].sort((left, right) => (numericCompare(left.name, right.name)))) {
|
|
658
|
+
appendTaskRole(task, role);
|
|
659
|
+
}
|
|
610
660
|
}
|
|
611
661
|
}
|
|
612
662
|
return { storageStatus: schema.status, roles };
|
|
@@ -616,6 +666,9 @@ function loadHomeState(home, warnings, options) {
|
|
|
616
666
|
return { storageStatus: "invalid", roles: [] };
|
|
617
667
|
}
|
|
618
668
|
}
|
|
669
|
+
function numericCompare(left, right) {
|
|
670
|
+
return left.localeCompare(right, undefined, { numeric: true });
|
|
671
|
+
}
|
|
619
672
|
function inspectHomePanes(home, environment, warnings) {
|
|
620
673
|
try {
|
|
621
674
|
const executor = new NodeCommandExecutor();
|
|
@@ -373,6 +373,7 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
373
373
|
intervalMs: options.intervalMs
|
|
374
374
|
?? reconciliationIntervalMilliseconds(store.getConfig().reconciliationIntervalSeconds),
|
|
375
375
|
signalWindowMs: options.signalWindowMs,
|
|
376
|
+
taskConcurrency: options.taskConcurrency,
|
|
376
377
|
deliveryRetryMs: options.deliveryRetryMs,
|
|
377
378
|
deliveryRetryLimit: options.deliveryRetryLimit,
|
|
378
379
|
now: options.now,
|