@mattstack/rt-client 0.28.0 → 0.30.0
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/commands.d.ts +60 -1
- package/dist/gate-answers.d.ts +3 -2
- package/dist/gate.js +20 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +151 -125
- package/dist/settings/identity-codec.js +2 -2
- package/package.json +1 -1
- package/src/commands.ts +60 -4
- package/src/gate-answers.ts +12 -7
- package/src/index.ts +2 -0
- package/src/settings/registry-defs.ts +18 -0
package/dist/commands.d.ts
CHANGED
|
@@ -27,6 +27,17 @@ export interface ProjectMRsScope {
|
|
|
27
27
|
daemon or before the first sweep that demanded a section. */
|
|
28
28
|
knownSections?: string[];
|
|
29
29
|
}
|
|
30
|
+
/** Classified cause of a repo's failing project sync (lib/daemon/project-sync-health.ts). */
|
|
31
|
+
export type ProjectSyncErrorKind = "rate-limited" | "auth" | "server-error" | "timeout" | "other";
|
|
32
|
+
/** A repo's current unbroken run of failed project syncs. */
|
|
33
|
+
export interface ProjectSyncError {
|
|
34
|
+
/** First failure of the run; later failures leave it alone. */
|
|
35
|
+
since: number;
|
|
36
|
+
lastAt: number;
|
|
37
|
+
kind: ProjectSyncErrorKind;
|
|
38
|
+
/** Raw error text, capped at 200 chars. */
|
|
39
|
+
message: string;
|
|
40
|
+
}
|
|
30
41
|
export interface ProjectMRsData {
|
|
31
42
|
mrs: Record<string, {
|
|
32
43
|
pr: PullRequest;
|
|
@@ -37,6 +48,8 @@ export interface ProjectMRsData {
|
|
|
37
48
|
source: "poll" | "events" | "mutation";
|
|
38
49
|
syncedAt: number;
|
|
39
50
|
scope?: ProjectMRsScope;
|
|
51
|
+
/** Present while this repo's most recent project sync failed. */
|
|
52
|
+
syncError?: ProjectSyncError;
|
|
40
53
|
}
|
|
41
54
|
export interface DiscussionsData {
|
|
42
55
|
discussions: Discussion[];
|
|
@@ -140,6 +153,7 @@ export interface GateAnswer {
|
|
|
140
153
|
answers: Record<string, string | string[] | {
|
|
141
154
|
value: string | string[];
|
|
142
155
|
note?: string;
|
|
156
|
+
text?: string;
|
|
143
157
|
}>;
|
|
144
158
|
by: string;
|
|
145
159
|
answeredAt: number;
|
|
@@ -691,10 +705,14 @@ export interface WorktreeProvisionData {
|
|
|
691
705
|
branchState: "new" | "tracking-remote" | "existing-clean" | "diverged" | "behind";
|
|
692
706
|
readyFailed?: true;
|
|
693
707
|
failedStep?: string;
|
|
708
|
+
/** Set only when the tree was built by hydrating from the repo's golden donor, not the on-deck pool or a cold create. */
|
|
709
|
+
hydratedFrom?: string;
|
|
694
710
|
}
|
|
695
711
|
export interface WorktreeCreateData {
|
|
696
712
|
tree: string;
|
|
697
713
|
path: string;
|
|
714
|
+
/** Set only for `--on-deck`, and only when that tree hydrated from the golden rather than cold-creating. */
|
|
715
|
+
hydratedFrom?: string;
|
|
698
716
|
}
|
|
699
717
|
export interface WorktreeDisposeData {
|
|
700
718
|
disposed: string[];
|
|
@@ -787,6 +805,28 @@ export interface DiscussionsDiffsData {
|
|
|
787
805
|
truncated: boolean;
|
|
788
806
|
}
|
|
789
807
|
export type MRActionName = "merge" | "rebase" | "approve" | "unapprove" | "setAutoMerge" | "cancelAutoMerge" | "retryJob" | "retryPipeline" | "toggleDraft" | "requestReReview";
|
|
808
|
+
/** One worktree's git badge as the daemon sweep computed it. All timestamps ISO 8601. */
|
|
809
|
+
export interface GitWorktreeBadge {
|
|
810
|
+
worktree: string;
|
|
811
|
+
branch: string | null;
|
|
812
|
+
detached: boolean;
|
|
813
|
+
staged: number;
|
|
814
|
+
unstaged: number;
|
|
815
|
+
untracked: number;
|
|
816
|
+
conflicted: number;
|
|
817
|
+
clean: boolean;
|
|
818
|
+
ahead: number | null;
|
|
819
|
+
behind: number | null;
|
|
820
|
+
upstream: string | null;
|
|
821
|
+
lastFetchedAt: string | null;
|
|
822
|
+
updatedAt: string;
|
|
823
|
+
}
|
|
824
|
+
/** repo is the serialized identity (the repo-index key). error is set when the last sweep could not read the repo; stale worktrees may accompany it. */
|
|
825
|
+
export interface RepoStatusRow {
|
|
826
|
+
repo: string;
|
|
827
|
+
worktrees: GitWorktreeBadge[];
|
|
828
|
+
error: string | null;
|
|
829
|
+
}
|
|
790
830
|
export interface Commands {
|
|
791
831
|
"project-mrs:read": {
|
|
792
832
|
payload: {
|
|
@@ -1456,6 +1496,15 @@ export interface Commands {
|
|
|
1456
1496
|
};
|
|
1457
1497
|
data: unknown;
|
|
1458
1498
|
};
|
|
1499
|
+
"repos:status": {
|
|
1500
|
+
payload: {
|
|
1501
|
+
refresh?: boolean;
|
|
1502
|
+
};
|
|
1503
|
+
data: {
|
|
1504
|
+
repos: RepoStatusRow[];
|
|
1505
|
+
sweptAt: string | null;
|
|
1506
|
+
};
|
|
1507
|
+
};
|
|
1459
1508
|
"freshness:reconcile": {
|
|
1460
1509
|
payload: Record<string, never>;
|
|
1461
1510
|
data: unknown;
|
|
@@ -1524,13 +1573,23 @@ export interface Commands {
|
|
|
1524
1573
|
/** `contextOmitted` appears only when the gate context plus every
|
|
1525
1574
|
question's `context` exceeded their shared 8192-byte budget: the
|
|
1526
1575
|
gate still opened, but question contexts were dropped, and the gate
|
|
1527
|
-
context too when it was over the budget on its own.
|
|
1576
|
+
context too when it was over the budget on its own.
|
|
1577
|
+
`formCapExceeded`/`formCapAdvisory` appear only when the pane could
|
|
1578
|
+
have presented an in-pane form and one or more questions exceeded
|
|
1579
|
+
its 4-option cap, which is the one thing that forced this gate to
|
|
1580
|
+
`wait`: the gate still opens, and the advisory names the structural
|
|
1581
|
+
fix so the caller can re-author. */
|
|
1528
1582
|
data: {
|
|
1529
1583
|
id: string;
|
|
1530
1584
|
presentation: "form" | "wait";
|
|
1531
1585
|
subject: string;
|
|
1532
1586
|
supersededId: string | null;
|
|
1533
1587
|
contextOmitted?: true;
|
|
1588
|
+
formCapExceeded?: Array<{
|
|
1589
|
+
question: string;
|
|
1590
|
+
options: number;
|
|
1591
|
+
}>;
|
|
1592
|
+
formCapAdvisory?: string;
|
|
1534
1593
|
};
|
|
1535
1594
|
};
|
|
1536
1595
|
/**
|
package/dist/gate-answers.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { GateQuestion, GateAnswer } from "./commands.ts";
|
|
2
2
|
export type GateAnswerWire = GateAnswer["answers"][string];
|
|
3
|
-
/** Both wire shapes carry the same value underneath: bare, or {value,
|
|
4
|
-
when a panel attaches free text
|
|
3
|
+
/** Both wire shapes carry the same value underneath: bare, or {value,
|
|
4
|
+
note?, text?} when a panel attaches free text or a replacement for text
|
|
5
|
+
the gate offered. Unwrapping keeps only the value. */
|
|
5
6
|
export declare function unwrapGateAnswerValue(raw: unknown): unknown;
|
|
6
7
|
/** Option membership is required whenever a question declares options,
|
|
7
8
|
checked against the unwrapped value (every element, for multi); an
|
package/dist/gate.js
CHANGED
|
@@ -54,12 +54,18 @@ function unwrapGateAnswerValue(raw) {
|
|
|
54
54
|
}
|
|
55
55
|
return raw;
|
|
56
56
|
}
|
|
57
|
-
function
|
|
57
|
+
function wrapperFieldError(qid, raw) {
|
|
58
58
|
if (!raw || typeof raw !== "object" || Array.isArray(raw) || !("value" in raw)) {
|
|
59
|
-
return
|
|
59
|
+
return null;
|
|
60
60
|
}
|
|
61
|
-
const note = raw
|
|
62
|
-
|
|
61
|
+
const { note, text } = raw;
|
|
62
|
+
if (note !== undefined && typeof note !== "string")
|
|
63
|
+
return `question ${qid} note must be a string`;
|
|
64
|
+
if (text !== undefined && typeof text !== "string")
|
|
65
|
+
return `question ${qid} text must be a string`;
|
|
66
|
+
if (typeof text === "string" && text.trim() === "")
|
|
67
|
+
return `question ${qid} text must not be empty`;
|
|
68
|
+
return null;
|
|
63
69
|
}
|
|
64
70
|
function validateGateAnswers(questions, answers) {
|
|
65
71
|
const byId = new Map(questions.map((q) => [q.id, q]));
|
|
@@ -67,8 +73,9 @@ function validateGateAnswers(questions, answers) {
|
|
|
67
73
|
const question = byId.get(qid);
|
|
68
74
|
if (!question)
|
|
69
75
|
return `unknown question id: ${qid}`;
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
const wrapperError = wrapperFieldError(qid, raw);
|
|
77
|
+
if (wrapperError)
|
|
78
|
+
return wrapperError;
|
|
72
79
|
const value = unwrapGateAnswerValue(raw);
|
|
73
80
|
const isArray = Array.isArray(value);
|
|
74
81
|
if (question.multi && !isArray)
|
|
@@ -99,12 +106,12 @@ function gatePresentation(args) {
|
|
|
99
106
|
return args.questions.every((q) => q.options.length <= GATE_FORM_OPTION_CAP) ? "form" : "wait";
|
|
100
107
|
}
|
|
101
108
|
export {
|
|
102
|
-
|
|
103
|
-
unwrapGateAnswerValue,
|
|
104
|
-
normalizeGateQuestions,
|
|
105
|
-
normalizeGateOptions,
|
|
106
|
-
gatePresentation,
|
|
107
|
-
gateOptionValue,
|
|
109
|
+
GATE_FORM_OPTION_CAP,
|
|
108
110
|
gateOptionLabel,
|
|
109
|
-
|
|
111
|
+
gateOptionValue,
|
|
112
|
+
gatePresentation,
|
|
113
|
+
normalizeGateOptions,
|
|
114
|
+
normalizeGateQuestions,
|
|
115
|
+
unwrapGateAnswerValue,
|
|
116
|
+
validateGateAnswers
|
|
110
117
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { normalizeGateOptions, normalizeGateQuestions } from "./gate-options.ts"
|
|
|
8
8
|
export type { GateOptionObject } from "./gate-options.ts";
|
|
9
9
|
export { unwrapGateAnswerValue, validateGateAnswers } from "./gate-answers.ts";
|
|
10
10
|
export type { GateAnswerWire } from "./gate-answers.ts";
|
|
11
|
-
export type { Discussion, DemandDecl, ProjectMRsScope, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, BranchEnrichment, Commands, CommandName, ForgeSlug, ForgeTokenData, Attention, RunSummary, RunStageRow, RunFieldRow, RunDecisionRow, RunDetail, WakeMode, ChatMember, ChatMessage, ChatClaimOutcome, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ExecutorState, ExecutorView, ReconcilerStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, PaneFocusResult, GateStatus, GateOption, GateOrigin, GateQuestion, GateAnswer, GateRow, GateSubscription, HerdInfo, HerdListRow, HerdJobInfo, HerdStatusData, } from "./commands.ts";
|
|
11
|
+
export type { Discussion, DemandDecl, ProjectMRsScope, ProjectSyncError, ProjectSyncErrorKind, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, BranchEnrichment, Commands, CommandName, ForgeSlug, ForgeTokenData, Attention, RunSummary, RunStageRow, RunFieldRow, RunDecisionRow, RunDetail, WakeMode, ChatMember, ChatMessage, ChatClaimOutcome, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ExecutorState, ExecutorView, ReconcilerStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, PaneFocusResult, GateStatus, GateOption, GateOrigin, GateQuestion, GateAnswer, GateRow, GateSubscription, HerdInfo, HerdListRow, HerdJobInfo, HerdStatusData, } from "./commands.ts";
|
|
12
12
|
export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";
|
|
13
13
|
export type { RelayEventType } from "./relay.ts";
|
|
14
14
|
export { daemonHealth } from "./health.ts";
|
package/dist/index.js
CHANGED
|
@@ -582,6 +582,7 @@ var COMMAND_NAMES = [
|
|
|
582
582
|
"endpoint:release",
|
|
583
583
|
"endpoint:status",
|
|
584
584
|
"repos:locate",
|
|
585
|
+
"repos:status",
|
|
585
586
|
"freshness:reconcile",
|
|
586
587
|
"reconciler:status",
|
|
587
588
|
"reconciler:clear",
|
|
@@ -642,12 +643,18 @@ function unwrapGateAnswerValue(raw) {
|
|
|
642
643
|
}
|
|
643
644
|
return raw;
|
|
644
645
|
}
|
|
645
|
-
function
|
|
646
|
+
function wrapperFieldError(qid, raw) {
|
|
646
647
|
if (!raw || typeof raw !== "object" || Array.isArray(raw) || !("value" in raw)) {
|
|
647
|
-
return
|
|
648
|
+
return null;
|
|
648
649
|
}
|
|
649
|
-
const note = raw
|
|
650
|
-
|
|
650
|
+
const { note, text } = raw;
|
|
651
|
+
if (note !== undefined && typeof note !== "string")
|
|
652
|
+
return `question ${qid} note must be a string`;
|
|
653
|
+
if (text !== undefined && typeof text !== "string")
|
|
654
|
+
return `question ${qid} text must be a string`;
|
|
655
|
+
if (typeof text === "string" && text.trim() === "")
|
|
656
|
+
return `question ${qid} text must not be empty`;
|
|
657
|
+
return null;
|
|
651
658
|
}
|
|
652
659
|
function validateGateAnswers(questions, answers) {
|
|
653
660
|
const byId = new Map(questions.map((q) => [q.id, q]));
|
|
@@ -655,8 +662,9 @@ function validateGateAnswers(questions, answers) {
|
|
|
655
662
|
const question = byId.get(qid);
|
|
656
663
|
if (!question)
|
|
657
664
|
return `unknown question id: ${qid}`;
|
|
658
|
-
|
|
659
|
-
|
|
665
|
+
const wrapperError = wrapperFieldError(qid, raw);
|
|
666
|
+
if (wrapperError)
|
|
667
|
+
return wrapperError;
|
|
660
668
|
const value = unwrapGateAnswerValue(raw);
|
|
661
669
|
const isArray = Array.isArray(value);
|
|
662
670
|
if (question.multi && !isArray)
|
|
@@ -1120,6 +1128,16 @@ var REGISTRY = [
|
|
|
1120
1128
|
migrated: true,
|
|
1121
1129
|
description: "Age floor in days for the log janitor pruning every surface's rotated log files under ~/.mattstack/rt/logs (default 14). A fresh key, not an ownership-latch port, so a default is fine here."
|
|
1122
1130
|
},
|
|
1131
|
+
{
|
|
1132
|
+
key: "rt.gitStatus",
|
|
1133
|
+
type: "object",
|
|
1134
|
+
scopes: ALL_SCOPES,
|
|
1135
|
+
default: { sweep: true, sweepIntervalSec: 300, fetchIntervalSec: 900 },
|
|
1136
|
+
merge: "deep",
|
|
1137
|
+
repoScoped: true,
|
|
1138
|
+
migrated: true,
|
|
1139
|
+
description: "Mission-control git badge sweep. sweep gates the daemon sweep and fetchIntervalSec (the background fetch cadence, 0 disables fetching) are both read per repo, so a per-repo override of either takes effect (a repo override of { sweep: false } opts that repo out). sweepIntervalSec, the minimum seconds between sweeps, is read only from the global config by the sweep tick; a per-repo override of sweepIntervalSec has no effect."
|
|
1140
|
+
},
|
|
1123
1141
|
{
|
|
1124
1142
|
key: "rt.logLevel",
|
|
1125
1143
|
type: "string",
|
|
@@ -1682,6 +1700,14 @@ var REGISTRY = [
|
|
|
1682
1700
|
default: false,
|
|
1683
1701
|
merge: "replace",
|
|
1684
1702
|
description: "Whether the watchdog may drive a mid-run folder-trust dialog on a daemon-provisioned tree itself, rather than only parking the job and notifying (RT-196). Off by default: the screen match cannot yet confirm the dialog's folder matches the job's worktree, so a working session showing an unrelated permission prompt with the same shape is a real risk."
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
key: "panes.relocationAutoAccept",
|
|
1706
|
+
type: "boolean",
|
|
1707
|
+
scopes: ["machine"],
|
|
1708
|
+
default: true,
|
|
1709
|
+
merge: "replace",
|
|
1710
|
+
description: "Whether the daemon may answer Claude Code's EnterWorktree permission-root relocation prompt on a blocked pane by itself (RT-200). Unlike the trust dialog, the prompt names the worktree path in its own body, and the daemon accepts only when that exact path is in rt's worktree registry, so this is on by default. Read by both the herd watchdog and the executor reconciler."
|
|
1685
1711
|
}
|
|
1686
1712
|
];
|
|
1687
1713
|
|
|
@@ -1856,11 +1882,11 @@ function expandString(input, ctx) {
|
|
|
1856
1882
|
return match;
|
|
1857
1883
|
});
|
|
1858
1884
|
}
|
|
1859
|
-
function teamPath(
|
|
1885
|
+
function teamPath(teamsDir, name) {
|
|
1860
1886
|
if (name.includes("/") || name.includes("\\") || name.includes("..")) {
|
|
1861
1887
|
throw new Error(`rt: cannot expand \${team:${name}} — a team name must be a single directory segment (no "/", "\\" or "..")`);
|
|
1862
1888
|
}
|
|
1863
|
-
return join6(
|
|
1889
|
+
return join6(teamsDir, name);
|
|
1864
1890
|
}
|
|
1865
1891
|
function required(value, name, needs) {
|
|
1866
1892
|
if (value === undefined || value === "") {
|
|
@@ -2525,122 +2551,122 @@ async function resolveNameToIdentity(name, reposJsonPath) {
|
|
|
2525
2551
|
}
|
|
2526
2552
|
}
|
|
2527
2553
|
export {
|
|
2528
|
-
|
|
2529
|
-
validateGateAnswers,
|
|
2530
|
-
unwrapGateAnswerValue,
|
|
2531
|
-
unsetSetting,
|
|
2532
|
-
subscribe,
|
|
2533
|
-
setSettingsWarnSink,
|
|
2534
|
-
setSetting,
|
|
2535
|
-
serializeIdentity,
|
|
2536
|
-
rtCommand,
|
|
2537
|
-
resolveNameToIdentity,
|
|
2538
|
-
resolveForgeToken,
|
|
2539
|
-
repoNameForPath,
|
|
2540
|
-
reconcilerStatus,
|
|
2541
|
-
reconcilerClear,
|
|
2542
|
-
readStore,
|
|
2543
|
-
readProjectMRs,
|
|
2544
|
-
readMrsByBranch,
|
|
2545
|
-
readDiscussions,
|
|
2546
|
-
readBranchCache,
|
|
2547
|
-
parsePaneRef,
|
|
2548
|
-
parseIdentity,
|
|
2549
|
-
paneSpawn,
|
|
2550
|
-
paneSend,
|
|
2551
|
-
panePeek,
|
|
2552
|
-
paneList,
|
|
2553
|
-
paneFocus,
|
|
2554
|
-
paneDirectories,
|
|
2555
|
-
paneAccounts,
|
|
2556
|
-
openSmartPane,
|
|
2557
|
-
normalizeRemote,
|
|
2558
|
-
normalizeGateQuestions,
|
|
2559
|
-
normalizeGateOptions,
|
|
2560
|
-
listTeams,
|
|
2561
|
-
listSettings,
|
|
2562
|
-
listRuns,
|
|
2563
|
-
isMigrated,
|
|
2564
|
-
identityFromRemote,
|
|
2565
|
-
herdWrapUp,
|
|
2566
|
-
herdStopHidden,
|
|
2567
|
-
herdStatus,
|
|
2568
|
-
herdStart,
|
|
2569
|
-
herdSpawn,
|
|
2570
|
-
herdResume,
|
|
2571
|
-
herdReport,
|
|
2572
|
-
herdMilestone,
|
|
2573
|
-
herdList,
|
|
2574
|
-
herdGates,
|
|
2575
|
-
herdClose,
|
|
2576
|
-
herdAttend,
|
|
2577
|
-
herdAsk,
|
|
2578
|
-
herdAnswer,
|
|
2579
|
-
guardTestDaemonEnv,
|
|
2580
|
-
getSetting,
|
|
2581
|
-
getRun,
|
|
2582
|
-
getDef,
|
|
2583
|
-
gateWait,
|
|
2584
|
-
gateUnsubscribe,
|
|
2585
|
-
gateSubscriptions,
|
|
2586
|
-
gateSubscribe,
|
|
2587
|
-
gatePresentation,
|
|
2588
|
-
gatePark,
|
|
2589
|
-
gateOptionValue,
|
|
2590
|
-
gateOptionLabel,
|
|
2591
|
-
gateOpen,
|
|
2592
|
-
gateList,
|
|
2593
|
-
gateClose,
|
|
2594
|
-
gateAsk,
|
|
2595
|
-
gateAnswer,
|
|
2596
|
-
formatPaneRef,
|
|
2597
|
-
explainSetting,
|
|
2598
|
-
expandVariables,
|
|
2599
|
-
eventsWait,
|
|
2600
|
-
eventsList,
|
|
2601
|
-
eventsHead,
|
|
2602
|
-
eventsEmit,
|
|
2603
|
-
deriveRepoIdentity,
|
|
2604
|
-
decidePlacement,
|
|
2605
|
-
daemonHealth,
|
|
2606
|
-
createRelay,
|
|
2607
|
-
clearIdentityMemo,
|
|
2608
|
-
chatWho,
|
|
2609
|
-
chatSignOut,
|
|
2610
|
-
chatSignIn,
|
|
2611
|
-
chatRooms,
|
|
2612
|
-
chatRelease,
|
|
2613
|
-
chatRead,
|
|
2614
|
-
chatPost,
|
|
2615
|
-
chatMessages,
|
|
2616
|
-
chatMark,
|
|
2617
|
-
chatLeave,
|
|
2618
|
-
chatJoin,
|
|
2619
|
-
chatInvite,
|
|
2620
|
-
chatDmOpen,
|
|
2621
|
-
chatDm,
|
|
2622
|
-
chatClaim,
|
|
2623
|
-
chatBuddies,
|
|
2624
|
-
chatBack,
|
|
2625
|
-
chatAway,
|
|
2626
|
-
chatArchive,
|
|
2627
|
-
chatAck,
|
|
2628
|
-
bgStop,
|
|
2629
|
-
bgStatus,
|
|
2630
|
-
bgRelease,
|
|
2631
|
-
bgEnsure,
|
|
2632
|
-
allDefs,
|
|
2633
|
-
agentStart,
|
|
2634
|
-
agentResume,
|
|
2635
|
-
agentList,
|
|
2636
|
-
agentGet,
|
|
2637
|
-
abandonRun,
|
|
2638
|
-
SCOPE_ORDER,
|
|
2639
|
-
REGISTRY,
|
|
2640
|
-
GATE_FORM_OPTION_CAP,
|
|
2641
|
-
GATE_BY_PANE,
|
|
2642
|
-
DEFAULT_WS_URL,
|
|
2643
|
-
DEFAULT_SOCK,
|
|
2554
|
+
BG_PREFIX,
|
|
2644
2555
|
COMMAND_NAMES,
|
|
2645
|
-
|
|
2556
|
+
DEFAULT_SOCK,
|
|
2557
|
+
DEFAULT_WS_URL,
|
|
2558
|
+
GATE_BY_PANE,
|
|
2559
|
+
GATE_FORM_OPTION_CAP,
|
|
2560
|
+
REGISTRY,
|
|
2561
|
+
SCOPE_ORDER,
|
|
2562
|
+
abandonRun,
|
|
2563
|
+
agentGet,
|
|
2564
|
+
agentList,
|
|
2565
|
+
agentResume,
|
|
2566
|
+
agentStart,
|
|
2567
|
+
allDefs,
|
|
2568
|
+
bgEnsure,
|
|
2569
|
+
bgRelease,
|
|
2570
|
+
bgStatus,
|
|
2571
|
+
bgStop,
|
|
2572
|
+
chatAck,
|
|
2573
|
+
chatArchive,
|
|
2574
|
+
chatAway,
|
|
2575
|
+
chatBack,
|
|
2576
|
+
chatBuddies,
|
|
2577
|
+
chatClaim,
|
|
2578
|
+
chatDm,
|
|
2579
|
+
chatDmOpen,
|
|
2580
|
+
chatInvite,
|
|
2581
|
+
chatJoin,
|
|
2582
|
+
chatLeave,
|
|
2583
|
+
chatMark,
|
|
2584
|
+
chatMessages,
|
|
2585
|
+
chatPost,
|
|
2586
|
+
chatRead,
|
|
2587
|
+
chatRelease,
|
|
2588
|
+
chatRooms,
|
|
2589
|
+
chatSignIn,
|
|
2590
|
+
chatSignOut,
|
|
2591
|
+
chatWho,
|
|
2592
|
+
clearIdentityMemo,
|
|
2593
|
+
createRelay,
|
|
2594
|
+
daemonHealth,
|
|
2595
|
+
decidePlacement,
|
|
2596
|
+
deriveRepoIdentity,
|
|
2597
|
+
eventsEmit,
|
|
2598
|
+
eventsHead,
|
|
2599
|
+
eventsList,
|
|
2600
|
+
eventsWait,
|
|
2601
|
+
expandVariables,
|
|
2602
|
+
explainSetting,
|
|
2603
|
+
formatPaneRef,
|
|
2604
|
+
gateAnswer,
|
|
2605
|
+
gateAsk,
|
|
2606
|
+
gateClose,
|
|
2607
|
+
gateList,
|
|
2608
|
+
gateOpen,
|
|
2609
|
+
gateOptionLabel,
|
|
2610
|
+
gateOptionValue,
|
|
2611
|
+
gatePark,
|
|
2612
|
+
gatePresentation,
|
|
2613
|
+
gateSubscribe,
|
|
2614
|
+
gateSubscriptions,
|
|
2615
|
+
gateUnsubscribe,
|
|
2616
|
+
gateWait,
|
|
2617
|
+
getDef,
|
|
2618
|
+
getRun,
|
|
2619
|
+
getSetting,
|
|
2620
|
+
guardTestDaemonEnv,
|
|
2621
|
+
herdAnswer,
|
|
2622
|
+
herdAsk,
|
|
2623
|
+
herdAttend,
|
|
2624
|
+
herdClose,
|
|
2625
|
+
herdGates,
|
|
2626
|
+
herdList,
|
|
2627
|
+
herdMilestone,
|
|
2628
|
+
herdReport,
|
|
2629
|
+
herdResume,
|
|
2630
|
+
herdSpawn,
|
|
2631
|
+
herdStart,
|
|
2632
|
+
herdStatus,
|
|
2633
|
+
herdStopHidden,
|
|
2634
|
+
herdWrapUp,
|
|
2635
|
+
identityFromRemote,
|
|
2636
|
+
isMigrated,
|
|
2637
|
+
listRuns,
|
|
2638
|
+
listSettings,
|
|
2639
|
+
listTeams,
|
|
2640
|
+
normalizeGateOptions,
|
|
2641
|
+
normalizeGateQuestions,
|
|
2642
|
+
normalizeRemote,
|
|
2643
|
+
openSmartPane,
|
|
2644
|
+
paneAccounts,
|
|
2645
|
+
paneDirectories,
|
|
2646
|
+
paneFocus,
|
|
2647
|
+
paneList,
|
|
2648
|
+
panePeek,
|
|
2649
|
+
paneSend,
|
|
2650
|
+
paneSpawn,
|
|
2651
|
+
parseIdentity,
|
|
2652
|
+
parsePaneRef,
|
|
2653
|
+
readBranchCache,
|
|
2654
|
+
readDiscussions,
|
|
2655
|
+
readMrsByBranch,
|
|
2656
|
+
readProjectMRs,
|
|
2657
|
+
readStore,
|
|
2658
|
+
reconcilerClear,
|
|
2659
|
+
reconcilerStatus,
|
|
2660
|
+
repoNameForPath,
|
|
2661
|
+
resolveForgeToken,
|
|
2662
|
+
resolveNameToIdentity,
|
|
2663
|
+
rtCommand,
|
|
2664
|
+
serializeIdentity,
|
|
2665
|
+
setSetting,
|
|
2666
|
+
setSettingsWarnSink,
|
|
2667
|
+
subscribe,
|
|
2668
|
+
unsetSetting,
|
|
2669
|
+
unwrapGateAnswerValue,
|
|
2670
|
+
validateGateAnswers,
|
|
2671
|
+
validateValue
|
|
2646
2672
|
};
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -31,12 +31,27 @@ export interface ProjectMRsScope {
|
|
|
31
31
|
knownSections?: string[];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/** Classified cause of a repo's failing project sync (lib/daemon/project-sync-health.ts). */
|
|
35
|
+
export type ProjectSyncErrorKind = "rate-limited" | "auth" | "server-error" | "timeout" | "other";
|
|
36
|
+
|
|
37
|
+
/** A repo's current unbroken run of failed project syncs. */
|
|
38
|
+
export interface ProjectSyncError {
|
|
39
|
+
/** First failure of the run; later failures leave it alone. */
|
|
40
|
+
since: number;
|
|
41
|
+
lastAt: number;
|
|
42
|
+
kind: ProjectSyncErrorKind;
|
|
43
|
+
/** Raw error text, capped at 200 chars. */
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
export interface ProjectMRsData {
|
|
35
48
|
mrs: Record<string, { pr: PullRequest; fetchedAt: number; codeownerSections?: string[] }>;
|
|
36
49
|
listSyncedAt: number;
|
|
37
50
|
source: "poll" | "events" | "mutation";
|
|
38
51
|
syncedAt: number;
|
|
39
52
|
scope?: ProjectMRsScope;
|
|
53
|
+
/** Present while this repo's most recent project sync failed. */
|
|
54
|
+
syncError?: ProjectSyncError;
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
export interface DiscussionsData {
|
|
@@ -118,7 +133,7 @@ export { gateOptionValue, gateOptionLabel } from "./gate-options.ts";
|
|
|
118
133
|
doorbell it would otherwise send back to the writer. Optional: a caller
|
|
119
134
|
that supplies none (the board status-bin answers `by: "pane"` with no
|
|
120
135
|
session) still matches self by `by === GATE_BY_PANE`. */
|
|
121
|
-
export interface GateAnswer { answers: Record<string, string | string[] | { value: string | string[]; note?: string }>; by: string; answeredAt: number; overridden?: boolean; session?: string }
|
|
136
|
+
export interface GateAnswer { answers: Record<string, string | string[] | { value: string | string[]; note?: string; text?: string }>; by: string; answeredAt: number; overridden?: boolean; session?: string }
|
|
122
137
|
export interface GateRow {
|
|
123
138
|
id: string; subject: string; kind: string;
|
|
124
139
|
questions: GateQuestion[]; meta: Record<string, unknown> | null;
|
|
@@ -464,8 +479,14 @@ export interface WorktreeProvisionData {
|
|
|
464
479
|
tree: string; path: string; branch: string; wasOnDeck: boolean;
|
|
465
480
|
readyAt: string | null; branchState: "new" | "tracking-remote" | "existing-clean" | "diverged" | "behind";
|
|
466
481
|
readyFailed?: true; failedStep?: string;
|
|
482
|
+
/** Set only when the tree was built by hydrating from the repo's golden donor, not the on-deck pool or a cold create. */
|
|
483
|
+
hydratedFrom?: string;
|
|
484
|
+
}
|
|
485
|
+
export interface WorktreeCreateData {
|
|
486
|
+
tree: string; path: string;
|
|
487
|
+
/** Set only for `--on-deck`, and only when that tree hydrated from the golden rather than cold-creating. */
|
|
488
|
+
hydratedFrom?: string;
|
|
467
489
|
}
|
|
468
|
-
export interface WorktreeCreateData { tree: string; path: string }
|
|
469
490
|
export interface WorktreeDisposeData {
|
|
470
491
|
disposed: string[];
|
|
471
492
|
/** `detail` is set only for a refusal whose bare `reason` code can't name
|
|
@@ -505,6 +526,30 @@ export type MRActionName =
|
|
|
505
526
|
| "retryJob" | "retryPipeline"
|
|
506
527
|
| "toggleDraft" | "requestReReview";
|
|
507
528
|
|
|
529
|
+
/** One worktree's git badge as the daemon sweep computed it. All timestamps ISO 8601. */
|
|
530
|
+
export interface GitWorktreeBadge {
|
|
531
|
+
worktree: string;
|
|
532
|
+
branch: string | null;
|
|
533
|
+
detached: boolean;
|
|
534
|
+
staged: number;
|
|
535
|
+
unstaged: number;
|
|
536
|
+
untracked: number;
|
|
537
|
+
conflicted: number;
|
|
538
|
+
clean: boolean;
|
|
539
|
+
ahead: number | null;
|
|
540
|
+
behind: number | null;
|
|
541
|
+
upstream: string | null;
|
|
542
|
+
lastFetchedAt: string | null;
|
|
543
|
+
updatedAt: string;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** repo is the serialized identity (the repo-index key). error is set when the last sweep could not read the repo; stale worktrees may accompany it. */
|
|
547
|
+
export interface RepoStatusRow {
|
|
548
|
+
repo: string;
|
|
549
|
+
worktrees: GitWorktreeBadge[];
|
|
550
|
+
error: string | null;
|
|
551
|
+
}
|
|
552
|
+
|
|
508
553
|
export interface Commands {
|
|
509
554
|
"project-mrs:read": { payload: { repoName: string; maxAgeMs?: number; demand?: DemandDecl }; data: ProjectMRsData };
|
|
510
555
|
"discussions:read": { payload: { repoName: string; iid: number }; data: DiscussionsData };
|
|
@@ -684,6 +729,7 @@ export interface Commands {
|
|
|
684
729
|
"endpoint:status": { payload: { repo?: string }; data: EndpointStatusData };
|
|
685
730
|
|
|
686
731
|
"repos:locate": { payload: { newPath: string; repo?: string; dryRun?: boolean }; data: unknown };
|
|
732
|
+
"repos:status": { payload: { refresh?: boolean }; data: { repos: RepoStatusRow[]; sweptAt: string | null } };
|
|
687
733
|
"freshness:reconcile": { payload: Record<string, never>; data: unknown };
|
|
688
734
|
|
|
689
735
|
// ─── Reconciler (executor state; lib/daemon/reconciler.ts) ───────────────
|
|
@@ -721,8 +767,17 @@ export interface Commands {
|
|
|
721
767
|
/** `contextOmitted` appears only when the gate context plus every
|
|
722
768
|
question's `context` exceeded their shared 8192-byte budget: the
|
|
723
769
|
gate still opened, but question contexts were dropped, and the gate
|
|
724
|
-
context too when it was over the budget on its own.
|
|
725
|
-
|
|
770
|
+
context too when it was over the budget on its own.
|
|
771
|
+
`formCapExceeded`/`formCapAdvisory` appear only when the pane could
|
|
772
|
+
have presented an in-pane form and one or more questions exceeded
|
|
773
|
+
its 4-option cap, which is the one thing that forced this gate to
|
|
774
|
+
`wait`: the gate still opens, and the advisory names the structural
|
|
775
|
+
fix so the caller can re-author. */
|
|
776
|
+
data: {
|
|
777
|
+
id: string; presentation: "form" | "wait"; subject: string; supersededId: string | null; contextOmitted?: true;
|
|
778
|
+
formCapExceeded?: Array<{ question: string; options: number }>;
|
|
779
|
+
formCapAdvisory?: string;
|
|
780
|
+
};
|
|
726
781
|
};
|
|
727
782
|
/**
|
|
728
783
|
* A CAS loss is a DEFINED OUTCOME, not an error: `ok:true` with
|
|
@@ -875,6 +930,7 @@ export const COMMAND_NAMES: readonly CommandName[] = [
|
|
|
875
930
|
"endpoint:release",
|
|
876
931
|
"endpoint:status",
|
|
877
932
|
"repos:locate",
|
|
933
|
+
"repos:status",
|
|
878
934
|
"freshness:reconcile",
|
|
879
935
|
"reconciler:status",
|
|
880
936
|
"reconciler:clear",
|
package/src/gate-answers.ts
CHANGED
|
@@ -3,8 +3,9 @@ import { gateOptionValue } from "./gate-options.ts";
|
|
|
3
3
|
|
|
4
4
|
export type GateAnswerWire = GateAnswer["answers"][string];
|
|
5
5
|
|
|
6
|
-
/** Both wire shapes carry the same value underneath: bare, or {value,
|
|
7
|
-
when a panel attaches free text
|
|
6
|
+
/** Both wire shapes carry the same value underneath: bare, or {value,
|
|
7
|
+
note?, text?} when a panel attaches free text or a replacement for text
|
|
8
|
+
the gate offered. Unwrapping keeps only the value. */
|
|
8
9
|
export function unwrapGateAnswerValue(raw: unknown): unknown {
|
|
9
10
|
if (raw && typeof raw === "object" && !Array.isArray(raw) && "value" in (raw as Record<string, unknown>)) {
|
|
10
11
|
return (raw as { value: unknown }).value;
|
|
@@ -12,12 +13,15 @@ export function unwrapGateAnswerValue(raw: unknown): unknown {
|
|
|
12
13
|
return raw;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function
|
|
16
|
+
function wrapperFieldError(qid: string, raw: unknown): string | null {
|
|
16
17
|
if (!raw || typeof raw !== "object" || Array.isArray(raw) || !("value" in (raw as Record<string, unknown>))) {
|
|
17
|
-
return
|
|
18
|
+
return null;
|
|
18
19
|
}
|
|
19
|
-
const note =
|
|
20
|
-
|
|
20
|
+
const { note, text } = raw as Record<string, unknown>;
|
|
21
|
+
if (note !== undefined && typeof note !== "string") return `question ${qid} note must be a string`;
|
|
22
|
+
if (text !== undefined && typeof text !== "string") return `question ${qid} text must be a string`;
|
|
23
|
+
if (typeof text === "string" && text.trim() === "") return `question ${qid} text must not be empty`;
|
|
24
|
+
return null;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/** Option membership is required whenever a question declares options,
|
|
@@ -32,7 +36,8 @@ export function validateGateAnswers(
|
|
|
32
36
|
for (const [qid, raw] of Object.entries(answers)) {
|
|
33
37
|
const question = byId.get(qid);
|
|
34
38
|
if (!question) return `unknown question id: ${qid}`;
|
|
35
|
-
|
|
39
|
+
const wrapperError = wrapperFieldError(qid, raw);
|
|
40
|
+
if (wrapperError) return wrapperError;
|
|
36
41
|
const value = unwrapGateAnswerValue(raw);
|
|
37
42
|
const isArray = Array.isArray(value);
|
|
38
43
|
if (question.multi && !isArray) return `question ${qid} expects an array (multi)`;
|
package/src/index.ts
CHANGED
|
@@ -222,6 +222,16 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
222
222
|
migrated: true,
|
|
223
223
|
description: "Age floor in days for the log janitor pruning every surface's rotated log files under ~/.mattstack/rt/logs (default 14). A fresh key, not an ownership-latch port, so a default is fine here.",
|
|
224
224
|
},
|
|
225
|
+
{
|
|
226
|
+
key: "rt.gitStatus",
|
|
227
|
+
type: "object",
|
|
228
|
+
scopes: ALL_SCOPES,
|
|
229
|
+
default: { sweep: true, sweepIntervalSec: 300, fetchIntervalSec: 900 },
|
|
230
|
+
merge: "deep",
|
|
231
|
+
repoScoped: true,
|
|
232
|
+
migrated: true,
|
|
233
|
+
description: "Mission-control git badge sweep. sweep gates the daemon sweep and fetchIntervalSec (the background fetch cadence, 0 disables fetching) are both read per repo, so a per-repo override of either takes effect (a repo override of { sweep: false } opts that repo out). sweepIntervalSec, the minimum seconds between sweeps, is read only from the global config by the sweep tick; a per-repo override of sweepIntervalSec has no effect.",
|
|
234
|
+
},
|
|
225
235
|
{
|
|
226
236
|
key: "rt.logLevel",
|
|
227
237
|
type: "string",
|
|
@@ -825,4 +835,12 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
825
835
|
merge: "replace",
|
|
826
836
|
description: "Whether the watchdog may drive a mid-run folder-trust dialog on a daemon-provisioned tree itself, rather than only parking the job and notifying (RT-196). Off by default: the screen match cannot yet confirm the dialog's folder matches the job's worktree, so a working session showing an unrelated permission prompt with the same shape is a real risk.",
|
|
827
837
|
},
|
|
838
|
+
{
|
|
839
|
+
key: "panes.relocationAutoAccept",
|
|
840
|
+
type: "boolean",
|
|
841
|
+
scopes: ["machine"],
|
|
842
|
+
default: true,
|
|
843
|
+
merge: "replace",
|
|
844
|
+
description: "Whether the daemon may answer Claude Code's EnterWorktree permission-root relocation prompt on a blocked pane by itself (RT-200). Unlike the trust dialog, the prompt names the worktree path in its own body, and the daemon accepts only when that exact path is in rt's worktree registry, so this is on by default. Read by both the herd watchdog and the executor reconciler.",
|
|
845
|
+
},
|
|
828
846
|
];
|