@dsh-workbench/core 1.1.14 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/host/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir, tmpdir } from "node:os";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
|
-
import fs, { accessSync, closeSync, constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import path, { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import fs, { accessSync, appendFileSync, closeSync, constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import path, { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
5
5
|
import writeFileAtomic from "write-file-atomic";
|
|
6
6
|
import utc from "dayjs/plugin/utc.js";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -782,6 +782,15 @@ function writeTextFile(path, text, options = {}) {
|
|
|
782
782
|
writeFileAtomic.sync(path, text, atomicOptionsOf(mode, options));
|
|
783
783
|
}
|
|
784
784
|
/**
|
|
785
|
+
* 往文件末尾追加文本;文件或目录不存在就建。追加不是原子写,只给「日志/采集」这类只增不改的流。
|
|
786
|
+
* @param path 目标文件。
|
|
787
|
+
* @param text 追加内容。
|
|
788
|
+
*/
|
|
789
|
+
function appendTextFile(path, text) {
|
|
790
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
791
|
+
appendFileSync(path, text, { mode: modeOf(path, "keep") });
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
785
794
|
* `writeTextFile` 的异步版;语义与默认值相同。
|
|
786
795
|
* @param path 目标文件。
|
|
787
796
|
* @param text 文本内容。
|
|
@@ -3805,7 +3814,7 @@ function registerDiyRecordAct(ctx, resource, log) {
|
|
|
3805
3814
|
}
|
|
3806
3815
|
//#endregion
|
|
3807
3816
|
//#region ../shared/dist/host/long-task.js
|
|
3808
|
-
var LIVE = /* @__PURE__ */ new Set(["running", "needs-login"]);
|
|
3817
|
+
var LIVE$1 = /* @__PURE__ */ new Set(["running", "needs-login"]);
|
|
3809
3818
|
var RECENT_MS = 6e5;
|
|
3810
3819
|
function createLongTaskRegistry(deps) {
|
|
3811
3820
|
const runs = /* @__PURE__ */ new Map();
|
|
@@ -3817,7 +3826,7 @@ function createLongTaskRegistry(deps) {
|
|
|
3817
3826
|
const listeners = /* @__PURE__ */ new Set();
|
|
3818
3827
|
const live = (id) => {
|
|
3819
3828
|
const current = runs.get(id);
|
|
3820
|
-
if (current === void 0 || !LIVE.has(current.status)) return null;
|
|
3829
|
+
if (current === void 0 || !LIVE$1.has(current.status)) return null;
|
|
3821
3830
|
return current;
|
|
3822
3831
|
};
|
|
3823
3832
|
const notify = (change) => {
|
|
@@ -3854,13 +3863,13 @@ function createLongTaskRegistry(deps) {
|
|
|
3854
3863
|
if (snapshot.workspace !== workspace) continue;
|
|
3855
3864
|
newest.set(snapshot.target, snapshot);
|
|
3856
3865
|
}
|
|
3857
|
-
return [...newest.values()].filter((snapshot) => LIVE.has(snapshot.status) || epochOf(snapshot.at) >= floor).reverse().map((snapshot) => ({
|
|
3866
|
+
return [...newest.values()].filter((snapshot) => LIVE$1.has(snapshot.status) || epochOf(snapshot.at) >= floor).reverse().map((snapshot) => ({
|
|
3858
3867
|
...deps.view(snapshot),
|
|
3859
3868
|
id: snapshot.id
|
|
3860
3869
|
}));
|
|
3861
3870
|
},
|
|
3862
3871
|
live() {
|
|
3863
|
-
return [...runs.values()].filter((snapshot) => LIVE.has(snapshot.status));
|
|
3872
|
+
return [...runs.values()].filter((snapshot) => LIVE$1.has(snapshot.status));
|
|
3864
3873
|
},
|
|
3865
3874
|
start(options) {
|
|
3866
3875
|
const running = this.active(options.workspace, options.target);
|
|
@@ -5137,6 +5146,33 @@ function createWorkspaceStore(options) {
|
|
|
5137
5146
|
};
|
|
5138
5147
|
}
|
|
5139
5148
|
//#endregion
|
|
5149
|
+
//#region ../shared/dist/host/shell-render.js
|
|
5150
|
+
var QUOTED_HEAD = /^\s*['"]/;
|
|
5151
|
+
function callOperatorOf(kind, script) {
|
|
5152
|
+
if (kind !== "pwsh" || !QUOTED_HEAD.test(script)) return script;
|
|
5153
|
+
return `& ${script}`;
|
|
5154
|
+
}
|
|
5155
|
+
function invocationOf(kind, script) {
|
|
5156
|
+
return kind === "pwsh" ? {
|
|
5157
|
+
file: "pwsh",
|
|
5158
|
+
args: [
|
|
5159
|
+
"-NoProfile",
|
|
5160
|
+
"-NonInteractive",
|
|
5161
|
+
"-Command",
|
|
5162
|
+
callOperatorOf("pwsh", script)
|
|
5163
|
+
]
|
|
5164
|
+
} : {
|
|
5165
|
+
file: "bash",
|
|
5166
|
+
args: ["-c", script]
|
|
5167
|
+
};
|
|
5168
|
+
}
|
|
5169
|
+
function quotedArgvLine(argv, platform = process.platform) {
|
|
5170
|
+
return argv.map((one) => quoteShellArgFor(one, platform)).join(" ");
|
|
5171
|
+
}
|
|
5172
|
+
function quotedLineOf(invocation, platform = process.platform) {
|
|
5173
|
+
return quotedArgvLine([invocation.file, ...invocation.args], platform);
|
|
5174
|
+
}
|
|
5175
|
+
//#endregion
|
|
5140
5176
|
//#region ../shared/dist/host/shell.js
|
|
5141
5177
|
var DEFAULT_MAX_BYTES$3 = 8388608;
|
|
5142
5178
|
var GUARD_SLACK_MS = 1e3;
|
|
@@ -5208,7 +5244,7 @@ function insideOf(root, path) {
|
|
|
5208
5244
|
}
|
|
5209
5245
|
function productPolicy(ceiling, productRoot) {
|
|
5210
5246
|
return {
|
|
5211
|
-
mode: ceiling === "read-only" ? "read-only" : "workspace-write",
|
|
5247
|
+
mode: ceiling === "read-only" ? "read-only" : ceiling === "danger-full-access" ? "danger-full-access" : "workspace-write",
|
|
5212
5248
|
workspaceRoot: resolve(productRoot)
|
|
5213
5249
|
};
|
|
5214
5250
|
}
|
|
@@ -5221,6 +5257,9 @@ function workspacePolicy(ceiling, grant) {
|
|
|
5221
5257
|
workspaceRoot: resolve(grant.workspaceRoot)
|
|
5222
5258
|
};
|
|
5223
5259
|
}
|
|
5260
|
+
function atFilesystemRoot(path) {
|
|
5261
|
+
return parse(path).root === path;
|
|
5262
|
+
}
|
|
5224
5263
|
function commonDirectory(paths) {
|
|
5225
5264
|
const dirs = paths.map((path) => dirname(resolve(path)));
|
|
5226
5265
|
let shared = dirs[0];
|
|
@@ -5233,8 +5272,8 @@ function pathsPolicy(ceiling, paths) {
|
|
|
5233
5272
|
if (!paths.every((path) => isAbsolute(path))) throw new SeamError("paths-span-trees", "外部路径必须是绝对路径");
|
|
5234
5273
|
if (ceiling === "read-only") throw new SeamError("read-only", "当前部署是只读模式:这条命令不能写外部路径");
|
|
5235
5274
|
const root = commonDirectory(paths);
|
|
5236
|
-
if (root === void 0
|
|
5237
|
-
if (root
|
|
5275
|
+
if (root === void 0) throw new SeamError("paths-span-trees", "这次写入没有给出任何路径");
|
|
5276
|
+
if (atFilesystemRoot(root)) throw new SeamError("paths-span-trees", paths.length > 1 ? "这次写入跨了多个目录:请分开做,或把产物改到工作台目录" : "不能把整个文件系统声明成写入域");
|
|
5238
5277
|
return {
|
|
5239
5278
|
mode: "workspace-write",
|
|
5240
5279
|
workspaceRoot: root
|
|
@@ -5422,23 +5461,9 @@ function targetOf(request) {
|
|
|
5422
5461
|
throw new SeamError("scope-not-declared", "这条执行请求要么给 argv,要么给一条命令行,不能两个都给或都不给");
|
|
5423
5462
|
}
|
|
5424
5463
|
function displayOf(target) {
|
|
5425
|
-
return "argv" in target ? target.argv
|
|
5464
|
+
return "argv" in target ? quotedArgvLine(target.argv) : target.line;
|
|
5426
5465
|
}
|
|
5427
|
-
function
|
|
5428
|
-
return shellKind === "pwsh" ? {
|
|
5429
|
-
file: "pwsh",
|
|
5430
|
-
args: [
|
|
5431
|
-
"-NoProfile",
|
|
5432
|
-
"-NonInteractive",
|
|
5433
|
-
"-Command",
|
|
5434
|
-
line
|
|
5435
|
-
]
|
|
5436
|
-
} : {
|
|
5437
|
-
file: "bash",
|
|
5438
|
-
args: ["-c", line]
|
|
5439
|
-
};
|
|
5440
|
-
}
|
|
5441
|
-
function invocationOf(target, shellKind) {
|
|
5466
|
+
function targetInvocationOf(target, shellKind) {
|
|
5442
5467
|
if ("argv" in target) {
|
|
5443
5468
|
const [file, ...args] = target.argv;
|
|
5444
5469
|
if (file === void 0) throw new SeamError("scope-not-declared", "argv 是空的:至少给一个可执行文件");
|
|
@@ -5447,13 +5472,12 @@ function invocationOf(target, shellKind) {
|
|
|
5447
5472
|
args
|
|
5448
5473
|
};
|
|
5449
5474
|
}
|
|
5450
|
-
return
|
|
5475
|
+
return invocationOf(shellKind ?? shellKindOf(), target.line);
|
|
5451
5476
|
}
|
|
5452
5477
|
function sandboxedLineOf(target, shellKind) {
|
|
5453
|
-
|
|
5454
|
-
if (shellKind === void 0) return
|
|
5455
|
-
|
|
5456
|
-
return [invocation.file, ...invocation.args].map((one) => quoteShellArg(one)).join(" ");
|
|
5478
|
+
const line = "argv" in target ? quotedArgvLine(target.argv) : target.line;
|
|
5479
|
+
if (shellKind === void 0) return callOperatorOf(shellKindOf(), line);
|
|
5480
|
+
return callOperatorOf(shellKindOf(), quotedLineOf(invocationOf(shellKind, line)));
|
|
5457
5481
|
}
|
|
5458
5482
|
function createSeam(ports) {
|
|
5459
5483
|
const runtimeRoot = ports.runtimeRoot ?? workbenchRoot();
|
|
@@ -5505,7 +5529,7 @@ function createSeam(ports) {
|
|
|
5505
5529
|
}
|
|
5506
5530
|
}
|
|
5507
5531
|
async function runHost(request, target) {
|
|
5508
|
-
const invocation =
|
|
5532
|
+
const invocation = targetInvocationOf(target, request.shellKind);
|
|
5509
5533
|
const child = processes.spawn({
|
|
5510
5534
|
file: invocation.file,
|
|
5511
5535
|
args: invocation.args,
|
|
@@ -5561,7 +5585,7 @@ function createSeam(ports) {
|
|
|
5561
5585
|
};
|
|
5562
5586
|
}
|
|
5563
5587
|
function spawnHost(request) {
|
|
5564
|
-
const invocation =
|
|
5588
|
+
const invocation = targetInvocationOf(request.target, void 0);
|
|
5565
5589
|
const logFile = request.logFile ?? join(runtimeRoot, "logs", `${request.label ?? "spawn"}-${String(now())}.log`);
|
|
5566
5590
|
mkdirSync(dirname(logFile), { recursive: true });
|
|
5567
5591
|
const child = processes.spawn({
|
|
@@ -5625,7 +5649,7 @@ function createSeam(ports) {
|
|
|
5625
5649
|
};
|
|
5626
5650
|
}
|
|
5627
5651
|
function openHost(request) {
|
|
5628
|
-
const invocation =
|
|
5652
|
+
const invocation = targetInvocationOf(request.target, void 0);
|
|
5629
5653
|
const child = processes.spawn({
|
|
5630
5654
|
file: invocation.file,
|
|
5631
5655
|
args: invocation.args,
|
|
@@ -10130,25 +10154,6 @@ function actErrorOf(step) {
|
|
|
10130
10154
|
return step.stderr || step.stdout || "运行时报了个错";
|
|
10131
10155
|
}
|
|
10132
10156
|
var SWEEP_TIMEOUT_MS = 4e3;
|
|
10133
|
-
async function stopNetworkExact(seam, file, timeoutMs = SWEEP_TIMEOUT_MS) {
|
|
10134
|
-
const pidFile = quoteShellArg(`${file}.pid`);
|
|
10135
|
-
const reaperFile = quoteShellArg(`${file}.reaper`);
|
|
10136
|
-
const line = [
|
|
10137
|
-
`p="$(cat ${pidFile} 2>/dev/null)"`,
|
|
10138
|
-
`r="$(cat ${reaperFile} 2>/dev/null)"`,
|
|
10139
|
-
`[ -n "$p" ] && pkill -P "$p" 2>/dev/null`,
|
|
10140
|
-
`[ -n "$p" ] && kill "$p" 2>/dev/null`,
|
|
10141
|
-
`[ -n "$r" ] && pkill -P "$r" 2>/dev/null`,
|
|
10142
|
-
`[ -n "$r" ] && kill "$r" 2>/dev/null`,
|
|
10143
|
-
`if [ "$(cat ${pidFile} 2>/dev/null)" = "$p" ]; then rm -f ${pidFile} ${reaperFile}; fi`,
|
|
10144
|
-
"true"
|
|
10145
|
-
].join("; ");
|
|
10146
|
-
return (await seam.run({
|
|
10147
|
-
command: line,
|
|
10148
|
-
intent: productWrite(),
|
|
10149
|
-
timeoutMs
|
|
10150
|
-
})).exitCode === 0;
|
|
10151
|
-
}
|
|
10152
10157
|
//#endregion
|
|
10153
10158
|
//#region src/modules/panels/model/diy-logs.ts
|
|
10154
10159
|
var LOGS_KEY = "__dshDiyLogs";
|
|
@@ -10639,7 +10644,7 @@ function wrapFirst(argv, card) {
|
|
|
10639
10644
|
if (first === void 0 || first.startsWith("-")) return [...argv];
|
|
10640
10645
|
return [evalWrapped(first, card), ...argv.slice(1)];
|
|
10641
10646
|
}
|
|
10642
|
-
function argvOf(row, raw, unit) {
|
|
10647
|
+
function argvOf$1(row, raw, unit) {
|
|
10643
10648
|
const card = cardSelectorIn(unit);
|
|
10644
10649
|
const declared = stringsOf$1(raw["args"]);
|
|
10645
10650
|
const tokens = withToken(row.evalInCard === true && asText(raw["in"]) === "card" ? wrapFirst(declared, card) : declared, card);
|
|
@@ -10925,7 +10930,7 @@ async function runBrowserTool(row, ctx, raw, exec) {
|
|
|
10925
10930
|
if (needsCardOf(row, page.args) && page.unit === "") throw new Error("要卡作用域(scope=card / $card / in=card)就得给一个 unit —— 这个工作区里不止一个模块时,我不知道你说哪一张卡。");
|
|
10926
10931
|
const requested = asNumber(page.args["timeoutMs"]);
|
|
10927
10932
|
const timeoutMs = requested !== void 0 && requested > 0 ? requested : DEFAULT_TIMEOUT_MS$4;
|
|
10928
|
-
const argv = argvOf(row, page.args, page.unit);
|
|
10933
|
+
const argv = argvOf$1(row, page.args, page.unit);
|
|
10929
10934
|
const clip = row.clip === true ? clipOf(page.args["clip"]) : void 0;
|
|
10930
10935
|
const clipAsked = row.clip === true && page.args["clip"] !== void 0;
|
|
10931
10936
|
let file = "";
|
|
@@ -11076,8 +11081,17 @@ function prepareStaging(unit, resume) {
|
|
|
11076
11081
|
force: true
|
|
11077
11082
|
});
|
|
11078
11083
|
mkdirSync(staging, { recursive: true });
|
|
11084
|
+
if (resume) seedJournal(staging, unit);
|
|
11079
11085
|
return staging;
|
|
11080
11086
|
}
|
|
11087
|
+
function seedJournal(staging, unit) {
|
|
11088
|
+
const to = journalFileOf(staging, unit);
|
|
11089
|
+
if (existsSync(to)) return;
|
|
11090
|
+
const from = join(workbenchRoot(), "releases", `${unit}.json`);
|
|
11091
|
+
if (!existsSync(from)) return;
|
|
11092
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
11093
|
+
copyFileSync(from, to);
|
|
11094
|
+
}
|
|
11081
11095
|
function cleanupStaging(unit) {
|
|
11082
11096
|
rmSync(stagingDirOf(unit), {
|
|
11083
11097
|
recursive: true,
|
|
@@ -11136,13 +11150,34 @@ function resumablePayload(payload, unit) {
|
|
|
11136
11150
|
}
|
|
11137
11151
|
function outcomeOf$1(result, unit) {
|
|
11138
11152
|
const payload = jsonOf(result.stdout);
|
|
11139
|
-
if (result.exitCode === 3 && payload !== void 0) return {
|
|
11140
|
-
|
|
11153
|
+
if (result.exitCode === 3 && payload !== void 0) return {
|
|
11154
|
+
kind: "needs-user",
|
|
11155
|
+
payload: needsUserPayload(payload)
|
|
11156
|
+
};
|
|
11157
|
+
if (result.exitCode === 4 && payload !== void 0) return {
|
|
11158
|
+
kind: "resumable",
|
|
11159
|
+
payload: resumablePayload(payload, unit)
|
|
11160
|
+
};
|
|
11141
11161
|
if (result.exitCode !== 0 || payload === void 0) {
|
|
11142
11162
|
const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-6).join(" | ");
|
|
11143
|
-
return {
|
|
11163
|
+
return {
|
|
11164
|
+
kind: "failed",
|
|
11165
|
+
error: /* @__PURE__ */ new Error(`发布没走通(exit ${String(result.exitCode)})${tail === "" ? "" : `:${tail}`}`)
|
|
11166
|
+
};
|
|
11167
|
+
}
|
|
11168
|
+
return {
|
|
11169
|
+
kind: "done",
|
|
11170
|
+
payload
|
|
11171
|
+
};
|
|
11172
|
+
}
|
|
11173
|
+
function publishProblemOf(payload) {
|
|
11174
|
+
if (payload["ok"] === false) {
|
|
11175
|
+
const reason = asText(payload["text"]);
|
|
11176
|
+
return `市场索引这一步回的是失败,这次没发出去${reason === "" ? "" : `:${reason}`}`;
|
|
11144
11177
|
}
|
|
11145
|
-
return
|
|
11178
|
+
if (asText(payload["target"]) !== "market") return void 0;
|
|
11179
|
+
if (asText(asRecord$1(payload["market"])["url"]) !== "" && asText(payload["install"]) !== "") return void 0;
|
|
11180
|
+
return "市场索引这一步没给出市场地址或安装命令 —— 这次没发出去。重发一次;再这样先把本地索引镜像同步到 origin 再发。";
|
|
11146
11181
|
}
|
|
11147
11182
|
function successPayloadOf(payload, found, options) {
|
|
11148
11183
|
const name = asText(payload["name"]);
|
|
@@ -11313,7 +11348,8 @@ function registerPublishTool(ctx, tools, deps) {
|
|
|
11313
11348
|
});
|
|
11314
11349
|
if (verify || refresh) {
|
|
11315
11350
|
const outcome = outcomeOf$1(await run([], { intent: productWrite() }), found.unit);
|
|
11316
|
-
if ("
|
|
11351
|
+
if (outcome.kind === "failed") throw outcome.error;
|
|
11352
|
+
if (outcome.kind !== "done") return plainJson(outcome.payload);
|
|
11317
11353
|
return plainJson(successPayloadOf(outcome.payload, found, { verify }));
|
|
11318
11354
|
}
|
|
11319
11355
|
const staging = prepareStaging(found.unit, resume);
|
|
@@ -11325,7 +11361,8 @@ function registerPublishTool(ctx, tools, deps) {
|
|
|
11325
11361
|
env
|
|
11326
11362
|
}), found.unit);
|
|
11327
11363
|
mirrorJournal(staging, found.unit);
|
|
11328
|
-
if ("
|
|
11364
|
+
if (firstOutcome.kind === "failed") throw firstOutcome.error;
|
|
11365
|
+
if (firstOutcome.kind !== "done") return plainJson(firstOutcome.payload);
|
|
11329
11366
|
const first = firstOutcome.payload;
|
|
11330
11367
|
if (first["stop"] !== "workspace-only" || asText(first["target"]) === "local") {
|
|
11331
11368
|
cleanupStaging(found.unit);
|
|
@@ -11336,9 +11373,13 @@ function registerPublishTool(ctx, tools, deps) {
|
|
|
11336
11373
|
env
|
|
11337
11374
|
}), found.unit);
|
|
11338
11375
|
mirrorJournal(staging, found.unit);
|
|
11339
|
-
if ("
|
|
11376
|
+
if (indexOutcome.kind === "failed") throw indexOutcome.error;
|
|
11377
|
+
if (indexOutcome.kind !== "done") return plainJson(indexOutcome.payload);
|
|
11378
|
+
const merged = adoptTarball(withPhasePayloads(first, indexOutcome.payload), found.unit);
|
|
11379
|
+
const problem = publishProblemOf(merged);
|
|
11380
|
+
if (problem !== void 0) throw new Error(problem);
|
|
11340
11381
|
cleanupStaging(found.unit);
|
|
11341
|
-
return plainJson(successPayloadOf(
|
|
11382
|
+
return plainJson(successPayloadOf(merged, found, { verify: false }));
|
|
11342
11383
|
}
|
|
11343
11384
|
};
|
|
11344
11385
|
return tools.register(publishTool);
|
|
@@ -11718,7 +11759,7 @@ function ageText(at, now) {
|
|
|
11718
11759
|
function stampOf(at) {
|
|
11719
11760
|
return `${dayjs(at).utc().format("HH:mm:ss")}Z`;
|
|
11720
11761
|
}
|
|
11721
|
-
function readText$
|
|
11762
|
+
function readText$2(read, now) {
|
|
11722
11763
|
const when = `${stampOf(read.at)} · ${ageText(read.at, now)}`;
|
|
11723
11764
|
if (read.phase === "ready") return `${read.route} 读到了(${when})`;
|
|
11724
11765
|
if (read.phase === "loading") return `${read.route} 正在读(${when})`;
|
|
@@ -11735,7 +11776,7 @@ function renderPanel(options) {
|
|
|
11735
11776
|
const title = `—— ${row.unit}${row.name === "" ? "" : `(${row.name})`} · ${healthText(row.health)}`;
|
|
11736
11777
|
const report = row.report;
|
|
11737
11778
|
if (report === void 0) return [title, ` 没上报:卡片没渲染出来,或者用户那一页还没刷新到带这份补丁的版本`].join("\n");
|
|
11738
|
-
const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText$
|
|
11779
|
+
const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText$2(one, now)}`);
|
|
11739
11780
|
const more = report.cards.length > 6 ? [` 数据 · …还有 ${String(report.cards.length - 6)} 条`] : [];
|
|
11740
11781
|
const crash = report.crash === void 0 ? " 崩过:没有" : ` 崩过:是 —— ${report.crash.reason}(${stampOf(report.crash.at)} · 共 ${String(report.crash.count)} 次)`;
|
|
11741
11782
|
return [
|
|
@@ -12344,6 +12385,197 @@ function changelogOf(dir, version, note, kind) {
|
|
|
12344
12385
|
};
|
|
12345
12386
|
}
|
|
12346
12387
|
//#endregion
|
|
12388
|
+
//#region src/shared/api/host/diy-capture-live.ts
|
|
12389
|
+
var LIVE_POLL_MS = 3e3;
|
|
12390
|
+
var LIVE_ARM_MS = 2e3;
|
|
12391
|
+
var LIVE_SNAP_TIMEOUT_MS = 15e3;
|
|
12392
|
+
var LIVE_TAIL_LINES = 2e4;
|
|
12393
|
+
var STATIC_URL = new RegExp(`^(data:)|(\\.(js|css|png|jpe?g|gif|svg|woff2?|ico|map|webp|mp4)(\\?|$))`, "i");
|
|
12394
|
+
var LIVE = /* @__PURE__ */ new Map();
|
|
12395
|
+
function plainOf(value) {
|
|
12396
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
12397
|
+
}
|
|
12398
|
+
function argvOf(command, session, args) {
|
|
12399
|
+
return [
|
|
12400
|
+
command === "" ? "runtime" : command,
|
|
12401
|
+
"browser",
|
|
12402
|
+
session,
|
|
12403
|
+
...args,
|
|
12404
|
+
"--window",
|
|
12405
|
+
"background"
|
|
12406
|
+
];
|
|
12407
|
+
}
|
|
12408
|
+
function parsedRecord(text) {
|
|
12409
|
+
let parsed;
|
|
12410
|
+
try {
|
|
12411
|
+
parsed = JSON.parse(text);
|
|
12412
|
+
} catch {
|
|
12413
|
+
return;
|
|
12414
|
+
}
|
|
12415
|
+
return plainOf(parsed);
|
|
12416
|
+
}
|
|
12417
|
+
function withoutStatic(record) {
|
|
12418
|
+
const inside = (Array.isArray(record["entries"]) ? record["entries"] : []).filter((one) => !STATIC_URL.test(String(plainOf(one)?.["url"] ?? "")));
|
|
12419
|
+
return inside.length === 0 ? "" : JSON.stringify({
|
|
12420
|
+
...record,
|
|
12421
|
+
entries: inside
|
|
12422
|
+
});
|
|
12423
|
+
}
|
|
12424
|
+
function filterStaticLines(raw) {
|
|
12425
|
+
const document = parsedRecord(raw.trim());
|
|
12426
|
+
if (document !== void 0 && Array.isArray(document["entries"])) return withoutStatic(document);
|
|
12427
|
+
const kept = [];
|
|
12428
|
+
for (const line of raw.split("\n")) {
|
|
12429
|
+
const text = line.trim();
|
|
12430
|
+
if (text === "") continue;
|
|
12431
|
+
const record = parsedRecord(text);
|
|
12432
|
+
if (record === void 0 || !Array.isArray(record["entries"])) {
|
|
12433
|
+
kept.push(text);
|
|
12434
|
+
continue;
|
|
12435
|
+
}
|
|
12436
|
+
const compact = withoutStatic(record);
|
|
12437
|
+
if (compact !== "") kept.push(compact);
|
|
12438
|
+
}
|
|
12439
|
+
return kept.join("\n");
|
|
12440
|
+
}
|
|
12441
|
+
async function captureTickOnce(seam, command, session, file, timeoutMs = LIVE_SNAP_TIMEOUT_MS) {
|
|
12442
|
+
const step = await seam.run({
|
|
12443
|
+
argv: argvOf(command, session, [
|
|
12444
|
+
"network",
|
|
12445
|
+
"--all",
|
|
12446
|
+
"--raw"
|
|
12447
|
+
]),
|
|
12448
|
+
intent: productWrite(),
|
|
12449
|
+
timeoutMs
|
|
12450
|
+
});
|
|
12451
|
+
const round = step.exitCode === 0 ? "ok" : "fail";
|
|
12452
|
+
writeTextFile(`${file}.first`, round);
|
|
12453
|
+
const text = filterStaticLines(step.stdout);
|
|
12454
|
+
if (text !== "") appendTextFile(file, `${text}\n`);
|
|
12455
|
+
return round;
|
|
12456
|
+
}
|
|
12457
|
+
async function startLiveCapture(input) {
|
|
12458
|
+
const { seam, command, session, file } = input;
|
|
12459
|
+
stopLiveCapture(file);
|
|
12460
|
+
writeTextFile(file, "");
|
|
12461
|
+
for (const suffix of [
|
|
12462
|
+
".first",
|
|
12463
|
+
".last",
|
|
12464
|
+
".round",
|
|
12465
|
+
".pid",
|
|
12466
|
+
".reaper"
|
|
12467
|
+
]) rmSync(`${file}${suffix}`, { force: true });
|
|
12468
|
+
try {
|
|
12469
|
+
const arm = await seam.run({
|
|
12470
|
+
argv: argvOf(command, session, [
|
|
12471
|
+
"network",
|
|
12472
|
+
"--follow",
|
|
12473
|
+
"--all"
|
|
12474
|
+
]),
|
|
12475
|
+
intent: productWrite(),
|
|
12476
|
+
timeoutMs: LIVE_ARM_MS
|
|
12477
|
+
});
|
|
12478
|
+
if (!arm.timedOut && arm.exitCode !== 0) return false;
|
|
12479
|
+
const text = arm.stdout.trim();
|
|
12480
|
+
if (text !== "") appendTextFile(file, `${text}\n`);
|
|
12481
|
+
} catch {
|
|
12482
|
+
return false;
|
|
12483
|
+
}
|
|
12484
|
+
const entry = {
|
|
12485
|
+
file,
|
|
12486
|
+
seam,
|
|
12487
|
+
command,
|
|
12488
|
+
session,
|
|
12489
|
+
timer: void 0,
|
|
12490
|
+
stopped: false
|
|
12491
|
+
};
|
|
12492
|
+
LIVE.set(file, entry);
|
|
12493
|
+
scheduleTick(entry);
|
|
12494
|
+
return true;
|
|
12495
|
+
}
|
|
12496
|
+
function scheduleTick(entry) {
|
|
12497
|
+
entry.timer = setTimeout(() => {
|
|
12498
|
+
runTick(entry);
|
|
12499
|
+
}, LIVE_POLL_MS);
|
|
12500
|
+
entry.timer.unref?.();
|
|
12501
|
+
}
|
|
12502
|
+
async function runTick(entry) {
|
|
12503
|
+
if (entry.stopped) return;
|
|
12504
|
+
try {
|
|
12505
|
+
await captureTickOnce(entry.seam, entry.command, entry.session, entry.file);
|
|
12506
|
+
} catch {
|
|
12507
|
+
entry.timer = void 0;
|
|
12508
|
+
}
|
|
12509
|
+
if (entry.stopped) return;
|
|
12510
|
+
scheduleTick(entry);
|
|
12511
|
+
}
|
|
12512
|
+
function stopLiveCapture(file) {
|
|
12513
|
+
const live = LIVE.get(file);
|
|
12514
|
+
for (const suffix of [
|
|
12515
|
+
".pid",
|
|
12516
|
+
".reaper",
|
|
12517
|
+
".round"
|
|
12518
|
+
]) rmSync(`${file}${suffix}`, { force: true });
|
|
12519
|
+
if (live !== void 0) {
|
|
12520
|
+
live.stopped = true;
|
|
12521
|
+
if (live.timer !== void 0) clearTimeout(live.timer);
|
|
12522
|
+
LIVE.delete(file);
|
|
12523
|
+
}
|
|
12524
|
+
return live !== void 0 || existsSync(`${file}.last`) || existsSync(`${file}.first`);
|
|
12525
|
+
}
|
|
12526
|
+
function liveCaptureOf(file) {
|
|
12527
|
+
return LIVE.has(file);
|
|
12528
|
+
}
|
|
12529
|
+
function readText$1(file) {
|
|
12530
|
+
try {
|
|
12531
|
+
return readFileSync(file, "utf8");
|
|
12532
|
+
} catch {
|
|
12533
|
+
return "";
|
|
12534
|
+
}
|
|
12535
|
+
}
|
|
12536
|
+
function readCaptureLines(file) {
|
|
12537
|
+
return (readText$1(file).match(/\n/g) ?? []).length;
|
|
12538
|
+
}
|
|
12539
|
+
function readCaptureRound(file) {
|
|
12540
|
+
const text = readText$1(`${file}.first`).trim();
|
|
12541
|
+
return text === "ok" || text === "fail" ? text : "";
|
|
12542
|
+
}
|
|
12543
|
+
function readCaptureText(file) {
|
|
12544
|
+
const text = readText$1(file);
|
|
12545
|
+
if (text === "") return {
|
|
12546
|
+
text: "",
|
|
12547
|
+
truncated: false
|
|
12548
|
+
};
|
|
12549
|
+
const lines = text.split("\n");
|
|
12550
|
+
if (text.endsWith("\n")) lines.pop();
|
|
12551
|
+
const kept = lines.slice(-2e4);
|
|
12552
|
+
return {
|
|
12553
|
+
text: kept.length === 0 ? "" : `${kept.join("\n")}\n`,
|
|
12554
|
+
truncated: lines.length > LIVE_TAIL_LINES
|
|
12555
|
+
};
|
|
12556
|
+
}
|
|
12557
|
+
function readCaptureLinesWithKey(file, key) {
|
|
12558
|
+
const text = readText$1(file);
|
|
12559
|
+
if (text === "") return "";
|
|
12560
|
+
const compact = `"key":"${key}"`;
|
|
12561
|
+
const spaced = `"key": "${key}"`;
|
|
12562
|
+
const out = [];
|
|
12563
|
+
for (const line of text.split("\n")) {
|
|
12564
|
+
if (out.length >= 20) break;
|
|
12565
|
+
if (line.includes(compact) || line.includes(spaced)) out.push(line);
|
|
12566
|
+
}
|
|
12567
|
+
return out.length === 0 ? "" : `${out.join("\n")}\n`;
|
|
12568
|
+
}
|
|
12569
|
+
function keepCaptureFile(file) {
|
|
12570
|
+
const last = `${file}.last`;
|
|
12571
|
+
try {
|
|
12572
|
+
renameSync(file, last);
|
|
12573
|
+
return statSync(last).size > 0;
|
|
12574
|
+
} catch {
|
|
12575
|
+
return false;
|
|
12576
|
+
}
|
|
12577
|
+
}
|
|
12578
|
+
//#endregion
|
|
12347
12579
|
//#region src/modules/panels/model/diy-observers.ts
|
|
12348
12580
|
async function stopObservers(ctx, workspace, session = BROWSER_SCREEN_SESSION) {
|
|
12349
12581
|
const seam = seamOf(ctx);
|
|
@@ -12353,9 +12585,9 @@ async function stopObservers(ctx, workspace, session = BROWSER_SCREEN_SESSION) {
|
|
|
12353
12585
|
};
|
|
12354
12586
|
const file = join(artifactsDirOf(workspace), `net-${session}.jsonl`);
|
|
12355
12587
|
const command = cliCommandOf(ctx);
|
|
12356
|
-
const [
|
|
12588
|
+
const [stopped, step] = await Promise.all([Promise.resolve(stopLiveCapture(file)), actBrowser(seam, command, session, ["eval", LOGS_STOP_SCRIPT], SWEEP_TIMEOUT_MS)]);
|
|
12357
12589
|
return {
|
|
12358
|
-
net,
|
|
12590
|
+
net: stopped || !liveCaptureOf(file),
|
|
12359
12591
|
logs: typeof step.payload?.["unhooked"] === "number"
|
|
12360
12592
|
};
|
|
12361
12593
|
}
|
|
@@ -13160,19 +13392,13 @@ function pickOf(entries, key, at = null) {
|
|
|
13160
13392
|
//#endregion
|
|
13161
13393
|
//#region src/modules/panels/model/diy-record.ts
|
|
13162
13394
|
var OPEN_TIMEOUT_MS = 3e4;
|
|
13163
|
-
var START_TIMEOUT_MS = 8e3;
|
|
13164
13395
|
var SETTLE_MS = 2e3;
|
|
13165
13396
|
var WAIT_CAP_MS = 9e5;
|
|
13166
13397
|
var POLL_MS = 1e3;
|
|
13167
|
-
var CAPTURE_TAIL_LINES =
|
|
13168
|
-
var CAPTURE_POLL_S = 3;
|
|
13169
|
-
var CAPTURE_ARM_S = 2;
|
|
13170
|
-
var STATIC_URL_PATTERN = `^(data:)|(\\.(js|css|png|jpe?g|gif|svg|woff2?|ico|map|webp|mp4)(\\?|$))`;
|
|
13398
|
+
var CAPTURE_TAIL_LINES = LIVE_TAIL_LINES;
|
|
13171
13399
|
var RECORD_WATCH_MS = 3e3;
|
|
13172
13400
|
var RECORD_WATCH_TRIES = 5;
|
|
13173
|
-
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\")}});";
|
|
13174
13401
|
var DETAIL_MAX_BYTES = 6e4;
|
|
13175
|
-
var HOST_PID = String(process.pid);
|
|
13176
13402
|
var pauseFor = (ms) => new Promise((resolve) => {
|
|
13177
13403
|
setTimeout(resolve, ms);
|
|
13178
13404
|
});
|
|
@@ -13221,40 +13447,13 @@ async function takeShotOf(input, held, n) {
|
|
|
13221
13447
|
}, Date.now());
|
|
13222
13448
|
}
|
|
13223
13449
|
}
|
|
13224
|
-
async function startRecordCapture(seam, command, session, file
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
session,
|
|
13229
|
-
"network",
|
|
13230
|
-
"--follow",
|
|
13231
|
-
"--all",
|
|
13232
|
-
"--window",
|
|
13233
|
-
"background"
|
|
13234
|
-
]);
|
|
13235
|
-
const snap = makeLine(bin, [
|
|
13236
|
-
"browser",
|
|
13450
|
+
async function startRecordCapture(seam, command, session, file) {
|
|
13451
|
+
return startLiveCapture({
|
|
13452
|
+
seam,
|
|
13453
|
+
command,
|
|
13237
13454
|
session,
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
"--raw",
|
|
13241
|
-
"--window",
|
|
13242
|
-
"background"
|
|
13243
|
-
]);
|
|
13244
|
-
const line = [
|
|
13245
|
-
`f=${quoteShellArg(file)}`,
|
|
13246
|
-
`r=${quoteShellArg(`${file}.round`)}`,
|
|
13247
|
-
": > \"$f\"",
|
|
13248
|
-
"rm -f \"$f.last\" \"$f.first\" \"$r\"",
|
|
13249
|
-
`( ${arm} >> "$f" 2>/dev/null & p=$!; sleep ${String(CAPTURE_ARM_S)}; kill "$p" 2>/dev/null )`,
|
|
13250
|
-
`( 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"`,
|
|
13251
|
-
"kill -0 \"$(cat \"$f.pid\" 2>/dev/null)\" 2>/dev/null"
|
|
13252
|
-
].join("; ");
|
|
13253
|
-
return (await seam.run({
|
|
13254
|
-
command: line,
|
|
13255
|
-
intent: productWrite(),
|
|
13256
|
-
timeoutMs: START_TIMEOUT_MS
|
|
13257
|
-
})).exitCode === 0;
|
|
13455
|
+
file
|
|
13456
|
+
});
|
|
13258
13457
|
}
|
|
13259
13458
|
async function releaseLeftoversOf(input) {
|
|
13260
13459
|
await releaseQuietly({
|
|
@@ -13267,7 +13466,7 @@ async function releaseLeftoversOf(input) {
|
|
|
13267
13466
|
}
|
|
13268
13467
|
async function stopCaptureQuiet(input) {
|
|
13269
13468
|
try {
|
|
13270
|
-
return
|
|
13469
|
+
return stopLiveCapture(input.captureFile);
|
|
13271
13470
|
} catch {
|
|
13272
13471
|
return false;
|
|
13273
13472
|
}
|
|
@@ -13372,64 +13571,22 @@ function watchRecordFinish(input) {
|
|
|
13372
13571
|
};
|
|
13373
13572
|
setTimeout(tick, 0);
|
|
13374
13573
|
}
|
|
13375
|
-
|
|
13376
|
-
const
|
|
13377
|
-
|
|
13378
|
-
intent: productWrite(),
|
|
13379
|
-
timeoutMs
|
|
13380
|
-
});
|
|
13381
|
-
const value = Number.parseInt(step.stdout.trim() === "" ? "0" : step.stdout.trim(), 10);
|
|
13382
|
-
return Number.isFinite(value) ? value : 0;
|
|
13383
|
-
}
|
|
13384
|
-
async function captureRoundOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
|
|
13385
|
-
const value = (await seam.run({
|
|
13386
|
-
command: `cat ${quoteShellArg(`${file}.first`)} 2>/dev/null || true`,
|
|
13387
|
-
intent: productWrite(),
|
|
13388
|
-
timeoutMs
|
|
13389
|
-
})).stdout.trim();
|
|
13390
|
-
return value === "ok" || value === "fail" ? value : "";
|
|
13391
|
-
}
|
|
13392
|
-
async function captureTextOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
|
|
13393
|
-
const step = await seam.run({
|
|
13394
|
-
command: `tail -n ${String(CAPTURE_TAIL_LINES)} ${quoteShellArg(file)} 2>/dev/null || true`,
|
|
13395
|
-
intent: productWrite(),
|
|
13396
|
-
timeoutMs
|
|
13397
|
-
});
|
|
13574
|
+
function captureReadOf(file) {
|
|
13575
|
+
const totalLines = readCaptureLines(file);
|
|
13576
|
+
const text = readCaptureText(file);
|
|
13398
13577
|
return {
|
|
13399
|
-
text:
|
|
13400
|
-
truncated: step.truncated
|
|
13401
|
-
};
|
|
13402
|
-
}
|
|
13403
|
-
async function captureReadOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
|
|
13404
|
-
const totalLines = await captureLinesOf(seam, file, timeoutMs);
|
|
13405
|
-
const read = await captureTextOf(seam, file, timeoutMs);
|
|
13406
|
-
const round = await captureRoundOf(seam, file, timeoutMs);
|
|
13407
|
-
return {
|
|
13408
|
-
text: read.text,
|
|
13578
|
+
text: text.text,
|
|
13409
13579
|
totalLines,
|
|
13410
13580
|
tailLimit: CAPTURE_TAIL_LINES,
|
|
13411
|
-
truncated:
|
|
13412
|
-
round
|
|
13581
|
+
truncated: text.truncated || totalLines > CAPTURE_TAIL_LINES,
|
|
13582
|
+
round: readCaptureRound(file)
|
|
13413
13583
|
};
|
|
13414
13584
|
}
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
timeoutMs
|
|
13421
|
-
})).exitCode === 0;
|
|
13422
|
-
}
|
|
13423
|
-
async function captureLinesWithKeyOf(seam, file, key, timeoutMs = START_TIMEOUT_MS) {
|
|
13424
|
-
const quoted = quoteShellArg(file);
|
|
13425
|
-
const compact = quoteShellArg(`"key":"${key}"`);
|
|
13426
|
-
const spaced = quoteShellArg(`"key": "${key}"`);
|
|
13427
|
-
const line = [`[ -f ${quoted} ] || exit 0`, `grep -m 20 -F ${compact} ${quoted} 2>/dev/null || grep -m 20 -F ${spaced} ${quoted} 2>/dev/null || true`].join("; ");
|
|
13428
|
-
return (await seam.run({
|
|
13429
|
-
command: line,
|
|
13430
|
-
intent: productWrite(),
|
|
13431
|
-
timeoutMs
|
|
13432
|
-
})).stdout;
|
|
13585
|
+
function keepCaptureOf(file) {
|
|
13586
|
+
return keepCaptureFile(file);
|
|
13587
|
+
}
|
|
13588
|
+
function captureLinesWithKeyOf(file, key) {
|
|
13589
|
+
return readCaptureLinesWithKey(file, key);
|
|
13433
13590
|
}
|
|
13434
13591
|
function bundleOf(capture, page, state) {
|
|
13435
13592
|
const entries = captureEntriesOf(capture.text);
|
|
@@ -13562,7 +13719,7 @@ async function closeRecordTab(seam, command, session, url) {
|
|
|
13562
13719
|
return (await actBrowser(seam, command, session, ["close"])).ok;
|
|
13563
13720
|
}
|
|
13564
13721
|
async function releaseRecord(input) {
|
|
13565
|
-
const capture =
|
|
13722
|
+
const capture = stopLiveCapture(input.captureFile);
|
|
13566
13723
|
return {
|
|
13567
13724
|
tab: await closeRecordTab(input.seam, input.command, input.session, input.url),
|
|
13568
13725
|
capture
|
|
@@ -13734,7 +13891,7 @@ function registerRecordTools(ctx, store) {
|
|
|
13734
13891
|
const armed = await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
|
|
13735
13892
|
const page = recordPageOf(armed.payload);
|
|
13736
13893
|
if (!armed.ok || !page.ok) throw new Error(`浮条没装上:${armed.stderr || armed.stdout || "那一页还在加载"} —— 等用户那一页站稳了再试。`);
|
|
13737
|
-
|
|
13894
|
+
stopLiveCapture(captureFile);
|
|
13738
13895
|
const capture = await startRecordCapture(seam, command, BROWSER_RECORD_SESSION, captureFile);
|
|
13739
13896
|
watchRecordFinish({
|
|
13740
13897
|
ctx,
|
|
@@ -13832,7 +13989,7 @@ function registerRecordTools(ctx, store) {
|
|
|
13832
13989
|
}
|
|
13833
13990
|
const state = store.live(Date.now()) ?? held;
|
|
13834
13991
|
const ledger = state.shots;
|
|
13835
|
-
const capture =
|
|
13992
|
+
const capture = captureReadOf(state.captureFile);
|
|
13836
13993
|
const bundle = bundleOf(capture, page, state);
|
|
13837
13994
|
const hookCaught = page.calls.length;
|
|
13838
13995
|
const nav = page.href !== "" && page.here !== "" && page.href !== page.here;
|
|
@@ -13943,7 +14100,7 @@ function registerRecordTools(ctx, store) {
|
|
|
13943
14100
|
if (index === null) throw new Error("给一个 key(清单里那一条的)或 index(清单里的序号)。");
|
|
13944
14101
|
if (held === null) throw new Error("收工之后只能用 key —— index 是\"那一场清单里\"的序号,这一场已经清掉了。");
|
|
13945
14102
|
page = recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
|
|
13946
|
-
const bundle = bundleOf(
|
|
14103
|
+
const bundle = bundleOf(captureReadOf(file), page, held);
|
|
13947
14104
|
const row = bundle.rows[index];
|
|
13948
14105
|
if (row === void 0) throw new Error(`这一段清单里没有第 ${String(index)} 条(一共 ${String(bundle.rows.length)} 条)—— 先 ${DIY_RECORD_READ_TOOL} 拿一份新的清单。`);
|
|
13949
14106
|
key = row.key;
|
|
@@ -13953,7 +14110,7 @@ function registerRecordTools(ctx, store) {
|
|
|
13953
14110
|
if (only !== null) return detailOfCall(only, maxBytes);
|
|
13954
14111
|
}
|
|
13955
14112
|
}
|
|
13956
|
-
const candidates = pickAllOf(captureEntriesOf(
|
|
14113
|
+
const candidates = pickAllOf(captureEntriesOf(captureLinesWithKeyOf(file, key)), key);
|
|
13957
14114
|
const fromCapture = pickOf(candidates, key, at);
|
|
13958
14115
|
if (fromCapture !== null) return detailOfEntry(fromCapture, maxBytes, candidates.length);
|
|
13959
14116
|
const over = held === null || held.phase === "finished";
|
|
@@ -14009,10 +14166,10 @@ function registerRecordTools(ctx, store) {
|
|
|
14009
14166
|
const command = cliCommandOf(ctx);
|
|
14010
14167
|
await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
|
|
14011
14168
|
const page = recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_STOP_SCRIPT])).payload);
|
|
14012
|
-
const bundle = bundleOf(
|
|
14169
|
+
const bundle = bundleOf(captureReadOf(held.captureFile), page, held);
|
|
14013
14170
|
const hookCaught = page.calls.length;
|
|
14014
14171
|
const noBody = bodyMissingOf(bundle.merged);
|
|
14015
|
-
const kept =
|
|
14172
|
+
const kept = keepCaptureOf(held.captureFile);
|
|
14016
14173
|
const cleaned = await releaseQuietly({
|
|
14017
14174
|
seam,
|
|
14018
14175
|
command,
|
|
@@ -7,4 +7,3 @@ export interface ActStep extends BrowserStep {
|
|
|
7
7
|
export declare function actBrowser(seam: Seam, command: string, session: string, argv: readonly string[], timeoutMs?: number, line?: LineMaker): Promise<ActStep>;
|
|
8
8
|
export declare function actErrorOf(step: ActStep): string;
|
|
9
9
|
export declare const SWEEP_TIMEOUT_MS = 4000;
|
|
10
|
-
export declare function stopNetworkExact(seam: Seam, file: string, timeoutMs?: number): Promise<boolean>;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { DshHostContext } from '@dsh-workbench/shared';
|
|
2
2
|
import type { RecordState, RecordStore } from '@dsh-workbench/shared/host';
|
|
3
3
|
import { type Seam } from '@dsh-workbench/shared/host';
|
|
4
|
-
import { type LineMaker } from './diy-browser';
|
|
5
4
|
import type { CaptureEntry } from './diy-record-capture';
|
|
6
5
|
import type { RecordCall, RecordListRow, RecordPageRead } from './diy-record-text';
|
|
7
6
|
export declare const OPEN_TIMEOUT_MS = 30000;
|
|
8
7
|
export declare const SETTLE_MS = 2000;
|
|
9
8
|
export declare const WAIT_CAP_MS: number;
|
|
10
9
|
export declare const POLL_MS = 1000;
|
|
11
|
-
export
|
|
10
|
+
export { STATIC_URL_PATTERN } from '../../../shared/api/host/diy-capture-live';
|
|
12
11
|
export declare const DETAIL_MAX_BYTES = 60000;
|
|
13
12
|
export declare const pauseFor: (ms: number) => Promise<void>;
|
|
14
13
|
export declare function waitOf(value: unknown): number;
|
|
@@ -17,7 +16,7 @@ export declare function captureFileOf(artifactsDir: string, unit: string): strin
|
|
|
17
16
|
export declare function lastCaptureFileOf(file: string): string;
|
|
18
17
|
export declare function shotFileOf(captureFile: string, unit: string, n: number): string;
|
|
19
18
|
export declare function takeShotOf(input: WatchFinishDeps, held: RecordState, n: number): Promise<void>;
|
|
20
|
-
export declare function startRecordCapture(seam: Seam, command: string, session: string, file: string
|
|
19
|
+
export declare function startRecordCapture(seam: Seam, command: string, session: string, file: string): Promise<boolean>;
|
|
21
20
|
export interface WatchFinishDeps {
|
|
22
21
|
readonly ctx: DshHostContext;
|
|
23
22
|
readonly seam: Seam;
|
|
@@ -44,15 +43,15 @@ export interface CaptureRead {
|
|
|
44
43
|
readonly truncated: boolean;
|
|
45
44
|
readonly round: string;
|
|
46
45
|
}
|
|
47
|
-
export declare function captureLinesOf(
|
|
48
|
-
export declare function captureRoundOf(
|
|
49
|
-
export declare function captureTextOf(
|
|
46
|
+
export declare function captureLinesOf(file: string): number;
|
|
47
|
+
export declare function captureRoundOf(file: string): string;
|
|
48
|
+
export declare function captureTextOf(file: string): {
|
|
50
49
|
readonly text: string;
|
|
51
50
|
readonly truncated: boolean;
|
|
52
|
-
}
|
|
53
|
-
export declare function captureReadOf(
|
|
54
|
-
export declare function keepCaptureOf(
|
|
55
|
-
export declare function captureLinesWithKeyOf(
|
|
51
|
+
};
|
|
52
|
+
export declare function captureReadOf(file: string): CaptureRead;
|
|
53
|
+
export declare function keepCaptureOf(file: string): boolean;
|
|
54
|
+
export declare function captureLinesWithKeyOf(file: string, key: string): string;
|
|
56
55
|
export interface SegmentBundle {
|
|
57
56
|
readonly capture: CaptureRead;
|
|
58
57
|
readonly entries: readonly CaptureEntry[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Seam } from '@dsh-workbench/shared/host';
|
|
2
|
+
export declare const LIVE_POLL_MS = 3000;
|
|
3
|
+
export declare const LIVE_ARM_MS = 2000;
|
|
4
|
+
export declare const LIVE_SNAP_TIMEOUT_MS = 15000;
|
|
5
|
+
export declare const LIVE_TAIL_LINES = 20000;
|
|
6
|
+
export declare const LIVE_KEY_LINES = 20;
|
|
7
|
+
export declare const STATIC_URL_PATTERN = "^(data:)|(\\.(js|css|png|jpe?g|gif|svg|woff2?|ico|map|webp|mp4)(\\?|$))";
|
|
8
|
+
export declare function filterStaticLines(raw: string): string;
|
|
9
|
+
export declare function captureTickOnce(seam: Seam, command: string, session: string, file: string, timeoutMs?: number): Promise<string>;
|
|
10
|
+
export declare function startLiveCapture(input: {
|
|
11
|
+
readonly seam: Seam;
|
|
12
|
+
readonly command: string;
|
|
13
|
+
readonly session: string;
|
|
14
|
+
readonly file: string;
|
|
15
|
+
}): Promise<boolean>;
|
|
16
|
+
export declare function stopLiveCapture(file: string): boolean;
|
|
17
|
+
export declare function liveCaptureOf(file: string): boolean;
|
|
18
|
+
export declare function readCaptureLines(file: string): number;
|
|
19
|
+
export declare function readCaptureRound(file: string): string;
|
|
20
|
+
export declare function readCaptureText(file: string): {
|
|
21
|
+
readonly text: string;
|
|
22
|
+
readonly truncated: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare function readCaptureLinesWithKey(file: string, key: string): string;
|
|
25
|
+
export declare function keepCaptureFile(file: string): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "个人工作台:Agent 工作台(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -97,8 +97,8 @@
|
|
|
97
97
|
"proper-lockfile": "^4.1.2",
|
|
98
98
|
"which": "^7.0.0",
|
|
99
99
|
"write-file-atomic": "^8.0.0",
|
|
100
|
-
"@dsh-workbench/adapters-default": "1.1.
|
|
101
|
-
"@dsh-workbench/shared": "1.1.
|
|
100
|
+
"@dsh-workbench/adapters-default": "1.1.16",
|
|
101
|
+
"@dsh-workbench/shared": "1.1.16"
|
|
102
102
|
},
|
|
103
103
|
"license": "MIT",
|
|
104
104
|
"repository": {
|