@repo-os/repoos 0.5.73 → 0.5.74
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/dist/.build-info.json +2 -2
- package/dist/.build-stamp.json +1 -1
- package/dist/commands/checks.js +5 -6
- package/dist/core/attention.d.ts +2 -2
- package/dist/core/attention.js +1 -0
- package/dist/core/check-wait.d.ts +7 -4
- package/dist/core/check-wait.js +11 -8
- package/dist/core/context-pack.js +2 -0
- package/dist/core/cross-process-slots.d.ts +3 -0
- package/dist/core/cross-process-slots.js +26 -5
- package/dist/core/git-maintenance.d.ts +16 -1
- package/dist/core/git-maintenance.js +82 -1
- package/dist/core/git.d.ts +2 -0
- package/dist/core/git.js +14 -2
- package/dist/core/local-gates.js +5 -2
- package/dist/core/local-queue-marker.d.ts +4 -0
- package/dist/core/local-queue-marker.js +7 -0
- package/dist/core/needs-input.js +2 -2
- package/dist/core/shot-fixtures.d.ts +1 -1
- package/dist/core/shot-fixtures.js +1 -0
- package/dist/server/attention-events.d.ts +1 -0
- package/dist/server/attention-events.js +7 -0
- package/dist/server/server.js +31 -1
- package/dist/server/task-watchdog.d.ts +1 -9
- package/dist/server/task-watchdog.js +35 -28
- package/dist/ui/assets/{ActivityIndicator-CFysp8fx.js → ActivityIndicator-4ufauHwi.js} +1 -1
- package/dist/ui/assets/{AgentModelControl-iHOGbgue.js → AgentModelControl-9-ZIzPrl.js} +1 -1
- package/dist/ui/assets/{AgentModelModal-Cifx9MEF.js → AgentModelModal-Dgv9l_07.js} +1 -1
- package/dist/ui/assets/{AgentsView-DoAgVcFi.js → AgentsView-Bl9Olksk.js} +1 -1
- package/dist/ui/assets/{ChatAgentModelChip-DPHWICS-.js → ChatAgentModelChip-CwDV90K2.js} +1 -1
- package/dist/ui/assets/{ChecksView-DaLrQ8xf.js → ChecksView-DTrP7Uuz.js} +0 -0
- package/dist/ui/assets/{ContextView-Btiqouai.js → ContextView-CxfgyJ-y.js} +2 -2
- package/dist/ui/assets/{DashboardView-BzKRZk7c.js → DashboardView-5rGQ9OEh.js} +1 -1
- package/dist/ui/assets/{DependencyChip-BZQN-6jN.js → DependencyChip-CRG4bs5o.js} +1 -1
- package/dist/ui/assets/{DeploymentsView-CyiTsye9.js → DeploymentsView-BFOhkvLs.js} +1 -1
- package/dist/ui/assets/{DevView-CwWIgB5t.js → DevView-CYfAT-0x.js} +1 -1
- package/dist/ui/assets/{DiffView-DPlMpZwl.js → DiffView-BTDGsIts.js} +1 -1
- package/dist/ui/assets/{FailedTestsList-DKSLDbG6.js → FailedTestsList-xY10Qzc8.js} +1 -1
- package/dist/ui/assets/{InputsView-BZzEXRPF.js → InputsView-7HAgIHQh.js} +1 -1
- package/dist/ui/assets/{LoginView-Bww-iSWH.js → LoginView-BwP14kSS.js} +1 -1
- package/dist/ui/assets/{ReleasesView-B9Ao5H9s.js → ReleasesView-BTN2vXUI.js} +1 -1
- package/dist/ui/assets/{SecretsView-BSEb1LP0.js → SecretsView-CAFjCFQF.js} +1 -1
- package/dist/ui/assets/{SettingsView-DE5rdOwQ.js → SettingsView-B2WOxPud.js} +1 -1
- package/dist/ui/assets/{StoriesView-CzcU-EES.js → StoriesView-C-euuUSI.js} +1 -1
- package/dist/ui/assets/{VoiceDictate-BIXyHCYF.js → VoiceDictate-CES0G8Vq.js} +1 -1
- package/dist/ui/assets/WorkView-C9qAO66W.js +2 -0
- package/dist/ui/assets/{abnfDiagram-VCTEODGH-D6t2YdDd.js → abnfDiagram-VCTEODGH-DqUj4qFG.js} +1 -1
- package/dist/ui/assets/{architectureDiagram-5GKGNRK7-DfvZY3qH.js → architectureDiagram-5GKGNRK7-DAHlI7NO.js} +1 -1
- package/dist/ui/assets/{blockDiagram-I7D4REHJ-p9SaIkVm.js → blockDiagram-I7D4REHJ-BZ71-q2F.js} +1 -1
- package/dist/ui/assets/{c4Diagram-7LVT6UL2-BL-ftUgy.js → c4Diagram-7LVT6UL2-CXbbODvo.js} +1 -1
- package/dist/ui/assets/channel-Cz_Tghq-.js +1 -0
- package/dist/ui/assets/{check-setup-DQZqfS14.js → check-setup-Bng0rbuw.js} +1 -1
- package/dist/ui/assets/{chunk-2E4U76K2-V3UlY0fF.js → chunk-2E4U76K2-RYa4g59L.js} +1 -1
- package/dist/ui/assets/{chunk-4HAMMTFA-CMXUfdBr.js → chunk-4HAMMTFA-CXuZpZ_d.js} +1 -1
- package/dist/ui/assets/{chunk-75Z2AOVW-BVgiO27L.js → chunk-75Z2AOVW-BP2GTRXO.js} +1 -1
- package/dist/ui/assets/{chunk-CLGD4ZFX-CRaWjb_k.js → chunk-CLGD4ZFX-DLzOmHqh.js} +1 -1
- package/dist/ui/assets/{chunk-DU6HZSFF-DoybIPWm.js → chunk-DU6HZSFF-DdjsSi6z.js} +1 -1
- package/dist/ui/assets/{chunk-F27PBJKO-N6cLLABx.js → chunk-F27PBJKO-Dnn5wO3i.js} +1 -1
- package/dist/ui/assets/{chunk-GMAD6QVW-iqG0Mryo.js → chunk-GMAD6QVW-DdG_nfFi.js} +1 -1
- package/dist/ui/assets/{chunk-GVQU2GXP-f8esshKW.js → chunk-GVQU2GXP-DMAbXLSv.js} +1 -1
- package/dist/ui/assets/{chunk-IMKFNOWR-DRyUVZ8K.js → chunk-IMKFNOWR-DHz_-TDB.js} +1 -1
- package/dist/ui/assets/{chunk-L3NEJ4N5-MIVQTWm1.js → chunk-L3NEJ4N5-BwhWYbi6.js} +1 -1
- package/dist/ui/assets/{chunk-OSK3NFVY-BCHyxVoP.js → chunk-OSK3NFVY-CWlbubvG.js} +1 -1
- package/dist/ui/assets/{chunk-P2QGCYS3-ChYZkZI_.js → chunk-P2QGCYS3-D9Muf9td.js} +1 -1
- package/dist/ui/assets/{chunk-POPQ4Y6H-TgXMUskQ.js → chunk-POPQ4Y6H-D3ODGb45.js} +1 -1
- package/dist/ui/assets/{chunk-PWAF6VOD-qsH24WJM.js → chunk-PWAF6VOD-D6LRyAPN.js} +1 -1
- package/dist/ui/assets/{chunk-SHT3W25Y-BQu5f8tz.js → chunk-SHT3W25Y-CXu4onhx.js} +1 -1
- package/dist/ui/assets/{chunk-SVP7TREG-CvmZPDcQ.js → chunk-SVP7TREG-7kV5LYQU.js} +1 -1
- package/dist/ui/assets/{chunk-TICWLB2K-DPOtzVuP.js → chunk-TICWLB2K-ygSlNLtb.js} +1 -1
- package/dist/ui/assets/{chunk-TLUHSLCS-D30OEKwA.js → chunk-TLUHSLCS-Bj-PwaDc.js} +2 -2
- package/dist/ui/assets/classDiagram-ZZMXUADV-DHAdi9dV.js +1 -0
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-DHAdi9dV.js +1 -0
- package/dist/ui/assets/{cynefinDiagram-5FMLGOSQ-BMMxTCVH.js → cynefinDiagram-5FMLGOSQ-DDFWjNjK.js} +1 -1
- package/dist/ui/assets/{dagre-GXQ25YYZ-mdcBXgnq.js → dagre-GXQ25YYZ-CClbhBuT.js} +1 -1
- package/dist/ui/assets/{diagram-S7CK7UJ4-BYHQN-__.js → diagram-S7CK7UJ4-BXdPXgUF.js} +1 -1
- package/dist/ui/assets/{diagram-UQ7AKVKN-du5xkY-t.js → diagram-UQ7AKVKN-Cx3iFpM0.js} +1 -1
- package/dist/ui/assets/{diagram-VSXAHHWV-CDljZt2c.js → diagram-VSXAHHWV-szmYWA2M.js} +1 -1
- package/dist/ui/assets/{diagram-VX7I27RA-66z56eI2.js → diagram-VX7I27RA-CX2RE3DT.js} +1 -1
- package/dist/ui/assets/{diagram-Z3DM3KII-DDSrtck7.js → diagram-Z3DM3KII-CmMfHB9Q.js} +1 -1
- package/dist/ui/assets/{docs-8_CFug_V.js → docs-eoM8eVGk.js} +1 -1
- package/dist/ui/assets/{ebnfDiagram-PWID7BFC-DQDNc_-9.js → ebnfDiagram-PWID7BFC-BO-2n4ZP.js} +1 -1
- package/dist/ui/assets/{erDiagram-RLTQ6QDP-CAWU1WaF.js → erDiagram-RLTQ6QDP-dCEqdV8F.js} +1 -1
- package/dist/ui/assets/flowDiagram-HODETNUW-CX9qpoV4.js +1 -0
- package/dist/ui/assets/{ganttDiagram-EL5Y4UJY-C6V1kaYI.js → ganttDiagram-EL5Y4UJY-BJb9E6tm.js} +1 -1
- package/dist/ui/assets/{gitGraphDiagram-WWUBYQGX-BGygR4v-.js → gitGraphDiagram-WWUBYQGX-B1CtsArh.js} +1 -1
- package/dist/ui/assets/index-Pr4f9cgO.js +68 -0
- package/dist/ui/assets/{index-BEQoXI01.css → index-m6du4z4N.css} +1 -1
- package/dist/ui/assets/{infoDiagram-27XIBGKW-DZILjOjA.js → infoDiagram-27XIBGKW-a6qt1YhM.js} +1 -1
- package/dist/ui/assets/{ishikawaDiagram-5VMMS53U-B2cFxmGF.js → ishikawaDiagram-5VMMS53U-DU6MVFDK.js} +1 -1
- package/dist/ui/assets/{journeyDiagram-3NMN7TZE-C5Sb8YTl.js → journeyDiagram-3NMN7TZE-oc0zPgCF.js} +1 -1
- package/dist/ui/assets/{kanban-definition-UXKFOSKX-CV3Osxtb.js → kanban-definition-UXKFOSKX-BFCluyXI.js} +1 -1
- package/dist/ui/assets/{line-BxE-EqfN.js → line-C0dnI-L4.js} +1 -1
- package/dist/ui/assets/{mermaid-parser.core-CQ4lbIXW.js → mermaid-parser.core-CfrNQx8w.js} +1 -1
- package/dist/ui/assets/{mermaid.core-Do6AR5iD.js → mermaid.core-CKD0p_aW.js} +3 -3
- package/dist/ui/assets/{mindmap-definition-YA3MSWOX-Cpi-IHX6.js → mindmap-definition-YA3MSWOX-DEEwX5b3.js} +1 -1
- package/dist/ui/assets/{pegDiagram-XKGWAZYB-DMcJVhJW.js → pegDiagram-XKGWAZYB-P0QyFQvY.js} +1 -1
- package/dist/ui/assets/{pieDiagram-E7YTZNPT-CdXUU05z.js → pieDiagram-E7YTZNPT-DUingoVK.js} +1 -1
- package/dist/ui/assets/{quadrantDiagram-AXDQQJYC-B2_Z5M8E.js → quadrantDiagram-AXDQQJYC-5KZfHHFZ.js} +1 -1
- package/dist/ui/assets/{railroadDiagram-O6MQD6OU-u4uj57HH.js → railroadDiagram-O6MQD6OU-D0sx6QJB.js} +1 -1
- package/dist/ui/assets/{repo-CPSBfMRE.js → repo-B9GZ5YkR.js} +1 -1
- package/dist/ui/assets/{requirementDiagram-BXWQKSXE-B4keHdJD.js → requirementDiagram-BXWQKSXE-B4DIJgDV.js} +1 -1
- package/dist/ui/assets/{sankeyDiagram-P5KCCOFB-dZdIPYfI.js → sankeyDiagram-P5KCCOFB-Ds9sRhEp.js} +1 -1
- package/dist/ui/assets/{sequenceDiagram-WJ2MYXX4-kBAuNmyS.js → sequenceDiagram-WJ2MYXX4-BsebZj2V.js} +1 -1
- package/dist/ui/assets/{stateDiagram-D77RDMKH-D83tShBX.js → stateDiagram-D77RDMKH-D4ndCrsI.js} +1 -1
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-BHwEjBfW.js +1 -0
- package/dist/ui/assets/{swimlanes-42K2YHIH-DRDo7aP4.js → swimlanes-42K2YHIH-DUuSZaDY.js} +1 -1
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-BrUdO8ca.js +8 -0
- package/dist/ui/assets/{timeline-definition-24CTP7MA-mnm_omtS.js → timeline-definition-24CTP7MA-UcuOAqHi.js} +1 -1
- package/dist/ui/assets/{ui-ClI5E21n.js → ui-fJTOEtHH.js} +2 -2
- package/dist/ui/assets/{vennDiagram-4TSXK5OY-Djq6ULjF.js → vennDiagram-4TSXK5OY-Doc_z7zd.js} +1 -1
- package/dist/ui/assets/{wardleyDiagram-VM6X3IG4-GfB-sti2.js → wardleyDiagram-VM6X3IG4-BbAt19tq.js} +1 -1
- package/dist/ui/assets/{xychartDiagram-S5SC5T6Z-DRh9G9Zb.js → xychartDiagram-S5SC5T6Z-CFT0no8b.js} +1 -1
- package/dist/ui/index.html +12 -12
- package/dist/ui/sw.js +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/WorkView-DxNhKHZ0.js +0 -2
- package/dist/ui/assets/channel-BV21EJtW.js +0 -1
- package/dist/ui/assets/classDiagram-ZZMXUADV-DqgLJsDt.js +0 -1
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-DqgLJsDt.js +0 -1
- package/dist/ui/assets/flowDiagram-HODETNUW-tMrL1QbJ.js +0 -1
- package/dist/ui/assets/index-ib5yOjK6.js +0 -68
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-CNtNh1Ls.js +0 -1
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-CGtDYnNu.js +0 -8
package/dist/.build-info.json
CHANGED
package/dist/.build-stamp.json
CHANGED
package/dist/commands/checks.js
CHANGED
|
@@ -2,8 +2,8 @@ import { getCheckStore } from "../core/check-store.js";
|
|
|
2
2
|
import { formatSlowestTestFiles } from "../core/test-file-timings.js";
|
|
3
3
|
import { c } from "../cli/colors.js";
|
|
4
4
|
import { loadConfig } from "../core/config.js";
|
|
5
|
-
import { inspectCrossProcessSlots, localCheckLockRoot, releaseLocalCheckSlot, } from "../core/cross-process-slots.js";
|
|
6
|
-
import { readQueueMarkers } from "../core/local-queue-marker.js";
|
|
5
|
+
import { inspectCrossProcessSlots, listSlotWaiterPids, localCheckLockRoot, releaseLocalCheckSlot, } from "../core/cross-process-slots.js";
|
|
6
|
+
import { currentQueueMarkers, readQueueMarkers } from "../core/local-queue-marker.js";
|
|
7
7
|
import { mainCheckoutRoot } from "../core/git.js";
|
|
8
8
|
import { heavySlotLimit } from "../core/resource-limits.js";
|
|
9
9
|
export function cmdChecks(args) {
|
|
@@ -73,10 +73,9 @@ export function cmdChecks(args) {
|
|
|
73
73
|
? `slot ${holder.slot}: ${task}, pid ${holder.pid}, ${holder.dead ? "dead, reclaiming" : holder.wedged ? "wedged" : "alive"}, held ${Math.round(holder.ageMs / 1000)}s, heartbeat ${Math.round(holder.heartbeatAgeMs / 1000)}s ago`
|
|
74
74
|
: `slot ${holder.slot}: free`);
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.sort((a, b) => a.position - b.position)) {
|
|
76
|
+
const queue = currentQueueMarkers(readQueueMarkers(boardRoot, cacheDir).filter((entry) => entry.kind === "local-check"), listSlotWaiterPids(root));
|
|
77
|
+
queue.forEach((marker) => {
|
|
79
78
|
const waitingMs = Math.max(0, Date.now() - Date.parse(marker.since));
|
|
80
79
|
console.log(`queue position ${marker.position}: #${marker.taskId}, waiting ${Math.round(waitingMs / 1000)}s`);
|
|
81
|
-
}
|
|
80
|
+
});
|
|
82
81
|
}
|
package/dist/core/attention.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface CloseOutOutcomeFeedEvent {
|
|
|
12
12
|
}
|
|
13
13
|
export type AttentionSeverity = "info" | "warning" | "error";
|
|
14
14
|
/** Every attention kind the bell and API expose. */
|
|
15
|
-
export type AttentionKind = "releaseNotesReady" | "releaseSucceeded" | "releaseFailed" | "closeOutSucceeded" | "closeOutFailed" | "closeOutTimedOut" | "taskReview" | "taskNeedsInput" | "taskNeedsMerge" | "taskAssigned" | "providerFailure" | "providerUsageLimit" | "silentRun" | "slowRun" | "slowRunsRecently" | "spendThreshold" | "awaitingVisualCheck" | "remoteFallback" | "remoteHostDegraded" | "ctoAction" | "ctoSilent";
|
|
15
|
+
export type AttentionKind = "releaseNotesReady" | "releaseSucceeded" | "releaseFailed" | "closeOutSucceeded" | "closeOutFailed" | "closeOutTimedOut" | "taskReview" | "taskNeedsInput" | "taskNeedsMerge" | "taskAssigned" | "providerFailure" | "providerUsageLimit" | "silentRun" | "slowRun" | "slowRunsRecently" | "spendThreshold" | "awaitingVisualCheck" | "remoteFallback" | "remoteHostDegraded" | "ctoAction" | "gitLock" | "ctoSilent";
|
|
16
16
|
export interface AttentionItem {
|
|
17
17
|
/** Stable per event — dedupe key for clients and dismiss markers. */
|
|
18
18
|
id: string;
|
|
@@ -33,7 +33,7 @@ export interface AttentionFeed {
|
|
|
33
33
|
/** Durable attention events recorded on the server (provider failures, remote fallback). */
|
|
34
34
|
export interface RecordedAttentionEvent {
|
|
35
35
|
id: string;
|
|
36
|
-
kind: "providerFailure" | "providerUsageLimit" | "remoteFallback" | "ctoAction";
|
|
36
|
+
kind: "providerFailure" | "providerUsageLimit" | "remoteFallback" | "ctoAction" | "gitLock";
|
|
37
37
|
taskId: string | null;
|
|
38
38
|
message: string;
|
|
39
39
|
detail: string;
|
package/dist/core/attention.js
CHANGED
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
* `[queue]` status lines and write queue markers; this recorder turns those
|
|
10
10
|
* lines into (a) a human-readable "waiting: <reason> for Nm" trail on stdout —
|
|
11
11
|
* which the handoff transcript captures — and (b) a `waited` fact persisted on
|
|
12
|
-
* the run's CheckRunRecord. A wait that crosses ten minutes
|
|
13
|
-
*
|
|
12
|
+
* the run's CheckRunRecord. A wait that crosses ten minutes reminds the
|
|
13
|
+
* engineer to keep waiting.
|
|
14
|
+
* Queueing is normal and does not itself request human input.
|
|
14
15
|
*/
|
|
15
16
|
/** How often the "still waiting" line repeats while the wait continues. */
|
|
16
17
|
export declare const CHECK_WAIT_REMINDER_MS = 60000;
|
|
17
|
-
/** A
|
|
18
|
-
export declare const
|
|
18
|
+
/** A long normal wait gets an explicit keep-waiting reminder. */
|
|
19
|
+
export declare const CHECK_WAIT_REMINDER_THRESHOLD_MS: number;
|
|
20
|
+
/** Missing holder heartbeats for this long indicate an abnormal wait. */
|
|
21
|
+
export declare const CHECK_WAIT_STALLED_MS: number;
|
|
19
22
|
export interface CheckWait {
|
|
20
23
|
/** Latest observed reason, verbatim from the gate's status line. */
|
|
21
24
|
reason: string;
|
package/dist/core/check-wait.js
CHANGED
|
@@ -9,17 +9,20 @@
|
|
|
9
9
|
* `[queue]` status lines and write queue markers; this recorder turns those
|
|
10
10
|
* lines into (a) a human-readable "waiting: <reason> for Nm" trail on stdout —
|
|
11
11
|
* which the handoff transcript captures — and (b) a `waited` fact persisted on
|
|
12
|
-
* the run's CheckRunRecord. A wait that crosses ten minutes
|
|
13
|
-
*
|
|
12
|
+
* the run's CheckRunRecord. A wait that crosses ten minutes reminds the
|
|
13
|
+
* engineer to keep waiting.
|
|
14
|
+
* Queueing is normal and does not itself request human input.
|
|
14
15
|
*/
|
|
15
16
|
/** How often the "still waiting" line repeats while the wait continues. */
|
|
16
17
|
export const CHECK_WAIT_REMINDER_MS = 60_000;
|
|
17
|
-
/** A
|
|
18
|
-
export const
|
|
18
|
+
/** A long normal wait gets an explicit keep-waiting reminder. */
|
|
19
|
+
export const CHECK_WAIT_REMINDER_THRESHOLD_MS = 10 * 60_000;
|
|
20
|
+
/** Missing holder heartbeats for this long indicate an abnormal wait. */
|
|
21
|
+
export const CHECK_WAIT_STALLED_MS = 30 * 60_000;
|
|
19
22
|
export function createCheckWaitRecorder(emit, now = Date.now) {
|
|
20
23
|
let current = null;
|
|
21
24
|
let lastAnnouncedAt = 0;
|
|
22
|
-
let
|
|
25
|
+
let announcedLongWait = false;
|
|
23
26
|
return {
|
|
24
27
|
onStatusLine(line) {
|
|
25
28
|
const reason = line.trim();
|
|
@@ -37,10 +40,10 @@ export function createCheckWaitRecorder(emit, now = Date.now) {
|
|
|
37
40
|
lastAnnouncedAt = now();
|
|
38
41
|
emit(`waiting: ${reason} for ${formatWaitDuration(waitedMs)}`);
|
|
39
42
|
}
|
|
40
|
-
if (!
|
|
41
|
-
|
|
43
|
+
if (!announcedLongWait && waitedMs >= CHECK_WAIT_REMINDER_THRESHOLD_MS) {
|
|
44
|
+
announcedLongWait = true;
|
|
42
45
|
emit(`waiting: ${reason} for ${formatWaitDuration(waitedMs)} — ` +
|
|
43
|
-
"
|
|
46
|
+
"queued checks are normal — keep waiting; do not end your turn");
|
|
44
47
|
}
|
|
45
48
|
},
|
|
46
49
|
finish() {
|
|
@@ -657,6 +657,8 @@ function renderPack(config, task, branch, cwd, cacheHit, bootstrapResult, releva
|
|
|
657
657
|
push("bun run build:ui # UI-only build");
|
|
658
658
|
push("```");
|
|
659
659
|
push("");
|
|
660
|
+
push("A queued check is normal, including a long wait for a live slot holder or the pressure guard. Keep waiting until it passes, then hand off; do not end your turn because of the waiting badge. Only abnormal waits request input.");
|
|
661
|
+
push("");
|
|
660
662
|
// ---- Managed Preview ----
|
|
661
663
|
push("### Managed Preview");
|
|
662
664
|
push("");
|
|
@@ -11,6 +11,7 @@ export interface SlotAcquireOptions {
|
|
|
11
11
|
/** Written into the slot dir for debugging and stale recovery. */
|
|
12
12
|
meta?: Record<string, unknown>;
|
|
13
13
|
onQueued?: (ahead: number, position: number) => void;
|
|
14
|
+
onWaiterCleared?: () => void;
|
|
14
15
|
onHeartbeat?: () => void;
|
|
15
16
|
staleAfterMs?: number;
|
|
16
17
|
reclaimGraceMs?: number;
|
|
@@ -37,6 +38,8 @@ export declare function reapOrphanedLocalCheckGroups(lockRoot: string): number[]
|
|
|
37
38
|
export declare function acquireCrossProcessSlot(opts: SlotAcquireOptions): Promise<() => void>;
|
|
38
39
|
/** Live waiters parked on this slot root (dead pids are ignored). */
|
|
39
40
|
export declare function countSlotWaiters(lockRoot: string): number;
|
|
41
|
+
/** PIDs that still have a live waiter entry in this slot queue. */
|
|
42
|
+
export declare function listSlotWaiterPids(lockRoot: string): number[];
|
|
40
43
|
export declare function heavyJobCounts(lockRoot: string): {
|
|
41
44
|
running: number;
|
|
42
45
|
queued: number;
|
|
@@ -352,6 +352,7 @@ export async function acquireCrossProcessSlot(opts) {
|
|
|
352
352
|
/* already gone */
|
|
353
353
|
}
|
|
354
354
|
waiterPath = null;
|
|
355
|
+
opts.onWaiterCleared?.();
|
|
355
356
|
};
|
|
356
357
|
const markWaiting = () => {
|
|
357
358
|
if (waiterPath)
|
|
@@ -363,6 +364,15 @@ export async function acquireCrossProcessSlot(opts) {
|
|
|
363
364
|
};
|
|
364
365
|
let heartbeat;
|
|
365
366
|
const signalHandlers = new Map();
|
|
367
|
+
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
368
|
+
const handler = () => {
|
|
369
|
+
clearWaiter();
|
|
370
|
+
process.off(signal, handler);
|
|
371
|
+
process.kill(process.pid, signal);
|
|
372
|
+
};
|
|
373
|
+
signalHandlers.set(signal, handler);
|
|
374
|
+
process.once(signal, handler);
|
|
375
|
+
}
|
|
366
376
|
const release = () => {
|
|
367
377
|
if (heartbeat)
|
|
368
378
|
clearInterval(heartbeat);
|
|
@@ -383,6 +393,9 @@ export async function acquireCrossProcessSlot(opts) {
|
|
|
383
393
|
held = tryTakeSlot(opts.lockRoot, slots, opts.meta, opts.staleAfterMs, opts.reclaimGraceMs);
|
|
384
394
|
if (held) {
|
|
385
395
|
clearWaiter();
|
|
396
|
+
for (const [signal, handler] of signalHandlers)
|
|
397
|
+
process.off(signal, handler);
|
|
398
|
+
signalHandlers.clear();
|
|
386
399
|
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
387
400
|
const handler = () => {
|
|
388
401
|
release();
|
|
@@ -423,20 +436,24 @@ export async function acquireCrossProcessSlot(opts) {
|
|
|
423
436
|
}
|
|
424
437
|
/** Live waiters parked on this slot root (dead pids are ignored). */
|
|
425
438
|
export function countSlotWaiters(lockRoot) {
|
|
439
|
+
return listSlotWaiterPids(lockRoot).length;
|
|
440
|
+
}
|
|
441
|
+
/** PIDs that still have a live waiter entry in this slot queue. */
|
|
442
|
+
export function listSlotWaiterPids(lockRoot) {
|
|
426
443
|
const dir = join(lockRoot, ".queue");
|
|
427
444
|
let names = [];
|
|
428
445
|
try {
|
|
429
446
|
names = readdirSync(dir);
|
|
430
447
|
}
|
|
431
448
|
catch {
|
|
432
|
-
return
|
|
449
|
+
return [];
|
|
433
450
|
}
|
|
434
|
-
|
|
451
|
+
const pids = [];
|
|
435
452
|
for (const name of names) {
|
|
436
453
|
try {
|
|
437
454
|
const pid = Number(readFileSync(join(dir, name), "utf8").trim());
|
|
438
455
|
if (processIsAlive(pid))
|
|
439
|
-
|
|
456
|
+
pids.push(pid);
|
|
440
457
|
else
|
|
441
458
|
rmSync(join(dir, name), { force: true });
|
|
442
459
|
}
|
|
@@ -444,7 +461,7 @@ export function countSlotWaiters(lockRoot) {
|
|
|
444
461
|
/* raced */
|
|
445
462
|
}
|
|
446
463
|
}
|
|
447
|
-
return
|
|
464
|
+
return pids;
|
|
448
465
|
}
|
|
449
466
|
export function heavyJobCounts(lockRoot) {
|
|
450
467
|
const holders = inspectCrossProcessSlots(lockRoot);
|
|
@@ -474,7 +491,11 @@ export async function acquireLocalFallbackSerializeSlot(repoRoot, cacheDir, wait
|
|
|
474
491
|
slots: 1,
|
|
475
492
|
lockRoot: localFallbackSerializeLockRoot(repoRoot, cacheDir),
|
|
476
493
|
waitTimeoutMs,
|
|
477
|
-
meta: {
|
|
494
|
+
meta: {
|
|
495
|
+
kind: "local-fallback-serialize",
|
|
496
|
+
pid: process.pid,
|
|
497
|
+
id: randomBytes(4).toString("hex"),
|
|
498
|
+
},
|
|
478
499
|
onQueued: (ahead) => onQueued?.(ahead),
|
|
479
500
|
});
|
|
480
501
|
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { gitOutputAsync } from "./git.js";
|
|
2
|
+
export interface GitLockMaintenanceOptions {
|
|
3
|
+
now?: () => number;
|
|
4
|
+
processHasLockOpen?: (path: string) => boolean | Promise<boolean>;
|
|
5
|
+
onLock?: (event: {
|
|
6
|
+
path: string;
|
|
7
|
+
state: "live" | "removed" | "cleared";
|
|
8
|
+
ageMs: number;
|
|
9
|
+
}) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function setGitMaintenanceLockSink(sink: GitLockMaintenanceOptions["onLock"]): void;
|
|
2
12
|
/** Independent of both worker queues: a history timeout cannot cancel maintenance. */
|
|
3
13
|
export declare class CommitGraphMaintenance {
|
|
4
14
|
private readonly root;
|
|
5
15
|
private readonly git;
|
|
6
16
|
private readonly warn;
|
|
17
|
+
private readonly lockOptions;
|
|
7
18
|
private debounce;
|
|
8
19
|
private tick;
|
|
20
|
+
private lockTick;
|
|
9
21
|
private running;
|
|
10
22
|
private closed;
|
|
11
|
-
|
|
23
|
+
private liveLocks;
|
|
24
|
+
constructor(root: string, git?: typeof gitOutputAsync, warn?: (message: string) => void, lockOptions?: GitLockMaintenanceOptions);
|
|
12
25
|
schedule(delayMs?: number): void;
|
|
13
26
|
check(): Promise<void>;
|
|
14
27
|
private rebuildIfNeeded;
|
|
28
|
+
private recoverIndexLocks;
|
|
29
|
+
private checkIndexLocks;
|
|
15
30
|
close(): void;
|
|
16
31
|
}
|
|
@@ -1,25 +1,51 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { dirname, join, resolve } from "node:path";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { gitOutputAsync } from "./git.js";
|
|
5
6
|
const TICK_MS = 5 * 60_000;
|
|
7
|
+
const INDEX_LOCK_TICK_MS = 60_000;
|
|
6
8
|
const MAX_GRAPH_AGE_MS = 60 * 60_000;
|
|
7
9
|
const DEBOUNCE_MS = 5_000;
|
|
10
|
+
const STALE_INDEX_LOCK_MS = 2 * 60_000;
|
|
11
|
+
let lockEventSink = undefined;
|
|
12
|
+
export function setGitMaintenanceLockSink(sink) {
|
|
13
|
+
lockEventSink = sink;
|
|
14
|
+
}
|
|
15
|
+
function processHasLockOpen(path) {
|
|
16
|
+
try {
|
|
17
|
+
return (execFileSync("lsof", ["-t", path], {
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
20
|
+
}).trim().length > 0);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
// lsof exits 1 when no process has the file open. If the probe itself is
|
|
24
|
+
// unavailable, fail safe and leave the lock for a later maintenance tick.
|
|
25
|
+
return error.status !== 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
8
28
|
/** Independent of both worker queues: a history timeout cannot cancel maintenance. */
|
|
9
29
|
export class CommitGraphMaintenance {
|
|
10
30
|
root;
|
|
11
31
|
git;
|
|
12
32
|
warn;
|
|
33
|
+
lockOptions;
|
|
13
34
|
debounce;
|
|
14
35
|
tick;
|
|
36
|
+
lockTick;
|
|
15
37
|
running = null;
|
|
16
38
|
closed = false;
|
|
17
|
-
|
|
39
|
+
liveLocks = new Set();
|
|
40
|
+
constructor(root, git = gitOutputAsync, warn = console.warn, lockOptions = {}) {
|
|
18
41
|
this.root = root;
|
|
19
42
|
this.git = git;
|
|
20
43
|
this.warn = warn;
|
|
44
|
+
this.lockOptions = lockOptions;
|
|
21
45
|
this.tick = setInterval(() => void this.check(), TICK_MS);
|
|
22
46
|
this.tick.unref?.();
|
|
47
|
+
this.lockTick = setInterval(() => void this.checkIndexLocks(), INDEX_LOCK_TICK_MS);
|
|
48
|
+
this.lockTick.unref?.();
|
|
23
49
|
this.schedule(0);
|
|
24
50
|
}
|
|
25
51
|
schedule(delayMs = DEBOUNCE_MS) {
|
|
@@ -48,6 +74,7 @@ export class CommitGraphMaintenance {
|
|
|
48
74
|
return this.running;
|
|
49
75
|
}
|
|
50
76
|
async rebuildIfNeeded() {
|
|
77
|
+
await this.recoverIndexLocks();
|
|
51
78
|
const head = await this.git(this.root, ["rev-parse", "HEAD"]);
|
|
52
79
|
if (!head || this.closed)
|
|
53
80
|
return;
|
|
@@ -89,9 +116,63 @@ export class CommitGraphMaintenance {
|
|
|
89
116
|
await rm(temporary, { force: true }).catch(() => { });
|
|
90
117
|
}
|
|
91
118
|
}
|
|
119
|
+
async recoverIndexLocks() {
|
|
120
|
+
const now = this.lockOptions.now ?? Date.now;
|
|
121
|
+
const listed = await this.git(this.root, ["worktree", "list", "--porcelain"]);
|
|
122
|
+
const roots = new Set([this.root]);
|
|
123
|
+
for (const line of (listed ?? "").split("\n")) {
|
|
124
|
+
if (line.startsWith("worktree "))
|
|
125
|
+
roots.add(line.slice("worktree ".length).trim());
|
|
126
|
+
}
|
|
127
|
+
for (const root of roots) {
|
|
128
|
+
if (this.closed)
|
|
129
|
+
return;
|
|
130
|
+
const gitDir = await this.git(root, ["rev-parse", "--absolute-git-dir"]);
|
|
131
|
+
if (!gitDir)
|
|
132
|
+
continue;
|
|
133
|
+
const path = join(gitDir, "index.lock");
|
|
134
|
+
const lock = await stat(path).catch(() => null);
|
|
135
|
+
if (!lock) {
|
|
136
|
+
if (this.liveLocks.delete(path))
|
|
137
|
+
(this.lockOptions.onLock ?? lockEventSink)?.({ path, state: "cleared", ageMs: 0 });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const ageMs = Math.max(0, now() - lock.mtimeMs);
|
|
141
|
+
if (ageMs <= STALE_INDEX_LOCK_MS)
|
|
142
|
+
continue;
|
|
143
|
+
let live = true;
|
|
144
|
+
try {
|
|
145
|
+
live = await (this.lockOptions.processHasLockOpen ?? processHasLockOpen)(path);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
live = true;
|
|
149
|
+
}
|
|
150
|
+
if (live) {
|
|
151
|
+
if (!this.liveLocks.has(path)) {
|
|
152
|
+
this.liveLocks.add(path);
|
|
153
|
+
(this.lockOptions.onLock ?? lockEventSink)?.({ path, state: "live", ageMs });
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
await rm(path, { force: true });
|
|
158
|
+
this.liveLocks.delete(path);
|
|
159
|
+
(this.lockOptions.onLock ?? lockEventSink)?.({ path, state: "removed", ageMs });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async checkIndexLocks() {
|
|
163
|
+
if (this.closed)
|
|
164
|
+
return;
|
|
165
|
+
try {
|
|
166
|
+
await this.recoverIndexLocks();
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.warn(`git index.lock maintenance failed: ${error.message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
92
172
|
close() {
|
|
93
173
|
this.closed = true;
|
|
94
174
|
clearInterval(this.tick);
|
|
175
|
+
clearInterval(this.lockTick);
|
|
95
176
|
clearTimeout(this.debounce);
|
|
96
177
|
}
|
|
97
178
|
}
|
package/dist/core/git.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare function runGit(root: string, args: string[], timeout: number): P
|
|
|
35
35
|
* `fsmonitor` daemons running and no known holder).
|
|
36
36
|
*/
|
|
37
37
|
export declare function isGitLockContention(text: string): boolean;
|
|
38
|
+
/** Install the server's durable logging hook for fail-soft bookkeeping commits. */
|
|
39
|
+
export declare function setGitLockFailureSink(sink: ((root: string, message: string, detail: string) => void) | null): void;
|
|
38
40
|
/** `retryOnGitLock` predicate for a raw `runGit` result: the lock text, or null. */
|
|
39
41
|
export declare function gitRunLockContention(r: {
|
|
40
42
|
status: number | null;
|
package/dist/core/git.js
CHANGED
|
@@ -135,6 +135,11 @@ export function runGit(root, args, timeout) {
|
|
|
135
135
|
export function isGitLockContention(text) {
|
|
136
136
|
return /Unable to create [^\n]*\.lock|Another git process seems to be running|index\.lock/i.test(text);
|
|
137
137
|
}
|
|
138
|
+
let gitLockFailureSink = null;
|
|
139
|
+
/** Install the server's durable logging hook for fail-soft bookkeeping commits. */
|
|
140
|
+
export function setGitLockFailureSink(sink) {
|
|
141
|
+
gitLockFailureSink = sink;
|
|
142
|
+
}
|
|
138
143
|
/** `retryOnGitLock` predicate for a raw `runGit` result: the lock text, or null. */
|
|
139
144
|
export function gitRunLockContention(r) {
|
|
140
145
|
return r.status !== 0 && isGitLockContention(r.stderr) ? r.stderr : null;
|
|
@@ -1752,12 +1757,19 @@ export function commitFiles(root, absPaths, message) {
|
|
|
1752
1757
|
return false;
|
|
1753
1758
|
if (status.trim() === "")
|
|
1754
1759
|
return true;
|
|
1755
|
-
|
|
1760
|
+
const add = gitCapture(root, ["add", "-A", "--", ...rels]);
|
|
1761
|
+
if (add.status !== 0) {
|
|
1762
|
+
if (isGitLockContention(add.stderr))
|
|
1763
|
+
gitLockFailureSink?.(root, message, add.stderr.trim());
|
|
1756
1764
|
return false;
|
|
1765
|
+
}
|
|
1757
1766
|
// `--only` with a pathspec commits just these files and leaves everything
|
|
1758
1767
|
// else already in the index staged (#0353): a plain `git commit` would sweep
|
|
1759
1768
|
// in whatever a human or agent had staged in the checkout under our message.
|
|
1760
|
-
const
|
|
1769
|
+
const commit = gitCapture(root, ["commit", "-o", "-m", message, "--", ...rels]);
|
|
1770
|
+
const committed = commit.status === 0;
|
|
1771
|
+
if (!committed && isGitLockContention(commit.stderr))
|
|
1772
|
+
gitLockFailureSink?.(root, message, commit.stderr.trim());
|
|
1761
1773
|
// RepoOS's own bookkeeping commits are the main reason the sidebar git-state
|
|
1762
1774
|
// indicator would otherwise flicker stale — tell it to recompute (#0584).
|
|
1763
1775
|
if (committed)
|
package/dist/core/local-gates.js
CHANGED
|
@@ -16,7 +16,7 @@ function taskIdFromEnv(env) {
|
|
|
16
16
|
const id = env.REPOOS_TASK_ID?.trim() || env.REPOOS_CHECK_TASK_ID?.trim();
|
|
17
17
|
return id && /^\d+$/.test(id) ? id : null;
|
|
18
18
|
}
|
|
19
|
-
function markQueue(repoRoot, cacheDir, env, kind, ahead, position, message) {
|
|
19
|
+
function markQueue(repoRoot, cacheDir, env, kind, ahead, position, message, pool) {
|
|
20
20
|
const taskId = taskIdFromEnv(env);
|
|
21
21
|
if (!taskId)
|
|
22
22
|
return;
|
|
@@ -28,6 +28,8 @@ function markQueue(repoRoot, cacheDir, env, kind, ahead, position, message) {
|
|
|
28
28
|
position,
|
|
29
29
|
message,
|
|
30
30
|
since: since ?? new Date().toISOString(),
|
|
31
|
+
pool,
|
|
32
|
+
pid: process.pid,
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
35
|
function unmarkQueue(repoRoot, cacheDir, env, kind) {
|
|
@@ -77,7 +79,7 @@ export async function acquireLocalCheckSlot(opts) {
|
|
|
77
79
|
? "waiting for serialized local fallback slot"
|
|
78
80
|
: `waiting for serialized local fallback (${ahead} ahead, position ${position})`;
|
|
79
81
|
emit(`[queue] ${msg}`);
|
|
80
|
-
markQueue(board, cacheDir, opts.env, "local-check", ahead, position, msg);
|
|
82
|
+
markQueue(board, cacheDir, opts.env, "local-check", ahead, position, msg, "local-fallback-serialize");
|
|
81
83
|
});
|
|
82
84
|
}
|
|
83
85
|
try {
|
|
@@ -104,6 +106,7 @@ export async function acquireLocalCheckSlot(opts) {
|
|
|
104
106
|
emit(`[queue] ${label}`);
|
|
105
107
|
markQueue(board, cacheDir, opts.env, "local-check", ahead, position, title);
|
|
106
108
|
},
|
|
109
|
+
onWaiterCleared: () => unmarkQueue(board, cacheDir, opts.env, "local-check"),
|
|
107
110
|
});
|
|
108
111
|
unmarkQueue(board, cacheDir, opts.env, "local-check");
|
|
109
112
|
return () => {
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
export type LocalQueueKind = "local-check" | "playwright" | "pressure";
|
|
2
2
|
export interface LocalQueueMarker {
|
|
3
3
|
taskId: string;
|
|
4
|
+
pid?: number;
|
|
4
5
|
kind: LocalQueueKind;
|
|
5
6
|
position: number;
|
|
6
7
|
ahead: number;
|
|
7
8
|
message: string;
|
|
8
9
|
since: string;
|
|
10
|
+
/** Slot pool being waited on; absent on older markers. */
|
|
11
|
+
pool?: "local-check" | "local-fallback-serialize" | "playwright";
|
|
9
12
|
}
|
|
10
13
|
export declare function queueMarkersDir(repoRoot: string, cacheDir: string): string;
|
|
11
14
|
export declare function writeQueueMarker(repoRoot: string, cacheDir: string, marker: LocalQueueMarker): void;
|
|
12
15
|
export declare function clearQueueMarker(repoRoot: string, cacheDir: string, taskId: string, kind: LocalQueueKind): void;
|
|
13
16
|
export declare function readQueueMarkers(repoRoot: string, cacheDir: string): LocalQueueMarker[];
|
|
17
|
+
export declare function currentQueueMarkers(markers: LocalQueueMarker[], liveWaiterPids: Iterable<number>): LocalQueueMarker[];
|
|
@@ -39,3 +39,10 @@ export function readQueueMarkers(repoRoot, cacheDir) {
|
|
|
39
39
|
return [];
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
export function currentQueueMarkers(markers, liveWaiterPids) {
|
|
43
|
+
const live = new Set(liveWaiterPids);
|
|
44
|
+
return markers
|
|
45
|
+
.filter((marker) => marker.kind !== "local-check" || (marker.pid && live.has(marker.pid)))
|
|
46
|
+
.sort((a, b) => Date.parse(a.since) - Date.parse(b.since))
|
|
47
|
+
.map((marker, index) => marker.kind === "local-check" ? { ...marker, ahead: index, position: index + 1 } : marker);
|
|
48
|
+
}
|
package/dist/core/needs-input.js
CHANGED
|
@@ -7,8 +7,8 @@ export function needsInputClearsOnNewEngineerRun(reason) {
|
|
|
7
7
|
return true;
|
|
8
8
|
if (reason === "review-human-attention")
|
|
9
9
|
return true;
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// A fresh engineer run retries an abnormal check wait (#0983). If the
|
|
11
|
+
// underlying holder is still dead/stalled, the watchdog will raise it again.
|
|
12
12
|
if (reason === "check-wait")
|
|
13
13
|
return true;
|
|
14
14
|
return reason === "provider-failure" || reason === "degenerate-output";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* path appends `?shotState=<id>` to the route; the UI applies the fixture only
|
|
4
4
|
* on preview builds (`isPreviewBuild`).
|
|
5
5
|
*/
|
|
6
|
-
export declare const SHOT_PREVIEW_FIXTURE_IDS: readonly ["system:worktreeGc", "closeOut:active", "closeOut:checkProgress", "closeOut:checkProgressUnknown", "checks:progress", "checks:timings", "checks:reuse", "pages:scroll", "closeOut:stalled", "closeOut:uploading", "closeOut:remoteTests", "runners:queued", "integration:median15", "card:doneError", "board:withReviewTask", "drawer:reviewWithBranch", "drawer:landable", "drawer:statusReviewFailed", "drawer:statusReviewFailedNarrow", "drawer:statusHandoffFailed", "drawer:statusChecksRunning", "drawer:statusChecksRunningNarrow", "drawer:statusConfirmingHandoff", "drawer:statusConfirmingHandoffNarrow", "drawer:statusChecksFailed", "drawer:statusChecksFailedNarrow", "drawer:statusDetails", "drawer:landableNarrow", "drawer:tabExpandWarnings", "drawer:activeRequirements", "drawer:nativePreviewBuilding", "drawer:nativePreviewRunning", "drawer:nativePreviewFailed", "drawer:shotEvidence", "drawer:debugLogs", "card:humanStep", "card:held", "drawer:held", "card:reviewRoundsExhausted", "card:handoffConflict", "card:checkpointRecovery", "board:cardChips", "card:noChanges", "board:activeCoding", "board:severalWorkingCards", "board:startWorkPending", "drawer:startWorkPending", "settings:birdQuickTrail", "settings:rgbKeystorm", "settings:wheelieCourier", "settings:booSwoop", "settings:pumpkinSpell", "drawer:queuedMessage", "dev:failed", "dev:loading", "dev:running", "secrets:populated", "secrets:removeDialog", "deployments:environmentBinding", "deployments:loading", "editor:unconfigured", "panel:newInputOutstandingUpload", "panel:freeformFailedScreenshot", "panel:defaultScreenshotPicker", "budget:paused", "schedule:newPanel", "reminders:due"];
|
|
6
|
+
export declare const SHOT_PREVIEW_FIXTURE_IDS: readonly ["system:worktreeGc", "closeOut:active", "closeOut:checkProgress", "closeOut:checkProgressUnknown", "checks:progress", "checks:timings", "checks:reuse", "pages:scroll", "closeOut:stalled", "closeOut:uploading", "closeOut:remoteTests", "runners:queued", "integration:median15", "card:doneError", "board:withReviewTask", "drawer:reviewWithBranch", "drawer:landable", "drawer:statusReviewFailed", "drawer:statusReviewFailedNarrow", "drawer:statusHandoffFailed", "drawer:statusChecksRunning", "drawer:statusChecksRunningNarrow", "drawer:statusConfirmingHandoff", "drawer:statusConfirmingHandoffNarrow", "drawer:statusChecksFailed", "drawer:statusChecksFailedNarrow", "drawer:statusDetails", "drawer:landableNarrow", "drawer:tabExpandWarnings", "drawer:activeRequirements", "drawer:nativePreviewBuilding", "drawer:nativePreviewRunning", "drawer:nativePreviewFailed", "drawer:shotEvidence", "drawer:debugLogs", "card:humanStep", "card:held", "drawer:held", "card:reviewRoundsExhausted", "card:handoffConflict", "card:checkpointRecovery", "board:cardChips", "card:noChanges", "board:activeCoding", "checks:waiting", "board:severalWorkingCards", "board:startWorkPending", "drawer:startWorkPending", "settings:birdQuickTrail", "settings:rgbKeystorm", "settings:wheelieCourier", "settings:booSwoop", "settings:pumpkinSpell", "drawer:queuedMessage", "dev:failed", "dev:loading", "dev:running", "secrets:populated", "secrets:removeDialog", "deployments:environmentBinding", "deployments:loading", "editor:unconfigured", "panel:newInputOutstandingUpload", "panel:freeformFailedScreenshot", "panel:defaultScreenshotPicker", "budget:paused", "schedule:newPanel", "reminders:due"];
|
|
7
7
|
export type ShotPreviewFixtureId = (typeof SHOT_PREVIEW_FIXTURE_IDS)[number];
|
|
8
8
|
export declare const SHOT_PREVIEW_FIXTURE_QUERY = "shotState";
|
|
9
9
|
/** Task id used by card/board fixtures so selectors can target one stable card. */
|
|
@@ -90,6 +90,7 @@ export const SHOT_PREVIEW_FIXTURE_IDS = [
|
|
|
90
90
|
// #0787 — active engineer run on the board so captures can show the compact
|
|
91
91
|
// coding indicator on a task card.
|
|
92
92
|
"board:activeCoding",
|
|
93
|
+
"checks:waiting",
|
|
93
94
|
// #0980 — six active cards for activity-animation captures and profiling.
|
|
94
95
|
"board:severalWorkingCards",
|
|
95
96
|
// #0822 — ready task with Start work in flight so the card shows Starting….
|
|
@@ -4,5 +4,6 @@ export interface AttentionEventStore {
|
|
|
4
4
|
id?: string;
|
|
5
5
|
}): RecordedAttentionEvent;
|
|
6
6
|
list(): RecordedAttentionEvent[];
|
|
7
|
+
remove(id: string): void;
|
|
7
8
|
}
|
|
8
9
|
export declare function createAttentionEventStore(root: string, cacheDir?: string, onPersistError?: (error: unknown) => void): AttentionEventStore;
|
|
@@ -84,6 +84,13 @@ export function createAttentionEventStore(root, cacheDir = ".repoos", onPersistE
|
|
|
84
84
|
persist(events);
|
|
85
85
|
return full;
|
|
86
86
|
},
|
|
87
|
+
remove(id) {
|
|
88
|
+
const next = load().filter((event) => event.id !== id);
|
|
89
|
+
if (next.length === load().length)
|
|
90
|
+
return;
|
|
91
|
+
events = next;
|
|
92
|
+
persist(events);
|
|
93
|
+
},
|
|
87
94
|
list() {
|
|
88
95
|
return [...load()].sort((a, b) => b.at.localeCompare(a.at));
|
|
89
96
|
},
|
package/dist/server/server.js
CHANGED
|
@@ -99,7 +99,8 @@ import { ensureStoryNumbers } from "../core/story-definition-files.js";
|
|
|
99
99
|
import { createLogger } from "../core/logger.js";
|
|
100
100
|
import { watchHttpRequest } from "./request-timing.js";
|
|
101
101
|
import { parseFlatToml, loadConfig, resolveServePort, projectDisplayName, } from "../core/config.js";
|
|
102
|
-
import { commitTaskFile, commitFiles, syncBranchWithMain, shouldTuneRepoForScaleAtStartup, tuneRepoForScale, runGit, } from "../core/git.js";
|
|
102
|
+
import { commitTaskFile, commitFiles, syncBranchWithMain, shouldTuneRepoForScaleAtStartup, tuneRepoForScale, runGit, setGitLockFailureSink, } from "../core/git.js";
|
|
103
|
+
import { setGitMaintenanceLockSink } from "../core/git-maintenance.js";
|
|
103
104
|
import { checkServerRegistryPortConflict, logServerRegistryError, refreshRunningServerProjectName, registerRunningServer, removeServerRegistryEntry, shouldRegisterServerRegistry, updateServerRegistryEngineerCount, } from "../core/server-registry.js";
|
|
104
105
|
import { onGitMutation } from "../core/git-activity.js";
|
|
105
106
|
import { sweepOrphanedBookkeeping } from "./orphan-bookkeeping.js";
|
|
@@ -694,6 +695,13 @@ export function startServer(opts = {}) {
|
|
|
694
695
|
bindHost = "127.0.0.1";
|
|
695
696
|
}
|
|
696
697
|
const logger = createLogger(config.root);
|
|
698
|
+
setGitLockFailureSink((root, commitMessage, detail) => {
|
|
699
|
+
const context = { root, commitMessage, detail };
|
|
700
|
+
logger.system("warn", `git write failed because index.lock is present: ${detail}`, context);
|
|
701
|
+
const taskId = commitMessage.match(/\b(?:docs|inputs|schedules|stories)\(([^)]+)\)/)?.[1];
|
|
702
|
+
if (taskId)
|
|
703
|
+
logger.task(taskId, "warn", `git bookkeeping write failed because index.lock is present: ${detail}`, context);
|
|
704
|
+
});
|
|
697
705
|
// Best-effort: enable git's fsmonitor + untracked-cache on the main checkout
|
|
698
706
|
// only — preview/shot servers and linked task worktrees share `.git/config`
|
|
699
707
|
// and must not re-enable watchers or spawn another daemon per start (#0958).
|
|
@@ -954,6 +962,28 @@ export function startServer(opts = {}) {
|
|
|
954
962
|
const bumpAttention = () => {
|
|
955
963
|
emitEvent({ type: "attention.updated", at: new Date().toISOString() });
|
|
956
964
|
};
|
|
965
|
+
setGitMaintenanceLockSink(({ path, state, ageMs }) => {
|
|
966
|
+
const id = `gitLock:${path}`;
|
|
967
|
+
if (state === "live") {
|
|
968
|
+
logger.system("warn", "Live git process still holds stale git index.lock", { path, ageMs });
|
|
969
|
+
attentionEvents.record({
|
|
970
|
+
id,
|
|
971
|
+
kind: "gitLock",
|
|
972
|
+
taskId: null,
|
|
973
|
+
message: "Git is still holding a stale index lock",
|
|
974
|
+
detail: `${path} has been present for ${Math.floor(ageMs / 60_000)} minutes. Git writes may be blocked until the process releases it.`,
|
|
975
|
+
at: new Date().toISOString(),
|
|
976
|
+
});
|
|
977
|
+
bumpAttention();
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (state === "removed")
|
|
981
|
+
logger.system("info", "Removed stale git index.lock", { path, ageMs });
|
|
982
|
+
if (state === "cleared") {
|
|
983
|
+
attentionEvents.remove(id);
|
|
984
|
+
bumpAttention();
|
|
985
|
+
}
|
|
986
|
+
});
|
|
957
987
|
wireAttentionNotifications(attentionEvents, bumpAttention);
|
|
958
988
|
const recordRemoteFallbackAttention = (taskId, detail) => {
|
|
959
989
|
const at = new Date().toISOString();
|
|
@@ -206,15 +206,7 @@ export declare class TaskWatchdog {
|
|
|
206
206
|
*/
|
|
207
207
|
private escalateToNeedsInput;
|
|
208
208
|
private readCurrent;
|
|
209
|
-
/**
|
|
210
|
-
* #0974: a check that is still waiting — pressure guard, a heavy slot, the
|
|
211
|
-
* git queue — after ten minutes is surfaced as needs_input instead of
|
|
212
|
-
* hanging silently (the #0970 incident: 26 minutes behind a stale pressure
|
|
213
|
-
* guard with nothing on the card). The gates write queue markers while they
|
|
214
|
-
* wait; this scan reads them. The flag clears itself once the marker is
|
|
215
|
-
* gone (the wait ended and the check is actually running) or on the next
|
|
216
|
-
* engineer run — it is informational, not a permanent failure.
|
|
217
|
-
*/
|
|
209
|
+
/** Queue markers are non-blocking waiting state. Only broken waits need input. */
|
|
218
210
|
private escalateStaleQueueWaits;
|
|
219
211
|
private escalateCheckWait;
|
|
220
212
|
private clearCheckWaitEscalation;
|