@deepseek-insight/dsi 0.6.16 → 0.6.18
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/README.md +2 -2
- package/bin/dsi.mjs +77 -21
- package/bin/lib/dev-mode.mjs +18 -1
- package/bin/lib/plugin-chain.mjs +97 -0
- package/build/client/_app/immutable/assets/2.C7lNCQCV.css +1 -0
- package/build/client/_app/immutable/chunks/{DOkUpqkd.js → Bqotzgnw.js} +1 -1
- package/build/client/_app/immutable/chunks/{BXGojodM.js → CtUqZStl.js} +2 -2
- package/build/client/_app/immutable/chunks/D-YP16Kb.js +1 -0
- package/build/client/_app/immutable/chunks/D21lpWN6.js +1 -0
- package/build/client/_app/immutable/entry/{app.CfbFUHxw.js → app.CGvLdbvX.js} +2 -2
- package/build/client/_app/immutable/entry/start.-NibTCMf.js +1 -0
- package/build/client/_app/immutable/nodes/{1.C2BfYC2f.js → 1.CiNEHoEh.js} +1 -1
- package/build/client/_app/immutable/nodes/{2.UdTbIvwR.js → 2.5NpWuIn7.js} +219 -219
- package/build/client/_app/version.json +1 -1
- package/build/handler.js +4 -4
- package/build/index.js +4 -4
- package/build/server/chunks/chunks/{dsh-connection.js-DvncrWM4.js → dsh-connection.js-DL5qjlq2.js} +211 -6
- package/build/server/chunks/chunks/{dsh-events.js-B_E1BU9F.js → dsh-events.js-35seDLwL.js} +3 -1
- package/build/server/chunks/chunks/{engine.js-1gYqtdBP.js → engine.js-EfjwaLzA.js} +2 -2
- package/build/server/chunks/chunks/{git-probe.js-S9w9gGXE.js → git-probe.js-D4DjBXSY.js} +3 -3
- package/build/server/chunks/chunks/{internal2.js-DMu68nYR.js → internal2.js-C4ukUWRE.js} +2 -2
- package/build/server/chunks/chunks/{restart-chain.js-D-VwGWE2.js → restart-chain.js-KZO2ISmM.js} +2 -2
- package/build/server/chunks/chunks/{state.js-CBD6RE0B.js → state.js-OSxIx7MQ.js} +2 -2
- package/build/server/chunks/chunks/{watcher.js-BfZOp5td.js → watcher.js-CACduBB9.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/a2a/{_server.ts.js-D9vpMh8K.js → _server.ts.js-DHFHuGSm.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/a2a/register/{_server.ts.js-HPJEmfbv.js → _server.ts.js-CUUfDBcc.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/dsh/directory/{_server.ts.js-BaAmRXrU.js → _server.ts.js-Cae4x1c7.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/jobs/_server.ts.js-hGmoSsDv.js +93 -0
- package/build/server/chunks/entries/endpoints/api/dsh/pick-directory/{_server.ts.js-D-Ftq5ei.js → _server.ts.js-BLb8o1cY.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/presets/{_server.ts.js-C0_j53OW.js → _server.ts.js-BFeDKrPz.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/attachment/{_server.ts.js-CxMvUVdS.js → _server.ts.js-DmSTTfI5.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/cancel/{_server.ts.js-CfjRtKMp.js → _server.ts.js-Cus9XrC9.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/catalog/{_server.ts.js-D7uC5b_j.js → _server.ts.js-DcrNU0_y.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/changes/{_server.ts.js-TeoUfrIl.js → _server.ts.js-CpuEcp6p.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/command/{_server.ts.js-uqUb-EdM.js → _server.ts.js-CU9o8Tho.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/events/{_server.ts.js-C_DF0fAc.js → _server.ts.js-BiAJcPpb.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/fork/{_server.ts.js-BfkqDQXU.js → _server.ts.js-BMMZlvr7.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/goal/{_server.ts.js-vCfxCACS.js → _server.ts.js-DZ6qFrfm.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/history/{_server.ts.js-DJlpW4hl.js → _server.ts.js-ecbtat_C.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/models/{_server.ts.js-Cqn5ZMGh.js → _server.ts.js-CPZuvN-_.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/permission/{_server.ts.js-ChB_0ZdM.js → _server.ts.js-Dcfp7gSm.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/prompt/{_server.ts.js-BVHypcWF.js → _server.ts.js-CmxqUwLT.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/rename/{_server.ts.js-BwDVfVrR.js → _server.ts.js-DCNKXRwy.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/respond/{_server.ts.js-DRmd83Ea.js → _server.ts.js-AURyR2ll.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/select-model/{_server.ts.js-hc61Nb64.js → _server.ts.js-BVF36vTO.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/sessions/{_server.ts.js-xkp4IT7y.js → _server.ts.js-LX3JZbRr.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspace-file/{_server.ts.js-BvPKBiTg.js → _server.ts.js-BJ0d199o.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspace-tree/{_server.ts.js-BSsF01K6.js → _server.ts.js-BDy9jQXv.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspaces/{_server.ts.js-DNLxFs1R.js → _server.ts.js-DoTpSXIc.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/delete/{_server.ts.js-5jaRPaKn.js → _server.ts.js-BnfIobo7.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/rename/{_server.ts.js-A-hvBqyy.js → _server.ts.js-KRyjIODB.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/floor/restart/{_server.ts.js-D5wnUph-.js → _server.ts.js-DKsahdJH.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/plugins/apply/{_server.ts.js-CCs56XtH.js → _server.ts.js-D-m-ci1C.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/plugins/host-bounce/_server.ts.js-ELTA8Cv_.js +49 -0
- package/build/server/chunks/entries/endpoints/api/plugins/reload/{_server.ts.js-Ct28d7_t.js → _server.ts.js-BwingoJE.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/plugins/snapshot/{_server.ts.js-DswGIO4S.js → _server.ts.js-50wKeeCi.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/workspace/file-write/{_server.ts.js-C0x68iMD.js → _server.ts.js-Cb7X2G82.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/workspace/git-events/{_server.ts.js-DzdGKZdV.js → _server.ts.js-DZ67v729.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/workspace/git-file-head/{_server.ts.js-BC1jb3zy.js → _server.ts.js-BuO5Stmm.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/workspace/git-map/{_server.ts.js-BE9XJyhN.js → _server.ts.js-Bb_I5Ze7.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/workspace/git-status/{_server.ts.js-CWlGtOu8.js → _server.ts.js-cHWxh5VB.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/workspace/write-gate/{_server.ts.js-CgcggNrH.js → _server.ts.js-Blcfn0zm.js} +4 -4
- package/build/server/chunks/entries/pages/{_error.svelte.js-CKrYUsNh.js → _error.svelte.js-2wYgv54Z.js} +3 -3
- package/build/server/chunks/entries/pages/{_page.server.ts.js-CbzURHkB.js → _page.server.ts.js-C_ydvzuE.js} +3 -3
- package/build/server/chunks/entries/pages/{_page.svelte.js-Bub68nnw.js → _page.svelte.js-BvcQOPvs.js} +472 -7
- package/build/server/chunks/{handler-29vKRsIm.js → handler-D0h_ogA5.js} +3 -3
- package/build/server/chunks/{index.js-BpxR9RqE.js → index.js-C43GYBhl.js} +2 -2
- package/build/server/chunks/{manifest.js-BUA2pFJz.js → manifest.js-DN0QODc_.js} +54 -40
- package/build/server/chunks/nodes/1.js-Da_l90xT.js +9 -0
- package/build/server/chunks/nodes/{2.js-B3UD9xCC.js → 2.js-_OZDbZ-E.js} +7 -7
- package/package.json +4 -4
- package/skills/dsi-add-plugin/references/allowbuilds-template.md +1 -1
- package/skills/dsi-sync/SKILL.md +8 -8
- package/template/.dsi/prompts.sqlite-shm +0 -0
- package/template/.dsi/prompts.sqlite-wal +0 -0
- package/template/.dsi/resources/plugins-reff.md +7 -6
- package/template/.dsi/resources/skills-reff.md +1 -1
- package/build/client/_app/immutable/assets/2.yPxdQ-n0.css +0 -1
- package/build/client/_app/immutable/chunks/C0ZiNuOj.js +0 -1
- package/build/client/_app/immutable/chunks/b58grRSF.js +0 -1
- package/build/client/_app/immutable/entry/start.4M41VX7w.js +0 -1
- package/build/server/chunks/nodes/1.js-BFDsDe_G.js +0 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DeepSeek Insight
|
|
2
2
|
|
|
3
|
-
> [DeepSeek Insight](https://github.com/mitmplay/
|
|
3
|
+
> [DeepSeek Insight](https://github.com/mitmplay/dsh-insight) (**DSI**) **See your multiple agents run on the floor (agent, sub-agent, etc)**, each **conversation on the floor are updated instantly**.
|
|
4
4
|
|
|
5
5
|
> A `Control room` built on [Deepseek Harness's](https://github.com/deepseek-ai/deepseek-harness) (**DSH**) **records**: any (running) session, show side by side on the floor; **Lineage tree** of **parent to sub-agents**; Clean conversation with **tool-call(s) chips** with status **+** auto-expand **thingking chips**.
|
|
6
6
|
|
|
@@ -43,7 +43,7 @@ The workspace explorer is DSI's window into the folder your agents are working i
|
|
|
43
43
|
|
|
44
44
|
<details>
|
|
45
45
|
<summary>Deepseek-insight-panels</summary>
|
|
46
|
-
<img src="https://raw.githubusercontent.com/mitmplay/
|
|
46
|
+
<img src="https://raw.githubusercontent.com/mitmplay/dsh-insight/refs/heads/main/docs/images/dsi-panels.png"/>
|
|
47
47
|
</details>
|
|
48
48
|
|
|
49
49
|
---
|
package/bin/dsi.mjs
CHANGED
|
@@ -28,7 +28,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
28
28
|
import { parse, stringify } from 'yaml';
|
|
29
29
|
|
|
30
30
|
import { mirrorSkills } from './lib/mirror-skills.mjs';
|
|
31
|
-
import { dshLauncherCmd, parsePluginArgs, takeoverFloorPorts } from './lib/plugin-chain.mjs';
|
|
31
|
+
import { dshLauncherCmd, parsePluginArgs, takeoverFloorPorts, hostBounceDecision, floorUiPort, parseDevFloorPids, launchUrl, tokenFromSettings } from './lib/plugin-chain.mjs';
|
|
32
32
|
import { parseDevArg, devModeEnv, modeFromBoolean, resolveBounceMode, extractInspectArgs, parseInspectArgsFromEnv } from './lib/dev-mode.mjs';
|
|
33
33
|
|
|
34
34
|
// Layman-facing help: wrapped near 76 columns, continuation lines indented,
|
|
@@ -260,18 +260,53 @@ if (command === 'dsh') {
|
|
|
260
260
|
// records the mode records the inspector flags — re-forward them so
|
|
261
261
|
// the bounce boot comes back debuggable when it left that way.
|
|
262
262
|
const bounceExecArgv = parseInspectArgsFromEnv(floorEnvDump);
|
|
263
|
-
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
263
|
+
// ADR-0025 D1/D3 — the bounce fork: an in-process floor with a live
|
|
264
|
+
// bounce verb bounces AT THE HOST via the running floor's route; the
|
|
265
|
+
// vite process never dies and the sweep is SKIPPED. Pair/build floors,
|
|
266
|
+
// a missing verb, or an unreachable route take the sweep below.
|
|
267
|
+
const sweepAndReserve = async () => {
|
|
268
|
+
killOrphans();
|
|
269
|
+
// The pkill sweep cannot see every floor shape (RCA 2026-09-27: the
|
|
270
|
+
// npx shims — 'npm exec @deepseek-ai/dsh…' and '.bin/dsh web' — are
|
|
271
|
+
// invisible to pkill -f on macOS, so the dsh host survived holding
|
|
272
|
+
// 3080 and preflightPort exited 64, killing the detached chain
|
|
273
|
+
// silently). The bounce chain owns the DSI ports by contract, so
|
|
274
|
+
// free them by listener, not by name.
|
|
275
|
+
await freeFloorPorts(floorPortSet());
|
|
276
|
+
// Restore the SAME UI mode the floor was serving. The bounce's
|
|
277
|
+
// boolean signal normalizes to 'pair'/'build' inside runSyncedPair;
|
|
278
|
+
// 3.5 teaches the bounce the full enum via DSI_DEV_MODE.
|
|
279
|
+
runSyncedPair([], mode, bounceExecArgv);
|
|
280
|
+
};
|
|
281
|
+
const bounceVerdict = await hostBounceDecision({
|
|
282
|
+
mode,
|
|
283
|
+
post: async () => {
|
|
284
|
+
// ADR-0025 Wave 3 gate finding (2026-10-14): the route is a DSI
|
|
285
|
+
// (vite) route - POST the vite UI port, not the DSH host port.
|
|
286
|
+
const uiPort = floorUiPort({
|
|
287
|
+
floorEnv: floorEnvDump,
|
|
288
|
+
command: floorCommand(devFloorPid())
|
|
289
|
+
});
|
|
290
|
+
const res = await fetch('http://127.0.0.1:' + uiPort + '/api/plugins/host-bounce', { method: 'POST' });
|
|
291
|
+
return res.ok;
|
|
292
|
+
},
|
|
293
|
+
sweep: sweepAndReserve,
|
|
294
|
+
});
|
|
295
|
+
if (bounceVerdict === 'host') {
|
|
296
|
+
console.log('dsi dsh: host-level bounce done via the running floor — vite never died, sweep skipped (ADR-0025)');
|
|
297
|
+
// D4 amended (2026-10-14): the DSH launch token is minted inside the
|
|
298
|
+
// host process (dsh-client-connection processLaunchToken, a WeakMap on
|
|
299
|
+
// the cordis root — no preset seam), so the bounce CANNOT re-boot with
|
|
300
|
+
// the old token. The bounce's announce intercept lands the NEW token in
|
|
301
|
+
// settings.yaml; hand the operator a URL that works on first click.
|
|
302
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
303
|
+
const fresh = tokenFromSettings(readFileSync(dsiConfigPath(), 'utf-8'));
|
|
304
|
+
const url = launchUrl({ port: process.env.DSH_HOST_PORT ?? 3080, token: fresh });
|
|
305
|
+
if (url) console.log('dsi dsh: fresh launch URL after the bounce (loaded pages keep working — durable cookie):\n\t ' + url);
|
|
306
|
+
} else {
|
|
307
|
+
console.log('dsi dsh: host bounce unavailable — whole-floor sweep fallback (ADR-0025 D3)');
|
|
308
|
+
await sweepAndReserve();
|
|
309
|
+
}
|
|
275
310
|
} else if (flag === '--sync-kills') {
|
|
276
311
|
killOrphans();
|
|
277
312
|
process.exit(0);
|
|
@@ -521,21 +556,42 @@ async function freeFloorPorts(ports) {
|
|
|
521
556
|
|
|
522
557
|
/** True when a vite dev server of THIS checkout is alive. Only the
|
|
523
558
|
* checkout's own vite matches — a foreign project's dev server never
|
|
524
|
-
* carries the deepseek-insight path segment.
|
|
525
|
-
*
|
|
526
|
-
*
|
|
559
|
+
* carries the deepseek-insight path segment. ADR-0025 Wave 3 (2026-10-14):
|
|
560
|
+
* the probe rides devFloorPid's ps -eo discovery — pgrep -f went blind to
|
|
561
|
+
* the vite process on the live floor, so this boolean read false-negative
|
|
562
|
+
* and the bounce chain swept floors it could have host-bounced. */
|
|
527
563
|
function isDevFloorAlive() {
|
|
528
|
-
|
|
529
|
-
return (probe.status ?? 1) === 0;
|
|
564
|
+
return devFloorPid() !== null;
|
|
530
565
|
}
|
|
531
566
|
|
|
532
567
|
/** The alive dev floor's vite pid (first match), or null. Same match rule
|
|
533
568
|
* as isDevFloorAlive — the pid feeds the D6 mode read (ps -E). */
|
|
534
569
|
function devFloorPid() {
|
|
535
|
-
|
|
570
|
+
|
|
571
|
+
// ADR-0025 Wave 3 gate finding (2026-10-14): pgrep -f went BLIND to the
|
|
572
|
+
// vite process on the live floor (exit 1 while ps listed it), so the
|
|
573
|
+
// chain misread the floor as absent and swept a bounceable in-process
|
|
574
|
+
// floor. ps -eo pid,command sees what pgrep does not; the parse is the
|
|
575
|
+
// pure parseDevFloorPids twin in plugin-chain.mjs.
|
|
576
|
+
const probe = spawnSync('ps', ['-eo', 'pid=,command='], { stdio: 'pipe', timeout: 5000 });
|
|
536
577
|
if ((probe.status ?? 1) !== 0) return null;
|
|
537
|
-
const first = (probe.stdout?.toString() || '')
|
|
538
|
-
return first
|
|
578
|
+
const first = parseDevFloorPids(probe.stdout?.toString() || '')[0];
|
|
579
|
+
return first ?? null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/** The floor process's command line (ps -o command), or '' on any failure -
|
|
583
|
+
* feeds floorUiPort's --port arm (the launcher passes the vite port as an
|
|
584
|
+
* argument, not as env). */
|
|
585
|
+
function floorCommand(pid) {
|
|
586
|
+
if (!pid) return '';
|
|
587
|
+
try {
|
|
588
|
+
const ps = spawnSync('ps', ['-o', 'command', '-p', String(pid)], { stdio: 'pipe', timeout: 5000 });
|
|
589
|
+
if (ps.status !== 0) return '';
|
|
590
|
+
const lines = (ps.stdout?.toString() || '').split('\n').filter(Boolean);
|
|
591
|
+
return lines.length > 1 ? lines.slice(1).join('\n') : '';
|
|
592
|
+
} catch {
|
|
593
|
+
return '';
|
|
594
|
+
}
|
|
539
595
|
}
|
|
540
596
|
|
|
541
597
|
/** The floor's DSI_DEV_MODE via ps -E (macOS: own processes only). Returns
|
package/bin/lib/dev-mode.mjs
CHANGED
|
@@ -24,6 +24,7 @@ const DEV_VALUES = new Set(['in-process', 'pair']);
|
|
|
24
24
|
*/
|
|
25
25
|
export function parseDevArg(rest) {
|
|
26
26
|
const out = [];
|
|
27
|
+
/** @type {'build' | 'pair' | 'in-process'} */
|
|
27
28
|
let mode = 'build';
|
|
28
29
|
for (let i = 0; i < rest.length; i++) {
|
|
29
30
|
const arg = rest[i];
|
|
@@ -48,10 +49,23 @@ export function parseDevArg(rest) {
|
|
|
48
49
|
* DSI_DEV=1 is the pre-existing dev-floor marker (RCA 2026-09-27) — kept for
|
|
49
50
|
* the bounce chain's boolean probe; DSI_DEV_MODE is the D6 mode enum.
|
|
50
51
|
*/
|
|
52
|
+
/** @param {string} mode
|
|
53
|
+
* @returns {Record<string, string>}
|
|
54
|
+
*/
|
|
51
55
|
export function devModeEnv(mode) {
|
|
52
56
|
if (mode === 'build') return {};
|
|
57
|
+
/** @type {Record<string, string>} */
|
|
53
58
|
const env = { DSI_DEV: '1', DSI_DEV_MODE: mode };
|
|
54
|
-
if (mode === 'in-process')
|
|
59
|
+
if (mode === 'in-process') {
|
|
60
|
+
env.DSI_DEV_IN_PROCESS = '1';
|
|
61
|
+
// ADR-0025 Wave 3 (real-floor gate finding, 2026-10-14): the bounce
|
|
62
|
+
// verb only exists when the embed OWNS the host - a runCli boot has
|
|
63
|
+
// no stop handle and the host-bounce route answers 409 no-handle,
|
|
64
|
+
// sending every in-process bounce down the whole-floor sweep. The
|
|
65
|
+
// introspection boot (DSH_HOST_INTROSPECT=1, ADR-0024) is the boot
|
|
66
|
+
// that carries the handle, so in-process floors default to it.
|
|
67
|
+
env.DSH_HOST_INTROSPECT = '1';
|
|
68
|
+
}
|
|
55
69
|
return env;
|
|
56
70
|
}
|
|
57
71
|
|
|
@@ -60,6 +74,9 @@ export function devModeEnv(mode) {
|
|
|
60
74
|
* to a mode. Wave 3.5 will teach the bounce the enum; until then the bounce
|
|
61
75
|
* can only ever restore 'pair' or 'build'.
|
|
62
76
|
*/
|
|
77
|
+
/** @param {boolean} dev
|
|
78
|
+
* @returns {'pair' | 'build'}
|
|
79
|
+
*/
|
|
63
80
|
export function modeFromBoolean(dev) {
|
|
64
81
|
return dev ? 'pair' : 'build';
|
|
65
82
|
}
|
package/bin/lib/plugin-chain.mjs
CHANGED
|
@@ -77,6 +77,103 @@ export function bounceDevMode(flagDev, devFloorAlive) {
|
|
|
77
77
|
export const TAKEOVER_BUDGET_MS = 10_000;
|
|
78
78
|
export const TAKEOVER_POLL_MS = 250;
|
|
79
79
|
|
|
80
|
+
/** The host-level bounce's retry window (ADR-0025 D3): the chain POSTs the
|
|
81
|
+
* running floor's /api/plugins/host-bounce at most this many times before
|
|
82
|
+
* giving up and taking the whole-floor sweep fallback. */
|
|
83
|
+
export const HOST_BOUNCE_TRIES = 2;
|
|
84
|
+
export const HOST_BOUNCE_RETRY_MS = 1_000;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The bounce fork (ADR-0025 D1/D3): an in-process floor with a live bounce
|
|
88
|
+
* verb bounces AT THE HOST — the chain POSTs the route and, on ok, sweeps
|
|
89
|
+
* NOTHING (vite never dies). Any other mode, refusal, or unreachable route
|
|
90
|
+
* falls back to today's whole-floor sweep. Pure orchestration: the POST and
|
|
91
|
+
* the sweep are injected, so unit tests pin BOTH branches.
|
|
92
|
+
*
|
|
93
|
+
* @param {{ mode: string, post: () => Promise<boolean>, sweep: () => void | Promise<void>,
|
|
94
|
+
* sleep?: (ms: number) => Promise<void>, tries?: number }} params
|
|
95
|
+
* @returns {Promise<'host' | 'sweep'>} which body ran
|
|
96
|
+
*/
|
|
97
|
+
export async function hostBounceDecision({ mode, post, sweep, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), tries = HOST_BOUNCE_TRIES }) {
|
|
98
|
+
if (mode !== 'in-process') {
|
|
99
|
+
await sweep();
|
|
100
|
+
return 'sweep';
|
|
101
|
+
}
|
|
102
|
+
for (let attempt = 1; attempt <= tries; attempt++) {
|
|
103
|
+
try {
|
|
104
|
+
if ((await post()) === true) return 'host';
|
|
105
|
+
} catch {
|
|
106
|
+
// unreachable or refused — retry, then sweep
|
|
107
|
+
}
|
|
108
|
+
if (attempt < tries) await sleep(HOST_BOUNCE_RETRY_MS);
|
|
109
|
+
}
|
|
110
|
+
await sweep();
|
|
111
|
+
return 'sweep';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The browser launch URL after a bounce (ADR-0025 D4, amended
|
|
115
|
+
* 2026-10-14): the DSH launch token is minted INSIDE the host process
|
|
116
|
+
* (WeakMap keyed on the cordis root, dsh-client-connection processLaunchToken)
|
|
117
|
+
* and has no preset seam, so an in-place bounce cannot re-boot with the OLD
|
|
118
|
+
* token. The equivalent guarantee: after the bounce the chain reads the NEW
|
|
119
|
+
* token from the sink and hands the operator a URL that works on first
|
|
120
|
+
* click. Pure: port and token are injected.
|
|
121
|
+
*
|
|
122
|
+
* @param {{ port?: number|string, token?: string, host?: string }} p
|
|
123
|
+
* @returns {string} the launch URL ('' when the token is unknown)
|
|
124
|
+
*/
|
|
125
|
+
export function launchUrl({ port = 3080, token = '', host = '127.0.0.1' } = {}) {
|
|
126
|
+
if (!token) return '';
|
|
127
|
+
return 'http://' + host + ':' + port + '/?token=' + encodeURIComponent(token);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Reads dsh.authToken out of the DSI settings.yaml text. Pure: the file
|
|
131
|
+
* text is injected; dsi.mjs reads the file.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} yamlText - full settings.yaml text
|
|
134
|
+
* @returns {string} the token ('' when absent or not parseable)
|
|
135
|
+
*/
|
|
136
|
+
export function tokenFromSettings(yamlText) {
|
|
137
|
+
const m = /authToken:\s*['"]?([^"'/\s]+)/.exec(String(yamlText || ''));
|
|
138
|
+
return m ? m[1] : '';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The dev floor's vite pid(s) parsed from ONE ps -eo pid,command dump
|
|
142
|
+
* (ADR-0025 Wave 3 gate finding, 2026-10-14): pgrep -f proved blind to the
|
|
143
|
+
* vite process on the live floor (exit 1 while ps listed it), which made
|
|
144
|
+
* the bounce chain misread the mode and sweep a bounceable floor. ps -eo
|
|
145
|
+
* sees what pgrep does not, and parsing is pure + unit-pinned here; the
|
|
146
|
+
* ps invocation stays in dsi.mjs.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} psOutput - full `ps -eo pid=,command=` dump
|
|
149
|
+
* @returns {string[]} matching pids (as strings), oldest-first order kept
|
|
150
|
+
*/
|
|
151
|
+
export function parseDevFloorPids(psOutput) {
|
|
152
|
+
return String(psOutput || '').split('\n').flatMap((line) => {
|
|
153
|
+
const m = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
154
|
+
return m && m[2].includes('node_modules/vite/bin/vite.js') ? [m[1]] : [];
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The port the floor's DSI UI answers on (ADR-0025 Wave 3 gate finding,
|
|
159
|
+
* 2026-10-14): the host-bounce route is a DSI (vite) route, so the bounce
|
|
160
|
+
* chain must POST the VITE port - the DSH host port only knows the fence
|
|
161
|
+
* (401) or 404. Resolution order: PORT= in the floor env dump, then
|
|
162
|
+
* --port N on the vite command line, then the 5175 default the launcher
|
|
163
|
+
* pins. Pure: both inputs are strings read by dsi.mjs (ps -E and ps -o
|
|
164
|
+
* command), so unit tests pin all three arms.
|
|
165
|
+
*
|
|
166
|
+
* @param {{ floorEnv?: string, command?: string }} params
|
|
167
|
+
* @returns {number} the UI port
|
|
168
|
+
*/
|
|
169
|
+
export function floorUiPort({ floorEnv = '', command = '' } = {}) {
|
|
170
|
+
const envPort = /(?:^|\s)PORT=(\d+)/.exec(floorEnv || '');
|
|
171
|
+
if (envPort) return Number(envPort[1]);
|
|
172
|
+
const argPort = /--port\s+(\d+)/.exec(command || '');
|
|
173
|
+
if (argPort) return Number(argPort[1]);
|
|
174
|
+
return 5175;
|
|
175
|
+
}
|
|
176
|
+
|
|
80
177
|
/**
|
|
81
178
|
* The takeover sweep (The Port Takeover ADR 2026-10-12, D2/D3/D5): SIGTERM
|
|
82
179
|
* whatever LISTENS on the given ports, then WAIT until each port frees,
|