@gilbertgt/dsh-plan-orchestrator 1.0.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 +9 -0
- package/README.md +32 -0
- package/compatibility.json +9 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +1242 -0
- package/lib/github-Cy4Pm35G.js +577 -0
- package/lib/index.js +3776 -0
- package/lib/planner-route-B9z5GziX.js +101 -0
- package/lib/publication-D352itYY.js +137 -0
- package/lib/rpc-server-CGs2ndXP.js +131 -0
- package/package.json +144 -0
- package/profiles/reviewer.cordis.yml +5 -0
- package/profiles/worker.cordis.yml +5 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1242 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@gilbertgt/dsh-plan-orchestrator",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __create = Object.create;
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
12
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
16
|
+
key = keys[i];
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
25
|
+
value: mod,
|
|
26
|
+
enumerable: true
|
|
27
|
+
}) : target, mod));
|
|
28
|
+
//#endregion
|
|
29
|
+
let react = require("react");
|
|
30
|
+
react = __toESM(react, 1);
|
|
31
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
32
|
+
//#region src/client/locales.ts
|
|
33
|
+
const zh = {
|
|
34
|
+
nav: "Plan Mode",
|
|
35
|
+
overview: "總覽",
|
|
36
|
+
roles: "角色模型",
|
|
37
|
+
planning: "規劃",
|
|
38
|
+
execution: "執行",
|
|
39
|
+
review: "審查",
|
|
40
|
+
recovery: "恢復",
|
|
41
|
+
external: "External Issue",
|
|
42
|
+
diagnostics: "進階/診斷",
|
|
43
|
+
loading: "載入中…",
|
|
44
|
+
save: "儲存",
|
|
45
|
+
stop: "停止",
|
|
46
|
+
resume: "安全恢復"
|
|
47
|
+
};
|
|
48
|
+
const en = {
|
|
49
|
+
nav: "Plan Mode",
|
|
50
|
+
overview: "Overview",
|
|
51
|
+
roles: "Roles",
|
|
52
|
+
planning: "Planning",
|
|
53
|
+
execution: "Execution",
|
|
54
|
+
review: "Review",
|
|
55
|
+
recovery: "Recovery",
|
|
56
|
+
external: "External Issue",
|
|
57
|
+
diagnostics: "Diagnostics",
|
|
58
|
+
loading: "Loading…",
|
|
59
|
+
save: "Save",
|
|
60
|
+
stop: "Stop",
|
|
61
|
+
resume: "Resume safely"
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/client/styles.ts
|
|
65
|
+
const STYLE = `
|
|
66
|
+
.planx{padding:18px;max-width:1180px;color:inherit}
|
|
67
|
+
.planx h2,.planx h3{margin:.2rem 0 .8rem}
|
|
68
|
+
.planx-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:16px}
|
|
69
|
+
.planx-tabs button,.planx button{padding:7px 10px;border-radius:8px;border:1px solid color-mix(in srgb,currentColor 18%,transparent);background:transparent;color:inherit;cursor:pointer}
|
|
70
|
+
.planx-tabs button[aria-selected="true"]{font-weight:700;outline:2px solid color-mix(in srgb,currentColor 35%,transparent)}
|
|
71
|
+
.planx-card{border:1px solid color-mix(in srgb,currentColor 15%,transparent);border-radius:12px;padding:14px;margin:10px 0;min-width:0}
|
|
72
|
+
.planx-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:10px}
|
|
73
|
+
.planx-muted{opacity:.72}.planx-error{border-left:3px solid currentColor;padding:8px;margin:8px 0}
|
|
74
|
+
.planx-chip{border-radius:999px!important;padding:4px 9px!important;white-space:nowrap}
|
|
75
|
+
.planx-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.planx-between{justify-content:space-between}
|
|
76
|
+
.planx label{display:grid;gap:4px;margin:8px 0}.planx input,.planx select{background:transparent;color:inherit;border:1px solid color-mix(in srgb,currentColor 20%,transparent);border-radius:7px;padding:7px;min-width:0}
|
|
77
|
+
.planx input[type="checkbox"]{width:auto}.planx pre{white-space:pre-wrap;word-break:break-word;max-height:320px;overflow:auto}
|
|
78
|
+
.planx-overlay{position:fixed;inset:0;background:rgba(0,0,0,.38);display:flex;justify-content:flex-end;z-index:9999}
|
|
79
|
+
.planx-dialog{width:min(720px,92vw);height:100%;overflow:auto;background:Canvas;color:CanvasText;padding:20px;box-shadow:0 0 30px rgba(0,0,0,.25)}
|
|
80
|
+
.planx-kv{display:grid;grid-template-columns:minmax(120px,1fr) 2fr;gap:6px 12px}.planx-kv>span:nth-child(odd){opacity:.72}
|
|
81
|
+
.planx progress{width:100%}
|
|
82
|
+
@media (prefers-color-scheme: dark){.planx-dialog{background:#161616;color:#f2f2f2}}
|
|
83
|
+
`;
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/client/rpc-client.ts
|
|
86
|
+
/**
|
|
87
|
+
* Call the Host-owned exact `/api/plan-orchestrator/*` routes. DSH's web
|
|
88
|
+
* carrier authenticates these requests with the browser session cookie before
|
|
89
|
+
* dispatch, so this intentionally uses same-origin Fetch rather than the
|
|
90
|
+
* logical `ctx.connection.rpc` surface (which only exposes call/open in rc.1).
|
|
91
|
+
*/
|
|
92
|
+
function createRpc(fetcher = globalThis.fetch.bind(globalThis)) {
|
|
93
|
+
return async (method, body = {}, signal) => {
|
|
94
|
+
const response = await fetcher(`/api/plan-orchestrator/${encodeURIComponent(method)}`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
credentials: "same-origin",
|
|
97
|
+
headers: { "content-type": "application/json" },
|
|
98
|
+
body: JSON.stringify(body),
|
|
99
|
+
signal
|
|
100
|
+
});
|
|
101
|
+
let value;
|
|
102
|
+
try {
|
|
103
|
+
value = await response.json();
|
|
104
|
+
} catch {
|
|
105
|
+
throw new Error(`Plan Mode RPC returned invalid JSON (${response.status})`);
|
|
106
|
+
}
|
|
107
|
+
if (!response.ok || !value?.ok) throw new Error(value?.error?.message ?? `Plan Mode RPC failed (${response.status})`);
|
|
108
|
+
return value.data;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/client/components/Overview.tsx
|
|
113
|
+
function Overview({ diagnostics, settings }) {
|
|
114
|
+
const caps = [
|
|
115
|
+
["Native Plan Mode", diagnostics?.nativePlanMode],
|
|
116
|
+
["SDK parallel backend", diagnostics?.sdkAvailable],
|
|
117
|
+
["Git", diagnostics?.gitAvailable],
|
|
118
|
+
["LSP", diagnostics?.lspAvailable],
|
|
119
|
+
["gh", diagnostics?.ghAvailable],
|
|
120
|
+
["Settings writable", diagnostics?.settingsWritable]
|
|
121
|
+
];
|
|
122
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
123
|
+
className: "planx-grid",
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
126
|
+
className: "planx-card",
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Compatibility" }),
|
|
129
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: ["DSH: ", diagnostics?.dshVersion ?? "detecting"] }),
|
|
130
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: ["Plugin: ", diagnostics?.pluginVersion ?? "1.0.0"] }),
|
|
131
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "Supported: 0.1.5-rc.1" }),
|
|
132
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "Preview: 0.1.5-rc.2" })
|
|
133
|
+
]
|
|
134
|
+
}),
|
|
135
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
136
|
+
className: "planx-card",
|
|
137
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Capabilities" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
138
|
+
className: "planx-kv",
|
|
139
|
+
children: caps.flatMap(([name, value]) => [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: name }, `${name}-n`), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: value === void 0 ? "—" : value ? "Available" : "Unavailable" }, `${name}-v`)])
|
|
140
|
+
})]
|
|
141
|
+
}),
|
|
142
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
143
|
+
className: "planx-card",
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Safety" }),
|
|
146
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: ["Plan Orchestrator: ", settings?.enabled ? "enabled" : "disabled"] }),
|
|
147
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: ["Strict Planner read-only: ", settings?.planning?.strictReadOnly ? "on" : "off"] }),
|
|
148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "Ownership / trusted validation: mandatory" }),
|
|
149
|
+
diagnostics?.readOnlyDegraded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
150
|
+
role: "alert",
|
|
151
|
+
children: ["Degraded: ", diagnostics.readOnlyDegraded]
|
|
152
|
+
})
|
|
153
|
+
]
|
|
154
|
+
})
|
|
155
|
+
]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/client/components/Roles.tsx
|
|
160
|
+
const roleNames = [
|
|
161
|
+
"planner",
|
|
162
|
+
"worker",
|
|
163
|
+
"integrator",
|
|
164
|
+
"reviewer"
|
|
165
|
+
];
|
|
166
|
+
function Roles({ settings, catalog, onRole, rpc }) {
|
|
167
|
+
const [errors, setErrors] = (0, react.useState)({});
|
|
168
|
+
const validate = async (role, route) => {
|
|
169
|
+
if (route.mode !== "fixed" || !route.provider || !route.model) {
|
|
170
|
+
setErrors((e) => ({
|
|
171
|
+
...e,
|
|
172
|
+
[role]: ""
|
|
173
|
+
}));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await rpc("route-validate", { route });
|
|
178
|
+
setErrors((e) => ({
|
|
179
|
+
...e,
|
|
180
|
+
[role]: ""
|
|
181
|
+
}));
|
|
182
|
+
} catch (e) {
|
|
183
|
+
setErrors((x) => ({
|
|
184
|
+
...x,
|
|
185
|
+
[role]: e.message
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
190
|
+
className: "planx-grid",
|
|
191
|
+
children: roleNames.map((role) => {
|
|
192
|
+
const r = settings?.roles?.[role] ?? {
|
|
193
|
+
mode: "current",
|
|
194
|
+
fallbacks: []
|
|
195
|
+
};
|
|
196
|
+
const providers = (catalog ?? []).map((x) => x.provider);
|
|
197
|
+
const models = (catalog ?? []).find((x) => x.provider.id === r.provider)?.models ?? [];
|
|
198
|
+
const change = (next) => {
|
|
199
|
+
onRole(role, next);
|
|
200
|
+
validate(role, next);
|
|
201
|
+
};
|
|
202
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
203
|
+
className: "planx-card",
|
|
204
|
+
children: [
|
|
205
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: role[0].toUpperCase() + role.slice(1) }),
|
|
206
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Mode ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
207
|
+
value: r.mode,
|
|
208
|
+
onChange: (e) => change(e.target.value === "current" ? {
|
|
209
|
+
mode: "current",
|
|
210
|
+
fallbacks: r.fallbacks ?? []
|
|
211
|
+
} : {
|
|
212
|
+
...r,
|
|
213
|
+
mode: "fixed"
|
|
214
|
+
}),
|
|
215
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
216
|
+
value: "current",
|
|
217
|
+
children: "Current Session"
|
|
218
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
219
|
+
value: "fixed",
|
|
220
|
+
children: "Fixed"
|
|
221
|
+
})]
|
|
222
|
+
})] }),
|
|
223
|
+
r.mode === "fixed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
224
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Provider ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
225
|
+
value: r.provider ?? "",
|
|
226
|
+
onChange: (e) => change({
|
|
227
|
+
...r,
|
|
228
|
+
provider: e.target.value,
|
|
229
|
+
model: ""
|
|
230
|
+
}),
|
|
231
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
232
|
+
value: "",
|
|
233
|
+
children: "Select…"
|
|
234
|
+
}), providers.map((p) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
235
|
+
value: p.id,
|
|
236
|
+
children: p.name ?? p.id
|
|
237
|
+
}, p.id))]
|
|
238
|
+
})] }),
|
|
239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Model ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
240
|
+
value: r.model ?? "",
|
|
241
|
+
onChange: (e) => change({
|
|
242
|
+
...r,
|
|
243
|
+
model: e.target.value
|
|
244
|
+
}),
|
|
245
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
246
|
+
value: "",
|
|
247
|
+
children: "Select…"
|
|
248
|
+
}), models.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
249
|
+
value: m.id,
|
|
250
|
+
children: m.name ?? m.id
|
|
251
|
+
}, m.id))]
|
|
252
|
+
})] }),
|
|
253
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Reasoning effort ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
254
|
+
value: r.reasoningEffort ?? "",
|
|
255
|
+
onBlur: () => void validate(role, r),
|
|
256
|
+
onChange: (e) => onRole(role, {
|
|
257
|
+
...r,
|
|
258
|
+
reasoningEffort: e.target.value || void 0
|
|
259
|
+
})
|
|
260
|
+
})] }),
|
|
261
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Max Tokens ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
262
|
+
type: "number",
|
|
263
|
+
min: 1,
|
|
264
|
+
value: r.maxTokens ?? "",
|
|
265
|
+
onBlur: () => void validate(role, r),
|
|
266
|
+
onChange: (e) => onRole(role, {
|
|
267
|
+
...r,
|
|
268
|
+
maxTokens: e.target.value ? Number(e.target.value) : void 0
|
|
269
|
+
})
|
|
270
|
+
})] })
|
|
271
|
+
] }),
|
|
272
|
+
errors[role] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
273
|
+
className: "planx-error",
|
|
274
|
+
role: "alert",
|
|
275
|
+
children: errors[role]
|
|
276
|
+
}),
|
|
277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
278
|
+
className: "planx-muted",
|
|
279
|
+
children: "Fallbacks remain host-validated at execution preflight."
|
|
280
|
+
})
|
|
281
|
+
]
|
|
282
|
+
}, role);
|
|
283
|
+
})
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/client/components/Planning.tsx
|
|
288
|
+
function Planning({ settings, patch }) {
|
|
289
|
+
const p = settings.planning;
|
|
290
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
291
|
+
className: "planx-card",
|
|
292
|
+
children: [
|
|
293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Planning" }),
|
|
294
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
295
|
+
type: "checkbox",
|
|
296
|
+
checked: p.adaptiveResearch,
|
|
297
|
+
onChange: (e) => patch({ adaptiveResearch: e.target.checked })
|
|
298
|
+
}), " Adaptive Research"] }) }),
|
|
299
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
300
|
+
type: "checkbox",
|
|
301
|
+
checked: p.strictReadOnly,
|
|
302
|
+
onChange: (e) => patch({ strictReadOnly: e.target.checked })
|
|
303
|
+
}), " Strict Planner read-only"] }) }),
|
|
304
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Initial read-file budget ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
305
|
+
type: "number",
|
|
306
|
+
min: 1,
|
|
307
|
+
max: 32,
|
|
308
|
+
value: p.maxInitialReadFiles,
|
|
309
|
+
onChange: (e) => patch({ maxInitialReadFiles: Number(e.target.value) })
|
|
310
|
+
})] }),
|
|
311
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Soft input-token budget ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
312
|
+
type: "number",
|
|
313
|
+
min: 1e3,
|
|
314
|
+
value: p.softInputTokens,
|
|
315
|
+
onChange: (e) => patch({ softInputTokens: Number(e.target.value) })
|
|
316
|
+
})] }),
|
|
317
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
318
|
+
type: "checkbox",
|
|
319
|
+
checked: p.progressiveDiscovery,
|
|
320
|
+
onChange: (e) => patch({ progressiveDiscovery: e.target.checked })
|
|
321
|
+
}), " Progressive discovery"] }) }),
|
|
322
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
323
|
+
type: "checkbox",
|
|
324
|
+
checked: p.requireExpansionReason,
|
|
325
|
+
onChange: (e) => patch({ requireExpansionReason: e.target.checked })
|
|
326
|
+
}), " Require reason before context expansion"] }) })
|
|
327
|
+
]
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/client/components/Execution.tsx
|
|
332
|
+
function Execution({ settings, patch }) {
|
|
333
|
+
const e = settings.execution;
|
|
334
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
335
|
+
className: "planx-card",
|
|
336
|
+
children: [
|
|
337
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Execution" }),
|
|
338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Parallel mode ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
339
|
+
value: e.parallelMode,
|
|
340
|
+
onChange: (x) => patch({ parallelMode: x.target.value }),
|
|
341
|
+
children: [
|
|
342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
343
|
+
value: "auto",
|
|
344
|
+
children: "Auto"
|
|
345
|
+
}),
|
|
346
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
347
|
+
value: "serial",
|
|
348
|
+
children: "Serial"
|
|
349
|
+
}),
|
|
350
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
351
|
+
value: "worktree",
|
|
352
|
+
children: "Worktree when safe"
|
|
353
|
+
})
|
|
354
|
+
]
|
|
355
|
+
})] }),
|
|
356
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Max parallel Workers ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
357
|
+
type: "number",
|
|
358
|
+
min: 1,
|
|
359
|
+
max: 8,
|
|
360
|
+
value: e.maxParallelWorkers,
|
|
361
|
+
onChange: (x) => patch({ maxParallelWorkers: Number(x.target.value) })
|
|
362
|
+
})] }),
|
|
363
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["SDK child profile ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
364
|
+
value: e.sdkProfile,
|
|
365
|
+
onChange: (x) => patch({ sdkProfile: x.target.value })
|
|
366
|
+
})] }),
|
|
367
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Mutation role timeout (ms) ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
368
|
+
type: "number",
|
|
369
|
+
min: 1e3,
|
|
370
|
+
max: 36e5,
|
|
371
|
+
value: e.roleTimeoutMs,
|
|
372
|
+
onChange: (x) => patch({ roleTimeoutMs: Number(x.target.value) })
|
|
373
|
+
})] }),
|
|
374
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
375
|
+
type: "checkbox",
|
|
376
|
+
checked: e.keepFailedWorktrees,
|
|
377
|
+
onChange: (x) => patch({ keepFailedWorktrees: x.target.checked })
|
|
378
|
+
}), " Retain failed worktrees for evidence"] }) }),
|
|
379
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
380
|
+
className: "planx-muted",
|
|
381
|
+
children: "The timeout is enforced for native/SDK Worker, Integrator and targeted-fix mutation roles. Explicit ownership is always enforced and cannot be disabled."
|
|
382
|
+
})
|
|
383
|
+
]
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/client/components/Review.tsx
|
|
388
|
+
function Review({ settings, patch }) {
|
|
389
|
+
const r = settings.review;
|
|
390
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
391
|
+
className: "planx-card",
|
|
392
|
+
children: [
|
|
393
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Review" }),
|
|
394
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Maximum targeted-fix rounds ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
395
|
+
type: "number",
|
|
396
|
+
min: 0,
|
|
397
|
+
max: 5,
|
|
398
|
+
value: r.maxReviewRounds,
|
|
399
|
+
onChange: (e) => patch({ maxReviewRounds: Number(e.target.value) })
|
|
400
|
+
})] }),
|
|
401
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Reviewer protocol retry ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
402
|
+
value: r.protocolRetry,
|
|
403
|
+
onChange: (e) => patch({ protocolRetry: Number(e.target.value) }),
|
|
404
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
405
|
+
value: 0,
|
|
406
|
+
children: "0"
|
|
407
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
408
|
+
value: 1,
|
|
409
|
+
children: "1"
|
|
410
|
+
})]
|
|
411
|
+
})] }),
|
|
412
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["Per-stream output cap (bytes) ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
413
|
+
type: "number",
|
|
414
|
+
min: 1024,
|
|
415
|
+
max: 16777216,
|
|
416
|
+
value: r.outputCapBytes,
|
|
417
|
+
onChange: (e) => patch({ outputCapBytes: Number(e.target.value) })
|
|
418
|
+
})] }),
|
|
419
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
420
|
+
className: "planx-muted",
|
|
421
|
+
children: "Trusted host validation is mandatory. Worker self-reports are never accepted as proof."
|
|
422
|
+
})
|
|
423
|
+
]
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/client/components/Recovery.tsx
|
|
428
|
+
function Recovery({ rpc, allowSafeResume }) {
|
|
429
|
+
const [runs, setRuns] = (0, react.useState)([]), [error, setError] = (0, react.useState)("");
|
|
430
|
+
const load = () => rpc("run-list", {}).then((r) => {
|
|
431
|
+
setRuns(r);
|
|
432
|
+
setError("");
|
|
433
|
+
}).catch((e) => setError(e.message));
|
|
434
|
+
(0, react.useEffect)(() => {
|
|
435
|
+
load();
|
|
436
|
+
}, []);
|
|
437
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
438
|
+
className: "planx-card",
|
|
439
|
+
children: [
|
|
440
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Recovery" }),
|
|
441
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
442
|
+
role: "alert",
|
|
443
|
+
children: error
|
|
444
|
+
}),
|
|
445
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
446
|
+
className: "planx-muted",
|
|
447
|
+
children: "Resume is available only when Safe Resume is enabled and an INTERRUPTED run's checkpoint still matches HEAD, ownership and working-tree fingerprint. No reset/clean/stash recovery exists."
|
|
448
|
+
}),
|
|
449
|
+
runs.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "No loaded runs." }) : runs.slice(0, 20).map((run) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
450
|
+
className: "planx-row",
|
|
451
|
+
children: [
|
|
452
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: run.runId.slice(0, 8) }),
|
|
453
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: run.phase }),
|
|
454
|
+
run.phase === "INTERRUPTED" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
455
|
+
disabled: !allowSafeResume,
|
|
456
|
+
title: !allowSafeResume ? "Safe Resume is disabled in Plan Mode settings." : void 0,
|
|
457
|
+
onClick: async () => {
|
|
458
|
+
const v = await rpc("run-resume", { runId: run.runId });
|
|
459
|
+
if (!v.ok) setError(v.reason);
|
|
460
|
+
await load();
|
|
461
|
+
},
|
|
462
|
+
children: "Resume safely"
|
|
463
|
+
}),
|
|
464
|
+
[
|
|
465
|
+
"COMPLETE",
|
|
466
|
+
"FAILED",
|
|
467
|
+
"BLOCKED",
|
|
468
|
+
"CANCELLED"
|
|
469
|
+
].includes(run.phase) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
470
|
+
onClick: async () => {
|
|
471
|
+
if (confirm("Cleanup this terminal run?")) {
|
|
472
|
+
await rpc("run-cleanup", { runId: run.runId });
|
|
473
|
+
await load();
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
children: "Cleanup"
|
|
477
|
+
})
|
|
478
|
+
]
|
|
479
|
+
}, run.runId))
|
|
480
|
+
]
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
//#endregion
|
|
484
|
+
//#region src/client/components/ExternalIssue.tsx
|
|
485
|
+
function ExternalIssue({ settings, patch }) {
|
|
486
|
+
const x = settings.externalIssue;
|
|
487
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
488
|
+
className: "planx-card",
|
|
489
|
+
children: [
|
|
490
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "External Issue" }),
|
|
491
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
492
|
+
type: "checkbox",
|
|
493
|
+
checked: x.enabled,
|
|
494
|
+
onChange: (e) => patch({ enabled: e.target.checked })
|
|
495
|
+
}), " Enable /plan-issue"] }) }),
|
|
496
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
497
|
+
type: "checkbox",
|
|
498
|
+
checked: x.publishAfterPass,
|
|
499
|
+
disabled: !x.enabled,
|
|
500
|
+
onChange: (e) => patch({ publishAfterPass: e.target.checked })
|
|
501
|
+
}), " After Reviewer PASS, commit/push/create or reuse PR"] }) }),
|
|
502
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
503
|
+
className: "planx-muted",
|
|
504
|
+
children: "Publication remains fail-closed: trusted revision, repo match, current validation receipts and Reviewer PASS are required. Force push and auto-merge are never used."
|
|
505
|
+
})
|
|
506
|
+
]
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/client/components/Diagnostics.tsx
|
|
511
|
+
function Diagnostics({ diagnostics }) {
|
|
512
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
513
|
+
className: "planx-card",
|
|
514
|
+
children: [
|
|
515
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: "Diagnostics" }),
|
|
516
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(diagnostics ?? {}, null, 2) }),
|
|
517
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
518
|
+
className: "planx-muted",
|
|
519
|
+
children: "Diagnostics intentionally expose capability state and plugin storage location, never credentials or raw validation logs."
|
|
520
|
+
})
|
|
521
|
+
]
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/contract/settings.ts
|
|
526
|
+
const SETTINGS_NAMESPACE = "plan-orchestrator";
|
|
527
|
+
const ROLE_NAMES = [
|
|
528
|
+
"planner",
|
|
529
|
+
"worker",
|
|
530
|
+
"integrator",
|
|
531
|
+
"reviewer"
|
|
532
|
+
];
|
|
533
|
+
const SETTINGS_KEYS = /* @__PURE__ */ new Set([
|
|
534
|
+
"enabled",
|
|
535
|
+
"roles",
|
|
536
|
+
"planning",
|
|
537
|
+
"execution",
|
|
538
|
+
"review",
|
|
539
|
+
"recovery",
|
|
540
|
+
"externalIssue",
|
|
541
|
+
"workspaceOverrides"
|
|
542
|
+
]);
|
|
543
|
+
const ROUTE_KEYS = /* @__PURE__ */ new Set([
|
|
544
|
+
"mode",
|
|
545
|
+
"provider",
|
|
546
|
+
"model",
|
|
547
|
+
"reasoningEffort",
|
|
548
|
+
"maxTokens",
|
|
549
|
+
"fallbacks"
|
|
550
|
+
]);
|
|
551
|
+
const CHOICE_KEYS = /* @__PURE__ */ new Set([
|
|
552
|
+
"provider",
|
|
553
|
+
"model",
|
|
554
|
+
"reasoningEffort",
|
|
555
|
+
"maxTokens"
|
|
556
|
+
]);
|
|
557
|
+
const PLANNING_KEYS = /* @__PURE__ */ new Set([
|
|
558
|
+
"adaptiveResearch",
|
|
559
|
+
"strictReadOnly",
|
|
560
|
+
"maxInitialReadFiles",
|
|
561
|
+
"softInputTokens",
|
|
562
|
+
"progressiveDiscovery",
|
|
563
|
+
"requireExpansionReason"
|
|
564
|
+
]);
|
|
565
|
+
const EXECUTION_KEYS = /* @__PURE__ */ new Set([
|
|
566
|
+
"maxParallelWorkers",
|
|
567
|
+
"parallelMode",
|
|
568
|
+
"requireExplicitOwnership",
|
|
569
|
+
"keepFailedWorktrees",
|
|
570
|
+
"sdkProfile",
|
|
571
|
+
"roleTimeoutMs"
|
|
572
|
+
]);
|
|
573
|
+
const REVIEW_KEYS = /* @__PURE__ */ new Set([
|
|
574
|
+
"maxReviewRounds",
|
|
575
|
+
"protocolRetry",
|
|
576
|
+
"trustedValidation",
|
|
577
|
+
"outputCapBytes"
|
|
578
|
+
]);
|
|
579
|
+
const RECOVERY_KEYS = /* @__PURE__ */ new Set(["allowSafeResume"]);
|
|
580
|
+
const EXTERNAL_KEYS = /* @__PURE__ */ new Set(["enabled", "publishAfterPass"]);
|
|
581
|
+
const WORKSPACE_KEYS = /* @__PURE__ */ new Set(["roles"]);
|
|
582
|
+
const defaultRoute = () => ({
|
|
583
|
+
mode: "current",
|
|
584
|
+
fallbacks: []
|
|
585
|
+
});
|
|
586
|
+
Object.freeze({
|
|
587
|
+
enabled: true,
|
|
588
|
+
roles: {
|
|
589
|
+
planner: defaultRoute(),
|
|
590
|
+
worker: defaultRoute(),
|
|
591
|
+
integrator: defaultRoute(),
|
|
592
|
+
reviewer: defaultRoute()
|
|
593
|
+
},
|
|
594
|
+
planning: {
|
|
595
|
+
adaptiveResearch: true,
|
|
596
|
+
strictReadOnly: true,
|
|
597
|
+
maxInitialReadFiles: 6,
|
|
598
|
+
softInputTokens: 2e4,
|
|
599
|
+
progressiveDiscovery: true,
|
|
600
|
+
requireExpansionReason: true
|
|
601
|
+
},
|
|
602
|
+
execution: {
|
|
603
|
+
maxParallelWorkers: 3,
|
|
604
|
+
parallelMode: "auto",
|
|
605
|
+
requireExplicitOwnership: true,
|
|
606
|
+
keepFailedWorktrees: true,
|
|
607
|
+
sdkProfile: "sdk",
|
|
608
|
+
roleTimeoutMs: 9e5
|
|
609
|
+
},
|
|
610
|
+
review: {
|
|
611
|
+
maxReviewRounds: 2,
|
|
612
|
+
protocolRetry: 1,
|
|
613
|
+
trustedValidation: true,
|
|
614
|
+
outputCapBytes: 16 * 1024 * 1024
|
|
615
|
+
},
|
|
616
|
+
recovery: { allowSafeResume: true },
|
|
617
|
+
externalIssue: {
|
|
618
|
+
enabled: false,
|
|
619
|
+
publishAfterPass: false
|
|
620
|
+
},
|
|
621
|
+
workspaceOverrides: {}
|
|
622
|
+
});
|
|
623
|
+
function object(value, where) {
|
|
624
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${where} must be an object`);
|
|
625
|
+
return value;
|
|
626
|
+
}
|
|
627
|
+
function strictKeys(value, allowed, where) {
|
|
628
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
629
|
+
if (unknown.length > 0) throw new Error(`${where} has unknown field(s): ${unknown.join(", ")}`);
|
|
630
|
+
}
|
|
631
|
+
function bool(value, where) {
|
|
632
|
+
if (typeof value !== "boolean") throw new Error(`${where} must be boolean`);
|
|
633
|
+
return value;
|
|
634
|
+
}
|
|
635
|
+
function integer(value, where, min, max) {
|
|
636
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) throw new Error(`${where} must be an integer from ${min} through ${max}`);
|
|
637
|
+
return value;
|
|
638
|
+
}
|
|
639
|
+
function optionalString(value, where, max = 256) {
|
|
640
|
+
if (value === void 0) return void 0;
|
|
641
|
+
if (typeof value !== "string" || value.length === 0 || value.length > max || value.includes("\0")) throw new Error(`${where} invalid`);
|
|
642
|
+
return value;
|
|
643
|
+
}
|
|
644
|
+
function routeChoice(value, where) {
|
|
645
|
+
const x = object(value, where);
|
|
646
|
+
strictKeys(x, CHOICE_KEYS, where);
|
|
647
|
+
const provider = optionalString(x.provider, `${where}.provider`);
|
|
648
|
+
const model = optionalString(x.model, `${where}.model`);
|
|
649
|
+
if (!provider || !model) throw new Error(`${where} needs provider and model`);
|
|
650
|
+
const reasoningEffort = optionalString(x.reasoningEffort, `${where}.reasoningEffort`, 64);
|
|
651
|
+
const maxTokens = x.maxTokens === void 0 ? void 0 : integer(x.maxTokens, `${where}.maxTokens`, 1, 2e6);
|
|
652
|
+
return {
|
|
653
|
+
provider,
|
|
654
|
+
model,
|
|
655
|
+
...reasoningEffort ? { reasoningEffort } : {},
|
|
656
|
+
...maxTokens ? { maxTokens } : {}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function roleRoute(value, where, workspace) {
|
|
660
|
+
const x = object(value, where);
|
|
661
|
+
strictKeys(x, ROUTE_KEYS, where);
|
|
662
|
+
const allowedModes = workspace ? [
|
|
663
|
+
"inherit",
|
|
664
|
+
"current",
|
|
665
|
+
"fixed"
|
|
666
|
+
] : ["current", "fixed"];
|
|
667
|
+
if (typeof x.mode !== "string" || !allowedModes.includes(x.mode)) throw new Error(`${where}.mode invalid`);
|
|
668
|
+
const mode = x.mode;
|
|
669
|
+
const provider = optionalString(x.provider, `${where}.provider`);
|
|
670
|
+
const model = optionalString(x.model, `${where}.model`);
|
|
671
|
+
const reasoningEffort = optionalString(x.reasoningEffort, `${where}.reasoningEffort`, 64);
|
|
672
|
+
const maxTokens = x.maxTokens === void 0 ? void 0 : integer(x.maxTokens, `${where}.maxTokens`, 1, 2e6);
|
|
673
|
+
if (mode === "fixed" && (!provider || !model)) throw new Error(`${where} fixed route needs provider and model`);
|
|
674
|
+
if (mode !== "fixed" && (provider !== void 0 || model !== void 0 || reasoningEffort !== void 0 || maxTokens !== void 0)) throw new Error(`${where} ${mode} route cannot carry fixed-route fields`);
|
|
675
|
+
if (!Array.isArray(x.fallbacks) || x.fallbacks.length > 8) throw new Error(`${where}.fallbacks invalid`);
|
|
676
|
+
return {
|
|
677
|
+
mode,
|
|
678
|
+
fallbacks: x.fallbacks.map((item, index) => routeChoice(item, `${where}.fallbacks[${index}]`)),
|
|
679
|
+
...provider ? { provider } : {},
|
|
680
|
+
...model ? { model } : {},
|
|
681
|
+
...reasoningEffort ? { reasoningEffort } : {},
|
|
682
|
+
...maxTokens ? { maxTokens } : {}
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function section(value, allowed, where) {
|
|
686
|
+
const x = object(value, where);
|
|
687
|
+
strictKeys(x, allowed, where);
|
|
688
|
+
return x;
|
|
689
|
+
}
|
|
690
|
+
function validateSettings(value) {
|
|
691
|
+
const x = object(value, "settings");
|
|
692
|
+
strictKeys(x, SETTINGS_KEYS, "settings");
|
|
693
|
+
const rolesRaw = object(x.roles, "settings.roles");
|
|
694
|
+
strictKeys(rolesRaw, new Set(ROLE_NAMES), "settings.roles");
|
|
695
|
+
const roles = Object.fromEntries(ROLE_NAMES.map((role) => [role, roleRoute(rolesRaw[role], `settings.roles.${role}`, false)]));
|
|
696
|
+
const planning = section(x.planning, PLANNING_KEYS, "settings.planning");
|
|
697
|
+
const execution = section(x.execution, EXECUTION_KEYS, "settings.execution");
|
|
698
|
+
const review = section(x.review, REVIEW_KEYS, "settings.review");
|
|
699
|
+
const recovery = section(x.recovery, RECOVERY_KEYS, "settings.recovery");
|
|
700
|
+
const externalIssue = section(x.externalIssue, EXTERNAL_KEYS, "settings.externalIssue");
|
|
701
|
+
const workspaceRaw = object(x.workspaceOverrides, "settings.workspaceOverrides");
|
|
702
|
+
const workspaceOverrides = {};
|
|
703
|
+
if (Object.keys(workspaceRaw).length > 256) throw new Error("settings.workspaceOverrides has too many entries");
|
|
704
|
+
for (const [workspaceKey, raw] of Object.entries(workspaceRaw)) {
|
|
705
|
+
if (!/^[A-Za-z0-9_-]{16,128}$/.test(workspaceKey)) throw new Error(`invalid workspace override key: ${workspaceKey}`);
|
|
706
|
+
const override = object(raw, `settings.workspaceOverrides.${workspaceKey}`);
|
|
707
|
+
strictKeys(override, WORKSPACE_KEYS, `settings.workspaceOverrides.${workspaceKey}`);
|
|
708
|
+
const result = {};
|
|
709
|
+
if (override.roles !== void 0) {
|
|
710
|
+
const roleObject = object(override.roles, `settings.workspaceOverrides.${workspaceKey}.roles`);
|
|
711
|
+
strictKeys(roleObject, new Set(ROLE_NAMES), `settings.workspaceOverrides.${workspaceKey}.roles`);
|
|
712
|
+
result.roles = {};
|
|
713
|
+
for (const [role, route] of Object.entries(roleObject)) result.roles[role] = roleRoute(route, `settings.workspaceOverrides.${workspaceKey}.roles.${role}`, true);
|
|
714
|
+
}
|
|
715
|
+
workspaceOverrides[workspaceKey] = result;
|
|
716
|
+
}
|
|
717
|
+
if (bool(execution.requireExplicitOwnership, "settings.execution.requireExplicitOwnership") !== true) throw new Error("settings.execution.requireExplicitOwnership is a security invariant and must be true");
|
|
718
|
+
if (bool(review.trustedValidation, "settings.review.trustedValidation") !== true) throw new Error("settings.review.trustedValidation is a security invariant and must be true");
|
|
719
|
+
const parallelMode = execution.parallelMode;
|
|
720
|
+
if (![
|
|
721
|
+
"auto",
|
|
722
|
+
"serial",
|
|
723
|
+
"worktree"
|
|
724
|
+
].includes(String(parallelMode))) throw new Error("settings.execution.parallelMode invalid");
|
|
725
|
+
return {
|
|
726
|
+
enabled: bool(x.enabled, "settings.enabled"),
|
|
727
|
+
roles,
|
|
728
|
+
planning: {
|
|
729
|
+
adaptiveResearch: bool(planning.adaptiveResearch, "settings.planning.adaptiveResearch"),
|
|
730
|
+
strictReadOnly: bool(planning.strictReadOnly, "settings.planning.strictReadOnly"),
|
|
731
|
+
maxInitialReadFiles: integer(planning.maxInitialReadFiles, "settings.planning.maxInitialReadFiles", 1, 32),
|
|
732
|
+
softInputTokens: integer(planning.softInputTokens, "settings.planning.softInputTokens", 1e3, 2e6),
|
|
733
|
+
progressiveDiscovery: bool(planning.progressiveDiscovery, "settings.planning.progressiveDiscovery"),
|
|
734
|
+
requireExpansionReason: bool(planning.requireExpansionReason, "settings.planning.requireExpansionReason")
|
|
735
|
+
},
|
|
736
|
+
execution: {
|
|
737
|
+
maxParallelWorkers: integer(execution.maxParallelWorkers, "settings.execution.maxParallelWorkers", 1, 8),
|
|
738
|
+
parallelMode,
|
|
739
|
+
requireExplicitOwnership: true,
|
|
740
|
+
keepFailedWorktrees: bool(execution.keepFailedWorktrees, "settings.execution.keepFailedWorktrees"),
|
|
741
|
+
sdkProfile: optionalString(execution.sdkProfile, "settings.execution.sdkProfile", 128),
|
|
742
|
+
roleTimeoutMs: integer(execution.roleTimeoutMs, "settings.execution.roleTimeoutMs", 1e3, 36e5)
|
|
743
|
+
},
|
|
744
|
+
review: {
|
|
745
|
+
maxReviewRounds: integer(review.maxReviewRounds, "settings.review.maxReviewRounds", 0, 5),
|
|
746
|
+
protocolRetry: integer(review.protocolRetry, "settings.review.protocolRetry", 0, 1),
|
|
747
|
+
trustedValidation: true,
|
|
748
|
+
outputCapBytes: integer(review.outputCapBytes, "settings.review.outputCapBytes", 1024, 16 * 1024 * 1024)
|
|
749
|
+
},
|
|
750
|
+
recovery: { allowSafeResume: bool(recovery.allowSafeResume, "settings.recovery.allowSafeResume") },
|
|
751
|
+
externalIssue: {
|
|
752
|
+
enabled: bool(externalIssue.enabled, "settings.externalIssue.enabled"),
|
|
753
|
+
publishAfterPass: bool(externalIssue.publishAfterPass, "settings.externalIssue.publishAfterPass")
|
|
754
|
+
},
|
|
755
|
+
workspaceOverrides
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region src/client/section.tsx
|
|
760
|
+
const tabs = [
|
|
761
|
+
["overview", "Overview"],
|
|
762
|
+
["roles", "Roles"],
|
|
763
|
+
["planning", "Planning"],
|
|
764
|
+
["execution", "Execution"],
|
|
765
|
+
["review", "Review"],
|
|
766
|
+
["recovery", "Recovery"],
|
|
767
|
+
["external", "ExternalIssue"],
|
|
768
|
+
["diagnostics", "Diagnostics"]
|
|
769
|
+
];
|
|
770
|
+
const settingFields = [
|
|
771
|
+
"enabled",
|
|
772
|
+
"roles",
|
|
773
|
+
"planning",
|
|
774
|
+
"execution",
|
|
775
|
+
"review",
|
|
776
|
+
"recovery",
|
|
777
|
+
"externalIssue",
|
|
778
|
+
"workspaceOverrides"
|
|
779
|
+
];
|
|
780
|
+
function PlanModeSection({ rpc, t, settingsScope }) {
|
|
781
|
+
const [tab, setTab] = (0, react.useState)("overview"), [diag, setDiag] = (0, react.useState)(), [settings, setSettings] = (0, react.useState)(), [catalog, setCatalog] = (0, react.useState)([]), [dirty, setDirty] = (0, react.useState)(false), [error, setError] = (0, react.useState)("");
|
|
782
|
+
const draftRevision = (0, react.useRef)();
|
|
783
|
+
(0, react.useEffect)(() => settingsScope.subscribe(() => {
|
|
784
|
+
const snap = settingsScope.getSnapshot();
|
|
785
|
+
if (!dirty && snap.status === "ready" && snap.value) {
|
|
786
|
+
setSettings(snap.value);
|
|
787
|
+
draftRevision.current = snap.revision;
|
|
788
|
+
}
|
|
789
|
+
}), [settingsScope, dirty]);
|
|
790
|
+
(0, react.useEffect)(() => {
|
|
791
|
+
const snap = settingsScope.getSnapshot();
|
|
792
|
+
if (snap.status === "ready" && snap.value) {
|
|
793
|
+
setSettings(snap.value);
|
|
794
|
+
draftRevision.current = snap.revision;
|
|
795
|
+
}
|
|
796
|
+
Promise.all([rpc("diagnostics"), rpc("model-catalog")]).then(([d, c]) => {
|
|
797
|
+
setDiag(d);
|
|
798
|
+
setCatalog(c);
|
|
799
|
+
}).catch((e) => setError(e.message));
|
|
800
|
+
}, [rpc, settingsScope]);
|
|
801
|
+
const mutate = (path, patch) => {
|
|
802
|
+
setSettings((s) => ({
|
|
803
|
+
...s,
|
|
804
|
+
[path]: {
|
|
805
|
+
...s[path],
|
|
806
|
+
...patch
|
|
807
|
+
}
|
|
808
|
+
}));
|
|
809
|
+
setDirty(true);
|
|
810
|
+
};
|
|
811
|
+
const onRole = (role, value) => {
|
|
812
|
+
setSettings((s) => ({
|
|
813
|
+
...s,
|
|
814
|
+
roles: {
|
|
815
|
+
...s.roles,
|
|
816
|
+
[role]: value
|
|
817
|
+
}
|
|
818
|
+
}));
|
|
819
|
+
setDirty(true);
|
|
820
|
+
};
|
|
821
|
+
const save = async () => {
|
|
822
|
+
try {
|
|
823
|
+
const checked = validateSettings(settings);
|
|
824
|
+
const ops = settingFields.map((field) => ({
|
|
825
|
+
op: "set",
|
|
826
|
+
path: [field],
|
|
827
|
+
value: checked[field]
|
|
828
|
+
}));
|
|
829
|
+
const expected = draftRevision.current;
|
|
830
|
+
await settingsScope.mutate(ops, expected);
|
|
831
|
+
const snap = settingsScope.getSnapshot();
|
|
832
|
+
if (snap.status !== "ready" || JSON.stringify(snap.value) !== JSON.stringify(checked)) throw new Error("Settings changed concurrently; your draft was not silently overwritten. Reload the current values and reapply the change.");
|
|
833
|
+
setSettings(snap.value);
|
|
834
|
+
draftRevision.current = snap.revision;
|
|
835
|
+
setDirty(false);
|
|
836
|
+
setError("");
|
|
837
|
+
} catch (e) {
|
|
838
|
+
setError(e.message);
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
const tabRefs = (0, react.useRef)([]);
|
|
842
|
+
const key = (event, index) => {
|
|
843
|
+
if (![
|
|
844
|
+
"ArrowLeft",
|
|
845
|
+
"ArrowRight",
|
|
846
|
+
"Home",
|
|
847
|
+
"End"
|
|
848
|
+
].includes(event.key)) return;
|
|
849
|
+
event.preventDefault();
|
|
850
|
+
let next = index;
|
|
851
|
+
if (event.key === "ArrowLeft") next = (index + tabs.length - 1) % tabs.length;
|
|
852
|
+
if (event.key === "ArrowRight") next = (index + 1) % tabs.length;
|
|
853
|
+
if (event.key === "Home") next = 0;
|
|
854
|
+
if (event.key === "End") next = tabs.length - 1;
|
|
855
|
+
setTab(tabs[next][0]);
|
|
856
|
+
tabRefs.current[next]?.focus();
|
|
857
|
+
};
|
|
858
|
+
if (!settings) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
859
|
+
className: "planx",
|
|
860
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t?.("loading") ?? "Loading…" }), error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
861
|
+
role: "alert",
|
|
862
|
+
children: error
|
|
863
|
+
})]
|
|
864
|
+
});
|
|
865
|
+
let content;
|
|
866
|
+
if (tab === "overview") content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Overview, {
|
|
867
|
+
diagnostics: diag,
|
|
868
|
+
settings
|
|
869
|
+
});
|
|
870
|
+
else if (tab === "roles") content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Roles, {
|
|
871
|
+
settings,
|
|
872
|
+
catalog,
|
|
873
|
+
onRole,
|
|
874
|
+
rpc
|
|
875
|
+
});
|
|
876
|
+
else if (tab === "planning") content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Planning, {
|
|
877
|
+
settings,
|
|
878
|
+
patch: (p) => mutate("planning", p)
|
|
879
|
+
});
|
|
880
|
+
else if (tab === "execution") content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Execution, {
|
|
881
|
+
settings,
|
|
882
|
+
patch: (p) => mutate("execution", p)
|
|
883
|
+
});
|
|
884
|
+
else if (tab === "review") content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Review, {
|
|
885
|
+
settings,
|
|
886
|
+
patch: (p) => mutate("review", p)
|
|
887
|
+
});
|
|
888
|
+
else if (tab === "recovery") content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Recovery, {
|
|
889
|
+
rpc,
|
|
890
|
+
allowSafeResume: settings.recovery.allowSafeResume
|
|
891
|
+
});
|
|
892
|
+
else if (tab === "external") content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExternalIssue, {
|
|
893
|
+
settings,
|
|
894
|
+
patch: (p) => mutate("externalIssue", p)
|
|
895
|
+
});
|
|
896
|
+
else content = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Diagnostics, { diagnostics: diag });
|
|
897
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
898
|
+
className: "planx",
|
|
899
|
+
children: [
|
|
900
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
901
|
+
className: "planx-row planx-between",
|
|
902
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: "Plan Mode" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
903
|
+
className: "planx-row",
|
|
904
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
905
|
+
type: "checkbox",
|
|
906
|
+
checked: settings.enabled,
|
|
907
|
+
onChange: (e) => {
|
|
908
|
+
setSettings((s) => ({
|
|
909
|
+
...s,
|
|
910
|
+
enabled: e.target.checked
|
|
911
|
+
}));
|
|
912
|
+
setDirty(true);
|
|
913
|
+
}
|
|
914
|
+
}), " Enabled"] }) }), dirty && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
915
|
+
onClick: save,
|
|
916
|
+
children: t?.("save") ?? "Save"
|
|
917
|
+
})]
|
|
918
|
+
})]
|
|
919
|
+
}),
|
|
920
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
921
|
+
role: "alert",
|
|
922
|
+
className: "planx-error",
|
|
923
|
+
children: error
|
|
924
|
+
}),
|
|
925
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
926
|
+
className: "planx-tabs",
|
|
927
|
+
role: "tablist",
|
|
928
|
+
"aria-label": "Plan Mode settings",
|
|
929
|
+
children: tabs.map(([id, label], index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
930
|
+
ref: (el) => {
|
|
931
|
+
tabRefs.current[index] = el;
|
|
932
|
+
},
|
|
933
|
+
role: "tab",
|
|
934
|
+
"aria-selected": tab === id,
|
|
935
|
+
tabIndex: tab === id ? 0 : -1,
|
|
936
|
+
onKeyDown: (e) => key(e, index),
|
|
937
|
+
onClick: () => setTab(id),
|
|
938
|
+
children: t?.(id) ?? label
|
|
939
|
+
}, id))
|
|
940
|
+
}),
|
|
941
|
+
content
|
|
942
|
+
]
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
//#endregion
|
|
946
|
+
//#region src/client/run-chip.tsx
|
|
947
|
+
function RunChip({ rpc, sessionId, overlay }) {
|
|
948
|
+
const [run, setRun] = (0, react.useState)();
|
|
949
|
+
(0, react.useEffect)(() => {
|
|
950
|
+
let live = true;
|
|
951
|
+
const load = () => rpc("run-list", { sessionId }).then((rows) => {
|
|
952
|
+
if (live) setRun(rows[0]);
|
|
953
|
+
}).catch(() => {});
|
|
954
|
+
load();
|
|
955
|
+
const timer = setInterval(load, 2500);
|
|
956
|
+
return () => {
|
|
957
|
+
live = false;
|
|
958
|
+
clearInterval(timer);
|
|
959
|
+
};
|
|
960
|
+
}, [rpc, sessionId]);
|
|
961
|
+
if (!run) return null;
|
|
962
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
963
|
+
className: "planx-chip",
|
|
964
|
+
"aria-label": `Plan Run ${run.phase}`,
|
|
965
|
+
onClick: () => overlay.open(run.runId),
|
|
966
|
+
children: [
|
|
967
|
+
"Plan Run · ",
|
|
968
|
+
run.tasksDone ?? 0,
|
|
969
|
+
"/",
|
|
970
|
+
run.tasksTotal ?? 0,
|
|
971
|
+
" · ",
|
|
972
|
+
run.phase
|
|
973
|
+
]
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
//#endregion
|
|
977
|
+
//#region src/client/run-overlay.tsx
|
|
978
|
+
var RunOverlayService = class {
|
|
979
|
+
#runId;
|
|
980
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
981
|
+
#returnFocus = null;
|
|
982
|
+
get runId() {
|
|
983
|
+
return this.#runId;
|
|
984
|
+
}
|
|
985
|
+
open(runId) {
|
|
986
|
+
if (typeof document !== "undefined" && document.activeElement instanceof HTMLElement) this.#returnFocus = document.activeElement;
|
|
987
|
+
this.#runId = runId;
|
|
988
|
+
this.#emit();
|
|
989
|
+
}
|
|
990
|
+
close() {
|
|
991
|
+
if (this.#runId === void 0) return;
|
|
992
|
+
this.#runId = void 0;
|
|
993
|
+
this.#emit();
|
|
994
|
+
const target = this.#returnFocus;
|
|
995
|
+
this.#returnFocus = null;
|
|
996
|
+
if (target?.isConnected) queueMicrotask(() => target.focus());
|
|
997
|
+
}
|
|
998
|
+
subscribe(listener) {
|
|
999
|
+
this.#listeners.add(listener);
|
|
1000
|
+
return () => this.#listeners.delete(listener);
|
|
1001
|
+
}
|
|
1002
|
+
#emit() {
|
|
1003
|
+
for (const listener of this.#listeners) listener();
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
function RunOverlay({ service, rpc, t }) {
|
|
1007
|
+
const [runId, setRunId] = (0, react.useState)(service.runId);
|
|
1008
|
+
const [run, setRun] = (0, react.useState)();
|
|
1009
|
+
const [error, setError] = (0, react.useState)("");
|
|
1010
|
+
const dialogRef = (0, react.useRef)(null);
|
|
1011
|
+
const titleId = "planx-run-dialog-title";
|
|
1012
|
+
(0, react.useEffect)(() => service.subscribe(() => setRunId(service.runId)), [service]);
|
|
1013
|
+
(0, react.useEffect)(() => {
|
|
1014
|
+
if (!runId) {
|
|
1015
|
+
setRun(void 0);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
let live = true;
|
|
1019
|
+
const load = async () => {
|
|
1020
|
+
try {
|
|
1021
|
+
const value = await rpc("run-detail", { runId });
|
|
1022
|
+
if (live) {
|
|
1023
|
+
setRun(value);
|
|
1024
|
+
setError("");
|
|
1025
|
+
}
|
|
1026
|
+
} catch (e) {
|
|
1027
|
+
if (live) setError(e.message);
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
load();
|
|
1031
|
+
const timer = setInterval(load, 2500);
|
|
1032
|
+
return () => {
|
|
1033
|
+
live = false;
|
|
1034
|
+
clearInterval(timer);
|
|
1035
|
+
};
|
|
1036
|
+
}, [runId, rpc]);
|
|
1037
|
+
(0, react.useEffect)(() => {
|
|
1038
|
+
if (!runId) return;
|
|
1039
|
+
dialogRef.current?.focus();
|
|
1040
|
+
const onKeyDown = (event) => {
|
|
1041
|
+
if (event.key === "Escape") {
|
|
1042
|
+
event.preventDefault();
|
|
1043
|
+
service.close();
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1047
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
1048
|
+
}, [runId, service]);
|
|
1049
|
+
if (!runId) return null;
|
|
1050
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1051
|
+
className: "planx-overlay",
|
|
1052
|
+
role: "presentation",
|
|
1053
|
+
onMouseDown: (event) => {
|
|
1054
|
+
if (event.target === event.currentTarget) service.close();
|
|
1055
|
+
},
|
|
1056
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1057
|
+
ref: dialogRef,
|
|
1058
|
+
role: "dialog",
|
|
1059
|
+
"aria-modal": "true",
|
|
1060
|
+
"aria-labelledby": titleId,
|
|
1061
|
+
className: "planx-dialog",
|
|
1062
|
+
tabIndex: -1,
|
|
1063
|
+
children: [
|
|
1064
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1065
|
+
className: "planx-row planx-between",
|
|
1066
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("b", {
|
|
1067
|
+
id: titleId,
|
|
1068
|
+
children: ["Plan Run ", runId]
|
|
1069
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1070
|
+
"aria-label": "Close",
|
|
1071
|
+
onClick: () => service.close(),
|
|
1072
|
+
children: "×"
|
|
1073
|
+
})]
|
|
1074
|
+
}),
|
|
1075
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1076
|
+
role: "alert",
|
|
1077
|
+
className: "planx-error",
|
|
1078
|
+
children: error
|
|
1079
|
+
}),
|
|
1080
|
+
!run ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t?.("loading") ?? "Loading…" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1081
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: run.phase }), run.message ? ` · ${run.message}` : ""] }),
|
|
1082
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
|
|
1083
|
+
"aria-label": "Plan task progress",
|
|
1084
|
+
max: Math.max(1, run.tasksTotal ?? 1),
|
|
1085
|
+
value: run.tasksDone ?? 0
|
|
1086
|
+
}),
|
|
1087
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
1088
|
+
run.tasksDone ?? 0,
|
|
1089
|
+
"/",
|
|
1090
|
+
run.tasksTotal ?? 0,
|
|
1091
|
+
" tasks · review round ",
|
|
1092
|
+
run.reviewRound ?? 0
|
|
1093
|
+
] }),
|
|
1094
|
+
run.activeTaskIds?.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: ["Active: ", run.activeTaskIds.join(", ")] }),
|
|
1095
|
+
run.review && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1096
|
+
className: "planx-card",
|
|
1097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: "Review" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(run.review, null, 2) })]
|
|
1098
|
+
}),
|
|
1099
|
+
Array.isArray(run.receipts) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1100
|
+
className: "planx-card",
|
|
1101
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: "Validation receipts" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(run.receipts, null, 2) })]
|
|
1102
|
+
}),
|
|
1103
|
+
run.usage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1104
|
+
className: "planx-card",
|
|
1105
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: "Usage" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(run.usage, null, 2) })]
|
|
1106
|
+
}),
|
|
1107
|
+
Array.isArray(run.worktrees) && run.worktrees.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1108
|
+
className: "planx-card",
|
|
1109
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: "Worktrees" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(run.worktrees.map((w) => ({
|
|
1110
|
+
taskId: w.taskId,
|
|
1111
|
+
status: w.status,
|
|
1112
|
+
baseHead: w.baseHead
|
|
1113
|
+
})), null, 2) })]
|
|
1114
|
+
}),
|
|
1115
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1116
|
+
className: "planx-row",
|
|
1117
|
+
children: [
|
|
1118
|
+
![
|
|
1119
|
+
"COMPLETE",
|
|
1120
|
+
"FAILED",
|
|
1121
|
+
"BLOCKED",
|
|
1122
|
+
"CANCELLED"
|
|
1123
|
+
].includes(run.phase) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1124
|
+
onClick: () => rpc("run-cancel", { runId }).then(() => setRun((r) => ({
|
|
1125
|
+
...r,
|
|
1126
|
+
phase: "CANCELLED"
|
|
1127
|
+
}))).catch((e) => setError(e.message)),
|
|
1128
|
+
children: t?.("stop") ?? "Stop"
|
|
1129
|
+
}),
|
|
1130
|
+
run.phase === "INTERRUPTED" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1131
|
+
onClick: async () => {
|
|
1132
|
+
try {
|
|
1133
|
+
const result = await rpc("run-resume", { runId });
|
|
1134
|
+
if (!result?.ok) setError(result?.reason ?? "Resume blocked");
|
|
1135
|
+
} catch (e) {
|
|
1136
|
+
setError(e.message);
|
|
1137
|
+
}
|
|
1138
|
+
},
|
|
1139
|
+
children: t?.("resume") ?? "Resume safely"
|
|
1140
|
+
}),
|
|
1141
|
+
[
|
|
1142
|
+
"COMPLETE",
|
|
1143
|
+
"FAILED",
|
|
1144
|
+
"BLOCKED",
|
|
1145
|
+
"CANCELLED"
|
|
1146
|
+
].includes(run.phase) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1147
|
+
onClick: async () => {
|
|
1148
|
+
if (confirm("Remove Plan Orchestrator run artifacts and owned worktrees?")) try {
|
|
1149
|
+
await rpc("run-cleanup", { runId });
|
|
1150
|
+
service.close();
|
|
1151
|
+
} catch (e) {
|
|
1152
|
+
setError(e.message);
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
children: "Cleanup"
|
|
1156
|
+
})
|
|
1157
|
+
]
|
|
1158
|
+
})
|
|
1159
|
+
] })
|
|
1160
|
+
]
|
|
1161
|
+
})
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
//#endregion
|
|
1165
|
+
//#region src/client/index.tsx
|
|
1166
|
+
const inject = [
|
|
1167
|
+
"slots",
|
|
1168
|
+
"locale",
|
|
1169
|
+
"connection",
|
|
1170
|
+
"settingsScope",
|
|
1171
|
+
"sessions"
|
|
1172
|
+
];
|
|
1173
|
+
function apply(ctx) {
|
|
1174
|
+
const NS = "plan-orchestrator";
|
|
1175
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1176
|
+
zh,
|
|
1177
|
+
en
|
|
1178
|
+
}), "plan-orchestrator: locale");
|
|
1179
|
+
ctx.effect(() => {
|
|
1180
|
+
const style = document.createElement("style");
|
|
1181
|
+
style.dataset.plugin = "dsh-plan-orchestrator";
|
|
1182
|
+
style.textContent = STYLE;
|
|
1183
|
+
document.head.append(style);
|
|
1184
|
+
return () => style.remove();
|
|
1185
|
+
}, "plan-orchestrator: style");
|
|
1186
|
+
const t = ctx.locale.bind(NS);
|
|
1187
|
+
const rpc = createRpc();
|
|
1188
|
+
const settingsScope = ctx.settingsScope.bind({
|
|
1189
|
+
namespace: SETTINGS_NAMESPACE,
|
|
1190
|
+
decode(value) {
|
|
1191
|
+
try {
|
|
1192
|
+
return validateSettings(value);
|
|
1193
|
+
} catch {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
const overlay = new RunOverlayService();
|
|
1199
|
+
ctx.effect(() => () => overlay.close(), "plan-orchestrator: overlay service");
|
|
1200
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1201
|
+
name: "settings.section",
|
|
1202
|
+
id: "plan-orchestrator",
|
|
1203
|
+
order: 20,
|
|
1204
|
+
label: () => t("nav"),
|
|
1205
|
+
locale: NS,
|
|
1206
|
+
inject: () => ({
|
|
1207
|
+
rpc,
|
|
1208
|
+
t,
|
|
1209
|
+
settingsScope
|
|
1210
|
+
})
|
|
1211
|
+
}, PlanModeSection));
|
|
1212
|
+
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
1213
|
+
name: "conversation.input.right",
|
|
1214
|
+
id: "plan-orchestrator-run",
|
|
1215
|
+
order: 20,
|
|
1216
|
+
locale: NS,
|
|
1217
|
+
inject: (sessionId) => ({
|
|
1218
|
+
rpc,
|
|
1219
|
+
sessionId,
|
|
1220
|
+
overlay
|
|
1221
|
+
})
|
|
1222
|
+
}, RunChip));
|
|
1223
|
+
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
1224
|
+
name: "shell.overlay",
|
|
1225
|
+
id: "plan-orchestrator-run-overlay",
|
|
1226
|
+
order: 21,
|
|
1227
|
+
locale: NS,
|
|
1228
|
+
inject: () => ({
|
|
1229
|
+
service: overlay,
|
|
1230
|
+
rpc,
|
|
1231
|
+
t
|
|
1232
|
+
})
|
|
1233
|
+
}, RunOverlay));
|
|
1234
|
+
}
|
|
1235
|
+
//#endregion
|
|
1236
|
+
exports.apply = apply;
|
|
1237
|
+
exports.inject = inject;
|
|
1238
|
+
return module.exports;
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
//# sourceMappingURL=client.js.map
|