@dsh-workbench/core 0.3.3 → 0.3.4
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/client.js +512 -42
- package/cordis.patch.yml +32 -0
- package/dist/client/client.body.js +512 -42
- package/dist/client/index.js +512 -42
- package/dist/host/index.js +2486 -323
- package/dist/types/app/client.d.ts +108 -3
- package/dist/types/app/layouts/approval-detail.d.ts +13 -0
- package/dist/types/app/layouts/sidebar/RecordFooterAction.d.ts +16 -0
- package/dist/types/app/layouts/sidebar/record-footer.d.ts +4 -0
- package/dist/types/modules/panels/client.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-components.d.ts +3 -0
- package/dist/types/modules/panels/model/diy-components.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-bar.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-capture.d.ts +27 -0
- package/dist/types/modules/panels/model/diy-record-capture.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-failure.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-routes.d.ts +2 -0
- package/dist/types/modules/panels/model/diy-record-routes.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-scripts.d.ts +6 -0
- package/dist/types/modules/panels/model/diy-record-text.d.ts +89 -0
- package/dist/types/modules/panels/model/diy-record-text.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-tools.d.ts +2 -0
- package/dist/types/modules/panels/model/diy-record.d.ts +76 -0
- package/dist/types/modules/panels/model/diy-record.test.d.ts +1 -0
- package/dist/types/modules/panels/model/handoff.d.ts +1 -0
- package/dist/types/modules/panels/unit.d.ts +10 -4
- package/dist/types/shared/api/host/diy-acts.d.ts +4 -0
- package/dist/types/shared/api/host/diy-components.d.ts +54 -0
- package/dist/types/shared/api/host/diy-debug-act.d.ts +24 -7
- package/dist/types/shared/api/host/diy-record-act.d.ts +19 -0
- package/dist/types/shared/api/host/diy-workshop.d.ts +1 -0
- package/dist/types/shared/api/host/mount-units.d.ts +4 -0
- package/dist/types/shared/hooks/use-diy-record.d.ts +8 -0
- package/package.json +2 -2
- package/scripts/seed-workshop.sh +42 -2
- package/skills/workbench-module-diy/BUILD.md +4 -3
- package/skills/workbench-module-diy/DESIGN.md +26 -1
- package/skills/workbench-module-diy/FAMILY.md +55 -0
- package/skills/workbench-module-diy/HOOKS.md +95 -0
- package/skills/workbench-module-diy/KIT.md +16 -6
- package/skills/workbench-module-diy/PANEL.md +5 -2
- package/skills/workbench-module-diy/REVIEW.md +5 -4
- package/skills/workbench-module-diy/SKILL.md +12 -3
- package/skills/workbench-module-diy/TOOLING.md +5 -2
- package/skills/workbench-module-diy/TOOLS.md +6 -40
- package/skills/workbench-module-diy/WIRING.md +12 -5
- package/skills/workbench-module-diy/WRITES.md +47 -6
package/dist/host/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path, { basename, delimiter, dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { deflateSync, inflateSync } from "node:zlib";
|
|
5
6
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
//#region ../shared/dist/config/constants.js
|
|
9
9
|
var PLUGIN_ID = "@dsh-workbench/core";
|
|
@@ -16,12 +16,15 @@ var PROVISION_PREFIX = `${API_PREFIX}/provision`;
|
|
|
16
16
|
var PANEL_STATE_PATH = "panel-state";
|
|
17
17
|
var DIY_DEBUG_PATH = "diy-debug";
|
|
18
18
|
`${DIY_DEBUG_PATH}`;
|
|
19
|
+
var DIY_RECORD_PATH = "diy-record";
|
|
20
|
+
`${DIY_RECORD_PATH}`;
|
|
19
21
|
var PANEL_STATE_PREFIX = `${API_PREFIX}/${PANEL_STATE_PATH}`;
|
|
20
22
|
var OPENCLI_PIN = "1.8.8";
|
|
21
23
|
var PANELS_SERVICE = "workbenchPanels";
|
|
22
24
|
var UNITS_SERVICE = "workbenchUnits";
|
|
23
25
|
var PANEL_STATE_SERVICE = "workbenchPanelState";
|
|
24
26
|
var DIY_DEBUG_SERVICE = "workbenchDiyDebug";
|
|
27
|
+
var DIY_RECORD_SERVICE = "workbenchDiyRecord";
|
|
25
28
|
var OPENCLI_TIMEOUT_MS = 12e4;
|
|
26
29
|
var WARM_TIMEOUT_MS = 2e4;
|
|
27
30
|
var INSTALL_TIMEOUT_MS = 3e5;
|
|
@@ -465,22 +468,22 @@ function boardSituation(probe, workshop) {
|
|
|
465
468
|
function parseDebugIntent(value) {
|
|
466
469
|
return value === "pause" || value === "resume" || value === "stop" ? value : null;
|
|
467
470
|
}
|
|
468
|
-
function alive(state, now) {
|
|
471
|
+
function alive$1(state, now) {
|
|
469
472
|
if (state === null) return null;
|
|
470
473
|
return now - state.at <= 144e5 ? state : null;
|
|
471
474
|
}
|
|
472
|
-
function ownerOf(state, sessionId, now) {
|
|
473
|
-
const held = alive(state, now);
|
|
475
|
+
function ownerOf$1(state, sessionId, now) {
|
|
476
|
+
const held = alive$1(state, now);
|
|
474
477
|
if (held === null) return "none";
|
|
475
478
|
return held.sessionId === sessionId ? held : "not-owner";
|
|
476
479
|
}
|
|
477
480
|
function createDebugStore() {
|
|
478
481
|
let held = null;
|
|
479
|
-
const live = (now) => alive(held, now);
|
|
482
|
+
const live = (now) => alive$1(held, now);
|
|
480
483
|
return {
|
|
481
484
|
live,
|
|
482
485
|
begin(pact) {
|
|
483
|
-
const current = alive(held, pact.now);
|
|
486
|
+
const current = alive$1(held, pact.now);
|
|
484
487
|
if (current !== null) return {
|
|
485
488
|
ok: false,
|
|
486
489
|
code: current.sessionId === pact.sessionId ? "already-held" : "held-by-other"
|
|
@@ -502,7 +505,7 @@ function createDebugStore() {
|
|
|
502
505
|
};
|
|
503
506
|
},
|
|
504
507
|
pause(sessionId, now) {
|
|
505
|
-
const found = ownerOf(held, sessionId, now);
|
|
508
|
+
const found = ownerOf$1(held, sessionId, now);
|
|
506
509
|
if (typeof found === "string") return {
|
|
507
510
|
ok: false,
|
|
508
511
|
code: found
|
|
@@ -523,7 +526,7 @@ function createDebugStore() {
|
|
|
523
526
|
};
|
|
524
527
|
},
|
|
525
528
|
resume(sessionId, now) {
|
|
526
|
-
const found = ownerOf(held, sessionId, now);
|
|
529
|
+
const found = ownerOf$1(held, sessionId, now);
|
|
527
530
|
if (typeof found === "string") return {
|
|
528
531
|
ok: false,
|
|
529
532
|
code: found
|
|
@@ -543,6 +546,113 @@ function createDebugStore() {
|
|
|
543
546
|
state
|
|
544
547
|
};
|
|
545
548
|
},
|
|
549
|
+
stop(sessionId, now) {
|
|
550
|
+
const found = ownerOf$1(held, sessionId, now);
|
|
551
|
+
if (typeof found === "string") return {
|
|
552
|
+
ok: false,
|
|
553
|
+
code: found
|
|
554
|
+
};
|
|
555
|
+
held = null;
|
|
556
|
+
return {
|
|
557
|
+
ok: true,
|
|
558
|
+
state: found
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function parseRecordIntent(raw) {
|
|
564
|
+
return raw === "peek" || raw === "halt" || raw === "stop" ? raw : null;
|
|
565
|
+
}
|
|
566
|
+
function alive(state, now) {
|
|
567
|
+
if (state === null) return null;
|
|
568
|
+
return now - state.at <= 9e5 ? state : null;
|
|
569
|
+
}
|
|
570
|
+
function ownerOf(state, sessionId, now) {
|
|
571
|
+
const held = alive(state, now);
|
|
572
|
+
if (held === null) return "none";
|
|
573
|
+
return held.sessionId === sessionId ? held : "not-owner";
|
|
574
|
+
}
|
|
575
|
+
function createRecordStore() {
|
|
576
|
+
let held = null;
|
|
577
|
+
const live = (now) => alive(held, now);
|
|
578
|
+
return {
|
|
579
|
+
live,
|
|
580
|
+
begin(pact) {
|
|
581
|
+
const current = alive(held, pact.now);
|
|
582
|
+
if (current !== null) return {
|
|
583
|
+
ok: false,
|
|
584
|
+
code: current.sessionId === pact.sessionId ? "already-held" : "held-by-other"
|
|
585
|
+
};
|
|
586
|
+
const state = {
|
|
587
|
+
phase: "armed",
|
|
588
|
+
sessionId: pact.sessionId,
|
|
589
|
+
workspace: pact.workspace,
|
|
590
|
+
unit: pact.unit,
|
|
591
|
+
name: pact.name,
|
|
592
|
+
url: pact.url,
|
|
593
|
+
title: pact.title,
|
|
594
|
+
action: pact.action,
|
|
595
|
+
expect: pact.expect,
|
|
596
|
+
captureFile: pact.captureFile,
|
|
597
|
+
startedAt: pact.now,
|
|
598
|
+
t0: null,
|
|
599
|
+
t1: null,
|
|
600
|
+
at: pact.now
|
|
601
|
+
};
|
|
602
|
+
held = state;
|
|
603
|
+
return {
|
|
604
|
+
ok: true,
|
|
605
|
+
state
|
|
606
|
+
};
|
|
607
|
+
},
|
|
608
|
+
started(sessionId, at, now) {
|
|
609
|
+
const found = ownerOf(held, sessionId, now);
|
|
610
|
+
if (typeof found === "string") return {
|
|
611
|
+
ok: false,
|
|
612
|
+
code: found
|
|
613
|
+
};
|
|
614
|
+
if (found.phase === "recording") return {
|
|
615
|
+
ok: false,
|
|
616
|
+
code: "already-recording"
|
|
617
|
+
};
|
|
618
|
+
if (found.phase !== "armed") return {
|
|
619
|
+
ok: false,
|
|
620
|
+
code: "not-armed"
|
|
621
|
+
};
|
|
622
|
+
const state = {
|
|
623
|
+
...found,
|
|
624
|
+
phase: "recording",
|
|
625
|
+
t0: at,
|
|
626
|
+
at: now
|
|
627
|
+
};
|
|
628
|
+
held = state;
|
|
629
|
+
return {
|
|
630
|
+
ok: true,
|
|
631
|
+
state
|
|
632
|
+
};
|
|
633
|
+
},
|
|
634
|
+
finished(sessionId, at, now) {
|
|
635
|
+
const found = ownerOf(held, sessionId, now);
|
|
636
|
+
if (typeof found === "string") return {
|
|
637
|
+
ok: false,
|
|
638
|
+
code: found
|
|
639
|
+
};
|
|
640
|
+
if (found.phase !== "recording") return {
|
|
641
|
+
ok: false,
|
|
642
|
+
code: "not-recording"
|
|
643
|
+
};
|
|
644
|
+
const state = {
|
|
645
|
+
...found,
|
|
646
|
+
phase: "finished",
|
|
647
|
+
t1: at,
|
|
648
|
+
at: now
|
|
649
|
+
};
|
|
650
|
+
held = state;
|
|
651
|
+
return {
|
|
652
|
+
ok: true,
|
|
653
|
+
state
|
|
654
|
+
};
|
|
655
|
+
},
|
|
546
656
|
stop(sessionId, now) {
|
|
547
657
|
const found = ownerOf(held, sessionId, now);
|
|
548
658
|
if (typeof found === "string") return {
|
|
@@ -596,6 +706,7 @@ async function runShell(shell, command, options = {}) {
|
|
|
596
706
|
//#endregion
|
|
597
707
|
//#region ../shared/dist/host/browser-read.js
|
|
598
708
|
var BROWSER_SCREEN_SESSION = "workbench";
|
|
709
|
+
var BROWSER_RECORD_SESSION = "workbench-rec";
|
|
599
710
|
var DEFAULT_TIMEOUT_MS$2 = 25e3;
|
|
600
711
|
var DEFAULT_RETRY_MS = 1200;
|
|
601
712
|
var DEFAULT_SETTLE_MS$1 = 900;
|
|
@@ -1235,7 +1346,7 @@ var TARGET_CODE = {
|
|
|
1235
1346
|
function asRecord$7(value) {
|
|
1236
1347
|
return typeof value === "object" && value !== null ? value : {};
|
|
1237
1348
|
}
|
|
1238
|
-
function stringsOf$
|
|
1349
|
+
function stringsOf$5(value) {
|
|
1239
1350
|
if (!Array.isArray(value)) return void 0;
|
|
1240
1351
|
const items = value.filter((item) => typeof item === "string" && item.trim() !== "");
|
|
1241
1352
|
return items.length === 0 ? void 0 : items;
|
|
@@ -1291,7 +1402,7 @@ function parseProvisionBody(body) {
|
|
|
1291
1402
|
request: requestOf$1(wanted, value.trim())
|
|
1292
1403
|
};
|
|
1293
1404
|
const registry = typeof raw.registry === "string" && raw.registry.trim() !== "" ? raw.registry.trim() : void 0;
|
|
1294
|
-
const approvedBuilds = stringsOf$
|
|
1405
|
+
const approvedBuilds = stringsOf$5(raw.approvedBuilds);
|
|
1295
1406
|
const requestId = typeof raw.requestId === "string" && raw.requestId.trim() !== "" ? raw.requestId.trim() : void 0;
|
|
1296
1407
|
return {
|
|
1297
1408
|
ok: true,
|
|
@@ -1796,6 +1907,15 @@ function registerDiyDebugAct(ctx, resource, log) {
|
|
|
1796
1907
|
wrote: () => log?.bump(["diy-debug"])
|
|
1797
1908
|
});
|
|
1798
1909
|
}
|
|
1910
|
+
function registerDiyRecordAct(ctx, resource, log) {
|
|
1911
|
+
return registerPost(ctx, {
|
|
1912
|
+
prefix: `${API_PREFIX}/${DIY_RECORD_PATH}`,
|
|
1913
|
+
what: "工作台 DIY 录制交接动作",
|
|
1914
|
+
refusedCode: "action-refused",
|
|
1915
|
+
resources: [resource],
|
|
1916
|
+
wrote: () => log?.bump(["diy-record"])
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1799
1919
|
//#endregion
|
|
1800
1920
|
//#region ../shared/dist/host/long-task.js
|
|
1801
1921
|
var LIVE = /* @__PURE__ */ new Set(["running", "needs-login"]);
|
|
@@ -2013,7 +2133,7 @@ function textOf$5(value) {
|
|
|
2013
2133
|
const trimmed = value.trim();
|
|
2014
2134
|
return trimmed === "" ? void 0 : trimmed;
|
|
2015
2135
|
}
|
|
2016
|
-
function entryOf(value) {
|
|
2136
|
+
function entryOf$1(value) {
|
|
2017
2137
|
if (typeof value !== "object" || value === null) return void 0;
|
|
2018
2138
|
const box = value;
|
|
2019
2139
|
const opencliHome = textOf$5(box["opencliHome"]);
|
|
@@ -2039,7 +2159,7 @@ function parsePluginLedger(raw) {
|
|
|
2039
2159
|
if (!Array.isArray(list)) return [];
|
|
2040
2160
|
const entries = [];
|
|
2041
2161
|
for (const item of list) {
|
|
2042
|
-
const entry = entryOf(item);
|
|
2162
|
+
const entry = entryOf$1(item);
|
|
2043
2163
|
if (entry === void 0) continue;
|
|
2044
2164
|
if (entries.some((known) => ledgerKeyOf(known) === ledgerKeyOf(entry))) continue;
|
|
2045
2165
|
entries.push(entry);
|
|
@@ -2210,11 +2330,14 @@ function withApprovalNote(parameters, options = {}) {
|
|
|
2210
2330
|
...required.length === 0 ? {} : { required }
|
|
2211
2331
|
};
|
|
2212
2332
|
}
|
|
2333
|
+
function oneLine(text) {
|
|
2334
|
+
return text.replace(/\s+/g, " ").trim();
|
|
2335
|
+
}
|
|
2213
2336
|
function approvalNoteOf(call) {
|
|
2214
2337
|
const raw = call.arguments;
|
|
2215
2338
|
const value = typeof raw === "object" && raw !== null ? raw[APPROVAL_NOTE_ARG] : void 0;
|
|
2216
2339
|
if (typeof value !== "string") return void 0;
|
|
2217
|
-
const line = value
|
|
2340
|
+
const line = oneLine(value);
|
|
2218
2341
|
if (line === "") return void 0;
|
|
2219
2342
|
return line.length > 40 ? `${line.slice(0, 40)}…` : line;
|
|
2220
2343
|
}
|
|
@@ -2226,11 +2349,14 @@ function compose(detail, verdict, note) {
|
|
|
2226
2349
|
en: `${detail.en} — ${verdict.question.en}`,
|
|
2227
2350
|
zh: `${detail.zh} —— ${verdict.question.zh}`
|
|
2228
2351
|
};
|
|
2229
|
-
|
|
2230
|
-
return {
|
|
2352
|
+
const line = note === void 0 ? base : {
|
|
2231
2353
|
en: `${base.en} — ${note}`,
|
|
2232
2354
|
zh: `${base.zh} —— ${note}`
|
|
2233
2355
|
};
|
|
2356
|
+
return {
|
|
2357
|
+
en: oneLine(line.en),
|
|
2358
|
+
zh: oneLine(line.zh)
|
|
2359
|
+
};
|
|
2234
2360
|
}
|
|
2235
2361
|
async function approvalFor(call, context = {}) {
|
|
2236
2362
|
if (context.writes !== true) return void 0;
|
|
@@ -2264,7 +2390,7 @@ async function approvalFor(call, context = {}) {
|
|
|
2264
2390
|
return {
|
|
2265
2391
|
kind: "ask",
|
|
2266
2392
|
reason: verdict.reason,
|
|
2267
|
-
displayReason: compose(facts, verdict, approvalNoteOf(call))
|
|
2393
|
+
displayReason: compose(facts, verdict, context.declaredNote === true ? approvalNoteOf(call) : void 0)
|
|
2268
2394
|
};
|
|
2269
2395
|
}
|
|
2270
2396
|
//#endregion
|
|
@@ -2489,7 +2615,7 @@ var CANCELLATIONS = [
|
|
|
2489
2615
|
function asRecord$6(value) {
|
|
2490
2616
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
2491
2617
|
}
|
|
2492
|
-
function asText$
|
|
2618
|
+
function asText$5(value) {
|
|
2493
2619
|
return typeof value === "string" ? value : "";
|
|
2494
2620
|
}
|
|
2495
2621
|
function pick(allowed, value) {
|
|
@@ -2519,9 +2645,9 @@ function asPackageResult(raw) {
|
|
|
2519
2645
|
const kind = pick(FAILURE_KINDS, record.kind);
|
|
2520
2646
|
return {
|
|
2521
2647
|
exitCode: typeof record.exitCode === "number" ? record.exitCode : -1,
|
|
2522
|
-
output: asText$
|
|
2648
|
+
output: asText$5(record.output),
|
|
2523
2649
|
truncated: record.truncated === true,
|
|
2524
|
-
logPath: asText$
|
|
2650
|
+
logPath: asText$5(record.logPath),
|
|
2525
2651
|
...kind === void 0 ? {} : { kind }
|
|
2526
2652
|
};
|
|
2527
2653
|
}
|
|
@@ -2536,14 +2662,14 @@ function asChange(raw) {
|
|
|
2536
2662
|
const pendingBuilds = asTextArray(record.pendingBuilds);
|
|
2537
2663
|
const restartReason = pick(RESTART_REASONS, record.restartReason);
|
|
2538
2664
|
const failedAt = pick(["registry", "spec-host"], record.failedAt);
|
|
2539
|
-
const bundle = asText$
|
|
2540
|
-
const errorCode = error === void 0 ? "" : asText$
|
|
2541
|
-
const errorDetail = error === void 0 ? "" : asText$
|
|
2665
|
+
const bundle = asText$5(record.bundle);
|
|
2666
|
+
const errorCode = error === void 0 ? "" : asText$5(error.code);
|
|
2667
|
+
const errorDetail = error === void 0 ? "" : asText$5(error.diagnostic);
|
|
2542
2668
|
return {
|
|
2543
2669
|
application,
|
|
2544
2670
|
stage,
|
|
2545
2671
|
changed: record.changed === true,
|
|
2546
|
-
target: asText$
|
|
2672
|
+
target: asText$5(record.target),
|
|
2547
2673
|
...bundle === "" ? {} : { bundle },
|
|
2548
2674
|
...errorCode === "" ? {} : { errorCode },
|
|
2549
2675
|
...errorDetail === "" ? {} : { errorDetail },
|
|
@@ -2556,14 +2682,14 @@ function asChange(raw) {
|
|
|
2556
2682
|
function asInspection(raw) {
|
|
2557
2683
|
const record = asRecord$6(raw);
|
|
2558
2684
|
if (record === void 0) return null;
|
|
2559
|
-
const status = asText$
|
|
2685
|
+
const status = asText$5(record.status);
|
|
2560
2686
|
if (status === "refused") {
|
|
2561
2687
|
const problem = pick(PROBLEMS, record.problem) ?? "unknown";
|
|
2562
2688
|
const registries = Array.isArray(record.registries) ? record.registries.map(asRegistry).filter((item) => item !== void 0) : [];
|
|
2563
2689
|
return {
|
|
2564
2690
|
status: "refused",
|
|
2565
2691
|
problem,
|
|
2566
|
-
reason: asText$
|
|
2692
|
+
reason: asText$5(record.reason),
|
|
2567
2693
|
...registries.length === 0 ? {} : { registries }
|
|
2568
2694
|
};
|
|
2569
2695
|
}
|
|
@@ -2571,10 +2697,10 @@ function asInspection(raw) {
|
|
|
2571
2697
|
const kind = pick(SPEC_KINDS, record.kind);
|
|
2572
2698
|
const registry = asRegistry(record.registry);
|
|
2573
2699
|
if (kind === void 0 || registry === void 0) return null;
|
|
2574
|
-
const name = asText$
|
|
2575
|
-
const version = asText$
|
|
2576
|
-
const description = asText$
|
|
2577
|
-
const host = asText$
|
|
2700
|
+
const name = asText$5(record.name);
|
|
2701
|
+
const version = asText$5(record.version);
|
|
2702
|
+
const description = asText$5(record.description);
|
|
2703
|
+
const host = asText$5(record.host);
|
|
2578
2704
|
return {
|
|
2579
2705
|
status: "accepted",
|
|
2580
2706
|
kind,
|
|
@@ -2626,7 +2752,7 @@ var PACKAGE_NAME$1 = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
|
2626
2752
|
function asRecord$5(value) {
|
|
2627
2753
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
2628
2754
|
}
|
|
2629
|
-
function asText$
|
|
2755
|
+
function asText$4(value) {
|
|
2630
2756
|
return typeof value === "string" ? value : "";
|
|
2631
2757
|
}
|
|
2632
2758
|
function isPackageName(value) {
|
|
@@ -2637,7 +2763,7 @@ function profileDirOf(ctx) {
|
|
|
2637
2763
|
const dir = typeof profile?.dir === "string" ? profile.dir.trim() : "";
|
|
2638
2764
|
return dir === "" ? null : dir;
|
|
2639
2765
|
}
|
|
2640
|
-
function stringsOf$
|
|
2766
|
+
function stringsOf$4(value) {
|
|
2641
2767
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2642
2768
|
}
|
|
2643
2769
|
function profileManifestOf(profileDir) {
|
|
@@ -2651,7 +2777,7 @@ function profileManifestOf(profileDir) {
|
|
|
2651
2777
|
function profileModules(profileDir) {
|
|
2652
2778
|
const record = profileManifestOf(profileDir);
|
|
2653
2779
|
if (record === void 0) return [];
|
|
2654
|
-
const bundles = stringsOf$
|
|
2780
|
+
const bundles = stringsOf$4(asRecord$5(asRecord$5(record.dsh)?.profile)?.bundles);
|
|
2655
2781
|
const dependencies = asRecord$5(record.dependencies) ?? {};
|
|
2656
2782
|
const names = bundles.length > 0 ? bundles : Object.keys(dependencies);
|
|
2657
2783
|
const found = [];
|
|
@@ -2662,7 +2788,7 @@ function profileModules(profileDir) {
|
|
|
2662
2788
|
unit: read.unit,
|
|
2663
2789
|
version: read.version,
|
|
2664
2790
|
...read.registry === void 0 ? {} : { registry: read.registry },
|
|
2665
|
-
local: isLocalDependencySpec(asText$
|
|
2791
|
+
local: isLocalDependencySpec(asText$4(dependencies[read.pkg]))
|
|
2666
2792
|
});
|
|
2667
2793
|
}
|
|
2668
2794
|
return found;
|
|
@@ -2757,18 +2883,18 @@ function readInstalledModule(profileDir, pkg) {
|
|
|
2757
2883
|
pkg
|
|
2758
2884
|
};
|
|
2759
2885
|
const declared = asRecord$5(asRecord$5(record.dsh)?.workbench);
|
|
2760
|
-
const unit = declared === void 0 ? "" : asText$
|
|
2886
|
+
const unit = declared === void 0 ? "" : asText$4(declared.unit);
|
|
2761
2887
|
if (unit === "") return {
|
|
2762
2888
|
kind: "not-module",
|
|
2763
2889
|
pkg
|
|
2764
2890
|
};
|
|
2765
|
-
const declaredRegistry = asText$
|
|
2891
|
+
const declaredRegistry = asText$4(asRecord$5(record.publishConfig)?.registry);
|
|
2766
2892
|
const registry = declaredRegistry === "" ? registryFromLockfile(profileDir, pkg) : declaredRegistry;
|
|
2767
2893
|
return {
|
|
2768
2894
|
kind: "module",
|
|
2769
2895
|
pkg,
|
|
2770
2896
|
unit,
|
|
2771
|
-
version: asText$
|
|
2897
|
+
version: asText$4(record.version),
|
|
2772
2898
|
...registry === "" ? {} : { registry }
|
|
2773
2899
|
};
|
|
2774
2900
|
}
|
|
@@ -2786,11 +2912,11 @@ function readProfilePackage(profileDir, pkg) {
|
|
|
2786
2912
|
}
|
|
2787
2913
|
const record = asRecord$5(manifest);
|
|
2788
2914
|
if (record === void 0) return void 0;
|
|
2789
|
-
const version = asText$
|
|
2915
|
+
const version = asText$4(record.version);
|
|
2790
2916
|
if (version === "") return void 0;
|
|
2791
|
-
const declaredRegistry = asText$
|
|
2917
|
+
const declaredRegistry = asText$4(asRecord$5(record.publishConfig)?.registry);
|
|
2792
2918
|
const registry = declaredRegistry === "" ? registryFromLockfile(profileDir, pkg) : declaredRegistry;
|
|
2793
|
-
const local = isLocalDependencySpec(asText$
|
|
2919
|
+
const local = isLocalDependencySpec(asText$4((asRecord$5(profileManifestOf(profileDir)?.dependencies) ?? {})[pkg]));
|
|
2794
2920
|
return {
|
|
2795
2921
|
pkg,
|
|
2796
2922
|
version,
|
|
@@ -2799,9 +2925,40 @@ function readProfilePackage(profileDir, pkg) {
|
|
|
2799
2925
|
};
|
|
2800
2926
|
}
|
|
2801
2927
|
//#endregion
|
|
2928
|
+
//#region src/shared/api/host/opencli-command.ts
|
|
2929
|
+
var OPENCLI_PATH_FALLBACK = "opencli";
|
|
2930
|
+
function isolatedOpencliPathOf(ctx) {
|
|
2931
|
+
const homePath = ctx.get("dshHomePath");
|
|
2932
|
+
if (typeof homePath !== "function") return null;
|
|
2933
|
+
try {
|
|
2934
|
+
return join(homePath("workbench/opencli/bin/opencli"));
|
|
2935
|
+
} catch {
|
|
2936
|
+
return null;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
function opencliCommandOf(ctx) {
|
|
2940
|
+
const isolated = isolatedOpencliPathOf(ctx);
|
|
2941
|
+
const fromEnv = process.env.DSH_WORKBENCH_OPENCLI;
|
|
2942
|
+
if (fromEnv !== void 0 && fromEnv !== "") return {
|
|
2943
|
+
command: fromEnv,
|
|
2944
|
+
source: "env",
|
|
2945
|
+
isolated
|
|
2946
|
+
};
|
|
2947
|
+
if (isolated !== null && existsSync(isolated)) return {
|
|
2948
|
+
command: isolated,
|
|
2949
|
+
source: "isolated",
|
|
2950
|
+
isolated
|
|
2951
|
+
};
|
|
2952
|
+
return {
|
|
2953
|
+
command: OPENCLI_PATH_FALLBACK,
|
|
2954
|
+
source: "path",
|
|
2955
|
+
isolated
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
//#endregion
|
|
2802
2959
|
//#region src/shared/api/host/profile-registries.ts
|
|
2803
2960
|
var REGISTRY_MARKER = "# dsh-workbench 写的:这一行的包只在这个源上(dsh 卸载没有源参数,pnpm 得问得到)";
|
|
2804
|
-
function scopeOf$
|
|
2961
|
+
function scopeOf$2(pkg) {
|
|
2805
2962
|
const slash = pkg.indexOf("/");
|
|
2806
2963
|
return pkg.startsWith("@") && slash > 1 ? pkg.slice(0, slash) : null;
|
|
2807
2964
|
}
|
|
@@ -2841,7 +2998,7 @@ function sourcesFromLockfile(text) {
|
|
|
2841
2998
|
if (name === null) continue;
|
|
2842
2999
|
const tarball = TARBALL.exec(line);
|
|
2843
3000
|
if (tarball === null) continue;
|
|
2844
|
-
const scope = scopeOf$
|
|
3001
|
+
const scope = scopeOf$2(name);
|
|
2845
3002
|
const root = registryRootOf(name, tarball[1] ?? "");
|
|
2846
3003
|
name = null;
|
|
2847
3004
|
if (scope === null || root === null) continue;
|
|
@@ -2930,7 +3087,7 @@ function workshopPathOf(ctx) {
|
|
|
2930
3087
|
function asRecord$4(value) {
|
|
2931
3088
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
2932
3089
|
}
|
|
2933
|
-
function asText$
|
|
3090
|
+
function asText$3(value) {
|
|
2934
3091
|
return typeof value === "string" ? value.trim() : "";
|
|
2935
3092
|
}
|
|
2936
3093
|
function workshopPackagesDir(ctx) {
|
|
@@ -2953,17 +3110,26 @@ function workshopModules(packagesDir, installed = []) {
|
|
|
2953
3110
|
}
|
|
2954
3111
|
const record = asRecord$4(manifest);
|
|
2955
3112
|
const declared = record === void 0 ? void 0 : asRecord$4(asRecord$4(record.dsh)?.workbench);
|
|
2956
|
-
const unit = declared === void 0 ? "" : asText$
|
|
3113
|
+
const unit = declared === void 0 ? "" : asText$3(declared.unit);
|
|
2957
3114
|
if (unit === "") return [];
|
|
2958
3115
|
return [{
|
|
2959
3116
|
unit,
|
|
2960
|
-
name: asText$
|
|
2961
|
-
description: asText$
|
|
3117
|
+
name: asText$3(record?.name),
|
|
3118
|
+
description: asText$3(record?.description),
|
|
2962
3119
|
dir,
|
|
2963
3120
|
installed: already.has(unit)
|
|
2964
3121
|
}];
|
|
2965
3122
|
}).sort((left, right) => left.unit.localeCompare(right.unit));
|
|
2966
3123
|
}
|
|
3124
|
+
function readDesignOfModule(dir) {
|
|
3125
|
+
const file = join(dir, "design.json");
|
|
3126
|
+
if (!existsSync(file)) return void 0;
|
|
3127
|
+
try {
|
|
3128
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
3129
|
+
} catch {
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
2967
3133
|
function inWorkshopSession(ctx, workspace) {
|
|
2968
3134
|
const root = workshopPathOf(ctx);
|
|
2969
3135
|
return root !== null && workspace !== null && insideWorkshop(root, workspace);
|
|
@@ -2977,20 +3143,20 @@ function workshopSyncOf(workshopRoot) {
|
|
|
2977
3143
|
}
|
|
2978
3144
|
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
2979
3145
|
const record = parsed;
|
|
2980
|
-
const state = asText$
|
|
3146
|
+
const state = asText$3(record["state"]);
|
|
2981
3147
|
if (state !== "ok" && state !== "syncing" && state !== "failed") return void 0;
|
|
2982
|
-
const reason = asText$
|
|
3148
|
+
const reason = asText$3(record["reason"]);
|
|
2983
3149
|
return {
|
|
2984
3150
|
state,
|
|
2985
|
-
version: asText$
|
|
2986
|
-
at: asText$
|
|
3151
|
+
version: asText$3(record["version"]),
|
|
3152
|
+
at: asText$3(record["at"]),
|
|
2987
3153
|
...reason === "" ? {} : { reason }
|
|
2988
3154
|
};
|
|
2989
3155
|
}
|
|
2990
3156
|
function workshopToolchainOf(workshopRoot) {
|
|
2991
3157
|
const versionOf = (name) => {
|
|
2992
3158
|
try {
|
|
2993
|
-
return asText$
|
|
3159
|
+
return asText$3(JSON.parse(readFileSync(join(workshopRoot, "node_modules", "@dsh-workbench", name, "package.json"), "utf8")).version);
|
|
2994
3160
|
} catch {
|
|
2995
3161
|
return "";
|
|
2996
3162
|
}
|
|
@@ -3025,6 +3191,7 @@ var MODULES_UPDATE_WORKBENCH_TOOL = "workbench_modules_update_workbench";
|
|
|
3025
3191
|
var DIY_LIST_TOOL = "workbench_diy_list";
|
|
3026
3192
|
var DIY_STATUS_TOOL = "workbench_diy_status";
|
|
3027
3193
|
var DIY_PANEL_TOOL = "workbench_diy_panel";
|
|
3194
|
+
var DIY_COMPONENTS_TOOL = "workbench_diy_components";
|
|
3028
3195
|
var DIY_RECON_TOOL = "workbench_diy_recon";
|
|
3029
3196
|
var DIY_SPEC_TOOL = "workbench_diy_spec";
|
|
3030
3197
|
var DIY_PROBE_TOOL = "workbench_diy_probe";
|
|
@@ -3047,6 +3214,10 @@ var DIY_DIALOG_TOOL = "workbench_diy_dialog";
|
|
|
3047
3214
|
var DIY_LOGS_WATCH_TOOL = "workbench_diy_logs_watch";
|
|
3048
3215
|
var DIY_LOGS_READ_TOOL = "workbench_diy_logs_read";
|
|
3049
3216
|
var DIY_LOGS_STOP_TOOL = "workbench_diy_logs_stop";
|
|
3217
|
+
var DIY_RECORD_PREPARE_TOOL = "workbench_diy_record_prepare";
|
|
3218
|
+
var DIY_RECORD_READ_TOOL = "workbench_diy_record_read";
|
|
3219
|
+
var DIY_RECORD_DETAIL_TOOL = "workbench_diy_record_detail";
|
|
3220
|
+
var DIY_RECORD_STOP_TOOL = "workbench_diy_record_stop";
|
|
3050
3221
|
var DIY_DEBUG_START_TOOL = "workbench_diy_debug_start";
|
|
3051
3222
|
var DIY_DEBUG_PAUSE_TOOL = "workbench_diy_debug_pause";
|
|
3052
3223
|
var DIY_DEBUG_RESUME_TOOL = "workbench_diy_debug_resume";
|
|
@@ -3057,6 +3228,7 @@ var READ_TOOLS = [
|
|
|
3057
3228
|
DIY_LIST_TOOL,
|
|
3058
3229
|
DIY_STATUS_TOOL,
|
|
3059
3230
|
DIY_PANEL_TOOL,
|
|
3231
|
+
DIY_COMPONENTS_TOOL,
|
|
3060
3232
|
DIY_RECON_TOOL,
|
|
3061
3233
|
DIY_SPEC_TOOL,
|
|
3062
3234
|
DIY_PROBE_TOOL,
|
|
@@ -3076,7 +3248,10 @@ var READ_TOOLS = [
|
|
|
3076
3248
|
DIY_DIALOG_TOOL,
|
|
3077
3249
|
DIY_LOGS_WATCH_TOOL,
|
|
3078
3250
|
DIY_LOGS_READ_TOOL,
|
|
3079
|
-
DIY_LOGS_STOP_TOOL
|
|
3251
|
+
DIY_LOGS_STOP_TOOL,
|
|
3252
|
+
DIY_RECORD_READ_TOOL,
|
|
3253
|
+
DIY_RECORD_DETAIL_TOOL,
|
|
3254
|
+
DIY_RECORD_STOP_TOOL
|
|
3080
3255
|
];
|
|
3081
3256
|
var WRITE_TOOLS = [
|
|
3082
3257
|
PANELS_SHOW_TOOL,
|
|
@@ -3094,6 +3269,7 @@ var WRITE_TOOLS = [
|
|
|
3094
3269
|
MODULES_UPDATE_WORKBENCH_TOOL,
|
|
3095
3270
|
DIY_INSTALL_TOOL,
|
|
3096
3271
|
DIY_SHOW_TOOL,
|
|
3272
|
+
DIY_RECORD_PREPARE_TOOL,
|
|
3097
3273
|
DIY_DEBUG_START_TOOL,
|
|
3098
3274
|
DIY_DEBUG_PAUSE_TOOL,
|
|
3099
3275
|
DIY_DEBUG_RESUME_TOOL,
|
|
@@ -3103,6 +3279,7 @@ var DIY_TOOLS = [
|
|
|
3103
3279
|
DIY_LIST_TOOL,
|
|
3104
3280
|
DIY_STATUS_TOOL,
|
|
3105
3281
|
DIY_PANEL_TOOL,
|
|
3282
|
+
DIY_COMPONENTS_TOOL,
|
|
3106
3283
|
DIY_RECON_TOOL,
|
|
3107
3284
|
DIY_SPEC_TOOL,
|
|
3108
3285
|
DIY_PROBE_TOOL,
|
|
@@ -3126,6 +3303,12 @@ var DIY_TOOLS = [
|
|
|
3126
3303
|
DIY_LOGS_READ_TOOL,
|
|
3127
3304
|
DIY_LOGS_STOP_TOOL
|
|
3128
3305
|
];
|
|
3306
|
+
var DIY_RECORD_TOOLS = [
|
|
3307
|
+
DIY_RECORD_PREPARE_TOOL,
|
|
3308
|
+
DIY_RECORD_READ_TOOL,
|
|
3309
|
+
DIY_RECORD_DETAIL_TOOL,
|
|
3310
|
+
DIY_RECORD_STOP_TOOL
|
|
3311
|
+
];
|
|
3129
3312
|
var DIY_DEBUG_TOOLS = [
|
|
3130
3313
|
DIY_DEBUG_START_TOOL,
|
|
3131
3314
|
DIY_DEBUG_PAUSE_TOOL,
|
|
@@ -3229,9 +3412,16 @@ var panelsApproval = (call) => {
|
|
|
3229
3412
|
const starting = call.name === DIY_DEBUG_START_TOOL;
|
|
3230
3413
|
const unit = argsOf$2(call)["unit"];
|
|
3231
3414
|
const target = typeof unit === "string" && unit !== "" ? ` [${unit}]` : "";
|
|
3232
|
-
return askOf(`${call.name}${target} takes over the user's screen to debug a workbench module`, pair(starting ? "要在「工作台DIY」那块板上开始调试 —— 调试中请留在那一屏,允许吗?" : "要接着调试 —— 请回到「工作台DIY」那一屏,允许吗?", starting ? "it takes over your screen to debug on the workshop board — please stay on that screen. Allow?" : "it needs your screen again to keep debugging — please go back to the workshop board. Allow?"), pair(starting ? "agent 要占用你的屏幕开始调试工作台模块,允许吗?" : "agent
|
|
3415
|
+
return askOf(`${call.name}${target} takes over the user's screen to debug a workbench module`, pair(starting ? "要在「工作台DIY」那块板上开始调试 —— 调试中请留在那一屏,允许吗?" : "要接着调试 —— 请回到「工作台DIY」那一屏,允许吗?", starting ? "it takes over your screen to debug on the workshop board — please stay on that screen. Allow?" : "it needs your screen again to keep debugging — please go back to the workshop board. Allow?"), pair(starting ? "agent 要占用你的屏幕开始调试工作台模块,允许吗?" : "agent 要接着占用你的屏幕,继续调试工作台模块,允许吗?", starting ? "The agent wants to take over your screen to start debugging a workbench module. Allow?" : "The agent wants your screen again to keep debugging a workbench module. Allow?"));
|
|
3233
3416
|
}
|
|
3234
3417
|
if (call.name === "workbench_diy_debug_pause" || call.name === "workbench_diy_debug_stop") return { kind: "allow" };
|
|
3418
|
+
if (call.name === "workbench_diy_record_prepare") {
|
|
3419
|
+
const raw = argsOf$2(call);
|
|
3420
|
+
const asked = typeof raw["url"] === "string" ? raw["url"].replace(/\s+/g, " ").trim() : "";
|
|
3421
|
+
const act = typeof raw["doing"] === "string" ? raw["doing"].replace(/\s+/g, " ").trim() : "";
|
|
3422
|
+
const title = pair("申请录制调试", "Request recording a debug session");
|
|
3423
|
+
return askOf(`${DIY_RECORD_PREPARE_TOOL} opens a browser window in front on ${asked} to record one real write` + (act === "" ? "" : ` (${act})`), title, title);
|
|
3424
|
+
}
|
|
3235
3425
|
if (call.name !== void 0 && DIY_TOOLS.includes(call.name)) return { kind: "allow" };
|
|
3236
3426
|
const raw = argsOf$2(call);
|
|
3237
3427
|
const text = (key) => {
|
|
@@ -3439,35 +3629,305 @@ function withMoved(board, id, index, visible) {
|
|
|
3439
3629
|
};
|
|
3440
3630
|
}
|
|
3441
3631
|
//#endregion
|
|
3442
|
-
//#region src/shared/api/host/
|
|
3443
|
-
var
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3632
|
+
//#region src/shared/api/host/diy-components.ts
|
|
3633
|
+
var SURFACE_TIMEOUT_MS = 3e4;
|
|
3634
|
+
var PRUNE_CHARS = 8192;
|
|
3635
|
+
var PRIMITIVES_PKG = "@deepseek-ai/dsh-client-ui-primitives";
|
|
3636
|
+
var MANIFEST_TAIL = "//# sourceMappingURL=";
|
|
3637
|
+
function scopeOf$1(raw) {
|
|
3638
|
+
if (raw === "primitives" || raw === "kit") return raw;
|
|
3639
|
+
return "all";
|
|
3640
|
+
}
|
|
3641
|
+
function wantsPrimitives(scope) {
|
|
3642
|
+
return scope !== "kit";
|
|
3643
|
+
}
|
|
3644
|
+
function wantsKit(scope) {
|
|
3645
|
+
return scope !== "primitives";
|
|
3646
|
+
}
|
|
3647
|
+
function toolchainBinOf(root) {
|
|
3648
|
+
const bin = join(root, "node_modules", ".bin", "dsh-wb-module");
|
|
3649
|
+
if (existsSync(bin)) return bin;
|
|
3650
|
+
const script = join(root, "node_modules", "@dsh-workbench", "module-tools", "bin", "dsh-wb-module.mjs");
|
|
3651
|
+
return existsSync(script) ? script : null;
|
|
3652
|
+
}
|
|
3653
|
+
function queryCommandOf(bin, only) {
|
|
3654
|
+
return `${quoteShellArg(bin)} check --only ${only} --json`;
|
|
3655
|
+
}
|
|
3656
|
+
async function runSurfaceQuery(shell, bin, only, root) {
|
|
3657
|
+
const result = await runShell(shell, queryCommandOf(bin, only), {
|
|
3658
|
+
workdir: root,
|
|
3659
|
+
timeoutMs: SURFACE_TIMEOUT_MS
|
|
3660
|
+
});
|
|
3661
|
+
const parsed = jsonOf$1(result.stdout);
|
|
3662
|
+
if (parsed !== null) return withoutProblems(parsed);
|
|
3663
|
+
if (result.exitCode !== 0) {
|
|
3664
|
+
const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-3).join(" | ");
|
|
3665
|
+
throw new Error(`工坊工具链的 \`check --only ${only} --json\` 没跑通(exit ${String(result.exitCode)})—— 这不是工具链坏了,是工坊里有模块过不了判据,所以连清单也没打出来${tail === "" ? "" : `:${tail}`}`);
|
|
3666
|
+
}
|
|
3667
|
+
throw new Error(`工坊工具链的 \`check --only ${only} --json\` 没回 JSON:${result.stdout.trim().split("\n").slice(-2).join(" | ")}`);
|
|
3668
|
+
}
|
|
3669
|
+
function jsonOf$1(stdout) {
|
|
3447
3670
|
try {
|
|
3448
|
-
|
|
3671
|
+
const parsed = JSON.parse(stdout);
|
|
3672
|
+
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
3449
3673
|
} catch {
|
|
3450
3674
|
return null;
|
|
3451
3675
|
}
|
|
3452
3676
|
}
|
|
3453
|
-
function
|
|
3454
|
-
const
|
|
3455
|
-
const
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3677
|
+
function withoutProblems(payload) {
|
|
3678
|
+
const rest = {};
|
|
3679
|
+
for (const [key, value] of Object.entries(payload)) if (key !== "problems") rest[key] = value;
|
|
3680
|
+
return rest;
|
|
3681
|
+
}
|
|
3682
|
+
function recordOf$1(value) {
|
|
3683
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
3684
|
+
}
|
|
3685
|
+
function asText$2(value) {
|
|
3686
|
+
return typeof value === "string" ? value : "";
|
|
3687
|
+
}
|
|
3688
|
+
function stringsOf$3(value) {
|
|
3689
|
+
return Array.isArray(value) ? value.filter((one) => typeof one === "string") : [];
|
|
3690
|
+
}
|
|
3691
|
+
function filesOf$1(payload) {
|
|
3692
|
+
const files = recordOf$1(payload?.["files"]);
|
|
3693
|
+
const out = {};
|
|
3694
|
+
for (const [name, file] of Object.entries(files)) if (typeof file === "string") out[name] = file;
|
|
3695
|
+
return out;
|
|
3696
|
+
}
|
|
3697
|
+
function rootsOf(payload) {
|
|
3698
|
+
const roots = recordOf$1(payload?.["roots"]);
|
|
3699
|
+
const out = {};
|
|
3700
|
+
for (const [key, dir] of Object.entries(roots)) if (typeof dir === "string") out[key] = dir;
|
|
3701
|
+
return out;
|
|
3702
|
+
}
|
|
3703
|
+
function installedVersionOf(payload) {
|
|
3704
|
+
const version = recordOf$1(payload?.["installed"])["version"];
|
|
3705
|
+
return typeof version === "string" ? version : null;
|
|
3706
|
+
}
|
|
3707
|
+
function distance(left, right) {
|
|
3708
|
+
let previous = Array.from({ length: right.length + 1 }, (_unused, index) => index);
|
|
3709
|
+
for (let row = 1; row <= left.length; row += 1) {
|
|
3710
|
+
const current = [row];
|
|
3711
|
+
for (let column = 1; column <= right.length; column += 1) {
|
|
3712
|
+
const same = left[row - 1] === right[column - 1] ? 0 : 1;
|
|
3713
|
+
current.push(Math.min((previous[column] ?? 0) + 1, (current[column - 1] ?? 0) + 1, (previous[column - 1] ?? 0) + same));
|
|
3714
|
+
}
|
|
3715
|
+
previous = current;
|
|
3716
|
+
}
|
|
3717
|
+
return previous[right.length] ?? 0;
|
|
3718
|
+
}
|
|
3719
|
+
function nearNamesOf(name, names, limit = 8) {
|
|
3720
|
+
const wanted = name.toLowerCase();
|
|
3721
|
+
return names.flatMap((one) => {
|
|
3722
|
+
const lower = one.toLowerCase();
|
|
3723
|
+
if (lower.length < 3) return lower === wanted ? [{
|
|
3724
|
+
one,
|
|
3725
|
+
score: 0
|
|
3726
|
+
}] : [];
|
|
3727
|
+
if (lower.includes(wanted)) return [{
|
|
3728
|
+
one,
|
|
3729
|
+
score: 0
|
|
3730
|
+
}];
|
|
3731
|
+
if (wanted.includes(lower)) return [{
|
|
3732
|
+
one,
|
|
3733
|
+
score: 1
|
|
3734
|
+
}];
|
|
3735
|
+
const typos = distance(lower, wanted);
|
|
3736
|
+
return typos <= 2 ? [{
|
|
3737
|
+
one,
|
|
3738
|
+
score: typos + 1
|
|
3739
|
+
}] : [];
|
|
3740
|
+
}).sort((left, right) => left.score - right.score || left.one.length - right.one.length || left.one.localeCompare(right.one)).slice(0, limit).map((one) => one.one);
|
|
3741
|
+
}
|
|
3742
|
+
function findFileOf(name, query) {
|
|
3743
|
+
const primitives = filesOf$1(query.primitives)[name];
|
|
3744
|
+
if (primitives !== void 0) return {
|
|
3745
|
+
file: primitives,
|
|
3746
|
+
from: "primitives"
|
|
3747
|
+
};
|
|
3748
|
+
const kit = filesOf$1(query.kit)[name];
|
|
3749
|
+
return kit === void 0 ? null : {
|
|
3750
|
+
file: kit,
|
|
3751
|
+
from: "kit"
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
function excerptOf(text, maxLines = 60) {
|
|
3755
|
+
const cutAt = text.indexOf(MANIFEST_TAIL);
|
|
3756
|
+
const body = (cutAt === -1 ? text : text.slice(0, cutAt)).trimEnd();
|
|
3757
|
+
const lines = body.split("\n");
|
|
3758
|
+
return lines.length <= maxLines ? body : `${lines.slice(0, maxLines).join("\n")}\n…(这份文件还有 ${String(lines.length - maxLines)} 行)`;
|
|
3759
|
+
}
|
|
3760
|
+
function excerptAt(file, maxLines = 60) {
|
|
3761
|
+
try {
|
|
3762
|
+
return excerptOf(readFileSync(file, "utf8"), maxLines);
|
|
3763
|
+
} catch {
|
|
3764
|
+
return "(打不开那份类型文件 —— 照上面的路径自己看一眼)";
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
function skillDirOf(from) {
|
|
3768
|
+
let dir = from;
|
|
3769
|
+
for (let hop = 0; hop < 6; hop += 1) {
|
|
3770
|
+
const candidate = join(dir, "skills", "workbench-module-diy");
|
|
3771
|
+
if (existsSync(join(candidate, "HOOKS.md"))) return candidate;
|
|
3772
|
+
const parent = dirname(dir);
|
|
3773
|
+
if (parent === dir) break;
|
|
3774
|
+
dir = parent;
|
|
3775
|
+
}
|
|
3776
|
+
return null;
|
|
3777
|
+
}
|
|
3778
|
+
function symbolsIn(heading) {
|
|
3779
|
+
return [...heading.matchAll(/`([^`]+)`/g)].map((one) => (one[1] ?? "").trim()).map((one) => one.replace(/[<({[].*$/u, "").trim()).map((one) => one.replace(/[.::,,\s].*$/u, "").trim()).filter((one) => /^[\w$]+$/u.test(one));
|
|
3780
|
+
}
|
|
3781
|
+
function sectionOf(text, name) {
|
|
3782
|
+
const rows = text.split("\n");
|
|
3783
|
+
const at = rows.findIndex((row) => row.startsWith("### ") && symbolsIn(row).includes(name));
|
|
3784
|
+
if (at === -1) return "";
|
|
3785
|
+
const rest = rows.slice(at + 1).findIndex((row) => row.startsWith("## ") || row.startsWith("### "));
|
|
3786
|
+
return rows.slice(at, rest === -1 ? rows.length : at + 1 + rest).join("\n").trim();
|
|
3787
|
+
}
|
|
3788
|
+
function docSectionOf(name, dir) {
|
|
3789
|
+
if (dir === null) return null;
|
|
3790
|
+
for (const doc of ["HOOKS.md", "KIT.md"]) {
|
|
3791
|
+
const file = join(dir, doc);
|
|
3792
|
+
try {
|
|
3793
|
+
const lines = sectionOf(readFileSync(file, "utf8"), name);
|
|
3794
|
+
if (lines !== "") return {
|
|
3795
|
+
file,
|
|
3796
|
+
lines
|
|
3797
|
+
};
|
|
3798
|
+
} catch {
|
|
3799
|
+
continue;
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
return null;
|
|
3803
|
+
}
|
|
3804
|
+
function truncated(names, all) {
|
|
3805
|
+
if (all || names.length <= 12) return names.join(", ");
|
|
3806
|
+
return `${names.slice(0, 12).join(", ")} …+${String(names.length - 12)}`;
|
|
3807
|
+
}
|
|
3808
|
+
function byLocationOf(payload) {
|
|
3809
|
+
const roots = Object.values(rootsOf(payload)).sort((left, right) => right.length - left.length);
|
|
3810
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3811
|
+
for (const [name, file] of Object.entries(filesOf$1(payload))) {
|
|
3812
|
+
const root = roots.find((one) => file === one || file.startsWith(`${one}/`));
|
|
3813
|
+
const where = (root === void 0 ? file.split("/").slice(-2).join("/") : file.slice(root.length + 1)).split("/").slice(0, -1).join("/") || ".";
|
|
3814
|
+
const bucket = groups.get(where);
|
|
3815
|
+
if (bucket === void 0) groups.set(where, [name]);
|
|
3816
|
+
else bucket.push(name);
|
|
3817
|
+
}
|
|
3818
|
+
return [...groups.entries()].map(([where, names]) => ({
|
|
3819
|
+
where,
|
|
3820
|
+
names: [...names].sort()
|
|
3821
|
+
})).sort((left, right) => right.names.length - left.names.length || left.where.localeCompare(right.where));
|
|
3822
|
+
}
|
|
3823
|
+
function isComponents(where) {
|
|
3824
|
+
return where.split("/").at(-1) === "components";
|
|
3825
|
+
}
|
|
3826
|
+
function renderSurfaceText(query, options) {
|
|
3827
|
+
const lines = [];
|
|
3828
|
+
const kitNames = Object.keys(filesOf$1(query.kit)).sort();
|
|
3829
|
+
if (query.kit !== void 0) {
|
|
3830
|
+
const groups = byLocationOf(query.kit);
|
|
3831
|
+
if (kitNames.length === 0 && groups.length === 0) {
|
|
3832
|
+
const anyNames = [...new Set(Object.values(recordOf$1(query.kit["buckets"])).flatMap((value) => stringsOf$3(value)))].sort();
|
|
3833
|
+
lines.push(anyNames.length === 0 ? "kit:读不到(没找到 client.js)" : `kit:类型面扫不到,只有名字 ${truncated(anyNames, options.all)}`);
|
|
3834
|
+
} else {
|
|
3835
|
+
const componentNames = groups.filter((one) => isComponents(one.where)).flatMap((one) => one.names).sort();
|
|
3836
|
+
const countOf = (one) => `${isComponents(one.where) ? "组件" : one.where} ${one.names.length}`;
|
|
3837
|
+
const counts = [...componentNames.length === 0 ? [] : [`组件 ${componentNames.length}`], ...groups.filter((one) => !isComponents(one.where)).map(countOf)];
|
|
3838
|
+
lines.push(`kit ${kitNames.length}:${counts.join(" · ")}`);
|
|
3839
|
+
if (componentNames.length > 0) lines.push(` 组件(${componentNames.length}):${truncated(componentNames, options.all)}`);
|
|
3840
|
+
for (const group of groups) {
|
|
3841
|
+
if (isComponents(group.where)) continue;
|
|
3842
|
+
lines.push(` ${group.where}(${group.names.length}):${truncated(group.names, options.all)}`);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
if (query.primitives !== void 0) {
|
|
3847
|
+
const version = installedVersionOf(query.primitives);
|
|
3848
|
+
const only = Object.entries(recordOf$1(query.primitives["onlyPrimitives"])).map(([bucket, value]) => ({
|
|
3849
|
+
bucket,
|
|
3850
|
+
names: [...stringsOf$3(value)].sort()
|
|
3851
|
+
})).filter((one) => one.names.length > 0);
|
|
3852
|
+
const covered = (Array.isArray(query.primitives["kitCovered"]) ? query.primitives["kitCovered"] : []).map((one) => recordOf$1(one)["name"]).filter((one) => typeof one === "string");
|
|
3853
|
+
if (version === null) {
|
|
3854
|
+
const want = asText$2(recordOf$1(recordOf$1(query.primitives["snippet"])["devDependency"])["version"]);
|
|
3855
|
+
lines.push(`原语:没装 —— devDependencies 加 "${PRIMITIVES_PKG}": "${want === "" ? "<外壳钉的那版>" : want}" 再 pnpm install`);
|
|
3856
|
+
} else lines.push(`原语 ${version}${only.length === 0 ? "" : `:只有它有 ${only.map((one) => `${one.bucket} ${truncated(one.names, options.all)}`).join(" · ")}`}`);
|
|
3857
|
+
if (only.length > 0 && version !== null) lines.push("(只有原语有的,用之前先在 dsh.client.external 里声明)");
|
|
3858
|
+
if (covered.length > 0) lines.push(`kit 里已有:${covered.join(", ")}`);
|
|
3859
|
+
}
|
|
3860
|
+
const roots = [...Object.entries(rootsOf(query.kit)).map(([key, dir]) => `${key}=${dir}`), ...Object.entries(rootsOf(query.primitives)).map(([key, dir]) => `${key}=${dir}`)];
|
|
3861
|
+
if (roots.length > 0) lines.push(`类型面:${roots.join(" · ")}`);
|
|
3862
|
+
lines.push(faceStateOf(query, "primitives") === "unreadable" ? "name=<件> 看它怎么用 · all=true 出全表(原语装过才能问原语那类)" : "name=<件> 看它怎么用 · all=true 出全表");
|
|
3863
|
+
const text = lines.join("\n");
|
|
3864
|
+
if (text.length <= 8192) return text;
|
|
3865
|
+
return [
|
|
3866
|
+
`⚠️ 这份清单 ${String(text.length)} 字符,超过 ${String(PRUNE_CHARS)} 会被修剪器裁掉中间:`,
|
|
3867
|
+
` 一件件问(name=<件>)或到 shell 里跑 \`dsh-wb-module check --only ${options.scope === "all" ? "primitives|kit" : options.scope} --all\` 再 grep。`,
|
|
3868
|
+
"",
|
|
3869
|
+
text
|
|
3870
|
+
].join("\n");
|
|
3871
|
+
}
|
|
3872
|
+
function listedNamesOf(query) {
|
|
3873
|
+
const out = /* @__PURE__ */ new Set([...Object.keys(filesOf$1(query.primitives)), ...Object.keys(filesOf$1(query.kit))]);
|
|
3874
|
+
for (const payload of [query.primitives, query.kit]) {
|
|
3875
|
+
if (payload === void 0) continue;
|
|
3876
|
+
for (const value of Object.values(recordOf$1(payload["buckets"]))) for (const one of stringsOf$3(value)) out.add(one);
|
|
3877
|
+
for (const value of Object.values(recordOf$1(payload["onlyPrimitives"]))) for (const one of stringsOf$3(value)) out.add(one);
|
|
3878
|
+
for (const one of Array.isArray(payload["kitCovered"]) ? payload["kitCovered"] : []) {
|
|
3879
|
+
const covered = asText$2(recordOf$1(one)["name"]);
|
|
3880
|
+
if (covered !== "") out.add(covered);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
return [...out].sort();
|
|
3884
|
+
}
|
|
3885
|
+
function faceStateOf(query, face) {
|
|
3886
|
+
const payload = face === "kit" ? query.kit : query.primitives;
|
|
3887
|
+
if (payload === void 0) return "not-queried";
|
|
3888
|
+
if (face === "primitives") {
|
|
3889
|
+
const named = Object.keys(filesOf$1(payload)).length > 0 || Object.keys(recordOf$1(payload["onlyPrimitives"])).length > 0;
|
|
3890
|
+
return installedVersionOf(payload) === null || !named ? "unreadable" : "ok";
|
|
3891
|
+
}
|
|
3892
|
+
return Object.keys(filesOf$1(payload)).length > 0 || Object.keys(rootsOf(payload)).length > 0 ? "ok" : "unreadable";
|
|
3893
|
+
}
|
|
3894
|
+
function installSnippetOf(query) {
|
|
3895
|
+
const dependency = recordOf$1(recordOf$1(query.primitives?.["snippet"])["devDependency"]);
|
|
3896
|
+
const name = asText$2(dependency["name"]) || "@deepseek-ai/dsh-client-ui-primitives";
|
|
3897
|
+
const version = asText$2(dependency["version"]);
|
|
3898
|
+
return `devDependencies 加 "${name}": "${version === "" ? "<外壳钉的那版>" : version}" 再 pnpm install`;
|
|
3899
|
+
}
|
|
3900
|
+
function renderNameText(name, found, query) {
|
|
3901
|
+
if (found === null) {
|
|
3902
|
+
const every = listedNamesOf(query);
|
|
3903
|
+
const listed = every.includes(name);
|
|
3904
|
+
const near = nearNamesOf(name, every).filter((one) => one !== name);
|
|
3905
|
+
const kit = faceStateOf(query, "kit");
|
|
3906
|
+
const primitives = faceStateOf(query, "primitives");
|
|
3907
|
+
const said = [kit === "ok" ? "kit 里没有" : "", primitives === "ok" ? "原语里也没有" : ""].filter((one) => one !== "");
|
|
3908
|
+
const blind = [kit === "ok" ? "" : `kit 那一面${kit === "not-queried" ? "没查" : "读不到"}`, primitives === "ok" ? "" : `原语那一面${primitives === "not-queried" ? "没查" : "读不到(没装)"}`].filter((one) => one !== "");
|
|
3909
|
+
const how = primitives === "unreadable" ? `${installSnippetOf(query)},才敢说它在不在。` : kit === "unreadable" ? "把这份外壳装上(让 client.js 读得到)再问。" : "不给 scope 就都查。";
|
|
3910
|
+
const lines = [...listed ? [`「${name}」在清单里(${String(every.length)} 个名字里有它),但扫不到它的类型文件 —— 类型面读不到,或这代外壳的声明形状变了。`] : kit === "ok" && primitives === "ok" ? [`没有「${name}」这个东西(kit 与原语里都没有)。`] : said.length > 0 ? [`「${name}」${said.join(",")}。`, ...blind.length === 0 ? [] : [`${blind.join("、")} —— ${how}`]] : [`没法确认「${name}」在不在:${blind.join("、")} —— ${how}`]];
|
|
3911
|
+
if (near.length > 0) lines.push(`近似的名字:${near.join(", ")}`);
|
|
3912
|
+
return lines.join("\n");
|
|
3913
|
+
}
|
|
3914
|
+
const body = excerptAt(found.file);
|
|
3915
|
+
if (body.includes("/**")) return [
|
|
3916
|
+
`${name} → ${found.file}(${found.from === "kit" ? "kit" : "原语"})`,
|
|
3917
|
+
"",
|
|
3918
|
+
body,
|
|
3919
|
+
"",
|
|
3920
|
+
found.from === "kit" ? "(上面就是它的声明;@param 写着默认值。kit 件最小写法:KIT.md)" : "(上面就是它的声明;@param 写着默认值。原语件要先在 dsh.client.external 里声明它)"
|
|
3921
|
+
].join("\n");
|
|
3922
|
+
const skillDir = skillDirOf(dirname(fileURLToPath(import.meta.url)));
|
|
3923
|
+
const doc = docSectionOf(name, skillDir);
|
|
3924
|
+
return [
|
|
3925
|
+
`${name} → ${found.file}(${found.from === "kit" ? "kit" : "原语"})`,
|
|
3926
|
+
"",
|
|
3927
|
+
body,
|
|
3928
|
+
"",
|
|
3929
|
+
doc === null ? `(声明没说明文字;${skillDir === null ? "指引也没装" : `指引里没有专门讲「${name}」的一节`} —— 源码零注释,tsc 不 emit JSDoc。用法看 KIT.md / HOOKS.md / LINKAGE.md / PANEL.md / TOOLS.md。)` : `(声明没说明文字(零注释);语义出自 ${basename(doc.file)}:)\n\n${doc.lines}`
|
|
3930
|
+
].join("\n");
|
|
3471
3931
|
}
|
|
3472
3932
|
var OUTPUT$2 = {
|
|
3473
3933
|
schema: {
|
|
@@ -3584,6 +4044,74 @@ function panelEntriesOf(panels, workspace) {
|
|
|
3584
4044
|
}
|
|
3585
4045
|
}
|
|
3586
4046
|
//#endregion
|
|
4047
|
+
//#region src/modules/panels/model/diy-components.ts
|
|
4048
|
+
function componentsTool(ctx) {
|
|
4049
|
+
return {
|
|
4050
|
+
name: DIY_COMPONENTS_TOOL,
|
|
4051
|
+
description: "工坊里能用什么:dsh 原语与外壳 kit 的清单、每件的类型文件在哪(给 name 连 JSDoc 与 prop 一起回)。",
|
|
4052
|
+
parameters: {
|
|
4053
|
+
type: "object",
|
|
4054
|
+
properties: {
|
|
4055
|
+
name: {
|
|
4056
|
+
type: "string",
|
|
4057
|
+
description: "要看的那件(例 Card;不填就给清单)。"
|
|
4058
|
+
},
|
|
4059
|
+
scope: {
|
|
4060
|
+
type: "string",
|
|
4061
|
+
enum: [
|
|
4062
|
+
"all",
|
|
4063
|
+
"primitives",
|
|
4064
|
+
"kit"
|
|
4065
|
+
],
|
|
4066
|
+
description: "只看某一面(默认 all)。"
|
|
4067
|
+
},
|
|
4068
|
+
all: {
|
|
4069
|
+
type: "boolean",
|
|
4070
|
+
description: "列全表(默认每组前 12 个)。"
|
|
4071
|
+
}
|
|
4072
|
+
},
|
|
4073
|
+
additionalProperties: false
|
|
4074
|
+
},
|
|
4075
|
+
output: OUTPUT$2,
|
|
4076
|
+
async execute(raw, exec) {
|
|
4077
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
4078
|
+
const root = dirname(packagesDir);
|
|
4079
|
+
const bin = toolchainBinOf(root);
|
|
4080
|
+
if (bin === null) throw new Error(`工坊里还没装工具链(${join(root, "node_modules")} 里没有 dsh-wb-module)—— 在工坊根目录跑一次 pnpm install 就有了;workbench_diy_status 会告诉你卡在哪一步`);
|
|
4081
|
+
const shell = ctx.get("shell");
|
|
4082
|
+
if (shell === void 0) throw new Error("这台机器没给 shell 服务,读不了组件清单");
|
|
4083
|
+
const args = asRecord$3(raw);
|
|
4084
|
+
const scope = scopeOf$1(asText$1(args["scope"]));
|
|
4085
|
+
const name = asText$1(args["name"]);
|
|
4086
|
+
const all = args["all"] === true;
|
|
4087
|
+
const [primitives, kit] = await Promise.all([wantsPrimitives(scope) ? runSurfaceQuery(shell, bin, "primitives", root) : Promise.resolve(void 0), wantsKit(scope) ? runSurfaceQuery(shell, bin, "kit", root) : Promise.resolve(void 0)]);
|
|
4088
|
+
const query = {
|
|
4089
|
+
...primitives === void 0 ? {} : { primitives },
|
|
4090
|
+
...kit === void 0 ? {} : { kit }
|
|
4091
|
+
};
|
|
4092
|
+
const found = name === "" ? null : findFileOf(name, query);
|
|
4093
|
+
const text = name === "" ? renderSurfaceText(query, {
|
|
4094
|
+
scope,
|
|
4095
|
+
all
|
|
4096
|
+
}) : renderNameText(name, found, query);
|
|
4097
|
+
return plainJson({
|
|
4098
|
+
workshop: root,
|
|
4099
|
+
scope,
|
|
4100
|
+
name,
|
|
4101
|
+
all,
|
|
4102
|
+
file: found?.file ?? null,
|
|
4103
|
+
from: found?.from ?? null,
|
|
4104
|
+
primitives: primitives ?? null,
|
|
4105
|
+
kit: kit ?? null,
|
|
4106
|
+
text
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
function registerComponentsTool(ctx, tools) {
|
|
4112
|
+
return [tools.register(componentsTool(ctx))];
|
|
4113
|
+
}
|
|
4114
|
+
//#endregion
|
|
3587
4115
|
//#region src/modules/panels/model/diy-scripts.ts
|
|
3588
4116
|
var PAGE_PROBE = `(async () => {
|
|
3589
4117
|
const answer = await fetch('/workbench/api/health').then((one) => one.json()).catch(() => null);
|
|
@@ -3842,7 +4370,7 @@ function renderDebugResume(state) {
|
|
|
3842
4370
|
function renderDebugStop(state) {
|
|
3843
4371
|
return `收工了:${state.unit} 的调试态清掉了(占用解除)。`;
|
|
3844
4372
|
}
|
|
3845
|
-
function moveOrThrow(move, held) {
|
|
4373
|
+
function moveOrThrow$1(move, held) {
|
|
3846
4374
|
if (move.ok) return move.state;
|
|
3847
4375
|
throw new Error(debugRefusalText(move.code, held ?? void 0));
|
|
3848
4376
|
}
|
|
@@ -3850,7 +4378,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
3850
4378
|
const startTool = {
|
|
3851
4379
|
name: DIY_DEBUG_START_TOOL,
|
|
3852
4380
|
description: "开始调试:占用用户那一屏(要点头一次)。没有它,看 / 动 / 网络那一族一律当场拒。",
|
|
3853
|
-
parameters:
|
|
4381
|
+
parameters: {
|
|
3854
4382
|
type: "object",
|
|
3855
4383
|
properties: { unit: {
|
|
3856
4384
|
type: "string",
|
|
@@ -3858,7 +4386,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
3858
4386
|
} },
|
|
3859
4387
|
required: ["unit"],
|
|
3860
4388
|
additionalProperties: false
|
|
3861
|
-
},
|
|
4389
|
+
},
|
|
3862
4390
|
output: OUTPUT$2,
|
|
3863
4391
|
async execute(raw, exec) {
|
|
3864
4392
|
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
@@ -3872,7 +4400,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
3872
4400
|
requireBoard: false,
|
|
3873
4401
|
requirePact: false
|
|
3874
4402
|
});
|
|
3875
|
-
const state = moveOrThrow(store.begin({
|
|
4403
|
+
const state = moveOrThrow$1(store.begin({
|
|
3876
4404
|
sessionId: sessionIdOf(exec) ?? "",
|
|
3877
4405
|
workspace: workspace ?? "",
|
|
3878
4406
|
board: page.boardPath,
|
|
@@ -3898,7 +4426,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
3898
4426
|
async execute(_raw, exec) {
|
|
3899
4427
|
const { packagesDir } = workshopOf(ctx, exec);
|
|
3900
4428
|
const held = store.live(Date.now());
|
|
3901
|
-
const state = moveOrThrow(store.pause(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
4429
|
+
const state = moveOrThrow$1(store.pause(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
3902
4430
|
return plainJson({
|
|
3903
4431
|
workshop: packagesDir,
|
|
3904
4432
|
unit: state.unit,
|
|
@@ -3911,7 +4439,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
3911
4439
|
const resumeTool = {
|
|
3912
4440
|
name: DIY_DEBUG_RESUME_TOOL,
|
|
3913
4441
|
description: "继续调试(要点头一次):请用户回到「工作台DIY」那一屏。",
|
|
3914
|
-
parameters:
|
|
4442
|
+
parameters: EMPTY,
|
|
3915
4443
|
output: OUTPUT$2,
|
|
3916
4444
|
async execute(_raw, exec) {
|
|
3917
4445
|
const { packagesDir } = workshopOf(ctx, exec);
|
|
@@ -3921,7 +4449,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
3921
4449
|
requirePact: false,
|
|
3922
4450
|
requireUnit: false
|
|
3923
4451
|
});
|
|
3924
|
-
const state = moveOrThrow(store.resume(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
4452
|
+
const state = moveOrThrow$1(store.resume(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
3925
4453
|
return plainJson({
|
|
3926
4454
|
workshop: packagesDir,
|
|
3927
4455
|
unit: state.unit,
|
|
@@ -3940,7 +4468,7 @@ function registerDebugTools(ctx, tools, store) {
|
|
|
3940
4468
|
async execute(_raw, exec) {
|
|
3941
4469
|
const { packagesDir } = workshopOf(ctx, exec);
|
|
3942
4470
|
const held = store.live(Date.now());
|
|
3943
|
-
const state = moveOrThrow(store.stop(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
4471
|
+
const state = moveOrThrow$1(store.stop(sessionIdOf(exec) ?? "", Date.now()), held);
|
|
3944
4472
|
await store.settle?.();
|
|
3945
4473
|
return plainJson({
|
|
3946
4474
|
workshop: packagesDir,
|
|
@@ -3990,6 +4518,7 @@ async function stopNetworkExact(shell, file, timeoutMs = SWEEP_TIMEOUT_MS) {
|
|
|
3990
4518
|
return (await runShell(shell, [
|
|
3991
4519
|
`p="$(cat ${pidFile} 2>/dev/null)"`,
|
|
3992
4520
|
`r="$(cat ${reaperFile} 2>/dev/null)"`,
|
|
4521
|
+
`[ -n "$p" ] && pkill -P "$p" 2>/dev/null`,
|
|
3993
4522
|
`[ -n "$p" ] && kill "$p" 2>/dev/null`,
|
|
3994
4523
|
`[ -n "$r" ] && pkill -P "$r" 2>/dev/null`,
|
|
3995
4524
|
`[ -n "$r" ] && kill "$r" 2>/dev/null`,
|
|
@@ -5772,7 +6301,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
5772
6301
|
const showTool = {
|
|
5773
6302
|
name: DIY_SHOW_TOOL,
|
|
5774
6303
|
description: "只上板(不装):把一个已经装好的工坊模块显示在当前项目的面板上。",
|
|
5775
|
-
parameters:
|
|
6304
|
+
parameters: {
|
|
5776
6305
|
type: "object",
|
|
5777
6306
|
properties: { unit: {
|
|
5778
6307
|
type: "string",
|
|
@@ -5780,7 +6309,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
5780
6309
|
} },
|
|
5781
6310
|
required: ["unit"],
|
|
5782
6311
|
additionalProperties: false
|
|
5783
|
-
},
|
|
6312
|
+
},
|
|
5784
6313
|
output: OUTPUT$2,
|
|
5785
6314
|
async execute(raw, exec) {
|
|
5786
6315
|
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
@@ -5800,7 +6329,7 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
5800
6329
|
const installTool = {
|
|
5801
6330
|
name: DIY_INSTALL_TOOL,
|
|
5802
6331
|
description: "把工坊里造好的模块装上并上到当前项目的面板。这是持久变更,一个模块只装一次:之后改源码只需 dsh-wb-module build(宿主侧秒级热换)。",
|
|
5803
|
-
parameters:
|
|
6332
|
+
parameters: {
|
|
5804
6333
|
type: "object",
|
|
5805
6334
|
properties: { unit: {
|
|
5806
6335
|
type: "string",
|
|
@@ -5808,121 +6337,1740 @@ function registerDiyTools(ctx, market, panels, debug) {
|
|
|
5808
6337
|
} },
|
|
5809
6338
|
required: ["unit"],
|
|
5810
6339
|
additionalProperties: false
|
|
5811
|
-
},
|
|
6340
|
+
},
|
|
6341
|
+
output: OUTPUT$2,
|
|
6342
|
+
async execute(raw, exec) {
|
|
6343
|
+
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
6344
|
+
const found = unitOf(packagesDir, raw, "workbench_diy_install");
|
|
6345
|
+
if (found.installed) {
|
|
6346
|
+
const panel = showOrReason(panels, found.unit, workspace);
|
|
6347
|
+
const text = ["它已经装过了(就是工坊里这一份)—— **不要再装**:改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面)。", panel.ok ? `面板上已经打开它了(${workspace})—— 验收就走这一张卡(你自己走一遍)` : `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})`].join("\n");
|
|
6348
|
+
return plainJson({
|
|
6349
|
+
workshop: packagesDir,
|
|
6350
|
+
unit: found.unit,
|
|
6351
|
+
path: found.dir,
|
|
6352
|
+
alreadyInstalled: true,
|
|
6353
|
+
panel,
|
|
6354
|
+
text
|
|
6355
|
+
});
|
|
6356
|
+
}
|
|
6357
|
+
const outcome = await market.installBundle(found.dir);
|
|
6358
|
+
const verdict = verdictOf$1(outcome.change);
|
|
6359
|
+
const restartRequired = outcome.change.application === "restart-required";
|
|
6360
|
+
const panel = outcome.change.application === "applied" ? showOrReason(panels, found.unit, workspace) : {
|
|
6361
|
+
ok: false,
|
|
6362
|
+
reason: `安装结果是 ${outcome.change.application},还没生效`
|
|
6363
|
+
};
|
|
6364
|
+
const panelText = panel.ok ? `面板上已经打开它了(${workspace})—— 验收就走这一张卡(你自己走一遍)` : outcome.change.application === "applied" ? `面板上还没打开:${panel.reason}。调 workbench_diy_show(unit=${found.unit})试试。` : restartRequired ? `面板上还没打开(${panel.reason}):模块要**重启一次工作台**才注册进面板 —— 把这句话转给用户,重启后再调 workbench_diy_show(unit=${found.unit})。` : `面板上还没打开:${panel.reason}。先把上面那句"没装成"处理掉,再谈上板。`;
|
|
6365
|
+
const text = [
|
|
6366
|
+
`${verdict}:${found.unit}(${found.name})`,
|
|
6367
|
+
panelText,
|
|
6368
|
+
"之后只改源码 + dsh-wb-module build(宿主侧与 client.js 都秒级热换 —— dsh-client-hmr 盯着每个 bundle 的 lib/client.js,重建了就换进已打开的页面):不要再装一遍。"
|
|
6369
|
+
].join("\n");
|
|
6370
|
+
return plainJson({
|
|
6371
|
+
workshop: packagesDir,
|
|
6372
|
+
unit: found.unit,
|
|
6373
|
+
path: found.dir,
|
|
6374
|
+
install: outcome.change,
|
|
6375
|
+
module: outcome.module,
|
|
6376
|
+
panel,
|
|
6377
|
+
text
|
|
6378
|
+
});
|
|
6379
|
+
}
|
|
6380
|
+
};
|
|
6381
|
+
return [
|
|
6382
|
+
tools.register(listTool),
|
|
6383
|
+
tools.register(statusTool),
|
|
6384
|
+
tools.register(panelTool),
|
|
6385
|
+
...registerComponentsTool(ctx, tools),
|
|
6386
|
+
tools.register(reconTool),
|
|
6387
|
+
...registerSpecTool(ctx, tools),
|
|
6388
|
+
tools.register(probeTool),
|
|
6389
|
+
...registerLogTools(ctx, tools),
|
|
6390
|
+
...registerBrowserTools(ctx, tools),
|
|
6391
|
+
...registerDebugTools(ctx, tools, debug),
|
|
6392
|
+
tools.register(showTool),
|
|
6393
|
+
tools.register(installTool)
|
|
6394
|
+
];
|
|
6395
|
+
}
|
|
6396
|
+
//#endregion
|
|
6397
|
+
//#region src/modules/panels/model/diy-observers.ts
|
|
6398
|
+
async function stopObservers(ctx, session = BROWSER_SCREEN_SESSION) {
|
|
6399
|
+
const packagesDir = workshopPackagesDir(ctx);
|
|
6400
|
+
const shell = ctx.get("shell");
|
|
6401
|
+
if (packagesDir === null || shell === void 0) return {
|
|
6402
|
+
net: false,
|
|
6403
|
+
logs: false
|
|
6404
|
+
};
|
|
6405
|
+
const file = join(dirname(packagesDir), "shots", `net-${session}.jsonl`);
|
|
6406
|
+
const command = opencliCommandOf(ctx).command;
|
|
6407
|
+
const [net, step] = await Promise.all([stopNetworkExact(shell, file), actBrowser(shell, command, session, ["eval", LOGS_STOP_SCRIPT], SWEEP_TIMEOUT_MS)]);
|
|
6408
|
+
return {
|
|
6409
|
+
net,
|
|
6410
|
+
logs: typeof step.payload?.["unhooked"] === "number"
|
|
6411
|
+
};
|
|
6412
|
+
}
|
|
6413
|
+
function withObserverCleanup(store, cleanup, logger) {
|
|
6414
|
+
let pending = Promise.resolve();
|
|
6415
|
+
let epoch = 0;
|
|
6416
|
+
const sweep = async (at) => {
|
|
6417
|
+
if (epoch !== at) {
|
|
6418
|
+
logger?.warn?.("收工那一拍:已经有新的调试约定开始了 —— 跳过收观察(新那一场自己收)");
|
|
6419
|
+
return;
|
|
6420
|
+
}
|
|
6421
|
+
try {
|
|
6422
|
+
const out = await cleanup();
|
|
6423
|
+
if (!out.net || !out.logs) logger?.warn?.(`收工那一拍没把观察收干净(网络 ${String(out.net)} / 日志 ${String(out.logs)})—— 网络那条最多 15 分钟自己收工,日志钩子随页面刷新消失`);
|
|
6424
|
+
} catch (error) {
|
|
6425
|
+
logger?.warn?.(`收工那一拍收观察抛了:${error instanceof Error ? error.message : String(error)}`);
|
|
6426
|
+
}
|
|
6427
|
+
};
|
|
6428
|
+
return {
|
|
6429
|
+
live: (now) => store.live(now),
|
|
6430
|
+
begin(pact) {
|
|
6431
|
+
epoch += 1;
|
|
6432
|
+
return store.begin(pact);
|
|
6433
|
+
},
|
|
6434
|
+
pause: (sessionId, now) => store.pause(sessionId, now),
|
|
6435
|
+
resume: (sessionId, now) => store.resume(sessionId, now),
|
|
6436
|
+
stop(sessionId, now) {
|
|
6437
|
+
const move = store.stop(sessionId, now);
|
|
6438
|
+
if (move.ok) {
|
|
6439
|
+
const at = epoch;
|
|
6440
|
+
pending = pending.then(() => sweep(at));
|
|
6441
|
+
}
|
|
6442
|
+
return move;
|
|
6443
|
+
},
|
|
6444
|
+
settle: () => pending
|
|
6445
|
+
};
|
|
6446
|
+
}
|
|
6447
|
+
//#endregion
|
|
6448
|
+
//#region src/modules/panels/model/diy-debug-routes.ts
|
|
6449
|
+
function diyDebugRoutes(store) {
|
|
6450
|
+
return { "panels/diy-debug": () => ({
|
|
6451
|
+
ok: true,
|
|
6452
|
+
state: store.live(Date.now())
|
|
6453
|
+
}) };
|
|
6454
|
+
}
|
|
6455
|
+
//#endregion
|
|
6456
|
+
//#region src/modules/panels/model/diy-record-routes.ts
|
|
6457
|
+
function diyRecordRoutes(store) {
|
|
6458
|
+
return { "panels/diy-record": () => ({
|
|
6459
|
+
ok: true,
|
|
6460
|
+
state: store.live(Date.now())
|
|
6461
|
+
}) };
|
|
6462
|
+
}
|
|
6463
|
+
//#endregion
|
|
6464
|
+
//#region src/shared/api/host/diy-debug-act.ts
|
|
6465
|
+
var DEBUG_NOTICE_KIND = "workbench-diy";
|
|
6466
|
+
function noticeMessage(text, summary, kind = DEBUG_NOTICE_KIND) {
|
|
6467
|
+
return {
|
|
6468
|
+
role: "user",
|
|
6469
|
+
id: randomUUID(),
|
|
6470
|
+
content: [{
|
|
6471
|
+
type: "text",
|
|
6472
|
+
text
|
|
6473
|
+
}],
|
|
6474
|
+
source: {
|
|
6475
|
+
kind,
|
|
6476
|
+
form: "notice",
|
|
6477
|
+
summary
|
|
6478
|
+
}
|
|
6479
|
+
};
|
|
6480
|
+
}
|
|
6481
|
+
function isNoticeMessage(value) {
|
|
6482
|
+
if (typeof value !== "object" || value === null) return false;
|
|
6483
|
+
const one = value;
|
|
6484
|
+
const source = one.source;
|
|
6485
|
+
return one.role === "user" && typeof one.id === "string" && Array.isArray(one.content) && typeof source === "object" && source !== null && typeof source.kind === "string";
|
|
6486
|
+
}
|
|
6487
|
+
function requireNoticeMessage(value) {
|
|
6488
|
+
if (!isNoticeMessage(value)) throw new Error("notice 得是 dsh 的 UserMessage 形状(role / id / content / source.kind)—— 裸字符串会写坏会话日志(agent/inbox/spliced requires message objects)");
|
|
6489
|
+
return value;
|
|
6490
|
+
}
|
|
6491
|
+
var INTENT_WORD$1 = {
|
|
6492
|
+
pause: "暂停",
|
|
6493
|
+
resume: "继续",
|
|
6494
|
+
stop: "结束"
|
|
6495
|
+
};
|
|
6496
|
+
function debugNoticeText(intent, state) {
|
|
6497
|
+
const head = `[工作台 DIY]用户按了「${INTENT_WORD$1[intent]}」:`;
|
|
6498
|
+
if (intent === "pause") return `${head}${state.unit} 这场调试暂停了 —— 现在别再把那一屏当目标(看 / 动 / 网络那一族发不出去)。要接着看屏就等用户按「继续」;在那之前照常改源码 / build / 跑门禁。`;
|
|
6499
|
+
if (intent === "resume") return `${head}${state.unit} 这场调试重新开始了 —— 那一屏又归你看。屏 / 板不对的话照旧会被拒,拒了就照实说清是哪种不对。`;
|
|
6500
|
+
return `${head}${state.unit} 这场调试收工了(占用解除)。要再调试就重新 start 一场。`;
|
|
6501
|
+
}
|
|
6502
|
+
function debugNoticeSummary(intent, state) {
|
|
6503
|
+
return `工作台 DIY:用户按了${INTENT_WORD$1[intent]}(${state.unit})`;
|
|
6504
|
+
}
|
|
6505
|
+
function debugNotice(intent, state) {
|
|
6506
|
+
const summary = debugNoticeSummary(intent, state);
|
|
6507
|
+
return {
|
|
6508
|
+
summary,
|
|
6509
|
+
message: noticeMessage(debugNoticeText(intent, state), summary)
|
|
6510
|
+
};
|
|
6511
|
+
}
|
|
6512
|
+
var NOTICE_MODE$1 = {
|
|
6513
|
+
resume: "wake",
|
|
6514
|
+
pause: "hold",
|
|
6515
|
+
stop: "drop"
|
|
6516
|
+
};
|
|
6517
|
+
var held = /* @__PURE__ */ new Map();
|
|
6518
|
+
function holdNotice(sessionId, message) {
|
|
6519
|
+
const checked = requireNoticeMessage(message);
|
|
6520
|
+
const list = held.get(sessionId);
|
|
6521
|
+
if (list === void 0) held.set(sessionId, [checked]);
|
|
6522
|
+
else list.push(checked);
|
|
6523
|
+
}
|
|
6524
|
+
function takeHeldNotices(sessionId) {
|
|
6525
|
+
const list = held.get(sessionId) ?? [];
|
|
6526
|
+
held.delete(sessionId);
|
|
6527
|
+
return list;
|
|
6528
|
+
}
|
|
6529
|
+
function deliverDebugNotice(agents, sessionId, message, mode = "wake") {
|
|
6530
|
+
const notice = requireNoticeMessage(message);
|
|
6531
|
+
const agent = agents?.get(sessionId);
|
|
6532
|
+
if (agent === void 0) {
|
|
6533
|
+
if (mode === "drop") return "none";
|
|
6534
|
+
holdNotice(sessionId, notice);
|
|
6535
|
+
return "held";
|
|
6536
|
+
}
|
|
6537
|
+
if (agent.status !== "idle") {
|
|
6538
|
+
agent.inject(notice);
|
|
6539
|
+
return "inject";
|
|
6540
|
+
}
|
|
6541
|
+
if (mode === "wake") {
|
|
6542
|
+
agent.followup(notice);
|
|
6543
|
+
return "followup";
|
|
6544
|
+
}
|
|
6545
|
+
if (mode === "hold") {
|
|
6546
|
+
holdNotice(sessionId, notice);
|
|
6547
|
+
return "held";
|
|
6548
|
+
}
|
|
6549
|
+
return "skipped-idle";
|
|
6550
|
+
}
|
|
6551
|
+
var REFUSAL$1 = {
|
|
6552
|
+
"already-paused": "这一场已经是暂停态了",
|
|
6553
|
+
"already-debugging": "这一场本来就在调试中",
|
|
6554
|
+
none: "现在没有调试态",
|
|
6555
|
+
"not-owner": "这场调试不是这个会话的"
|
|
6556
|
+
};
|
|
6557
|
+
function debugActResource(deps) {
|
|
6558
|
+
const now = () => deps.now?.() ?? Date.now();
|
|
6559
|
+
const settled = (intent, delivered, summary, state) => ({
|
|
6560
|
+
ok: true,
|
|
6561
|
+
intent,
|
|
6562
|
+
delivered,
|
|
6563
|
+
summary,
|
|
6564
|
+
state
|
|
6565
|
+
});
|
|
6566
|
+
return {
|
|
6567
|
+
resource: "act",
|
|
6568
|
+
handle(payload) {
|
|
6569
|
+
const intent = parseDebugIntent(payload?.intent);
|
|
6570
|
+
if (intent === null) return {
|
|
6571
|
+
ok: false,
|
|
6572
|
+
text: "不认识的意图(只认 pause / resume / stop)"
|
|
6573
|
+
};
|
|
6574
|
+
const store = deps.store();
|
|
6575
|
+
if (store === void 0) return {
|
|
6576
|
+
ok: false,
|
|
6577
|
+
text: "现在没有调试态 —— 没什么可暂停 / 继续 / 结束的"
|
|
6578
|
+
};
|
|
6579
|
+
const at = now();
|
|
6580
|
+
const held = store.live(at);
|
|
6581
|
+
if (held === null) return {
|
|
6582
|
+
ok: false,
|
|
6583
|
+
text: "现在没有调试态 —— 没什么可暂停 / 继续 / 结束的"
|
|
6584
|
+
};
|
|
6585
|
+
const move = intent === "pause" ? store.pause(held.sessionId, at) : intent === "resume" ? store.resume(held.sessionId, at) : store.stop(held.sessionId, at);
|
|
6586
|
+
if (!move.ok) return {
|
|
6587
|
+
ok: false,
|
|
6588
|
+
text: REFUSAL$1[move.code] ?? `按不动:${move.code}`
|
|
6589
|
+
};
|
|
6590
|
+
const notice = deps.notice?.(intent, move.state) ?? debugNotice(intent, move.state);
|
|
6591
|
+
const delivered = deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE$1[intent]);
|
|
6592
|
+
if (intent !== "stop") return settled(intent, delivered, notice.summary, store.live(now()));
|
|
6593
|
+
return (store.settle?.() ?? Promise.resolve()).then(() => settled(intent, delivered, notice.summary, store.live(now())));
|
|
6594
|
+
}
|
|
6595
|
+
};
|
|
6596
|
+
}
|
|
6597
|
+
//#endregion
|
|
6598
|
+
//#region src/modules/panels/model/diy-record-scripts.ts
|
|
6599
|
+
var RECORD_KEY = "__dshDiyRecord";
|
|
6600
|
+
var RECORD_KEEP_KEY = "__dshDiyRecordKeep";
|
|
6601
|
+
var RECORD_OFF_KEY = "__dshDiyRecordOff";
|
|
6602
|
+
var RECORD_ARM_SCRIPT = `(() => {
|
|
6603
|
+
const KEY = ${JSON.stringify(RECORD_KEY)};
|
|
6604
|
+
const KEEP = ${JSON.stringify(RECORD_KEEP_KEY)};
|
|
6605
|
+
const OFF = ${JSON.stringify(RECORD_OFF_KEY)};
|
|
6606
|
+
const CAP = ${String(50)};
|
|
6607
|
+
const BODY_MAX = ${String(16384)};
|
|
6608
|
+
const blank = () => ({ phase: 'armed', t0: null, t1: null, href: location.href, dropped: 0, calls: [] });
|
|
6609
|
+
const readKeep = () => {
|
|
6610
|
+
try {
|
|
6611
|
+
const raw = sessionStorage.getItem(KEEP);
|
|
6612
|
+
const parsed = raw === null ? null : JSON.parse(raw);
|
|
6613
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : blank();
|
|
6614
|
+
} catch (error) {
|
|
6615
|
+
return blank();
|
|
6616
|
+
}
|
|
6617
|
+
};
|
|
6618
|
+
const state = window[KEY] !== null && typeof window[KEY] === 'object' ? window[KEY] : readKeep();
|
|
6619
|
+
state.calls = Array.isArray(state.calls) ? state.calls : [];
|
|
6620
|
+
state.phase = typeof state.phase === 'string' ? state.phase : 'armed';
|
|
6621
|
+
window[KEY] = state;
|
|
6622
|
+
const keep = () => {
|
|
6623
|
+
try {
|
|
6624
|
+
sessionStorage.setItem(KEEP, JSON.stringify(state));
|
|
6625
|
+
} catch (error) {}
|
|
6626
|
+
};
|
|
6627
|
+
const slice = (value) => {
|
|
6628
|
+
try {
|
|
6629
|
+
if (value === undefined || value === null || value === '') return '';
|
|
6630
|
+
if (typeof value === 'string') return value.slice(0, BODY_MAX);
|
|
6631
|
+
if (typeof FormData === 'function' && value instanceof FormData) return '[form-data]';
|
|
6632
|
+
if (typeof Blob === 'function' && value instanceof Blob) return '[blob ' + String(value.size) + ']';
|
|
6633
|
+
if (typeof URLSearchParams === 'function' && value instanceof URLSearchParams) return String(value).slice(0, BODY_MAX);
|
|
6634
|
+
const json = JSON.stringify(value);
|
|
6635
|
+
return typeof json === 'string' ? json.slice(0, BODY_MAX) : '';
|
|
6636
|
+
} catch (error) {
|
|
6637
|
+
return '[unprintable]';
|
|
6638
|
+
}
|
|
6639
|
+
};
|
|
6640
|
+
const repaint = () => {
|
|
6641
|
+
if (typeof state.__repaint === 'function') state.__repaint();
|
|
6642
|
+
};
|
|
6643
|
+
const record = (method, url, body, status, ct, response) => {
|
|
6644
|
+
if (state.phase !== 'recording') return;
|
|
6645
|
+
state.calls.push({
|
|
6646
|
+
method: String(method === '' || method === undefined ? 'GET' : method).toUpperCase(),
|
|
6647
|
+
url: String(url === undefined || url === null ? '' : url),
|
|
6648
|
+
body: slice(body === undefined || body === null ? '' : body),
|
|
6649
|
+
status: typeof status === 'number' ? status : 0,
|
|
6650
|
+
ct: String(ct === undefined || ct === null ? '' : ct),
|
|
6651
|
+
response: slice(response === undefined || response === null ? '' : response),
|
|
6652
|
+
at: Date.now(),
|
|
6653
|
+
});
|
|
6654
|
+
if (state.calls.length > CAP) {
|
|
6655
|
+
state.dropped = (typeof state.dropped === 'number' ? state.dropped : 0) + (state.calls.length - CAP);
|
|
6656
|
+
state.calls.splice(0, state.calls.length - CAP);
|
|
6657
|
+
}
|
|
6658
|
+
keep();
|
|
6659
|
+
repaint();
|
|
6660
|
+
};
|
|
6661
|
+
const fresh = typeof window[OFF] !== 'function';
|
|
6662
|
+
if (fresh) {
|
|
6663
|
+
const realFetch = window.fetch;
|
|
6664
|
+
const proto = XMLHttpRequest.prototype;
|
|
6665
|
+
const realOpen = proto.open;
|
|
6666
|
+
const realSend = proto.send;
|
|
6667
|
+
const realBeacon = typeof navigator === 'object' && navigator !== null ? navigator.sendBeacon : undefined;
|
|
6668
|
+
const hasBeacon = typeof realBeacon === 'function';
|
|
6669
|
+
const ctOf = (xhr) => {
|
|
6670
|
+
try {
|
|
6671
|
+
return typeof xhr.getResponseHeader === 'function' ? xhr.getResponseHeader('content-type') || '' : '';
|
|
6672
|
+
} catch (error) {
|
|
6673
|
+
return '';
|
|
6674
|
+
}
|
|
6675
|
+
};
|
|
6676
|
+
const textOf = (xhr) => {
|
|
6677
|
+
try {
|
|
6678
|
+
return typeof xhr.responseText === 'string' ? xhr.responseText : '';
|
|
6679
|
+
} catch (error) {
|
|
6680
|
+
return '';
|
|
6681
|
+
}
|
|
6682
|
+
};
|
|
6683
|
+
window.fetch = function () {
|
|
6684
|
+
const args = arguments;
|
|
6685
|
+
const init = args[1] || {};
|
|
6686
|
+
const asked = args[0];
|
|
6687
|
+
const url = typeof asked === 'string' ? asked : asked !== null && typeof asked === 'object' ? asked.url : '';
|
|
6688
|
+
const method = init.method !== undefined ? init.method : asked !== null && typeof asked === 'object' ? asked.method : 'GET';
|
|
6689
|
+
const body = init.body === undefined ? '' : init.body;
|
|
6690
|
+
const done = realFetch.apply(this, args);
|
|
6691
|
+
try {
|
|
6692
|
+
done.then((response) => {
|
|
6693
|
+
try {
|
|
6694
|
+
const ct = response.headers.get('content-type') || '';
|
|
6695
|
+
const clone = response.clone();
|
|
6696
|
+
const target = response.url || url;
|
|
6697
|
+
clone.text().then((text) => record(method, target, body, response.status, ct, text)).catch(() => record(method, target, body, response.status, ct, ''));
|
|
6698
|
+
} catch (error) {}
|
|
6699
|
+
}).catch((error) => {
|
|
6700
|
+
try {
|
|
6701
|
+
record(method, url, body, 0, 'fetch-error', String(error !== null && typeof error === 'object' && error.message !== undefined ? error.message : error));
|
|
6702
|
+
} catch (inner) {}
|
|
6703
|
+
});
|
|
6704
|
+
} catch (error) {}
|
|
6705
|
+
return done;
|
|
6706
|
+
};
|
|
6707
|
+
proto.open = function (method, url) {
|
|
6708
|
+
this.__dshMethod = method;
|
|
6709
|
+
this.__dshUrl = url;
|
|
6710
|
+
return realOpen.apply(this, arguments);
|
|
6711
|
+
};
|
|
6712
|
+
proto.send = function (body) {
|
|
6713
|
+
const xhr = this;
|
|
6714
|
+
if (typeof xhr.__dshLoadEnd === 'function' && typeof xhr.removeEventListener === 'function') {
|
|
6715
|
+
try {
|
|
6716
|
+
xhr.removeEventListener('loadend', xhr.__dshLoadEnd);
|
|
6717
|
+
} catch (error) {}
|
|
6718
|
+
}
|
|
6719
|
+
const onEnd = () => {
|
|
6720
|
+
try {
|
|
6721
|
+
record(xhr.__dshMethod, xhr.__dshUrl, body, xhr.status, ctOf(xhr), textOf(xhr));
|
|
6722
|
+
} catch (error) {}
|
|
6723
|
+
};
|
|
6724
|
+
xhr.__dshLoadEnd = onEnd;
|
|
6725
|
+
xhr.addEventListener('loadend', onEnd);
|
|
6726
|
+
return realSend.apply(this, arguments);
|
|
6727
|
+
};
|
|
6728
|
+
if (hasBeacon) {
|
|
6729
|
+
navigator.sendBeacon = function (url, data) {
|
|
6730
|
+
try {
|
|
6731
|
+
record('POST', url, data === undefined || data === null ? '' : data, 0, 'beacon', '');
|
|
6732
|
+
} catch (error) {}
|
|
6733
|
+
return realBeacon.apply(navigator, arguments);
|
|
6734
|
+
};
|
|
6735
|
+
}
|
|
6736
|
+
const onResize = () => {
|
|
6737
|
+
if (typeof state.barX === 'number' && typeof state.barY === 'number') place(state.barX, state.barY);
|
|
6738
|
+
};
|
|
6739
|
+
window.addEventListener('resize', onResize);
|
|
6740
|
+
window[OFF] = () => {
|
|
6741
|
+
window.fetch = realFetch;
|
|
6742
|
+
proto.open = realOpen;
|
|
6743
|
+
proto.send = realSend;
|
|
6744
|
+
if (hasBeacon) {
|
|
6745
|
+
try {
|
|
6746
|
+
navigator.sendBeacon = realBeacon;
|
|
6747
|
+
} catch (error) {}
|
|
6748
|
+
}
|
|
6749
|
+
if (typeof window.removeEventListener === 'function') {
|
|
6750
|
+
try {
|
|
6751
|
+
window.removeEventListener('resize', onResize);
|
|
6752
|
+
} catch (error) {}
|
|
6753
|
+
}
|
|
6754
|
+
};
|
|
6755
|
+
}
|
|
6756
|
+
let bar = document.getElementById('__dsh-record-bar');
|
|
6757
|
+
const freshBar = bar === null;
|
|
6758
|
+
if (freshBar) {
|
|
6759
|
+
bar = document.createElement('div');
|
|
6760
|
+
bar.id = '__dsh-record-bar';
|
|
6761
|
+
document.documentElement.appendChild(bar);
|
|
6762
|
+
}
|
|
6763
|
+
bar.setAttribute('style', 'position:fixed;z-index:2147483647;right:16px;bottom:16px;display:flex;gap:8px;align-items:center;padding:8px 10px;border-radius:10px;background:#1f2430;color:#fff;box-shadow:0 6px 24px rgba(0,0,0,.35);font:12px/1.5 -apple-system,system-ui,sans-serif');
|
|
6764
|
+
let sheet = document.getElementById('__dsh-record-bar-style');
|
|
6765
|
+
if (sheet === null) {
|
|
6766
|
+
sheet = document.createElement('style');
|
|
6767
|
+
sheet.id = '__dsh-record-bar-style';
|
|
6768
|
+
sheet.textContent = '@keyframes __dshRecordBreath{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.25;transform:scale(.7)}}#__dsh-record-bar .__dsh-record-dot{animation:__dshRecordBreath 1.1s ease-in-out infinite}';
|
|
6769
|
+
const sheetHost = document.head === null || document.head === undefined ? document.documentElement : document.head;
|
|
6770
|
+
sheetHost.appendChild(sheet);
|
|
6771
|
+
}
|
|
6772
|
+
const place = (x, y, width, height) => {
|
|
6773
|
+
const rect = bar.getBoundingClientRect();
|
|
6774
|
+
const w = typeof width === 'number' ? width : rect.width;
|
|
6775
|
+
const h = typeof height === 'number' ? height : rect.height;
|
|
6776
|
+
const maxX = Math.max(0, (window.innerWidth || 0) - w);
|
|
6777
|
+
const maxY = Math.max(0, (window.innerHeight || 0) - h);
|
|
6778
|
+
const left = Math.min(Math.max(0, x), maxX);
|
|
6779
|
+
const top = Math.min(Math.max(0, y), maxY);
|
|
6780
|
+
state.barX = left;
|
|
6781
|
+
state.barY = top;
|
|
6782
|
+
bar.style.right = 'auto';
|
|
6783
|
+
bar.style.bottom = 'auto';
|
|
6784
|
+
bar.style.left = String(left) + 'px';
|
|
6785
|
+
bar.style.top = String(top) + 'px';
|
|
6786
|
+
};
|
|
6787
|
+
const grip = document.createElement('span');
|
|
6788
|
+
grip.setAttribute('role', 'button');
|
|
6789
|
+
grip.setAttribute('tabindex', '0');
|
|
6790
|
+
grip.setAttribute('title', '拖动浮条');
|
|
6791
|
+
grip.setAttribute('aria-label', '拖动浮条');
|
|
6792
|
+
grip.setAttribute('style', 'flex:0 0 auto;width:12px;height:18px;border-radius:3px;cursor:grab;touch-action:none;background-image:radial-gradient(circle,rgba(255,255,255,.5) 1.2px,transparent 1.3px);background-size:6px 6px;background-position:center center');
|
|
6793
|
+
const dot = document.createElement('span');
|
|
6794
|
+
dot.className = '__dsh-record-dot';
|
|
6795
|
+
dot.setAttribute('aria-hidden', 'true');
|
|
6796
|
+
dot.setAttribute('style', 'flex:0 0 auto;display:none;width:8px;height:8px;border-radius:999px;corner-shape:round;background:#ef4444');
|
|
6797
|
+
const label = document.createElement('span');
|
|
6798
|
+
label.setAttribute('style', 'flex:0 0 auto;white-space:nowrap;font-variant-numeric:tabular-nums');
|
|
6799
|
+
const start = document.createElement('button');
|
|
6800
|
+
const end = document.createElement('button');
|
|
6801
|
+
start.type = 'button';
|
|
6802
|
+
start.setAttribute('title', '开始录制');
|
|
6803
|
+
start.setAttribute('aria-label', '开始录制');
|
|
6804
|
+
start.innerHTML = '<svg width="13" height="13" viewBox="0 0 16 16" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="1.5" y="4" width="9" height="8" rx="2"></rect><path d="M10.5 7.4 14.5 5v6l-4-2.4z"></path></svg>开始';
|
|
6805
|
+
end.type = 'button';
|
|
6806
|
+
end.setAttribute('title', '结束录制');
|
|
6807
|
+
end.setAttribute('aria-label', '结束录制');
|
|
6808
|
+
end.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true" focusable="false"><rect x="2" y="2" width="8" height="8" rx="1.5" fill="currentColor"></rect></svg>结束';
|
|
6809
|
+
const look = (on) => 'display:inline-flex;align-items:center;gap:4px;height:24px;padding:0 8px;border:0;border-radius:6px;font-size:12px;line-height:1;font-family:inherit;white-space:nowrap;cursor:' + (on ? 'pointer' : 'default') + ';color:#fff;background:' + (on ? '#3b82f6' : '#334155');
|
|
6810
|
+
const paint = () => {
|
|
6811
|
+
const phase = state.phase;
|
|
6812
|
+
label.textContent = (phase === 'recording' ? '录制中 · ' : phase === 'finished' ? '录制完成 · ' : '录制 · 等开始 · ') + String(state.calls.length) + ' 条';
|
|
6813
|
+
dot.style.display = phase === 'recording' ? 'inline-block' : 'none';
|
|
6814
|
+
start.disabled = phase !== 'armed';
|
|
6815
|
+
end.disabled = phase !== 'recording';
|
|
6816
|
+
start.setAttribute('style', look(phase === 'armed'));
|
|
6817
|
+
end.setAttribute('style', look(phase === 'recording'));
|
|
6818
|
+
};
|
|
6819
|
+
state.__repaint = paint;
|
|
6820
|
+
const drag = { on: false, px: 0, py: 0, left: 0, top: 0, w: 0, h: 0 };
|
|
6821
|
+
const drop = () => {
|
|
6822
|
+
if (!drag.on) return;
|
|
6823
|
+
drag.on = false;
|
|
6824
|
+
grip.style.cursor = 'grab';
|
|
6825
|
+
keep();
|
|
6826
|
+
};
|
|
6827
|
+
grip.onpointerdown = (event) => {
|
|
6828
|
+
const rect = bar.getBoundingClientRect();
|
|
6829
|
+
drag.on = true;
|
|
6830
|
+
drag.px = event.clientX;
|
|
6831
|
+
drag.py = event.clientY;
|
|
6832
|
+
drag.left = rect.left;
|
|
6833
|
+
drag.top = rect.top;
|
|
6834
|
+
drag.w = rect.width;
|
|
6835
|
+
drag.h = rect.height;
|
|
6836
|
+
grip.style.cursor = 'grabbing';
|
|
6837
|
+
if (typeof grip.setPointerCapture === 'function') {
|
|
6838
|
+
try {
|
|
6839
|
+
grip.setPointerCapture(event.pointerId);
|
|
6840
|
+
} catch (error) {}
|
|
6841
|
+
}
|
|
6842
|
+
if (typeof event.preventDefault === 'function') event.preventDefault();
|
|
6843
|
+
};
|
|
6844
|
+
grip.onpointermove = (event) => {
|
|
6845
|
+
if (!drag.on) return;
|
|
6846
|
+
place(drag.left + event.clientX - drag.px, drag.top + event.clientY - drag.py, drag.w, drag.h);
|
|
6847
|
+
};
|
|
6848
|
+
grip.onpointerup = drop;
|
|
6849
|
+
grip.onpointercancel = drop;
|
|
6850
|
+
grip.onkeydown = (event) => {
|
|
6851
|
+
const step = event.shiftKey ? 40 : 16;
|
|
6852
|
+
const rect = bar.getBoundingClientRect();
|
|
6853
|
+
const left = event.key === 'ArrowLeft' ? rect.left - step : event.key === 'ArrowRight' ? rect.left + step : rect.left;
|
|
6854
|
+
const top = event.key === 'ArrowUp' ? rect.top - step : event.key === 'ArrowDown' ? rect.top + step : rect.top;
|
|
6855
|
+
if (left === rect.left && top === rect.top) return;
|
|
6856
|
+
place(left, top);
|
|
6857
|
+
keep();
|
|
6858
|
+
if (typeof event.preventDefault === 'function') event.preventDefault();
|
|
6859
|
+
};
|
|
6860
|
+
start.onclick = () => {
|
|
6861
|
+
if (state.phase !== 'armed') return;
|
|
6862
|
+
state.phase = 'recording';
|
|
6863
|
+
state.t0 = Date.now();
|
|
6864
|
+
state.t1 = null;
|
|
6865
|
+
state.href = location.href;
|
|
6866
|
+
state.calls = [];
|
|
6867
|
+
state.dropped = 0;
|
|
6868
|
+
keep();
|
|
6869
|
+
paint();
|
|
6870
|
+
};
|
|
6871
|
+
end.onclick = () => {
|
|
6872
|
+
state.phase = 'finished';
|
|
6873
|
+
state.t1 = Date.now();
|
|
6874
|
+
keep();
|
|
6875
|
+
paint();
|
|
6876
|
+
};
|
|
6877
|
+
bar.textContent = '';
|
|
6878
|
+
bar.appendChild(grip);
|
|
6879
|
+
bar.appendChild(dot);
|
|
6880
|
+
bar.appendChild(label);
|
|
6881
|
+
bar.appendChild(start);
|
|
6882
|
+
bar.appendChild(end);
|
|
6883
|
+
if (typeof state.barX === 'number' && typeof state.barY === 'number') place(state.barX, state.barY);
|
|
6884
|
+
paint();
|
|
6885
|
+
return { ok: true, fresh, phase: state.phase, t0: state.t0 === undefined ? null : state.t0, t1: state.t1 === undefined ? null : state.t1, href: state.href || location.href, size: state.calls.length, dropped: typeof state.dropped === 'number' ? state.dropped : 0 };
|
|
6886
|
+
})()`;
|
|
6887
|
+
var RECORD_READ_SCRIPT = `(() => {
|
|
6888
|
+
const KEY = ${JSON.stringify(RECORD_KEY)};
|
|
6889
|
+
const KEEP = ${JSON.stringify(RECORD_KEEP_KEY)};
|
|
6890
|
+
let state = window[KEY] !== null && typeof window[KEY] === 'object' ? window[KEY] : null;
|
|
6891
|
+
if (state === null) {
|
|
6892
|
+
try {
|
|
6893
|
+
const raw = sessionStorage.getItem(KEEP);
|
|
6894
|
+
state = raw === null ? null : JSON.parse(raw);
|
|
6895
|
+
} catch (error) {
|
|
6896
|
+
state = null;
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
6899
|
+
if (state === null || typeof state !== 'object') return { ok: false, code: 'not-armed', phase: 'none', calls: [] };
|
|
6900
|
+
return { ok: true, phase: typeof state.phase === 'string' ? state.phase : 'armed', t0: state.t0 === undefined ? null : state.t0, t1: state.t1 === undefined ? null : state.t1, href: typeof state.href === 'string' ? state.href : '', here: location.href, dropped: typeof state.dropped === 'number' ? state.dropped : 0, calls: Array.isArray(state.calls) ? state.calls : [] };
|
|
6901
|
+
})()`;
|
|
6902
|
+
var RECORD_STOP_SCRIPT = `(() => {
|
|
6903
|
+
const KEY = ${JSON.stringify(RECORD_KEY)};
|
|
6904
|
+
const KEEP = ${JSON.stringify(RECORD_KEEP_KEY)};
|
|
6905
|
+
const OFF = ${JSON.stringify(RECORD_OFF_KEY)};
|
|
6906
|
+
let state = window[KEY] !== null && typeof window[KEY] === 'object' ? window[KEY] : null;
|
|
6907
|
+
if (state === null) {
|
|
6908
|
+
try {
|
|
6909
|
+
const raw = sessionStorage.getItem(KEEP);
|
|
6910
|
+
state = raw === null ? null : JSON.parse(raw);
|
|
6911
|
+
} catch (error) {
|
|
6912
|
+
state = null;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
let unhooked = 0;
|
|
6916
|
+
if (typeof window[OFF] === 'function') {
|
|
6917
|
+
try {
|
|
6918
|
+
window[OFF]();
|
|
6919
|
+
unhooked = 1;
|
|
6920
|
+
} catch (error) {}
|
|
6921
|
+
}
|
|
6922
|
+
const bar = document.getElementById('__dsh-record-bar');
|
|
6923
|
+
if (bar !== null) bar.remove();
|
|
6924
|
+
const sheet = document.getElementById('__dsh-record-bar-style');
|
|
6925
|
+
if (sheet !== null) sheet.remove();
|
|
6926
|
+
try {
|
|
6927
|
+
delete window[KEY];
|
|
6928
|
+
} catch (error) {
|
|
6929
|
+
window[KEY] = undefined;
|
|
6930
|
+
}
|
|
6931
|
+
try {
|
|
6932
|
+
delete window[OFF];
|
|
6933
|
+
} catch (error) {
|
|
6934
|
+
window[OFF] = undefined;
|
|
6935
|
+
}
|
|
6936
|
+
try {
|
|
6937
|
+
sessionStorage.removeItem(KEEP);
|
|
6938
|
+
} catch (error) {}
|
|
6939
|
+
return { ok: true, unhooked, phase: state !== null && typeof state === 'object' ? state.phase : 'none', t0: state !== null && typeof state === 'object' && state.t0 !== undefined ? state.t0 : null, t1: state !== null && typeof state === 'object' && state.t1 !== undefined ? state.t1 : null, calls: state !== null && typeof state === 'object' && Array.isArray(state.calls) ? state.calls : [], dropped: state !== null && typeof state === 'object' && typeof state.dropped === 'number' ? state.dropped : 0 };
|
|
6940
|
+
})()`;
|
|
6941
|
+
//#endregion
|
|
6942
|
+
//#region src/modules/panels/model/diy-record-capture.ts
|
|
6943
|
+
var SEGMENT_SLACK_MS = 1500;
|
|
6944
|
+
function parsedOf(line) {
|
|
6945
|
+
const trimmed = line.trim();
|
|
6946
|
+
if (trimmed === "") return null;
|
|
6947
|
+
let parsed;
|
|
6948
|
+
try {
|
|
6949
|
+
parsed = JSON.parse(trimmed);
|
|
6950
|
+
} catch {
|
|
6951
|
+
return null;
|
|
6952
|
+
}
|
|
6953
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
|
|
6954
|
+
}
|
|
6955
|
+
function plain$1(value) {
|
|
6956
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
6957
|
+
}
|
|
6958
|
+
function bodyTextOf(value) {
|
|
6959
|
+
if (typeof value === "string") return value;
|
|
6960
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
6961
|
+
if (typeof value === "object" && value !== null) try {
|
|
6962
|
+
return JSON.stringify(value);
|
|
6963
|
+
} catch {
|
|
6964
|
+
return "";
|
|
6965
|
+
}
|
|
6966
|
+
return "";
|
|
6967
|
+
}
|
|
6968
|
+
function sizeOf(value) {
|
|
6969
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
6970
|
+
}
|
|
6971
|
+
function entryOf(row) {
|
|
6972
|
+
const url = row["url"];
|
|
6973
|
+
const method = row["method"];
|
|
6974
|
+
if (typeof url !== "string" || url === "") return null;
|
|
6975
|
+
if (typeof method !== "string" || method === "") return null;
|
|
6976
|
+
const stamp = typeof row["timestamp"] === "string" ? Date.parse(row["timestamp"]) : NaN;
|
|
6977
|
+
const request = plain$1(row["request"]);
|
|
6978
|
+
const requestBody = bodyTextOf(request["body"]);
|
|
6979
|
+
const responseBody = bodyTextOf(row["body"]);
|
|
6980
|
+
const requestFull = sizeOf(request["body_full_size"]);
|
|
6981
|
+
const responseFull = sizeOf(row["body_full_size"]);
|
|
6982
|
+
return {
|
|
6983
|
+
key: typeof row["key"] === "string" ? row["key"] : "",
|
|
6984
|
+
at: Number.isFinite(stamp) ? stamp : 0,
|
|
6985
|
+
method: method.toUpperCase(),
|
|
6986
|
+
url,
|
|
6987
|
+
status: sizeOf(row["status"]),
|
|
6988
|
+
ct: typeof row["ct"] === "string" ? row["ct"] : "",
|
|
6989
|
+
size: sizeOf(row["size"]),
|
|
6990
|
+
request: requestBody,
|
|
6991
|
+
response: responseBody,
|
|
6992
|
+
requestFullSize: requestFull || requestBody.length,
|
|
6993
|
+
responseFullSize: responseFull || responseBody.length,
|
|
6994
|
+
requestSizeExact: requestFull > 0,
|
|
6995
|
+
responseSizeExact: responseFull > 0,
|
|
6996
|
+
headers: Object.keys(request["headers"] === void 0 ? {} : plain$1(request["headers"])).length === 0 ? "" : bodyTextOf(request["headers"])
|
|
6997
|
+
};
|
|
6998
|
+
}
|
|
6999
|
+
function richerOf(left, right) {
|
|
7000
|
+
const leftScore = (left.status === 0 ? 0 : 1) + (left.response === "" ? 0 : 1) + (left.ct === "" ? 0 : 1);
|
|
7001
|
+
const best = (right.status === 0 ? 0 : 1) + (right.response === "" ? 0 : 1) + (right.ct === "" ? 0 : 1) > leftScore ? right : left;
|
|
7002
|
+
const other = best === left ? right : left;
|
|
7003
|
+
return {
|
|
7004
|
+
...best,
|
|
7005
|
+
request: best.request === "" ? other.request : best.request,
|
|
7006
|
+
requestFullSize: best.request === "" ? other.requestFullSize : best.requestFullSize,
|
|
7007
|
+
requestSizeExact: best.requestSizeExact || other.requestSizeExact,
|
|
7008
|
+
response: best.response === "" ? other.response : best.response,
|
|
7009
|
+
responseFullSize: best.response === "" ? other.responseFullSize : best.responseFullSize,
|
|
7010
|
+
responseSizeExact: best.responseSizeExact || other.responseSizeExact,
|
|
7011
|
+
headers: best.headers === "" ? other.headers : best.headers
|
|
7012
|
+
};
|
|
7013
|
+
}
|
|
7014
|
+
function captureEntriesOf(text) {
|
|
7015
|
+
const out = [];
|
|
7016
|
+
const at = /* @__PURE__ */ new Map();
|
|
7017
|
+
const push = (one) => {
|
|
7018
|
+
if (one === null) return;
|
|
7019
|
+
const id = one.key === "" ? `${one.method} ${one.url}@${String(one.at)}` : `${one.key}@${String(one.at)}`;
|
|
7020
|
+
const found = at.get(id);
|
|
7021
|
+
if (found === void 0) {
|
|
7022
|
+
at.set(id, out.length);
|
|
7023
|
+
out.push(one);
|
|
7024
|
+
return;
|
|
7025
|
+
}
|
|
7026
|
+
const kept = out[found];
|
|
7027
|
+
if (kept !== void 0) out[found] = richerOf(kept, one);
|
|
7028
|
+
};
|
|
7029
|
+
for (const line of text.split("\n")) {
|
|
7030
|
+
const row = parsedOf(line);
|
|
7031
|
+
if (row === null) continue;
|
|
7032
|
+
const inside = row["entries"];
|
|
7033
|
+
if (Array.isArray(inside)) {
|
|
7034
|
+
for (const one of inside) push(entryOf(plain$1(one)));
|
|
7035
|
+
continue;
|
|
7036
|
+
}
|
|
7037
|
+
push(entryOf(row));
|
|
7038
|
+
}
|
|
7039
|
+
return out;
|
|
7040
|
+
}
|
|
7041
|
+
function segmentOf(entries, t0, t1) {
|
|
7042
|
+
if (t0 === null || t1 === null) return {
|
|
7043
|
+
inside: entries,
|
|
7044
|
+
outside: 0
|
|
7045
|
+
};
|
|
7046
|
+
const from = t0 - SEGMENT_SLACK_MS;
|
|
7047
|
+
const to = t1 + SEGMENT_SLACK_MS;
|
|
7048
|
+
const inside = [];
|
|
7049
|
+
let outside = 0;
|
|
7050
|
+
for (const one of entries) if (one.at === 0 || one.at >= from && one.at <= to) inside.push(one);
|
|
7051
|
+
else outside += 1;
|
|
7052
|
+
return {
|
|
7053
|
+
inside,
|
|
7054
|
+
outside
|
|
7055
|
+
};
|
|
7056
|
+
}
|
|
7057
|
+
function captureCallsOf(entries) {
|
|
7058
|
+
return entries.map((one) => ({
|
|
7059
|
+
key: one.key,
|
|
7060
|
+
method: one.method,
|
|
7061
|
+
url: one.url,
|
|
7062
|
+
body: one.request,
|
|
7063
|
+
status: one.status,
|
|
7064
|
+
ct: one.ct,
|
|
7065
|
+
response: one.response,
|
|
7066
|
+
at: one.at,
|
|
7067
|
+
source: "capture"
|
|
7068
|
+
}));
|
|
7069
|
+
}
|
|
7070
|
+
function callKeyOf(one) {
|
|
7071
|
+
return `${one.method} ${one.url}`;
|
|
7072
|
+
}
|
|
7073
|
+
function mergeCalls(hook, captured) {
|
|
7074
|
+
const out = [...hook];
|
|
7075
|
+
const hooks = hook.length;
|
|
7076
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
7077
|
+
for (const one of captured) {
|
|
7078
|
+
const key = callKeyOf(one);
|
|
7079
|
+
let found = -1;
|
|
7080
|
+
for (let index = 0; index < hooks; index += 1) {
|
|
7081
|
+
if (claimed.has(index)) continue;
|
|
7082
|
+
const kept = out[index];
|
|
7083
|
+
if (kept === void 0 || callKeyOf(kept) !== key) continue;
|
|
7084
|
+
if (kept.key !== "" && kept.key !== one.key) continue;
|
|
7085
|
+
found = index;
|
|
7086
|
+
break;
|
|
7087
|
+
}
|
|
7088
|
+
if (found === -1) {
|
|
7089
|
+
out.push(one);
|
|
7090
|
+
continue;
|
|
7091
|
+
}
|
|
7092
|
+
claimed.add(found);
|
|
7093
|
+
const kept = out[found];
|
|
7094
|
+
if (kept === void 0) continue;
|
|
7095
|
+
out[found] = {
|
|
7096
|
+
...kept,
|
|
7097
|
+
key: kept.key === "" ? one.key : kept.key,
|
|
7098
|
+
body: kept.body === "" ? one.body : kept.body,
|
|
7099
|
+
response: kept.response === "" ? one.response : kept.response
|
|
7100
|
+
};
|
|
7101
|
+
}
|
|
7102
|
+
return out.sort((left, right) => left.at - right.at);
|
|
7103
|
+
}
|
|
7104
|
+
function matchKey(one, want) {
|
|
7105
|
+
if (one.key === want) return true;
|
|
7106
|
+
const bare = want.replace(/#\d+$/, "");
|
|
7107
|
+
if (one.key.replace(/#\d+$/, "") === bare) return true;
|
|
7108
|
+
return callKeyOf(one) === bare;
|
|
7109
|
+
}
|
|
7110
|
+
function pickAllOf(entries, key) {
|
|
7111
|
+
const want = key.trim();
|
|
7112
|
+
if (want === "") return [];
|
|
7113
|
+
const exact = entries.filter((one) => one.key === want);
|
|
7114
|
+
if (exact.length > 0) return exact;
|
|
7115
|
+
return entries.filter((one) => matchKey(one, want));
|
|
7116
|
+
}
|
|
7117
|
+
function pickOf(entries, key, at = null) {
|
|
7118
|
+
const found = pickAllOf(entries, key);
|
|
7119
|
+
const first = found[0];
|
|
7120
|
+
if (first === void 0) return null;
|
|
7121
|
+
if (at === null) return first;
|
|
7122
|
+
let best = first;
|
|
7123
|
+
for (const one of found) if (Math.abs(one.at - at) < Math.abs(best.at - at)) best = one;
|
|
7124
|
+
return best;
|
|
7125
|
+
}
|
|
7126
|
+
//#endregion
|
|
7127
|
+
//#region src/modules/panels/model/diy-record-text.ts
|
|
7128
|
+
var CALLS_SHOWN = 12;
|
|
7129
|
+
function recordPageOf(payload) {
|
|
7130
|
+
const raw = payload ?? {};
|
|
7131
|
+
const calls = [];
|
|
7132
|
+
const rows = Array.isArray(raw["calls"]) ? raw["calls"] : [];
|
|
7133
|
+
for (const one of rows) {
|
|
7134
|
+
if (typeof one !== "object" || one === null) continue;
|
|
7135
|
+
const row = one;
|
|
7136
|
+
calls.push({
|
|
7137
|
+
key: "",
|
|
7138
|
+
method: typeof row["method"] === "string" ? row["method"] : "",
|
|
7139
|
+
url: typeof row["url"] === "string" ? row["url"] : "",
|
|
7140
|
+
body: typeof row["body"] === "string" ? row["body"] : "",
|
|
7141
|
+
status: typeof row["status"] === "number" ? row["status"] : 0,
|
|
7142
|
+
ct: typeof row["ct"] === "string" ? row["ct"] : "",
|
|
7143
|
+
response: typeof row["response"] === "string" ? row["response"] : "",
|
|
7144
|
+
at: typeof row["at"] === "number" ? row["at"] : 0,
|
|
7145
|
+
source: "hook"
|
|
7146
|
+
});
|
|
7147
|
+
}
|
|
7148
|
+
const phase = typeof raw["phase"] === "string" ? raw["phase"] : "none";
|
|
7149
|
+
return {
|
|
7150
|
+
ok: raw["ok"] === true,
|
|
7151
|
+
code: typeof raw["code"] === "string" ? raw["code"] : "",
|
|
7152
|
+
phase,
|
|
7153
|
+
t0: typeof raw["t0"] === "number" ? raw["t0"] : null,
|
|
7154
|
+
t1: typeof raw["t1"] === "number" ? raw["t1"] : null,
|
|
7155
|
+
href: typeof raw["href"] === "string" ? raw["href"] : "",
|
|
7156
|
+
here: typeof raw["here"] === "string" ? raw["here"] : "",
|
|
7157
|
+
dropped: typeof raw["dropped"] === "number" ? raw["dropped"] : 0,
|
|
7158
|
+
unhooked: raw["unhooked"] === 1 || raw["unhooked"] === true,
|
|
7159
|
+
calls
|
|
7160
|
+
};
|
|
7161
|
+
}
|
|
7162
|
+
function filterTextOf(expect) {
|
|
7163
|
+
const want = expect;
|
|
7164
|
+
if (want === null || want.method === "" && want.urlContains === "") return "";
|
|
7165
|
+
const parts = [];
|
|
7166
|
+
if (want.method !== "") parts.push(want.method);
|
|
7167
|
+
if (want.urlContains !== "") parts.push(`url 含 ${want.urlContains}`);
|
|
7168
|
+
return parts.join(" · ");
|
|
7169
|
+
}
|
|
7170
|
+
function probeCommandOf(calls) {
|
|
7171
|
+
const pick = calls.find((one) => one.body !== "");
|
|
7172
|
+
if (pick === void 0) return "";
|
|
7173
|
+
return `${pick.method} ${pick.url}\n${pick.body.slice(0, 400)}`;
|
|
7174
|
+
}
|
|
7175
|
+
function bodyMissingOf(calls) {
|
|
7176
|
+
return calls.length > 0 && calls.every((one) => one.body === "");
|
|
7177
|
+
}
|
|
7178
|
+
function recordRefusalText(code, held) {
|
|
7179
|
+
switch (code) {
|
|
7180
|
+
case "held-by-other": return `另一场录制还占着(${held === void 0 ? "另一个会话" : held.sessionId})—— 一台机器同时只允许一场:让它先 ${DIY_RECORD_STOP_TOOL},或等它收工。`;
|
|
7181
|
+
case "already-held": return `你这个会话已经持有一场录制(${held === void 0 ? "" : `${held.unit},${held.phase}`})—— 要换目标先 ${DIY_RECORD_STOP_TOOL};上一场录完了就再录一段(同一场不用重新审批)。`;
|
|
7182
|
+
case "not-owner": return "这一场录制不是你这个会话的 —— 谁开的谁收工。";
|
|
7183
|
+
case "none": return "现在没有录制中的那一场。";
|
|
7184
|
+
case "already-recording": return "已经在录了 —— 请他按「结束录制」,再调这一次。";
|
|
7185
|
+
case "not-armed": return "这一场已经录完了 —— 要再录一段就先起一段新的(同一场不用重新审批)。";
|
|
7186
|
+
case "not-recording": return "还没开始录(他还没按「开始录制」)—— 先请他按开始。";
|
|
7187
|
+
case "not-finished": return "这一场还没录完 —— 等他按「结束录制」再起下一段。";
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
function recordMissingText() {
|
|
7191
|
+
return `现在没有录制中的那一场(${DIY_RECORD_PREPARE_TOOL} 起的那一场可能已经过期或收工了)—— 先调 ${DIY_RECORD_PREPARE_TOOL}。`;
|
|
7192
|
+
}
|
|
7193
|
+
function recordTalkOf() {
|
|
7194
|
+
return "已开始录制";
|
|
7195
|
+
}
|
|
7196
|
+
function noFilter(expect) {
|
|
7197
|
+
return expect === null || expect.method === "" && expect.urlContains === "";
|
|
7198
|
+
}
|
|
7199
|
+
function matchesExpect(one, expect) {
|
|
7200
|
+
if (noFilter(expect)) return false;
|
|
7201
|
+
const want = expect;
|
|
7202
|
+
if (want.method !== "" && one.method !== want.method) return false;
|
|
7203
|
+
if (want.urlContains !== "" && !one.url.includes(want.urlContains)) return false;
|
|
7204
|
+
return true;
|
|
7205
|
+
}
|
|
7206
|
+
function listRowsOf(calls, expect) {
|
|
7207
|
+
return calls.map((one, index) => {
|
|
7208
|
+
const hasRequest = one.body !== "";
|
|
7209
|
+
const hasResponse = one.response !== "";
|
|
7210
|
+
const likelyWrite = one.method !== "GET" || hasRequest;
|
|
7211
|
+
return {
|
|
7212
|
+
index,
|
|
7213
|
+
key: one.key,
|
|
7214
|
+
at: one.at,
|
|
7215
|
+
method: one.method,
|
|
7216
|
+
url: one.url,
|
|
7217
|
+
status: one.status,
|
|
7218
|
+
ct: one.ct,
|
|
7219
|
+
size: one.response.length === 0 ? 0 : one.response.length,
|
|
7220
|
+
source: one.source,
|
|
7221
|
+
hasRequest,
|
|
7222
|
+
hasResponse,
|
|
7223
|
+
likelyWrite,
|
|
7224
|
+
marked: matchesExpect(one, expect) || noFilter(expect) && likelyWrite
|
|
7225
|
+
};
|
|
7226
|
+
}).slice().sort((left, right) => Number(right.marked) - Number(left.marked) || left.at - right.at).map((one, index) => ({
|
|
7227
|
+
...one,
|
|
7228
|
+
index
|
|
7229
|
+
}));
|
|
7230
|
+
}
|
|
7231
|
+
function sizeText(bytes) {
|
|
7232
|
+
if (bytes <= 0) return "无";
|
|
7233
|
+
if (bytes < 1024) return `${String(bytes)} B`;
|
|
7234
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
7235
|
+
}
|
|
7236
|
+
function rowTextOf(one) {
|
|
7237
|
+
const mark = one.marked ? " ⭐" : " ";
|
|
7238
|
+
const where = one.source === "capture" ? "捕获" : "钩子";
|
|
7239
|
+
return `· [${String(one.index)}]${mark} ${one.method} ${one.url}(${where} · 入参 ${one.hasRequest ? "有" : "无"} · 出参 ${sizeText(one.size)})`;
|
|
7240
|
+
}
|
|
7241
|
+
function renderRecordRead(input) {
|
|
7242
|
+
if (!input.armed) return `那一页上还没有录制条(${DIY_RECORD_PREPARE_TOOL} 起的那一场可能被导航冲掉了)—— 先 ${DIY_RECORD_PREPARE_TOOL},或让他回到那一页再 ${DIY_RECORD_READ_TOOL}。`;
|
|
7243
|
+
if (input.phase === "armed") return "他还没按「开始录制」—— 等他按了再读。";
|
|
7244
|
+
if (input.phase === "recording") return `还在录(页面钩子上已经攒了 ${String(input.hookCaught)} 条)—— 等他按「结束录制」。`;
|
|
7245
|
+
const hook = String(input.hookCaught);
|
|
7246
|
+
const inside = String(input.capturedInSegment);
|
|
7247
|
+
const markShown = input.rows.filter((one) => one.marked).slice(0, CALLS_SHOWN);
|
|
7248
|
+
const restShown = input.rows.filter((one) => !one.marked).slice(-Math.max(0, CALLS_SHOWN - markShown.length));
|
|
7249
|
+
const shown = [...markShown, ...restShown];
|
|
7250
|
+
const lines = [];
|
|
7251
|
+
if (input.rows.length > 0) lines.push(`录完了:这一段一共 ${String(input.merged)} 条请求(页面钩子 ${hook} 条 · 会话捕获落在这个时间段里 ${inside} 条,合并去重后 ${String(input.merged)} 条)。下面 ⭐ 都列上、其余按时间取最近几条:`, ...shown.map(rowTextOf));
|
|
7252
|
+
else lines.push(input.capturedTruncated ? `录完了:这一段(就解析到的那部分而言)一条请求都没抓到(页面钩子 0 条 · 解析到的会话捕获里落在这个时间段里 0 条)—— 但捕获文件被截过,见下一行。` : "录完了:这一段一条请求都没抓到(页面钩子 0 条 · 会话捕获落在这个时间段里 0 条,两边都是 0 —— 不是筛掉的)。");
|
|
7253
|
+
if (input.capturedTruncated) lines.push(input.capturedTotalLines > input.capturedTailLimit ? `(捕获文件这一场一共落了 ${String(input.capturedTotalLines)} 行,这里只解析了最后 ${String(input.capturedTailLimit)} 行 —— 更早的没看;上面那几个"有几条 / 没落"的捕获数只对最后这段成立)` : `(捕获文件这一场一共落了 ${String(input.capturedTotalLines)} 行(没顶到 ${String(input.capturedTailLimit)} 行那顶帽),是**这一读本身被输出上限截了** —— 读到的只是最近一段,上面那几个捕获数只对读到的这段成立)`);
|
|
7254
|
+
if (input.dropped > 0) lines.push(`(缓冲区满了,最早丢了 ${String(input.dropped)} 条)`);
|
|
7255
|
+
if (input.captureRound === "fail") lines.push("(会话捕获最近一轮没跑成(opencli 那条报了错)—— 这一场可能只有页面钩子。)");
|
|
7256
|
+
else if (input.captureRound === "") lines.push("(会话捕获还没回过一轮 —— 再等一拍再看,别急着下\"没抓到\"的结论。)");
|
|
7257
|
+
if (input.capturedLines === 0 && !input.capturedTruncated) lines.push("(opencli 那条会话捕获一条都没落 —— 这次只有页面钩子。请求体抓不到时请他打开 DevTools 把那次请求复制成 curl)");
|
|
7258
|
+
if (input.nav) lines.push("(这一段里那一页导航过 —— 浮条重新注入过,导航前的那几条可能不在)");
|
|
7259
|
+
return lines.join("\n");
|
|
7260
|
+
}
|
|
7261
|
+
function clip(text, maxBytes) {
|
|
7262
|
+
if (maxBytes <= 0 || text.length <= maxBytes) return {
|
|
7263
|
+
text,
|
|
7264
|
+
cut: false
|
|
7265
|
+
};
|
|
7266
|
+
return {
|
|
7267
|
+
text: text.slice(0, maxBytes),
|
|
7268
|
+
cut: true
|
|
7269
|
+
};
|
|
7270
|
+
}
|
|
7271
|
+
function sizeLabel(size, exact) {
|
|
7272
|
+
return exact ? `${String(size)} 字节` : `${String(size)} 字符(opencli 没给 body_full_size,按字符数算)`;
|
|
7273
|
+
}
|
|
7274
|
+
function renderRecordDetail(input) {
|
|
7275
|
+
if (!input.found) return [`没找到这一条:${input.note}`, `可用的取法:② 还在这场录制里时用清单里的 key 或 index;③ 这一场已经收工的话,给 unit 让我去读 shots/rec-<unit>.jsonl.last;④ 都不行就让他打开 DevTools,把那次请求复制成 curl。`].join("\n");
|
|
7276
|
+
const req = clip(input.request, input.maxBytes);
|
|
7277
|
+
const res = clip(input.response, input.maxBytes);
|
|
7278
|
+
const status = input.status === 0 ? "回包还没到(status 0)" : String(input.status);
|
|
7279
|
+
const lines = [`${input.method} ${input.url} · ${status}${input.ct === "" ? "" : ` · ${input.ct}`}`, `(${input.where} · ${new Date(input.at).toISOString()})`];
|
|
7280
|
+
lines.push(req.text === "" ? "入参(request body):没有" : `入参(request body,原长 ${sizeLabel(input.requestFullSize, input.requestSizeExact)}${req.cut ? `,这里截到 ${String(input.maxBytes)}` : ""}):\n${req.text}`);
|
|
7281
|
+
lines.push(res.text === "" ? "出参(response body):没有" : `出参(response body,原长 ${sizeLabel(input.responseFullSize, input.responseSizeExact)}${res.cut ? `,这里截到 ${String(input.maxBytes)}` : ""}):\n${res.text}`);
|
|
7282
|
+
if (input.headers !== "") lines.push(`请求头(可能含凭据 —— 别落进 design.json):${input.headers}`);
|
|
7283
|
+
if (input.ambiguous > 1) lines.push(`(这个 key 在这一场的原文里出现 ${String(input.ambiguous)} 次 —— 跨轮会重名;带上清单里那一行的 at 才钉得住是哪一次。)`);
|
|
7284
|
+
if (input.note !== "") lines.push(`(${input.note})`);
|
|
7285
|
+
return lines.join("\n");
|
|
7286
|
+
}
|
|
7287
|
+
function moveOrThrow(move, held) {
|
|
7288
|
+
if (move.ok) return move.state;
|
|
7289
|
+
throw new Error(recordRefusalText(move.code, held ?? void 0));
|
|
7290
|
+
}
|
|
7291
|
+
function plain(value) {
|
|
7292
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
7293
|
+
}
|
|
7294
|
+
function domainOf(url) {
|
|
7295
|
+
try {
|
|
7296
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
7297
|
+
return host.startsWith("www.") ? host.slice(4) : host;
|
|
7298
|
+
} catch {
|
|
7299
|
+
return "";
|
|
7300
|
+
}
|
|
7301
|
+
}
|
|
7302
|
+
function readDomainsOf(design) {
|
|
7303
|
+
const root = plain(design);
|
|
7304
|
+
const many = root["reads"];
|
|
7305
|
+
const one = root["datasource"];
|
|
7306
|
+
const rows = Array.isArray(many) ? many : one === void 0 ? [] : [one];
|
|
7307
|
+
const found = /* @__PURE__ */ new Set();
|
|
7308
|
+
for (const row of rows) {
|
|
7309
|
+
const read = plain(row)["read"];
|
|
7310
|
+
if (typeof read !== "string") continue;
|
|
7311
|
+
for (const match of read.matchAll(/https?:\/\/[^\s"'`)\\]+/g)) {
|
|
7312
|
+
const domain = domainOf(match[0]);
|
|
7313
|
+
if (domain !== "") found.add(domain);
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
return [...found];
|
|
7317
|
+
}
|
|
7318
|
+
//#endregion
|
|
7319
|
+
//#region src/modules/panels/model/diy-record.ts
|
|
7320
|
+
var OPEN_TIMEOUT_MS = 3e4;
|
|
7321
|
+
var START_TIMEOUT_MS = 8e3;
|
|
7322
|
+
var SETTLE_MS = 2e3;
|
|
7323
|
+
var WAIT_CAP_MS = 9e5;
|
|
7324
|
+
var POLL_MS = 1e3;
|
|
7325
|
+
var CAPTURE_TAIL_LINES = 2e4;
|
|
7326
|
+
var CAPTURE_POLL_S = 3;
|
|
7327
|
+
var CAPTURE_ARM_S = 2;
|
|
7328
|
+
var STATIC_URL_PATTERN = "^(data:)|(\\.(js|css|png|jpe?g|gif|svg|woff2?|ico|map|webp|mp4)(\\?|$))";
|
|
7329
|
+
var RECORD_WATCH_MS = 3e3;
|
|
7330
|
+
var RECORD_WATCH_TRIES = 5;
|
|
7331
|
+
var DROP_STATIC_JS = "let s=\"\";process.stdin.on(\"data\",(d)=>s+=d).on(\"end\",()=>{const re=new RegExp(process.argv[1],\"i\");for(const line of s.split(\"\\n\")){const t=line.trim();if(t===\"\")continue;let one;try{one=JSON.parse(t)}catch{process.stdout.write(t+\"\\n\");continue}if(Array.isArray(one.entries)){one.entries=one.entries.filter((e)=>!re.test(String((e||{}).url||\"\")));if(one.entries.length===0)continue}process.stdout.write(JSON.stringify(one)+\"\\n\")}});";
|
|
7332
|
+
var DETAIL_MAX_BYTES = 6e4;
|
|
7333
|
+
var HOST_PID = String(process.pid);
|
|
7334
|
+
var pauseFor = (ms) => new Promise((resolve) => {
|
|
7335
|
+
setTimeout(resolve, ms);
|
|
7336
|
+
});
|
|
7337
|
+
function waitOf(value) {
|
|
7338
|
+
return Math.min(Math.max(typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : 0, 0), WAIT_CAP_MS);
|
|
7339
|
+
}
|
|
7340
|
+
function foreignDomainText(design, url) {
|
|
7341
|
+
const known = readDomainsOf(design);
|
|
7342
|
+
if (known.length === 0) return "";
|
|
7343
|
+
const domain = domainOf(url);
|
|
7344
|
+
if (domain === "" || known.includes(domain)) return "";
|
|
7345
|
+
return `这一页(${domain})跟这个模块已经探明的读源(${known.join(" / ")})不是一个域 —— 录下来的请求原文是要留在工坊里的,不许跨域录。换成它那个域的页面,或者先把这个模块的读源说清。`;
|
|
7346
|
+
}
|
|
7347
|
+
function captureFileOf(packagesDir, unit) {
|
|
7348
|
+
return join(dirname(packagesDir), "shots", `rec-${unit}.jsonl`);
|
|
7349
|
+
}
|
|
7350
|
+
function lastCaptureFileOf(file) {
|
|
7351
|
+
return `${file}.last`;
|
|
7352
|
+
}
|
|
7353
|
+
async function startRecordCapture(shell, command, session, file) {
|
|
7354
|
+
const bin = command === "" ? "opencli" : command;
|
|
7355
|
+
const arm = backgroundLine(bin, [
|
|
7356
|
+
"browser",
|
|
7357
|
+
session,
|
|
7358
|
+
"network",
|
|
7359
|
+
"--follow",
|
|
7360
|
+
"--all",
|
|
7361
|
+
"--window",
|
|
7362
|
+
"background"
|
|
7363
|
+
]);
|
|
7364
|
+
const snap = backgroundLine(bin, [
|
|
7365
|
+
"browser",
|
|
7366
|
+
session,
|
|
7367
|
+
"network",
|
|
7368
|
+
"--all",
|
|
7369
|
+
"--raw",
|
|
7370
|
+
"--window",
|
|
7371
|
+
"background"
|
|
7372
|
+
]);
|
|
7373
|
+
return (await runShell(shell, [
|
|
7374
|
+
`f=${quoteShellArg(file)}`,
|
|
7375
|
+
`r=${quoteShellArg(`${file}.round`)}`,
|
|
7376
|
+
": > \"$f\"",
|
|
7377
|
+
"rm -f \"$f.last\" \"$f.first\" \"$r\"",
|
|
7378
|
+
`( ${arm} >> "$f" 2>/dev/null & p=$!; sleep ${String(CAPTURE_ARM_S)}; kill "$p" 2>/dev/null )`,
|
|
7379
|
+
`( while kill -0 ${HOST_PID} 2>/dev/null; do if ${snap} > "$r" 2>/dev/null; then printf ok > "$f.first"; else printf fail > "$f.first"; fi; if [ -s "$r" ]; then if command -v node >/dev/null 2>&1; then tr -d '\\n' < "$r" | node -e ${quoteShellArg(DROP_STATIC_JS)} ${quoteShellArg(STATIC_URL_PATTERN)} >> "$f"; else tr -d '\\n' < "$r" >> "$f"; printf '\\n' >> "$f"; fi; fi; rm -f "$r"; sleep ${String(CAPTURE_POLL_S)}; done & echo $! > "$f.pid" ) & echo $! > "$f.reaper"`,
|
|
7380
|
+
"kill -0 \"$(cat \"$f.pid\" 2>/dev/null)\" 2>/dev/null"
|
|
7381
|
+
].join("; "), { timeoutMs: START_TIMEOUT_MS })).exitCode === 0;
|
|
7382
|
+
}
|
|
7383
|
+
async function releaseLeftoversOf(input) {
|
|
7384
|
+
await releaseQuietly({
|
|
7385
|
+
shell: input.shell,
|
|
7386
|
+
command: input.command,
|
|
7387
|
+
session: BROWSER_RECORD_SESSION,
|
|
7388
|
+
url: input.url,
|
|
7389
|
+
captureFile: input.captureFile
|
|
7390
|
+
});
|
|
7391
|
+
}
|
|
7392
|
+
async function watchFinishStepOf(input) {
|
|
7393
|
+
const live = input.store.live(Date.now());
|
|
7394
|
+
if (live === null) {
|
|
7395
|
+
await releaseLeftoversOf(input);
|
|
7396
|
+
return {
|
|
7397
|
+
stop: true,
|
|
7398
|
+
delivered: false
|
|
7399
|
+
};
|
|
7400
|
+
}
|
|
7401
|
+
if (live.sessionId !== input.sessionId) return {
|
|
7402
|
+
stop: true,
|
|
7403
|
+
delivered: false
|
|
7404
|
+
};
|
|
7405
|
+
if (live.phase === "finished") return {
|
|
7406
|
+
stop: false,
|
|
7407
|
+
delivered: false
|
|
7408
|
+
};
|
|
7409
|
+
let page;
|
|
7410
|
+
try {
|
|
7411
|
+
page = recordPageOf((await actBrowser(input.shell, input.command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
|
|
7412
|
+
} catch {
|
|
7413
|
+
return {
|
|
7414
|
+
stop: false,
|
|
7415
|
+
delivered: false
|
|
7416
|
+
};
|
|
7417
|
+
}
|
|
7418
|
+
const held = input.store.live(Date.now());
|
|
7419
|
+
if (held === null) {
|
|
7420
|
+
await releaseLeftoversOf(input);
|
|
7421
|
+
return {
|
|
7422
|
+
stop: true,
|
|
7423
|
+
delivered: false
|
|
7424
|
+
};
|
|
7425
|
+
}
|
|
7426
|
+
if (held.sessionId !== input.sessionId) return {
|
|
7427
|
+
stop: true,
|
|
7428
|
+
delivered: false
|
|
7429
|
+
};
|
|
7430
|
+
if (held.phase === "finished") return {
|
|
7431
|
+
stop: false,
|
|
7432
|
+
delivered: false
|
|
7433
|
+
};
|
|
7434
|
+
if (!page.ok) return {
|
|
7435
|
+
stop: false,
|
|
7436
|
+
delivered: false
|
|
7437
|
+
};
|
|
7438
|
+
if (page.phase === "finished" && page.t1 !== null) {
|
|
7439
|
+
const now = Date.now();
|
|
7440
|
+
if (held.phase === "recording") input.store.finished(input.sessionId, page.t1, now);
|
|
7441
|
+
const caught = page.calls.length;
|
|
7442
|
+
const summary = `工作台 DIY:他按了「结束录制」(${String(caught)} 条)`;
|
|
7443
|
+
deliverDebugNotice(input.ctx.get("agents"), input.sessionId, noticeMessage(`他按了「结束录制」(页面钩子上 ${String(caught)} 条)—— 去 ${DIY_RECORD_READ_TOOL} 取这一段清单,要某一条原文用 ${DIY_RECORD_DETAIL_TOOL}。`, summary), "wake");
|
|
7444
|
+
return {
|
|
7445
|
+
stop: false,
|
|
7446
|
+
delivered: true
|
|
7447
|
+
};
|
|
7448
|
+
}
|
|
7449
|
+
if (page.phase === "recording" && page.t0 !== null && held.phase === "armed") input.store.started(input.sessionId, page.t0, Date.now());
|
|
7450
|
+
return {
|
|
7451
|
+
stop: false,
|
|
7452
|
+
delivered: false
|
|
7453
|
+
};
|
|
7454
|
+
}
|
|
7455
|
+
function watchRecordFinish(input) {
|
|
7456
|
+
let misses = 0;
|
|
7457
|
+
const tick = () => {
|
|
7458
|
+
watchFinishStepOf(input).then((out) => {
|
|
7459
|
+
misses = 0;
|
|
7460
|
+
if (!out.stop) setTimeout(tick, RECORD_WATCH_MS);
|
|
7461
|
+
}).catch(() => {
|
|
7462
|
+
misses += 1;
|
|
7463
|
+
if (misses < RECORD_WATCH_TRIES) setTimeout(tick, RECORD_WATCH_MS);
|
|
7464
|
+
});
|
|
7465
|
+
};
|
|
7466
|
+
setTimeout(tick, RECORD_WATCH_MS);
|
|
7467
|
+
}
|
|
7468
|
+
async function captureLinesOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
|
|
7469
|
+
const step = await runShell(shell, `wc -l < ${quoteShellArg(file)} 2>/dev/null || echo 0`, { timeoutMs });
|
|
7470
|
+
const value = Number.parseInt(step.stdout.trim() === "" ? "0" : step.stdout.trim(), 10);
|
|
7471
|
+
return Number.isFinite(value) ? value : 0;
|
|
7472
|
+
}
|
|
7473
|
+
async function captureRoundOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
|
|
7474
|
+
const value = (await runShell(shell, `cat ${quoteShellArg(`${file}.first`)} 2>/dev/null || true`, { timeoutMs })).stdout.trim();
|
|
7475
|
+
return value === "ok" || value === "fail" ? value : "";
|
|
7476
|
+
}
|
|
7477
|
+
async function captureTextOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
|
|
7478
|
+
const step = await runShell(shell, `tail -n ${String(CAPTURE_TAIL_LINES)} ${quoteShellArg(file)} 2>/dev/null || true`, { timeoutMs });
|
|
7479
|
+
return {
|
|
7480
|
+
text: step.stdout,
|
|
7481
|
+
truncated: step.truncated
|
|
7482
|
+
};
|
|
7483
|
+
}
|
|
7484
|
+
async function captureReadOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
|
|
7485
|
+
const totalLines = await captureLinesOf(shell, file, timeoutMs);
|
|
7486
|
+
const read = await captureTextOf(shell, file, timeoutMs);
|
|
7487
|
+
const round = await captureRoundOf(shell, file, timeoutMs);
|
|
7488
|
+
return {
|
|
7489
|
+
text: read.text,
|
|
7490
|
+
totalLines,
|
|
7491
|
+
tailLimit: CAPTURE_TAIL_LINES,
|
|
7492
|
+
truncated: read.truncated || totalLines > CAPTURE_TAIL_LINES,
|
|
7493
|
+
round
|
|
7494
|
+
};
|
|
7495
|
+
}
|
|
7496
|
+
async function keepCaptureOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
|
|
7497
|
+
return (await runShell(shell, `[ -f ${quoteShellArg(file)} ] && mv ${quoteShellArg(file)} ${quoteShellArg(lastCaptureFileOf(file))} && [ -s ${quoteShellArg(lastCaptureFileOf(file))} ]`, { timeoutMs })).exitCode === 0;
|
|
7498
|
+
}
|
|
7499
|
+
async function captureLinesWithKeyOf(shell, file, key, timeoutMs = START_TIMEOUT_MS) {
|
|
7500
|
+
const quoted = quoteShellArg(file);
|
|
7501
|
+
const compact = quoteShellArg(`"key":"${key}"`);
|
|
7502
|
+
const spaced = quoteShellArg(`"key": "${key}"`);
|
|
7503
|
+
return (await runShell(shell, [`[ -f ${quoted} ] || exit 0`, `grep -m 20 -F ${compact} ${quoted} 2>/dev/null || grep -m 20 -F ${spaced} ${quoted} 2>/dev/null || true`].join("; "), { timeoutMs })).stdout;
|
|
7504
|
+
}
|
|
7505
|
+
function bundleOf(capture, page, state) {
|
|
7506
|
+
const entries = captureEntriesOf(capture.text);
|
|
7507
|
+
const segment = segmentOf(entries, page.t0 ?? state.t0, page.t1 ?? state.t1);
|
|
7508
|
+
const merged = mergeCalls(page.calls, captureCallsOf(segment.inside));
|
|
7509
|
+
return {
|
|
7510
|
+
capture,
|
|
7511
|
+
entries,
|
|
7512
|
+
inside: segment.inside,
|
|
7513
|
+
outside: segment.outside,
|
|
7514
|
+
merged,
|
|
7515
|
+
rows: listRowsOf(merged, state.expect)
|
|
7516
|
+
};
|
|
7517
|
+
}
|
|
7518
|
+
function hookCallOf(page, key) {
|
|
7519
|
+
const want = key.trim();
|
|
7520
|
+
const bare = want.replace(/#\d+$/, "");
|
|
7521
|
+
for (const one of page.calls) {
|
|
7522
|
+
if (one.key !== "" && one.key === want) return one;
|
|
7523
|
+
if (`${one.method} ${one.url}` === bare) return one;
|
|
7524
|
+
}
|
|
7525
|
+
return null;
|
|
7526
|
+
}
|
|
7527
|
+
var DETAIL_WHERE_CAPTURE = "会话捕获(浏览器那一层,快照原文)";
|
|
7528
|
+
var DETAIL_WHERE_HOOK = "页面钩子(这一段)";
|
|
7529
|
+
function detailFactsOf(input) {
|
|
7530
|
+
return plainJson({
|
|
7531
|
+
ok: true,
|
|
7532
|
+
found: true,
|
|
7533
|
+
key: input.key,
|
|
7534
|
+
method: input.method,
|
|
7535
|
+
url: input.url,
|
|
7536
|
+
status: input.status,
|
|
7537
|
+
ct: input.ct,
|
|
7538
|
+
at: input.at,
|
|
7539
|
+
request: input.request,
|
|
7540
|
+
response: input.response,
|
|
7541
|
+
headers: input.headers,
|
|
7542
|
+
requestFullSize: input.requestFullSize,
|
|
7543
|
+
responseFullSize: input.responseFullSize,
|
|
7544
|
+
requestSizeExact: input.requestSizeExact,
|
|
7545
|
+
responseSizeExact: input.responseSizeExact,
|
|
7546
|
+
ambiguous: input.ambiguous,
|
|
7547
|
+
source: input.source,
|
|
7548
|
+
text: renderRecordDetail({
|
|
7549
|
+
found: true,
|
|
7550
|
+
where: input.where,
|
|
7551
|
+
method: input.method,
|
|
7552
|
+
url: input.url,
|
|
7553
|
+
status: input.status,
|
|
7554
|
+
ct: input.ct,
|
|
7555
|
+
at: input.at,
|
|
7556
|
+
request: input.request,
|
|
7557
|
+
response: input.response,
|
|
7558
|
+
headers: input.headers,
|
|
7559
|
+
requestFullSize: input.requestFullSize,
|
|
7560
|
+
responseFullSize: input.responseFullSize,
|
|
7561
|
+
requestSizeExact: input.requestSizeExact,
|
|
7562
|
+
responseSizeExact: input.responseSizeExact,
|
|
7563
|
+
ambiguous: input.ambiguous,
|
|
7564
|
+
source: input.source,
|
|
7565
|
+
maxBytes: input.maxBytes,
|
|
7566
|
+
note: ""
|
|
7567
|
+
})
|
|
7568
|
+
});
|
|
7569
|
+
}
|
|
7570
|
+
function detailOfEntry(entry, maxBytes, ambiguous = 1) {
|
|
7571
|
+
return detailFactsOf({
|
|
7572
|
+
where: DETAIL_WHERE_CAPTURE,
|
|
7573
|
+
key: entry.key,
|
|
7574
|
+
method: entry.method,
|
|
7575
|
+
url: entry.url,
|
|
7576
|
+
status: entry.status,
|
|
7577
|
+
ct: entry.ct,
|
|
7578
|
+
at: entry.at,
|
|
7579
|
+
request: entry.request,
|
|
7580
|
+
response: entry.response,
|
|
7581
|
+
headers: entry.headers,
|
|
7582
|
+
requestFullSize: entry.requestFullSize,
|
|
7583
|
+
responseFullSize: entry.responseFullSize,
|
|
7584
|
+
requestSizeExact: entry.requestSizeExact,
|
|
7585
|
+
responseSizeExact: entry.responseSizeExact,
|
|
7586
|
+
ambiguous,
|
|
7587
|
+
source: "capture",
|
|
7588
|
+
maxBytes
|
|
7589
|
+
});
|
|
7590
|
+
}
|
|
7591
|
+
function detailOfCall(call, maxBytes) {
|
|
7592
|
+
return detailFactsOf({
|
|
7593
|
+
where: DETAIL_WHERE_HOOK,
|
|
7594
|
+
key: call.key,
|
|
7595
|
+
method: call.method,
|
|
7596
|
+
url: call.url,
|
|
7597
|
+
status: call.status,
|
|
7598
|
+
ct: call.ct,
|
|
7599
|
+
at: call.at,
|
|
7600
|
+
request: call.body,
|
|
7601
|
+
response: call.response,
|
|
7602
|
+
headers: "",
|
|
7603
|
+
requestFullSize: call.body.length,
|
|
7604
|
+
responseFullSize: call.response.length,
|
|
7605
|
+
requestSizeExact: false,
|
|
7606
|
+
responseSizeExact: false,
|
|
7607
|
+
ambiguous: 0,
|
|
7608
|
+
source: "hook",
|
|
7609
|
+
maxBytes
|
|
7610
|
+
});
|
|
7611
|
+
}
|
|
7612
|
+
function tabsOf(raw) {
|
|
7613
|
+
const start = raw.indexOf("[");
|
|
7614
|
+
const end = raw.lastIndexOf("]");
|
|
7615
|
+
if (start === -1 || end <= start) return [];
|
|
7616
|
+
try {
|
|
7617
|
+
const parsed = JSON.parse(raw.slice(start, end + 1));
|
|
7618
|
+
if (!Array.isArray(parsed)) return [];
|
|
7619
|
+
return parsed.filter((one) => typeof one === "object" && one !== null);
|
|
7620
|
+
} catch {
|
|
7621
|
+
return [];
|
|
7622
|
+
}
|
|
7623
|
+
}
|
|
7624
|
+
async function closeRecordTab(shell, command, session, url) {
|
|
7625
|
+
const pages = tabsOf((await actBrowser(shell, command, session, ["tab", "list"])).stdout);
|
|
7626
|
+
const pick = pages.find((one) => typeof one["url"] === "string" && one["url"] === url) ?? pages[0];
|
|
7627
|
+
const page = pick === void 0 ? "" : typeof pick["page"] === "string" ? pick["page"] : "";
|
|
7628
|
+
if (page !== "") {
|
|
7629
|
+
if ((await actBrowser(shell, command, session, [
|
|
7630
|
+
"tab",
|
|
7631
|
+
"close",
|
|
7632
|
+
page
|
|
7633
|
+
])).ok) return true;
|
|
7634
|
+
}
|
|
7635
|
+
return (await actBrowser(shell, command, session, ["close"])).ok;
|
|
7636
|
+
}
|
|
7637
|
+
async function releaseRecord(input) {
|
|
7638
|
+
const capture = await stopNetworkExact(input.shell, input.captureFile);
|
|
7639
|
+
return {
|
|
7640
|
+
tab: await closeRecordTab(input.shell, input.command, input.session, input.url),
|
|
7641
|
+
capture
|
|
7642
|
+
};
|
|
7643
|
+
}
|
|
7644
|
+
async function releaseQuietly(input) {
|
|
7645
|
+
try {
|
|
7646
|
+
return await releaseRecord(input);
|
|
7647
|
+
} catch {
|
|
7648
|
+
return {
|
|
7649
|
+
tab: false,
|
|
7650
|
+
capture: false
|
|
7651
|
+
};
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
//#endregion
|
|
7655
|
+
//#region src/modules/panels/model/diy-record-tools.ts
|
|
7656
|
+
function registerRecordTools(ctx, store) {
|
|
7657
|
+
const tools = ctx.get("tools");
|
|
7658
|
+
if (tools === void 0) {
|
|
7659
|
+
ctx.logger?.info?.("[dsh-workbench] 没有 tools 服务,跳过录制交接工具注册");
|
|
7660
|
+
return [];
|
|
7661
|
+
}
|
|
7662
|
+
const prepareTool = {
|
|
7663
|
+
name: DIY_RECORD_PREPARE_TOOL,
|
|
7664
|
+
description: `开一扇浏览器窗口并顶到最前,让他自己录一次真实的写(开窗要点头一次);他按开始/结束后用 ${DIY_RECORD_READ_TOOL} 取那一段、${DIY_RECORD_STOP_TOOL} 收工。`,
|
|
7665
|
+
parameters: {
|
|
7666
|
+
type: "object",
|
|
7667
|
+
properties: {
|
|
7668
|
+
unit: {
|
|
7669
|
+
type: "string",
|
|
7670
|
+
description: "工坊里的 unit(workbench_diy_list 会给)。"
|
|
7671
|
+
},
|
|
7672
|
+
url: {
|
|
7673
|
+
type: "string",
|
|
7674
|
+
description: "那一页(他平时看这套系统的入口;与已探明的读源同 domain)。"
|
|
7675
|
+
},
|
|
7676
|
+
doing: {
|
|
7677
|
+
type: "string",
|
|
7678
|
+
description: "那一下叫什么,例:发布 / 提交(不是多路复用:这把工具只做录制这一件事)。"
|
|
7679
|
+
},
|
|
7680
|
+
expect: {
|
|
7681
|
+
type: "object",
|
|
7682
|
+
properties: {
|
|
7683
|
+
method: {
|
|
7684
|
+
type: "string",
|
|
7685
|
+
description: "想抓的那条的 HTTP 方法,例 POST(不填就按\"像写的那几条\"挑)。"
|
|
7686
|
+
},
|
|
7687
|
+
urlContains: {
|
|
7688
|
+
type: "string",
|
|
7689
|
+
description: "URL 里必须含的片段(不填就不筛)。"
|
|
7690
|
+
}
|
|
7691
|
+
},
|
|
7692
|
+
additionalProperties: false,
|
|
7693
|
+
description: "可选:你想抓的那条长什么样。"
|
|
7694
|
+
}
|
|
7695
|
+
},
|
|
7696
|
+
required: [
|
|
7697
|
+
"unit",
|
|
7698
|
+
"url",
|
|
7699
|
+
"doing"
|
|
7700
|
+
],
|
|
7701
|
+
additionalProperties: false
|
|
7702
|
+
},
|
|
7703
|
+
output: OUTPUT$2,
|
|
7704
|
+
async execute(raw, exec) {
|
|
7705
|
+
const { packagesDir, workspace } = workshopOf(ctx, exec);
|
|
7706
|
+
const args = asRecord$3(raw);
|
|
7707
|
+
const unit = asText$1(args["unit"]);
|
|
7708
|
+
const modules = modulesOf(ctx, packagesDir).modules;
|
|
7709
|
+
if (unit === "") throw new Error(`要一个 unit(工坊里:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
|
|
7710
|
+
const found = modules.find((one) => one.unit === unit);
|
|
7711
|
+
if (found === void 0) throw new Error(`工坊里没有单元 id 为「${unit}」的模块(可用的:${modules.map((one) => one.unit).join(", ") || "(空)"})`);
|
|
7712
|
+
const url = asText$1(args["url"]);
|
|
7713
|
+
if (url === "") throw new Error("要那一页的 url(他平时看这套系统的入口;先用 workbench_diy_recon 或问他\"你平时在哪看这个\")—— 我不猜是哪个站点。");
|
|
7714
|
+
const foreign = foreignDomainText(readDesignOfModule(found.dir), url);
|
|
7715
|
+
if (foreign !== "") throw new Error(foreign);
|
|
7716
|
+
const doing = asText$1(args["doing"]);
|
|
7717
|
+
const rawExpect = asRecord$3(args["expect"]);
|
|
7718
|
+
const method = asText$1(rawExpect["method"]).toUpperCase();
|
|
7719
|
+
const urlContains = asText$1(rawExpect["urlContains"]);
|
|
7720
|
+
const expect = method === "" && urlContains === "" ? null : {
|
|
7721
|
+
method,
|
|
7722
|
+
urlContains
|
|
7723
|
+
};
|
|
7724
|
+
const shell = ctx.get("shell");
|
|
7725
|
+
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,开不了录制窗");
|
|
7726
|
+
const command = opencliCommandOf(ctx).command;
|
|
7727
|
+
const sessionId = sessionIdOf(exec) ?? "";
|
|
7728
|
+
const held = store.live(Date.now());
|
|
7729
|
+
const captureFile = captureFileOf(packagesDir, unit);
|
|
7730
|
+
moveOrThrow(store.begin({
|
|
7731
|
+
sessionId,
|
|
7732
|
+
workspace: workspace ?? "",
|
|
7733
|
+
unit,
|
|
7734
|
+
name: found.name,
|
|
7735
|
+
url,
|
|
7736
|
+
title: "",
|
|
7737
|
+
action: doing,
|
|
7738
|
+
expect,
|
|
7739
|
+
captureFile,
|
|
7740
|
+
now: Date.now()
|
|
7741
|
+
}), held);
|
|
7742
|
+
try {
|
|
7743
|
+
const opened = await runBrowserStep(shell, command, [
|
|
7744
|
+
"browser",
|
|
7745
|
+
BROWSER_RECORD_SESSION,
|
|
7746
|
+
"tab",
|
|
7747
|
+
"new",
|
|
7748
|
+
url,
|
|
7749
|
+
"--window",
|
|
7750
|
+
"foreground"
|
|
7751
|
+
], OPEN_TIMEOUT_MS);
|
|
7752
|
+
if (!opened.ok) throw new Error(`开不了录制窗:${opened.stderr || opened.stdout || "opencli 报了个错"}。先让用户把工作台那一页切到前台(第一次要这样),或跑 opencli doctor 看扩展连没连上。`);
|
|
7753
|
+
await runBrowserStep(shell, command, [
|
|
7754
|
+
"browser",
|
|
7755
|
+
BROWSER_RECORD_SESSION,
|
|
7756
|
+
"wait",
|
|
7757
|
+
"time",
|
|
7758
|
+
String(SETTLE_MS / 1e3)
|
|
7759
|
+
], OPEN_TIMEOUT_MS);
|
|
7760
|
+
const armed = await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
|
|
7761
|
+
const page = recordPageOf(armed.payload);
|
|
7762
|
+
if (!armed.ok || !page.ok) throw new Error(`浮条没装上:${armed.stderr || armed.stdout || "那一页还在加载"} —— 等他那一页站稳了再试。`);
|
|
7763
|
+
await stopNetworkExact(shell, captureFile);
|
|
7764
|
+
const capture = await startRecordCapture(shell, command, BROWSER_RECORD_SESSION, captureFile);
|
|
7765
|
+
watchRecordFinish({
|
|
7766
|
+
ctx,
|
|
7767
|
+
shell,
|
|
7768
|
+
command,
|
|
7769
|
+
sessionId,
|
|
7770
|
+
store,
|
|
7771
|
+
url,
|
|
7772
|
+
captureFile
|
|
7773
|
+
});
|
|
7774
|
+
const talk = recordTalkOf();
|
|
7775
|
+
return plainJson({
|
|
7776
|
+
workshop: packagesDir,
|
|
7777
|
+
ok: true,
|
|
7778
|
+
wrote: false,
|
|
7779
|
+
phase: page.phase,
|
|
7780
|
+
site: url,
|
|
7781
|
+
session: BROWSER_RECORD_SESSION,
|
|
7782
|
+
capture,
|
|
7783
|
+
captureFile,
|
|
7784
|
+
size: page.calls.length,
|
|
7785
|
+
talk,
|
|
7786
|
+
text: capture ? talk : `${talk}(opencli 那条捕获没起来 —— 这次只有页面钩子)`
|
|
7787
|
+
});
|
|
7788
|
+
} catch (error) {
|
|
7789
|
+
await releaseQuietly({
|
|
7790
|
+
shell,
|
|
7791
|
+
command,
|
|
7792
|
+
session: BROWSER_RECORD_SESSION,
|
|
7793
|
+
url,
|
|
7794
|
+
captureFile
|
|
7795
|
+
});
|
|
7796
|
+
store.stop(sessionId, Date.now());
|
|
7797
|
+
throw error;
|
|
7798
|
+
}
|
|
7799
|
+
}
|
|
7800
|
+
};
|
|
7801
|
+
const readTool = {
|
|
7802
|
+
name: DIY_RECORD_READ_TOOL,
|
|
7803
|
+
description: "读这一段录到的请求(他按「结束录制」之后用);没按开始 / 没装钩子都会说清,不假装是\"没有请求\"。",
|
|
7804
|
+
parameters: {
|
|
7805
|
+
type: "object",
|
|
7806
|
+
properties: { waitMs: {
|
|
7807
|
+
type: "integer",
|
|
7808
|
+
description: "可选:阻塞等他按「结束」最多这么久(毫秒,默认 0 = 立刻回;帽 15 分钟)。"
|
|
7809
|
+
} },
|
|
7810
|
+
additionalProperties: false
|
|
7811
|
+
},
|
|
7812
|
+
output: OUTPUT$2,
|
|
7813
|
+
async execute(raw, exec) {
|
|
7814
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
7815
|
+
const shell = ctx.get("shell");
|
|
7816
|
+
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
|
|
7817
|
+
const sessionId = sessionIdOf(exec) ?? "";
|
|
7818
|
+
const held = store.live(Date.now());
|
|
7819
|
+
if (held === null) throw new Error(recordMissingText());
|
|
7820
|
+
if (held.sessionId !== sessionId) moveOrThrow({
|
|
7821
|
+
ok: false,
|
|
7822
|
+
code: "not-owner"
|
|
7823
|
+
}, held);
|
|
7824
|
+
const command = opencliCommandOf(ctx).command;
|
|
7825
|
+
const budget = waitOf(asRecord$3(raw)["waitMs"]);
|
|
7826
|
+
const opened = budget === 0 ? 0 : Date.now();
|
|
7827
|
+
const deadline = opened + budget;
|
|
7828
|
+
const sync = (page) => {
|
|
7829
|
+
if (page.ok && page.phase === "recording" && page.t0 !== null) {
|
|
7830
|
+
if (store.live(Date.now())?.phase === "armed") store.started(sessionId, page.t0, Date.now());
|
|
7831
|
+
}
|
|
7832
|
+
if (page.ok && page.phase === "finished" && page.t1 !== null) {
|
|
7833
|
+
if (store.live(Date.now())?.phase === "armed" && page.t0 !== null) store.started(sessionId, page.t0, Date.now());
|
|
7834
|
+
if (store.live(Date.now())?.phase === "recording") store.finished(sessionId, page.t1, Date.now());
|
|
7835
|
+
}
|
|
7836
|
+
};
|
|
7837
|
+
await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
|
|
7838
|
+
let step = await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
|
|
7839
|
+
let page = recordPageOf(step.payload);
|
|
7840
|
+
sync(page);
|
|
7841
|
+
while (budget > 0 && page.phase !== "finished" && Date.now() < deadline) {
|
|
7842
|
+
await pauseFor(Math.min(POLL_MS, Math.max(0, deadline - Date.now())));
|
|
7843
|
+
step = await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
|
|
7844
|
+
const next = recordPageOf(step.payload);
|
|
7845
|
+
if (next.ok) page = next;
|
|
7846
|
+
sync(page);
|
|
7847
|
+
}
|
|
7848
|
+
const timedOut = budget > 0 && page.phase !== "finished";
|
|
7849
|
+
const waitedMs = opened === 0 ? 0 : Date.now() - opened;
|
|
7850
|
+
if (timedOut) {
|
|
7851
|
+
const waited = String(Math.round(budget / 1e3));
|
|
7852
|
+
holdNotice(sessionId, noticeMessage(`录制还没结束(等了约 ${waited} 秒):他按了「结束」再 read 一次。`, `工作台 DIY:录制还没结束(等了约 ${waited} 秒)`));
|
|
7853
|
+
}
|
|
7854
|
+
const state = store.live(Date.now()) ?? held;
|
|
7855
|
+
const capture = await captureReadOf(shell, state.captureFile);
|
|
7856
|
+
const bundle = bundleOf(capture, page, state);
|
|
7857
|
+
const hookCaught = page.calls.length;
|
|
7858
|
+
const nav = page.href !== "" && page.here !== "" && page.href !== page.here;
|
|
7859
|
+
const noBody = bodyMissingOf(bundle.merged);
|
|
7860
|
+
const text = step.ok ? [renderRecordRead({
|
|
7861
|
+
phase: page.phase,
|
|
7862
|
+
armed: page.ok,
|
|
7863
|
+
hookCaught,
|
|
7864
|
+
rows: bundle.rows,
|
|
7865
|
+
merged: bundle.merged.length,
|
|
7866
|
+
capturedLines: bundle.entries.length,
|
|
7867
|
+
capturedInSegment: bundle.inside.length,
|
|
7868
|
+
capturedOutside: bundle.outside,
|
|
7869
|
+
capturedTotalLines: capture.totalLines,
|
|
7870
|
+
capturedTruncated: capture.truncated,
|
|
7871
|
+
capturedTailLimit: capture.tailLimit,
|
|
7872
|
+
dropped: page.dropped,
|
|
7873
|
+
nav,
|
|
7874
|
+
filter: filterTextOf(state.expect),
|
|
7875
|
+
captureRound: bundle.capture.round
|
|
7876
|
+
}), timedOut ? `还在录 —— 等他按「结束」(要等就带 waitMs,帽 ${String(WAIT_CAP_MS / 6e4)} 分钟)。` : ""].filter((one) => one !== "").join("\n") : `读不到那一页上的录制条:${step.stderr || step.stdout || "opencli 报了个错"}`;
|
|
7877
|
+
return plainJson({
|
|
7878
|
+
workshop: packagesDir,
|
|
7879
|
+
ok: step.ok && page.ok,
|
|
7880
|
+
phase: state.phase,
|
|
7881
|
+
t0: state.t0,
|
|
7882
|
+
t1: state.t1,
|
|
7883
|
+
rows: bundle.rows,
|
|
7884
|
+
dropped: page.dropped,
|
|
7885
|
+
captureLines: bundle.entries.length,
|
|
7886
|
+
capturedInSegment: bundle.inside.length,
|
|
7887
|
+
capturedOutside: bundle.outside,
|
|
7888
|
+
capturedTotalLines: capture.totalLines,
|
|
7889
|
+
capturedTruncated: capture.truncated,
|
|
7890
|
+
hookCaught,
|
|
7891
|
+
noBody,
|
|
7892
|
+
nav,
|
|
7893
|
+
waitedMs,
|
|
7894
|
+
timedOut,
|
|
7895
|
+
text
|
|
7896
|
+
});
|
|
7897
|
+
}
|
|
7898
|
+
};
|
|
7899
|
+
const detailTool = {
|
|
7900
|
+
name: DIY_RECORD_DETAIL_TOOL,
|
|
7901
|
+
description: "取录制这一段里某一条请求的原文:入参(request body)与出参(response body)。先用 record_read 拿清单(每条带 key),再用它挑一条 —— 清单不删条,\"那一条\"永远挑得到。",
|
|
7902
|
+
parameters: {
|
|
7903
|
+
type: "object",
|
|
7904
|
+
properties: {
|
|
7905
|
+
key: {
|
|
7906
|
+
type: "string",
|
|
7907
|
+
description: "清单里那一条的 key(原样给,含 #2 这种后缀)。"
|
|
7908
|
+
},
|
|
7909
|
+
index: {
|
|
7910
|
+
type: "integer",
|
|
7911
|
+
description: "清单里的序号(0 起);只在那一场还开着时可用。"
|
|
7912
|
+
},
|
|
7913
|
+
at: {
|
|
7914
|
+
type: "integer",
|
|
7915
|
+
description: "清单里那一行的 at(毫秒时间戳):同一个 key 跨轮会重名,带上它才钉得住是哪一次。"
|
|
7916
|
+
},
|
|
7917
|
+
unit: {
|
|
7918
|
+
type: "string",
|
|
7919
|
+
description: "那一场已经收工、只能去读 shots/rec-<unit>.jsonl.last 时给。"
|
|
7920
|
+
},
|
|
7921
|
+
maxBody: {
|
|
7922
|
+
type: "integer",
|
|
7923
|
+
description: `入参 / 出参各自最多回多少字符(默认 ${String(DETAIL_MAX_BYTES)},0 = 不限)。`
|
|
7924
|
+
}
|
|
7925
|
+
},
|
|
7926
|
+
additionalProperties: false
|
|
7927
|
+
},
|
|
5812
7928
|
output: OUTPUT$2,
|
|
5813
7929
|
async execute(raw, exec) {
|
|
5814
|
-
const { packagesDir
|
|
5815
|
-
const
|
|
5816
|
-
if (
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
7930
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
7931
|
+
const shell = ctx.get("shell");
|
|
7932
|
+
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,取不了详情");
|
|
7933
|
+
const args = asRecord$3(raw);
|
|
7934
|
+
const unit = asText$1(args["unit"]);
|
|
7935
|
+
const askedKey = asText$1(args["key"]).trim();
|
|
7936
|
+
const rawIndex = args["index"];
|
|
7937
|
+
const index = typeof rawIndex === "number" && Number.isFinite(rawIndex) ? Math.floor(rawIndex) : null;
|
|
7938
|
+
const rawAt = args["at"];
|
|
7939
|
+
let at = typeof rawAt === "number" && Number.isFinite(rawAt) ? Math.floor(rawAt) : null;
|
|
7940
|
+
const rawMax = args["maxBody"];
|
|
7941
|
+
const maxBytes = typeof rawMax === "number" && Number.isFinite(rawMax) && rawMax >= 0 ? Math.floor(rawMax) : DETAIL_MAX_BYTES;
|
|
7942
|
+
const sessionId = sessionIdOf(exec) ?? "";
|
|
7943
|
+
const held = store.live(Date.now());
|
|
7944
|
+
if (held !== null && held.sessionId !== sessionId) moveOrThrow({
|
|
7945
|
+
ok: false,
|
|
7946
|
+
code: "not-owner"
|
|
7947
|
+
}, held);
|
|
7948
|
+
const file = held !== null ? held.captureFile : unit === "" ? "" : lastCaptureFileOf(captureFileOf(packagesDir, unit));
|
|
7949
|
+
if (file === "") throw new Error(`这一场录制已经不在了(收工 / 过期 / 不是你这个会话的)—— 要给一个 unit(我去读 shots/rec-<unit>.jsonl.last),或者重新 ${DIY_RECORD_PREPARE_TOOL} 一场。`);
|
|
7950
|
+
const command = opencliCommandOf(ctx).command;
|
|
7951
|
+
let key = askedKey;
|
|
7952
|
+
let page = null;
|
|
7953
|
+
if (key === "") {
|
|
7954
|
+
if (index === null) throw new Error("给一个 key(清单里那一条的)或 index(清单里的序号)。");
|
|
7955
|
+
if (held === null) throw new Error("收工之后只能用 key —— index 是\"那一场清单里\"的序号,这一场已经清掉了。");
|
|
7956
|
+
page = recordPageOf((await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
|
|
7957
|
+
const bundle = bundleOf(await captureReadOf(shell, file), page, held);
|
|
7958
|
+
const row = bundle.rows[index];
|
|
7959
|
+
if (row === void 0) throw new Error(`这一段清单里没有第 ${String(index)} 条(一共 ${String(bundle.rows.length)} 条)—— 先 ${DIY_RECORD_READ_TOOL} 拿一份新的清单。`);
|
|
7960
|
+
key = row.key;
|
|
7961
|
+
at = at ?? row.at;
|
|
7962
|
+
if (key === "") {
|
|
7963
|
+
const only = hookCallOf(page, `${row.method} ${row.url}`);
|
|
7964
|
+
if (only !== null) return detailOfCall(only, maxBytes);
|
|
7965
|
+
}
|
|
5827
7966
|
}
|
|
5828
|
-
const
|
|
5829
|
-
const
|
|
5830
|
-
|
|
5831
|
-
const
|
|
7967
|
+
const candidates = pickAllOf(captureEntriesOf(await captureLinesWithKeyOf(shell, file, key)), key);
|
|
7968
|
+
const fromCapture = pickOf(candidates, key, at);
|
|
7969
|
+
if (fromCapture !== null) return detailOfEntry(fromCapture, maxBytes, candidates.length);
|
|
7970
|
+
const call = hookCallOf(page ?? recordPageOf((await actBrowser(shell, command, "workbench-rec", ["eval", RECORD_READ_SCRIPT])).payload), key);
|
|
7971
|
+
if (call !== null) return detailOfCall(call, maxBytes);
|
|
7972
|
+
return plainJson({
|
|
7973
|
+
workshop: packagesDir,
|
|
7974
|
+
ok: true,
|
|
7975
|
+
found: false,
|
|
7976
|
+
key,
|
|
7977
|
+
text: renderRecordDetail({
|
|
7978
|
+
found: false,
|
|
7979
|
+
where: "",
|
|
7980
|
+
method: "",
|
|
7981
|
+
url: "",
|
|
7982
|
+
status: 0,
|
|
7983
|
+
ct: "",
|
|
7984
|
+
at: 0,
|
|
7985
|
+
request: "",
|
|
7986
|
+
response: "",
|
|
7987
|
+
headers: "",
|
|
7988
|
+
requestFullSize: 0,
|
|
7989
|
+
responseFullSize: 0,
|
|
7990
|
+
requestSizeExact: false,
|
|
7991
|
+
responseSizeExact: false,
|
|
7992
|
+
ambiguous: 0,
|
|
7993
|
+
source: "",
|
|
7994
|
+
maxBytes,
|
|
7995
|
+
note: `「${key}」既不在这一场的快照原文(${file})里,也不在这一页的钩子那一份里`
|
|
7996
|
+
})
|
|
7997
|
+
});
|
|
7998
|
+
}
|
|
7999
|
+
};
|
|
8000
|
+
const stopTool = {
|
|
8001
|
+
name: DIY_RECORD_STOP_TOOL,
|
|
8002
|
+
description: "录制收工:卸浮条、停捕获、收掉那个标签、清状态(不动你的其他标签)。",
|
|
8003
|
+
parameters: {
|
|
8004
|
+
type: "object",
|
|
8005
|
+
properties: {},
|
|
8006
|
+
additionalProperties: false
|
|
8007
|
+
},
|
|
8008
|
+
output: OUTPUT$2,
|
|
8009
|
+
async execute(_raw, exec) {
|
|
8010
|
+
const { packagesDir } = workshopOf(ctx, exec);
|
|
8011
|
+
const shell = ctx.get("shell");
|
|
8012
|
+
if (shell === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
|
|
8013
|
+
const sessionId = sessionIdOf(exec) ?? "";
|
|
8014
|
+
const held = store.live(Date.now());
|
|
8015
|
+
if (held === null) throw new Error(recordMissingText());
|
|
8016
|
+
if (held.sessionId !== sessionId) moveOrThrow({
|
|
5832
8017
|
ok: false,
|
|
5833
|
-
|
|
5834
|
-
};
|
|
5835
|
-
const
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
8018
|
+
code: "not-owner"
|
|
8019
|
+
}, held);
|
|
8020
|
+
const command = opencliCommandOf(ctx).command;
|
|
8021
|
+
await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
|
|
8022
|
+
const page = recordPageOf((await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_STOP_SCRIPT])).payload);
|
|
8023
|
+
const bundle = bundleOf(await captureReadOf(shell, held.captureFile), page, held);
|
|
8024
|
+
const hookCaught = page.calls.length;
|
|
8025
|
+
const noBody = bodyMissingOf(bundle.merged);
|
|
8026
|
+
const kept = await keepCaptureOf(shell, held.captureFile);
|
|
8027
|
+
const cleaned = await releaseQuietly({
|
|
8028
|
+
shell,
|
|
8029
|
+
command,
|
|
8030
|
+
session: BROWSER_RECORD_SESSION,
|
|
8031
|
+
url: held.url,
|
|
8032
|
+
captureFile: held.captureFile
|
|
8033
|
+
});
|
|
8034
|
+
store.stop(sessionId, Date.now());
|
|
8035
|
+
const probeCommand = probeCommandOf(bundle.merged);
|
|
8036
|
+
const lastFile = lastCaptureFileOf(held.captureFile);
|
|
5841
8037
|
return plainJson({
|
|
5842
8038
|
workshop: packagesDir,
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
8039
|
+
ok: true,
|
|
8040
|
+
phase: "idle",
|
|
8041
|
+
rows: bundle.rows,
|
|
8042
|
+
lastFile: kept ? lastFile : "",
|
|
8043
|
+
dropped: page.dropped,
|
|
8044
|
+
captureLines: bundle.entries.length,
|
|
8045
|
+
capturedInSegment: bundle.inside.length,
|
|
8046
|
+
capturedOutside: bundle.outside,
|
|
8047
|
+
hookCaught,
|
|
8048
|
+
noBody,
|
|
8049
|
+
probeCommand,
|
|
8050
|
+
cleaned: {
|
|
8051
|
+
...cleaned,
|
|
8052
|
+
hook: page.ok && page.unhooked
|
|
8053
|
+
},
|
|
8054
|
+
text: [
|
|
8055
|
+
`收工了:${held.unit} 的「${held.action}」这一段一共 ${String(bundle.merged.length)} 条请求(页面钩子 ${String(hookCaught)} 条 · 会话捕获这一段 ${String(bundle.inside.length)} 条)。`,
|
|
8056
|
+
kept ? `原文留在 ${lastFile}(下一场 prepare 会清掉它)—— 要再看某一条用 ${DIY_RECORD_DETAIL_TOOL}(给 unit + key;同一个 key 跨轮会重名,再带上清单里那行的 at 就钉住了)。` : "没留下原文(这一场的捕获文件是空的或没起来)—— 要哪一条就让他从 DevTools 复制 curl。",
|
|
8057
|
+
bundle.capture.round === "fail" ? "(会话捕获最近一轮没跑成(opencli 那条报了错)—— 这一场可能只有页面钩子。)" : "",
|
|
8058
|
+
cleaned.tab ? "录制窗那个标签收掉了。" : "那个标签没关掉(他自己关一下)。",
|
|
8059
|
+
cleaned.capture ? "" : "捕获那条没停干净(最多 15 分钟自己收工)。",
|
|
8060
|
+
probeCommand !== "" ? "这段原文可以直接落 design.json 的 writes[].probeCommand。" : noBody ? "只有方法 / URL,body 拿不到 —— 补到 body 再落 writes[].probeCommand;要体就让他从 DevTools 复制 curl,或在他那一页上用 _eval 里的 fetch 打一条同形的(先问过他)。" : "这一段的原文是空的 —— 别硬写进 design.json。",
|
|
8061
|
+
"不许复现这一段(没预览档的写一次就是真的)。"
|
|
8062
|
+
].filter((one) => one !== "").join("\n")
|
|
5849
8063
|
});
|
|
5850
8064
|
}
|
|
5851
8065
|
};
|
|
5852
8066
|
return [
|
|
5853
|
-
tools.register(
|
|
5854
|
-
tools.register(
|
|
5855
|
-
tools.register(
|
|
5856
|
-
tools.register(
|
|
5857
|
-
...registerSpecTool(ctx, tools),
|
|
5858
|
-
tools.register(probeTool),
|
|
5859
|
-
...registerLogTools(ctx, tools),
|
|
5860
|
-
...registerBrowserTools(ctx, tools),
|
|
5861
|
-
...registerDebugTools(ctx, tools, debug),
|
|
5862
|
-
tools.register(showTool),
|
|
5863
|
-
tools.register(installTool)
|
|
8067
|
+
tools.register(prepareTool),
|
|
8068
|
+
tools.register(readTool),
|
|
8069
|
+
tools.register(detailTool),
|
|
8070
|
+
tools.register(stopTool)
|
|
5864
8071
|
];
|
|
5865
8072
|
}
|
|
5866
8073
|
//#endregion
|
|
5867
|
-
//#region src/modules/panels/model/diy-observers.ts
|
|
5868
|
-
async function stopObservers(ctx, session = BROWSER_SCREEN_SESSION) {
|
|
5869
|
-
const packagesDir = workshopPackagesDir(ctx);
|
|
5870
|
-
const shell = ctx.get("shell");
|
|
5871
|
-
if (packagesDir === null || shell === void 0) return {
|
|
5872
|
-
net: false,
|
|
5873
|
-
logs: false
|
|
5874
|
-
};
|
|
5875
|
-
const file = join(dirname(packagesDir), "shots", `net-${session}.jsonl`);
|
|
5876
|
-
const command = opencliCommandOf(ctx).command;
|
|
5877
|
-
const [net, step] = await Promise.all([stopNetworkExact(shell, file), actBrowser(shell, command, session, ["eval", LOGS_STOP_SCRIPT], SWEEP_TIMEOUT_MS)]);
|
|
5878
|
-
return {
|
|
5879
|
-
net,
|
|
5880
|
-
logs: typeof step.payload?.["unhooked"] === "number"
|
|
5881
|
-
};
|
|
5882
|
-
}
|
|
5883
|
-
function withObserverCleanup(store, cleanup, logger) {
|
|
5884
|
-
let pending = Promise.resolve();
|
|
5885
|
-
let epoch = 0;
|
|
5886
|
-
const sweep = async (at) => {
|
|
5887
|
-
if (epoch !== at) {
|
|
5888
|
-
logger?.warn?.("收工那一拍:已经有新的调试约定开始了 —— 跳过收观察(新那一场自己收)");
|
|
5889
|
-
return;
|
|
5890
|
-
}
|
|
5891
|
-
try {
|
|
5892
|
-
const out = await cleanup();
|
|
5893
|
-
if (!out.net || !out.logs) logger?.warn?.(`收工那一拍没把观察收干净(网络 ${String(out.net)} / 日志 ${String(out.logs)})—— 网络那条最多 15 分钟自己收工,日志钩子随页面刷新消失`);
|
|
5894
|
-
} catch (error) {
|
|
5895
|
-
logger?.warn?.(`收工那一拍收观察抛了:${error instanceof Error ? error.message : String(error)}`);
|
|
5896
|
-
}
|
|
5897
|
-
};
|
|
5898
|
-
return {
|
|
5899
|
-
live: (now) => store.live(now),
|
|
5900
|
-
begin(pact) {
|
|
5901
|
-
epoch += 1;
|
|
5902
|
-
return store.begin(pact);
|
|
5903
|
-
},
|
|
5904
|
-
pause: (sessionId, now) => store.pause(sessionId, now),
|
|
5905
|
-
resume: (sessionId, now) => store.resume(sessionId, now),
|
|
5906
|
-
stop(sessionId, now) {
|
|
5907
|
-
const move = store.stop(sessionId, now);
|
|
5908
|
-
if (move.ok) {
|
|
5909
|
-
const at = epoch;
|
|
5910
|
-
pending = pending.then(() => sweep(at));
|
|
5911
|
-
}
|
|
5912
|
-
return move;
|
|
5913
|
-
},
|
|
5914
|
-
settle: () => pending
|
|
5915
|
-
};
|
|
5916
|
-
}
|
|
5917
|
-
//#endregion
|
|
5918
|
-
//#region src/modules/panels/model/diy-debug-routes.ts
|
|
5919
|
-
function diyDebugRoutes(store) {
|
|
5920
|
-
return { "panels/diy-debug": () => ({
|
|
5921
|
-
ok: true,
|
|
5922
|
-
state: store.live(Date.now())
|
|
5923
|
-
}) };
|
|
5924
|
-
}
|
|
5925
|
-
//#endregion
|
|
5926
8074
|
//#region src/shared/api/host/release.ts
|
|
5927
8075
|
function parseLedgerVersion(stdout, name) {
|
|
5928
8076
|
const parsed = parseJsonLoose(stdout);
|
|
@@ -6813,7 +8961,7 @@ function createEnvDropper(options) {
|
|
|
6813
8961
|
async function rememberInstalledSource(deps, manager, pkg, registry) {
|
|
6814
8962
|
const { registries, logger } = deps;
|
|
6815
8963
|
if (registries === void 0 || registry === void 0) return;
|
|
6816
|
-
const scope = scopeOf$
|
|
8964
|
+
const scope = scopeOf$2(pkg);
|
|
6817
8965
|
if (scope === null) return;
|
|
6818
8966
|
const known = await manager.registries().catch(() => null);
|
|
6819
8967
|
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
@@ -7440,7 +9588,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7440
9588
|
return {
|
|
7441
9589
|
name: spec.name,
|
|
7442
9590
|
description: spec.description,
|
|
7443
|
-
parameters:
|
|
9591
|
+
parameters: {
|
|
7444
9592
|
type: "object",
|
|
7445
9593
|
properties: { id: {
|
|
7446
9594
|
type: "string",
|
|
@@ -7448,7 +9596,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7448
9596
|
} },
|
|
7449
9597
|
required: ["id"],
|
|
7450
9598
|
additionalProperties: false
|
|
7451
|
-
},
|
|
9599
|
+
},
|
|
7452
9600
|
output: OUTPUT$1,
|
|
7453
9601
|
async execute(raw) {
|
|
7454
9602
|
const id = await idOrList(raw, spec.name);
|
|
@@ -7487,7 +9635,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7487
9635
|
const uninstallTool = {
|
|
7488
9636
|
name: MODULES_UNINSTALL_TOOL,
|
|
7489
9637
|
description: "卸模块:连模块包一起卸(只动工作台运行时);要换一份实现先卸再装。",
|
|
7490
|
-
parameters:
|
|
9638
|
+
parameters: {
|
|
7491
9639
|
type: "object",
|
|
7492
9640
|
properties: { id: {
|
|
7493
9641
|
type: "string",
|
|
@@ -7495,7 +9643,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7495
9643
|
} },
|
|
7496
9644
|
required: ["id"],
|
|
7497
9645
|
additionalProperties: false
|
|
7498
|
-
},
|
|
9646
|
+
},
|
|
7499
9647
|
output: OUTPUT$1,
|
|
7500
9648
|
async execute(raw) {
|
|
7501
9649
|
const id = await idOrList(raw, MODULES_UNINSTALL_TOOL);
|
|
@@ -7510,7 +9658,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7510
9658
|
const installBundleTool = {
|
|
7511
9659
|
name: MODULES_INSTALL_BUNDLE_TOOL,
|
|
7512
9660
|
description: "按 spec 把模块包装进这份 profile(包名 / git 地址 / tarball / 本地路径);装完它要的插件也一起装。",
|
|
7513
|
-
parameters:
|
|
9661
|
+
parameters: {
|
|
7514
9662
|
type: "object",
|
|
7515
9663
|
properties: {
|
|
7516
9664
|
spec: {
|
|
@@ -7524,7 +9672,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7524
9672
|
},
|
|
7525
9673
|
required: ["spec"],
|
|
7526
9674
|
additionalProperties: false
|
|
7527
|
-
},
|
|
9675
|
+
},
|
|
7528
9676
|
output: OUTPUT$1,
|
|
7529
9677
|
async execute(raw) {
|
|
7530
9678
|
const spec = textArg(raw, "spec");
|
|
@@ -7542,7 +9690,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7542
9690
|
const updateDependencyTool = {
|
|
7543
9691
|
name: MODULES_UPDATE_DEPENDENCY_TOOL,
|
|
7544
9692
|
description: "升共用依赖(工作台自己那份营生,如 plugin:coding):只有升级,用依赖 id。",
|
|
7545
|
-
parameters:
|
|
9693
|
+
parameters: {
|
|
7546
9694
|
type: "object",
|
|
7547
9695
|
properties: { id: {
|
|
7548
9696
|
type: "string",
|
|
@@ -7550,7 +9698,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7550
9698
|
} },
|
|
7551
9699
|
required: ["id"],
|
|
7552
9700
|
additionalProperties: false
|
|
7553
|
-
},
|
|
9701
|
+
},
|
|
7554
9702
|
output: OUTPUT$1,
|
|
7555
9703
|
async execute(raw) {
|
|
7556
9704
|
const id = await idOrList(raw, MODULES_UPDATE_DEPENDENCY_TOOL);
|
|
@@ -7566,7 +9714,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7566
9714
|
return {
|
|
7567
9715
|
name: spec.name,
|
|
7568
9716
|
description: spec.description,
|
|
7569
|
-
parameters:
|
|
9717
|
+
parameters: {
|
|
7570
9718
|
type: "object",
|
|
7571
9719
|
properties: { plugin: {
|
|
7572
9720
|
type: "string",
|
|
@@ -7574,7 +9722,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7574
9722
|
} },
|
|
7575
9723
|
required: ["plugin"],
|
|
7576
9724
|
additionalProperties: false
|
|
7577
|
-
},
|
|
9725
|
+
},
|
|
7578
9726
|
output: OUTPUT$1,
|
|
7579
9727
|
async execute(raw) {
|
|
7580
9728
|
const plugin = textArg(raw, "plugin");
|
|
@@ -7608,7 +9756,7 @@ function registerMarketTools(ctx, market) {
|
|
|
7608
9756
|
const updateWorkbenchTool = {
|
|
7609
9757
|
name: MODULES_UPDATE_WORKBENCH_TOOL,
|
|
7610
9758
|
description: "把工作台自己更新到源上的新版(要重启时结果里会说;重启由用户来)。",
|
|
7611
|
-
parameters:
|
|
9759
|
+
parameters: noParams,
|
|
7612
9760
|
output: OUTPUT$1,
|
|
7613
9761
|
async execute() {
|
|
7614
9762
|
const outcome = await market.updateWorkbench("");
|
|
@@ -7687,6 +9835,7 @@ function applyPanelsPref(panels, payload) {
|
|
|
7687
9835
|
//#region src/modules/panels/model/handoff.ts
|
|
7688
9836
|
var PANELS_FACT = "panels";
|
|
7689
9837
|
var DIY_DEBUG_FACT = "diy-debug";
|
|
9838
|
+
var DIY_RECORD_FACT = "diy-record";
|
|
7690
9839
|
var CAPABILITY_FACT = "capability";
|
|
7691
9840
|
var DEBUG_TOOLS = [
|
|
7692
9841
|
DIY_DEBUG_START_TOOL,
|
|
@@ -7694,6 +9843,12 @@ var DEBUG_TOOLS = [
|
|
|
7694
9843
|
DIY_DEBUG_RESUME_TOOL,
|
|
7695
9844
|
DIY_DEBUG_STOP_TOOL
|
|
7696
9845
|
];
|
|
9846
|
+
var RECORD_TOOLS = [
|
|
9847
|
+
DIY_RECORD_PREPARE_TOOL,
|
|
9848
|
+
DIY_RECORD_READ_TOOL,
|
|
9849
|
+
DIY_RECORD_DETAIL_TOOL,
|
|
9850
|
+
DIY_RECORD_STOP_TOOL
|
|
9851
|
+
];
|
|
7697
9852
|
var PROVISION_TOOLS = {
|
|
7698
9853
|
[MODULES_INSTALL_TOOL]: "install",
|
|
7699
9854
|
[MODULES_UPDATE_TOOL]: "update",
|
|
@@ -7730,6 +9885,7 @@ function panelsFactsOf(call, manifests) {
|
|
|
7730
9885
|
if (call.source === "workbench_modules_update_workbench") return [PANELS_FACT, CAPABILITY_FACT];
|
|
7731
9886
|
if (call.source === "workbench_diy_list") return [PANELS_FACT];
|
|
7732
9887
|
if (DEBUG_TOOLS.includes(call.source)) return [DIY_DEBUG_FACT];
|
|
9888
|
+
if (RECORD_TOOLS.includes(call.source)) return [DIY_RECORD_FACT];
|
|
7733
9889
|
const request = requestOf(call);
|
|
7734
9890
|
if (request === void 0) return void 0;
|
|
7735
9891
|
return [
|
|
@@ -8066,9 +10222,6 @@ var ID_PARAM = {
|
|
|
8066
10222
|
type: "string",
|
|
8067
10223
|
description: "套件 id(如 cmdb)。"
|
|
8068
10224
|
};
|
|
8069
|
-
function noted(parameters) {
|
|
8070
|
-
return withApprovalNote(parameters, { required: true });
|
|
8071
|
-
}
|
|
8072
10225
|
function listed(entries) {
|
|
8073
10226
|
return entries.map((entry, index) => `${index + 1}. ${entry.id} · ${entry.shown ? "已启用" : "未启用"}${entry.hasCard ? "" : "(无卡片)"}${entry.source === "user" ? " · 用户改过" : ""}`).join("\n");
|
|
8074
10227
|
}
|
|
@@ -8157,7 +10310,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8157
10310
|
const showTool = {
|
|
8158
10311
|
name: PANELS_SHOW_TOOL,
|
|
8159
10312
|
description: "这个工作区要它:之前收起的也放回来,卡片与 agent 工具一起上线;那份板上写过\"不要\"的也划掉。",
|
|
8160
|
-
parameters:
|
|
10313
|
+
parameters: {
|
|
8161
10314
|
type: "object",
|
|
8162
10315
|
properties: {
|
|
8163
10316
|
id: ID_PARAM,
|
|
@@ -8165,7 +10318,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8165
10318
|
},
|
|
8166
10319
|
required: ["id"],
|
|
8167
10320
|
additionalProperties: false
|
|
8168
|
-
}
|
|
10321
|
+
},
|
|
8169
10322
|
output: OUTPUT,
|
|
8170
10323
|
async execute(raw, exec) {
|
|
8171
10324
|
const id = idOf(raw);
|
|
@@ -8182,7 +10335,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8182
10335
|
const hideTool = {
|
|
8183
10336
|
name: PANELS_HIDE_TOOL,
|
|
8184
10337
|
description: "这个工作区明确不要它:卡片与 agent 工具一起下线(机器板上开着的也照样收起);别的项目不受影响,要放回来用 workbench_panels_show。在机器板上 hide 是写下\"不要\"这一笔,比名单管用。",
|
|
8185
|
-
parameters:
|
|
10338
|
+
parameters: {
|
|
8186
10339
|
type: "object",
|
|
8187
10340
|
properties: {
|
|
8188
10341
|
id: ID_PARAM,
|
|
@@ -8190,7 +10343,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8190
10343
|
},
|
|
8191
10344
|
required: ["id"],
|
|
8192
10345
|
additionalProperties: false
|
|
8193
|
-
}
|
|
10346
|
+
},
|
|
8194
10347
|
output: OUTPUT,
|
|
8195
10348
|
async execute(raw, exec) {
|
|
8196
10349
|
const id = idOf(raw);
|
|
@@ -8207,7 +10360,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8207
10360
|
const moveTool = {
|
|
8208
10361
|
name: PANELS_MOVE_TOOL,
|
|
8209
10362
|
description: "调顺序:放到最前面(新开一行时排最后)。顺序只属于工作区;机器级默认板上没有顺序一说。",
|
|
8210
|
-
parameters:
|
|
10363
|
+
parameters: {
|
|
8211
10364
|
type: "object",
|
|
8212
10365
|
properties: {
|
|
8213
10366
|
id: ID_PARAM,
|
|
@@ -8219,7 +10372,7 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8219
10372
|
},
|
|
8220
10373
|
required: ["id"],
|
|
8221
10374
|
additionalProperties: false
|
|
8222
|
-
}
|
|
10375
|
+
},
|
|
8223
10376
|
output: OUTPUT,
|
|
8224
10377
|
async execute(raw, exec) {
|
|
8225
10378
|
const id = idOf(raw);
|
|
@@ -8237,11 +10390,11 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8237
10390
|
const resetTool = {
|
|
8238
10391
|
name: PANELS_RESET_TOOL,
|
|
8239
10392
|
description: "清掉这个工作区改过的行(改完全部回到跟着机器级默认板走);scope=machine 清的是整份机器级默认板。",
|
|
8240
|
-
parameters:
|
|
10393
|
+
parameters: {
|
|
8241
10394
|
type: "object",
|
|
8242
10395
|
properties: { scope: SCOPE_PARAM },
|
|
8243
10396
|
additionalProperties: false
|
|
8244
|
-
}
|
|
10397
|
+
},
|
|
8245
10398
|
output: OUTPUT,
|
|
8246
10399
|
async execute(raw, exec) {
|
|
8247
10400
|
const scope = scopeOf(raw);
|
|
@@ -8256,14 +10409,14 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
8256
10409
|
const followTool = {
|
|
8257
10410
|
name: PANELS_FOLLOW_TOOL,
|
|
8258
10411
|
description: "某一行放回跟随机器级默认板(带 id = 只那一行,不带 = 全部)。",
|
|
8259
|
-
parameters:
|
|
10412
|
+
parameters: {
|
|
8260
10413
|
type: "object",
|
|
8261
10414
|
properties: {
|
|
8262
10415
|
id: ID_PARAM,
|
|
8263
10416
|
scope: SCOPE_PARAM
|
|
8264
10417
|
},
|
|
8265
10418
|
additionalProperties: false
|
|
8266
|
-
}
|
|
10419
|
+
},
|
|
8267
10420
|
output: OUTPUT,
|
|
8268
10421
|
async execute(raw, exec) {
|
|
8269
10422
|
const id = idOf(raw);
|
|
@@ -8306,6 +10459,24 @@ var OWNED = [{
|
|
|
8306
10459
|
ctx.provide(PANELS_SERVICE, panels);
|
|
8307
10460
|
const debug = withObserverCleanup(createDebugStore(), () => stopObservers(ctx), logger);
|
|
8308
10461
|
ctx.provide(DIY_DEBUG_SERVICE, debug);
|
|
10462
|
+
const record = {
|
|
10463
|
+
...createRecordStore(),
|
|
10464
|
+
release: (state) => {
|
|
10465
|
+
const shellNow = ctx.get("shell");
|
|
10466
|
+
if (shellNow === void 0) return Promise.resolve({
|
|
10467
|
+
tab: false,
|
|
10468
|
+
capture: false
|
|
10469
|
+
});
|
|
10470
|
+
return releaseQuietly({
|
|
10471
|
+
shell: shellNow,
|
|
10472
|
+
command: opencliCommandOf(ctx).command,
|
|
10473
|
+
session: BROWSER_RECORD_SESSION,
|
|
10474
|
+
url: state.url,
|
|
10475
|
+
captureFile: state.captureFile
|
|
10476
|
+
});
|
|
10477
|
+
}
|
|
10478
|
+
};
|
|
10479
|
+
ctx.provide(DIY_RECORD_SERVICE, record);
|
|
8309
10480
|
const profileDir = profileDirOf(ctx);
|
|
8310
10481
|
const shell = ctx.get("shell");
|
|
8311
10482
|
const moduleManifests = () => {
|
|
@@ -8354,12 +10525,14 @@ var OWNED = [{
|
|
|
8354
10525
|
ledger,
|
|
8355
10526
|
...shell === void 0 ? {} : { shell }
|
|
8356
10527
|
}),
|
|
8357
|
-
...diyDebugRoutes(debug)
|
|
10528
|
+
...diyDebugRoutes(debug),
|
|
10529
|
+
...diyRecordRoutes(record)
|
|
8358
10530
|
},
|
|
8359
10531
|
registerTools: () => [
|
|
8360
10532
|
...registerPanelTools(ctx, panels, values),
|
|
8361
10533
|
...registerMarketTools(ctx, market),
|
|
8362
|
-
...registerDiyTools(ctx, market, panels, debug)
|
|
10534
|
+
...registerDiyTools(ctx, market, panels, debug),
|
|
10535
|
+
...registerRecordTools(ctx, record)
|
|
8363
10536
|
],
|
|
8364
10537
|
factsOf: (call) => panelsFactsOf(call, manifests()),
|
|
8365
10538
|
needsApproval: panelsApproval,
|
|
@@ -8370,7 +10543,11 @@ var OWNED = [{
|
|
|
8370
10543
|
return [...manifests().flatMap((manifest) => owned.has(manifest.id) || resolveScope({
|
|
8371
10544
|
id: manifest.id,
|
|
8372
10545
|
boards
|
|
8373
|
-
}) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [
|
|
10546
|
+
}) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [
|
|
10547
|
+
...DIY_TOOLS,
|
|
10548
|
+
...DIY_DEBUG_TOOLS,
|
|
10549
|
+
...DIY_RECORD_TOOLS
|
|
10550
|
+
]];
|
|
8374
10551
|
},
|
|
8375
10552
|
subscribe: (listener) => panels.subscribe(listener)
|
|
8376
10553
|
},
|
|
@@ -8418,19 +10595,26 @@ function withRunsRoute(result, unit) {
|
|
|
8418
10595
|
}
|
|
8419
10596
|
};
|
|
8420
10597
|
}
|
|
8421
|
-
function
|
|
10598
|
+
function declaresNote(definition) {
|
|
10599
|
+
const parameters = definition.parameters;
|
|
10600
|
+
if (typeof parameters !== "object" || parameters === null) return false;
|
|
10601
|
+
const properties = parameters["properties"];
|
|
10602
|
+
if (typeof properties !== "object" || properties === null) return false;
|
|
10603
|
+
return APPROVAL_NOTE_ARG in properties;
|
|
10604
|
+
}
|
|
10605
|
+
function declaredNotesOf(plans) {
|
|
10606
|
+
return new Set([...plans].flatMap((plan) => [...plan.declaredNotes]));
|
|
10607
|
+
}
|
|
10608
|
+
function journaledRegistry(registry, journal, declaredNotes) {
|
|
8422
10609
|
return { register(definition) {
|
|
8423
|
-
const
|
|
8424
|
-
const registered = registry.register(noted === void 0 || noted === definition.parameters ? definition : {
|
|
8425
|
-
...definition,
|
|
8426
|
-
parameters: noted
|
|
8427
|
-
});
|
|
10610
|
+
const registered = registry.register(definition);
|
|
8428
10611
|
journal.push(registered);
|
|
10612
|
+
if (declaresNote(definition)) declaredNotes.add(definition.name);
|
|
8429
10613
|
return registered;
|
|
8430
10614
|
} };
|
|
8431
10615
|
}
|
|
8432
|
-
function scopedMountCtx(ctx, registry,
|
|
8433
|
-
const tools =
|
|
10616
|
+
function scopedMountCtx(ctx, registry, journal, declaredNotes) {
|
|
10617
|
+
const tools = journaledRegistry(registry, journal, declaredNotes);
|
|
8434
10618
|
const get = (name) => {
|
|
8435
10619
|
const value = ctx.get(name);
|
|
8436
10620
|
return name === "tools" && value !== void 0 ? tools : value;
|
|
@@ -8574,6 +10758,7 @@ function mountUnits(units, deps) {
|
|
|
8574
10758
|
const store = deps.store ?? createWorkspaceStore({ root: null });
|
|
8575
10759
|
const ownedUnits = deps.ownedUnits ?? (() => []);
|
|
8576
10760
|
const mounted = [];
|
|
10761
|
+
const declaredNotes = /* @__PURE__ */ new Set();
|
|
8577
10762
|
const skipped = [];
|
|
8578
10763
|
const longTaskDisposers = /* @__PURE__ */ new Map();
|
|
8579
10764
|
const ledger = {
|
|
@@ -8591,9 +10776,8 @@ function mountUnits(units, deps) {
|
|
|
8591
10776
|
}
|
|
8592
10777
|
const unit = host.manifest.id;
|
|
8593
10778
|
const tools = deps.ctx.get("tools");
|
|
8594
|
-
const writes = toolUnits(deps.manifests());
|
|
8595
10779
|
const toolJournal = [];
|
|
8596
|
-
const mountCtx = tools === void 0 ? deps.ctx : scopedMountCtx(deps.ctx, tools,
|
|
10780
|
+
const mountCtx = tools === void 0 ? deps.ctx : scopedMountCtx(deps.ctx, tools, toolJournal, declaredNotes);
|
|
8597
10781
|
const longTasks = deps.longTasks === void 0 ? void 0 : createLongTaskHost({
|
|
8598
10782
|
scope: deps.longTasks.scope,
|
|
8599
10783
|
unit,
|
|
@@ -8667,6 +10851,7 @@ function mountUnits(units, deps) {
|
|
|
8667
10851
|
skipped,
|
|
8668
10852
|
failed: ledger.failures,
|
|
8669
10853
|
warmReads: collectWarmReads(alive),
|
|
10854
|
+
declaredNotes,
|
|
8670
10855
|
disposeLongTasks: () => {
|
|
8671
10856
|
for (const dispose of [...longTaskDisposers.values()]) dispose();
|
|
8672
10857
|
longTaskDisposers.clear();
|
|
@@ -8703,125 +10888,105 @@ function createPostChannels(deps) {
|
|
|
8703
10888
|
};
|
|
8704
10889
|
}
|
|
8705
10890
|
//#endregion
|
|
8706
|
-
//#region src/shared/api/host/diy-
|
|
8707
|
-
var
|
|
10891
|
+
//#region src/shared/api/host/diy-record-act.ts
|
|
10892
|
+
var RECORD_NOTICE_KIND = "workbench-diy-record";
|
|
8708
10893
|
var INTENT_WORD = {
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
stop: "
|
|
10894
|
+
peek: "看结果",
|
|
10895
|
+
halt: "停",
|
|
10896
|
+
stop: "收工"
|
|
8712
10897
|
};
|
|
8713
|
-
function
|
|
10898
|
+
function recordNoticeText(intent, state) {
|
|
8714
10899
|
const head = `[工作台 DIY]用户按了「${INTENT_WORD[intent]}」:`;
|
|
8715
|
-
if (intent === "
|
|
8716
|
-
if (intent === "
|
|
8717
|
-
return `${head}${state.unit}
|
|
10900
|
+
if (intent === "peek") return state.phase === "finished" ? `${head}${state.unit} 这一段录制他做完了 —— 用 workbench_diy_record_read 取那一段(他不在了也照取),取过用 workbench_diy_record_stop 收工。` : `${head}${state.unit} 这一段现在还是 ${state.phase}(他还没按「结束录制」)—— 等他按完再 read;他要是说浮条没了,read 那一步会顺手把浮条重新装上。`;
|
|
10901
|
+
if (intent === "halt") return `${head}${state.unit} 这一段他停下来了(浮条可能被导航干掉过)。**先 read 再 stop**:read 会把那一页上的缓冲取出来,stop 之后就取不到了 —— 顺序反了就等于白录。`;
|
|
10902
|
+
return `${head}${state.unit} 这一场收了(占用解除)。已经 read 过的部分在你手上;没读的那部分没了 —— 要再录一段重新 prepare 一场。`;
|
|
8718
10903
|
}
|
|
8719
|
-
function
|
|
10904
|
+
function recordNoticeSummary(intent, state) {
|
|
8720
10905
|
return `工作台 DIY:用户按了${INTENT_WORD[intent]}(${state.unit})`;
|
|
8721
10906
|
}
|
|
8722
|
-
function
|
|
8723
|
-
const summary =
|
|
10907
|
+
function recordNotice(intent, state) {
|
|
10908
|
+
const summary = recordNoticeSummary(intent, state);
|
|
8724
10909
|
return {
|
|
8725
10910
|
summary,
|
|
8726
|
-
message:
|
|
8727
|
-
role: "user",
|
|
8728
|
-
id: randomUUID(),
|
|
8729
|
-
content: [{
|
|
8730
|
-
type: "text",
|
|
8731
|
-
text: debugNoticeText(intent, state)
|
|
8732
|
-
}],
|
|
8733
|
-
source: {
|
|
8734
|
-
kind: DEBUG_NOTICE_KIND,
|
|
8735
|
-
form: "notice",
|
|
8736
|
-
summary
|
|
8737
|
-
}
|
|
8738
|
-
}
|
|
10911
|
+
message: noticeMessage(recordNoticeText(intent, state), summary, RECORD_NOTICE_KIND)
|
|
8739
10912
|
};
|
|
8740
10913
|
}
|
|
8741
10914
|
var NOTICE_MODE = {
|
|
8742
|
-
|
|
8743
|
-
|
|
10915
|
+
peek: "wake",
|
|
10916
|
+
halt: "wake",
|
|
8744
10917
|
stop: "drop"
|
|
8745
10918
|
};
|
|
8746
|
-
var
|
|
8747
|
-
function
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
const agent = agents?.get(sessionId);
|
|
8759
|
-
if (agent === void 0) {
|
|
8760
|
-
if (mode === "drop") return "none";
|
|
8761
|
-
holdNotice(sessionId, message);
|
|
8762
|
-
return "held";
|
|
8763
|
-
}
|
|
8764
|
-
if (agent.status !== "idle") {
|
|
8765
|
-
agent.inject(message);
|
|
8766
|
-
return "inject";
|
|
8767
|
-
}
|
|
8768
|
-
if (mode === "wake") {
|
|
8769
|
-
agent.followup(message);
|
|
8770
|
-
return "followup";
|
|
8771
|
-
}
|
|
8772
|
-
if (mode === "hold") {
|
|
8773
|
-
holdNotice(sessionId, message);
|
|
8774
|
-
return "held";
|
|
10919
|
+
var REFUSAL = "现在没有录制中的那一场 —— 没什么可停 / 可收的";
|
|
10920
|
+
async function releaseHeld(store, held) {
|
|
10921
|
+
try {
|
|
10922
|
+
return await (store.release?.(held) ?? Promise.resolve({
|
|
10923
|
+
tab: false,
|
|
10924
|
+
capture: false
|
|
10925
|
+
}));
|
|
10926
|
+
} catch {
|
|
10927
|
+
return {
|
|
10928
|
+
tab: false,
|
|
10929
|
+
capture: false
|
|
10930
|
+
};
|
|
8775
10931
|
}
|
|
8776
|
-
return "skipped-idle";
|
|
8777
10932
|
}
|
|
8778
|
-
|
|
8779
|
-
"already-paused": "这一场已经是暂停态了",
|
|
8780
|
-
"already-debugging": "这一场本来就在调试中",
|
|
8781
|
-
none: "现在没有调试态",
|
|
8782
|
-
"not-owner": "这场调试不是这个会话的"
|
|
8783
|
-
};
|
|
8784
|
-
function debugActResource(deps) {
|
|
10933
|
+
function recordActResource(deps) {
|
|
8785
10934
|
const now = () => deps.now?.() ?? Date.now();
|
|
8786
|
-
const settled = (intent, delivered, summary, state) => ({
|
|
8787
|
-
ok: true,
|
|
8788
|
-
intent,
|
|
8789
|
-
delivered,
|
|
8790
|
-
summary,
|
|
8791
|
-
state
|
|
8792
|
-
});
|
|
8793
10935
|
return {
|
|
8794
10936
|
resource: "act",
|
|
8795
|
-
handle(payload) {
|
|
8796
|
-
const intent =
|
|
10937
|
+
async handle(payload) {
|
|
10938
|
+
const intent = parseRecordIntent(payload?.intent);
|
|
8797
10939
|
if (intent === null) return {
|
|
8798
10940
|
ok: false,
|
|
8799
|
-
text: "不认识的意图(只认
|
|
10941
|
+
text: "不认识的意图(只认 peek / halt / stop)"
|
|
8800
10942
|
};
|
|
8801
10943
|
const store = deps.store();
|
|
8802
|
-
if (store === void 0) return {
|
|
8803
|
-
ok: false,
|
|
8804
|
-
text: "现在没有调试态 —— 没什么可暂停 / 继续 / 结束的"
|
|
8805
|
-
};
|
|
8806
10944
|
const at = now();
|
|
8807
|
-
const held = store
|
|
8808
|
-
if (held === null) return {
|
|
10945
|
+
const held = store?.live(at) ?? null;
|
|
10946
|
+
if (store === void 0 || held === null) return {
|
|
8809
10947
|
ok: false,
|
|
8810
|
-
text:
|
|
10948
|
+
text: REFUSAL
|
|
8811
10949
|
};
|
|
8812
|
-
const
|
|
8813
|
-
if (
|
|
8814
|
-
ok:
|
|
8815
|
-
|
|
10950
|
+
const notice = deps.notice?.(intent, held) ?? recordNotice(intent, held);
|
|
10951
|
+
if (intent !== "stop") return {
|
|
10952
|
+
ok: true,
|
|
10953
|
+
intent,
|
|
10954
|
+
delivered: deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE[intent]),
|
|
10955
|
+
summary: notice.summary,
|
|
10956
|
+
state: store.live(now())
|
|
10957
|
+
};
|
|
10958
|
+
const cleaned = await releaseHeld(store, held);
|
|
10959
|
+
const move = store.stop(held.sessionId, at);
|
|
10960
|
+
return {
|
|
10961
|
+
ok: true,
|
|
10962
|
+
intent,
|
|
10963
|
+
delivered: deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE[intent]),
|
|
10964
|
+
summary: notice.summary,
|
|
10965
|
+
cleaned,
|
|
10966
|
+
refused: move.ok ? "" : move.code,
|
|
10967
|
+
state: store.live(now())
|
|
8816
10968
|
};
|
|
8817
|
-
const notice = deps.notice?.(intent, move.state) ?? debugNotice(intent, move.state);
|
|
8818
|
-
const delivered = deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE[intent]);
|
|
8819
|
-
if (intent !== "stop") return settled(intent, delivered, notice.summary, store.live(now()));
|
|
8820
|
-
return (store.settle?.() ?? Promise.resolve()).then(() => settled(intent, delivered, notice.summary, store.live(now())));
|
|
8821
10969
|
}
|
|
8822
10970
|
};
|
|
8823
10971
|
}
|
|
8824
10972
|
//#endregion
|
|
10973
|
+
//#region src/shared/api/host/diy-acts.ts
|
|
10974
|
+
function registerDiyActs(ctx, facts) {
|
|
10975
|
+
const log = { bump: (names) => facts.bump(names) };
|
|
10976
|
+
const offDebug = registerDiyDebugAct(ctx, debugActResource({
|
|
10977
|
+
store: () => ctx.get(DIY_DEBUG_SERVICE),
|
|
10978
|
+
agents: () => ctx.get("agents")
|
|
10979
|
+
}), log);
|
|
10980
|
+
const offRecord = registerDiyRecordAct(ctx, recordActResource({
|
|
10981
|
+
store: () => ctx.get(DIY_RECORD_SERVICE),
|
|
10982
|
+
agents: () => ctx.get("agents")
|
|
10983
|
+
}), log);
|
|
10984
|
+
return () => {
|
|
10985
|
+
offDebug();
|
|
10986
|
+
offRecord();
|
|
10987
|
+
};
|
|
10988
|
+
}
|
|
10989
|
+
//#endregion
|
|
8825
10990
|
//#region src/shared/api/host/events.ts
|
|
8826
10991
|
var HEARTBEAT_MS = 25e3;
|
|
8827
10992
|
function frame(payload) {
|
|
@@ -11083,6 +13248,7 @@ function apply(ctx) {
|
|
|
11083
13248
|
const factsOfUnit = (unit) => [...routesOfUnitMap.get(unit) ?? [], "capability"];
|
|
11084
13249
|
const callFacts = (unit, call) => factsOfMap.get(unit)?.(call) ?? factsOfUnit(unit);
|
|
11085
13250
|
const deniedNow = (workspace) => [...new Set([...mounted.values()].flatMap((item) => item.plan.gate?.deniedTools(workspace) ?? []))];
|
|
13251
|
+
const declaredNoteTools = () => declaredNotesOf([...mounted.values()].map((item) => item.plan));
|
|
11086
13252
|
const wrote = (resource, write) => {
|
|
11087
13253
|
const unit = ownersMap.get(resource);
|
|
11088
13254
|
if (unit !== void 0) facts.bump(callFacts(unit, {
|
|
@@ -11314,10 +13480,12 @@ function apply(ctx) {
|
|
|
11314
13480
|
...workspace === void 0 ? {} : { workspace }
|
|
11315
13481
|
};
|
|
11316
13482
|
const policy = owner?.needsApproval;
|
|
13483
|
+
const declaredNote = name !== void 0 && declaredNoteTools().has(name);
|
|
11317
13484
|
return await approvalFor(call, {
|
|
11318
13485
|
detailOf,
|
|
11319
13486
|
policy,
|
|
11320
|
-
writes: tool?.writes ?? false
|
|
13487
|
+
writes: tool?.writes ?? false,
|
|
13488
|
+
declaredNote
|
|
11321
13489
|
}) ?? decision;
|
|
11322
13490
|
});
|
|
11323
13491
|
return () => off?.();
|
|
@@ -11347,12 +13515,7 @@ function apply(ctx) {
|
|
|
11347
13515
|
}, `${PLUGIN_ID}: 对话引用注入`);
|
|
11348
13516
|
ctx.effect(() => registerFactStream(ctx, facts), `${PLUGIN_ID}: 变更流`);
|
|
11349
13517
|
ctx.effect(() => registerPanelState(ctx, panelState), `${PLUGIN_ID}: 面板运行态上报(浏览器半侧报自己的取数与渲染收尾)`);
|
|
11350
|
-
ctx.effect(() => {
|
|
11351
|
-
return registerDiyDebugAct(ctx, debugActResource({
|
|
11352
|
-
store: () => ctx.get(DIY_DEBUG_SERVICE),
|
|
11353
|
-
agents: () => ctx.get("agents")
|
|
11354
|
-
}), { bump: (names) => facts.bump(names) });
|
|
11355
|
-
}, `${PLUGIN_ID}: DIY 调试的三个键(用户按的)`);
|
|
13518
|
+
ctx.effect(() => registerDiyActs(ctx, { bump: (names) => facts.bump(names) }), `${PLUGIN_ID}: DIY 的两族键(用户按的)`);
|
|
11356
13519
|
ctx.effect(() => registerApi(ctx, routes), `${PLUGIN_ID}: HTTP 投影`);
|
|
11357
13520
|
}
|
|
11358
13521
|
//#endregion
|