psyclaw 0.29.17 → 0.29.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/dist/apps/panel/index.html +441 -154
- package/dist/apps/panel/observability.js +87 -0
- package/dist/src/adapters/pi/extension.js +40 -2
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/chat.js +2 -0
- package/dist/src/chat.js.map +1 -1
- package/dist/src/cli.js +63 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/observability/config.d.ts +47 -0
- package/dist/src/observability/config.js +125 -0
- package/dist/src/observability/config.js.map +1 -0
- package/dist/src/observability/index.d.ts +29 -0
- package/dist/src/observability/index.js +75 -0
- package/dist/src/observability/index.js.map +1 -0
- package/dist/src/observability/node-sdks.d.ts +11 -0
- package/dist/src/observability/node-sdks.js +92 -0
- package/dist/src/observability/node-sdks.js.map +1 -0
- package/dist/src/observability/notice.d.ts +9 -0
- package/dist/src/observability/notice.js +43 -0
- package/dist/src/observability/notice.js.map +1 -0
- package/dist/src/observability/preference.d.ts +23 -0
- package/dist/src/observability/preference.js +102 -0
- package/dist/src/observability/preference.js.map +1 -0
- package/dist/src/observability/public-keys.d.ts +10 -0
- package/dist/src/observability/public-keys.js +11 -0
- package/dist/src/observability/public-keys.js.map +1 -0
- package/dist/src/orchestration/runner.js +21 -2
- package/dist/src/orchestration/runner.js.map +1 -1
- package/dist/src/panel/extension.js +5 -1
- package/dist/src/panel/extension.js.map +1 -1
- package/dist/src/panel/hub.d.ts +2 -0
- package/dist/src/panel/hub.js +12 -0
- package/dist/src/panel/hub.js.map +1 -1
- package/dist/src/panel/server.d.ts +4 -0
- package/dist/src/panel/server.js +188 -75
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/panel/workbench.js +15 -1
- package/dist/src/panel/workbench.js.map +1 -1
- package/dist/src/session/modes.d.ts +13 -0
- package/dist/src/session/modes.js +39 -3
- package/dist/src/session/modes.js.map +1 -1
- package/dist/src/style/cli-ui.js +2 -0
- package/dist/src/style/cli-ui.js.map +1 -1
- package/docs/PsyClaw/344/275/277/347/224/250/347/231/275/347/232/256/344/271/246_v0.29.15.md +907 -0
- package/docs//344/275/277/347/224/250/347/231/275/347/232/256/344/271/246.md +3 -0
- package/package.json +7 -2
- package/scripts/rebrand-pi.mjs +74 -1
- package/skills/recommended/catalog.json +4 -2
package/dist/src/panel/server.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { lstat, mkdir, readFile, readdir, realpath, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
6
5
|
import { fileURLToPath } from "node:url";
|
|
7
6
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
8
7
|
import { listRuns, projectRunSnapshot } from "./projection.js";
|
|
@@ -34,6 +33,8 @@ import { recommendedSkillTarget } from "../skills/recommended.js";
|
|
|
34
33
|
import { readUserSkillState, scanLocalSkills, setLocalSkillEnabled, userSkillId, } from "../skills/user-skills.js";
|
|
35
34
|
import { loadVerifyChecklist, markVerifyItem, skipUnverifiedItems } from "../verify/checklist.js";
|
|
36
35
|
import { readActiveAnalysisPlan } from "../analysis/plan.js";
|
|
36
|
+
import { browserConfigForPreference, injectBrowserObservabilityConfig } from "../observability/config.js";
|
|
37
|
+
import { readTelemetryPreference, telemetryPreferenceOptions, writeTelemetryPreference } from "../observability/preference.js";
|
|
37
38
|
async function readRecommendationState(root) {
|
|
38
39
|
try {
|
|
39
40
|
const value = JSON.parse(await readFile(join(root, ".psyclaw", "recommendations.json"), "utf8"));
|
|
@@ -433,6 +434,21 @@ function publicInstallPlan(plan) {
|
|
|
433
434
|
const { projectRoot: _projectRoot, ...publicFields } = plan;
|
|
434
435
|
return publicFields;
|
|
435
436
|
}
|
|
437
|
+
async function readObservabilityScript(panelHtmlPath) {
|
|
438
|
+
const candidates = [
|
|
439
|
+
join(dirname(panelHtmlPath), "observability.js"),
|
|
440
|
+
join(dirname(panelHtmlPath), "..", "shared", "observability.js"),
|
|
441
|
+
];
|
|
442
|
+
for (const candidate of candidates) {
|
|
443
|
+
try {
|
|
444
|
+
return await readFile(candidate, "utf8");
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
/* try the next known package path */
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
436
452
|
function pluginSourceIdentity(source) {
|
|
437
453
|
return source.trim().replace(/^git:/, "").replace(/\.git$/i, "").replace(/\/$/, "").toLocaleLowerCase();
|
|
438
454
|
}
|
|
@@ -462,6 +478,28 @@ function panelExternalToolInstallTask(root, item) {
|
|
|
462
478
|
"这是外部工具,不要将它宣称为来源仓库提供的 Skill 或 Plugin。完成后用自然语言报告实际安装位置、版本和验证结果。",
|
|
463
479
|
].join("\n");
|
|
464
480
|
}
|
|
481
|
+
function panelMcpInstallTask(root, item, prep) {
|
|
482
|
+
const id = String(item.id ?? "");
|
|
483
|
+
const name = String(item.name ?? id);
|
|
484
|
+
const sourceRef = String(item.sourceRef ?? "");
|
|
485
|
+
const suggestedCommand = typeof prep?.command === "string" && prep.command.trim()
|
|
486
|
+
? prep.command.trim()
|
|
487
|
+
: "请根据来源仓库的最新安装说明确定";
|
|
488
|
+
const suggestedTarget = typeof prep?.target === "string" && prep.target.trim()
|
|
489
|
+
? prep.target.trim()
|
|
490
|
+
: `.psyclaw/mcp/${id}.json`;
|
|
491
|
+
return [
|
|
492
|
+
`安装并配置推荐 MCP:${name} (${id})。`,
|
|
493
|
+
`来源网址:${sourceRef || "未提供;请停止并告知用户"}`,
|
|
494
|
+
`当前项目:${root}`,
|
|
495
|
+
`目录中的参考命令:${suggestedCommand}`,
|
|
496
|
+
`建议的项目配置记录:${suggestedTarget}`,
|
|
497
|
+
"用户已经通过 Panel 推荐页明确授权本次安装。你可以直接联网、克隆或下载来源、使用所需包管理器安装依赖、执行安装命令,并写入与该 MCP 有关的项目或用户级运行配置;不要再次要求用户批准这些安装步骤。",
|
|
498
|
+
"请先阅读来源仓库的实际安装与启动说明,再根据当前操作系统和宿主环境完成可运行的 stdio/HTTP MCP 配置。目录中的版本、命令和依赖只作参考,不得因为哈希、许可证、依赖或本地数据策略检查而中止安装。",
|
|
499
|
+
"安装完成后实际启动一次服务器或执行等价的最小连通性检查,确认启动命令和工作目录有效。不要输出、复制或提交 API Key、令牌及其他凭据。",
|
|
500
|
+
`成功后确保 .psyclaw/recommendations.json 的 mcp 列表包含 ${JSON.stringify(id)},说明写入了哪些配置,并提醒用户执行 /reload。若安装命令本身失败,直接诊断并修复;只有遇到必须由用户提供的凭据或外部软件许可证时才向用户说明。`,
|
|
501
|
+
].join("\n");
|
|
502
|
+
}
|
|
465
503
|
/** Metadata-only catalog for the optional panel. No install or credential read. */
|
|
466
504
|
async function panelCatalog(root) {
|
|
467
505
|
const scans = await discoverAgents();
|
|
@@ -590,6 +628,57 @@ async function panelCatalog(root) {
|
|
|
590
628
|
}
|
|
591
629
|
return { schemaVersion: "psyclaw/panel-catalog/v1", agents, models };
|
|
592
630
|
}
|
|
631
|
+
function packageRootCandidates() {
|
|
632
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
633
|
+
return [
|
|
634
|
+
join(moduleDir, "..", "..", ".."),
|
|
635
|
+
join(moduleDir, "..", "..", "..", ".."),
|
|
636
|
+
join(moduleDir, "..", ".."),
|
|
637
|
+
process.cwd(),
|
|
638
|
+
];
|
|
639
|
+
}
|
|
640
|
+
function isAllowlistedDocsRelative(rel) {
|
|
641
|
+
const normalized = rel.split(sep).join("/");
|
|
642
|
+
if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("/") || normalized.includes("\0")) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
return normalized === "docs" || normalized.startsWith("docs/")
|
|
646
|
+
|| normalized.startsWith("research-materials/whitepaper/");
|
|
647
|
+
}
|
|
648
|
+
/** Read Panel help whitepaper from package docs/, following a single .md link when present. */
|
|
649
|
+
async function readPanelWhitepaper() {
|
|
650
|
+
const stubRel = join("docs", "使用白皮书.md");
|
|
651
|
+
for (const root of packageRootCandidates()) {
|
|
652
|
+
const stubPath = join(root, stubRel);
|
|
653
|
+
try {
|
|
654
|
+
const rootReal = await realpath(root);
|
|
655
|
+
const stubReal = await realpath(stubPath);
|
|
656
|
+
const stubFromRoot = relative(rootReal, stubReal).split(sep).join("/");
|
|
657
|
+
if (stubFromRoot !== "docs/使用白皮书.md")
|
|
658
|
+
continue;
|
|
659
|
+
const stub = await readFile(stubReal, "utf8");
|
|
660
|
+
const linkMatch = stub.match(/\[[^\]]*\]\(([^)\s]+\.md)\)/);
|
|
661
|
+
if (linkMatch?.[1]) {
|
|
662
|
+
const targetPath = resolve(dirname(stubReal), linkMatch[1]);
|
|
663
|
+
try {
|
|
664
|
+
const targetReal = await realpath(targetPath);
|
|
665
|
+
const rel = relative(rootReal, targetReal).split(sep).join("/");
|
|
666
|
+
if (isAllowlistedDocsRelative(rel)) {
|
|
667
|
+
return { markdown: await readFile(targetReal, "utf8"), source: rel };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
/* linked file missing — fall through to stub */
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return { markdown: stub, source: stubFromRoot };
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
/* try next package root */
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
throw new Error("whitepaper not found");
|
|
681
|
+
}
|
|
593
682
|
async function recommendedSkills() {
|
|
594
683
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
595
684
|
const candidates = [
|
|
@@ -665,21 +754,6 @@ async function findRecommendedItem(kind, id) {
|
|
|
665
754
|
const prep = (catalog.installPrep ?? []).find((candidate) => candidate.id === id);
|
|
666
755
|
return { item, ...(prep === undefined ? {} : { prep }) };
|
|
667
756
|
}
|
|
668
|
-
function runShellCommand(command, cwd) {
|
|
669
|
-
return new Promise((resolve) => {
|
|
670
|
-
const child = spawn(command, { shell: true, cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
671
|
-
let output = "";
|
|
672
|
-
const append = (chunk) => {
|
|
673
|
-
output += String(chunk);
|
|
674
|
-
if (output.length > 24_000)
|
|
675
|
-
output = output.slice(-24_000);
|
|
676
|
-
};
|
|
677
|
-
child.stdout?.on("data", append);
|
|
678
|
-
child.stderr?.on("data", append);
|
|
679
|
-
child.on("error", (error) => resolve({ exitCode: 1, output: `spawn error: ${error.message}` }));
|
|
680
|
-
child.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
757
|
const USER_HOOK_ID = /^u-[a-z0-9][a-z0-9._-]{0,40}$/i;
|
|
684
758
|
const HOOK_EVENTS = new Set(["before-plan", "before-analysis", "before-delegation", "before-write", "after-analysis", "before-report", "after-report"]);
|
|
685
759
|
function sanitizeUserHooks(body) {
|
|
@@ -793,6 +867,7 @@ export function createPanelServer(root, options = {}) {
|
|
|
793
867
|
"/api/assistant",
|
|
794
868
|
"/api/crosscheck",
|
|
795
869
|
"/api/wake-options/respond",
|
|
870
|
+
"/api/telemetry",
|
|
796
871
|
];
|
|
797
872
|
if (request.method !== "GET" && request.method !== "HEAD" && !(request.method === "POST" && panelWriteRoutes.includes(url.pathname))) {
|
|
798
873
|
response.writeHead(405, { "content-type": "application/json", allow: "GET, HEAD" });
|
|
@@ -940,6 +1015,22 @@ export function createPanelServer(root, options = {}) {
|
|
|
940
1015
|
response.end(JSON.stringify({ schemaVersion: "psyclaw/panel-help/v1", ...sessionHelpDocument() }));
|
|
941
1016
|
return;
|
|
942
1017
|
}
|
|
1018
|
+
if (url.pathname === "/api/docs/whitepaper") {
|
|
1019
|
+
try {
|
|
1020
|
+
const doc = await readPanelWhitepaper();
|
|
1021
|
+
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
|
|
1022
|
+
response.end(JSON.stringify({
|
|
1023
|
+
schemaVersion: "psyclaw/panel-whitepaper/v1",
|
|
1024
|
+
source: doc.source,
|
|
1025
|
+
markdown: doc.markdown,
|
|
1026
|
+
}));
|
|
1027
|
+
}
|
|
1028
|
+
catch {
|
|
1029
|
+
response.writeHead(404, { "content-type": "application/json" });
|
|
1030
|
+
response.end(JSON.stringify({ error: "whitepaper not found" }));
|
|
1031
|
+
}
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
943
1034
|
if (url.pathname === "/api/files") {
|
|
944
1035
|
response.writeHead(200, { "content-type": "application/json" });
|
|
945
1036
|
response.end(JSON.stringify({ schemaVersion: "psyclaw/panel-files/v1", files: await listProjectFiles(root) }));
|
|
@@ -1159,6 +1250,7 @@ export function createPanelServer(root, options = {}) {
|
|
|
1159
1250
|
}
|
|
1160
1251
|
await options.installSkill(panelSkillInstallTask(root, found.item, scope));
|
|
1161
1252
|
const state = await readRecommendationState(root);
|
|
1253
|
+
state.skills = [...new Set([...state.skills, id])];
|
|
1162
1254
|
state.skillScopes = { ...(state.skillScopes ?? {}), [id]: scope };
|
|
1163
1255
|
await writeRecommendationState(root, state);
|
|
1164
1256
|
response.writeHead(202, { "content-type": "application/json" });
|
|
@@ -1169,7 +1261,32 @@ export function createPanelServer(root, options = {}) {
|
|
|
1169
1261
|
id,
|
|
1170
1262
|
scope,
|
|
1171
1263
|
reloadHint: "/reload",
|
|
1172
|
-
message: "
|
|
1264
|
+
message: "安装任务已交给当前 PsyClaw 对话中的模型;请回到终端会话查看进度。模型完成后执行 /reload。",
|
|
1265
|
+
}));
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (kind === "mcp") {
|
|
1269
|
+
if (options.installMcp === undefined) {
|
|
1270
|
+
response.writeHead(503, { "content-type": "application/json" });
|
|
1271
|
+
response.end(JSON.stringify({ error: "MCP 安装需要当前模型通道;请在 PsyClaw 对话中执行 /panel 后再安装。", reasonCode: "panel.mcp-installer-unavailable" }));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const sourceRef = String(found.item.sourceRef ?? "").trim();
|
|
1275
|
+
if (!sourceRef)
|
|
1276
|
+
throw new Error("MCP source is missing");
|
|
1277
|
+
await options.installMcp(panelMcpInstallTask(root, found.item, found.prep));
|
|
1278
|
+
const state = await readRecommendationState(root);
|
|
1279
|
+
state.mcp = [...new Set([...state.mcp, id])];
|
|
1280
|
+
await writeRecommendationState(root, state);
|
|
1281
|
+
response.writeHead(202, { "content-type": "application/json" });
|
|
1282
|
+
response.end(JSON.stringify({
|
|
1283
|
+
schemaVersion: "psyclaw/model-install-task/v1",
|
|
1284
|
+
ok: true,
|
|
1285
|
+
queued: true,
|
|
1286
|
+
id,
|
|
1287
|
+
reloadHint: "/reload",
|
|
1288
|
+
message: "安装与配置任务已交给当前 PsyClaw 对话中的模型;请回到终端会话查看进度。模型完成后执行 /reload。",
|
|
1289
|
+
...(typeof found.prep?.blockedReason === "string" ? { blockedReason: found.prep.blockedReason } : {}),
|
|
1173
1290
|
}));
|
|
1174
1291
|
return;
|
|
1175
1292
|
}
|
|
@@ -1200,61 +1317,8 @@ export function createPanelServer(root, options = {}) {
|
|
|
1200
1317
|
response.end(JSON.stringify({ schemaVersion: "psyclaw/external-tool-install-task/v1", ok: true, queued: true, id, message: "安装任务已交给当前模型;完成后会报告版本与验证结果。" }));
|
|
1201
1318
|
return;
|
|
1202
1319
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
response.writeHead(400, { "content-type": "application/json" });
|
|
1206
|
-
response.end(JSON.stringify({ error: "no install command is available for this item", blockedReason: found.prep?.blockedReason ?? null }));
|
|
1207
|
-
return;
|
|
1208
|
-
}
|
|
1209
|
-
const command = shellCommand;
|
|
1210
|
-
const startedAt = new Date().toISOString();
|
|
1211
|
-
const runId = `panel_${randomUUID().replaceAll("-", "")}`;
|
|
1212
|
-
const idempotencyKey = `panel:install:${sha256Text(`${kind}\u0000${id}\u0000${actor}`).slice(0, 24)}`;
|
|
1213
|
-
const receipt = {
|
|
1214
|
-
schemaVersion: "psyclaw/tool-receipt/v1",
|
|
1215
|
-
runId,
|
|
1216
|
-
taskId: `install:${kind}:${id}`,
|
|
1217
|
-
tool: "panel.install.execute",
|
|
1218
|
-
effect: "write",
|
|
1219
|
-
approval: "approved",
|
|
1220
|
-
idempotencyKey,
|
|
1221
|
-
ok: false,
|
|
1222
|
-
command,
|
|
1223
|
-
startedAt,
|
|
1224
|
-
};
|
|
1225
|
-
let exitCode;
|
|
1226
|
-
let output;
|
|
1227
|
-
({ exitCode, output } = await runShellCommand(command, root));
|
|
1228
|
-
receipt.ok = exitCode === 0;
|
|
1229
|
-
receipt.exitCode = exitCode;
|
|
1230
|
-
receipt.finishedAt = new Date().toISOString();
|
|
1231
|
-
const receiptPath = await assertSafeProjectPath(root, `.psyclaw/manifests/${runId}.receipt.json`);
|
|
1232
|
-
await atomicWriteFile(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
1233
|
-
if (receipt.ok) {
|
|
1234
|
-
const state = await readRecommendationState(root);
|
|
1235
|
-
state.mcp = [...new Set([...state.mcp, id])];
|
|
1236
|
-
await writeRecommendationState(root, state);
|
|
1237
|
-
}
|
|
1238
|
-
await appendJsonlIfMissing(projectPaths(root).audit, {
|
|
1239
|
-
schemaVersion: "psyclaw/audit-event/v1",
|
|
1240
|
-
at: new Date().toISOString(),
|
|
1241
|
-
actor,
|
|
1242
|
-
action: `panel.install.${kind}`,
|
|
1243
|
-
targetId: id,
|
|
1244
|
-
ok: receipt.ok,
|
|
1245
|
-
runId,
|
|
1246
|
-
idempotencyKey,
|
|
1247
|
-
}, (item) => item.runId);
|
|
1248
|
-
response.writeHead(200, { "content-type": "application/json" });
|
|
1249
|
-
response.end(JSON.stringify({
|
|
1250
|
-
schemaVersion: "psyclaw/install-execution-receipt/v1",
|
|
1251
|
-
ok: receipt.ok,
|
|
1252
|
-
exitCode,
|
|
1253
|
-
command,
|
|
1254
|
-
output: output.slice(-2000),
|
|
1255
|
-
reloadHint: "/reload",
|
|
1256
|
-
...(typeof found.prep?.blockedReason === "string" ? { blockedReason: found.prep.blockedReason } : {}),
|
|
1257
|
-
}));
|
|
1320
|
+
response.writeHead(400, { "content-type": "application/json" });
|
|
1321
|
+
response.end(JSON.stringify({ error: `unsupported install kind: ${String(kind)}` }));
|
|
1258
1322
|
return;
|
|
1259
1323
|
}
|
|
1260
1324
|
if (url.pathname === "/api/recommendation-state") {
|
|
@@ -1828,8 +1892,57 @@ export function createPanelServer(root, options = {}) {
|
|
|
1828
1892
|
response.end(JSON.stringify({ schemaVersion: "psyclaw/provider-config-receipt/v1", ok: true, provider: id, modelCount: models.length, apiKeyStored: Boolean(apiKey?.trim()) }));
|
|
1829
1893
|
return;
|
|
1830
1894
|
}
|
|
1895
|
+
if (url.pathname === "/api/telemetry") {
|
|
1896
|
+
if (request.method === "GET" || request.method === "HEAD") {
|
|
1897
|
+
const preference = await readTelemetryPreference(telemetryPreferenceOptions(options.telemetrySettingsPath));
|
|
1898
|
+
const config = browserConfigForPreference(process.env, preference, "panel");
|
|
1899
|
+
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
|
|
1900
|
+
response.end(JSON.stringify({
|
|
1901
|
+
schemaVersion: "psyclaw/telemetry-preference/v1",
|
|
1902
|
+
enabled: config.telemetryEnabled,
|
|
1903
|
+
noticeAcknowledged: preference.noticeAcknowledged,
|
|
1904
|
+
showNotice: config.showTelemetryNotice,
|
|
1905
|
+
}));
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
const body = await readJsonBody(request);
|
|
1909
|
+
const action = String(body.action ?? "").trim();
|
|
1910
|
+
if (action === "ack") {
|
|
1911
|
+
const next = await writeTelemetryPreference({ noticeAcknowledged: true }, telemetryPreferenceOptions(options.telemetrySettingsPath));
|
|
1912
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
1913
|
+
response.end(JSON.stringify({ schemaVersion: "psyclaw/telemetry-preference/v1", ok: true, enabled: next.enabled, noticeAcknowledged: true }));
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
if (action === "disable" || action === "off") {
|
|
1917
|
+
const next = await writeTelemetryPreference({ enabled: false, noticeAcknowledged: true }, telemetryPreferenceOptions(options.telemetrySettingsPath));
|
|
1918
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
1919
|
+
response.end(JSON.stringify({ schemaVersion: "psyclaw/telemetry-preference/v1", ok: true, enabled: next.enabled, noticeAcknowledged: true }));
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
if (action === "enable" || action === "on") {
|
|
1923
|
+
const next = await writeTelemetryPreference({ enabled: true, noticeAcknowledged: true }, telemetryPreferenceOptions(options.telemetrySettingsPath));
|
|
1924
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
1925
|
+
response.end(JSON.stringify({ schemaVersion: "psyclaw/telemetry-preference/v1", ok: true, enabled: next.enabled, noticeAcknowledged: true }));
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
response.writeHead(400, { "content-type": "application/json" });
|
|
1929
|
+
response.end(JSON.stringify({ error: "action must be ack, disable, or enable" }));
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
if (url.pathname === "/observability.js") {
|
|
1933
|
+
const script = await readObservabilityScript(panelHtmlPath);
|
|
1934
|
+
if (script === undefined) {
|
|
1935
|
+
response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
1936
|
+
response.end("not found");
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
response.writeHead(200, { "content-type": "application/javascript; charset=utf-8", "cache-control": "no-store" });
|
|
1940
|
+
response.end(script);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1831
1943
|
if (url.pathname === "/" || url.pathname === "/index.html") {
|
|
1832
|
-
const
|
|
1944
|
+
const preference = await readTelemetryPreference(telemetryPreferenceOptions(options.telemetrySettingsPath));
|
|
1945
|
+
const html = injectBrowserObservabilityConfig(await readFile(panelHtmlPath, "utf8"), browserConfigForPreference(process.env, preference, "panel"));
|
|
1833
1946
|
response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
1834
1947
|
response.end(html);
|
|
1835
1948
|
return;
|