@wrongstack/webui-server 0.296.4 → 0.297.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/index.js +934 -429
- package/dist/index.js.map +4 -4
- package/dist/protocol/client-conversation.d.ts +1 -1
- package/dist/protocol/client-conversation.d.ts.map +1 -1
- package/dist/protocol/index.js +7 -1
- package/dist/protocol/index.js.map +2 -2
- package/dist/protocol/projections.d.ts +5 -1
- package/dist/protocol/projections.d.ts.map +1 -1
- package/dist/protocol/registry.d.ts +2 -2
- package/dist/protocol/registry.d.ts.map +1 -1
- package/dist/protocol/server-conversation.d.ts +1 -1
- package/dist/protocol/server-conversation.d.ts.map +1 -1
- package/dist/server/backend-services.d.ts +6 -2
- package/dist/server/backend-services.d.ts.map +1 -1
- package/dist/server/collaboration-ws-handler.d.ts +2 -2
- package/dist/server/collaboration-ws-handler.d.ts.map +1 -1
- package/dist/server/embedded-lifecycle.d.ts +2 -0
- package/dist/server/embedded-lifecycle.d.ts.map +1 -1
- package/dist/server/entry.js +856 -395
- package/dist/server/entry.js.map +4 -4
- package/dist/server/goal-ws-handler.d.ts +3 -1
- package/dist/server/goal-ws-handler.d.ts.map +1 -1
- package/dist/server/handlers.js.map +2 -2
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/kanban-supervisor.d.ts +6 -0
- package/dist/server/kanban-supervisor.d.ts.map +1 -1
- package/dist/server/mailbox-handlers.d.ts +2 -1
- package/dist/server/mailbox-handlers.d.ts.map +1 -1
- package/dist/server/mailbox-routes.d.ts +1 -0
- package/dist/server/mailbox-routes.d.ts.map +1 -1
- package/dist/server/memory-diagnostics.d.ts +69 -0
- package/dist/server/memory-diagnostics.d.ts.map +1 -0
- package/dist/server/sdd-board-ws-handler.d.ts +14 -8
- package/dist/server/sdd-board-ws-handler.d.ts.map +1 -1
- package/dist/server/sdd-wizard-wiring.d.ts +5 -6
- package/dist/server/sdd-wizard-wiring.d.ts.map +1 -1
- package/dist/server/sdd-wizard-ws-handler.d.ts +16 -6
- package/dist/server/sdd-wizard-ws-handler.d.ts.map +1 -1
- package/dist/server/start-webui.d.ts.map +1 -1
- package/dist/server/types.d.ts +2 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/worktree-ws-handler.d.ts +30 -2
- package/dist/server/worktree-ws-handler.d.ts.map +1 -1
- package/dist/server/ws-payload-validation.d.ts +8 -0
- package/dist/server/ws-payload-validation.d.ts.map +1 -1
- package/package.json +10 -10
package/dist/server/entry.js
CHANGED
|
@@ -135,85 +135,85 @@ var ENUM_PREF_KEYS = {
|
|
|
135
135
|
autoReviewCascadeOn: /* @__PURE__ */ new Set(["off", "critical", "high"]),
|
|
136
136
|
fleetChatVerbosity: /* @__PURE__ */ new Set(["off", "full"])
|
|
137
137
|
};
|
|
138
|
-
function validateModelRuntimeValue(modelRuntime,
|
|
138
|
+
function validateModelRuntimeValue(modelRuntime, path30) {
|
|
139
139
|
const reasoning = modelRuntime["reasoning"];
|
|
140
140
|
if (reasoning !== void 0) {
|
|
141
|
-
if (!isRecord(reasoning)) return `${
|
|
141
|
+
if (!isRecord(reasoning)) return `${path30}.reasoning must be an object when provided`;
|
|
142
142
|
const mode = reasoning["mode"];
|
|
143
143
|
const effort = reasoning["effort"];
|
|
144
144
|
const preserve = reasoning["preserve"];
|
|
145
145
|
if (mode !== void 0 && (typeof mode !== "string" || !REASONING_MODE_VALUES.has(mode))) {
|
|
146
|
-
return `${
|
|
146
|
+
return `${path30}.reasoning.mode must be one of: ${Array.from(REASONING_MODE_VALUES).join(", ")}`;
|
|
147
147
|
}
|
|
148
148
|
if (effort !== void 0 && (typeof effort !== "string" || !REASONING_EFFORT_VALUES.has(effort))) {
|
|
149
|
-
return `${
|
|
149
|
+
return `${path30}.reasoning.effort must be one of: ${Array.from(REASONING_EFFORT_VALUES).join(", ")}`;
|
|
150
150
|
}
|
|
151
151
|
if (preserve !== void 0 && typeof preserve !== "boolean") {
|
|
152
|
-
return `${
|
|
152
|
+
return `${path30}.reasoning.preserve must be a boolean when provided`;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
const cache2 = modelRuntime["cache"];
|
|
156
156
|
if (cache2 !== void 0) {
|
|
157
|
-
if (!isRecord(cache2)) return `${
|
|
157
|
+
if (!isRecord(cache2)) return `${path30}.cache must be an object when provided`;
|
|
158
158
|
const ttl = cache2["ttl"];
|
|
159
159
|
if (ttl !== void 0 && (typeof ttl !== "string" || !CACHE_TTL_VALUES.has(ttl) || ttl === "default")) {
|
|
160
|
-
return `${
|
|
160
|
+
return `${path30}.cache.ttl must be one of: 5m, 1h`;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
const parameters = modelRuntime["parameters"];
|
|
164
164
|
if (parameters !== void 0 && !isRecord(parameters)) {
|
|
165
|
-
return `${
|
|
165
|
+
return `${path30}.parameters must be an object when provided`;
|
|
166
166
|
}
|
|
167
167
|
return null;
|
|
168
168
|
}
|
|
169
|
-
function validateModelBlackoutRule(rule,
|
|
169
|
+
function validateModelBlackoutRule(rule, path30) {
|
|
170
170
|
const id = rule["id"];
|
|
171
171
|
if (typeof id !== "string" || id.trim().length === 0) {
|
|
172
|
-
return `${
|
|
172
|
+
return `${path30}.id must be a non-empty string`;
|
|
173
173
|
}
|
|
174
174
|
const start = rule["start"];
|
|
175
175
|
if (typeof start !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(start)) {
|
|
176
|
-
return `${
|
|
176
|
+
return `${path30}.start must be a string in HH:mm (00:00-23:59) format`;
|
|
177
177
|
}
|
|
178
178
|
const end = rule["end"];
|
|
179
179
|
if (typeof end !== "string" || !/^([01]\d|2[0-3]):[0-5]\d$/.test(end)) {
|
|
180
|
-
return `${
|
|
180
|
+
return `${path30}.end must be a string in HH:mm (00:00-23:59) format`;
|
|
181
181
|
}
|
|
182
182
|
if (rule["enabled"] !== void 0 && typeof rule["enabled"] !== "boolean") {
|
|
183
|
-
return `${
|
|
183
|
+
return `${path30}.enabled must be a boolean when provided`;
|
|
184
184
|
}
|
|
185
185
|
if (rule["provider"] !== void 0 && typeof rule["provider"] !== "string") {
|
|
186
|
-
return `${
|
|
186
|
+
return `${path30}.provider must be a string when provided`;
|
|
187
187
|
}
|
|
188
188
|
if (rule["model"] !== void 0 && typeof rule["model"] !== "string") {
|
|
189
|
-
return `${
|
|
189
|
+
return `${path30}.model must be a string when provided`;
|
|
190
190
|
}
|
|
191
191
|
if (rule["days"] !== void 0) {
|
|
192
|
-
if (!Array.isArray(rule["days"])) return `${
|
|
192
|
+
if (!Array.isArray(rule["days"])) return `${path30}.days must be an array when provided`;
|
|
193
193
|
const seen = /* @__PURE__ */ new Set();
|
|
194
194
|
for (const d of rule["days"]) {
|
|
195
195
|
if (typeof d !== "number" || !Number.isInteger(d) || d < 0 || d > 6) {
|
|
196
|
-
return `${
|
|
196
|
+
return `${path30}.days elements must be integers 0-6 when provided`;
|
|
197
197
|
}
|
|
198
|
-
if (seen.has(d)) return `${
|
|
198
|
+
if (seen.has(d)) return `${path30}.days contains duplicate day: ${d}`;
|
|
199
199
|
seen.add(d);
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
if (rule["timezone"] !== void 0) {
|
|
203
203
|
if (typeof rule["timezone"] !== "string") {
|
|
204
|
-
return `${
|
|
204
|
+
return `${path30}.timezone must be a string when provided`;
|
|
205
205
|
}
|
|
206
206
|
try {
|
|
207
207
|
Intl.DateTimeFormat(void 0, { timeZone: rule["timezone"] });
|
|
208
208
|
} catch {
|
|
209
|
-
return `${
|
|
209
|
+
return `${path30}.timezone is not a valid IANA timezone (e.g. "America/New_York")`;
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
if (rule["label"] !== void 0 && typeof rule["label"] !== "string") {
|
|
213
|
-
return `${
|
|
213
|
+
return `${path30}.label must be a string when provided`;
|
|
214
214
|
}
|
|
215
215
|
if (rule["mode"] !== void 0 && rule["mode"] !== "blackout" && rule["mode"] !== "allow_only") {
|
|
216
|
-
return `${
|
|
216
|
+
return `${path30}.mode must be 'blackout' or 'allow_only' when provided`;
|
|
217
217
|
}
|
|
218
218
|
return null;
|
|
219
219
|
}
|
|
@@ -386,6 +386,37 @@ function validateMailboxMessagesPayload(payload) {
|
|
|
386
386
|
}
|
|
387
387
|
};
|
|
388
388
|
}
|
|
389
|
+
var MAILBOX_ACTIONS = /* @__PURE__ */ new Set(["mark-read", "acknowledge", "reopen", "soft-delete"]);
|
|
390
|
+
function validateMailboxActionPayload(payload) {
|
|
391
|
+
if (!isRecord2(payload)) {
|
|
392
|
+
return { ok: false, message: "mailbox.action payload must be an object" };
|
|
393
|
+
}
|
|
394
|
+
const requestId = payload["requestId"];
|
|
395
|
+
const mailId = payload["mailId"];
|
|
396
|
+
const action = payload["action"];
|
|
397
|
+
const readerId = payload["readerId"];
|
|
398
|
+
if (typeof requestId !== "string" || requestId.trim().length === 0) {
|
|
399
|
+
return { ok: false, message: "mailbox.action payload.requestId must be a non-empty string" };
|
|
400
|
+
}
|
|
401
|
+
if (typeof mailId !== "string" || mailId.trim().length === 0) {
|
|
402
|
+
return { ok: false, message: "mailbox.action payload.mailId must be a non-empty string" };
|
|
403
|
+
}
|
|
404
|
+
if (typeof action !== "string" || !MAILBOX_ACTIONS.has(action)) {
|
|
405
|
+
return { ok: false, message: "mailbox.action payload.action must be a supported action" };
|
|
406
|
+
}
|
|
407
|
+
if (typeof readerId !== "string" || readerId.trim().length === 0) {
|
|
408
|
+
return { ok: false, message: "mailbox.action payload.readerId must be a non-empty string" };
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
ok: true,
|
|
412
|
+
value: {
|
|
413
|
+
requestId: requestId.trim(),
|
|
414
|
+
mailId: mailId.trim(),
|
|
415
|
+
action,
|
|
416
|
+
readerId: readerId.trim()
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
389
420
|
var MAILBOX_SEND_TYPES = /* @__PURE__ */ new Set([
|
|
390
421
|
"note",
|
|
391
422
|
"ask",
|
|
@@ -402,6 +433,7 @@ function validateMailboxSendPayload(payload) {
|
|
|
402
433
|
return { ok: false, message: "mailbox.send payload must be an object" };
|
|
403
434
|
}
|
|
404
435
|
const requestId = payload["requestId"];
|
|
436
|
+
const rawFrom = payload["from"];
|
|
405
437
|
const rawTo = payload["to"];
|
|
406
438
|
const rawType = payload["type"];
|
|
407
439
|
const rawAudience = payload["audience"];
|
|
@@ -412,6 +444,9 @@ function validateMailboxSendPayload(payload) {
|
|
|
412
444
|
if (typeof requestId !== "string" || requestId.trim().length === 0) {
|
|
413
445
|
return { ok: false, message: "mailbox.send payload.requestId must be a non-empty string" };
|
|
414
446
|
}
|
|
447
|
+
if (rawFrom !== void 0 && (typeof rawFrom !== "string" || rawFrom.trim().length === 0)) {
|
|
448
|
+
return { ok: false, message: "mailbox.send payload.from must be a non-empty string when provided" };
|
|
449
|
+
}
|
|
415
450
|
if (typeof rawTo !== "string" || rawTo.trim().length === 0) {
|
|
416
451
|
return { ok: false, message: "mailbox.send payload.to must be a non-empty string" };
|
|
417
452
|
}
|
|
@@ -448,6 +483,7 @@ function validateMailboxSendPayload(payload) {
|
|
|
448
483
|
ok: true,
|
|
449
484
|
value: {
|
|
450
485
|
requestId: requestId.trim(),
|
|
486
|
+
...rawFrom !== void 0 ? { from: rawFrom.trim() } : {},
|
|
451
487
|
to,
|
|
452
488
|
type,
|
|
453
489
|
audience: rawAudience,
|
|
@@ -737,8 +773,8 @@ function validateShellOpenPayload(payload) {
|
|
|
737
773
|
if (!isRecord2(payload)) {
|
|
738
774
|
return { ok: false, message: "shell.open payload must be an object with string path" };
|
|
739
775
|
}
|
|
740
|
-
const
|
|
741
|
-
if (typeof
|
|
776
|
+
const path30 = payload["path"];
|
|
777
|
+
if (typeof path30 !== "string" || path30.trim().length === 0) {
|
|
742
778
|
return { ok: false, message: "shell.open payload.path must be a non-empty string" };
|
|
743
779
|
}
|
|
744
780
|
const target = payload["target"];
|
|
@@ -751,7 +787,7 @@ function validateShellOpenPayload(payload) {
|
|
|
751
787
|
return {
|
|
752
788
|
ok: true,
|
|
753
789
|
value: {
|
|
754
|
-
path:
|
|
790
|
+
path: path30,
|
|
755
791
|
...target !== void 0 ? { target } : {}
|
|
756
792
|
}
|
|
757
793
|
};
|
|
@@ -760,14 +796,14 @@ function validateGitDiffPayload(payload) {
|
|
|
760
796
|
if (!isRecord2(payload)) {
|
|
761
797
|
return { ok: false, message: "git.diff payload must be an object" };
|
|
762
798
|
}
|
|
763
|
-
const
|
|
764
|
-
if (
|
|
799
|
+
const path30 = payload["path"];
|
|
800
|
+
if (path30 === void 0 || path30 === null) {
|
|
765
801
|
return { ok: true, value: { path: "" } };
|
|
766
802
|
}
|
|
767
|
-
if (typeof
|
|
803
|
+
if (typeof path30 !== "string") {
|
|
768
804
|
return { ok: false, message: "git.diff payload.path must be a string when provided" };
|
|
769
805
|
}
|
|
770
|
-
return { ok: true, value: { path:
|
|
806
|
+
return { ok: true, value: { path: path30 } };
|
|
771
807
|
}
|
|
772
808
|
function validateProjectsAddPayload(payload) {
|
|
773
809
|
if (!isRecord2(payload)) {
|
|
@@ -1529,6 +1565,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1529
1565
|
for (const off of this.offs) off();
|
|
1530
1566
|
this.offs.length = 0;
|
|
1531
1567
|
this.stopBroadcast();
|
|
1568
|
+
this.clients.clear();
|
|
1532
1569
|
}
|
|
1533
1570
|
// ── Inbound client messages ────────────────────────────────────────────
|
|
1534
1571
|
/**
|
|
@@ -1588,9 +1625,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1588
1625
|
if (activeSessionId !== void 0 && sessionId !== activeSessionId) {
|
|
1589
1626
|
this.send(
|
|
1590
1627
|
ws,
|
|
1591
|
-
this.errorMessage(
|
|
1592
|
-
`collab.join sessionId mismatch (active: ${activeSessionId})`
|
|
1593
|
-
)
|
|
1628
|
+
this.errorMessage(`collab.join sessionId mismatch (active: ${activeSessionId})`)
|
|
1594
1629
|
);
|
|
1595
1630
|
return;
|
|
1596
1631
|
}
|
|
@@ -1601,26 +1636,19 @@ var CollaborationWebSocketHandler = class {
|
|
|
1601
1636
|
if (role === "controller" && !this.bus) {
|
|
1602
1637
|
this.send(
|
|
1603
1638
|
ws,
|
|
1604
|
-
this.errorMessage(
|
|
1605
|
-
`role 'controller' is not available: server has no CollaborationBus`
|
|
1606
|
-
)
|
|
1639
|
+
this.errorMessage(`role 'controller' is not available: server has no CollaborationBus`)
|
|
1607
1640
|
);
|
|
1608
1641
|
return;
|
|
1609
1642
|
}
|
|
1610
1643
|
if (role === "annotator" && !this.annotations) {
|
|
1611
1644
|
this.send(
|
|
1612
1645
|
ws,
|
|
1613
|
-
this.errorMessage(
|
|
1614
|
-
`role 'annotator' is not available: server has no annotations store`
|
|
1615
|
-
)
|
|
1646
|
+
this.errorMessage(`role 'annotator' is not available: server has no annotations store`)
|
|
1616
1647
|
);
|
|
1617
1648
|
return;
|
|
1618
1649
|
}
|
|
1619
1650
|
if (role !== "observer" && this.options.authorizeRole?.({ ws, sessionId, requestedRole: role }) !== true) {
|
|
1620
|
-
this.send(
|
|
1621
|
-
ws,
|
|
1622
|
-
this.errorMessage(`role '${role}' requires explicit server authorization`)
|
|
1623
|
-
);
|
|
1651
|
+
this.send(ws, this.errorMessage(`role '${role}' requires explicit server authorization`));
|
|
1624
1652
|
return;
|
|
1625
1653
|
}
|
|
1626
1654
|
const participant = {
|
|
@@ -1649,14 +1677,10 @@ var CollaborationWebSocketHandler = class {
|
|
|
1649
1677
|
this.broadcast(sessionId, this.stateMessage(sessionId));
|
|
1650
1678
|
if (this.reader) {
|
|
1651
1679
|
this.replayHistory(ws, sessionId).catch((err) => {
|
|
1652
|
-
this.logger.debug?.(
|
|
1653
|
-
`collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`
|
|
1654
|
-
);
|
|
1680
|
+
this.logger.debug?.(`collab: replay failed for ${sessionId}: ${toErrorMessage(err)}`);
|
|
1655
1681
|
});
|
|
1656
1682
|
}
|
|
1657
|
-
this.logger.debug?.(
|
|
1658
|
-
`collab: participant ${participant.participantId} joined ${sessionId}`
|
|
1659
|
-
);
|
|
1683
|
+
this.logger.debug?.(`collab: participant ${participant.participantId} joined ${sessionId}`);
|
|
1660
1684
|
}
|
|
1661
1685
|
leave(ws) {
|
|
1662
1686
|
this.handleDisconnect(ws);
|
|
@@ -1727,18 +1751,13 @@ var CollaborationWebSocketHandler = class {
|
|
|
1727
1751
|
}
|
|
1728
1752
|
const payload = raw;
|
|
1729
1753
|
if (!payload?.sessionId || typeof payload.atEventIndex !== "number" || typeof payload.text !== "string") {
|
|
1730
|
-
this.send(
|
|
1731
|
-
ws,
|
|
1732
|
-
this.errorMessage("annotate requires { sessionId, atEventIndex, text }")
|
|
1733
|
-
);
|
|
1754
|
+
this.send(ws, this.errorMessage("annotate requires { sessionId, atEventIndex, text }"));
|
|
1734
1755
|
return;
|
|
1735
1756
|
}
|
|
1736
1757
|
if (payload.sessionId !== participant.sessionId) {
|
|
1737
1758
|
this.send(
|
|
1738
1759
|
ws,
|
|
1739
|
-
this.errorMessage(
|
|
1740
|
-
`annotate sessionId mismatch (joined: ${participant.sessionId})`
|
|
1741
|
-
)
|
|
1760
|
+
this.errorMessage(`annotate sessionId mismatch (joined: ${participant.sessionId})`)
|
|
1742
1761
|
);
|
|
1743
1762
|
return;
|
|
1744
1763
|
}
|
|
@@ -1765,12 +1784,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1765
1784
|
}
|
|
1766
1785
|
});
|
|
1767
1786
|
} catch (err) {
|
|
1768
|
-
this.send(
|
|
1769
|
-
ws,
|
|
1770
|
-
this.errorMessage(
|
|
1771
|
-
`annotation rejected: ${toErrorMessage(err)}`
|
|
1772
|
-
)
|
|
1773
|
-
);
|
|
1787
|
+
this.send(ws, this.errorMessage(`annotation rejected: ${toErrorMessage(err)}`));
|
|
1774
1788
|
}
|
|
1775
1789
|
}
|
|
1776
1790
|
async handleResolve(ws, raw) {
|
|
@@ -1786,26 +1800,19 @@ var CollaborationWebSocketHandler = class {
|
|
|
1786
1800
|
if (participant.role !== "annotator") {
|
|
1787
1801
|
this.send(
|
|
1788
1802
|
ws,
|
|
1789
|
-
this.errorMessage(
|
|
1790
|
-
`resolve requires the 'annotator' role (current: '${participant.role}')`
|
|
1791
|
-
)
|
|
1803
|
+
this.errorMessage(`resolve requires the 'annotator' role (current: '${participant.role}')`)
|
|
1792
1804
|
);
|
|
1793
1805
|
return;
|
|
1794
1806
|
}
|
|
1795
1807
|
const payload = raw;
|
|
1796
1808
|
if (!payload?.sessionId || !payload.annotationId) {
|
|
1797
|
-
this.send(
|
|
1798
|
-
ws,
|
|
1799
|
-
this.errorMessage("resolve requires { sessionId, annotationId }")
|
|
1800
|
-
);
|
|
1809
|
+
this.send(ws, this.errorMessage("resolve requires { sessionId, annotationId }"));
|
|
1801
1810
|
return;
|
|
1802
1811
|
}
|
|
1803
1812
|
if (payload.sessionId !== participant.sessionId) {
|
|
1804
1813
|
this.send(
|
|
1805
1814
|
ws,
|
|
1806
|
-
this.errorMessage(
|
|
1807
|
-
`resolve sessionId mismatch (joined: ${participant.sessionId})`
|
|
1808
|
-
)
|
|
1815
|
+
this.errorMessage(`resolve sessionId mismatch (joined: ${participant.sessionId})`)
|
|
1809
1816
|
);
|
|
1810
1817
|
return;
|
|
1811
1818
|
}
|
|
@@ -1816,10 +1823,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1816
1823
|
resolvedBy: participant.participantId
|
|
1817
1824
|
});
|
|
1818
1825
|
if (!updated) {
|
|
1819
|
-
this.send(
|
|
1820
|
-
ws,
|
|
1821
|
-
this.errorMessage(`annotation not found: ${payload.annotationId}`)
|
|
1822
|
-
);
|
|
1826
|
+
this.send(ws, this.errorMessage(`annotation not found: ${payload.annotationId}`));
|
|
1823
1827
|
return;
|
|
1824
1828
|
}
|
|
1825
1829
|
this.broadcast(payload.sessionId, {
|
|
@@ -1832,12 +1836,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1832
1836
|
}
|
|
1833
1837
|
});
|
|
1834
1838
|
} catch (err) {
|
|
1835
|
-
this.send(
|
|
1836
|
-
ws,
|
|
1837
|
-
this.errorMessage(
|
|
1838
|
-
`resolve failed: ${toErrorMessage(err)}`
|
|
1839
|
-
)
|
|
1840
|
-
);
|
|
1839
|
+
this.send(ws, this.errorMessage(`resolve failed: ${toErrorMessage(err)}`));
|
|
1841
1840
|
}
|
|
1842
1841
|
}
|
|
1843
1842
|
// ── Event subscription (live mirror) ───────────────────────────────────
|
|
@@ -1908,9 +1907,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1908
1907
|
seen++;
|
|
1909
1908
|
}
|
|
1910
1909
|
} catch (err) {
|
|
1911
|
-
this.logger.debug?.(
|
|
1912
|
-
`collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`
|
|
1913
|
-
);
|
|
1910
|
+
this.logger.debug?.(`collab: session reader rejected ${sessionId}: ${toErrorMessage(err)}`);
|
|
1914
1911
|
return;
|
|
1915
1912
|
}
|
|
1916
1913
|
const tail2 = seen <= REPLAY_LIMIT ? ring.slice(0, seen) : (
|
|
@@ -1992,9 +1989,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
1992
1989
|
try {
|
|
1993
1990
|
sendSerialized(p.ws, data);
|
|
1994
1991
|
} catch (err) {
|
|
1995
|
-
this.logger.debug?.(
|
|
1996
|
-
`collab broadcast failed: ${toErrorMessage(err)}`
|
|
1997
|
-
);
|
|
1992
|
+
this.logger.debug?.(`collab broadcast failed: ${toErrorMessage(err)}`);
|
|
1998
1993
|
}
|
|
1999
1994
|
}
|
|
2000
1995
|
}
|
|
@@ -2021,9 +2016,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2021
2016
|
if (participant.role !== "controller") {
|
|
2022
2017
|
this.send(
|
|
2023
2018
|
ws,
|
|
2024
|
-
this.errorMessage(
|
|
2025
|
-
`pause requires the 'controller' role (current: '${participant.role}')`
|
|
2026
|
-
)
|
|
2019
|
+
this.errorMessage(`pause requires the 'controller' role (current: '${participant.role}')`)
|
|
2027
2020
|
);
|
|
2028
2021
|
return;
|
|
2029
2022
|
}
|
|
@@ -2068,9 +2061,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2068
2061
|
if (participant.role !== "controller") {
|
|
2069
2062
|
this.send(
|
|
2070
2063
|
ws,
|
|
2071
|
-
this.errorMessage(
|
|
2072
|
-
`resume requires the 'controller' role (current: '${participant.role}')`
|
|
2073
|
-
)
|
|
2064
|
+
this.errorMessage(`resume requires the 'controller' role (current: '${participant.role}')`)
|
|
2074
2065
|
);
|
|
2075
2066
|
return;
|
|
2076
2067
|
}
|
|
@@ -2168,9 +2159,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2168
2159
|
if (payload.sessionId !== participant.sessionId) {
|
|
2169
2160
|
this.send(
|
|
2170
2161
|
ws,
|
|
2171
|
-
this.errorMessage(
|
|
2172
|
-
`inject_tool sessionId mismatch (joined: ${participant.sessionId})`
|
|
2173
|
-
)
|
|
2162
|
+
this.errorMessage(`inject_tool sessionId mismatch (joined: ${participant.sessionId})`)
|
|
2174
2163
|
);
|
|
2175
2164
|
return;
|
|
2176
2165
|
}
|
|
@@ -2184,9 +2173,7 @@ var CollaborationWebSocketHandler = class {
|
|
|
2184
2173
|
if (!queued) {
|
|
2185
2174
|
this.send(
|
|
2186
2175
|
ws,
|
|
2187
|
-
this.errorMessage(
|
|
2188
|
-
`an injection for toolUseId ${payload.toolUseId} is already queued`
|
|
2189
|
-
)
|
|
2176
|
+
this.errorMessage(`an injection for toolUseId ${payload.toolUseId} is already queued`)
|
|
2190
2177
|
);
|
|
2191
2178
|
return;
|
|
2192
2179
|
}
|
|
@@ -4288,8 +4275,8 @@ function jsonByteLength(value) {
|
|
|
4288
4275
|
return MAX_PAYLOAD_BYTES + 1;
|
|
4289
4276
|
}
|
|
4290
4277
|
}
|
|
4291
|
-
function error(errors,
|
|
4292
|
-
errors.push({ path:
|
|
4278
|
+
function error(errors, path30, code, message) {
|
|
4279
|
+
errors.push({ path: path30, code, message });
|
|
4293
4280
|
}
|
|
4294
4281
|
function isMessageRole(value) {
|
|
4295
4282
|
return value === "user" || value === "assistant" || value === "system";
|
|
@@ -4297,25 +4284,25 @@ function isMessageRole(value) {
|
|
|
4297
4284
|
function isPlainJsonObject(value) {
|
|
4298
4285
|
return isRecord3(value);
|
|
4299
4286
|
}
|
|
4300
|
-
function validateCacheControl(value,
|
|
4287
|
+
function validateCacheControl(value, path30, errors) {
|
|
4301
4288
|
if (value === void 0) return void 0;
|
|
4302
4289
|
if (!isRecord3(value) || value["type"] !== "ephemeral") {
|
|
4303
|
-
error(errors,
|
|
4290
|
+
error(errors, path30, "INVALID_CACHE_CONTROL", 'cache_control must be { type: "ephemeral" }.');
|
|
4304
4291
|
return void 0;
|
|
4305
4292
|
}
|
|
4306
4293
|
return { type: "ephemeral" };
|
|
4307
4294
|
}
|
|
4308
|
-
function validateProviderMeta(value,
|
|
4295
|
+
function validateProviderMeta(value, path30, errors) {
|
|
4309
4296
|
if (value === void 0) return void 0;
|
|
4310
4297
|
if (!isPlainJsonObject(value)) {
|
|
4311
|
-
error(errors,
|
|
4298
|
+
error(errors, path30, "INVALID_PROVIDER_META", "providerMeta must be a JSON object.");
|
|
4312
4299
|
return void 0;
|
|
4313
4300
|
}
|
|
4314
4301
|
return value;
|
|
4315
4302
|
}
|
|
4316
|
-
function validateBlock(value,
|
|
4303
|
+
function validateBlock(value, path30, errors) {
|
|
4317
4304
|
if (!isRecord3(value)) {
|
|
4318
|
-
error(errors,
|
|
4305
|
+
error(errors, path30, "INVALID_BLOCK", "Content block must be an object.");
|
|
4319
4306
|
return void 0;
|
|
4320
4307
|
}
|
|
4321
4308
|
const type = value["type"];
|
|
@@ -4323,14 +4310,14 @@ function validateBlock(value, path29, errors) {
|
|
|
4323
4310
|
case "text": {
|
|
4324
4311
|
const text = value["text"];
|
|
4325
4312
|
if (typeof text !== "string") {
|
|
4326
|
-
error(errors, `${
|
|
4313
|
+
error(errors, `${path30}/text`, "INVALID_TEXT", "Text block text must be a string.");
|
|
4327
4314
|
return void 0;
|
|
4328
4315
|
}
|
|
4329
4316
|
if (text.length > MAX_STRING_LENGTH) {
|
|
4330
|
-
error(errors, `${
|
|
4317
|
+
error(errors, `${path30}/text`, "TEXT_TOO_LARGE", "Text block is too large.");
|
|
4331
4318
|
return void 0;
|
|
4332
4319
|
}
|
|
4333
|
-
const cacheControl = validateCacheControl(value["cache_control"], `${
|
|
4320
|
+
const cacheControl = validateCacheControl(value["cache_control"], `${path30}/cache_control`, errors);
|
|
4334
4321
|
return cacheControl ? { type: "text", text, cache_control: cacheControl } : { type: "text", text };
|
|
4335
4322
|
}
|
|
4336
4323
|
case "tool_use": {
|
|
@@ -4338,15 +4325,15 @@ function validateBlock(value, path29, errors) {
|
|
|
4338
4325
|
const name2 = value["name"];
|
|
4339
4326
|
const input = value["input"];
|
|
4340
4327
|
if (typeof id !== "string" || id.length === 0) {
|
|
4341
|
-
error(errors, `${
|
|
4328
|
+
error(errors, `${path30}/id`, "INVALID_TOOL_USE_ID", "tool_use.id must be a non-empty string.");
|
|
4342
4329
|
}
|
|
4343
4330
|
if (typeof name2 !== "string" || name2.length === 0) {
|
|
4344
|
-
error(errors, `${
|
|
4331
|
+
error(errors, `${path30}/name`, "INVALID_TOOL_NAME", "tool_use.name must be a non-empty string.");
|
|
4345
4332
|
}
|
|
4346
4333
|
if (!isPlainJsonObject(input)) {
|
|
4347
|
-
error(errors, `${
|
|
4334
|
+
error(errors, `${path30}/input`, "INVALID_TOOL_INPUT", "tool_use.input must be an object.");
|
|
4348
4335
|
}
|
|
4349
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4336
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path30}/providerMeta`, errors);
|
|
4350
4337
|
if (typeof id !== "string" || id.length === 0 || typeof name2 !== "string" || name2.length === 0 || !isPlainJsonObject(input)) {
|
|
4351
4338
|
return void 0;
|
|
4352
4339
|
}
|
|
@@ -4358,18 +4345,18 @@ function validateBlock(value, path29, errors) {
|
|
|
4358
4345
|
const content = value["content"];
|
|
4359
4346
|
const isError = value["is_error"];
|
|
4360
4347
|
if (typeof toolUseId !== "string" || toolUseId.length === 0) {
|
|
4361
|
-
error(errors, `${
|
|
4348
|
+
error(errors, `${path30}/tool_use_id`, "INVALID_TOOL_RESULT_ID", "tool_result.tool_use_id must be a non-empty string.");
|
|
4362
4349
|
}
|
|
4363
4350
|
if (name2 !== void 0 && typeof name2 !== "string") {
|
|
4364
|
-
error(errors, `${
|
|
4351
|
+
error(errors, `${path30}/name`, "INVALID_TOOL_RESULT_NAME", "tool_result.name must be a string.");
|
|
4365
4352
|
}
|
|
4366
4353
|
if (typeof content !== "string") {
|
|
4367
|
-
error(errors, `${
|
|
4354
|
+
error(errors, `${path30}/content`, "INVALID_TOOL_RESULT_CONTENT", "tool_result.content must be a string.");
|
|
4368
4355
|
} else if (content.length > MAX_STRING_LENGTH) {
|
|
4369
|
-
error(errors, `${
|
|
4356
|
+
error(errors, `${path30}/content`, "TOOL_RESULT_TOO_LARGE", "tool_result.content is too large.");
|
|
4370
4357
|
}
|
|
4371
4358
|
if (isError !== void 0 && typeof isError !== "boolean") {
|
|
4372
|
-
error(errors, `${
|
|
4359
|
+
error(errors, `${path30}/is_error`, "INVALID_TOOL_RESULT_ERROR", "tool_result.is_error must be boolean.");
|
|
4373
4360
|
}
|
|
4374
4361
|
if (typeof toolUseId !== "string" || toolUseId.length === 0 || typeof content !== "string") return void 0;
|
|
4375
4362
|
return {
|
|
@@ -4383,25 +4370,25 @@ function validateBlock(value, path29, errors) {
|
|
|
4383
4370
|
case "image": {
|
|
4384
4371
|
const source = value["source"];
|
|
4385
4372
|
if (!isRecord3(source)) {
|
|
4386
|
-
error(errors, `${
|
|
4373
|
+
error(errors, `${path30}/source`, "INVALID_IMAGE_SOURCE", "image.source must be an object.");
|
|
4387
4374
|
return void 0;
|
|
4388
4375
|
}
|
|
4389
4376
|
const sourceType = source["type"];
|
|
4390
4377
|
if (sourceType !== "base64" && sourceType !== "url") {
|
|
4391
|
-
error(errors, `${
|
|
4378
|
+
error(errors, `${path30}/source/type`, "INVALID_IMAGE_SOURCE_TYPE", "image.source.type must be base64 or url.");
|
|
4392
4379
|
return void 0;
|
|
4393
4380
|
}
|
|
4394
4381
|
const mediaType = source["media_type"];
|
|
4395
4382
|
const data = source["data"];
|
|
4396
4383
|
const url = source["url"];
|
|
4397
4384
|
if (mediaType !== void 0 && typeof mediaType !== "string") {
|
|
4398
|
-
error(errors, `${
|
|
4385
|
+
error(errors, `${path30}/source/media_type`, "INVALID_IMAGE_MEDIA_TYPE", "image.source.media_type must be a string.");
|
|
4399
4386
|
}
|
|
4400
4387
|
if (data !== void 0 && typeof data !== "string") {
|
|
4401
|
-
error(errors, `${
|
|
4388
|
+
error(errors, `${path30}/source/data`, "INVALID_IMAGE_DATA", "image.source.data must be a string.");
|
|
4402
4389
|
}
|
|
4403
4390
|
if (url !== void 0 && typeof url !== "string") {
|
|
4404
|
-
error(errors, `${
|
|
4391
|
+
error(errors, `${path30}/source/url`, "INVALID_IMAGE_URL", "image.source.url must be a string.");
|
|
4405
4392
|
}
|
|
4406
4393
|
return {
|
|
4407
4394
|
type: "image",
|
|
@@ -4417,13 +4404,13 @@ function validateBlock(value, path29, errors) {
|
|
|
4417
4404
|
const thinking = value["thinking"];
|
|
4418
4405
|
const signature = value["signature"];
|
|
4419
4406
|
if (typeof thinking !== "string") {
|
|
4420
|
-
error(errors, `${
|
|
4407
|
+
error(errors, `${path30}/thinking`, "INVALID_THINKING", "thinking.thinking must be a string.");
|
|
4421
4408
|
return void 0;
|
|
4422
4409
|
}
|
|
4423
4410
|
if (signature !== void 0 && typeof signature !== "string") {
|
|
4424
|
-
error(errors, `${
|
|
4411
|
+
error(errors, `${path30}/signature`, "INVALID_THINKING_SIGNATURE", "thinking.signature must be a string.");
|
|
4425
4412
|
}
|
|
4426
|
-
const providerMeta = validateProviderMeta(value["providerMeta"], `${
|
|
4413
|
+
const providerMeta = validateProviderMeta(value["providerMeta"], `${path30}/providerMeta`, errors);
|
|
4427
4414
|
return {
|
|
4428
4415
|
type: "thinking",
|
|
4429
4416
|
thinking,
|
|
@@ -4432,7 +4419,7 @@ function validateBlock(value, path29, errors) {
|
|
|
4432
4419
|
};
|
|
4433
4420
|
}
|
|
4434
4421
|
default:
|
|
4435
|
-
error(errors, `${
|
|
4422
|
+
error(errors, `${path30}/type`, "UNKNOWN_BLOCK_TYPE", `Unknown content block type: ${String(type)}`);
|
|
4436
4423
|
return void 0;
|
|
4437
4424
|
}
|
|
4438
4425
|
}
|
|
@@ -4450,39 +4437,39 @@ function validateContextEditorMessages(value, currentMessageCount = 0) {
|
|
|
4450
4437
|
error(errors, "/messages", "PAYLOAD_TOO_LARGE", "Context editor payload is too large.");
|
|
4451
4438
|
}
|
|
4452
4439
|
value.forEach((item, index) => {
|
|
4453
|
-
const
|
|
4440
|
+
const path30 = `/messages/${index}`;
|
|
4454
4441
|
if (!isRecord3(item)) {
|
|
4455
|
-
error(errors,
|
|
4442
|
+
error(errors, path30, "INVALID_MESSAGE", "Message must be an object.");
|
|
4456
4443
|
return;
|
|
4457
4444
|
}
|
|
4458
4445
|
const role = item["role"];
|
|
4459
4446
|
if (!isMessageRole(role)) {
|
|
4460
|
-
error(errors, `${
|
|
4447
|
+
error(errors, `${path30}/role`, "INVALID_ROLE", "Message role must be user, assistant, or system.");
|
|
4461
4448
|
return;
|
|
4462
4449
|
}
|
|
4463
4450
|
const rawContent = item["content"];
|
|
4464
4451
|
let content;
|
|
4465
4452
|
if (typeof rawContent === "string") {
|
|
4466
4453
|
if (rawContent.length > MAX_STRING_LENGTH) {
|
|
4467
|
-
error(errors, `${
|
|
4454
|
+
error(errors, `${path30}/content`, "CONTENT_TOO_LARGE", "Message content is too large.");
|
|
4468
4455
|
return;
|
|
4469
4456
|
}
|
|
4470
4457
|
content = rawContent;
|
|
4471
4458
|
} else if (Array.isArray(rawContent)) {
|
|
4472
4459
|
const blocks = [];
|
|
4473
4460
|
rawContent.forEach((block, blockIndex) => {
|
|
4474
|
-
const parsed = validateBlock(block, `${
|
|
4461
|
+
const parsed = validateBlock(block, `${path30}/content/${blockIndex}`, errors);
|
|
4475
4462
|
if (parsed) blocks.push(parsed);
|
|
4476
4463
|
});
|
|
4477
4464
|
content = blocks;
|
|
4478
4465
|
} else {
|
|
4479
|
-
error(errors, `${
|
|
4466
|
+
error(errors, `${path30}/content`, "INVALID_CONTENT", "Message content must be a string or content block array.");
|
|
4480
4467
|
return;
|
|
4481
4468
|
}
|
|
4482
4469
|
const ts = item["ts"];
|
|
4483
4470
|
if (ts !== void 0) {
|
|
4484
4471
|
if (typeof ts !== "string" || Number.isNaN(Date.parse(ts))) {
|
|
4485
|
-
error(errors, `${
|
|
4472
|
+
error(errors, `${path30}/ts`, "INVALID_TIMESTAMP", "Message ts must be an ISO-like timestamp string.");
|
|
4486
4473
|
return;
|
|
4487
4474
|
}
|
|
4488
4475
|
}
|
|
@@ -5420,15 +5407,15 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5420
5407
|
if (!m) continue;
|
|
5421
5408
|
const added = m[1] === "-" ? 0 : Number(m[1]);
|
|
5422
5409
|
const deleted = m[2] === "-" ? 0 : Number(m[2]);
|
|
5423
|
-
let
|
|
5424
|
-
if (
|
|
5410
|
+
let path30 = m[3] ?? "";
|
|
5411
|
+
if (path30 === "") {
|
|
5425
5412
|
i += 1;
|
|
5426
|
-
|
|
5413
|
+
path30 = parts[i + 1] ?? parts[i] ?? "";
|
|
5427
5414
|
i += 1;
|
|
5428
5415
|
}
|
|
5429
|
-
if (!
|
|
5430
|
-
const prev = counts.get(
|
|
5431
|
-
counts.set(
|
|
5416
|
+
if (!path30) continue;
|
|
5417
|
+
const prev = counts.get(path30) ?? { added: 0, deleted: 0 };
|
|
5418
|
+
counts.set(path30, { added: prev.added + added, deleted: prev.deleted + deleted });
|
|
5432
5419
|
}
|
|
5433
5420
|
};
|
|
5434
5421
|
parseNumstat(unstagedNumstat);
|
|
@@ -5440,7 +5427,7 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5440
5427
|
if (!rec || rec.length < 3) continue;
|
|
5441
5428
|
const x = rec[0] ?? " ";
|
|
5442
5429
|
const y = rec[1] ?? " ";
|
|
5443
|
-
const
|
|
5430
|
+
const path30 = rec.slice(3);
|
|
5444
5431
|
const isRename = x === "R" || x === "C" || y === "R" || y === "C";
|
|
5445
5432
|
if (isRename) i += 1;
|
|
5446
5433
|
let status;
|
|
@@ -5452,13 +5439,13 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5452
5439
|
else if (x === "D" || y === "D") status = "D";
|
|
5453
5440
|
else status = "M";
|
|
5454
5441
|
const staged = x !== " " && x !== "?";
|
|
5455
|
-
let added = counts.get(
|
|
5456
|
-
let deleted = counts.get(
|
|
5442
|
+
let added = counts.get(path30)?.added ?? 0;
|
|
5443
|
+
let deleted = counts.get(path30)?.deleted ?? 0;
|
|
5457
5444
|
if (status === "?") {
|
|
5458
5445
|
added = 0;
|
|
5459
5446
|
deleted = 0;
|
|
5460
5447
|
}
|
|
5461
|
-
files.push({ path:
|
|
5448
|
+
files.push({ path: path30, status, added, deleted, staged });
|
|
5462
5449
|
}
|
|
5463
5450
|
send(ws, { type: "git.changes", payload: { files } });
|
|
5464
5451
|
} catch (err) {
|
|
@@ -5469,21 +5456,21 @@ async function handleGitChanges(ws, projectRoot) {
|
|
|
5469
5456
|
}
|
|
5470
5457
|
}
|
|
5471
5458
|
var MAX_DIFF_BYTES = 2 * 1024 * 1024;
|
|
5472
|
-
async function handleGitDiff(ws, projectRoot,
|
|
5459
|
+
async function handleGitDiff(ws, projectRoot, path30) {
|
|
5473
5460
|
const cwd = projectRoot || void 0;
|
|
5474
|
-
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path:
|
|
5475
|
-
if (!
|
|
5461
|
+
const reply2 = (extra) => send(ws, { type: "git.diff", payload: { path: path30, ...extra } });
|
|
5462
|
+
if (!path30 || path30.includes("\0") || path30.includes("..") || nodePath.isAbsolute(path30)) {
|
|
5476
5463
|
reply2({ oldText: "", newText: "", error: "invalid path" });
|
|
5477
5464
|
return;
|
|
5478
5465
|
}
|
|
5479
5466
|
try {
|
|
5480
5467
|
const git = makeGit(cwd);
|
|
5481
5468
|
const { readFile: readFile11 } = await import("node:fs/promises");
|
|
5482
|
-
const { join:
|
|
5483
|
-
const oldText = await git(["show", `HEAD:${
|
|
5469
|
+
const { join: join16 } = await import("node:path");
|
|
5470
|
+
const oldText = await git(["show", `HEAD:${path30}`]);
|
|
5484
5471
|
let newText = "";
|
|
5485
5472
|
try {
|
|
5486
|
-
const abs = cwd ?
|
|
5473
|
+
const abs = cwd ? join16(cwd, path30) : path30;
|
|
5487
5474
|
const buf = await readFile11(abs);
|
|
5488
5475
|
if (buf.includes(0)) {
|
|
5489
5476
|
reply2({ oldText: "", newText: "", binary: true });
|
|
@@ -5545,10 +5532,7 @@ async function handleGoalSnapshotRoute(ws, msg, handlers) {
|
|
|
5545
5532
|
}
|
|
5546
5533
|
|
|
5547
5534
|
// src/server/goal-ws-handler.ts
|
|
5548
|
-
import {
|
|
5549
|
-
import {
|
|
5550
|
-
assignNickname
|
|
5551
|
-
} from "@wrongstack/core/coordination";
|
|
5535
|
+
import { assignNickname } from "@wrongstack/core/coordination";
|
|
5552
5536
|
import {
|
|
5553
5537
|
GoalAssessor,
|
|
5554
5538
|
GoalPlanner,
|
|
@@ -5556,6 +5540,7 @@ import {
|
|
|
5556
5540
|
PhaseOrchestrator,
|
|
5557
5541
|
PhaseStore
|
|
5558
5542
|
} from "@wrongstack/core/goal";
|
|
5543
|
+
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
5559
5544
|
import { WorktreeManager } from "@wrongstack/core/worktree";
|
|
5560
5545
|
|
|
5561
5546
|
// src/server/git-process.ts
|
|
@@ -5592,12 +5577,7 @@ function deriveTitle(goal) {
|
|
|
5592
5577
|
return trimmed || "Goal";
|
|
5593
5578
|
}
|
|
5594
5579
|
async function commitsSince(cwd, baseSha, branch) {
|
|
5595
|
-
const output = await gitStdout(cwd, [
|
|
5596
|
-
"log",
|
|
5597
|
-
"--reverse",
|
|
5598
|
-
"--format=%H",
|
|
5599
|
-
`${baseSha}..${branch}`
|
|
5600
|
-
]);
|
|
5580
|
+
const output = await gitStdout(cwd, ["log", "--reverse", "--format=%H", `${baseSha}..${branch}`]);
|
|
5601
5581
|
if (output === null) return [];
|
|
5602
5582
|
return output.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
5603
5583
|
}
|
|
@@ -5648,6 +5628,17 @@ var GoalWebSocketHandler = class {
|
|
|
5648
5628
|
ws.on("error", () => this.clients.delete(client));
|
|
5649
5629
|
this.sendState(client);
|
|
5650
5630
|
}
|
|
5631
|
+
/** Release timers, in-flight work, and socket references owned by this host. */
|
|
5632
|
+
dispose() {
|
|
5633
|
+
this.stopping = true;
|
|
5634
|
+
this.abort?.abort();
|
|
5635
|
+
this.abort = null;
|
|
5636
|
+
this.assessAbort?.abort();
|
|
5637
|
+
this.assessAbort = null;
|
|
5638
|
+
this.orchestrator?.stop();
|
|
5639
|
+
this.stopBroadcast();
|
|
5640
|
+
this.clients.clear();
|
|
5641
|
+
}
|
|
5651
5642
|
async handleMessage(ws, msg) {
|
|
5652
5643
|
switch (msg.type) {
|
|
5653
5644
|
case "goal.assess":
|
|
@@ -5695,7 +5686,8 @@ var GoalWebSocketHandler = class {
|
|
|
5695
5686
|
}
|
|
5696
5687
|
case "goal.assignTask": {
|
|
5697
5688
|
const { taskId, agentId, agentName } = msg.payload;
|
|
5698
|
-
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
5689
|
+
if (this.orchestrator?.setTaskAssignee(taskId, agentId, agentName))
|
|
5690
|
+
this.afterBoardMutation();
|
|
5699
5691
|
break;
|
|
5700
5692
|
}
|
|
5701
5693
|
case "goal.addTask": {
|
|
@@ -5740,7 +5732,10 @@ var GoalWebSocketHandler = class {
|
|
|
5740
5732
|
this.graph = graph;
|
|
5741
5733
|
this.broadcast({ type: "goal.state", payload: this.buildState() });
|
|
5742
5734
|
} else {
|
|
5743
|
-
this.broadcast({
|
|
5735
|
+
this.broadcast({
|
|
5736
|
+
type: "goal.error",
|
|
5737
|
+
payload: { message: `Graph not found: ${graphId}` }
|
|
5738
|
+
});
|
|
5744
5739
|
}
|
|
5745
5740
|
}
|
|
5746
5741
|
break;
|
|
@@ -5763,10 +5758,13 @@ var GoalWebSocketHandler = class {
|
|
|
5763
5758
|
const mySeq = ++this.assessSeq;
|
|
5764
5759
|
const sendResult7 = (result) => {
|
|
5765
5760
|
if (mySeq !== this.assessSeq) return;
|
|
5766
|
-
sendSerialized(
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5761
|
+
sendSerialized(
|
|
5762
|
+
ws,
|
|
5763
|
+
JSON.stringify({
|
|
5764
|
+
type: "goal.assess.result",
|
|
5765
|
+
payload: { ...result, reqSeq: seq }
|
|
5766
|
+
})
|
|
5767
|
+
);
|
|
5770
5768
|
};
|
|
5771
5769
|
if (!goal.trim()) {
|
|
5772
5770
|
sendResult7({
|
|
@@ -5812,15 +5810,24 @@ var GoalWebSocketHandler = class {
|
|
|
5812
5810
|
const multiBoard = payload?.multiBoard ?? false;
|
|
5813
5811
|
const verifyTasks = payload?.verifyTasks ?? false;
|
|
5814
5812
|
const chimeraReview = payload?.chimeraReview ?? false;
|
|
5815
|
-
|
|
5813
|
+
const runAbort = new AbortController();
|
|
5814
|
+
this.abort = runAbort;
|
|
5816
5815
|
this.stopping = false;
|
|
5817
|
-
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal,
|
|
5818
|
-
if (this.stopping ||
|
|
5816
|
+
const phases = Array.isArray(payload?.phases) ? payload.phases : await this.planPhases(goal, runAbort.signal);
|
|
5817
|
+
if (this.stopping || runAbort.signal.aborted) {
|
|
5819
5818
|
this.broadcast({ type: "goal.stopped", payload: { title } });
|
|
5820
5819
|
return;
|
|
5821
5820
|
}
|
|
5822
5821
|
this.logger.info(`[Goal] Starting: ${title}`);
|
|
5823
|
-
const graph = await new PhaseGraphBuilder({
|
|
5822
|
+
const graph = await new PhaseGraphBuilder({
|
|
5823
|
+
title,
|
|
5824
|
+
description: goal,
|
|
5825
|
+
phases,
|
|
5826
|
+
autonomous,
|
|
5827
|
+
multiBoard,
|
|
5828
|
+
verifyTasks,
|
|
5829
|
+
chimeraReview
|
|
5830
|
+
}).build();
|
|
5824
5831
|
this.graph = graph;
|
|
5825
5832
|
await this.store.save(graph);
|
|
5826
5833
|
const useWorktrees = payload?.worktrees ?? process.env["WRONGSTACK_GOAL_WORKTREES"] !== "0";
|
|
@@ -5839,9 +5846,10 @@ var GoalWebSocketHandler = class {
|
|
|
5839
5846
|
maybeVerify.verifyPhase = (async (phase, env) => {
|
|
5840
5847
|
const cwd = env?.cwd ?? this.projectRoot;
|
|
5841
5848
|
try {
|
|
5842
|
-
const {
|
|
5849
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
5843
5850
|
const result = await new Promise((resolve15) => {
|
|
5844
|
-
|
|
5851
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
5852
|
+
execFile2(npxCommand, ["tsc", "--noEmit"], { cwd, timeout: 6e4 }, (err, stdout, stderr) => {
|
|
5845
5853
|
if (err && err.code === "ENOENT") {
|
|
5846
5854
|
resolve15("[verify] tsc not found \u2014 skipping");
|
|
5847
5855
|
return;
|
|
@@ -5983,11 +5991,41 @@ Run npx tsc --noEmit to verify the fix. Output the fixed file paths.`;
|
|
|
5983
5991
|
/** Generic fallback phases when the LLM planner produces nothing usable. */
|
|
5984
5992
|
defaultPhases() {
|
|
5985
5993
|
return [
|
|
5986
|
-
{
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5994
|
+
{
|
|
5995
|
+
name: "Discovery",
|
|
5996
|
+
description: "Requirements gathering",
|
|
5997
|
+
priority: "high",
|
|
5998
|
+
estimateHours: 2,
|
|
5999
|
+
parallelizable: false
|
|
6000
|
+
},
|
|
6001
|
+
{
|
|
6002
|
+
name: "Design",
|
|
6003
|
+
description: "Architecture and design",
|
|
6004
|
+
priority: "critical",
|
|
6005
|
+
estimateHours: 4,
|
|
6006
|
+
parallelizable: false
|
|
6007
|
+
},
|
|
6008
|
+
{
|
|
6009
|
+
name: "Implementation",
|
|
6010
|
+
description: "Core development",
|
|
6011
|
+
priority: "critical",
|
|
6012
|
+
estimateHours: 12,
|
|
6013
|
+
parallelizable: false
|
|
6014
|
+
},
|
|
6015
|
+
{
|
|
6016
|
+
name: "Testing",
|
|
6017
|
+
description: "Unit and integration tests",
|
|
6018
|
+
priority: "high",
|
|
6019
|
+
estimateHours: 6,
|
|
6020
|
+
parallelizable: true
|
|
6021
|
+
},
|
|
6022
|
+
{
|
|
6023
|
+
name: "Deployment",
|
|
6024
|
+
description: "Deploy to production",
|
|
6025
|
+
priority: "medium",
|
|
6026
|
+
estimateHours: 2,
|
|
6027
|
+
parallelizable: false
|
|
6028
|
+
}
|
|
5991
6029
|
];
|
|
5992
6030
|
}
|
|
5993
6031
|
/** Plan phases+todos for the goal via the LLM; fall back to defaults on failure.
|
|
@@ -6068,8 +6106,10 @@ Type: ${task.type}`;
|
|
|
6068
6106
|
try {
|
|
6069
6107
|
const result_ = await this.agent.run(reviewPrompt);
|
|
6070
6108
|
if (result_.status === "done" && result_.finalText) {
|
|
6071
|
-
this.logger.info(
|
|
6072
|
-
${
|
|
6109
|
+
this.logger.info(
|
|
6110
|
+
`[Goal] Chimera review for "${task.title}":
|
|
6111
|
+
${result_.finalText.slice(0, 2e3)}`
|
|
6112
|
+
);
|
|
6073
6113
|
}
|
|
6074
6114
|
} catch (err) {
|
|
6075
6115
|
this.logger.warn(`[Goal] Chimera review failed for "${task.title}": ${toErrorMessage2(err)}`);
|
|
@@ -6122,7 +6162,16 @@ ${result_.finalText.slice(0, 2e3)}`);
|
|
|
6122
6162
|
}
|
|
6123
6163
|
buildState(activePhaseId) {
|
|
6124
6164
|
if (!this.graph) {
|
|
6125
|
-
return {
|
|
6165
|
+
return {
|
|
6166
|
+
phases: [],
|
|
6167
|
+
tasks: [],
|
|
6168
|
+
overallPercent: 0,
|
|
6169
|
+
autonomous: true,
|
|
6170
|
+
title: "",
|
|
6171
|
+
multiBoard: false,
|
|
6172
|
+
verifyTasks: false,
|
|
6173
|
+
chimeraReview: false
|
|
6174
|
+
};
|
|
6126
6175
|
}
|
|
6127
6176
|
const phases = Array.from(this.graph.phases.values());
|
|
6128
6177
|
const currentActiveId = activePhaseId || phases.find((p) => p.status === "running")?.id || phases[0]?.id || "";
|
|
@@ -6620,9 +6669,9 @@ async function handleApiAnalyticsSummary(res) {
|
|
|
6620
6669
|
}
|
|
6621
6670
|
|
|
6622
6671
|
// src/server/http-server.ts
|
|
6623
|
-
import * as
|
|
6672
|
+
import * as fs10 from "node:fs/promises";
|
|
6624
6673
|
import * as http from "node:http";
|
|
6625
|
-
import * as
|
|
6674
|
+
import * as path13 from "node:path";
|
|
6626
6675
|
import * as v8 from "node:v8";
|
|
6627
6676
|
import { getIndexState as getIndexState2 } from "@wrongstack/tools";
|
|
6628
6677
|
|
|
@@ -7306,18 +7355,153 @@ async function handleApiFleetBroadcast(res, req, globalRoot) {
|
|
|
7306
7355
|
}
|
|
7307
7356
|
}
|
|
7308
7357
|
|
|
7309
|
-
// src/server/
|
|
7358
|
+
// src/server/memory-diagnostics.ts
|
|
7310
7359
|
import * as fs8 from "node:fs/promises";
|
|
7311
7360
|
import * as path11 from "node:path";
|
|
7361
|
+
var DEFAULT_TAIL_BYTES = 1024 * 1024;
|
|
7362
|
+
var MAX_PROCESSES = 32;
|
|
7363
|
+
function finiteNumber(record, key) {
|
|
7364
|
+
const value = record[key];
|
|
7365
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
7366
|
+
}
|
|
7367
|
+
function stringValue(record, key) {
|
|
7368
|
+
const value = record[key];
|
|
7369
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
7370
|
+
}
|
|
7371
|
+
function toProcessDiagnostic(record) {
|
|
7372
|
+
const pid = finiteNumber(record, "pid");
|
|
7373
|
+
const ts = stringValue(record, "ts");
|
|
7374
|
+
if (pid === void 0 || ts === void 0) return null;
|
|
7375
|
+
const queueEntries = finiteNumber(record, "hqQueueEntries");
|
|
7376
|
+
const queueBytes = finiteNumber(record, "hqQueueBytes");
|
|
7377
|
+
const queueMaxBytes = finiteNumber(record, "hqQueueMaxBytes");
|
|
7378
|
+
const snapshotInFlight = finiteNumber(record, "hqSnapshotInFlight");
|
|
7379
|
+
const snapshotPending = finiteNumber(record, "hqSnapshotPending");
|
|
7380
|
+
const snapshotTimerScheduled = finiteNumber(record, "hqSnapshotTimerScheduled");
|
|
7381
|
+
const eventInFlight = finiteNumber(record, "hqEventInFlight");
|
|
7382
|
+
const eventPending = finiteNumber(record, "hqEventPending");
|
|
7383
|
+
const eventCoalesced = finiteNumber(record, "hqEventCoalesced");
|
|
7384
|
+
const eventDropped = finiteNumber(record, "hqEventDropped");
|
|
7385
|
+
return {
|
|
7386
|
+
pid,
|
|
7387
|
+
surface: stringValue(record, "surface") ?? "unknown",
|
|
7388
|
+
...stringValue(record, "sessionId") !== void 0 ? { sessionId: stringValue(record, "sessionId") } : {},
|
|
7389
|
+
ts,
|
|
7390
|
+
memory: {
|
|
7391
|
+
rss: finiteNumber(record, "rss") ?? 0,
|
|
7392
|
+
heapUsed: finiteNumber(record, "heapUsed") ?? 0,
|
|
7393
|
+
heapTotal: finiteNumber(record, "heapTotal") ?? 0,
|
|
7394
|
+
retainedHeapUsed: finiteNumber(record, "retainedHeapUsed"),
|
|
7395
|
+
nativeResidual: finiteNumber(record, "nativeResidual"),
|
|
7396
|
+
external: finiteNumber(record, "external"),
|
|
7397
|
+
arrayBuffers: finiteNumber(record, "arrayBuffers")
|
|
7398
|
+
},
|
|
7399
|
+
signal: stringValue(record, "memorySignal"),
|
|
7400
|
+
heapGrowthBytesPerHour: finiteNumber(record, "heapGrowthBytesPerHour"),
|
|
7401
|
+
rssGrowthBytesPerHour: finiteNumber(record, "rssGrowthBytesPerHour"),
|
|
7402
|
+
workload: {
|
|
7403
|
+
messages: finiteNumber(record, "messages"),
|
|
7404
|
+
messageEstimatedTokens: finiteNumber(record, "messageEstimatedTokens"),
|
|
7405
|
+
historyEntries: finiteNumber(record, "historyEntries"),
|
|
7406
|
+
historyMountedEntries: finiteNumber(record, "historyMountedEntries"),
|
|
7407
|
+
historyCachedGroups: finiteNumber(record, "historyCachedGroups"),
|
|
7408
|
+
appRenders: finiteNumber(record, "appRenders"),
|
|
7409
|
+
metricsDroppedObservations: finiteNumber(record, "metricsDroppedObservations"),
|
|
7410
|
+
kanbanSyncActive: finiteNumber(record, "kanbanSyncActive") === 1,
|
|
7411
|
+
kanbanSyncPendingBoards: finiteNumber(record, "kanbanSyncPendingBoards"),
|
|
7412
|
+
kanbanSyncFullRescanPending: finiteNumber(record, "kanbanSyncFullRescanPending") === 1,
|
|
7413
|
+
kanbanSyncRemoteApplyQueued: finiteNumber(record, "kanbanSyncRemoteApplyQueued") === 1,
|
|
7414
|
+
kanbanSyncPendingRemoteBoards: finiteNumber(record, "kanbanSyncPendingRemoteBoards"),
|
|
7415
|
+
kanbanSyncPublishRuns: finiteNumber(record, "kanbanSyncPublishRuns"),
|
|
7416
|
+
kanbanSyncCoalescedRefreshes: finiteNumber(record, "kanbanSyncCoalescedRefreshes"),
|
|
7417
|
+
kanbanSupervisorSnapshots: finiteNumber(record, "kanbanSupervisorSnapshots"),
|
|
7418
|
+
kanbanSupervisorScheduledBoards: finiteNumber(record, "kanbanSupervisorScheduledBoards"),
|
|
7419
|
+
kanbanSupervisorAgentCooldowns: finiteNumber(record, "kanbanSupervisorAgentCooldowns"),
|
|
7420
|
+
kanbanSupervisorRunningAgents: finiteNumber(record, "kanbanSupervisorRunningAgents")
|
|
7421
|
+
},
|
|
7422
|
+
resources: {
|
|
7423
|
+
active: finiteNumber(record, "activeResources"),
|
|
7424
|
+
types: stringValue(record, "activeResourceTypes")
|
|
7425
|
+
},
|
|
7426
|
+
...queueEntries !== void 0 && queueBytes !== void 0 && queueMaxBytes !== void 0 ? {
|
|
7427
|
+
hqQueue: {
|
|
7428
|
+
entries: queueEntries,
|
|
7429
|
+
bytes: queueBytes,
|
|
7430
|
+
maxBytes: queueMaxBytes,
|
|
7431
|
+
droppedFrames: finiteNumber(record, "hqQueueDroppedFrames") ?? 0,
|
|
7432
|
+
droppedBytes: finiteNumber(record, "hqQueueDroppedBytes") ?? 0,
|
|
7433
|
+
coalescedFrames: finiteNumber(record, "hqQueueCoalescedFrames") ?? 0,
|
|
7434
|
+
coalescedBytes: finiteNumber(record, "hqQueueCoalescedBytes") ?? 0
|
|
7435
|
+
}
|
|
7436
|
+
} : {},
|
|
7437
|
+
...snapshotInFlight !== void 0 || snapshotPending !== void 0 || snapshotTimerScheduled !== void 0 ? {
|
|
7438
|
+
hqSnapshot: {
|
|
7439
|
+
inFlight: snapshotInFlight === 1,
|
|
7440
|
+
pending: snapshotPending === 1,
|
|
7441
|
+
timerScheduled: snapshotTimerScheduled === 1,
|
|
7442
|
+
eventInFlight: eventInFlight === 1,
|
|
7443
|
+
pendingEvents: eventPending ?? 0,
|
|
7444
|
+
coalescedEvents: eventCoalesced ?? 0,
|
|
7445
|
+
droppedEvents: eventDropped ?? 0
|
|
7446
|
+
}
|
|
7447
|
+
} : {},
|
|
7448
|
+
profileTopStack: stringValue(record, "memoryProfileTopStack"),
|
|
7449
|
+
profileTopStackBytes: finiteNumber(record, "memoryProfileTopStackBytes")
|
|
7450
|
+
};
|
|
7451
|
+
}
|
|
7452
|
+
async function readRecentProcessMemoryDiagnostics(globalRoot, tailBytes = DEFAULT_TAIL_BYTES) {
|
|
7453
|
+
if (!globalRoot) return [];
|
|
7454
|
+
const heapLog = path11.join(globalRoot, "logs", "heap.jsonl");
|
|
7455
|
+
let handle;
|
|
7456
|
+
try {
|
|
7457
|
+
handle = await fs8.open(heapLog, "r");
|
|
7458
|
+
const stat3 = await handle.stat();
|
|
7459
|
+
const length = Math.min(stat3.size, Math.max(1, tailBytes));
|
|
7460
|
+
if (length === 0) return [];
|
|
7461
|
+
const start = stat3.size - length;
|
|
7462
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
7463
|
+
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
7464
|
+
let text = buffer.subarray(0, bytesRead).toString("utf8");
|
|
7465
|
+
if (start > 0) {
|
|
7466
|
+
const firstNewline = text.indexOf("\n");
|
|
7467
|
+
if (firstNewline === -1) return [];
|
|
7468
|
+
text = text.slice(firstNewline + 1);
|
|
7469
|
+
}
|
|
7470
|
+
const newestByPid = /* @__PURE__ */ new Map();
|
|
7471
|
+
const lines = text.split(/\r?\n/u);
|
|
7472
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
7473
|
+
const line = lines[index]?.trim();
|
|
7474
|
+
if (!line) continue;
|
|
7475
|
+
try {
|
|
7476
|
+
const parsed = JSON.parse(line);
|
|
7477
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
|
|
7478
|
+
const diagnostic = toProcessDiagnostic(parsed);
|
|
7479
|
+
if (!diagnostic || newestByPid.has(diagnostic.pid)) continue;
|
|
7480
|
+
newestByPid.set(diagnostic.pid, diagnostic);
|
|
7481
|
+
if (newestByPid.size >= MAX_PROCESSES) break;
|
|
7482
|
+
} catch {
|
|
7483
|
+
}
|
|
7484
|
+
}
|
|
7485
|
+
return [...newestByPid.values()].sort((a, b) => b.ts.localeCompare(a.ts));
|
|
7486
|
+
} catch {
|
|
7487
|
+
return [];
|
|
7488
|
+
} finally {
|
|
7489
|
+
await handle?.close().catch(() => void 0);
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7493
|
+
// src/server/projects-manifest.ts
|
|
7494
|
+
import * as fs9 from "node:fs/promises";
|
|
7495
|
+
import * as path12 from "node:path";
|
|
7312
7496
|
import { ConfigError } from "@wrongstack/core/types";
|
|
7313
7497
|
import { projectSlug, withFileLock } from "@wrongstack/core/utils";
|
|
7314
7498
|
function projectsJsonPath(globalConfigPath) {
|
|
7315
|
-
const base =
|
|
7316
|
-
return
|
|
7499
|
+
const base = path12.dirname(globalConfigPath);
|
|
7500
|
+
return path12.join(base, "projects.json");
|
|
7317
7501
|
}
|
|
7318
7502
|
async function loadManifest(globalConfigPath) {
|
|
7319
7503
|
try {
|
|
7320
|
-
const raw = await
|
|
7504
|
+
const raw = await fs9.readFile(projectsJsonPath(globalConfigPath), "utf8");
|
|
7321
7505
|
const parsed = JSON.parse(raw);
|
|
7322
7506
|
return { projects: parsed.projects ?? [] };
|
|
7323
7507
|
} catch {
|
|
@@ -7326,37 +7510,37 @@ async function loadManifest(globalConfigPath) {
|
|
|
7326
7510
|
}
|
|
7327
7511
|
async function saveManifest(manifest, globalConfigPath) {
|
|
7328
7512
|
const file = projectsJsonPath(globalConfigPath);
|
|
7329
|
-
await
|
|
7330
|
-
await
|
|
7513
|
+
await fs9.mkdir(path12.dirname(file), { recursive: true });
|
|
7514
|
+
await fs9.writeFile(file, JSON.stringify(manifest, null, 2), "utf8");
|
|
7331
7515
|
}
|
|
7332
7516
|
function generateProjectSlug(rootPath) {
|
|
7333
7517
|
return projectSlug(rootPath);
|
|
7334
7518
|
}
|
|
7335
7519
|
async function ensureProjectDataDir(slug, globalConfigPath) {
|
|
7336
|
-
const base =
|
|
7337
|
-
const dir =
|
|
7338
|
-
await
|
|
7520
|
+
const base = path12.dirname(globalConfigPath);
|
|
7521
|
+
const dir = path12.join(base, "projects", slug);
|
|
7522
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
7339
7523
|
return dir;
|
|
7340
7524
|
}
|
|
7341
7525
|
async function touchProjectInManifest(options, globalConfigPath) {
|
|
7342
|
-
const root =
|
|
7526
|
+
const root = path12.resolve(options.projectRoot);
|
|
7343
7527
|
const file = projectsJsonPath(globalConfigPath);
|
|
7344
7528
|
let entry;
|
|
7345
7529
|
await withFileLock(file, async () => {
|
|
7346
7530
|
const manifest = await loadManifest(globalConfigPath);
|
|
7347
7531
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7348
|
-
entry = manifest.projects.find((candidate) =>
|
|
7532
|
+
entry = manifest.projects.find((candidate) => path12.resolve(candidate.root) === root);
|
|
7349
7533
|
if (entry) {
|
|
7350
7534
|
entry.lastSeen = now;
|
|
7351
|
-
if (options.workingDir) entry.lastWorkingDir =
|
|
7535
|
+
if (options.workingDir) entry.lastWorkingDir = path12.resolve(options.workingDir);
|
|
7352
7536
|
} else {
|
|
7353
7537
|
entry = {
|
|
7354
|
-
name: options.name ??
|
|
7538
|
+
name: options.name ?? path12.basename(root),
|
|
7355
7539
|
root,
|
|
7356
7540
|
slug: generateProjectSlug(root),
|
|
7357
7541
|
createdAt: now,
|
|
7358
7542
|
lastSeen: now,
|
|
7359
|
-
lastWorkingDir: options.workingDir ?
|
|
7543
|
+
lastWorkingDir: options.workingDir ? path12.resolve(options.workingDir) : void 0
|
|
7360
7544
|
};
|
|
7361
7545
|
manifest.projects.push(entry);
|
|
7362
7546
|
}
|
|
@@ -7767,9 +7951,9 @@ function buildCspHeader(publicWsUrl, host, port) {
|
|
|
7767
7951
|
return `default-src 'self'; script-src ${scriptSrc}; style-src 'self' 'unsafe-inline'; connect-src ${Array.from(connect).join(" ")}; img-src 'self' data:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`;
|
|
7768
7952
|
}
|
|
7769
7953
|
function isInsideDist(candidate, distDir) {
|
|
7770
|
-
const root =
|
|
7771
|
-
const resolved =
|
|
7772
|
-
return resolved === root || resolved.startsWith(root +
|
|
7954
|
+
const root = path13.resolve(distDir);
|
|
7955
|
+
const resolved = path13.resolve(candidate);
|
|
7956
|
+
return resolved === root || resolved.startsWith(root + path13.sep);
|
|
7773
7957
|
}
|
|
7774
7958
|
function decodeSessionId(segment) {
|
|
7775
7959
|
try {
|
|
@@ -7789,7 +7973,7 @@ function strictDecodeParam(segment, res) {
|
|
|
7789
7973
|
}
|
|
7790
7974
|
function createHttpServer(opts) {
|
|
7791
7975
|
const port = opts.port ?? Number.parseInt(process.env["PORT"] ?? "3456", 10);
|
|
7792
|
-
const distDir =
|
|
7976
|
+
const distDir = path13.resolve(opts.distDir);
|
|
7793
7977
|
const requireAccessToken = Boolean(opts.requireToken) || !isLoopbackBind(opts.host);
|
|
7794
7978
|
let techStackRuntime = null;
|
|
7795
7979
|
const getTechStackRuntime = async () => {
|
|
@@ -8117,11 +8301,7 @@ function createHttpServer(opts) {
|
|
|
8117
8301
|
if (researchMatch && req.method === "POST") {
|
|
8118
8302
|
const pkg = strictDecodeParam(researchMatch[1], res);
|
|
8119
8303
|
if (pkg === null) return;
|
|
8120
|
-
await handleTechStackDependencyResearch(
|
|
8121
|
-
res,
|
|
8122
|
-
deps2,
|
|
8123
|
-
pkg
|
|
8124
|
-
);
|
|
8304
|
+
await handleTechStackDependencyResearch(res, deps2, pkg);
|
|
8125
8305
|
return;
|
|
8126
8306
|
}
|
|
8127
8307
|
} catch (error2) {
|
|
@@ -8157,6 +8337,7 @@ function createHttpServer(opts) {
|
|
|
8157
8337
|
return;
|
|
8158
8338
|
}
|
|
8159
8339
|
if (url.pathname === "/debug/system" && req.method === "GET") {
|
|
8340
|
+
const processes = await readRecentProcessMemoryDiagnostics(opts.globalRoot);
|
|
8160
8341
|
res.writeHead(200, {
|
|
8161
8342
|
"Content-Type": "application/json",
|
|
8162
8343
|
"Cache-Control": "no-store"
|
|
@@ -8169,6 +8350,7 @@ function createHttpServer(opts) {
|
|
|
8169
8350
|
uptime: process.uptime(),
|
|
8170
8351
|
cpuUsage: process.cpuUsage(),
|
|
8171
8352
|
codebaseIndexServer: getIndexState2().server,
|
|
8353
|
+
processes,
|
|
8172
8354
|
timestamp: Date.now()
|
|
8173
8355
|
})
|
|
8174
8356
|
);
|
|
@@ -8176,24 +8358,24 @@ function createHttpServer(opts) {
|
|
|
8176
8358
|
}
|
|
8177
8359
|
let filePath;
|
|
8178
8360
|
if (url.pathname === "/" || url.pathname === "") {
|
|
8179
|
-
filePath =
|
|
8361
|
+
filePath = path13.join(distDir, "index.html");
|
|
8180
8362
|
} else {
|
|
8181
|
-
filePath =
|
|
8363
|
+
filePath = path13.join(distDir, url.pathname);
|
|
8182
8364
|
}
|
|
8183
|
-
const resolvedPath =
|
|
8365
|
+
const resolvedPath = path13.resolve(filePath);
|
|
8184
8366
|
if (!isInsideDist(resolvedPath, distDir)) {
|
|
8185
8367
|
res.writeHead(403, { "Content-Type": "text/plain" });
|
|
8186
8368
|
res.end("Forbidden");
|
|
8187
8369
|
return;
|
|
8188
8370
|
}
|
|
8189
|
-
const ext =
|
|
8371
|
+
const ext = path13.extname(resolvedPath);
|
|
8190
8372
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
8191
8373
|
res.setHeader("Content-Type", contentType);
|
|
8192
8374
|
setStaticSecurityHeaders(res);
|
|
8193
8375
|
if (ext === ".html") {
|
|
8194
8376
|
if (!shouldSetAuthCookie) res.setHeader("Cache-Control", "no-cache");
|
|
8195
8377
|
res.setHeader("Content-Security-Policy", buildCspHeader(opts.publicWsUrl, opts.host, port));
|
|
8196
|
-
const html = await
|
|
8378
|
+
const html = await fs10.readFile(resolvedPath, "utf8");
|
|
8197
8379
|
res.writeHead(200);
|
|
8198
8380
|
res.end(injectWsConfig(html, { publicWsUrl: opts.publicWsUrl }));
|
|
8199
8381
|
return;
|
|
@@ -8204,13 +8386,13 @@ function createHttpServer(opts) {
|
|
|
8204
8386
|
url.pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "public, max-age=3600"
|
|
8205
8387
|
);
|
|
8206
8388
|
}
|
|
8207
|
-
const fileContent = await
|
|
8389
|
+
const fileContent = await fs10.readFile(resolvedPath);
|
|
8208
8390
|
res.writeHead(200);
|
|
8209
8391
|
res.end(fileContent);
|
|
8210
8392
|
} catch (err) {
|
|
8211
8393
|
if (err.code === "ENOENT") {
|
|
8212
8394
|
try {
|
|
8213
|
-
const html = await
|
|
8395
|
+
const html = await fs10.readFile(path13.join(distDir, "index.html"), "utf8");
|
|
8214
8396
|
setStaticSecurityHeaders(res);
|
|
8215
8397
|
res.writeHead(200, {
|
|
8216
8398
|
"Content-Type": "text/html",
|
|
@@ -8240,14 +8422,14 @@ function createHttpServer(opts) {
|
|
|
8240
8422
|
|
|
8241
8423
|
// src/server/instance-registry.ts
|
|
8242
8424
|
import * as os from "node:os";
|
|
8243
|
-
import * as
|
|
8244
|
-
import * as
|
|
8425
|
+
import * as path14 from "node:path";
|
|
8426
|
+
import * as fs11 from "node:fs/promises";
|
|
8245
8427
|
import { atomicWrite as atomicWrite4 } from "@wrongstack/core/utils";
|
|
8246
8428
|
function defaultBaseDir() {
|
|
8247
|
-
return
|
|
8429
|
+
return path14.join(os.homedir(), ".wrongstack");
|
|
8248
8430
|
}
|
|
8249
8431
|
function registryPath(baseDir = defaultBaseDir()) {
|
|
8250
|
-
return
|
|
8432
|
+
return path14.join(baseDir, "webui-instances.json");
|
|
8251
8433
|
}
|
|
8252
8434
|
function isPidAlive(pid) {
|
|
8253
8435
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -8260,7 +8442,7 @@ function isPidAlive(pid) {
|
|
|
8260
8442
|
}
|
|
8261
8443
|
async function load(file) {
|
|
8262
8444
|
try {
|
|
8263
|
-
const raw = await
|
|
8445
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
8264
8446
|
const parsed = JSON.parse(raw);
|
|
8265
8447
|
if (parsed?.version === 1 && Array.isArray(parsed.instances)) {
|
|
8266
8448
|
return parsed;
|
|
@@ -10058,14 +10240,14 @@ function registerShutdownHandlers(res) {
|
|
|
10058
10240
|
}
|
|
10059
10241
|
|
|
10060
10242
|
// src/server/config-doctor.ts
|
|
10061
|
-
import * as
|
|
10243
|
+
import * as fs12 from "node:fs/promises";
|
|
10062
10244
|
import {
|
|
10063
10245
|
repairConfigDefaults
|
|
10064
10246
|
} from "@wrongstack/core/storage";
|
|
10065
10247
|
import { atomicWrite as atomicWrite5 } from "@wrongstack/core/utils";
|
|
10066
10248
|
import { decryptConfigSecrets } from "@wrongstack/core/security";
|
|
10067
10249
|
async function readConfig(file, vault) {
|
|
10068
|
-
const raw = await
|
|
10250
|
+
const raw = await fs12.readFile(file, "utf8");
|
|
10069
10251
|
const parsed = JSON.parse(raw);
|
|
10070
10252
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10071
10253
|
throw new Error("Active profile config must contain a JSON object.");
|
|
@@ -10129,6 +10311,7 @@ async function handleConfigDoctor(ws, apply, deps2) {
|
|
|
10129
10311
|
|
|
10130
10312
|
// src/server/mailbox-handlers.ts
|
|
10131
10313
|
import {
|
|
10314
|
+
actionToAckInput,
|
|
10132
10315
|
getSharedProjectMailbox,
|
|
10133
10316
|
isMailboxMessageVisibleTo,
|
|
10134
10317
|
MAILBOX_TYPE_PROPERTIES,
|
|
@@ -10145,6 +10328,43 @@ function getMailboxForDeps(deps2) {
|
|
|
10145
10328
|
const dir = resolveProjectDir(projectRoot, globalRoot);
|
|
10146
10329
|
return getSharedProjectMailbox(dir, deps2.events);
|
|
10147
10330
|
}
|
|
10331
|
+
async function handleMailboxAction(ws, deps2, payload) {
|
|
10332
|
+
const mb = getMailboxForDeps(deps2);
|
|
10333
|
+
if (!mb) {
|
|
10334
|
+
send(ws, {
|
|
10335
|
+
type: "mailbox.action_result",
|
|
10336
|
+
payload: {
|
|
10337
|
+
requestId: payload.requestId,
|
|
10338
|
+
success: false,
|
|
10339
|
+
error: "No project root available"
|
|
10340
|
+
}
|
|
10341
|
+
});
|
|
10342
|
+
return;
|
|
10343
|
+
}
|
|
10344
|
+
try {
|
|
10345
|
+
const message = payload.action === "soft-delete" ? await mb.softDelete(payload.mailId, payload.readerId) : await mb.ack(actionToAckInput(payload.action, payload));
|
|
10346
|
+
send(ws, {
|
|
10347
|
+
type: "mailbox.action_result",
|
|
10348
|
+
payload: {
|
|
10349
|
+
requestId: payload.requestId,
|
|
10350
|
+
success: message !== null,
|
|
10351
|
+
action: payload.action,
|
|
10352
|
+
mailId: payload.mailId
|
|
10353
|
+
}
|
|
10354
|
+
});
|
|
10355
|
+
} catch (err) {
|
|
10356
|
+
send(ws, {
|
|
10357
|
+
type: "mailbox.action_result",
|
|
10358
|
+
payload: {
|
|
10359
|
+
requestId: payload.requestId,
|
|
10360
|
+
success: false,
|
|
10361
|
+
action: payload.action,
|
|
10362
|
+
mailId: payload.mailId,
|
|
10363
|
+
error: errMessage(err)
|
|
10364
|
+
}
|
|
10365
|
+
});
|
|
10366
|
+
}
|
|
10367
|
+
}
|
|
10148
10368
|
async function handleMailboxSend(ws, deps2, payload) {
|
|
10149
10369
|
const mb = getMailboxForDeps(deps2);
|
|
10150
10370
|
if (!mb) {
|
|
@@ -10160,7 +10380,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10160
10380
|
}
|
|
10161
10381
|
try {
|
|
10162
10382
|
const message = await mb.send({
|
|
10163
|
-
from: "webui",
|
|
10383
|
+
from: payload.from ?? "webui",
|
|
10164
10384
|
to: payload.to,
|
|
10165
10385
|
type: payload.type,
|
|
10166
10386
|
audience: payload.audience,
|
|
@@ -10175,6 +10395,7 @@ async function handleMailboxSend(ws, deps2, payload) {
|
|
|
10175
10395
|
requestId: payload.requestId,
|
|
10176
10396
|
success: true,
|
|
10177
10397
|
messageId: message.id,
|
|
10398
|
+
from: message.from,
|
|
10178
10399
|
to: message.to,
|
|
10179
10400
|
audience: message.audience ?? "all"
|
|
10180
10401
|
}
|
|
@@ -10219,6 +10440,7 @@ async function handleMailboxMessages(ws, deps2, payload) {
|
|
|
10219
10440
|
send(ws, {
|
|
10220
10441
|
type: "mailbox.messages",
|
|
10221
10442
|
payload: {
|
|
10443
|
+
...payload?.unreadOnly === true ? { unreadOnly: true } : {},
|
|
10222
10444
|
messages: visibleMessages.map((m) => {
|
|
10223
10445
|
const readByMe = payload?.agentId !== void 0 ? payload.agentId in m.readBy : false;
|
|
10224
10446
|
const completedByMe = payload?.agentId !== void 0 ? m.completedBy === payload.agentId : false;
|
|
@@ -10334,6 +10556,14 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10334
10556
|
...ctx.events ? { events: ctx.events } : {}
|
|
10335
10557
|
};
|
|
10336
10558
|
return {
|
|
10559
|
+
action: (ws, msg) => {
|
|
10560
|
+
const parsed = validateMailboxActionPayload(msg.payload);
|
|
10561
|
+
if (!parsed.ok) {
|
|
10562
|
+
sendResult2(ws, false, parsed.message);
|
|
10563
|
+
return;
|
|
10564
|
+
}
|
|
10565
|
+
return handleMailboxAction(ws, deps2, parsed.value);
|
|
10566
|
+
},
|
|
10337
10567
|
send: (ws, msg) => {
|
|
10338
10568
|
const parsed = validateMailboxSendPayload(msg.payload);
|
|
10339
10569
|
if (!parsed.ok) {
|
|
@@ -10384,6 +10614,9 @@ function createMailboxRouteHandlers(ctx) {
|
|
|
10384
10614
|
}
|
|
10385
10615
|
async function handleMailboxRoute(ws, msg, handlers) {
|
|
10386
10616
|
switch (msg.type) {
|
|
10617
|
+
case "mailbox.action":
|
|
10618
|
+
await handlers.action(ws, msg);
|
|
10619
|
+
return true;
|
|
10387
10620
|
case "mailbox.send":
|
|
10388
10621
|
await handlers.send(ws, msg);
|
|
10389
10622
|
return true;
|
|
@@ -11930,8 +12163,8 @@ function seedContextMeta(config, context) {
|
|
|
11930
12163
|
}
|
|
11931
12164
|
|
|
11932
12165
|
// src/server/pref-helpers.ts
|
|
11933
|
-
import * as
|
|
11934
|
-
import * as
|
|
12166
|
+
import * as fs13 from "node:fs/promises";
|
|
12167
|
+
import * as path15 from "node:path";
|
|
11935
12168
|
import { decryptConfigSecrets as decryptConfigSecrets2, encryptConfigSecrets } from "@wrongstack/core/security";
|
|
11936
12169
|
import { atomicWrite as atomicWrite6, backupConfigFile, FORBIDDEN_PROTO_KEYS as FORBIDDEN_PROTO_KEYS2 } from "@wrongstack/core/utils";
|
|
11937
12170
|
var PREF_KEYS = [
|
|
@@ -12027,11 +12260,11 @@ function prefSnapshot(contextMeta) {
|
|
|
12027
12260
|
return snapshot;
|
|
12028
12261
|
}
|
|
12029
12262
|
async function writeGlobalConfigFile(filePath, vault, mutate, logger, errorLabel) {
|
|
12030
|
-
const globalRoot =
|
|
12263
|
+
const globalRoot = path15.dirname(filePath);
|
|
12031
12264
|
await backupConfigFile(filePath, { globalRoot });
|
|
12032
12265
|
let raw;
|
|
12033
12266
|
try {
|
|
12034
|
-
raw = await
|
|
12267
|
+
raw = await fs13.readFile(filePath, "utf8");
|
|
12035
12268
|
} catch {
|
|
12036
12269
|
raw = "{}";
|
|
12037
12270
|
}
|
|
@@ -12506,8 +12739,8 @@ async function handleProcessRoute(ws, msg, handlers) {
|
|
|
12506
12739
|
}
|
|
12507
12740
|
|
|
12508
12741
|
// src/server/project-handlers.ts
|
|
12509
|
-
import * as
|
|
12510
|
-
import * as
|
|
12742
|
+
import * as fs14 from "node:fs/promises";
|
|
12743
|
+
import * as path16 from "node:path";
|
|
12511
12744
|
import { DefaultSessionStore } from "@wrongstack/core/storage";
|
|
12512
12745
|
import { resolveWstackPaths as resolveWstackPaths4 } from "@wrongstack/core/utils";
|
|
12513
12746
|
function createProjectHandlers(ctx) {
|
|
@@ -12559,10 +12792,10 @@ function createProjectHandlers(ctx) {
|
|
|
12559
12792
|
});
|
|
12560
12793
|
return;
|
|
12561
12794
|
}
|
|
12562
|
-
const resolved =
|
|
12563
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12795
|
+
const resolved = path16.resolve(parsed.value.root);
|
|
12796
|
+
const name2 = parsed.value.name?.trim() || path16.basename(resolved);
|
|
12564
12797
|
try {
|
|
12565
|
-
const stat3 = await
|
|
12798
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
12566
12799
|
if (!stat3?.isDirectory()) {
|
|
12567
12800
|
sendTo(ws, {
|
|
12568
12801
|
type: "projects.added",
|
|
@@ -12571,7 +12804,7 @@ function createProjectHandlers(ctx) {
|
|
|
12571
12804
|
return;
|
|
12572
12805
|
}
|
|
12573
12806
|
const before = await loadManifest(ctx.globalConfigPath);
|
|
12574
|
-
const already = before.projects.some((project) =>
|
|
12807
|
+
const already = before.projects.some((project) => path16.resolve(project.root) === resolved);
|
|
12575
12808
|
const entry = await touchProjectInManifest(
|
|
12576
12809
|
{ projectRoot: resolved, workingDir: resolved, name: name2 },
|
|
12577
12810
|
ctx.globalConfigPath
|
|
@@ -12601,8 +12834,8 @@ function createProjectHandlers(ctx) {
|
|
|
12601
12834
|
});
|
|
12602
12835
|
return;
|
|
12603
12836
|
}
|
|
12604
|
-
const resolved =
|
|
12605
|
-
const name2 = parsed.value.name?.trim() ||
|
|
12837
|
+
const resolved = path16.resolve(parsed.value.root);
|
|
12838
|
+
const name2 = parsed.value.name?.trim() || path16.basename(resolved);
|
|
12606
12839
|
if (!ctx.allowProjectMutations) {
|
|
12607
12840
|
sendTo(ws, {
|
|
12608
12841
|
type: "projects.selected",
|
|
@@ -12615,7 +12848,7 @@ function createProjectHandlers(ctx) {
|
|
|
12615
12848
|
return;
|
|
12616
12849
|
}
|
|
12617
12850
|
try {
|
|
12618
|
-
const stat3 = await
|
|
12851
|
+
const stat3 = await fs14.stat(resolved).catch(() => null);
|
|
12619
12852
|
if (!stat3?.isDirectory()) {
|
|
12620
12853
|
sendTo(ws, {
|
|
12621
12854
|
type: "projects.selected",
|
|
@@ -12645,7 +12878,7 @@ function createProjectHandlers(ctx) {
|
|
|
12645
12878
|
const previousIdentityTarget = {
|
|
12646
12879
|
projectSlug: previousPaths.projectSlug,
|
|
12647
12880
|
projectRoot: previousProjectRoot,
|
|
12648
|
-
projectName:
|
|
12881
|
+
projectName: path16.basename(previousProjectRoot),
|
|
12649
12882
|
workingDir: ctx.context.workingDir
|
|
12650
12883
|
};
|
|
12651
12884
|
const previousUsage = ctx.tokenCounter.total();
|
|
@@ -12808,14 +13041,14 @@ async function searchCatalogModels(registry, rawQuery, limit = 8) {
|
|
|
12808
13041
|
}
|
|
12809
13042
|
|
|
12810
13043
|
// src/server/provider-config-io.ts
|
|
12811
|
-
import * as
|
|
13044
|
+
import * as fs15 from "node:fs/promises";
|
|
12812
13045
|
import { ConfigError as ConfigError2 } from "@wrongstack/core/types";
|
|
12813
13046
|
import { atomicWrite as atomicWrite7 } from "@wrongstack/core/utils";
|
|
12814
13047
|
import { decryptConfigSecrets as decryptConfigSecrets3, encryptConfigSecrets as encryptConfigSecrets2 } from "@wrongstack/core/security";
|
|
12815
13048
|
async function loadSavedProviders(configPath, vault) {
|
|
12816
13049
|
let raw;
|
|
12817
13050
|
try {
|
|
12818
|
-
raw = await
|
|
13051
|
+
raw = await fs15.readFile(configPath, "utf8");
|
|
12819
13052
|
} catch {
|
|
12820
13053
|
return {};
|
|
12821
13054
|
}
|
|
@@ -12833,7 +13066,7 @@ async function saveProviders(configPath, vault, providers, profileConfigPath) {
|
|
|
12833
13066
|
let raw;
|
|
12834
13067
|
let fileExists = true;
|
|
12835
13068
|
try {
|
|
12836
|
-
raw = await
|
|
13069
|
+
raw = await fs15.readFile(targetPath, "utf8");
|
|
12837
13070
|
} catch (err) {
|
|
12838
13071
|
if (err.code !== "ENOENT") {
|
|
12839
13072
|
throw new ConfigError2({
|
|
@@ -13536,6 +13769,7 @@ var CLIENT_COLLABORATION_MESSAGE_TYPES = [
|
|
|
13536
13769
|
"collab.resume",
|
|
13537
13770
|
"collab.grant_control",
|
|
13538
13771
|
"collab.inject_tool",
|
|
13772
|
+
"mailbox.action",
|
|
13539
13773
|
"mailbox.agents",
|
|
13540
13774
|
"mailbox.clear",
|
|
13541
13775
|
"mailbox.compact",
|
|
@@ -13801,6 +14035,7 @@ var SERVER_COLLABORATION_MESSAGE_TYPES = [
|
|
|
13801
14035
|
"collab.pause.granted",
|
|
13802
14036
|
"collab.pause.released",
|
|
13803
14037
|
"collab.state",
|
|
14038
|
+
"mailbox.action_result",
|
|
13804
14039
|
"mailbox.agent_registered",
|
|
13805
14040
|
"mailbox.agents",
|
|
13806
14041
|
"mailbox.cleared",
|
|
@@ -14024,13 +14259,13 @@ function isRegisteredMessageType(type, direction) {
|
|
|
14024
14259
|
// src/protocol/decoder.ts
|
|
14025
14260
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
14026
14261
|
var MAX_PAYLOAD_DEPTH = 32;
|
|
14027
|
-
function inspectValue(value,
|
|
14262
|
+
function inspectValue(value, path30, depth) {
|
|
14028
14263
|
if (depth > MAX_PAYLOAD_DEPTH) {
|
|
14029
|
-
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path:
|
|
14264
|
+
return { code: "too_deep", message: "Protocol payload exceeds the nesting limit", path: path30 };
|
|
14030
14265
|
}
|
|
14031
14266
|
if (value === null || typeof value !== "object") return null;
|
|
14032
14267
|
for (const key of Object.keys(value)) {
|
|
14033
|
-
const childPath = `${
|
|
14268
|
+
const childPath = `${path30}.${key}`;
|
|
14034
14269
|
if (FORBIDDEN_KEYS.has(key)) {
|
|
14035
14270
|
return { code: "unsafe_key", message: `Unsafe protocol key: ${key}`, path: childPath };
|
|
14036
14271
|
}
|
|
@@ -15726,8 +15961,8 @@ function createRouteFamilyDispatcher(options) {
|
|
|
15726
15961
|
}
|
|
15727
15962
|
|
|
15728
15963
|
// src/server/shell-open.ts
|
|
15729
|
-
import * as
|
|
15730
|
-
import * as
|
|
15964
|
+
import * as fs16 from "node:fs/promises";
|
|
15965
|
+
import * as path17 from "node:path";
|
|
15731
15966
|
import { spawn } from "node:child_process";
|
|
15732
15967
|
function normalizeShellOpenTarget(target) {
|
|
15733
15968
|
return target === "terminal" ? "terminal" : "file-manager";
|
|
@@ -15738,11 +15973,11 @@ function shellQuote(s) {
|
|
|
15738
15973
|
}
|
|
15739
15974
|
async function handleShellOpen(req, logger, options) {
|
|
15740
15975
|
try {
|
|
15741
|
-
const resolved =
|
|
15976
|
+
const resolved = path17.resolve(req.path);
|
|
15742
15977
|
if (options?.projectRoot) {
|
|
15743
|
-
const root =
|
|
15744
|
-
const relative5 =
|
|
15745
|
-
const escapes = relative5.startsWith("..") ||
|
|
15978
|
+
const root = path17.resolve(options.projectRoot);
|
|
15979
|
+
const relative5 = path17.relative(root, resolved);
|
|
15980
|
+
const escapes = relative5.startsWith("..") || path17.isAbsolute(relative5);
|
|
15746
15981
|
if (escapes) {
|
|
15747
15982
|
return {
|
|
15748
15983
|
success: false,
|
|
@@ -15750,7 +15985,7 @@ async function handleShellOpen(req, logger, options) {
|
|
|
15750
15985
|
};
|
|
15751
15986
|
}
|
|
15752
15987
|
}
|
|
15753
|
-
await
|
|
15988
|
+
await fs16.access(resolved);
|
|
15754
15989
|
if (METACHAR_REGEX.test(resolved)) {
|
|
15755
15990
|
return { success: false, message: "Path contains unsupported characters." };
|
|
15756
15991
|
}
|
|
@@ -15801,7 +16036,12 @@ async function handleShellOpen(req, logger, options) {
|
|
|
15801
16036
|
}
|
|
15802
16037
|
|
|
15803
16038
|
// src/server/sdd-board-ws-handler.ts
|
|
15804
|
-
import {
|
|
16039
|
+
import {
|
|
16040
|
+
enqueueKanbanWorkflowCommand,
|
|
16041
|
+
kanbanWorkflowId,
|
|
16042
|
+
listBoards as listBoards3,
|
|
16043
|
+
listKanbanWorkflowStates
|
|
16044
|
+
} from "@wrongstack/kanban";
|
|
15805
16045
|
import {
|
|
15806
16046
|
applySddLifecycle,
|
|
15807
16047
|
extractVerificationCommand,
|
|
@@ -15828,7 +16068,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15828
16068
|
clients = /* @__PURE__ */ new Set();
|
|
15829
16069
|
lifecycle;
|
|
15830
16070
|
security;
|
|
15831
|
-
|
|
16071
|
+
standalonePollingEnabled;
|
|
15832
16072
|
latest = null;
|
|
15833
16073
|
poll = null;
|
|
15834
16074
|
pollInFlight = false;
|
|
@@ -15837,7 +16077,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15837
16077
|
this.store = new SddBoardStore({ baseDir: boardsDir });
|
|
15838
16078
|
this.lifecycle = lifecycle;
|
|
15839
16079
|
this.security = security;
|
|
15840
|
-
this.
|
|
16080
|
+
this.standalonePollingEnabled = events === void 0;
|
|
15841
16081
|
if (events) {
|
|
15842
16082
|
const handler = (e) => {
|
|
15843
16083
|
this.latest = e.snapshot;
|
|
@@ -15872,7 +16112,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15872
16112
|
return;
|
|
15873
16113
|
}
|
|
15874
16114
|
if (msg.type === "sdd.board.list") {
|
|
15875
|
-
const boards = await this.
|
|
16115
|
+
const boards = await this.listBoardEntries();
|
|
15876
16116
|
this.broadcast({ type: "sdd.board.list", payload: { boards } });
|
|
15877
16117
|
return;
|
|
15878
16118
|
}
|
|
@@ -15885,7 +16125,8 @@ var SddBoardWebSocketHandler = class {
|
|
|
15885
16125
|
const verificationCommands = [];
|
|
15886
16126
|
if (action === "set_task_verification") {
|
|
15887
16127
|
const command = msg.payload?.verificationCommand;
|
|
15888
|
-
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
16128
|
+
if (command !== void 0 && (typeof command !== "string" || command.length > 8192))
|
|
16129
|
+
return;
|
|
15889
16130
|
if (typeof command === "string" && command.trim()) {
|
|
15890
16131
|
verificationCommands.push({ command, operation: "sdd.set_task_verification" });
|
|
15891
16132
|
}
|
|
@@ -15919,18 +16160,26 @@ var SddBoardWebSocketHandler = class {
|
|
|
15919
16160
|
);
|
|
15920
16161
|
if (!authorization.allowed) return;
|
|
15921
16162
|
}
|
|
15922
|
-
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.
|
|
16163
|
+
const runId = msg.payload?.runId ?? this.latest?.runId ?? (await this.listBoardEntries())[0]?.runId;
|
|
15923
16164
|
if (runId) {
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15928
|
-
|
|
16165
|
+
if (this.lifecycle && this.lifecycle.controlTransport !== "legacy-file") {
|
|
16166
|
+
await enqueueKanbanWorkflowCommand(
|
|
16167
|
+
this.lifecycle.projectRoot,
|
|
16168
|
+
kanbanWorkflowId("sdd", runId),
|
|
16169
|
+
{ type: action, payload: msg.payload }
|
|
16170
|
+
);
|
|
16171
|
+
} else {
|
|
16172
|
+
await this.store.appendControl(runId, {
|
|
16173
|
+
ts: Date.now(),
|
|
16174
|
+
type: action,
|
|
16175
|
+
payload: msg.payload
|
|
16176
|
+
});
|
|
16177
|
+
}
|
|
15929
16178
|
}
|
|
15930
16179
|
}
|
|
15931
16180
|
}
|
|
15932
16181
|
/**
|
|
15933
|
-
* Apply a cleanup/rollback/destroy from
|
|
16182
|
+
* Apply a cleanup/rollback/destroy from durable state and broadcast a structured
|
|
15934
16183
|
* `sdd.board.lifecycle_result`. Refuses (no-op) while a run is still active —
|
|
15935
16184
|
* the user must stop it first; the UI gates the buttons on `!active` and the
|
|
15936
16185
|
* Destroy flow auto-stops then waits before sending `destroy`.
|
|
@@ -15939,7 +16188,11 @@ var SddBoardWebSocketHandler = class {
|
|
|
15939
16188
|
if (!this.lifecycle) {
|
|
15940
16189
|
this.broadcast({
|
|
15941
16190
|
type: "sdd.board.lifecycle_result",
|
|
15942
|
-
payload: {
|
|
16191
|
+
payload: {
|
|
16192
|
+
op,
|
|
16193
|
+
ok: false,
|
|
16194
|
+
reason: "Lifecycle operations are not available in this session."
|
|
16195
|
+
}
|
|
15943
16196
|
});
|
|
15944
16197
|
return;
|
|
15945
16198
|
}
|
|
@@ -15955,6 +16208,7 @@ var SddBoardWebSocketHandler = class {
|
|
|
15955
16208
|
projectRoot: this.lifecycle.projectRoot,
|
|
15956
16209
|
paths: this.lifecycle.paths,
|
|
15957
16210
|
runId,
|
|
16211
|
+
stateTransport: this.lifecycle.stateTransport ?? "kanban",
|
|
15958
16212
|
revertMerged: payload?.revertMerged === true
|
|
15959
16213
|
});
|
|
15960
16214
|
this.broadcast({ type: "sdd.board.lifecycle_result", payload: result });
|
|
@@ -15983,22 +16237,27 @@ var SddBoardWebSocketHandler = class {
|
|
|
15983
16237
|
if (this.pollInFlight) return;
|
|
15984
16238
|
this.pollInFlight = true;
|
|
15985
16239
|
try {
|
|
15986
|
-
const
|
|
15987
|
-
if (!
|
|
15988
|
-
if (this.latest && this.latest.updatedAt >=
|
|
16240
|
+
const snap = await this.loadLatestSnapshot();
|
|
16241
|
+
if (!snap) return;
|
|
16242
|
+
if (this.latest && this.latest.updatedAt >= snap.updatedAt && this.latest.runId === snap.runId) {
|
|
15989
16243
|
return;
|
|
15990
16244
|
}
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
16245
|
+
this.latest = snap;
|
|
16246
|
+
this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
16247
|
+
} catch (err) {
|
|
16248
|
+
console.warn(
|
|
16249
|
+
JSON.stringify({
|
|
16250
|
+
level: "warn",
|
|
16251
|
+
event: "sdd_board.poll_failed",
|
|
16252
|
+
message: err instanceof Error ? err.message : String(err)
|
|
16253
|
+
})
|
|
16254
|
+
);
|
|
15996
16255
|
} finally {
|
|
15997
16256
|
this.pollInFlight = false;
|
|
15998
16257
|
}
|
|
15999
16258
|
}
|
|
16000
16259
|
startPolling() {
|
|
16001
|
-
if (!this.
|
|
16260
|
+
if (!this.standalonePollingEnabled || this.poll !== null || this.clients.size === 0) return;
|
|
16002
16261
|
this.poll = setInterval(() => void this.pollLatest(), 1e3);
|
|
16003
16262
|
this.poll.unref?.();
|
|
16004
16263
|
}
|
|
@@ -16008,17 +16267,44 @@ var SddBoardWebSocketHandler = class {
|
|
|
16008
16267
|
this.poll = null;
|
|
16009
16268
|
}
|
|
16010
16269
|
async sendCurrent(client) {
|
|
16011
|
-
const snap = this.latest ?? await this.
|
|
16270
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
16012
16271
|
if (snap) this.send(client, { type: "sdd.board.snapshot", payload: snap });
|
|
16013
16272
|
}
|
|
16014
16273
|
async broadcastCurrent() {
|
|
16015
|
-
const snap = this.latest ?? await this.
|
|
16274
|
+
const snap = this.latest ?? await this.loadLatestSnapshot();
|
|
16016
16275
|
if (snap) this.broadcast({ type: "sdd.board.snapshot", payload: snap });
|
|
16017
16276
|
}
|
|
16018
|
-
async
|
|
16277
|
+
async loadLatestSnapshot() {
|
|
16278
|
+
if (this.usesKanbanState()) {
|
|
16279
|
+
const states = await listKanbanWorkflowStates(this.lifecycle.projectRoot, "sdd:");
|
|
16280
|
+
const snapshots = states.map((state) => state.value).filter(isSddBoardSnapshot).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
16281
|
+
return snapshots[0] ?? null;
|
|
16282
|
+
}
|
|
16019
16283
|
const entry = await this.store.latest();
|
|
16020
16284
|
return entry ? this.store.load(entry.runId) : null;
|
|
16021
16285
|
}
|
|
16286
|
+
async listBoardEntries() {
|
|
16287
|
+
if (!this.usesKanbanState()) return this.store.list();
|
|
16288
|
+
const states = await listKanbanWorkflowStates(this.lifecycle.projectRoot, "sdd:");
|
|
16289
|
+
return states.flatMap((state) => {
|
|
16290
|
+
if (!isSddBoardSnapshot(state.value)) return [];
|
|
16291
|
+
const snapshot = state.value;
|
|
16292
|
+
return [
|
|
16293
|
+
{
|
|
16294
|
+
runId: snapshot.runId,
|
|
16295
|
+
...snapshot.specId ? { specId: snapshot.specId } : {},
|
|
16296
|
+
title: snapshot.title,
|
|
16297
|
+
status: snapshot.status,
|
|
16298
|
+
total: snapshot.progress.total,
|
|
16299
|
+
completed: snapshot.progress.completed,
|
|
16300
|
+
updatedAt: snapshot.updatedAt
|
|
16301
|
+
}
|
|
16302
|
+
];
|
|
16303
|
+
});
|
|
16304
|
+
}
|
|
16305
|
+
usesKanbanState() {
|
|
16306
|
+
return Boolean(this.lifecycle && this.lifecycle.stateTransport !== "legacy-file");
|
|
16307
|
+
}
|
|
16022
16308
|
broadcast(msg) {
|
|
16023
16309
|
const data = JSON.stringify(msg);
|
|
16024
16310
|
for (const client of this.clients) {
|
|
@@ -16029,17 +16315,20 @@ var SddBoardWebSocketHandler = class {
|
|
|
16029
16315
|
sendSerialized(client.ws, JSON.stringify(msg));
|
|
16030
16316
|
}
|
|
16031
16317
|
};
|
|
16318
|
+
function isSddBoardSnapshot(value) {
|
|
16319
|
+
if (!value || typeof value !== "object") return false;
|
|
16320
|
+
const snapshot = value;
|
|
16321
|
+
return typeof snapshot.runId === "string" && typeof snapshot.title === "string" && typeof snapshot.status === "string" && typeof snapshot.updatedAt === "number" && Array.isArray(snapshot.tasks) && Boolean(snapshot.progress && typeof snapshot.progress.total === "number");
|
|
16322
|
+
}
|
|
16032
16323
|
|
|
16033
16324
|
// src/server/sdd-wizard-wiring.ts
|
|
16034
|
-
import * as
|
|
16035
|
-
import {
|
|
16036
|
-
DefaultTaskStore,
|
|
16037
|
-
TaskTracker
|
|
16038
|
-
} from "@wrongstack/core/tasking";
|
|
16325
|
+
import * as path18 from "node:path";
|
|
16326
|
+
import { DefaultTaskStore, TaskTracker } from "@wrongstack/core/tasking";
|
|
16039
16327
|
import { ToolValidationError as ToolValidationError5 } from "@wrongstack/core/types";
|
|
16040
16328
|
import { WorktreeManager as WorktreeManager2 } from "@wrongstack/core/worktree";
|
|
16041
16329
|
import {
|
|
16042
16330
|
cleanupStaleSddWorktrees,
|
|
16331
|
+
createKanbanSddSessionPersistence,
|
|
16043
16332
|
decomposeNonAtomicTasks,
|
|
16044
16333
|
gatherProjectContext,
|
|
16045
16334
|
makeAcceptanceCriteriaVerifier,
|
|
@@ -16057,7 +16346,7 @@ import {
|
|
|
16057
16346
|
} from "@wrongstack/sdd";
|
|
16058
16347
|
async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
16059
16348
|
const runTracker = tracker ?? (() => {
|
|
16060
|
-
const t = new TaskTracker({ store: new DefaultTaskStore() });
|
|
16349
|
+
const t = new TaskTracker({ store: deps2.taskStore ?? new DefaultTaskStore() });
|
|
16061
16350
|
t.setGraph(graph);
|
|
16062
16351
|
return t;
|
|
16063
16352
|
})();
|
|
@@ -16066,7 +16355,8 @@ async function startSddRunFromGraph(graph, deps2, config = {}, tracker) {
|
|
|
16066
16355
|
if (worktreesEnabled && await isGitWorkTree(deps2.projectRoot)) {
|
|
16067
16356
|
void cleanupStaleSddWorktrees({
|
|
16068
16357
|
projectRoot: deps2.projectRoot,
|
|
16069
|
-
boardsDir: deps2.projectSddBoards
|
|
16358
|
+
boardsDir: deps2.projectSddBoards,
|
|
16359
|
+
stateTransport: "kanban"
|
|
16070
16360
|
}).catch(() => void 0);
|
|
16071
16361
|
worktrees = new WorktreeManager2({
|
|
16072
16362
|
projectRoot: deps2.projectRoot,
|
|
@@ -16145,7 +16435,8 @@ function buildSddWizardDeps(opts) {
|
|
|
16145
16435
|
}).catch(() => {
|
|
16146
16436
|
projectContext = "";
|
|
16147
16437
|
});
|
|
16148
|
-
const
|
|
16438
|
+
const legacySessionPath = opts.paths.projectSddSession ?? path18.join(opts.paths.projectDir, "sdd-session.json");
|
|
16439
|
+
const sessionPersistence = createKanbanSddSessionPersistence(opts.projectRoot, legacySessionPath);
|
|
16149
16440
|
const specStore = new SpecStore({ baseDir: opts.paths.projectSpecs });
|
|
16150
16441
|
const graphStore = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
16151
16442
|
const runIsolatedTurn = async (prompt, name2) => {
|
|
@@ -16166,7 +16457,7 @@ function buildSddWizardDeps(opts) {
|
|
|
16166
16457
|
const makeDriver = () => new SddInterviewDriver({
|
|
16167
16458
|
specStore,
|
|
16168
16459
|
graphStore,
|
|
16169
|
-
|
|
16460
|
+
sessionPersistence,
|
|
16170
16461
|
projectContext
|
|
16171
16462
|
});
|
|
16172
16463
|
const launchFromGraph = async (graph, config, tracker) => {
|
|
@@ -16178,6 +16469,7 @@ function buildSddWizardDeps(opts) {
|
|
|
16178
16469
|
projectRoot: opts.projectRoot,
|
|
16179
16470
|
subagentFactory: opts.subagentFactory,
|
|
16180
16471
|
projectSddBoards: opts.paths.projectSddBoards,
|
|
16472
|
+
taskStore: graphStore,
|
|
16181
16473
|
registry,
|
|
16182
16474
|
runIsolatedTurn,
|
|
16183
16475
|
...opts.brain ? { brain: opts.brain } : {}
|
|
@@ -16247,8 +16539,18 @@ var SddWizardWebSocketHandler = class {
|
|
|
16247
16539
|
lastAgentText = "";
|
|
16248
16540
|
/** Guards against overlapping interview turns (one in flight at a time). */
|
|
16249
16541
|
busy = false;
|
|
16250
|
-
/**
|
|
16542
|
+
/** Set when authoritative session state could not be read during bootstrap. */
|
|
16543
|
+
resumeError = null;
|
|
16544
|
+
/** Resolves once project-context gather + durable resume finish. */
|
|
16251
16545
|
ready;
|
|
16546
|
+
/**
|
|
16547
|
+
* Single-flight slot for the resume probe. `handleMessage` awaits this
|
|
16548
|
+
* when `resumeError` is set, so concurrent frames cannot race the retry
|
|
16549
|
+
* — one probe either clears the gate or re-latches it, then every
|
|
16550
|
+
* queued frame re-checks and proceeds. `null` when no probe is in
|
|
16551
|
+
* flight (or after the probe has settled).
|
|
16552
|
+
*/
|
|
16553
|
+
resumeProbe = null;
|
|
16252
16554
|
async bootstrap() {
|
|
16253
16555
|
try {
|
|
16254
16556
|
await this.deps.ensureReady?.();
|
|
@@ -16256,7 +16558,7 @@ var SddWizardWebSocketHandler = class {
|
|
|
16256
16558
|
}
|
|
16257
16559
|
await this.tryResume();
|
|
16258
16560
|
}
|
|
16259
|
-
/** Rehydrate a persisted interview if one exists
|
|
16561
|
+
/** Rehydrate a persisted interview if one exists. */
|
|
16260
16562
|
async tryResume() {
|
|
16261
16563
|
if (this.driver) return;
|
|
16262
16564
|
try {
|
|
@@ -16265,9 +16567,11 @@ var SddWizardWebSocketHandler = class {
|
|
|
16265
16567
|
this.driver = driver;
|
|
16266
16568
|
this.lastAgentText = driver.getLastAgentText() ?? "";
|
|
16267
16569
|
}
|
|
16268
|
-
|
|
16570
|
+
this.resumeError = null;
|
|
16571
|
+
} catch (error2) {
|
|
16269
16572
|
this.driver = null;
|
|
16270
16573
|
this.lastAgentText = "";
|
|
16574
|
+
this.resumeError = error2 instanceof Error ? error2.message : String(error2);
|
|
16271
16575
|
}
|
|
16272
16576
|
}
|
|
16273
16577
|
addClient(ws) {
|
|
@@ -16276,6 +16580,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
16276
16580
|
ws.on("close", () => this.clients.delete(client));
|
|
16277
16581
|
ws.on("error", () => this.clients.delete(client));
|
|
16278
16582
|
void this.ready.then(() => {
|
|
16583
|
+
if (this.resumeError) {
|
|
16584
|
+
this.send(client, { type: "sdd.spec.error", payload: { message: this.resumeError } });
|
|
16585
|
+
return;
|
|
16586
|
+
}
|
|
16279
16587
|
if (this.driver) {
|
|
16280
16588
|
this.send(client, this.snapshotMsg());
|
|
16281
16589
|
if (this.lastAgentText) {
|
|
@@ -16287,6 +16595,22 @@ var SddWizardWebSocketHandler = class {
|
|
|
16287
16595
|
async handleMessage(msg) {
|
|
16288
16596
|
try {
|
|
16289
16597
|
await this.ready;
|
|
16598
|
+
if (this.resumeError) {
|
|
16599
|
+
if (this.resumeProbe === null) {
|
|
16600
|
+
const probe = Promise.resolve().then(() => this.tryResume());
|
|
16601
|
+
this.resumeProbe = probe;
|
|
16602
|
+
void probe.finally(() => {
|
|
16603
|
+
setTimeout(() => {
|
|
16604
|
+
if (this.resumeProbe === probe) this.resumeProbe = null;
|
|
16605
|
+
}, 0);
|
|
16606
|
+
});
|
|
16607
|
+
}
|
|
16608
|
+
await this.resumeProbe;
|
|
16609
|
+
if (this.resumeError) {
|
|
16610
|
+
this.broadcast({ type: "sdd.spec.error", payload: { message: this.resumeError } });
|
|
16611
|
+
return;
|
|
16612
|
+
}
|
|
16613
|
+
}
|
|
16290
16614
|
switch (msg.type) {
|
|
16291
16615
|
case "sdd.spec.start":
|
|
16292
16616
|
await this.onStart(String(msg.payload?.goal ?? "").trim(), {
|
|
@@ -16303,7 +16627,10 @@ var SddWizardWebSocketHandler = class {
|
|
|
16303
16627
|
if (this.driver) {
|
|
16304
16628
|
this.broadcast(this.snapshotMsg());
|
|
16305
16629
|
if (this.lastAgentText) {
|
|
16306
|
-
this.broadcast({
|
|
16630
|
+
this.broadcast({
|
|
16631
|
+
type: "sdd.spec.agent_text",
|
|
16632
|
+
payload: { text: this.lastAgentText }
|
|
16633
|
+
});
|
|
16307
16634
|
}
|
|
16308
16635
|
}
|
|
16309
16636
|
break;
|
|
@@ -16517,10 +16844,10 @@ import { recordTaskFileActivity } from "@wrongstack/kanban";
|
|
|
16517
16844
|
|
|
16518
16845
|
// src/server/setup-events-fleet-broadcaster.ts
|
|
16519
16846
|
import { watch as fsWatch } from "node:fs";
|
|
16520
|
-
import * as
|
|
16847
|
+
import * as path19 from "node:path";
|
|
16521
16848
|
function registerSetupEventsFleetBroadcaster(deps2) {
|
|
16522
16849
|
const { globalConfigPath, wpaths, context, clients, broadcast: broadcast2, onFleetBroadcaster, isDisposed } = deps2;
|
|
16523
|
-
const globalRoot = globalConfigPath ?
|
|
16850
|
+
const globalRoot = globalConfigPath ? path19.dirname(globalConfigPath) : void 0;
|
|
16524
16851
|
if (!globalRoot) return void 0;
|
|
16525
16852
|
const disposers = [];
|
|
16526
16853
|
const broadcastSessions = async () => {
|
|
@@ -16530,8 +16857,8 @@ function registerSetupEventsFleetBroadcaster(deps2) {
|
|
|
16530
16857
|
const sessions = await registry.list();
|
|
16531
16858
|
const ownEntry = sessions.find((s) => s.pid === process.pid);
|
|
16532
16859
|
const mySlug = ownEntry?.projectSlug ?? wpaths?.projectSlug;
|
|
16533
|
-
const myRoot =
|
|
16534
|
-
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug :
|
|
16860
|
+
const myRoot = path19.resolve(context.projectRoot);
|
|
16861
|
+
const live = sessions.filter((s) => s.status === "active" || s.status === "idle").filter((s) => mySlug ? s.projectSlug === mySlug : path19.resolve(s.projectRoot) === myRoot).map((s) => ({
|
|
16535
16862
|
sessionId: s.sessionId,
|
|
16536
16863
|
projectName: s.projectName,
|
|
16537
16864
|
projectSlug: s.projectSlug,
|
|
@@ -16721,14 +17048,14 @@ function createSetupEventSessionHelpers(context, sessionBridge) {
|
|
|
16721
17048
|
|
|
16722
17049
|
// src/server/setup-events-status-watcher.ts
|
|
16723
17050
|
import { watch as fsWatch2 } from "node:fs";
|
|
16724
|
-
import * as
|
|
16725
|
-
import * as
|
|
17051
|
+
import * as fs17 from "node:fs/promises";
|
|
17052
|
+
import * as path21 from "node:path";
|
|
16726
17053
|
|
|
16727
17054
|
// src/server/setup-events-watcher.ts
|
|
16728
|
-
import * as
|
|
17055
|
+
import * as path20 from "node:path";
|
|
16729
17056
|
function statusProjectHashFromWatchFilename(projectsDir, filename) {
|
|
16730
17057
|
const raw = String(filename);
|
|
16731
|
-
const relative5 =
|
|
17058
|
+
const relative5 = path20.isAbsolute(raw) ? path20.relative(projectsDir, raw) : raw;
|
|
16732
17059
|
const parts = relative5.split(/[\\/]+/).filter(Boolean);
|
|
16733
17060
|
if (parts.length < 2 || parts.at(-1) !== "status.json") return null;
|
|
16734
17061
|
return parts.at(-2) ?? null;
|
|
@@ -16763,7 +17090,7 @@ function logFileWatcherMetrics(metrics) {
|
|
|
16763
17090
|
function registerSetupEventsStatusWatcher(deps2) {
|
|
16764
17091
|
const { wpaths, watcherMetrics, clients, broadcast: broadcast2, on, isDisposed } = deps2;
|
|
16765
17092
|
if (!wpaths?.projectStatus || !wpaths.globalRoot) return void 0;
|
|
16766
|
-
const projectsDir =
|
|
17093
|
+
const projectsDir = path21.join(wpaths.globalRoot, "projects");
|
|
16767
17094
|
const knownProjectHashes = /* @__PURE__ */ new Set();
|
|
16768
17095
|
const debounceTimers = /* @__PURE__ */ new Map();
|
|
16769
17096
|
const DEBOUNCE_MS = 150;
|
|
@@ -16808,7 +17135,7 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16808
17135
|
let watcher;
|
|
16809
17136
|
const startWatcher = async () => {
|
|
16810
17137
|
try {
|
|
16811
|
-
await
|
|
17138
|
+
await fs17.mkdir(projectsDir, { recursive: true });
|
|
16812
17139
|
if (isDisposed()) return;
|
|
16813
17140
|
watcher = fsWatch2(
|
|
16814
17141
|
projectsDir,
|
|
@@ -16822,8 +17149,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16822
17149
|
if (!knownProjectHashes.has(projectHash)) return;
|
|
16823
17150
|
if (watcherMetrics) watcherMetrics.filesProcessed++;
|
|
16824
17151
|
try {
|
|
16825
|
-
const targetFile =
|
|
16826
|
-
const content = await
|
|
17152
|
+
const targetFile = path21.join(projectsDir, projectHash, "status.json");
|
|
17153
|
+
const content = await fs17.readFile(targetFile, "utf-8");
|
|
16827
17154
|
const statusData = JSON.parse(content);
|
|
16828
17155
|
scheduleBroadcast(projectHash, statusData);
|
|
16829
17156
|
} catch {
|
|
@@ -16880,8 +17207,8 @@ function registerSetupEventsStatusWatcher(deps2) {
|
|
|
16880
17207
|
}
|
|
16881
17208
|
|
|
16882
17209
|
// src/server/setup-events-core-watchers.ts
|
|
16883
|
-
import * as
|
|
16884
|
-
import * as
|
|
17210
|
+
import * as fs18 from "node:fs/promises";
|
|
17211
|
+
import * as path22 from "node:path";
|
|
16885
17212
|
function registerSetupEventsCoreWatchers(deps2) {
|
|
16886
17213
|
const { broadcast: broadcast2, clients, context } = deps2;
|
|
16887
17214
|
const disposers = [];
|
|
@@ -16917,9 +17244,9 @@ function registerSetupEventsClientStatusWriter(deps2) {
|
|
|
16917
17244
|
if (wpaths?.projectStatus) {
|
|
16918
17245
|
try {
|
|
16919
17246
|
const statusFile = wpaths.projectStatus(e.projectHash);
|
|
16920
|
-
const dir =
|
|
16921
|
-
await
|
|
16922
|
-
await
|
|
17247
|
+
const dir = path22.dirname(statusFile);
|
|
17248
|
+
await fs18.mkdir(dir, { recursive: true });
|
|
17249
|
+
await fs18.writeFile(statusFile, JSON.stringify(e, null, 2), "utf-8");
|
|
16923
17250
|
} catch (err) {
|
|
16924
17251
|
console.error(
|
|
16925
17252
|
JSON.stringify({
|
|
@@ -17944,7 +18271,7 @@ var SpecsWebSocketHandler = class {
|
|
|
17944
18271
|
// src/server/start-webui.ts
|
|
17945
18272
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
17946
18273
|
import * as http2 from "node:http";
|
|
17947
|
-
import * as
|
|
18274
|
+
import * as path29 from "node:path";
|
|
17948
18275
|
import { createDefaultPipelines } from "@wrongstack/core/agent";
|
|
17949
18276
|
import { getSharedProjectMailbox as getSharedProjectMailbox4, resolveProjectDir as resolveProjectDir3 } from "@wrongstack/core/coordination";
|
|
17950
18277
|
import { createCompatibilityTrustBoundary as createCompatibilityTrustBoundary3 } from "@wrongstack/core/security";
|
|
@@ -17958,7 +18285,7 @@ import { DEFAULT_CONTEXT_WINDOW_MODE_ID as DEFAULT_CONTEXT_WINDOW_MODE_ID2 } fro
|
|
|
17958
18285
|
import {
|
|
17959
18286
|
expectDefined as expectDefined3,
|
|
17960
18287
|
sessionScopedPath as sessionScopedPath3,
|
|
17961
|
-
|
|
18288
|
+
startSharedHeapWatchdog,
|
|
17962
18289
|
toErrorMessage as toErrorMessage13,
|
|
17963
18290
|
wstackGlobalRoot as wstackGlobalRoot2
|
|
17964
18291
|
} from "@wrongstack/core/utils";
|
|
@@ -17967,10 +18294,8 @@ import { toLanguagePackageInput } from "@wrongstack/techstack";
|
|
|
17967
18294
|
import { ensureSessionShell } from "@wrongstack/tools";
|
|
17968
18295
|
|
|
17969
18296
|
// src/server/backend-services.ts
|
|
17970
|
-
import { join as
|
|
17971
|
-
import {
|
|
17972
|
-
Agent
|
|
17973
|
-
} from "@wrongstack/core/agent";
|
|
18297
|
+
import { join as join13 } from "node:path";
|
|
18298
|
+
import { Agent } from "@wrongstack/core/agent";
|
|
17974
18299
|
import {
|
|
17975
18300
|
BrainDecisionLedger,
|
|
17976
18301
|
BrainMonitor,
|
|
@@ -17982,8 +18307,8 @@ import {
|
|
|
17982
18307
|
mailboxSessionTag,
|
|
17983
18308
|
ObservableBrainArbiter as ObservableBrainArbiterCtor
|
|
17984
18309
|
} from "@wrongstack/core/coordination";
|
|
17985
|
-
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
17986
18310
|
import { DEFAULT_TOOLS_CONFIG } from "@wrongstack/core/defaults";
|
|
18311
|
+
import { installDesignStudioMiddleware } from "@wrongstack/core/design";
|
|
17987
18312
|
import {
|
|
17988
18313
|
AutoCompactionMiddleware as AutoCompactionMiddlewareCtor,
|
|
17989
18314
|
createBrainRuntime,
|
|
@@ -17993,7 +18318,9 @@ import {
|
|
|
17993
18318
|
} from "@wrongstack/core/execution";
|
|
17994
18319
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
17995
18320
|
import { SessionMemoryConsolidator } from "@wrongstack/core/storage";
|
|
17996
|
-
import {
|
|
18321
|
+
import {
|
|
18322
|
+
resolveContextWindowPolicy as resolveContextWindowPolicy2
|
|
18323
|
+
} from "@wrongstack/core/types";
|
|
17997
18324
|
import {
|
|
17998
18325
|
estimateRequestTokensCalibrated,
|
|
17999
18326
|
toErrorMessage as toErrorMessage9
|
|
@@ -18010,7 +18337,7 @@ import {
|
|
|
18010
18337
|
import { spawn as spawn2 } from "node:child_process";
|
|
18011
18338
|
import { createRequire } from "node:module";
|
|
18012
18339
|
import { existsSync } from "node:fs";
|
|
18013
|
-
import { dirname as dirname7, join as
|
|
18340
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
18014
18341
|
import { resolveProjectDir as resolveProjectDir2 } from "@wrongstack/core/coordination";
|
|
18015
18342
|
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
18016
18343
|
import { readLiveLock } from "@wrongstack/core/coordination";
|
|
@@ -18139,7 +18466,7 @@ function mailboxServeInvocation(projectRoot) {
|
|
|
18139
18466
|
function findWorkspaceCliEntry(projectRoot) {
|
|
18140
18467
|
let dir = projectRoot;
|
|
18141
18468
|
for (let i = 0; i < 6; i++) {
|
|
18142
|
-
const candidate =
|
|
18469
|
+
const candidate = join11(dir, "packages", "cli", "dist", "index.js");
|
|
18143
18470
|
if (existsSync(candidate)) return candidate;
|
|
18144
18471
|
const parent = dirname7(dir);
|
|
18145
18472
|
if (parent === dir) return null;
|
|
@@ -18384,10 +18711,10 @@ function clampDim(value, fallback) {
|
|
|
18384
18711
|
}
|
|
18385
18712
|
|
|
18386
18713
|
// src/server/worktree-ws-handler.ts
|
|
18387
|
-
import { join as
|
|
18714
|
+
import { join as join12, resolve as resolve12, sep as sep5 } from "node:path";
|
|
18715
|
+
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
18388
18716
|
import { WorktreeManager as WorktreeManager3 } from "@wrongstack/core/worktree";
|
|
18389
18717
|
import { cleanupStaleSddWorktrees as cleanupStaleSddWorktrees2 } from "@wrongstack/sdd";
|
|
18390
|
-
import { toErrorMessage as toErrorMessage8 } from "@wrongstack/core/utils";
|
|
18391
18718
|
var MAX_ACTIVITY = 6;
|
|
18392
18719
|
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["allocating", "active", "committing", "merging"]);
|
|
18393
18720
|
var MANAGED_BRANCH_RE = /^wstack\/ap\/[A-Za-z0-9._/-]+$/;
|
|
@@ -18406,6 +18733,20 @@ var WorktreeWebSocketHandler = class {
|
|
|
18406
18733
|
baseBranch = "";
|
|
18407
18734
|
broadcastInterval = null;
|
|
18408
18735
|
offs = [];
|
|
18736
|
+
/**
|
|
18737
|
+
* Single-flight guard for orphan scans. Two concurrent `scanAndBroadcast()`
|
|
18738
|
+
* calls would race: the first call reads `wt.listManaged()`, then the
|
|
18739
|
+
* second call's mutation completes (e.g. `removeOne`), then the first call
|
|
18740
|
+
* broadcasts a stale orphan list — the user sees a row persist and clicks
|
|
18741
|
+
* Remove a second time, hitting "remove failed (not a managed worktree?)".
|
|
18742
|
+
*
|
|
18743
|
+
* We coalesce concurrent triggers: while a scan is in flight, additional
|
|
18744
|
+
* callers are remembered as "need a re-scan" and exactly one follow-up
|
|
18745
|
+
* scan runs after the in-flight one finishes. The re-scan picks up any
|
|
18746
|
+
* mutations that landed during the previous scan's `listManaged()` window.
|
|
18747
|
+
*/
|
|
18748
|
+
scanInFlight = null;
|
|
18749
|
+
scanRescanNeeded = false;
|
|
18409
18750
|
addClient(ws) {
|
|
18410
18751
|
this.clients.add(ws);
|
|
18411
18752
|
ws.on("close", () => this.clients.delete(ws));
|
|
@@ -18424,7 +18765,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
18424
18765
|
return true;
|
|
18425
18766
|
}
|
|
18426
18767
|
if (msg.type === "worktree.remove") {
|
|
18427
|
-
await this.removeOne(
|
|
18768
|
+
await this.removeOne(
|
|
18769
|
+
msg.payload?.["dir"],
|
|
18770
|
+
msg.payload?.["branch"]
|
|
18771
|
+
);
|
|
18428
18772
|
return true;
|
|
18429
18773
|
}
|
|
18430
18774
|
if (msg.type === "worktree.merge") {
|
|
@@ -18432,7 +18776,10 @@ var WorktreeWebSocketHandler = class {
|
|
|
18432
18776
|
return true;
|
|
18433
18777
|
}
|
|
18434
18778
|
if (msg.type === "worktree.diff") {
|
|
18435
|
-
await this.diffOne(
|
|
18779
|
+
await this.diffOne(
|
|
18780
|
+
msg.payload?.["dir"],
|
|
18781
|
+
msg.payload?.["baseBranch"]
|
|
18782
|
+
);
|
|
18436
18783
|
return true;
|
|
18437
18784
|
}
|
|
18438
18785
|
return false;
|
|
@@ -18445,7 +18792,7 @@ var WorktreeWebSocketHandler = class {
|
|
|
18445
18792
|
// ── orphan management ─────────────────────────────────────────────────────
|
|
18446
18793
|
/** Absolute managed-worktrees root for this project. */
|
|
18447
18794
|
worktreesRoot() {
|
|
18448
|
-
return resolve12(
|
|
18795
|
+
return resolve12(join12(this.management.projectRoot, ".wrongstack", "worktrees"));
|
|
18449
18796
|
}
|
|
18450
18797
|
/** True iff `dir` resolves strictly inside the managed worktrees root. */
|
|
18451
18798
|
underRoot(dir) {
|
|
@@ -18461,12 +18808,43 @@ var WorktreeWebSocketHandler = class {
|
|
|
18461
18808
|
}
|
|
18462
18809
|
return live;
|
|
18463
18810
|
}
|
|
18811
|
+
/**
|
|
18812
|
+
* Coalesced orphan scan entry point. Multiple concurrent callers share one
|
|
18813
|
+
* in-flight scan; if a caller arrives during a scan, a single follow-up
|
|
18814
|
+
* scan is scheduled to pick up mutations that landed mid-scan. Returns the
|
|
18815
|
+
* shared promise so `await this.scanAndBroadcast()` still works for callers
|
|
18816
|
+
* that want to chain after the next broadcast.
|
|
18817
|
+
*/
|
|
18818
|
+
scanAndBroadcast() {
|
|
18819
|
+
if (this.scanInFlight) {
|
|
18820
|
+
this.scanRescanNeeded = true;
|
|
18821
|
+
return this.scanInFlight;
|
|
18822
|
+
}
|
|
18823
|
+
this.scanInFlight = this.runScanAndMaybeRescan();
|
|
18824
|
+
return this.scanInFlight;
|
|
18825
|
+
}
|
|
18826
|
+
/**
|
|
18827
|
+
* Run the actual scan, then drain a pending re-scan request if one came in
|
|
18828
|
+
* during the scan. The re-scan is bounded (one follow-up) — additional
|
|
18829
|
+
* requests that arrive during the follow-up coalesce into the next one.
|
|
18830
|
+
*/
|
|
18831
|
+
async runScanAndMaybeRescan() {
|
|
18832
|
+
try {
|
|
18833
|
+
await this.runScanOnce();
|
|
18834
|
+
while (this.scanRescanNeeded) {
|
|
18835
|
+
this.scanRescanNeeded = false;
|
|
18836
|
+
await this.runScanOnce();
|
|
18837
|
+
}
|
|
18838
|
+
} finally {
|
|
18839
|
+
this.scanInFlight = null;
|
|
18840
|
+
}
|
|
18841
|
+
}
|
|
18464
18842
|
/**
|
|
18465
18843
|
* Scan the disk for managed worktrees/branches NOT owned by a live in-session
|
|
18466
18844
|
* run and broadcast them as orphans, with whether it is safe to clean now.
|
|
18467
18845
|
* No-op (empty inventory) when management deps were not wired.
|
|
18468
18846
|
*/
|
|
18469
|
-
async
|
|
18847
|
+
async runScanOnce() {
|
|
18470
18848
|
if (!this.management) {
|
|
18471
18849
|
this.broadcast({ type: "worktree.orphans", payload: { orphans: [], canClean: false } });
|
|
18472
18850
|
return;
|
|
@@ -18522,7 +18900,8 @@ var WorktreeWebSocketHandler = class {
|
|
|
18522
18900
|
}
|
|
18523
18901
|
const res = await cleanupStaleSddWorktrees2({
|
|
18524
18902
|
projectRoot: this.management.projectRoot,
|
|
18525
|
-
boardsDir: this.management.boardsDir
|
|
18903
|
+
boardsDir: this.management.boardsDir,
|
|
18904
|
+
stateTransport: "kanban"
|
|
18526
18905
|
});
|
|
18527
18906
|
if (res.skippedReason) {
|
|
18528
18907
|
this.broadcast({
|
|
@@ -18535,26 +18914,45 @@ var WorktreeWebSocketHandler = class {
|
|
|
18535
18914
|
for (const [id, h] of [...this.handles]) {
|
|
18536
18915
|
if (!ACTIVE_STATUSES.has(h.status)) this.handles.delete(id);
|
|
18537
18916
|
}
|
|
18538
|
-
this.broadcast({
|
|
18917
|
+
this.broadcast({
|
|
18918
|
+
type: "worktree.cleanup_result",
|
|
18919
|
+
payload: { ok: true, removed: res.removed }
|
|
18920
|
+
});
|
|
18539
18921
|
this.broadcastState();
|
|
18540
18922
|
await this.scanAndBroadcast();
|
|
18541
18923
|
}
|
|
18542
18924
|
/** Remove/discard ONE worktree + branch. Refused while a live run owns it. */
|
|
18543
18925
|
async removeOne(dir, branch) {
|
|
18544
18926
|
if (!this.management || !dir && !branch) {
|
|
18545
|
-
this.broadcast({
|
|
18927
|
+
this.broadcast({
|
|
18928
|
+
type: "worktree.cleanup_result",
|
|
18929
|
+
payload: { ok: false, removed: 0, reason: "nothing to remove" }
|
|
18930
|
+
});
|
|
18546
18931
|
return;
|
|
18547
18932
|
}
|
|
18548
18933
|
if (branch && !MANAGED_BRANCH_RE.test(branch)) {
|
|
18549
|
-
this.broadcast({
|
|
18934
|
+
this.broadcast({
|
|
18935
|
+
type: "worktree.cleanup_result",
|
|
18936
|
+
payload: { ok: false, removed: 0, reason: "not a managed worktree branch" }
|
|
18937
|
+
});
|
|
18550
18938
|
return;
|
|
18551
18939
|
}
|
|
18552
18940
|
if (dir && !this.underRoot(dir)) {
|
|
18553
|
-
this.broadcast({
|
|
18941
|
+
this.broadcast({
|
|
18942
|
+
type: "worktree.cleanup_result",
|
|
18943
|
+
payload: { ok: false, removed: 0, reason: "path is outside the managed worktrees root" }
|
|
18944
|
+
});
|
|
18554
18945
|
return;
|
|
18555
18946
|
}
|
|
18556
18947
|
if (branch && this.liveActiveBranches().has(branch)) {
|
|
18557
|
-
this.broadcast({
|
|
18948
|
+
this.broadcast({
|
|
18949
|
+
type: "worktree.cleanup_result",
|
|
18950
|
+
payload: {
|
|
18951
|
+
ok: false,
|
|
18952
|
+
removed: 0,
|
|
18953
|
+
reason: "a run is live on this worktree \u2014 stop it first"
|
|
18954
|
+
}
|
|
18955
|
+
});
|
|
18558
18956
|
return;
|
|
18559
18957
|
}
|
|
18560
18958
|
let removed = false;
|
|
@@ -18563,31 +18961,54 @@ var WorktreeWebSocketHandler = class {
|
|
|
18563
18961
|
({ removed } = await wt.removeOne(dir, branch));
|
|
18564
18962
|
}
|
|
18565
18963
|
for (const [id, h] of [...this.handles]) {
|
|
18566
|
-
if (branch && h.branch === branch || dir && h.handleId && dir.endsWith(h.handleId))
|
|
18964
|
+
if (branch && h.branch === branch || dir && h.handleId && dir.endsWith(h.handleId))
|
|
18965
|
+
this.handles.delete(id);
|
|
18567
18966
|
}
|
|
18568
|
-
this.broadcast({
|
|
18967
|
+
this.broadcast({
|
|
18968
|
+
type: "worktree.cleanup_result",
|
|
18969
|
+
payload: {
|
|
18970
|
+
ok: removed,
|
|
18971
|
+
removed: removed ? 1 : 0,
|
|
18972
|
+
reason: removed ? void 0 : "remove failed (not a managed worktree?)"
|
|
18973
|
+
}
|
|
18974
|
+
});
|
|
18569
18975
|
this.broadcastState();
|
|
18570
18976
|
await this.scanAndBroadcast();
|
|
18571
18977
|
}
|
|
18572
18978
|
/** Squash-merge ONE branch into base. Refused while a live run owns it. */
|
|
18573
18979
|
async mergeBranch(branch) {
|
|
18574
18980
|
if (!this.management || !branch) {
|
|
18575
|
-
this.broadcast({
|
|
18981
|
+
this.broadcast({
|
|
18982
|
+
type: "worktree.merge_result",
|
|
18983
|
+
payload: { ok: false, branch: branch ?? "", reason: "no branch" }
|
|
18984
|
+
});
|
|
18576
18985
|
return;
|
|
18577
18986
|
}
|
|
18578
18987
|
if (!MANAGED_BRANCH_RE.test(branch)) {
|
|
18579
|
-
this.broadcast({
|
|
18988
|
+
this.broadcast({
|
|
18989
|
+
type: "worktree.merge_result",
|
|
18990
|
+
payload: { ok: false, branch, reason: "not a managed worktree branch" }
|
|
18991
|
+
});
|
|
18580
18992
|
return;
|
|
18581
18993
|
}
|
|
18582
18994
|
if (this.liveActiveBranches().has(branch)) {
|
|
18583
|
-
this.broadcast({
|
|
18995
|
+
this.broadcast({
|
|
18996
|
+
type: "worktree.merge_result",
|
|
18997
|
+
payload: { ok: false, branch, reason: "a run is live on this worktree \u2014 stop it first" }
|
|
18998
|
+
});
|
|
18584
18999
|
return;
|
|
18585
19000
|
}
|
|
18586
19001
|
const wt = new WorktreeManager3({ projectRoot: this.management.projectRoot });
|
|
18587
19002
|
const res = await wt.mergeBranch(branch);
|
|
18588
19003
|
this.broadcast({
|
|
18589
19004
|
type: "worktree.merge_result",
|
|
18590
|
-
payload: {
|
|
19005
|
+
payload: {
|
|
19006
|
+
ok: res.ok,
|
|
19007
|
+
branch,
|
|
19008
|
+
conflict: res.conflict,
|
|
19009
|
+
conflictFiles: res.conflictFiles,
|
|
19010
|
+
reason: res.reason
|
|
19011
|
+
}
|
|
18591
19012
|
});
|
|
18592
19013
|
await this.scanAndBroadcast();
|
|
18593
19014
|
}
|
|
@@ -18629,8 +19050,14 @@ var WorktreeWebSocketHandler = class {
|
|
|
18629
19050
|
}),
|
|
18630
19051
|
on("worktree.committed", (p) => {
|
|
18631
19052
|
const e = p;
|
|
18632
|
-
this.patch(e.handleId, {
|
|
18633
|
-
|
|
19053
|
+
this.patch(e.handleId, {
|
|
19054
|
+
status: "committing",
|
|
19055
|
+
insertions: e.insertions,
|
|
19056
|
+
deletions: e.deletions,
|
|
19057
|
+
files: e.files
|
|
19058
|
+
});
|
|
19059
|
+
if (e.committed)
|
|
19060
|
+
this.activity(e.handleId, "committed", `+${e.insertions}/-${e.deletions} (${e.files}f)`);
|
|
18634
19061
|
this.broadcastState();
|
|
18635
19062
|
}),
|
|
18636
19063
|
on("worktree.merged", (p) => {
|
|
@@ -18671,10 +19098,15 @@ var WorktreeWebSocketHandler = class {
|
|
|
18671
19098
|
activity(id, kind, text) {
|
|
18672
19099
|
const cur = this.handles.get(id);
|
|
18673
19100
|
if (cur) {
|
|
18674
|
-
const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(
|
|
19101
|
+
const recentActivity = [...cur.recentActivity, { kind, text, at: Date.now() }].slice(
|
|
19102
|
+
-MAX_ACTIVITY
|
|
19103
|
+
);
|
|
18675
19104
|
this.handles.set(id, { ...cur, recentActivity });
|
|
18676
19105
|
}
|
|
18677
|
-
this.broadcast({
|
|
19106
|
+
this.broadcast({
|
|
19107
|
+
type: "worktree.event",
|
|
19108
|
+
payload: { kind, handleId: id, text, at: Date.now() }
|
|
19109
|
+
});
|
|
18678
19110
|
}
|
|
18679
19111
|
stateMessage() {
|
|
18680
19112
|
return {
|
|
@@ -18916,7 +19348,7 @@ async function createAgentServices(input) {
|
|
|
18916
19348
|
const brainCfg = resolveBrainConfigDefaults(config.brain, {
|
|
18917
19349
|
fallbackModels: config.fallbackModels
|
|
18918
19350
|
});
|
|
18919
|
-
const brainLedgerPath =
|
|
19351
|
+
const brainLedgerPath = join13(wpaths.projectDir, "brain-ledger.jsonl");
|
|
18920
19352
|
let brainLedgerEnabled = brainCfg.ledger?.enabled !== false;
|
|
18921
19353
|
let brainLedger;
|
|
18922
19354
|
const startBrainLedger = () => {
|
|
@@ -18981,33 +19413,35 @@ async function createAgentServices(input) {
|
|
|
18981
19413
|
brainLog.push(entry);
|
|
18982
19414
|
if (brainLog.length > 20) brainLog.shift();
|
|
18983
19415
|
};
|
|
18984
|
-
|
|
18985
|
-
|
|
18986
|
-
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
|
|
19010
|
-
|
|
19416
|
+
const brainLogOffs = [
|
|
19417
|
+
events.on(
|
|
19418
|
+
"brain.decision_answered",
|
|
19419
|
+
(e) => pushBrainLog({
|
|
19420
|
+
at: e.at,
|
|
19421
|
+
kind: "answered",
|
|
19422
|
+
question: e.request.question,
|
|
19423
|
+
outcome: e.decision.type === "answer" ? e.decision.optionId ?? e.decision.text : ""
|
|
19424
|
+
})
|
|
19425
|
+
),
|
|
19426
|
+
events.on(
|
|
19427
|
+
"brain.decision_ask_human",
|
|
19428
|
+
(e) => pushBrainLog({
|
|
19429
|
+
at: e.at,
|
|
19430
|
+
kind: "ask_human",
|
|
19431
|
+
question: e.request.question,
|
|
19432
|
+
outcome: "needs human judgement"
|
|
19433
|
+
})
|
|
19434
|
+
),
|
|
19435
|
+
events.on(
|
|
19436
|
+
"brain.decision_denied",
|
|
19437
|
+
(e) => pushBrainLog({
|
|
19438
|
+
at: e.at,
|
|
19439
|
+
kind: "denied",
|
|
19440
|
+
question: e.request.question,
|
|
19441
|
+
outcome: e.decision.type === "deny" ? e.decision.reason : ""
|
|
19442
|
+
})
|
|
19443
|
+
)
|
|
19444
|
+
];
|
|
19011
19445
|
const brainMailbox = getSharedProjectMailbox2(wpaths.projectDir, events);
|
|
19012
19446
|
brainMonitor = new BrainMonitor({
|
|
19013
19447
|
events,
|
|
@@ -19119,6 +19553,17 @@ async function createAgentServices(input) {
|
|
|
19119
19553
|
getActiveSessionId: () => context.session.id
|
|
19120
19554
|
}
|
|
19121
19555
|
);
|
|
19556
|
+
let realtimeHandlersDisposed = false;
|
|
19557
|
+
const disposeRealtimeHandlers = () => {
|
|
19558
|
+
if (realtimeHandlersDisposed) return;
|
|
19559
|
+
realtimeHandlersDisposed = true;
|
|
19560
|
+
for (const off of brainLogOffs) off();
|
|
19561
|
+
goalHandler.dispose();
|
|
19562
|
+
sddBoardHandler.dispose();
|
|
19563
|
+
worktreeHandler.dispose();
|
|
19564
|
+
terminalHandler.dispose();
|
|
19565
|
+
collabHandler.dispose();
|
|
19566
|
+
};
|
|
19122
19567
|
return {
|
|
19123
19568
|
collabBus,
|
|
19124
19569
|
compactor,
|
|
@@ -19144,6 +19589,7 @@ async function createAgentServices(input) {
|
|
|
19144
19589
|
worktreeHandler,
|
|
19145
19590
|
terminalHandler,
|
|
19146
19591
|
collabHandler,
|
|
19592
|
+
disposeRealtimeHandlers,
|
|
19147
19593
|
updateAutoCompactionMaxContext
|
|
19148
19594
|
};
|
|
19149
19595
|
}
|
|
@@ -19230,7 +19676,7 @@ function createConnectionHandler(options) {
|
|
|
19230
19676
|
}
|
|
19231
19677
|
|
|
19232
19678
|
// src/server/message-dispatcher.ts
|
|
19233
|
-
import
|
|
19679
|
+
import path23 from "node:path";
|
|
19234
19680
|
function createMessageDispatcher(opts) {
|
|
19235
19681
|
const { state, deps: deps2, routes, promptsCtx, codebaseIndexing, runLock, pendingConfirms } = opts;
|
|
19236
19682
|
function makeWorklistContext() {
|
|
@@ -19251,7 +19697,7 @@ function createMessageDispatcher(opts) {
|
|
|
19251
19697
|
skillLoader: deps2.skillLoader,
|
|
19252
19698
|
skillInstaller: deps2.skillInstaller,
|
|
19253
19699
|
projectRoot,
|
|
19254
|
-
projectSkillsDir:
|
|
19700
|
+
projectSkillsDir: path23.join(projectRoot, ".wrongstack", "skills"),
|
|
19255
19701
|
globalSkillsDir: deps2.wpaths.globalSkills
|
|
19256
19702
|
};
|
|
19257
19703
|
}
|
|
@@ -19503,7 +19949,7 @@ function createMessageDispatcher(opts) {
|
|
|
19503
19949
|
|
|
19504
19950
|
// src/server/pre-context-services.ts
|
|
19505
19951
|
import { createRequire as createRequire3 } from "node:module";
|
|
19506
|
-
import * as
|
|
19952
|
+
import * as path26 from "node:path";
|
|
19507
19953
|
import { Context, DefaultSystemPromptBuilder } from "@wrongstack/core/agent";
|
|
19508
19954
|
import {
|
|
19509
19955
|
getSharedProjectMailbox as getSharedProjectMailbox3,
|
|
@@ -19557,8 +20003,8 @@ import { configureDangerBypass, configureExecPolicy } from "@wrongstack/tools";
|
|
|
19557
20003
|
import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/tools/session-kanban";
|
|
19558
20004
|
|
|
19559
20005
|
// src/server/model-auto-discovery.ts
|
|
19560
|
-
import * as
|
|
19561
|
-
import * as
|
|
20006
|
+
import * as fs19 from "node:fs/promises";
|
|
20007
|
+
import * as path24 from "node:path";
|
|
19562
20008
|
import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
|
|
19563
20009
|
function isOverlayRegistry(value) {
|
|
19564
20010
|
return !!value && typeof value === "object" && typeof value.mergeOverlay === "function";
|
|
@@ -19584,7 +20030,7 @@ function eligibleProviders(config) {
|
|
|
19584
20030
|
}
|
|
19585
20031
|
async function readCache(file) {
|
|
19586
20032
|
try {
|
|
19587
|
-
return JSON.parse(await
|
|
20033
|
+
return JSON.parse(await fs19.readFile(file, "utf8"));
|
|
19588
20034
|
} catch {
|
|
19589
20035
|
return {};
|
|
19590
20036
|
}
|
|
@@ -19594,7 +20040,7 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19594
20040
|
if (!isOverlayRegistry(registry)) return;
|
|
19595
20041
|
const targets = eligibleProviders(opts.config);
|
|
19596
20042
|
if (targets.length === 0) return;
|
|
19597
|
-
const cacheFile =
|
|
20043
|
+
const cacheFile = path24.join(opts.cacheDir, "discovered-models-cache.json");
|
|
19598
20044
|
const cache2 = await readCache(cacheFile);
|
|
19599
20045
|
let cacheDirty = false;
|
|
19600
20046
|
await Promise.all(
|
|
@@ -19631,8 +20077,8 @@ async function discoverAndMergeWebuiProviders(opts) {
|
|
|
19631
20077
|
);
|
|
19632
20078
|
if (cacheDirty) {
|
|
19633
20079
|
try {
|
|
19634
|
-
await
|
|
19635
|
-
await
|
|
20080
|
+
await fs19.mkdir(path24.dirname(cacheFile), { recursive: true });
|
|
20081
|
+
await fs19.writeFile(cacheFile, JSON.stringify(cache2), "utf8");
|
|
19636
20082
|
} catch {
|
|
19637
20083
|
opts.logger?.debug?.("provider auto-discovery cache write failed");
|
|
19638
20084
|
}
|
|
@@ -19728,7 +20174,7 @@ function resolveSetupProvider(opts) {
|
|
|
19728
20174
|
}
|
|
19729
20175
|
|
|
19730
20176
|
// src/server/standalone-session-identity.ts
|
|
19731
|
-
import * as
|
|
20177
|
+
import * as path25 from "node:path";
|
|
19732
20178
|
import {
|
|
19733
20179
|
AgentStatusTracker,
|
|
19734
20180
|
FleetNotifier,
|
|
@@ -19747,7 +20193,7 @@ async function createStandaloneSessionIdentityLifecycle(opts) {
|
|
|
19747
20193
|
let activeTarget = {
|
|
19748
20194
|
projectSlug: paths.projectSlug,
|
|
19749
20195
|
projectRoot: paths.projectRoot,
|
|
19750
|
-
projectName:
|
|
20196
|
+
projectName: path25.basename(paths.projectRoot),
|
|
19751
20197
|
workingDir: opts.workingDir
|
|
19752
20198
|
};
|
|
19753
20199
|
let pendingClaim;
|
|
@@ -19978,7 +20424,7 @@ async function createPreContextServices(input) {
|
|
|
19978
20424
|
await discoverAndMergeWebuiProviders({
|
|
19979
20425
|
config,
|
|
19980
20426
|
registry: modelsRegistry,
|
|
19981
|
-
cacheDir:
|
|
20427
|
+
cacheDir: path26.dirname(wpaths.modelsCache),
|
|
19982
20428
|
logger
|
|
19983
20429
|
});
|
|
19984
20430
|
} catch (err) {
|
|
@@ -20030,7 +20476,7 @@ async function createPreContextServices(input) {
|
|
|
20030
20476
|
configureChildEnvGitIdentity(config.git?.identity ?? null);
|
|
20031
20477
|
console.log("[WebUI] Tool registry loaded:", toolRegistry.list().length, "tools");
|
|
20032
20478
|
const mcpTokenStore = new MCPVaultTokenStore(
|
|
20033
|
-
|
|
20479
|
+
path26.join(wpaths.projectDir, "mcp-auth.json"),
|
|
20034
20480
|
vault
|
|
20035
20481
|
);
|
|
20036
20482
|
const mcpAuthorizationManager = new MCPAuthorizationManager({ store: mcpTokenStore });
|
|
@@ -20136,7 +20582,7 @@ async function createPreContextServices(input) {
|
|
|
20136
20582
|
};
|
|
20137
20583
|
const skillLoader = config.features.skills ? new DefaultSkillLoader({ paths: wpaths }) : void 0;
|
|
20138
20584
|
const skillInstaller = config.features.skills ? new SkillInstaller({
|
|
20139
|
-
manifestPath:
|
|
20585
|
+
manifestPath: path26.join(wpaths.configDir, "installed-skills.json"),
|
|
20140
20586
|
projectSkillsDir: wpaths.inProjectSkills,
|
|
20141
20587
|
globalSkillsDir: wpaths.globalSkills,
|
|
20142
20588
|
projectHash: wpaths.projectHash,
|
|
@@ -20146,8 +20592,8 @@ async function createPreContextServices(input) {
|
|
|
20146
20592
|
const bundledPromptsDir = promptsEnabled ? (() => {
|
|
20147
20593
|
try {
|
|
20148
20594
|
const req = createRequire3(import.meta.url);
|
|
20149
|
-
return
|
|
20150
|
-
|
|
20595
|
+
return path26.join(
|
|
20596
|
+
path26.dirname(req.resolve("@wrongstack/core/package.json")),
|
|
20151
20597
|
"data",
|
|
20152
20598
|
"prompts"
|
|
20153
20599
|
);
|
|
@@ -20251,7 +20697,7 @@ async function createPreContextServices(input) {
|
|
|
20251
20697
|
}
|
|
20252
20698
|
|
|
20253
20699
|
// src/server/routes.ts
|
|
20254
|
-
import
|
|
20700
|
+
import path27 from "node:path";
|
|
20255
20701
|
import { makeProviderFromConfig as makeProviderFromConfig2, withCatalogCapabilities } from "@wrongstack/providers";
|
|
20256
20702
|
|
|
20257
20703
|
// src/server/mode-handlers.ts
|
|
@@ -20543,7 +20989,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20543
20989
|
};
|
|
20544
20990
|
const mailboxRoutes = createMailboxRouteHandlers({
|
|
20545
20991
|
getProjectRoot: state.getProjectRoot,
|
|
20546
|
-
getGlobalRoot: () =>
|
|
20992
|
+
getGlobalRoot: () => path27.dirname(deps2.globalConfigPath),
|
|
20547
20993
|
events: deps2.events
|
|
20548
20994
|
});
|
|
20549
20995
|
const mcpRoutes = {
|
|
@@ -20616,7 +21062,7 @@ function buildRoutes(state, deps2, cb) {
|
|
|
20616
21062
|
}
|
|
20617
21063
|
|
|
20618
21064
|
// src/server/server-runtime.ts
|
|
20619
|
-
import * as
|
|
21065
|
+
import * as path28 from "node:path";
|
|
20620
21066
|
import { createRequire as createRequire4 } from "node:module";
|
|
20621
21067
|
import { fileURLToPath } from "node:url";
|
|
20622
21068
|
import { WebSocketServer } from "ws";
|
|
@@ -20677,7 +21123,7 @@ function createSessionStartPayload(g) {
|
|
|
20677
21123
|
inputCost,
|
|
20678
21124
|
outputCost,
|
|
20679
21125
|
cacheReadCost,
|
|
20680
|
-
projectName:
|
|
21126
|
+
projectName: path28.basename(projectRoot) || projectRoot,
|
|
20681
21127
|
projectRoot,
|
|
20682
21128
|
cwd: g.getWorkingDir(),
|
|
20683
21129
|
mode: g.getModeId(),
|
|
@@ -20765,13 +21211,13 @@ function armEvents(wssPrimary, wssSecondary, wsHost, httpPort, setupInput, watch
|
|
|
20765
21211
|
};
|
|
20766
21212
|
}
|
|
20767
21213
|
function resolveWebuiDistDir(fromUrl, explicitDistDir) {
|
|
20768
|
-
if (explicitDistDir) return
|
|
21214
|
+
if (explicitDistDir) return path28.resolve(explicitDistDir);
|
|
20769
21215
|
try {
|
|
20770
21216
|
const requireFromHere2 = createRequire4(fromUrl);
|
|
20771
21217
|
const serverEntry = requireFromHere2.resolve("@wrongstack/webui");
|
|
20772
|
-
return
|
|
21218
|
+
return path28.dirname(serverEntry);
|
|
20773
21219
|
} catch {
|
|
20774
|
-
return
|
|
21220
|
+
return path28.resolve(path28.dirname(fileURLToPath(fromUrl)), "..", "..", "dist");
|
|
20775
21221
|
}
|
|
20776
21222
|
}
|
|
20777
21223
|
function startHttpServer(opts) {
|
|
@@ -20985,6 +21431,7 @@ async function startWebUI(opts = {}) {
|
|
|
20985
21431
|
worktreeHandler,
|
|
20986
21432
|
terminalHandler,
|
|
20987
21433
|
collabHandler,
|
|
21434
|
+
disposeRealtimeHandlers,
|
|
20988
21435
|
updateAutoCompactionMaxContext
|
|
20989
21436
|
} = agentServices;
|
|
20990
21437
|
if (typeof context.meta["yolo"] === "boolean") {
|
|
@@ -21163,21 +21610,21 @@ async function startWebUI(opts = {}) {
|
|
|
21163
21610
|
});
|
|
21164
21611
|
}
|
|
21165
21612
|
async function touchProjectEntry(root, workDir) {
|
|
21166
|
-
const resolved =
|
|
21613
|
+
const resolved = path29.resolve(root);
|
|
21167
21614
|
const manifest = await loadManifest(globalConfigPath);
|
|
21168
21615
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21169
|
-
const existing = manifest.projects.find((p) =>
|
|
21616
|
+
const existing = manifest.projects.find((p) => path29.resolve(p.root) === resolved);
|
|
21170
21617
|
if (existing) {
|
|
21171
21618
|
existing.lastSeen = now;
|
|
21172
|
-
if (workDir) existing.lastWorkingDir =
|
|
21619
|
+
if (workDir) existing.lastWorkingDir = path29.resolve(workDir);
|
|
21173
21620
|
} else {
|
|
21174
21621
|
manifest.projects.push({
|
|
21175
|
-
name:
|
|
21622
|
+
name: path29.basename(resolved),
|
|
21176
21623
|
root: resolved,
|
|
21177
21624
|
slug: generateProjectSlug(resolved),
|
|
21178
21625
|
createdAt: now,
|
|
21179
21626
|
lastSeen: now,
|
|
21180
|
-
lastWorkingDir: workDir ?
|
|
21627
|
+
lastWorkingDir: workDir ? path29.resolve(workDir) : void 0
|
|
21181
21628
|
});
|
|
21182
21629
|
}
|
|
21183
21630
|
await saveManifest(manifest, globalConfigPath);
|
|
@@ -21354,7 +21801,20 @@ async function startWebUI(opts = {}) {
|
|
|
21354
21801
|
);
|
|
21355
21802
|
credentialWatcherClose = credentialWatcher.close;
|
|
21356
21803
|
}
|
|
21357
|
-
const stopHeapWatchdog =
|
|
21804
|
+
const stopHeapWatchdog = startSharedHeapWatchdog({
|
|
21805
|
+
collectStats: () => ({
|
|
21806
|
+
surface: opts.surface ?? "webui",
|
|
21807
|
+
sessionId: context.session.id,
|
|
21808
|
+
messages: context.state.messages.length,
|
|
21809
|
+
messageEstimatedTokens: context.state.messages.reduce(
|
|
21810
|
+
(sum, message) => sum + (message._estTokens ?? 0),
|
|
21811
|
+
0
|
|
21812
|
+
),
|
|
21813
|
+
webClients: clients.size,
|
|
21814
|
+
pendingConfirms: pendingConfirms.size,
|
|
21815
|
+
runActive: runLockControl.get() !== null
|
|
21816
|
+
})
|
|
21817
|
+
});
|
|
21358
21818
|
const routes = buildRoutes(state, deps2, cb);
|
|
21359
21819
|
const handleMessage = createMessageDispatcher({
|
|
21360
21820
|
state,
|
|
@@ -21432,6 +21892,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21432
21892
|
await todosCheckpoint.detach();
|
|
21433
21893
|
await stopHeapWatchdog();
|
|
21434
21894
|
credentialWatcherClose?.();
|
|
21895
|
+
disposeRealtimeHandlers();
|
|
21435
21896
|
brainMonitor.stop();
|
|
21436
21897
|
await agentServices.brainLedger?.stop().catch(() => {
|
|
21437
21898
|
});
|
|
@@ -21455,7 +21916,7 @@ projectRoot: ${ev.projectRoot ?? "?"}`,
|
|
|
21455
21916
|
await memoryStore.dispose().catch(
|
|
21456
21917
|
(err) => logger.warn(`sage connection disposal failed: ${toErrorMessage13(err)}`)
|
|
21457
21918
|
);
|
|
21458
|
-
await unregisterInstance(process.pid,
|
|
21919
|
+
await unregisterInstance(process.pid, path29.dirname(globalConfigPath));
|
|
21459
21920
|
}
|
|
21460
21921
|
});
|
|
21461
21922
|
}
|
|
@@ -21548,7 +22009,7 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
21548
22009
|
console.error(err instanceof Error ? err.message : String(err));
|
|
21549
22010
|
process.exit(1);
|
|
21550
22011
|
}
|
|
21551
|
-
const
|
|
22012
|
+
const open2 = argv.includes("--open") || argv.includes("-o") || process.env["WEBUI_OPEN"] === "1";
|
|
21552
22013
|
const requireToken = argv.includes("--require-token") || envFlag2("WEBUI_REQUIRE_TOKEN");
|
|
21553
22014
|
console.log(`[WebUI] Starting standalone server on ${wsHost} (http:${httpPort})...`);
|
|
21554
22015
|
startWebUI({
|
|
@@ -21558,7 +22019,7 @@ if (argv.includes("--help") || argv.includes("-h")) {
|
|
|
21558
22019
|
publicUrl,
|
|
21559
22020
|
publicWsUrl,
|
|
21560
22021
|
requireToken,
|
|
21561
|
-
open,
|
|
22022
|
+
open: open2,
|
|
21562
22023
|
distDir
|
|
21563
22024
|
}).catch((err) => {
|
|
21564
22025
|
console.error(JSON.stringify({
|