@dadado/agent-kit-cli 4.8.9 → 5.1.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/LICENSE +75 -21
- package/README.md +19 -0
- package/dashboard/dashboard-data.mjs +103 -0
- package/dashboard/dashboard.html +539 -172
- package/dashboard/lib/broadcast-share.mjs +251 -0
- package/dashboard/lib/guards.d.mts +10 -0
- package/dashboard/lib/guards.mjs +71 -4
- package/dashboard/lib/open-browser.d.mts +31 -0
- package/dashboard/lib/open-browser.mjs +298 -0
- package/dashboard/lib/semantic-model.mjs +215 -9
- package/dashboard/open.html +213 -0
- package/dashboard/serve.mjs +6 -2
- package/dashboard/start-broadcast.mjs +75 -31
- package/dashboard/start.mjs +23 -26
- package/dist/index.js +1837 -447
- package/package.json +4 -2
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cosmetic Mission Kit / BYO share URLs for dashboard-broadcast.
|
|
3
|
+
* Fragment-only payloads (never sent to Hostinger access logs).
|
|
4
|
+
* ADR: .cursor/memory/decisions/2026-08-11_mission-control-broadcast-url-mask.md
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Live Hostinger path uses the `.html` artifact; extensionless `/mc/open` may 404 until a host alias exists. */
|
|
8
|
+
export const DEFAULT_SHARE_BASE = "https://missionkit.io/mc/open.html";
|
|
9
|
+
export const SHARE_BASE_ENV = "MISSION_CONTROL_SHARE_BASE";
|
|
10
|
+
export const SHARE_TTL_ENV = "MISSION_CONTROL_SHARE_TTL_SEC";
|
|
11
|
+
export const SHARE_SHOW_LAN_ENV = "MISSION_CONTROL_SHARE_SHOW_LAN";
|
|
12
|
+
export const DEFAULT_SHARE_TTL_SEC = 86_400;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* True when hostname is loopback, link-local, or RFC1918 private (IPv4) / ULA (IPv6).
|
|
16
|
+
* Used by share-target validation (open redirect harden).
|
|
17
|
+
* @param {string} hostname
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export function isPrivateOrLoopbackHostname(hostname) {
|
|
21
|
+
const host = String(hostname || "")
|
|
22
|
+
.trim()
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/^\[|\]$/g, "");
|
|
25
|
+
if (!host) return false;
|
|
26
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) return true;
|
|
27
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
28
|
+
// IPv4 dotted quad
|
|
29
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
30
|
+
if (m) {
|
|
31
|
+
const a = Number(m[1]);
|
|
32
|
+
const b = Number(m[2]);
|
|
33
|
+
const c = Number(m[3]);
|
|
34
|
+
const d = Number(m[4]);
|
|
35
|
+
if ([a, b, c, d].some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return false;
|
|
36
|
+
if (a === 127) return true; // loopback
|
|
37
|
+
if (a === 10) return true; // 10/8
|
|
38
|
+
if (a === 192 && b === 168) return true; // 192.168/16
|
|
39
|
+
if (a === 172 && b >= 16 && b <= 31) return true; // 172.16/12
|
|
40
|
+
if (a === 169 && b === 254) return true; // link-local
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
// IPv6 ULA fc00::/7 and link-local fe80::/10
|
|
44
|
+
if (host.includes(":")) {
|
|
45
|
+
if (host.startsWith("fc") || host.startsWith("fd")) return true;
|
|
46
|
+
if (
|
|
47
|
+
host.startsWith("fe8") ||
|
|
48
|
+
host.startsWith("fe9") ||
|
|
49
|
+
host.startsWith("fea") ||
|
|
50
|
+
host.startsWith("feb")
|
|
51
|
+
) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate a decoded share target URL (LAN Mission Control with optional ?token=).
|
|
60
|
+
* @param {string} url
|
|
61
|
+
* @returns {{ ok: true, url: string } | { ok: false, error: string }}
|
|
62
|
+
*/
|
|
63
|
+
export function validateBroadcastShareTarget(url) {
|
|
64
|
+
const raw = typeof url === "string" ? url.trim() : "";
|
|
65
|
+
if (!raw) return { ok: false, error: "invalid-target" };
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = new URL(raw);
|
|
69
|
+
} catch {
|
|
70
|
+
return { ok: false, error: "invalid-target" };
|
|
71
|
+
}
|
|
72
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
73
|
+
return { ok: false, error: "invalid-target" };
|
|
74
|
+
}
|
|
75
|
+
if (!isPrivateOrLoopbackHostname(parsed.hostname)) {
|
|
76
|
+
return { ok: false, error: "non-private-target" };
|
|
77
|
+
}
|
|
78
|
+
return { ok: true, url: raw };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Normalize and validate a share page base. Rejects non-HTTPS (except loopback http for local preview).
|
|
83
|
+
* @param {string} raw
|
|
84
|
+
* @returns {{ ok: true, base: string } | { ok: false, error: string }}
|
|
85
|
+
*/
|
|
86
|
+
export function normalizeShareBase(raw) {
|
|
87
|
+
const trimmed = String(raw || "")
|
|
88
|
+
.trim()
|
|
89
|
+
.replace(/#.*$/, "")
|
|
90
|
+
.replace(/\/$/, "");
|
|
91
|
+
if (!trimmed) return { ok: false, error: "empty-base" };
|
|
92
|
+
let parsed;
|
|
93
|
+
try {
|
|
94
|
+
parsed = new URL(trimmed);
|
|
95
|
+
} catch {
|
|
96
|
+
return { ok: false, error: "invalid-base" };
|
|
97
|
+
}
|
|
98
|
+
const host = parsed.hostname.toLowerCase();
|
|
99
|
+
const loopback =
|
|
100
|
+
host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".localhost");
|
|
101
|
+
if (parsed.protocol === "https:") {
|
|
102
|
+
return { ok: true, base: trimmed };
|
|
103
|
+
}
|
|
104
|
+
if (parsed.protocol === "http:" && loopback) {
|
|
105
|
+
return { ok: true, base: trimmed };
|
|
106
|
+
}
|
|
107
|
+
return { ok: false, error: "non-https-base" };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* GET /open.html and /open are the public cosmetic share-resolver shell under broadcast bind.
|
|
112
|
+
* All other methods/paths keep the normal token gate.
|
|
113
|
+
* @param {string} method
|
|
114
|
+
* @param {string} pathname
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
export function isPublicBroadcastShareShell(method, pathname) {
|
|
118
|
+
return method === "GET" && (pathname === "/open.html" || pathname === "/open");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {boolean} tokenRequired
|
|
123
|
+
* @param {string} method
|
|
124
|
+
* @param {string} pathname
|
|
125
|
+
* @returns {boolean}
|
|
126
|
+
*/
|
|
127
|
+
export function shareShellTokenRequired(tokenRequired, method, pathname) {
|
|
128
|
+
return Boolean(tokenRequired) && !isPublicBroadcastShareShell(method, pathname);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve share page base URL. Empty / "0" / "off" / "false" disables masking.
|
|
133
|
+
* Non-HTTPS BYO bases (except loopback http) are rejected → masking off (with stderr warn when available).
|
|
134
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
|
135
|
+
* @returns {string | null}
|
|
136
|
+
*/
|
|
137
|
+
export function resolveShareBase(env = process.env) {
|
|
138
|
+
const raw = env?.[SHARE_BASE_ENV];
|
|
139
|
+
if (raw === undefined || raw === null) return DEFAULT_SHARE_BASE;
|
|
140
|
+
const trimmed = String(raw).trim();
|
|
141
|
+
if (!trimmed) return null;
|
|
142
|
+
const lower = trimmed.toLowerCase();
|
|
143
|
+
if (lower === "0" || lower === "off" || lower === "false" || lower === "none") return null;
|
|
144
|
+
const normalized = normalizeShareBase(trimmed);
|
|
145
|
+
if (!normalized.ok) {
|
|
146
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
147
|
+
console.warn(
|
|
148
|
+
`[broadcast-share] ${SHARE_BASE_ENV} rejected (${normalized.error}); printing LAN URL only. Use HTTPS (or loopback http) or set off.`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return normalized.base;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
|
158
|
+
* @returns {number}
|
|
159
|
+
*/
|
|
160
|
+
export function resolveShareTtlSec(env = process.env) {
|
|
161
|
+
const raw = env?.[SHARE_TTL_ENV];
|
|
162
|
+
if (raw === undefined || raw === null || String(raw).trim() === "") {
|
|
163
|
+
return DEFAULT_SHARE_TTL_SEC;
|
|
164
|
+
}
|
|
165
|
+
const n = Number.parseInt(String(raw), 10);
|
|
166
|
+
if (!Number.isFinite(n) || n < 0) return DEFAULT_SHARE_TTL_SEC;
|
|
167
|
+
return n;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* When false, starter omits secondary LAN/Local URL lines (share + token only).
|
|
172
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
|
173
|
+
*/
|
|
174
|
+
export function resolveShareShowLan(env = process.env) {
|
|
175
|
+
const raw = env?.[SHARE_SHOW_LAN_ENV];
|
|
176
|
+
if (raw === undefined || raw === null || String(raw).trim() === "") return true;
|
|
177
|
+
const lower = String(raw).trim().toLowerCase();
|
|
178
|
+
return !(lower === "0" || lower === "off" || lower === "false" || lower === "no");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param {string} lanUrl full http URL including ?token=
|
|
183
|
+
* @param {{ ttlSec?: number, nowSec?: number }} [opts]
|
|
184
|
+
* @returns {string} fragment without leading '#'
|
|
185
|
+
*/
|
|
186
|
+
export function encodeBroadcastSharePayload(lanUrl, opts = {}) {
|
|
187
|
+
const url = typeof lanUrl === "string" ? lanUrl.trim() : "";
|
|
188
|
+
const target = validateBroadcastShareTarget(url);
|
|
189
|
+
if (!target.ok) {
|
|
190
|
+
throw new Error(`encodeBroadcastSharePayload: ${target.error}`);
|
|
191
|
+
}
|
|
192
|
+
const ttlSec = opts.ttlSec ?? DEFAULT_SHARE_TTL_SEC;
|
|
193
|
+
/** @type {{ v: number, u: string, e?: number }} */
|
|
194
|
+
const body = { v: 1, u: target.url };
|
|
195
|
+
if (ttlSec > 0) {
|
|
196
|
+
const now = opts.nowSec ?? Math.floor(Date.now() / 1000);
|
|
197
|
+
body.e = now + ttlSec;
|
|
198
|
+
}
|
|
199
|
+
return `v1.${Buffer.from(JSON.stringify(body), "utf8").toString("base64url")}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {string} lanUrl
|
|
204
|
+
* @param {{ base?: string | null, ttlSec?: number, nowSec?: number }} [opts]
|
|
205
|
+
* @returns {string | null}
|
|
206
|
+
*/
|
|
207
|
+
export function buildBroadcastShareUrl(lanUrl, opts = {}) {
|
|
208
|
+
const base = opts.base === undefined ? DEFAULT_SHARE_BASE : opts.base;
|
|
209
|
+
if (!base) return null;
|
|
210
|
+
const cleaned = String(base).replace(/#.*$/, "").replace(/\/$/, "");
|
|
211
|
+
const frag = encodeBroadcastSharePayload(lanUrl, opts);
|
|
212
|
+
return `${cleaned}#${frag}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {string} fragment hash with or without leading '#'
|
|
217
|
+
* @param {{ nowSec?: number }} [opts]
|
|
218
|
+
* @returns
|
|
219
|
+
* | { ok: true, url: string, expiresAt: number | null }
|
|
220
|
+
* | { ok: false, error: string }
|
|
221
|
+
*/
|
|
222
|
+
export function decodeBroadcastShareFragment(fragment, opts = {}) {
|
|
223
|
+
const raw = String(fragment || "")
|
|
224
|
+
.replace(/^#/, "")
|
|
225
|
+
.trim();
|
|
226
|
+
if (!raw) return { ok: false, error: "missing-fragment" };
|
|
227
|
+
const m = /^v1\.([A-Za-z0-9_-]+)$/.exec(raw);
|
|
228
|
+
if (!m) return { ok: false, error: "unsupported-version" };
|
|
229
|
+
let parsed;
|
|
230
|
+
try {
|
|
231
|
+
const json = Buffer.from(m[1], "base64url").toString("utf8");
|
|
232
|
+
parsed = JSON.parse(json);
|
|
233
|
+
} catch {
|
|
234
|
+
return { ok: false, error: "invalid-payload" };
|
|
235
|
+
}
|
|
236
|
+
if (!parsed || typeof parsed !== "object" || parsed.v !== 1) {
|
|
237
|
+
return { ok: false, error: "unsupported-version" };
|
|
238
|
+
}
|
|
239
|
+
const url = typeof parsed.u === "string" ? parsed.u.trim() : "";
|
|
240
|
+
const target = validateBroadcastShareTarget(url);
|
|
241
|
+
if (!target.ok) return { ok: false, error: target.error };
|
|
242
|
+
let expiresAt = null;
|
|
243
|
+
if (parsed.e !== undefined && parsed.e !== null) {
|
|
244
|
+
const e = Number(parsed.e);
|
|
245
|
+
if (!Number.isFinite(e)) return { ok: false, error: "invalid-expiry" };
|
|
246
|
+
expiresAt = e;
|
|
247
|
+
const now = opts.nowSec ?? Math.floor(Date.now() / 1000);
|
|
248
|
+
if (now > e) return { ok: false, error: "expired" };
|
|
249
|
+
}
|
|
250
|
+
return { ok: true, url: target.url, expiresAt };
|
|
251
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Ambient types for dashboard/lib/guards.mjs (consumed by CLI TypeScript). */
|
|
2
|
+
|
|
3
|
+
export function resolveContextConfigPath(
|
|
4
|
+
repoRoot: string,
|
|
5
|
+
fsHooks?: {
|
|
6
|
+
existsSync?: (path: string) => boolean;
|
|
7
|
+
realpathSync?: (path: string) => string;
|
|
8
|
+
mkdirSync?: (path: string, opts?: { recursive?: boolean }) => void;
|
|
9
|
+
},
|
|
10
|
+
): { ok: true; path: string } | { ok: false; error: string };
|
package/dashboard/lib/guards.mjs
CHANGED
|
@@ -445,7 +445,10 @@ export const CONFIG_PERSONA_IDS = Object.freeze(["autopilot", "night-shift", "gh
|
|
|
445
445
|
export const CONFIG_PERSONA_MODES = Object.freeze(["continue-plan", "run-plan", "cli-run-plan"]);
|
|
446
446
|
|
|
447
447
|
/** Allowed externalPlanReview.backend values. */
|
|
448
|
-
export const CONFIG_REVIEW_BACKENDS = Object.freeze(["claude"]);
|
|
448
|
+
export const CONFIG_REVIEW_BACKENDS = Object.freeze(["auto", "claude", "cursor"]);
|
|
449
|
+
|
|
450
|
+
/** Named reviewer / advisor / implementer model id (no secrets). */
|
|
451
|
+
const REVIEW_MODEL_ID = /^[A-Za-z0-9._:+-]{1,64}$/;
|
|
449
452
|
|
|
450
453
|
/** Allowed externalPlanReview.mode values (audits arming path). */
|
|
451
454
|
export const CONFIG_REVIEW_MODES = Object.freeze(["paste", "autonomous"]);
|
|
@@ -591,6 +594,10 @@ export function validateConfigWriteBody(body) {
|
|
|
591
594
|
const eprAllowed = new Set([
|
|
592
595
|
"enabled",
|
|
593
596
|
"backend",
|
|
597
|
+
"reviewerModel",
|
|
598
|
+
"advisorModel",
|
|
599
|
+
"waitSliceSeconds",
|
|
600
|
+
"waitTimeoutSeconds",
|
|
594
601
|
"autoRemediate",
|
|
595
602
|
"offerOnExhausted",
|
|
596
603
|
"mode",
|
|
@@ -612,10 +619,45 @@ export function validateConfigWriteBody(body) {
|
|
|
612
619
|
}
|
|
613
620
|
if ("backend" in epr) {
|
|
614
621
|
if (typeof epr.backend !== "string" || !CONFIG_REVIEW_BACKENDS.includes(epr.backend)) {
|
|
615
|
-
return { ok: false, error: "externalPlanReview.backend must be
|
|
622
|
+
return { ok: false, error: "externalPlanReview.backend must be auto, claude, or cursor" };
|
|
616
623
|
}
|
|
617
624
|
eprPatch.backend = epr.backend;
|
|
618
625
|
}
|
|
626
|
+
if ("reviewerModel" in epr) {
|
|
627
|
+
if (
|
|
628
|
+
typeof epr.reviewerModel !== "string" ||
|
|
629
|
+
!REVIEW_MODEL_ID.test(epr.reviewerModel.trim())
|
|
630
|
+
) {
|
|
631
|
+
return { ok: false, error: "externalPlanReview.reviewerModel must be a model id" };
|
|
632
|
+
}
|
|
633
|
+
eprPatch.reviewerModel = epr.reviewerModel.trim();
|
|
634
|
+
}
|
|
635
|
+
if ("advisorModel" in epr) {
|
|
636
|
+
if (typeof epr.advisorModel !== "string" || !REVIEW_MODEL_ID.test(epr.advisorModel.trim())) {
|
|
637
|
+
return { ok: false, error: "externalPlanReview.advisorModel must be a model id" };
|
|
638
|
+
}
|
|
639
|
+
eprPatch.advisorModel = epr.advisorModel.trim();
|
|
640
|
+
}
|
|
641
|
+
if ("waitSliceSeconds" in epr) {
|
|
642
|
+
const n = epr.waitSliceSeconds;
|
|
643
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 3600) {
|
|
644
|
+
return {
|
|
645
|
+
ok: false,
|
|
646
|
+
error: "externalPlanReview.waitSliceSeconds must be an integer 1..3600",
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
eprPatch.waitSliceSeconds = n;
|
|
650
|
+
}
|
|
651
|
+
if ("waitTimeoutSeconds" in epr) {
|
|
652
|
+
const n = epr.waitTimeoutSeconds;
|
|
653
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 86400) {
|
|
654
|
+
return {
|
|
655
|
+
ok: false,
|
|
656
|
+
error: "externalPlanReview.waitTimeoutSeconds must be an integer 1..86400",
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
eprPatch.waitTimeoutSeconds = n;
|
|
660
|
+
}
|
|
619
661
|
if ("autoRemediate" in epr) {
|
|
620
662
|
if (typeof epr.autoRemediate !== "boolean") {
|
|
621
663
|
return { ok: false, error: "externalPlanReview.autoRemediate must be boolean" };
|
|
@@ -675,12 +717,17 @@ export function validateConfigWriteBody(body) {
|
|
|
675
717
|
if (!modes || typeof modes !== "object" || Array.isArray(modes)) {
|
|
676
718
|
return { ok: false, error: "agentPersona.modes must be an object" };
|
|
677
719
|
}
|
|
678
|
-
/** @type {Record<string, string>} */
|
|
720
|
+
/** @type {Record<string, string | null>} */
|
|
679
721
|
const modesPatch = {};
|
|
680
722
|
for (const [mode, persona] of Object.entries(modes)) {
|
|
681
723
|
if (!CONFIG_PERSONA_MODES.includes(mode)) {
|
|
682
724
|
return { ok: false, error: `unknown agentPersona.modes key: ${mode}` };
|
|
683
725
|
}
|
|
726
|
+
// null clears an existing mode override (Inherit default).
|
|
727
|
+
if (persona === null) {
|
|
728
|
+
modesPatch[mode] = null;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
684
731
|
if (typeof persona !== "string" || !CONFIG_PERSONA_IDS.includes(persona)) {
|
|
685
732
|
return { ok: false, error: `agentPersona.modes.${mode} must be a builtin persona id` };
|
|
686
733
|
}
|
|
@@ -767,7 +814,15 @@ export function mergeConfigAllowlist(existing, patch) {
|
|
|
767
814
|
prev.modes && typeof prev.modes === "object" && !Array.isArray(prev.modes)
|
|
768
815
|
? { ...prev.modes }
|
|
769
816
|
: {};
|
|
770
|
-
|
|
817
|
+
const nextModes = { ...prevModes };
|
|
818
|
+
for (const [mode, persona] of Object.entries(patch.agentPersona.modes)) {
|
|
819
|
+
if (persona === null) {
|
|
820
|
+
delete nextModes[mode];
|
|
821
|
+
} else {
|
|
822
|
+
nextModes[mode] = persona;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
next.modes = nextModes;
|
|
771
826
|
}
|
|
772
827
|
base.agentPersona = next;
|
|
773
828
|
}
|
|
@@ -834,6 +889,18 @@ export function allowlistConfig(raw) {
|
|
|
834
889
|
if (typeof raw.externalPlanReview.preflight === "string") {
|
|
835
890
|
epr.preflight = truncateStr(raw.externalPlanReview.preflight, 16);
|
|
836
891
|
}
|
|
892
|
+
if (typeof raw.externalPlanReview.reviewerModel === "string") {
|
|
893
|
+
epr.reviewerModel = truncateStr(raw.externalPlanReview.reviewerModel, 64);
|
|
894
|
+
}
|
|
895
|
+
if (typeof raw.externalPlanReview.advisorModel === "string") {
|
|
896
|
+
epr.advisorModel = truncateStr(raw.externalPlanReview.advisorModel, 64);
|
|
897
|
+
}
|
|
898
|
+
if (typeof raw.externalPlanReview.waitSliceSeconds === "number") {
|
|
899
|
+
epr.waitSliceSeconds = raw.externalPlanReview.waitSliceSeconds;
|
|
900
|
+
}
|
|
901
|
+
if (typeof raw.externalPlanReview.waitTimeoutSeconds === "number") {
|
|
902
|
+
epr.waitTimeoutSeconds = raw.externalPlanReview.waitTimeoutSeconds;
|
|
903
|
+
}
|
|
837
904
|
summary.externalPlanReview = epr;
|
|
838
905
|
}
|
|
839
906
|
if (raw.agentPersona && typeof raw.agentPersona === "object") {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import type { readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
export const OS_DEFAULT_TOKENS: Set<string>;
|
|
5
|
+
export function isSafePreferredBrowser(value: string): boolean;
|
|
6
|
+
export function shouldSkipOpen(env?: NodeJS.ProcessEnv): boolean;
|
|
7
|
+
export function normalizePreferredBrowser(value: unknown): string | null;
|
|
8
|
+
export function resolvePreferredBrowser(opts?: {
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
configValue?: unknown;
|
|
11
|
+
}): string | null;
|
|
12
|
+
export function readPreferredBrowserFromConfig(
|
|
13
|
+
configPath: string,
|
|
14
|
+
fsHooks?: { readFileSync?: typeof readFileSync },
|
|
15
|
+
): unknown;
|
|
16
|
+
export function buildOpenBrowserCommand(opts: {
|
|
17
|
+
url: string;
|
|
18
|
+
preferred?: string | null;
|
|
19
|
+
platform?: NodeJS.Platform;
|
|
20
|
+
}): { command: string; args: string[] } | null;
|
|
21
|
+
export function openBrowser(
|
|
22
|
+
url: string,
|
|
23
|
+
options?: {
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
preferred?: string | null;
|
|
26
|
+
configValue?: unknown;
|
|
27
|
+
platform?: NodeJS.Platform;
|
|
28
|
+
spawnFn?: typeof spawn;
|
|
29
|
+
spawnSyncFn?: typeof spawnSync;
|
|
30
|
+
},
|
|
31
|
+
): { opened: boolean; reason?: string; command?: string; args?: string[] };
|