tickmarkr 1.72.0 → 1.73.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/cli/commands/approve.js +21 -3
- package/dist/cli/commands/resume.js +10 -2
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +219 -30
- package/dist/cli/commands/ui.d.ts +6 -2
- package/dist/cli/commands/ui.js +2 -4
- package/dist/config/config.d.ts +4 -0
- package/dist/config/config.js +6 -1
- package/dist/drivers/herdr.d.ts +4 -0
- package/dist/drivers/herdr.js +72 -8
- package/dist/drivers/subprocess.d.ts +3 -3
- package/dist/drivers/subprocess.js +10 -5
- package/dist/route/preference.d.ts +2 -1
- package/dist/route/preference.js +8 -2
- package/dist/run/daemon.d.ts +1 -0
- package/dist/run/daemon.js +139 -4
- package/dist/run/git.d.ts +2 -0
- package/dist/run/git.js +55 -0
- package/dist/run/journal.d.ts +6 -1
- package/dist/run/journal.js +28 -1
- package/dist/tui/ink/components.d.ts +18 -0
- package/dist/tui/ink/components.js +30 -2
- package/dist/tui/ink/studio-app.d.ts +59 -0
- package/dist/tui/ink/studio-app.js +320 -0
- package/package.json +1 -1
- package/dist/tui/app.d.ts +0 -72
- package/dist/tui/app.js +0 -334
- package/dist/tui/engine.d.ts +0 -52
- package/dist/tui/engine.js +0 -92
- package/dist/tui/frame.d.ts +0 -22
- package/dist/tui/frame.js +0 -56
- package/dist/tui/input.d.ts +0 -41
- package/dist/tui/input.js +0 -100
- package/dist/tui/views/consult-dossier.d.ts +0 -49
- package/dist/tui/views/consult-dossier.js +0 -169
- package/dist/tui/views/diff-modal.d.ts +0 -9
- package/dist/tui/views/diff-modal.js +0 -19
- package/dist/tui/views/fleet-view.d.ts +0 -91
- package/dist/tui/views/fleet-view.js +0 -337
- package/dist/tui/views/preview-view.d.ts +0 -7
- package/dist/tui/views/preview-view.js +0 -80
- package/dist/tui/views/profile-view.d.ts +0 -15
- package/dist/tui/views/profile-view.js +0 -93
- package/dist/tui/views/routing-view.d.ts +0 -48
- package/dist/tui/views/routing-view.js +0 -234
- package/dist/tui/views/runs-view.d.ts +0 -70
- package/dist/tui/views/runs-view.js +0 -387
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { ConsultVerdict } from "../../run/consult.js";
|
|
2
|
-
import type { JournalEvent } from "../../run/journal.js";
|
|
3
|
-
/** Placeholder marker surfaced in the Runs detail panel until T2 fills the viewer. */
|
|
4
|
-
export declare const DOSSIER_PLACEHOLDER = "consult dossier viewer \u2014 stub, filled by T2";
|
|
5
|
-
/** Placeholder renderer — returns one dim line. */
|
|
6
|
-
export declare function renderDossierPlaceholder(): string;
|
|
7
|
-
export type ConsultAction = ConsultVerdict["action"];
|
|
8
|
-
/** One recorded consult verdict, with its persisted prompt content injected by the caller. */
|
|
9
|
-
export interface DossierVerdict {
|
|
10
|
-
action: ConsultAction;
|
|
11
|
-
reason?: string;
|
|
12
|
-
notes?: string;
|
|
13
|
-
guidance?: string;
|
|
14
|
-
/** Persisted prompt content (the consults/<task>-<n>.md artifact), loaded by the caller. */
|
|
15
|
-
prompt?: string;
|
|
16
|
-
}
|
|
17
|
-
/** Everything the dossier panel renders — loaded by the caller, never by the render path. */
|
|
18
|
-
export interface ConsultDossierData {
|
|
19
|
-
taskId: string;
|
|
20
|
-
/** Verdicts in dispatch order (journal order). */
|
|
21
|
-
verdicts: DossierVerdict[];
|
|
22
|
-
}
|
|
23
|
-
export interface ConsultDossierPanel {
|
|
24
|
-
/** Change the selected task. Any selection change invalidates every cached prompt render. */
|
|
25
|
-
select(data: ConsultDossierData | undefined): void;
|
|
26
|
-
/** "up"/"down" move the verdict cursor; "enter" toggles the selected verdict's dossier. */
|
|
27
|
-
key(name: string): void;
|
|
28
|
-
/** Render the panel body lines (chrome/divider lines are the host view's job). */
|
|
29
|
-
render(): string[];
|
|
30
|
-
/** Index of the expanded verdict, or null when collapsed. */
|
|
31
|
-
readonly expandedIndex: number | null;
|
|
32
|
-
}
|
|
33
|
-
/** Fold one task's consult-verdict journal events into dossier verdicts, in dispatch order.
|
|
34
|
-
* Pure function over injected events. opts.prompts carries the persisted prompt contents in
|
|
35
|
-
* the same dispatch order (the caller reads the consults/ artifacts); a verdict whose artifact
|
|
36
|
-
* was not loaded simply has no prompt. Malformed events are skipped, never fatal. */
|
|
37
|
-
export declare function foldConsultVerdicts(events: JournalEvent[], taskId: string, opts?: {
|
|
38
|
-
prompts?: string[];
|
|
39
|
-
}): DossierVerdict[];
|
|
40
|
-
/** Translate persisted prompt content into styled terminal lines. Pure. */
|
|
41
|
-
export declare function translatePromptContent(content: string): string[];
|
|
42
|
-
/** A verdict's guidance field is "imperative steps for the worker (newline-separated ok)" —
|
|
43
|
-
* already list-shaped, so it renders as a bullet list, never a wall of prose. Pure. */
|
|
44
|
-
export declare function renderGuidanceSteps(guidance: string): string[];
|
|
45
|
-
/** The consult dossier panel. Stateful over injected data: a verdict cursor, at most one
|
|
46
|
-
* expanded dossier, and a per-verdict render cache cleared only when the selection changes. */
|
|
47
|
-
export declare function createConsultDossierPanel(initial?: ConsultDossierData, opts?: {
|
|
48
|
-
translate?: (content: string) => string[];
|
|
49
|
-
}): ConsultDossierPanel;
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// T2 (v1.68): consult dossier viewer — the Runs cockpit's detail panel.
|
|
2
|
-
// Lists every consult verdict recorded for the selected task (action + reason, dispatch order)
|
|
3
|
-
// and expands one verdict's persisted prompt content on request. Pure render over INJECTED data:
|
|
4
|
-
// this module never touches the filesystem — the caller folds journal events and loads the
|
|
5
|
-
// consults/ artifacts, then hands both in. The prompt-content translator is a hand-written
|
|
6
|
-
// line-oriented pass over a known narrow subset (headings, blockquotes, bullets, fenced blocks),
|
|
7
|
-
// never a markdown-AST dependency. Rendered prompt lines are cached per verdict and invalidated
|
|
8
|
-
// only on selection change — never re-parsed on repaint (opencode's opentui regression lesson).
|
|
9
|
-
// Read-only by construction: there is no edit or delete path for a dossier or a verdict.
|
|
10
|
-
import { GLYPHS, bold, dim, warn } from "../../brand.js";
|
|
11
|
-
/** Placeholder marker surfaced in the Runs detail panel until T2 fills the viewer. */
|
|
12
|
-
export const DOSSIER_PLACEHOLDER = "consult dossier viewer — stub, filled by T2";
|
|
13
|
-
/** Placeholder renderer — returns one dim line. */
|
|
14
|
-
export function renderDossierPlaceholder() {
|
|
15
|
-
return ` ${DOSSIER_PLACEHOLDER}`;
|
|
16
|
-
}
|
|
17
|
-
const ACTIONS = ["retry", "reroute", "decompose", "human"];
|
|
18
|
-
/** Fold one task's consult-verdict journal events into dossier verdicts, in dispatch order.
|
|
19
|
-
* Pure function over injected events. opts.prompts carries the persisted prompt contents in
|
|
20
|
-
* the same dispatch order (the caller reads the consults/ artifacts); a verdict whose artifact
|
|
21
|
-
* was not loaded simply has no prompt. Malformed events are skipped, never fatal. */
|
|
22
|
-
export function foldConsultVerdicts(events, taskId, opts = {}) {
|
|
23
|
-
const verdicts = [];
|
|
24
|
-
for (const e of events) {
|
|
25
|
-
if (e.taskId !== taskId || e.event !== "consult-verdict")
|
|
26
|
-
continue;
|
|
27
|
-
const d = e.data;
|
|
28
|
-
if (typeof d.action !== "string" || !ACTIONS.includes(d.action))
|
|
29
|
-
continue;
|
|
30
|
-
verdicts.push({
|
|
31
|
-
action: d.action,
|
|
32
|
-
...(typeof d.reason === "string" ? { reason: d.reason } : {}),
|
|
33
|
-
...(typeof d.notes === "string" ? { notes: d.notes } : {}),
|
|
34
|
-
...(typeof d.guidance === "string" ? { guidance: d.guidance } : {}),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
if (opts.prompts) {
|
|
38
|
-
for (const [i, prompt] of opts.prompts.entries()) {
|
|
39
|
-
if (verdicts[i])
|
|
40
|
-
verdicts[i].prompt = prompt;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return verdicts;
|
|
44
|
-
}
|
|
45
|
-
// ── prompt-content translator ────────────────────────────────────────────────
|
|
46
|
-
// Hand-written, line-oriented, pure. Known subset only: ATX headings (# = bold, deeper = dim),
|
|
47
|
-
// `> ` blockquotes (dim `│ ` bar prefix), `- `/`* ` bullets (• marker), ``` fenced blocks
|
|
48
|
-
// (dim, indented), everything else passes through plain. No markdown-AST dependency.
|
|
49
|
-
const HEADING_RE = /^(#{1,6})[ \t]+(.*)$/;
|
|
50
|
-
const QUOTE_RE = /^>[ \t]?(.*)$/;
|
|
51
|
-
const BULLET_RE = /^[-*][ \t]+(.*)$/;
|
|
52
|
-
const FENCE_RE = /^\s*```/;
|
|
53
|
-
/** Translate persisted prompt content into styled terminal lines. Pure. */
|
|
54
|
-
export function translatePromptContent(content) {
|
|
55
|
-
const out = [];
|
|
56
|
-
let fenced = false;
|
|
57
|
-
for (const raw of content.split("\n")) {
|
|
58
|
-
const line = raw.replace(/\s+$/, "");
|
|
59
|
-
if (FENCE_RE.test(line)) {
|
|
60
|
-
fenced = !fenced;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (fenced) {
|
|
64
|
-
out.push(line.trim() ? dim(` ${line}`) : "");
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
const heading = HEADING_RE.exec(line);
|
|
68
|
-
if (heading) {
|
|
69
|
-
out.push(heading[1].length === 1 ? bold(heading[2]) : dim(heading[2]));
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const quote = QUOTE_RE.exec(line);
|
|
73
|
-
if (quote) {
|
|
74
|
-
out.push(dim(`│ ${quote[1]}`));
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const bullet = BULLET_RE.exec(line);
|
|
78
|
-
if (bullet) {
|
|
79
|
-
out.push(`• ${bullet[1]}`);
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
out.push(line);
|
|
83
|
-
}
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
|
-
/** A verdict's guidance field is "imperative steps for the worker (newline-separated ok)" —
|
|
87
|
-
* already list-shaped, so it renders as a bullet list, never a wall of prose. Pure. */
|
|
88
|
-
export function renderGuidanceSteps(guidance) {
|
|
89
|
-
return guidance
|
|
90
|
-
.split(/\n+/)
|
|
91
|
-
.map((s) => s.trim())
|
|
92
|
-
.filter(Boolean)
|
|
93
|
-
.map((s) => `• ${s}`);
|
|
94
|
-
}
|
|
95
|
-
/** The consult dossier panel. Stateful over injected data: a verdict cursor, at most one
|
|
96
|
-
* expanded dossier, and a per-verdict render cache cleared only when the selection changes. */
|
|
97
|
-
export function createConsultDossierPanel(initial, opts = {}) {
|
|
98
|
-
const translate = opts.translate ?? translatePromptContent;
|
|
99
|
-
let data = initial;
|
|
100
|
-
let cursor = 0;
|
|
101
|
-
let expanded = null;
|
|
102
|
-
// Per-verdict cached render of the expanded dossier block — parsed once, reused on every
|
|
103
|
-
// later expand of the same verdict within the same selection; never re-parsed per repaint.
|
|
104
|
-
const cache = new Map();
|
|
105
|
-
const dossierBlock = (verdict, index) => {
|
|
106
|
-
const hit = cache.get(index);
|
|
107
|
-
if (hit)
|
|
108
|
-
return hit;
|
|
109
|
-
const lines = [];
|
|
110
|
-
lines.push(dim(`── persisted prompt · consult · ${data?.taskId ?? "?"} · verdict ${index + 1} ──`));
|
|
111
|
-
if (verdict.prompt !== undefined)
|
|
112
|
-
lines.push(...translate(verdict.prompt));
|
|
113
|
-
else
|
|
114
|
-
lines.push(dim("(no persisted prompt content recorded for this verdict)"));
|
|
115
|
-
lines.push(`action: ${verdict.action}`);
|
|
116
|
-
const steps = verdict.guidance ? renderGuidanceSteps(verdict.guidance) : [];
|
|
117
|
-
if (steps.length > 0) {
|
|
118
|
-
lines.push("guidance:");
|
|
119
|
-
for (const s of steps)
|
|
120
|
-
lines.push(` ${s}`);
|
|
121
|
-
}
|
|
122
|
-
cache.set(index, lines);
|
|
123
|
-
return lines;
|
|
124
|
-
};
|
|
125
|
-
return {
|
|
126
|
-
get expandedIndex() {
|
|
127
|
-
return expanded;
|
|
128
|
-
},
|
|
129
|
-
select(next) {
|
|
130
|
-
data = next;
|
|
131
|
-
cursor = 0;
|
|
132
|
-
expanded = null;
|
|
133
|
-
cache.clear();
|
|
134
|
-
},
|
|
135
|
-
key(name) {
|
|
136
|
-
if (!data || data.verdicts.length === 0)
|
|
137
|
-
return;
|
|
138
|
-
if (name === "up")
|
|
139
|
-
cursor = Math.max(cursor - 1, 0);
|
|
140
|
-
else if (name === "down")
|
|
141
|
-
cursor = Math.min(cursor + 1, data.verdicts.length - 1);
|
|
142
|
-
else if (name === "enter")
|
|
143
|
-
expanded = expanded === cursor ? null : cursor;
|
|
144
|
-
},
|
|
145
|
-
render() {
|
|
146
|
-
if (!data)
|
|
147
|
-
return [];
|
|
148
|
-
if (data.verdicts.length === 0) {
|
|
149
|
-
return [
|
|
150
|
-
` no consult verdicts recorded for ${data.taskId} — every attempt finished without needing a consult.`,
|
|
151
|
-
];
|
|
152
|
-
}
|
|
153
|
-
const lines = [];
|
|
154
|
-
data.verdicts.forEach((verdict, i) => {
|
|
155
|
-
const pointer = i === cursor ? `${GLYPHS.pointer} ` : " ";
|
|
156
|
-
// Consult actions are not pass/fail: human → warn (needs the operator), the rest plain.
|
|
157
|
-
const actionWord = verdict.action === "human" ? warn(verdict.action) : verdict.action;
|
|
158
|
-
const reason = verdict.reason ?? verdict.notes;
|
|
159
|
-
const hint = dim(i === expanded ? "[enter: collapse]" : "[enter: expand]");
|
|
160
|
-
lines.push(` ${pointer}${i + 1}. ${actionWord}${reason ? ` — ${reason}` : ""} ${hint}`);
|
|
161
|
-
if (i === expanded) {
|
|
162
|
-
for (const l of dossierBlock(verdict, i))
|
|
163
|
-
lines.push(` ${l}`);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
return lines;
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type SaveTarget } from "../save.js";
|
|
2
|
-
export type DiffModalProps = {
|
|
3
|
-
target: SaveTarget;
|
|
4
|
-
targetPath: string;
|
|
5
|
-
diff: string;
|
|
6
|
-
liveRun: boolean;
|
|
7
|
-
};
|
|
8
|
-
/** Render the save-confirmation modal: header names the target path, body shows the exact overlay diff. */
|
|
9
|
-
export declare function renderDiffModal(props: DiffModalProps): string[];
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { bold, dim, fail, legend } from "../../brand.js";
|
|
2
|
-
import { RELOAD_GUARD_NOTICE } from "../save.js";
|
|
3
|
-
/** Render the save-confirmation modal: header names the target path, body shows the exact overlay diff. */
|
|
4
|
-
export function renderDiffModal(props) {
|
|
5
|
-
const lines = [];
|
|
6
|
-
lines.push(bold(`Save overlay — ${props.targetPath} (${props.target === "repo" ? "repository" : "global"} target)`));
|
|
7
|
-
lines.push(legend("t toggle target · y confirm · n/esc cancel"));
|
|
8
|
-
if (props.liveRun) {
|
|
9
|
-
lines.push(fail(RELOAD_GUARD_NOTICE));
|
|
10
|
-
}
|
|
11
|
-
lines.push(dim("─ diff ─"));
|
|
12
|
-
if (props.diff) {
|
|
13
|
-
lines.push(...props.diff.trimEnd().split("\n"));
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
lines.push(dim("(no overlay changes)"));
|
|
17
|
-
}
|
|
18
|
-
return lines;
|
|
19
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { View } from "../app.js";
|
|
2
|
-
import type { AuthHealth } from "../../adapters/types.js";
|
|
3
|
-
import type { Tier } from "../../config/config.js";
|
|
4
|
-
import type { TelemetryRow } from "../../run/journal.js";
|
|
5
|
-
import type { FleetStaging } from "../staging.js";
|
|
6
|
-
/** One discovered adapter and its classified models (cfg.tiers via channels()). */
|
|
7
|
-
export type FleetRosterEntry = {
|
|
8
|
-
adapter: string;
|
|
9
|
-
models: {
|
|
10
|
-
model: string;
|
|
11
|
-
tier: Tier;
|
|
12
|
-
channel: "sub" | "api";
|
|
13
|
-
}[];
|
|
14
|
-
};
|
|
15
|
-
/** Provenance notes harvested from overlay `# comment`s (fleet-overlay.ts shapes). */
|
|
16
|
-
export type FleetProvenanceNotes = {
|
|
17
|
-
denyAdapters?: Record<string, string>;
|
|
18
|
-
denyModels?: Record<string, string>;
|
|
19
|
-
};
|
|
20
|
-
/** Everything the roster renders — loaded by the caller, never by the render path. */
|
|
21
|
-
export type FleetViewData = {
|
|
22
|
-
adapters: FleetRosterEntry[];
|
|
23
|
-
/** Doctor cache contents (doctor.json), keyed by adapter id. */
|
|
24
|
-
health: Record<string, AuthHealth>;
|
|
25
|
-
/** Age of the doctor cache in ms; null when no probe data exists. */
|
|
26
|
-
doctorAgeMs: number | null;
|
|
27
|
-
denyAdapters: string[];
|
|
28
|
-
/** routing.deny.models entries in channelKey form ("adapter:model"). */
|
|
29
|
-
denyModels: string[];
|
|
30
|
-
notes?: FleetProvenanceNotes;
|
|
31
|
-
/** Telemetry folded from recent run journals (readAllTelemetry); runId optional. */
|
|
32
|
-
telemetry: (TelemetryRow & {
|
|
33
|
-
runId?: string;
|
|
34
|
-
})[];
|
|
35
|
-
};
|
|
36
|
-
/** Per-channel health digest, folded from journal telemetry rows only. */
|
|
37
|
-
export type ChannelHealth = {
|
|
38
|
-
key: string;
|
|
39
|
-
tasks: number;
|
|
40
|
-
attempts: number;
|
|
41
|
-
done: number;
|
|
42
|
-
failed: number;
|
|
43
|
-
human: number;
|
|
44
|
-
overruns: number;
|
|
45
|
-
quotaFailovers: number;
|
|
46
|
-
/** Sum of OBSERVED gateFails; null when no row carried the field (unobserved, never 0). */
|
|
47
|
-
gateFails: number | null;
|
|
48
|
-
medianDurationMs: number | null;
|
|
49
|
-
lastOutcome: TelemetryRow["outcome"] | null;
|
|
50
|
-
};
|
|
51
|
-
/** Fold journal telemetry rows into a per-channel health digest keyed by channelKey
|
|
52
|
-
* ("adapter:model"). The digest derives from journal telemetry (TelemetryRow, the rows the
|
|
53
|
-
* daemon appends to each run's telemetry.jsonl) rather than any new state file: this module
|
|
54
|
-
* reads nothing and writes nothing — the caller folds rows out of the existing run journals
|
|
55
|
-
* (readAllTelemetry) and the fold is a pure function of them, same telemetry in, same digest
|
|
56
|
-
* out. */
|
|
57
|
-
export declare function foldChannelHealth(rows: (TelemetryRow & {
|
|
58
|
-
runId?: string;
|
|
59
|
-
})[]): Map<string, ChannelHealth>;
|
|
60
|
-
/** The studio View plus the roster's own key seam. The T2 shell binds only view-level keys, so
|
|
61
|
-
* expand/collapse and selection are driven through key() — the same names the engine decodes. */
|
|
62
|
-
export type FleetView = View & {
|
|
63
|
-
/** Handle a decoded key name ("up" | "down" | "left" | "right" | "space" | "return"). */
|
|
64
|
-
key(name: string): void;
|
|
65
|
-
/** Whether an adapter's model rows are currently shown. */
|
|
66
|
-
expanded(adapter: string): boolean;
|
|
67
|
-
/** channelKey of the cursor's channel, or null when the cursor sits on an adapter row. */
|
|
68
|
-
selectedChannel(): string | null;
|
|
69
|
-
/** Stage a deny⇄allow toggle for one adapter into the shared buffer. No-op without staging. */
|
|
70
|
-
toggleDenyAdapter(adapter: string): void;
|
|
71
|
-
/** Stage a deny⇄allow toggle for one channel ("adapter:model"). No-op without staging. */
|
|
72
|
-
toggleDenyChannel(channelKey: string): void;
|
|
73
|
-
/** Stage a tier override for one channel ("adapter:model"). No-op without staging. */
|
|
74
|
-
stageTierOverride(channelKey: string, tier: Tier): void;
|
|
75
|
-
};
|
|
76
|
-
/** A channel that is denied in the staged buffer while a routing.map pin still points at it —
|
|
77
|
-
* the fleet view stages the deny, the routing view stages the pin, and BOTH are read out of the
|
|
78
|
-
* one shared staging model, so either view surfaces the same conflict set. */
|
|
79
|
-
export type StagedConflict = {
|
|
80
|
-
/** channelKey ("adapter:model") that is both denied and pinned in the buffer. */
|
|
81
|
-
channelKey: string;
|
|
82
|
-
/** shapes whose pin the deny would dead-end (deny wins over pin in saved config). */
|
|
83
|
-
shapes: string[];
|
|
84
|
-
};
|
|
85
|
-
/** Detect deny-vs-pin conflicts from the shared staging model. The buffer's CURRENT state holds
|
|
86
|
-
* both views' staged state — the fleet view's deny/allow + tier edits and the routing view's
|
|
87
|
-
* pin/floor edits — so reading it sees the combination before anything is saved. A conflict is
|
|
88
|
-
* a pinned channel denied by its own entry OR by its adapter's deny (the adapter deny reaches
|
|
89
|
-
* every channel under it, same rule as denyOf). */
|
|
90
|
-
export declare function stagedConflicts(staging: FleetStaging): StagedConflict[];
|
|
91
|
-
export declare function createFleetView(data?: FleetViewData, staging?: FleetStaging): FleetView;
|