dsh-deeppilot 0.9.13 → 0.9.14
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/COMPATIBILITY.md +113 -19
- package/README.md +18 -19
- package/README.zh-CN.md +16 -16
- package/bin/SHA256SUMS +6 -6
- package/bin/darwin-amd64/dsh-deeppilot-tunnel +0 -0
- package/bin/darwin-arm64/dsh-deeppilot-tunnel +0 -0
- package/bin/linux-amd64/dsh-deeppilot-tunnel +0 -0
- package/bin/linux-arm64/dsh-deeppilot-tunnel +0 -0
- package/bin/windows-amd64/dsh-deeppilot-tunnel.exe +0 -0
- package/bin/windows-arm64/dsh-deeppilot-tunnel.exe +0 -0
- package/docs/DSH_022_FEATURES.md +38 -3
- package/docs/DSH_RELEASE_MEMORY.md +158 -2
- package/docs/NPM_PUBLISHING.md +33 -1
- package/lib/client.js +91 -93
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +305 -33
- package/lib/index.js +1611 -162
- package/lib/index.js.map +1 -1
- package/locale/en.json +2 -1
- package/locale/zh.json +2 -1
- package/package.json +22 -22
package/lib/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { Context } from "@deepseek-ai/cordis";
|
|
|
16
16
|
import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
17
17
|
import { isIP } from "node:net";
|
|
18
18
|
import { generate } from "selfsigned";
|
|
19
|
-
|
|
19
|
+
// src/wire-errors.ts
|
|
20
20
|
/**
|
|
21
21
|
* 错误词表(error vocabulary)的唯一属主。
|
|
22
22
|
*
|
|
@@ -42,7 +42,8 @@ const ERROR_CODES = {
|
|
|
42
42
|
E_BUSY: "session is busy",
|
|
43
43
|
E_UNSUPPORTED: "protocol version or capability unsupported",
|
|
44
44
|
E_INTERNAL: "internal error",
|
|
45
|
-
E_STEER_UNAVAILABLE: "queued item can no longer steer the current turn"
|
|
45
|
+
E_STEER_UNAVAILABLE: "queued item can no longer steer the current turn",
|
|
46
|
+
E_TOO_LARGE: "response exceeds the client frame limit"
|
|
46
47
|
};
|
|
47
48
|
/** Bridge 未携带 code 的结果(内部守卫失败等)落到这一档。 */
|
|
48
49
|
const WIRE_ERROR_FALLBACK = "E_INTERNAL";
|
|
@@ -90,6 +91,9 @@ const DOMAIN_TABLES = {
|
|
|
90
91
|
"schedule_not_found": "E_NOT_FOUND",
|
|
91
92
|
"delivery_cursor_not_found": "E_NOT_FOUND",
|
|
92
93
|
"schedule_conflict": "E_BUSY",
|
|
94
|
+
"schedule_execution_pending": "E_BUSY",
|
|
95
|
+
"schedule/target-archived": "E_NOT_FOUND",
|
|
96
|
+
"schedule/input-invalid": "E_PROTOCOL",
|
|
93
97
|
"invalid_prompt": "E_PROTOCOL",
|
|
94
98
|
"invalid_selector": "E_PROTOCOL",
|
|
95
99
|
"invalid_rule": "E_PROTOCOL",
|
|
@@ -209,9 +213,10 @@ function pendingResponseMessage(kind, reason) {
|
|
|
209
213
|
case "transport": return "host connection failed while answering " + kind;
|
|
210
214
|
}
|
|
211
215
|
}
|
|
212
|
-
|
|
213
|
-
//#region src/connection-policy.ts
|
|
216
|
+
// src/connection-policy.ts
|
|
214
217
|
const AUTH_TIMEOUT_MS = 35e3;
|
|
218
|
+
/** 附件 ack 在 `{mediaType,data}` 之外的信封字段(v/type/id/ts)余量。 */
|
|
219
|
+
const ATTACHMENT_ENVELOPE_HEADROOM_BYTES = 1024;
|
|
215
220
|
const IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
216
221
|
"image/png",
|
|
217
222
|
"image/jpeg",
|
|
@@ -287,8 +292,20 @@ function sanitizeImageName(value) {
|
|
|
287
292
|
function sanitizeDocumentField(value, maxChars) {
|
|
288
293
|
return value.replace(/[\u0000-\u001F\u007F]/g, " ").trim().slice(0, maxChars);
|
|
289
294
|
}
|
|
290
|
-
|
|
291
|
-
|
|
295
|
+
/** 二进制模式下导出与附件的单块字节数。连接在发送缓冲降到 256 KiB 以下才发
|
|
296
|
+
* 下一块,缓冲峰值约 512 KiB,远低于 4 MiB 的慢客户端断开线。 */
|
|
297
|
+
const BINARY_CHUNK_BYTES = 262144;
|
|
298
|
+
/** 把 envelope 头部与原始字节拼成一帧。 */
|
|
299
|
+
function encodeBinaryFrame(header, body) {
|
|
300
|
+
const headerBytes = Buffer.from(JSON.stringify(header), "utf8");
|
|
301
|
+
const frame = Buffer.allocUnsafe(5 + headerBytes.length + body.length);
|
|
302
|
+
frame.writeUInt8(1, 0);
|
|
303
|
+
frame.writeUInt32BE(headerBytes.length, 1);
|
|
304
|
+
headerBytes.copy(frame, 5);
|
|
305
|
+
frame.set(body, 5 + headerBytes.length);
|
|
306
|
+
return frame;
|
|
307
|
+
}
|
|
308
|
+
// src/auth-rate-limit.ts
|
|
292
309
|
const DEFAULT_AUTH_RATE_POLICY = {
|
|
293
310
|
windowMs: 6e4,
|
|
294
311
|
attemptsPerSource: 12,
|
|
@@ -405,8 +422,7 @@ var AuthRateLimiter = class {
|
|
|
405
422
|
}
|
|
406
423
|
}
|
|
407
424
|
};
|
|
408
|
-
|
|
409
|
-
//#region src/diagnostics.ts
|
|
425
|
+
// src/diagnostics.ts
|
|
410
426
|
/**
|
|
411
427
|
* 连接诊断事件模型(Diagnostics)——插件唯一的「失败可观测」面。
|
|
412
428
|
*
|
|
@@ -732,8 +748,7 @@ function buildHealthPayload(input) {
|
|
|
732
748
|
}
|
|
733
749
|
};
|
|
734
750
|
}
|
|
735
|
-
|
|
736
|
-
//#region src/device-auth.ts
|
|
751
|
+
// src/device-auth.ts
|
|
737
752
|
function getPairingCodeTtlMs() {
|
|
738
753
|
return Number(process.env.DEEPPILOT_PAIRING_TTL_MS) || 3e5;
|
|
739
754
|
}
|
|
@@ -822,6 +837,30 @@ function normalizeDeviceScopes(value) {
|
|
|
822
837
|
const allowed = new Set(DEVICE_SCOPES);
|
|
823
838
|
return [...new Set(value.filter((scope) => typeof scope === "string" && allowed.has(scope)))];
|
|
824
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* 0.8.3 之前的「全部权限」:当时还没有 `schedule.manage`。App 配对时一直申请
|
|
842
|
+
* DeviceScope 的全部取值,设置页也没有收窄权限的入口,所以存量记录恰好等于这五项,
|
|
843
|
+
* 只说明「配对时这项权限还不存在」,不是有意关闭。
|
|
844
|
+
*/
|
|
845
|
+
const LEGACY_FULL_SCOPES = [
|
|
846
|
+
"sessions.read",
|
|
847
|
+
"prompt.send",
|
|
848
|
+
"sessions.manage",
|
|
849
|
+
"interactions.respond",
|
|
850
|
+
"notifications.register"
|
|
851
|
+
];
|
|
852
|
+
/**
|
|
853
|
+
* 存量设备权限升级:恰好等于旧版全部权限时补上之后新增的默认权限,返回新列表;
|
|
854
|
+
* 其余情况(含任何非完整集合)返回 null,原样保留。只放宽「旧版全量」这一种形态,
|
|
855
|
+
* 避免把将来可能出现的、有意收窄过的记录一并放开。
|
|
856
|
+
*/
|
|
857
|
+
function upgradeLegacyFullScopes(value) {
|
|
858
|
+
if (!Array.isArray(value)) return null;
|
|
859
|
+
const current = new Set(value);
|
|
860
|
+
if (current.size !== LEGACY_FULL_SCOPES.length) return null;
|
|
861
|
+
if (!LEGACY_FULL_SCOPES.every((scope) => current.has(scope))) return null;
|
|
862
|
+
return [...DEFAULT_DEVICE_SCOPES];
|
|
863
|
+
}
|
|
825
864
|
/** One active, single-use pairing grant per plugin runtime. */
|
|
826
865
|
var PairingCodeManager = class {
|
|
827
866
|
active = null;
|
|
@@ -857,8 +896,7 @@ function createAuthChallenge(audience, now = Date.now()) {
|
|
|
857
896
|
expiresAt: now + AUTH_CHALLENGE_TTL_MS
|
|
858
897
|
};
|
|
859
898
|
}
|
|
860
|
-
|
|
861
|
-
//#region src/wire-interaction.ts
|
|
899
|
+
// src/wire-interaction.ts
|
|
862
900
|
/**
|
|
863
901
|
* 交互特性的 wire 行:回答待决的 approval 与 question。
|
|
864
902
|
*
|
|
@@ -918,8 +956,7 @@ const interactionRows = [{
|
|
|
918
956
|
ctx.send("s2c.ack", {});
|
|
919
957
|
}
|
|
920
958
|
}];
|
|
921
|
-
|
|
922
|
-
//#region src/wire-device.ts
|
|
959
|
+
// src/wire-device.ts
|
|
923
960
|
/**
|
|
924
961
|
* 设备生命周期特性的 wire 行:自撤销(解绑)。
|
|
925
962
|
*
|
|
@@ -957,8 +994,7 @@ const deviceRows = [{
|
|
|
957
994
|
ctx.close(4401, "device revoked");
|
|
958
995
|
}
|
|
959
996
|
}];
|
|
960
|
-
|
|
961
|
-
//#region src/token.ts
|
|
997
|
+
// src/token.ts
|
|
962
998
|
/** Expand a leading ~ using the process home directory. */
|
|
963
999
|
function expandHome(p) {
|
|
964
1000
|
if (p === "~") return homedir();
|
|
@@ -1035,7 +1071,20 @@ var DeviceStore = class DeviceStore {
|
|
|
1035
1071
|
const store = new DeviceStore(filePath);
|
|
1036
1072
|
try {
|
|
1037
1073
|
const raw = JSON.parse(await readFile(expandHome(filePath), "utf8"));
|
|
1038
|
-
|
|
1074
|
+
let upgraded = 0;
|
|
1075
|
+
for (const rec of raw.devices ?? []) {
|
|
1076
|
+
if (typeof rec.deviceId !== "string") continue;
|
|
1077
|
+
const next = rec.revokedAt === void 0 ? upgradeLegacyFullScopes(rec.scopes) : null;
|
|
1078
|
+
if (next !== null) {
|
|
1079
|
+
rec.scopes = next;
|
|
1080
|
+
upgraded += 1;
|
|
1081
|
+
}
|
|
1082
|
+
store.devices.set(rec.deviceId, rec);
|
|
1083
|
+
}
|
|
1084
|
+
if (upgraded > 0) {
|
|
1085
|
+
console.log(formatLogLine("info", `device scopes upgraded for ${upgraded} legacy device(s): schedule.manage added`));
|
|
1086
|
+
store.flush();
|
|
1087
|
+
}
|
|
1039
1088
|
} catch (error) {
|
|
1040
1089
|
if (error?.code === "ENOENT") {} else console.log(formatLogLine("warn", "device registry unreadable, starting empty: " + String(error)));
|
|
1041
1090
|
}
|
|
@@ -1260,8 +1309,7 @@ var DeviceStore = class DeviceStore {
|
|
|
1260
1309
|
console.log(formatLogLine("warn", "device registry write failed" + (typeof code === "string" ? " code=" + code : "") + ": " + message.slice(0, 160)));
|
|
1261
1310
|
}
|
|
1262
1311
|
};
|
|
1263
|
-
|
|
1264
|
-
//#region src/wire-push.ts
|
|
1312
|
+
// src/wire-push.ts
|
|
1265
1313
|
/** Live Activity 需要的三个 scope:通知注册 + 会话读取 + 交互应答。 */
|
|
1266
1314
|
const LIVE_ACTIVITY_SCOPES = [
|
|
1267
1315
|
"notifications.register",
|
|
@@ -1408,21 +1456,83 @@ const pushRows = [
|
|
|
1408
1456
|
}
|
|
1409
1457
|
}
|
|
1410
1458
|
];
|
|
1411
|
-
|
|
1412
|
-
//#region src/wire-schedule.ts
|
|
1459
|
+
// src/wire-schedule.ts
|
|
1413
1460
|
/**
|
|
1414
|
-
* 定时任务特性的 wire
|
|
1461
|
+
* 定时任务特性的 wire 行:会话级(列表、历史、创建、修改、删除)与宿主级
|
|
1462
|
+
* (list/view/create/edit/run/limits)两套。
|
|
1415
1463
|
*
|
|
1416
1464
|
* scope 取 `schedule.manage` + `sessions.read`:PROTOCOL.md scope 映射表写明
|
|
1417
1465
|
* 「`schedule.manage` 定时任务/提醒的列表、历史、创建、修改和删除;同时需要
|
|
1418
1466
|
* `sessions.read` 才能读取任务内容」。迁移前这条组合一半住在 requiredScope、
|
|
1419
1467
|
* 一半住在 connection.ts 调用点的一行补充规则里(334-336),现在是一行声明。
|
|
1420
1468
|
*
|
|
1421
|
-
*
|
|
1422
|
-
* (见 host-capabilities.ts 的 G4
|
|
1469
|
+
* 五个会话级帧共享能力位 `schedules`;该位现在只要求 Schedule 服务存在
|
|
1470
|
+
* (见 host-capabilities.ts 的 G4 修正)。六个宿主级帧共享 `schedulesHost`:
|
|
1471
|
+
* 该位由 adapter 判别位推导,false/缺失时新帧一律 E_UNSUPPORTED,客户端只能
|
|
1472
|
+
* 走会话级帧(PROTOCOL.md「Schedule 宿主级任务扩展」)。
|
|
1423
1473
|
*/
|
|
1424
1474
|
/** PROTOCOL.md scope 映射表:schedule.manage 需搭配 sessions.read。 */
|
|
1425
1475
|
const SCHEDULE_SCOPES = ["schedule.manage", "sessions.read"];
|
|
1476
|
+
/** 普通 JSON 对象(宿主级 timing/target/patch 都是嵌套对象)。 */
|
|
1477
|
+
function isObjectValue(value) {
|
|
1478
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1479
|
+
}
|
|
1480
|
+
/** `{time, time_zone}` 本地时间输入:取值合法性由 Host 完整校验。 */
|
|
1481
|
+
function isLocalTimeInput(value) {
|
|
1482
|
+
return isObjectValue(value) && isText(value.time) && isText(value.time_zone);
|
|
1483
|
+
}
|
|
1484
|
+
function isWeekdayList(value) {
|
|
1485
|
+
return Array.isArray(value) && value.length > 0 && value.every((day) => isInteger(day, 1, 7));
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* 宿主级 timing 判别联合的形状检查。
|
|
1489
|
+
*
|
|
1490
|
+
* 只判结构与数量级(PROTOCOL.md 章节限额);日期格式、时区与频率是否可达由
|
|
1491
|
+
* Host 的 Schedule 服务负责——重复一遍只会制造两套口径。
|
|
1492
|
+
*/
|
|
1493
|
+
function isHostTiming(value) {
|
|
1494
|
+
if (!isObjectValue(value)) return false;
|
|
1495
|
+
switch (value.kind) {
|
|
1496
|
+
case "after": return isInteger(value.after_seconds, 1, Number.MAX_SAFE_INTEGER);
|
|
1497
|
+
case "at": return isText(value.at) || isObjectValue(value.at) && isText(value.at.date) && isText(value.at.time) && isText(value.at.time_zone);
|
|
1498
|
+
case "every": return isInteger(value.every_seconds, 60, Number.MAX_SAFE_INTEGER);
|
|
1499
|
+
case "daily": return isLocalTimeInput(value.daily);
|
|
1500
|
+
case "weekly": return isObjectValue(value.weekly) && isLocalTimeInput(value.weekly) && isWeekdayList(value.weekly.weekdays);
|
|
1501
|
+
case "cron": return isObjectValue(value.cron) && isText(value.cron.expression) && isText(value.cron.time_zone);
|
|
1502
|
+
case "rrule": return isText(value.rrule);
|
|
1503
|
+
default: return false;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
/** `{cwd?, agentPreset?}`:省略即 Host 默认。 */
|
|
1507
|
+
function isHostCreateSpec(value) {
|
|
1508
|
+
return isObjectValue(value) && isOptionalField(value, "cwd", (v) => isText(v)) && isOptionalField(value, "agentPreset", (v) => isText(v));
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* 创建/编辑请求里的 target。`session` 必须给 sessionId;`new_once`/`new` 的
|
|
1512
|
+
* 会话由 Host 预留或新建,`create` 省略即默认。客户端若把查看结果里的
|
|
1513
|
+
* `new_once.sessionId` 原样回传,也接受(未知/冗余字段不该让编辑失败)。
|
|
1514
|
+
*/
|
|
1515
|
+
function isHostTarget(value) {
|
|
1516
|
+
if (!isObjectValue(value)) return false;
|
|
1517
|
+
switch (value.kind) {
|
|
1518
|
+
case "session": return isText(value.sessionId);
|
|
1519
|
+
case "new_once": return isOptionalField(value, "sessionId", (v) => isText(v)) && isOptionalField(value, "create", isHostCreateSpec);
|
|
1520
|
+
case "new": return isOptionalField(value, "create", isHostCreateSpec);
|
|
1521
|
+
default: return false;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/** 编辑补丁:至少一个字段,且各字段形状合法。 */
|
|
1525
|
+
function isHostPatch(value) {
|
|
1526
|
+
if (!isObjectValue(value)) return false;
|
|
1527
|
+
if ([
|
|
1528
|
+
"title",
|
|
1529
|
+
"prompt",
|
|
1530
|
+
"timing",
|
|
1531
|
+
"target",
|
|
1532
|
+
"active"
|
|
1533
|
+
].filter((key) => value[key] !== void 0).length === 0) return false;
|
|
1534
|
+
return isOptionalField(value, "title", (v) => isText(v, 120)) && isOptionalField(value, "prompt", (v) => isText(v, 262144)) && isOptionalField(value, "timing", isHostTiming) && isOptionalField(value, "target", isHostTarget) && isOptionalField(value, "active", (v) => typeof v === "boolean");
|
|
1535
|
+
}
|
|
1426
1536
|
const scheduleRows = [
|
|
1427
1537
|
{
|
|
1428
1538
|
type: "c2s.schedule.list",
|
|
@@ -1454,14 +1564,16 @@ const scheduleRows = [
|
|
|
1454
1564
|
validate: (payload) => {
|
|
1455
1565
|
const checked = payloadObject(payload);
|
|
1456
1566
|
if (!checked.ok) return checked;
|
|
1457
|
-
if (!
|
|
1567
|
+
if (!isOptionalField(checked.value, "sessionId", (v) => isText(v)) || !isText(checked.value.id) || !isInteger(checked.value.limit, 1, 100) || !isOptionalField(checked.value, "before", (v) => isText(v))) return reject$1("E_PROTOCOL", "invalid schedule history");
|
|
1458
1568
|
return checked;
|
|
1459
1569
|
},
|
|
1460
1570
|
handle: async (ctx, payload) => {
|
|
1461
|
-
const
|
|
1571
|
+
const sessionId = payload.sessionId;
|
|
1572
|
+
if (sessionId === void 0 && ctx.bridge.capabilities.schedulesHost !== true) return ctx.fail("E_PROTOCOL", "invalid schedule history");
|
|
1573
|
+
const result = await ctx.bridge.scheduleHistory(sessionId ?? "", payload.id, payload.limit, payload.before);
|
|
1462
1574
|
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1463
1575
|
ctx.send("s2c.schedule.history", {
|
|
1464
|
-
sessionId:
|
|
1576
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
1465
1577
|
history: result.value
|
|
1466
1578
|
});
|
|
1467
1579
|
}
|
|
@@ -1553,10 +1665,159 @@ const scheduleRows = [
|
|
|
1553
1665
|
...result.replayed ? { replayed: true } : {}
|
|
1554
1666
|
});
|
|
1555
1667
|
}
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
type: "c2s.schedule.host.list",
|
|
1671
|
+
stage: "authenticated",
|
|
1672
|
+
scopes: SCHEDULE_SCOPES,
|
|
1673
|
+
capability: "schedulesHost",
|
|
1674
|
+
doc: "PROTOCOL.md c2s.schedule.host.list",
|
|
1675
|
+
validate: (payload) => {
|
|
1676
|
+
const checked = payloadObject(payload);
|
|
1677
|
+
if (!checked.ok) return checked;
|
|
1678
|
+
const p = checked.value;
|
|
1679
|
+
if (!isOptionalField(p, "active", (v) => typeof v === "boolean") || !isOptionalField(p, "limit", (v) => isInteger(v, 1, 500)) || !isOptionalField(p, "offset", (v) => isInteger(v, 0, Number.MAX_SAFE_INTEGER))) return reject$1("E_PROTOCOL", "invalid schedule host list");
|
|
1680
|
+
return checked;
|
|
1681
|
+
},
|
|
1682
|
+
handle: async (ctx, payload) => {
|
|
1683
|
+
const result = await ctx.bridge.listHostSchedules({
|
|
1684
|
+
...payload.active !== void 0 ? { active: payload.active } : {},
|
|
1685
|
+
...payload.limit !== void 0 ? { limit: payload.limit } : {},
|
|
1686
|
+
...payload.offset !== void 0 ? { offset: payload.offset } : {}
|
|
1687
|
+
});
|
|
1688
|
+
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1689
|
+
ctx.send("s2c.schedule.host.snapshot", result.value);
|
|
1690
|
+
}
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
type: "c2s.schedule.host.view",
|
|
1694
|
+
stage: "authenticated",
|
|
1695
|
+
scopes: SCHEDULE_SCOPES,
|
|
1696
|
+
capability: "schedulesHost",
|
|
1697
|
+
doc: "PROTOCOL.md c2s.schedule.host.view",
|
|
1698
|
+
validate: (payload) => {
|
|
1699
|
+
const checked = payloadObject(payload);
|
|
1700
|
+
if (!checked.ok) return checked;
|
|
1701
|
+
if (!isText(checked.value.id)) return reject$1("E_PROTOCOL", "invalid schedule id");
|
|
1702
|
+
return checked;
|
|
1703
|
+
},
|
|
1704
|
+
handle: async (ctx, payload) => {
|
|
1705
|
+
const result = await ctx.bridge.viewHostSchedule(payload.id);
|
|
1706
|
+
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1707
|
+
ctx.send("s2c.schedule.host.task", { task: result.value });
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
type: "c2s.schedule.host.create",
|
|
1712
|
+
stage: "authenticated",
|
|
1713
|
+
scopes: SCHEDULE_SCOPES,
|
|
1714
|
+
capability: "schedulesHost",
|
|
1715
|
+
doc: "PROTOCOL.md c2s.schedule.host.create",
|
|
1716
|
+
validate: (payload) => {
|
|
1717
|
+
const checked = payloadObject(payload);
|
|
1718
|
+
if (!checked.ok) return checked;
|
|
1719
|
+
const p = checked.value;
|
|
1720
|
+
if (!validSendId(p.clientRequestId) || !isText(p.title, 120) || !isText(p.prompt, 262144) || !isHostTiming(p.timing) || !isOptionalField(p, "target", isHostTarget)) return reject$1("E_PROTOCOL", "invalid schedule host create fields");
|
|
1721
|
+
return checked;
|
|
1722
|
+
},
|
|
1723
|
+
handle: async (ctx, payload) => {
|
|
1724
|
+
const result = await ctx.bridge.createHostSchedule(ctx.deviceId, payload);
|
|
1725
|
+
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1726
|
+
ctx.send("s2c.schedule.updated", {
|
|
1727
|
+
clientRequestId: payload.clientRequestId,
|
|
1728
|
+
task: result.value,
|
|
1729
|
+
...result.replayed ? { replayed: true } : {}
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
type: "c2s.schedule.host.edit",
|
|
1735
|
+
stage: "authenticated",
|
|
1736
|
+
scopes: SCHEDULE_SCOPES,
|
|
1737
|
+
capability: "schedulesHost",
|
|
1738
|
+
doc: "PROTOCOL.md c2s.schedule.host.edit",
|
|
1739
|
+
validate: (payload) => {
|
|
1740
|
+
const checked = payloadObject(payload);
|
|
1741
|
+
if (!checked.ok) return checked;
|
|
1742
|
+
const p = checked.value;
|
|
1743
|
+
if (!validSendId(p.clientRequestId) || !isText(p.id) || !isOptionalField(p, "expectedUpdatedAt", (v) => v === null || isText(v))) return reject$1("E_PROTOCOL", "invalid schedule host edit fields");
|
|
1744
|
+
if (p.action !== "update" && p.action !== "delete") return reject$1("E_PROTOCOL", "invalid schedule host edit action");
|
|
1745
|
+
if (p.action === "update" && !isHostPatch(p.patch)) return reject$1("E_PROTOCOL", "invalid schedule host edit patch");
|
|
1746
|
+
return checked;
|
|
1747
|
+
},
|
|
1748
|
+
handle: async (ctx, payload) => {
|
|
1749
|
+
const result = await ctx.bridge.editHostSchedule(ctx.deviceId, payload);
|
|
1750
|
+
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1751
|
+
const value = result.value;
|
|
1752
|
+
ctx.send("s2c.schedule.updated", {
|
|
1753
|
+
clientRequestId: payload.clientRequestId,
|
|
1754
|
+
..."deleted" in value ? { deleted: true } : { task: value.task },
|
|
1755
|
+
...result.replayed ? { replayed: true } : {}
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
type: "c2s.schedule.host.run",
|
|
1761
|
+
stage: "authenticated",
|
|
1762
|
+
scopes: SCHEDULE_SCOPES,
|
|
1763
|
+
capability: "schedulesHost",
|
|
1764
|
+
doc: "PROTOCOL.md c2s.schedule.host.run",
|
|
1765
|
+
validate: (payload) => {
|
|
1766
|
+
const checked = payloadObject(payload);
|
|
1767
|
+
if (!checked.ok) return checked;
|
|
1768
|
+
if (!validSendId(checked.value.clientRequestId) || !isText(checked.value.id)) return reject$1("E_PROTOCOL", "invalid schedule host run fields");
|
|
1769
|
+
return checked;
|
|
1770
|
+
},
|
|
1771
|
+
handle: async (ctx, payload) => {
|
|
1772
|
+
const result = await ctx.bridge.runHostSchedule(ctx.deviceId, {
|
|
1773
|
+
clientRequestId: payload.clientRequestId,
|
|
1774
|
+
id: payload.id
|
|
1775
|
+
});
|
|
1776
|
+
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1777
|
+
ctx.send("s2c.schedule.updated", {
|
|
1778
|
+
clientRequestId: payload.clientRequestId,
|
|
1779
|
+
task: result.value,
|
|
1780
|
+
...result.replayed ? { replayed: true } : {}
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
type: "c2s.schedule.host.deleteEnded",
|
|
1786
|
+
stage: "authenticated",
|
|
1787
|
+
scopes: SCHEDULE_SCOPES,
|
|
1788
|
+
capability: "schedulesHost",
|
|
1789
|
+
doc: "PROTOCOL.md c2s.schedule.host.deleteEnded",
|
|
1790
|
+
validate: (payload) => {
|
|
1791
|
+
const checked = payloadObject(payload);
|
|
1792
|
+
if (!checked.ok) return checked;
|
|
1793
|
+
if (!validSendId(checked.value.clientRequestId)) return reject$1("E_PROTOCOL", "invalid schedule host delete ended fields");
|
|
1794
|
+
return checked;
|
|
1795
|
+
},
|
|
1796
|
+
handle: async (ctx, payload) => {
|
|
1797
|
+
const result = await ctx.bridge.deleteEndedHostSchedules(ctx.deviceId, { clientRequestId: payload.clientRequestId });
|
|
1798
|
+
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1799
|
+
ctx.send("s2c.schedule.updated", {
|
|
1800
|
+
clientRequestId: payload.clientRequestId,
|
|
1801
|
+
...result.value.deleted !== void 0 ? { deletedEnded: result.value.deleted } : {},
|
|
1802
|
+
...result.replayed ? { replayed: true } : {}
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
{
|
|
1807
|
+
type: "c2s.schedule.host.limits",
|
|
1808
|
+
stage: "authenticated",
|
|
1809
|
+
scopes: SCHEDULE_SCOPES,
|
|
1810
|
+
capability: "schedulesHost",
|
|
1811
|
+
doc: "PROTOCOL.md c2s.schedule.host.limits",
|
|
1812
|
+
validate: (payload) => payloadObject(payload),
|
|
1813
|
+
handle: async (ctx) => {
|
|
1814
|
+
const result = await ctx.bridge.scheduleLimits();
|
|
1815
|
+
if (!result.ok) return ctx.fail(result.code, result.message);
|
|
1816
|
+
ctx.send("s2c.schedule.host.limits", result.value);
|
|
1817
|
+
}
|
|
1556
1818
|
}
|
|
1557
1819
|
];
|
|
1558
|
-
|
|
1559
|
-
//#region src/document-payload.ts
|
|
1820
|
+
// src/document-payload.ts
|
|
1560
1821
|
const PREFIX = "[DeepPilot document:";
|
|
1561
1822
|
const SUFFIX = "]";
|
|
1562
1823
|
/**
|
|
@@ -1593,8 +1854,7 @@ function projectedDocument(text) {
|
|
|
1593
1854
|
return;
|
|
1594
1855
|
}
|
|
1595
1856
|
}
|
|
1596
|
-
|
|
1597
|
-
//#region src/host-event-projection.ts
|
|
1857
|
+
// src/host-event-projection.ts
|
|
1598
1858
|
const MAX_MESSAGE_PROJECTION_BYTES = 262144;
|
|
1599
1859
|
/** One durable host event sequence becomes exactly one phone message row.
|
|
1600
1860
|
* Keep the last projection when a host history response repeats an event. */
|
|
@@ -1682,7 +1942,8 @@ function projectEvent(sessionId, event) {
|
|
|
1682
1942
|
name: String(data?.name ?? "tool"),
|
|
1683
1943
|
state: "running",
|
|
1684
1944
|
summary: summarizeArgs(data?.arguments),
|
|
1685
|
-
...data?.callId ? { callId: String(data.callId) } : {}
|
|
1945
|
+
...data?.callId ? { callId: String(data.callId) } : {},
|
|
1946
|
+
...editField(data?.name, data?.arguments)
|
|
1686
1947
|
},
|
|
1687
1948
|
ts: tsOf(event)
|
|
1688
1949
|
}
|
|
@@ -1882,6 +2143,10 @@ function chunkText(data) {
|
|
|
1882
2143
|
const direct = data;
|
|
1883
2144
|
return typeof direct.text === "string" ? direct.text : "";
|
|
1884
2145
|
}
|
|
2146
|
+
function editField(name, raw) {
|
|
2147
|
+
const edit = toolEditOf(name, raw);
|
|
2148
|
+
return edit ? { edit } : {};
|
|
2149
|
+
}
|
|
1885
2150
|
function summarizeArgs(raw) {
|
|
1886
2151
|
if (typeof raw !== "string" || raw.length === 0) return "";
|
|
1887
2152
|
try {
|
|
@@ -1893,6 +2158,68 @@ function summarizeArgs(raw) {
|
|
|
1893
2158
|
return truncate(raw, 90);
|
|
1894
2159
|
}
|
|
1895
2160
|
}
|
|
2161
|
+
/** 宿主的文件编辑工具名(DSH `dsh-tool-str-replace-editor`)。 */
|
|
2162
|
+
const EDITOR_TOOL_NAME = "str_replace_editor";
|
|
2163
|
+
/**
|
|
2164
|
+
* 把编辑类工具调用的原始参数归一化成 `ToolEditProjection`。
|
|
2165
|
+
*
|
|
2166
|
+
* 只认 `str_replace_editor` 的 `str_replace` / `create` / `insert`(`view` 不改文件),
|
|
2167
|
+
* 其他工具或无法解析的参数返回 undefined,工具行保持原样。与 iOS `ToolEditDiff.parse`
|
|
2168
|
+
* 的解析口径一致:`new_str` 缺失视为删除(空串)。
|
|
2169
|
+
*/
|
|
2170
|
+
function toolEditOf(name, raw) {
|
|
2171
|
+
if (name !== EDITOR_TOOL_NAME || typeof raw !== "string" || raw.length === 0) return void 0;
|
|
2172
|
+
let args;
|
|
2173
|
+
try {
|
|
2174
|
+
const parsed = JSON.parse(raw);
|
|
2175
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
2176
|
+
args = parsed;
|
|
2177
|
+
} catch {
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
const path = args.path;
|
|
2181
|
+
if (typeof path !== "string" || path.length === 0) return void 0;
|
|
2182
|
+
let edit;
|
|
2183
|
+
switch (args.command) {
|
|
2184
|
+
case "str_replace":
|
|
2185
|
+
if (typeof args.old_str !== "string") return void 0;
|
|
2186
|
+
edit = {
|
|
2187
|
+
path,
|
|
2188
|
+
operation: "replace",
|
|
2189
|
+
oldText: args.old_str,
|
|
2190
|
+
newText: typeof args.new_str === "string" ? args.new_str : ""
|
|
2191
|
+
};
|
|
2192
|
+
break;
|
|
2193
|
+
case "create":
|
|
2194
|
+
if (typeof args.file_text !== "string") return void 0;
|
|
2195
|
+
edit = {
|
|
2196
|
+
path,
|
|
2197
|
+
operation: "create",
|
|
2198
|
+
newText: args.file_text
|
|
2199
|
+
};
|
|
2200
|
+
break;
|
|
2201
|
+
case "insert":
|
|
2202
|
+
if (typeof args.new_str !== "string") return void 0;
|
|
2203
|
+
edit = {
|
|
2204
|
+
path,
|
|
2205
|
+
operation: "insert",
|
|
2206
|
+
newText: args.new_str,
|
|
2207
|
+
...Number.isSafeInteger(args.insert_line) ? { insertLine: args.insert_line } : {}
|
|
2208
|
+
};
|
|
2209
|
+
break;
|
|
2210
|
+
default: return;
|
|
2211
|
+
}
|
|
2212
|
+
return [edit.oldText, edit.newText].some((text) => text !== void 0 && Buffer.byteLength(text, "utf8") > 16384) ? withoutEditBodies(edit) : edit;
|
|
2213
|
+
}
|
|
2214
|
+
/** 去掉改动正文,只留路径、操作与 truncated 标记(超限或整条消息超限时用)。 */
|
|
2215
|
+
function withoutEditBodies(edit) {
|
|
2216
|
+
return {
|
|
2217
|
+
path: edit.path,
|
|
2218
|
+
operation: edit.operation,
|
|
2219
|
+
...edit.insertLine !== void 0 ? { insertLine: edit.insertLine } : {},
|
|
2220
|
+
truncated: true
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
1896
2223
|
function truncate(text, max) {
|
|
1897
2224
|
return text.length <= max ? text : text.slice(0, max - 1) + "…";
|
|
1898
2225
|
}
|
|
@@ -1936,7 +2263,8 @@ function projectHistory(events) {
|
|
|
1936
2263
|
tool: {
|
|
1937
2264
|
name: String(data?.name ?? "tool"),
|
|
1938
2265
|
state: "running",
|
|
1939
|
-
summary: summarizeArgs(data?.arguments)
|
|
2266
|
+
summary: summarizeArgs(data?.arguments),
|
|
2267
|
+
...editField(data?.name, data?.arguments)
|
|
1940
2268
|
}
|
|
1941
2269
|
};
|
|
1942
2270
|
messages.push(row);
|
|
@@ -1985,7 +2313,8 @@ function limitMessageProjection(message) {
|
|
|
1985
2313
|
...message.tool ? { tool: {
|
|
1986
2314
|
...message.tool,
|
|
1987
2315
|
name: truncateUtf8(message.tool.name, 4096),
|
|
1988
|
-
summary: truncateUtf8(message.tool.summary, 65536)
|
|
2316
|
+
summary: truncateUtf8(message.tool.summary, 65536),
|
|
2317
|
+
...message.tool.edit ? { edit: withoutEditBodies(message.tool.edit) } : {}
|
|
1989
2318
|
} } : {},
|
|
1990
2319
|
...message.attachments ? { attachments: message.attachments.slice(0, 16).map((attachment) => ({
|
|
1991
2320
|
...attachment,
|
|
@@ -2129,8 +2458,7 @@ function historyWindow(source, options) {
|
|
|
2129
2458
|
hasMore: older.length > 0
|
|
2130
2459
|
};
|
|
2131
2460
|
}
|
|
2132
|
-
|
|
2133
|
-
//#region src/wire-session.ts
|
|
2461
|
+
// src/wire-session.ts
|
|
2134
2462
|
/**
|
|
2135
2463
|
* 会话特性的 wire 行:列表/归档、打开与关闭、创建/分支/重命名/取消、历史与
|
|
2136
2464
|
* 附件、模型目录与切换、prompt 投递。
|
|
@@ -2464,6 +2792,9 @@ const sessionRows = [
|
|
|
2464
2792
|
const invalid = requireSessionId(checked.value);
|
|
2465
2793
|
if (invalid) return invalid;
|
|
2466
2794
|
if (!isText(checked.value.attachmentId)) return reject$1("E_PROTOCOL", "invalid attachmentId");
|
|
2795
|
+
const maxFrameBytes = checked.value.maxFrameBytes;
|
|
2796
|
+
if (maxFrameBytes !== void 0 && (!Number.isSafeInteger(maxFrameBytes) || maxFrameBytes <= 0)) return reject$1("E_PROTOCOL", "invalid maxFrameBytes");
|
|
2797
|
+
if (checked.value.binary !== void 0 && typeof checked.value.binary !== "boolean") return reject$1("E_PROTOCOL", "invalid binary");
|
|
2467
2798
|
return checked;
|
|
2468
2799
|
},
|
|
2469
2800
|
handle: async (ctx, payload) => {
|
|
@@ -2471,6 +2802,34 @@ const sessionRows = [
|
|
|
2471
2802
|
if (attachmentId.length === 0) return ctx.fail("E_PROTOCOL", "sessionId and attachmentId required");
|
|
2472
2803
|
const image = await ctx.bridge.attachmentData(payload.sessionId, attachmentId);
|
|
2473
2804
|
if (!image) return ctx.fail("E_NOT_FOUND", "attachment unavailable");
|
|
2805
|
+
if (payload.binary === true && ctx.sendBinary) {
|
|
2806
|
+
const bytes = Buffer.from(image.data, "base64");
|
|
2807
|
+
if (bytes.length === 0) return ctx.fail("E_NOT_FOUND", "attachment unavailable");
|
|
2808
|
+
if (bytes.length > 12582912) return ctx.fail("E_TOO_LARGE", `attachment ${bytes.length} bytes exceeds limit`);
|
|
2809
|
+
let index = 0;
|
|
2810
|
+
try {
|
|
2811
|
+
for (let offset = 0; offset < bytes.length; offset += BINARY_CHUNK_BYTES) {
|
|
2812
|
+
await ctx.waitForWritable?.();
|
|
2813
|
+
ctx.sendBinary("s2c.session.attachment.chunk", { index: index++ }, bytes.subarray(offset, offset + BINARY_CHUNK_BYTES));
|
|
2814
|
+
}
|
|
2815
|
+
} catch {
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
const ack = {
|
|
2819
|
+
...image.mediaType !== void 0 ? { mediaType: image.mediaType } : {},
|
|
2820
|
+
chunks: index,
|
|
2821
|
+
bytes: bytes.length
|
|
2822
|
+
};
|
|
2823
|
+
return ctx.send("s2c.ack", ack);
|
|
2824
|
+
}
|
|
2825
|
+
const clientLimit = payload.maxFrameBytes ?? 1048576;
|
|
2826
|
+
const frameBytes = image.data.length + (image.mediaType?.length ?? 0) + ATTACHMENT_ENVELOPE_HEADROOM_BYTES;
|
|
2827
|
+
if (frameBytes > Math.min(clientLimit, 2097152)) return ctx.fail("E_TOO_LARGE", `attachment frame ${frameBytes} bytes exceeds limit`);
|
|
2828
|
+
try {
|
|
2829
|
+
await ctx.waitForWritable?.();
|
|
2830
|
+
} catch {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2474
2833
|
ctx.send("s2c.ack", image);
|
|
2475
2834
|
}
|
|
2476
2835
|
},
|
|
@@ -2682,8 +3041,7 @@ const sessionRows = [
|
|
|
2682
3041
|
}
|
|
2683
3042
|
}
|
|
2684
3043
|
];
|
|
2685
|
-
|
|
2686
|
-
//#region src/wire-command.ts
|
|
3044
|
+
// src/wire-command.ts
|
|
2687
3045
|
function sessionPayload(value) {
|
|
2688
3046
|
const result = payloadObject(value);
|
|
2689
3047
|
return result.ok && !isText(result.value.sessionId) ? reject$1("E_PROTOCOL", "会话标识无效") : result;
|
|
@@ -2755,6 +3113,7 @@ const commandRows = [...[
|
|
|
2755
3113
|
const result = sessionPayload(value);
|
|
2756
3114
|
if (!result.ok) return result;
|
|
2757
3115
|
if (result.value.format !== void 0 && result.value.format !== "zip" && result.value.format !== "markdown") return reject$1("E_PROTOCOL", "导出格式无效");
|
|
3116
|
+
if (result.value.binary !== void 0 && typeof result.value.binary !== "boolean") return reject$1("E_PROTOCOL", "传输方式无效");
|
|
2758
3117
|
return result;
|
|
2759
3118
|
},
|
|
2760
3119
|
handle: async (ctx, payload) => {
|
|
@@ -2768,22 +3127,34 @@ const commandRows = [...[
|
|
|
2768
3127
|
try {
|
|
2769
3128
|
reader = (format === void 0 ? await ctx.bridge.exportSession(payload.sessionId, signal) : await ctx.bridge.exportSession(payload.sessionId, signal, format)).body.getReader();
|
|
2770
3129
|
signal.addEventListener("abort", cancel, { once: true });
|
|
3130
|
+
const binary = payload.binary === true && ctx.sendBinary !== void 0;
|
|
2771
3131
|
let index = 0, bytes = 0;
|
|
3132
|
+
const sendChunk = async (chunk) => {
|
|
3133
|
+
bytes += chunk.length;
|
|
3134
|
+
await ctx.waitForWritable?.();
|
|
3135
|
+
if (binary) ctx.sendBinary("s2c.session.export.chunk", { index: index++ }, chunk);
|
|
3136
|
+
else ctx.send("s2c.session.export.chunk", {
|
|
3137
|
+
index: index++,
|
|
3138
|
+
data: Buffer.from(chunk).toString("base64")
|
|
3139
|
+
});
|
|
3140
|
+
};
|
|
3141
|
+
let carry;
|
|
2772
3142
|
while (true) {
|
|
2773
3143
|
signal.throwIfAborted();
|
|
2774
3144
|
const next = await reader.read();
|
|
2775
3145
|
if (next.done) break;
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
3146
|
+
if (!binary) {
|
|
3147
|
+
for (let offset = 0; offset < next.value.length; offset += 49152) await sendChunk(next.value.subarray(offset, offset + 49152));
|
|
3148
|
+
continue;
|
|
3149
|
+
}
|
|
3150
|
+
carry = carry ? Buffer.concat([carry, next.value]) : Buffer.from(next.value);
|
|
3151
|
+
while (carry.length >= BINARY_CHUNK_BYTES) {
|
|
3152
|
+
await sendChunk(carry.subarray(0, BINARY_CHUNK_BYTES));
|
|
3153
|
+
carry = carry.subarray(BINARY_CHUNK_BYTES);
|
|
2784
3154
|
}
|
|
2785
3155
|
}
|
|
2786
3156
|
signal.throwIfAborted();
|
|
3157
|
+
if (carry !== void 0 && carry.length > 0) await sendChunk(carry);
|
|
2787
3158
|
ctx.send("s2c.ack", {
|
|
2788
3159
|
sessionId: payload.sessionId,
|
|
2789
3160
|
fileName: format === "markdown" ? "dsh-session.md" : "dsh-session-log.zip",
|
|
@@ -2806,8 +3177,7 @@ const commandRows = [...[
|
|
|
2806
3177
|
}
|
|
2807
3178
|
}
|
|
2808
3179
|
}];
|
|
2809
|
-
|
|
2810
|
-
//#region src/plugin-update.ts
|
|
3180
|
+
// src/plugin-update.ts
|
|
2811
3181
|
const PACKAGE = "dsh-deeppilot";
|
|
2812
3182
|
const PHASES = [
|
|
2813
3183
|
"preparing",
|
|
@@ -3254,8 +3624,7 @@ function supportsPluginUpdate(value) {
|
|
|
3254
3624
|
"registries"
|
|
3255
3625
|
].every((key) => typeof value[key] === "function");
|
|
3256
3626
|
}
|
|
3257
|
-
|
|
3258
|
-
//#region src/wire-plugin-update.ts
|
|
3627
|
+
// src/wire-plugin-update.ts
|
|
3259
3628
|
function payloadObject$1(value) {
|
|
3260
3629
|
if (value === void 0 || value === null) return {
|
|
3261
3630
|
ok: true,
|
|
@@ -3459,8 +3828,7 @@ const ROWS_BY_TYPE = new Map(WIRE_FRAME_ROWS.map((row) => [row.type, row]));
|
|
|
3459
3828
|
function registryRowFor(type) {
|
|
3460
3829
|
return ROWS_BY_TYPE.get(type);
|
|
3461
3830
|
}
|
|
3462
|
-
|
|
3463
|
-
//#region src/connection-gate.ts
|
|
3831
|
+
// src/connection-gate.ts
|
|
3464
3832
|
/**
|
|
3465
3833
|
* 连接门(ConnectionGate)——「一个匿名 socket 被允许做什么」的唯一属主。
|
|
3466
3834
|
*
|
|
@@ -3752,6 +4120,7 @@ var ConnectionGate = class {
|
|
|
3752
4120
|
debug: host.debug,
|
|
3753
4121
|
log: (message) => host.log(message),
|
|
3754
4122
|
send: (type, payload) => host.send(type, payload, env.id),
|
|
4123
|
+
...host.sendBinary ? { sendBinary: (type, payload, body) => host.sendBinary(type, payload, body, env.id) } : {},
|
|
3755
4124
|
fail: (code, message) => host.fail(env.id, code, message),
|
|
3756
4125
|
close: (code, reason) => host.close(code, reason),
|
|
3757
4126
|
canReceive: (scope) => host.canReceive(scope),
|
|
@@ -3779,8 +4148,7 @@ var ConnectionGate = class {
|
|
|
3779
4148
|
};
|
|
3780
4149
|
}
|
|
3781
4150
|
};
|
|
3782
|
-
|
|
3783
|
-
//#region src/connection.ts
|
|
4151
|
+
// src/connection.ts
|
|
3784
4152
|
/**
|
|
3785
4153
|
* One connected phone. Implements BridgeSink so the HostBridge can push
|
|
3786
4154
|
* projected frames and replays.
|
|
@@ -3852,6 +4220,7 @@ var BridgeConnection = class {
|
|
|
3852
4220
|
},
|
|
3853
4221
|
log: (message) => deps.log(message),
|
|
3854
4222
|
send: (type, payload, id) => connection.send(type, payload, id),
|
|
4223
|
+
sendBinary: (type, payload, body, id) => connection.send(type, payload, id, void 0, body),
|
|
3855
4224
|
fail: (id, code, message) => connection.fail(id, code, message),
|
|
3856
4225
|
close: (code, reason) => connection.close(code, reason),
|
|
3857
4226
|
canReceive: (scope) => connection.canReceive(scope),
|
|
@@ -4021,7 +4390,8 @@ var BridgeConnection = class {
|
|
|
4021
4390
|
this.ws.terminate();
|
|
4022
4391
|
}
|
|
4023
4392
|
}
|
|
4024
|
-
|
|
4393
|
+
/** `body` 存在时按二进制帧发送(见 binary-frame.ts),头部仍是同一个 envelope。 */
|
|
4394
|
+
send(type, payload, id, seq, body) {
|
|
4025
4395
|
const envelope = {
|
|
4026
4396
|
v: 2,
|
|
4027
4397
|
type,
|
|
@@ -4035,7 +4405,7 @@ var BridgeConnection = class {
|
|
|
4035
4405
|
this.close(1013, "client too slow");
|
|
4036
4406
|
return;
|
|
4037
4407
|
}
|
|
4038
|
-
this.ws.send(JSON.stringify(envelope));
|
|
4408
|
+
this.ws.send(body === void 0 ? JSON.stringify(envelope) : encodeBinaryFrame(envelope, body));
|
|
4039
4409
|
}
|
|
4040
4410
|
fail(id, code, message) {
|
|
4041
4411
|
this.send("s2c.error", {
|
|
@@ -4069,8 +4439,7 @@ var BridgeConnection = class {
|
|
|
4069
4439
|
function normalizeFunnelConnectionLimit(value) {
|
|
4070
4440
|
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 16 ? value : 8;
|
|
4071
4441
|
}
|
|
4072
|
-
|
|
4073
|
-
//#region src/tailscale-version.ts
|
|
4442
|
+
// src/tailscale-version.ts
|
|
4074
4443
|
/**
|
|
4075
4444
|
* 内嵌 helper 上报的 tailscale.com 模块版本。
|
|
4076
4445
|
*
|
|
@@ -4093,8 +4462,7 @@ const MAX_TAILSCALE_VERSION_LENGTH = 80;
|
|
|
4093
4462
|
function isTailscaleVersion(value) {
|
|
4094
4463
|
return typeof value === "string" && value.length <= MAX_TAILSCALE_VERSION_LENGTH && TAILSCALE_VERSION_PATTERN.test(value);
|
|
4095
4464
|
}
|
|
4096
|
-
|
|
4097
|
-
//#region src/remote-supervisor.ts
|
|
4465
|
+
// src/remote-supervisor.ts
|
|
4098
4466
|
const RESTART_DELAYS_MS = [
|
|
4099
4467
|
1e3,
|
|
4100
4468
|
2e3,
|
|
@@ -4523,8 +4891,7 @@ var RemoteSupervisor = class {
|
|
|
4523
4891
|
else if (next.phase === "error" || next.phase === "unavailable") this.options.log(`remote Funnel ${next.phase}: ${next.message ?? "unknown error"}`);
|
|
4524
4892
|
}
|
|
4525
4893
|
};
|
|
4526
|
-
|
|
4527
|
-
//#region src/local-policy.ts
|
|
4894
|
+
// src/local-policy.ts
|
|
4528
4895
|
const DEFAULT_LOCAL_PORT = 3098;
|
|
4529
4896
|
const MIN_LOCAL_PORT = 1024;
|
|
4530
4897
|
const MAX_LOCAL_PORT = 65535;
|
|
@@ -4542,8 +4909,7 @@ function localListenError(error, port) {
|
|
|
4542
4909
|
if (code === "EACCES") return `permission denied while opening local port ${port}`;
|
|
4543
4910
|
return error instanceof Error ? error.message : String(error);
|
|
4544
4911
|
}
|
|
4545
|
-
|
|
4546
|
-
//#region src/config.ts
|
|
4912
|
+
// src/config.ts
|
|
4547
4913
|
/** Operator-run relay used by distributed builds; overridable via config. */
|
|
4548
4914
|
const DEFAULT_RELAY_URL = "https://pilot.hailab.dev";
|
|
4549
4915
|
const Config = z.object({
|
|
@@ -4632,8 +4998,7 @@ function normalizeOptions(options) {
|
|
|
4632
4998
|
if (options && typeof options === "object") return plainConfig(options);
|
|
4633
4999
|
return plainConfig(Config(void 0) ?? {});
|
|
4634
5000
|
}
|
|
4635
|
-
|
|
4636
|
-
//#region src/apns.ts
|
|
5001
|
+
// src/apns.ts
|
|
4637
5002
|
/**
|
|
4638
5003
|
* Minimal APNs provider client (HTTP/2) with zero npm dependencies.
|
|
4639
5004
|
*
|
|
@@ -4870,8 +5235,7 @@ var ApnsClient = class {
|
|
|
4870
5235
|
return token.length <= 10 ? "…" : token.slice(0, 6) + "…" + token.slice(-4);
|
|
4871
5236
|
}
|
|
4872
5237
|
};
|
|
4873
|
-
|
|
4874
|
-
//#region src/relay-url.ts
|
|
5238
|
+
// src/relay-url.ts
|
|
4875
5239
|
/**
|
|
4876
5240
|
* Normalize an operator-configured relay URL without a backtracking regular
|
|
4877
5241
|
* expression. Scanning from the end keeps even pathological inputs linear.
|
|
@@ -4882,8 +5246,7 @@ function normalizeRelayBaseUrl(value) {
|
|
|
4882
5246
|
while (end > 0 && trimmed.charCodeAt(end - 1) === 47) end -= 1;
|
|
4883
5247
|
return end === trimmed.length ? trimmed : trimmed.slice(0, end);
|
|
4884
5248
|
}
|
|
4885
|
-
|
|
4886
|
-
//#region src/relay-client.ts
|
|
5249
|
+
// src/relay-client.ts
|
|
4887
5250
|
var RelayClient = class {
|
|
4888
5251
|
base;
|
|
4889
5252
|
token;
|
|
@@ -4976,8 +5339,7 @@ var RelayClient = class {
|
|
|
4976
5339
|
}
|
|
4977
5340
|
}
|
|
4978
5341
|
};
|
|
4979
|
-
|
|
4980
|
-
//#region src/widget-push.ts
|
|
5342
|
+
// src/widget-push.ts
|
|
4981
5343
|
/** Coalesce bursts and bound continuous updates, without starving the trailing
|
|
4982
5344
|
* state. One scheduler per host, not per token or per streaming text frame. */
|
|
4983
5345
|
var WidgetPushScheduler = class {
|
|
@@ -5014,8 +5376,7 @@ var WidgetPushScheduler = class {
|
|
|
5014
5376
|
this.timer = void 0;
|
|
5015
5377
|
}
|
|
5016
5378
|
};
|
|
5017
|
-
|
|
5018
|
-
//#region src/live-activity.ts
|
|
5379
|
+
// src/live-activity.ts
|
|
5019
5380
|
function liveActivityState(session) {
|
|
5020
5381
|
const total = Math.max(0, session?.todos?.total ?? session?.todoItems?.length ?? 0);
|
|
5021
5382
|
const done = Math.min(total, Math.max(0, session?.todos?.done ?? 0));
|
|
@@ -5087,8 +5448,7 @@ var LiveActivityPushManager = class {
|
|
|
5087
5448
|
this.scheduler.dispose();
|
|
5088
5449
|
}
|
|
5089
5450
|
};
|
|
5090
|
-
|
|
5091
|
-
//#region src/push-policy.ts
|
|
5451
|
+
// src/push-policy.ts
|
|
5092
5452
|
/** Prune only when the provider supplies an authoritative token-lifecycle verdict. */
|
|
5093
5453
|
function shouldPrunePushToken(outcome, reason) {
|
|
5094
5454
|
return outcome === "invalid-token" && (reason === "Unregistered" || reason === "ExpiredToken");
|
|
@@ -5124,8 +5484,7 @@ function pushContent(notification, mode) {
|
|
|
5124
5484
|
body: bodies[notification.category] ?? "Open DeepPilot for an update."
|
|
5125
5485
|
};
|
|
5126
5486
|
}
|
|
5127
|
-
|
|
5128
|
-
//#region src/relay-test.ts
|
|
5487
|
+
// src/relay-test.ts
|
|
5129
5488
|
/**
|
|
5130
5489
|
* Relay connectivity self-test used by the settings page "测试中继" button.
|
|
5131
5490
|
*
|
|
@@ -5251,8 +5610,7 @@ async function runRelayProbe(options) {
|
|
|
5251
5610
|
steps
|
|
5252
5611
|
};
|
|
5253
5612
|
}
|
|
5254
|
-
|
|
5255
|
-
//#region src/push-gateway.ts
|
|
5613
|
+
// src/push-gateway.ts
|
|
5256
5614
|
/**
|
|
5257
5615
|
* 推送网关(PushGateway)——离线推送的单一属主。
|
|
5258
5616
|
*
|
|
@@ -5742,8 +6100,7 @@ var PushGateway = class {
|
|
|
5742
6100
|
await this.ensureRelayEnrolled(url);
|
|
5743
6101
|
}
|
|
5744
6102
|
};
|
|
5745
|
-
|
|
5746
|
-
//#region src/phone-server.ts
|
|
6103
|
+
// src/phone-server.ts
|
|
5747
6104
|
/**
|
|
5748
6105
|
* A deliberately narrow transport listener. Both the LAN endpoint and the
|
|
5749
6106
|
* loopback-only Funnel origin use this factory, so neither can accidentally
|
|
@@ -5822,8 +6179,7 @@ function closeServer(server) {
|
|
|
5822
6179
|
if (server === void 0 || !server.listening) return Promise.resolve();
|
|
5823
6180
|
return new Promise((resolve) => server.close(() => resolve()));
|
|
5824
6181
|
}
|
|
5825
|
-
|
|
5826
|
-
//#region src/transport-reconciler.ts
|
|
6182
|
+
// src/transport-reconciler.ts
|
|
5827
6183
|
var TransportReconciler = class {
|
|
5828
6184
|
spec;
|
|
5829
6185
|
config;
|
|
@@ -6051,8 +6407,7 @@ function createRemoteTransport(deps) {
|
|
|
6051
6407
|
statusOf: (supervisor) => supervisor?.status() ?? fallbackStatus()
|
|
6052
6408
|
}, deps.config, deps.log, fallbackStatus());
|
|
6053
6409
|
}
|
|
6054
|
-
|
|
6055
|
-
//#region src/dispatch-journal.ts
|
|
6410
|
+
// src/dispatch-journal.ts
|
|
6056
6411
|
/**
|
|
6057
6412
|
* at-most-once dispatch journal:prompt 投递与非 prompt 变更的**同一个** module。
|
|
6058
6413
|
*
|
|
@@ -6484,8 +6839,7 @@ function scheduleMutationCodec(persistValues) {
|
|
|
6484
6839
|
}))
|
|
6485
6840
|
};
|
|
6486
6841
|
}
|
|
6487
|
-
|
|
6488
|
-
//#region src/notification-text.ts
|
|
6842
|
+
// src/notification-text.ts
|
|
6489
6843
|
/**
|
|
6490
6844
|
* 通知正文的纯文本投影。
|
|
6491
6845
|
*
|
|
@@ -6528,8 +6882,7 @@ function decodePercentRuns(text) {
|
|
|
6528
6882
|
}
|
|
6529
6883
|
});
|
|
6530
6884
|
}
|
|
6531
|
-
|
|
6532
|
-
//#region src/host-api.ts
|
|
6885
|
+
// src/host-api.ts
|
|
6533
6886
|
/**
|
|
6534
6887
|
* Subagent sessions are host-internal workers of a parent conversation.
|
|
6535
6888
|
* They must never surface on the phone: not in the project/session list,
|
|
@@ -6552,8 +6905,7 @@ function unwrapStreamItem(item) {
|
|
|
6552
6905
|
} : nested;
|
|
6553
6906
|
return item;
|
|
6554
6907
|
}
|
|
6555
|
-
|
|
6556
|
-
//#region src/host-capabilities.ts
|
|
6908
|
+
// src/host-capabilities.ts
|
|
6557
6909
|
const pluginUpdateProbes = /* @__PURE__ */ new WeakMap();
|
|
6558
6910
|
function setPluginUpdateProbe(proxy, probe) {
|
|
6559
6911
|
if (probe) pluginUpdateProbes.set(proxy, probe);
|
|
@@ -6569,6 +6921,7 @@ const HOST_CAPABILITY_PROBES = {
|
|
|
6569
6921
|
sessionRestore: (proxy) => typeof proxy.workspace?.unarchiveSession === "function",
|
|
6570
6922
|
projectSelection: (proxy) => typeof proxy.workspace?.list === "function" && typeof proxy.workspace?.create === "function",
|
|
6571
6923
|
schedules: (proxy) => proxy.schedule !== void 0,
|
|
6924
|
+
schedulesHost: (proxy) => proxy.schedule?.mode === "host",
|
|
6572
6925
|
promptQueue: (proxy) => typeof proxy.sessions.updateQueue === "function",
|
|
6573
6926
|
sessionMarkdown: (proxy) => proxy.commands?.markdownAvailable === true && typeof proxy.commands?.export === "function",
|
|
6574
6927
|
assistantStream: (proxy) => proxy.assistantStreamAvailable === true,
|
|
@@ -6582,6 +6935,7 @@ function capabilityBits(proxy, pushOutlet) {
|
|
|
6582
6935
|
return {
|
|
6583
6936
|
historyPaging: true,
|
|
6584
6937
|
replay: true,
|
|
6938
|
+
binaryFrames: true,
|
|
6585
6939
|
approvals: true,
|
|
6586
6940
|
questions: true,
|
|
6587
6941
|
pendingSnapshot: true,
|
|
@@ -6595,6 +6949,7 @@ function capabilityBits(proxy, pushOutlet) {
|
|
|
6595
6949
|
sessionRestore: HOST_CAPABILITY_PROBES.sessionRestore(proxy),
|
|
6596
6950
|
projectSelection: HOST_CAPABILITY_PROBES.projectSelection(proxy),
|
|
6597
6951
|
schedules: HOST_CAPABILITY_PROBES.schedules(proxy),
|
|
6952
|
+
schedulesHost: HOST_CAPABILITY_PROBES.schedulesHost(proxy),
|
|
6598
6953
|
promptQueue: HOST_CAPABILITY_PROBES.promptQueue(proxy),
|
|
6599
6954
|
sessionMarkdown: HOST_CAPABILITY_PROBES.sessionMarkdown(proxy),
|
|
6600
6955
|
assistantStream: HOST_CAPABILITY_PROBES.assistantStream(proxy),
|
|
@@ -6605,8 +6960,7 @@ function capabilityBits(proxy, pushOutlet) {
|
|
|
6605
6960
|
deviceRevoke: true
|
|
6606
6961
|
};
|
|
6607
6962
|
}
|
|
6608
|
-
|
|
6609
|
-
//#region src/prompt-queue.ts
|
|
6963
|
+
// src/prompt-queue.ts
|
|
6610
6964
|
/**
|
|
6611
6965
|
* 运行中排队与转向(PROTOCOL.md「运行中排队与转向扩展」)的纯函数部分。
|
|
6612
6966
|
*
|
|
@@ -6857,8 +7211,7 @@ var AssistantStream = class {
|
|
|
6857
7211
|
this.attempts.clear();
|
|
6858
7212
|
}
|
|
6859
7213
|
};
|
|
6860
|
-
|
|
6861
|
-
//#region src/host-bridge.ts
|
|
7214
|
+
// src/host-bridge.ts
|
|
6862
7215
|
const MAX_RING_DEFAULT = 2e3;
|
|
6863
7216
|
/**
|
|
6864
7217
|
* Process-wide bridge state: session mirror, pending approvals/questions,
|
|
@@ -8182,6 +8535,17 @@ var HostBridge = class {
|
|
|
8182
8535
|
replayed: result.replayed
|
|
8183
8536
|
};
|
|
8184
8537
|
}
|
|
8538
|
+
/**
|
|
8539
|
+
* 当前 adapter 是否宿主级计划任务模型。
|
|
8540
|
+
*
|
|
8541
|
+
* 能力位 `schedulesHost`(host-capabilities.ts)与新帧、旧帧映射全部读这一个
|
|
8542
|
+
* 判别位:探测只发生一次(DshApiProxy.resolveSchedule),这里不再看控制器
|
|
8543
|
+
* 方法,也不看版本号字符串。
|
|
8544
|
+
*/
|
|
8545
|
+
get hostSchedule() {
|
|
8546
|
+
const schedule = this.apiProxy.schedule;
|
|
8547
|
+
return schedule?.mode === "host" ? schedule : void 0;
|
|
8548
|
+
}
|
|
8185
8549
|
async listSchedules(sessionId) {
|
|
8186
8550
|
const schedule = this.apiProxy.schedule;
|
|
8187
8551
|
if (schedule === void 0) return {
|
|
@@ -8189,6 +8553,7 @@ var HostBridge = class {
|
|
|
8189
8553
|
code: "E_UNSUPPORTED",
|
|
8190
8554
|
message: "schedules unavailable on this host version"
|
|
8191
8555
|
};
|
|
8556
|
+
if (schedule.mode === "host") return this.listHostSchedulesAsLegacy(schedule, sessionId);
|
|
8192
8557
|
try {
|
|
8193
8558
|
const response = await schedule.list({
|
|
8194
8559
|
rpcId: randomUUID(),
|
|
@@ -8212,64 +8577,126 @@ var HostBridge = class {
|
|
|
8212
8577
|
};
|
|
8213
8578
|
}
|
|
8214
8579
|
}
|
|
8215
|
-
|
|
8216
|
-
|
|
8580
|
+
/** 宿主级列表 -> 旧会话级视图:过滤 session 目标、丢弃 ended、按 scheduledAt 升序。 */
|
|
8581
|
+
async listHostSchedulesAsLegacy(schedule, sessionId) {
|
|
8582
|
+
try {
|
|
8583
|
+
const tasks = [];
|
|
8584
|
+
let offset = 0;
|
|
8585
|
+
for (;;) {
|
|
8586
|
+
const response = await schedule.list({
|
|
8587
|
+
rpcId: randomUUID(),
|
|
8588
|
+
payload: {
|
|
8589
|
+
limit: HOST_SCHEDULE_PAGE_SIZE,
|
|
8590
|
+
offset
|
|
8591
|
+
}
|
|
8592
|
+
});
|
|
8593
|
+
if (!response.result) return {
|
|
8594
|
+
ok: false,
|
|
8595
|
+
code: "E_INTERNAL",
|
|
8596
|
+
message: "schedule list returned no result"
|
|
8597
|
+
};
|
|
8598
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8599
|
+
const page = response.result.value;
|
|
8600
|
+
tasks.push(...page.tasks);
|
|
8601
|
+
offset += page.tasks.length;
|
|
8602
|
+
if (page.tasks.length === 0 || offset >= page.total) break;
|
|
8603
|
+
}
|
|
8604
|
+
const views = [];
|
|
8605
|
+
for (const task of tasks) {
|
|
8606
|
+
if (task.target.kind !== "session" || task.target.sessionId !== sessionId) continue;
|
|
8607
|
+
if (task.status === "ended") continue;
|
|
8608
|
+
const view = toLegacyScheduleView(task);
|
|
8609
|
+
if (view !== void 0) views.push(view);
|
|
8610
|
+
}
|
|
8611
|
+
views.sort((left, right) => left.scheduledAt < right.scheduledAt ? -1 : left.scheduledAt > right.scheduledAt ? 1 : 0);
|
|
8612
|
+
return {
|
|
8613
|
+
ok: true,
|
|
8614
|
+
value: views
|
|
8615
|
+
};
|
|
8616
|
+
} catch (error) {
|
|
8617
|
+
return wireErrorOf("schedule", hostFailureOf(error, "internal"));
|
|
8618
|
+
}
|
|
8619
|
+
}
|
|
8620
|
+
async listHostSchedules(request = {}) {
|
|
8621
|
+
const schedule = this.hostSchedule;
|
|
8217
8622
|
if (schedule === void 0) return {
|
|
8218
8623
|
ok: false,
|
|
8219
8624
|
code: "E_UNSUPPORTED",
|
|
8220
|
-
message: "schedules unavailable on this host version"
|
|
8625
|
+
message: "host-level schedules unavailable on this host version"
|
|
8221
8626
|
};
|
|
8222
8627
|
try {
|
|
8223
|
-
const response = await schedule.
|
|
8628
|
+
const response = await schedule.list({
|
|
8224
8629
|
rpcId: randomUUID(),
|
|
8225
|
-
payload:
|
|
8226
|
-
sessionId,
|
|
8227
|
-
id,
|
|
8228
|
-
limit,
|
|
8229
|
-
...before !== void 0 ? { before } : {}
|
|
8230
|
-
}
|
|
8630
|
+
payload: request
|
|
8231
8631
|
});
|
|
8232
8632
|
if (!response.result) return {
|
|
8233
8633
|
ok: false,
|
|
8234
8634
|
code: "E_INTERNAL",
|
|
8235
|
-
message: "schedule
|
|
8635
|
+
message: "schedule host list returned no result"
|
|
8236
8636
|
};
|
|
8237
8637
|
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8238
8638
|
return {
|
|
8239
8639
|
ok: true,
|
|
8240
|
-
value: response.result.value
|
|
8640
|
+
value: response.result.value
|
|
8241
8641
|
};
|
|
8242
8642
|
} catch (error) {
|
|
8243
|
-
return
|
|
8643
|
+
return wireErrorOf("schedule", hostFailureOf(error, "internal"));
|
|
8644
|
+
}
|
|
8645
|
+
}
|
|
8646
|
+
async viewHostSchedule(id) {
|
|
8647
|
+
const schedule = this.hostSchedule;
|
|
8648
|
+
if (schedule === void 0) return {
|
|
8649
|
+
ok: false,
|
|
8650
|
+
code: "E_UNSUPPORTED",
|
|
8651
|
+
message: "host-level schedules unavailable on this host version"
|
|
8652
|
+
};
|
|
8653
|
+
try {
|
|
8654
|
+
const response = await schedule.view({
|
|
8655
|
+
rpcId: randomUUID(),
|
|
8656
|
+
payload: { id }
|
|
8657
|
+
});
|
|
8658
|
+
if (!response.result) return {
|
|
8244
8659
|
ok: false,
|
|
8245
8660
|
code: "E_INTERNAL",
|
|
8246
|
-
message:
|
|
8661
|
+
message: "schedule host view returned no result"
|
|
8662
|
+
};
|
|
8663
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8664
|
+
const value = response.result.value;
|
|
8665
|
+
if (value.kind === "not-found") return {
|
|
8666
|
+
ok: false,
|
|
8667
|
+
code: "E_NOT_FOUND",
|
|
8668
|
+
message: "schedule not found"
|
|
8247
8669
|
};
|
|
8670
|
+
return {
|
|
8671
|
+
ok: true,
|
|
8672
|
+
value: value.task
|
|
8673
|
+
};
|
|
8674
|
+
} catch (error) {
|
|
8675
|
+
return wireErrorOf("schedule", hostFailureOf(error, "internal"));
|
|
8248
8676
|
}
|
|
8249
8677
|
}
|
|
8250
|
-
async
|
|
8251
|
-
const schedule = this.
|
|
8678
|
+
async createHostSchedule(deviceId, payload) {
|
|
8679
|
+
const schedule = this.hostSchedule;
|
|
8252
8680
|
if (schedule === void 0) return {
|
|
8253
8681
|
ok: false,
|
|
8254
8682
|
code: "E_UNSUPPORTED",
|
|
8255
|
-
message: "schedules unavailable on this host version"
|
|
8683
|
+
message: "host-level schedules unavailable on this host version"
|
|
8256
8684
|
};
|
|
8257
|
-
const { clientRequestId,
|
|
8258
|
-
const result = await this.scheduleMutations.dispatch(deviceId, clientRequestId, {
|
|
8259
|
-
sessionId,
|
|
8260
|
-
request
|
|
8261
|
-
}, async () => {
|
|
8685
|
+
const { clientRequestId, ...request } = payload;
|
|
8686
|
+
const result = await this.scheduleMutations.dispatch(deviceId, clientRequestId, request, async () => {
|
|
8262
8687
|
const response = await schedule.create({
|
|
8263
8688
|
rpcId: randomUUID(),
|
|
8264
8689
|
payload: {
|
|
8265
|
-
|
|
8266
|
-
|
|
8690
|
+
title: request.title,
|
|
8691
|
+
prompt: request.prompt,
|
|
8692
|
+
timing: request.timing,
|
|
8693
|
+
...request.target !== void 0 ? { target: request.target } : {}
|
|
8267
8694
|
}
|
|
8268
8695
|
});
|
|
8269
8696
|
if (!response.result) return {
|
|
8270
8697
|
ok: false,
|
|
8271
8698
|
code: "E_INTERNAL",
|
|
8272
|
-
message: "schedule create returned no result"
|
|
8699
|
+
message: "schedule host create returned no result"
|
|
8273
8700
|
};
|
|
8274
8701
|
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8275
8702
|
return {
|
|
@@ -8292,39 +8719,500 @@ var HostBridge = class {
|
|
|
8292
8719
|
replayed: result.replayed
|
|
8293
8720
|
};
|
|
8294
8721
|
}
|
|
8295
|
-
async
|
|
8296
|
-
const schedule = this.
|
|
8722
|
+
async editHostSchedule(deviceId, payload) {
|
|
8723
|
+
const schedule = this.hostSchedule;
|
|
8297
8724
|
if (schedule === void 0) return {
|
|
8298
8725
|
ok: false,
|
|
8299
8726
|
code: "E_UNSUPPORTED",
|
|
8300
|
-
message: "schedules unavailable on this host version"
|
|
8727
|
+
message: "host-level schedules unavailable on this host version"
|
|
8301
8728
|
};
|
|
8302
8729
|
const { clientRequestId, ...request } = payload;
|
|
8303
8730
|
const result = await this.scheduleMutations.dispatch(deviceId, clientRequestId, request, async () => {
|
|
8304
|
-
const
|
|
8731
|
+
const expected = request.expectedUpdatedAt !== void 0 ? { expectedUpdatedAt: request.expectedUpdatedAt } : {};
|
|
8732
|
+
const response = await schedule.edit({
|
|
8305
8733
|
rpcId: randomUUID(),
|
|
8306
|
-
payload: request
|
|
8734
|
+
payload: request.action === "update" ? {
|
|
8735
|
+
id: request.id,
|
|
8736
|
+
action: "update",
|
|
8737
|
+
patch: request.patch,
|
|
8738
|
+
...expected
|
|
8739
|
+
} : {
|
|
8740
|
+
id: request.id,
|
|
8741
|
+
action: "delete",
|
|
8742
|
+
...expected
|
|
8743
|
+
}
|
|
8307
8744
|
});
|
|
8308
8745
|
if (!response.result) return {
|
|
8309
8746
|
ok: false,
|
|
8310
8747
|
code: "E_INTERNAL",
|
|
8311
|
-
message: "schedule
|
|
8748
|
+
message: "schedule host edit returned no result"
|
|
8312
8749
|
};
|
|
8313
8750
|
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8314
8751
|
const value = response.result.value;
|
|
8315
|
-
if (value.
|
|
8752
|
+
if (value.kind === "conflict") return {
|
|
8316
8753
|
ok: false,
|
|
8317
|
-
code:
|
|
8318
|
-
message:
|
|
8754
|
+
code: "E_BUSY",
|
|
8755
|
+
message: "schedule changed since it was read"
|
|
8319
8756
|
};
|
|
8320
|
-
return {
|
|
8321
|
-
ok:
|
|
8322
|
-
|
|
8757
|
+
if (value.kind === "not-found") return {
|
|
8758
|
+
ok: false,
|
|
8759
|
+
code: "E_NOT_FOUND",
|
|
8760
|
+
message: "schedule not found"
|
|
8323
8761
|
};
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8762
|
+
if (request.action === "delete") {
|
|
8763
|
+
if (value.kind !== "deleted") return {
|
|
8764
|
+
ok: false,
|
|
8765
|
+
code: "E_INTERNAL",
|
|
8766
|
+
message: "schedule delete returned an updated task"
|
|
8767
|
+
};
|
|
8768
|
+
return {
|
|
8769
|
+
ok: true,
|
|
8770
|
+
value: {
|
|
8771
|
+
deleted: true,
|
|
8772
|
+
id: value.id
|
|
8773
|
+
}
|
|
8774
|
+
};
|
|
8775
|
+
}
|
|
8776
|
+
if (value.kind !== "task") return {
|
|
8777
|
+
ok: false,
|
|
8778
|
+
code: "E_INTERNAL",
|
|
8779
|
+
message: "schedule update returned a deletion"
|
|
8780
|
+
};
|
|
8781
|
+
return {
|
|
8782
|
+
ok: true,
|
|
8783
|
+
value: { task: value.task }
|
|
8784
|
+
};
|
|
8785
|
+
});
|
|
8786
|
+
if (result.ok) {
|
|
8787
|
+
this.record("s2c.schedule.changed", {});
|
|
8788
|
+
return {
|
|
8789
|
+
ok: true,
|
|
8790
|
+
value: result.value,
|
|
8791
|
+
replayed: result.replayed
|
|
8792
|
+
};
|
|
8793
|
+
}
|
|
8794
|
+
return {
|
|
8795
|
+
ok: false,
|
|
8796
|
+
code: result.code,
|
|
8797
|
+
message: result.message ?? result.code,
|
|
8798
|
+
replayed: result.replayed
|
|
8799
|
+
};
|
|
8800
|
+
}
|
|
8801
|
+
async runHostSchedule(deviceId, payload) {
|
|
8802
|
+
const schedule = this.hostSchedule;
|
|
8803
|
+
if (schedule === void 0) return {
|
|
8804
|
+
ok: false,
|
|
8805
|
+
code: "E_UNSUPPORTED",
|
|
8806
|
+
message: "host-level schedules unavailable on this host version"
|
|
8807
|
+
};
|
|
8808
|
+
const result = await this.scheduleMutations.dispatch(deviceId, payload.clientRequestId, { id: payload.id }, async () => {
|
|
8809
|
+
const response = await schedule.run({
|
|
8810
|
+
rpcId: randomUUID(),
|
|
8811
|
+
payload: { id: payload.id }
|
|
8812
|
+
});
|
|
8813
|
+
if (!response.result) return {
|
|
8814
|
+
ok: false,
|
|
8815
|
+
code: "E_INTERNAL",
|
|
8816
|
+
message: "schedule host run returned no result"
|
|
8817
|
+
};
|
|
8818
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8819
|
+
const value = response.result.value;
|
|
8820
|
+
if (value.kind === "pending") return {
|
|
8821
|
+
ok: false,
|
|
8822
|
+
code: "E_BUSY",
|
|
8823
|
+
message: "schedule execution already pending"
|
|
8824
|
+
};
|
|
8825
|
+
if (value.kind === "not-found") return {
|
|
8826
|
+
ok: false,
|
|
8827
|
+
code: "E_NOT_FOUND",
|
|
8828
|
+
message: "schedule not found"
|
|
8829
|
+
};
|
|
8830
|
+
return {
|
|
8831
|
+
ok: true,
|
|
8832
|
+
value: value.task
|
|
8833
|
+
};
|
|
8834
|
+
});
|
|
8835
|
+
if (result.ok) {
|
|
8836
|
+
this.record("s2c.schedule.changed", {});
|
|
8837
|
+
return {
|
|
8838
|
+
ok: true,
|
|
8839
|
+
value: result.value,
|
|
8840
|
+
replayed: result.replayed
|
|
8841
|
+
};
|
|
8842
|
+
}
|
|
8843
|
+
return {
|
|
8844
|
+
ok: false,
|
|
8845
|
+
code: result.code,
|
|
8846
|
+
message: result.message ?? result.code,
|
|
8847
|
+
replayed: result.replayed
|
|
8848
|
+
};
|
|
8849
|
+
}
|
|
8850
|
+
/**
|
|
8851
|
+
* 删除宿主上全部已结束(`status="ended"`)的任务,返回删除条数。
|
|
8852
|
+
*
|
|
8853
|
+
* 与其他变更帧共用 scheduleMutations journal:同一 clientRequestId 重放时直接回放
|
|
8854
|
+
* 首次结果,不会再删一次。成功后广播 `s2c.schedule.changed`;失败时同样广播——上游
|
|
8855
|
+
* 文档说明失败的批次无法确定哪些行已删除,客户端应重新读取列表。
|
|
8856
|
+
*/
|
|
8857
|
+
async deleteEndedHostSchedules(deviceId, payload) {
|
|
8858
|
+
const schedule = this.hostSchedule;
|
|
8859
|
+
if (schedule === void 0) return {
|
|
8860
|
+
ok: false,
|
|
8861
|
+
code: "E_UNSUPPORTED",
|
|
8862
|
+
message: "host-level schedules unavailable on this host version"
|
|
8863
|
+
};
|
|
8864
|
+
const result = await this.scheduleMutations.dispatch(deviceId, payload.clientRequestId, { operation: "deleteEnded" }, async () => {
|
|
8865
|
+
const response = await schedule.deleteEnded({
|
|
8866
|
+
rpcId: randomUUID(),
|
|
8867
|
+
payload: {}
|
|
8868
|
+
});
|
|
8869
|
+
if (!response.result) return {
|
|
8870
|
+
ok: false,
|
|
8871
|
+
code: "E_INTERNAL",
|
|
8872
|
+
message: "schedule delete ended returned no result"
|
|
8873
|
+
};
|
|
8874
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8875
|
+
return {
|
|
8876
|
+
ok: true,
|
|
8877
|
+
value: response.result.value
|
|
8878
|
+
};
|
|
8879
|
+
});
|
|
8880
|
+
this.record("s2c.schedule.changed", {});
|
|
8881
|
+
if (result.ok) return {
|
|
8882
|
+
ok: true,
|
|
8883
|
+
value: result.value ?? {},
|
|
8884
|
+
replayed: result.replayed
|
|
8885
|
+
};
|
|
8886
|
+
return {
|
|
8887
|
+
ok: false,
|
|
8888
|
+
code: result.code,
|
|
8889
|
+
message: result.message ?? result.code,
|
|
8890
|
+
replayed: result.replayed
|
|
8891
|
+
};
|
|
8892
|
+
}
|
|
8893
|
+
async scheduleLimits() {
|
|
8894
|
+
const schedule = this.hostSchedule;
|
|
8895
|
+
if (schedule === void 0) return {
|
|
8896
|
+
ok: false,
|
|
8897
|
+
code: "E_UNSUPPORTED",
|
|
8898
|
+
message: "host-level schedules unavailable on this host version"
|
|
8899
|
+
};
|
|
8900
|
+
try {
|
|
8901
|
+
const response = await schedule.limits({
|
|
8902
|
+
rpcId: randomUUID(),
|
|
8903
|
+
payload: {}
|
|
8904
|
+
});
|
|
8905
|
+
if (!response.result) return {
|
|
8906
|
+
ok: false,
|
|
8907
|
+
code: "E_INTERNAL",
|
|
8908
|
+
message: "schedule limits returned no result"
|
|
8909
|
+
};
|
|
8910
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8911
|
+
return {
|
|
8912
|
+
ok: true,
|
|
8913
|
+
value: response.result.value
|
|
8914
|
+
};
|
|
8915
|
+
} catch (error) {
|
|
8916
|
+
return wireErrorOf("schedule", hostFailureOf(error, "internal"));
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
async scheduleHistory(sessionId, id, limit, before) {
|
|
8920
|
+
const schedule = this.apiProxy.schedule;
|
|
8921
|
+
if (schedule === void 0) return {
|
|
8922
|
+
ok: false,
|
|
8923
|
+
code: "E_UNSUPPORTED",
|
|
8924
|
+
message: "schedules unavailable on this host version"
|
|
8925
|
+
};
|
|
8926
|
+
if (schedule.mode === "host") try {
|
|
8927
|
+
const response = await schedule.history({
|
|
8928
|
+
rpcId: randomUUID(),
|
|
8929
|
+
payload: {
|
|
8930
|
+
id,
|
|
8931
|
+
limit,
|
|
8932
|
+
...before !== void 0 ? { before } : {}
|
|
8933
|
+
}
|
|
8934
|
+
});
|
|
8935
|
+
if (!response.result) return {
|
|
8936
|
+
ok: false,
|
|
8937
|
+
code: "E_INTERNAL",
|
|
8938
|
+
message: "schedule history returned no result"
|
|
8939
|
+
};
|
|
8940
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8941
|
+
return {
|
|
8942
|
+
ok: true,
|
|
8943
|
+
value: response.result.value
|
|
8944
|
+
};
|
|
8945
|
+
} catch (error) {
|
|
8946
|
+
return wireErrorOf("schedule", hostFailureOf(error, "internal"));
|
|
8947
|
+
}
|
|
8948
|
+
try {
|
|
8949
|
+
const response = await schedule.history({
|
|
8950
|
+
rpcId: randomUUID(),
|
|
8951
|
+
payload: {
|
|
8952
|
+
sessionId,
|
|
8953
|
+
id,
|
|
8954
|
+
limit,
|
|
8955
|
+
...before !== void 0 ? { before } : {}
|
|
8956
|
+
}
|
|
8957
|
+
});
|
|
8958
|
+
if (!response.result) return {
|
|
8959
|
+
ok: false,
|
|
8960
|
+
code: "E_INTERNAL",
|
|
8961
|
+
message: "schedule history returned no result"
|
|
8962
|
+
};
|
|
8963
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
8964
|
+
return {
|
|
8965
|
+
ok: true,
|
|
8966
|
+
value: response.result.value.history
|
|
8967
|
+
};
|
|
8968
|
+
} catch (error) {
|
|
8969
|
+
return {
|
|
8970
|
+
ok: false,
|
|
8971
|
+
code: "E_INTERNAL",
|
|
8972
|
+
message: String(error)
|
|
8973
|
+
};
|
|
8974
|
+
}
|
|
8975
|
+
}
|
|
8976
|
+
async createSchedule(deviceId, payload) {
|
|
8977
|
+
const schedule = this.apiProxy.schedule;
|
|
8978
|
+
if (schedule === void 0) return {
|
|
8979
|
+
ok: false,
|
|
8980
|
+
code: "E_UNSUPPORTED",
|
|
8981
|
+
message: "schedules unavailable on this host version"
|
|
8982
|
+
};
|
|
8983
|
+
if (schedule.mode === "host") {
|
|
8984
|
+
const { clientRequestId, sessionId, title, prompt, ...selectors } = payload;
|
|
8985
|
+
const timing = hostTimingFromLegacySelector(selectors);
|
|
8986
|
+
if (timing === void 0) return {
|
|
8987
|
+
ok: false,
|
|
8988
|
+
code: "E_PROTOCOL",
|
|
8989
|
+
message: "unsupported schedule selector"
|
|
8990
|
+
};
|
|
8991
|
+
const request = {
|
|
8992
|
+
title,
|
|
8993
|
+
prompt,
|
|
8994
|
+
timing,
|
|
8995
|
+
target: {
|
|
8996
|
+
kind: "session",
|
|
8997
|
+
sessionId
|
|
8998
|
+
}
|
|
8999
|
+
};
|
|
9000
|
+
const result = await this.scheduleMutations.dispatch(deviceId, clientRequestId, request, async () => {
|
|
9001
|
+
const response = await schedule.create({
|
|
9002
|
+
rpcId: randomUUID(),
|
|
9003
|
+
payload: {
|
|
9004
|
+
title,
|
|
9005
|
+
prompt,
|
|
9006
|
+
timing,
|
|
9007
|
+
target: {
|
|
9008
|
+
kind: "session",
|
|
9009
|
+
sessionId
|
|
9010
|
+
}
|
|
9011
|
+
}
|
|
9012
|
+
});
|
|
9013
|
+
if (!response.result) return {
|
|
9014
|
+
ok: false,
|
|
9015
|
+
code: "E_INTERNAL",
|
|
9016
|
+
message: "schedule create returned no result"
|
|
9017
|
+
};
|
|
9018
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
9019
|
+
const legacy = toLegacyScheduleView(response.result.value);
|
|
9020
|
+
if (legacy === void 0) return {
|
|
9021
|
+
ok: false,
|
|
9022
|
+
code: "E_PROTOCOL",
|
|
9023
|
+
message: "schedule cannot be represented in the session-scoped view"
|
|
9024
|
+
};
|
|
9025
|
+
return {
|
|
9026
|
+
ok: true,
|
|
9027
|
+
value: legacy
|
|
9028
|
+
};
|
|
9029
|
+
});
|
|
9030
|
+
if (result.ok) {
|
|
9031
|
+
this.record("s2c.schedule.changed", {});
|
|
9032
|
+
return {
|
|
9033
|
+
ok: true,
|
|
9034
|
+
value: result.value,
|
|
9035
|
+
replayed: result.replayed
|
|
9036
|
+
};
|
|
9037
|
+
}
|
|
9038
|
+
return {
|
|
9039
|
+
ok: false,
|
|
9040
|
+
code: result.code,
|
|
9041
|
+
message: result.message ?? result.code,
|
|
9042
|
+
replayed: result.replayed
|
|
9043
|
+
};
|
|
9044
|
+
}
|
|
9045
|
+
const { clientRequestId, sessionId, ...request } = payload;
|
|
9046
|
+
const result = await this.scheduleMutations.dispatch(deviceId, clientRequestId, {
|
|
9047
|
+
sessionId,
|
|
9048
|
+
request
|
|
9049
|
+
}, async () => {
|
|
9050
|
+
const response = await schedule.create({
|
|
9051
|
+
rpcId: randomUUID(),
|
|
9052
|
+
payload: {
|
|
9053
|
+
sessionId,
|
|
9054
|
+
...request
|
|
9055
|
+
}
|
|
9056
|
+
});
|
|
9057
|
+
if (!response.result) return {
|
|
9058
|
+
ok: false,
|
|
9059
|
+
code: "E_INTERNAL",
|
|
9060
|
+
message: "schedule create returned no result"
|
|
9061
|
+
};
|
|
9062
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
9063
|
+
return {
|
|
9064
|
+
ok: true,
|
|
9065
|
+
value: response.result.value
|
|
9066
|
+
};
|
|
9067
|
+
});
|
|
9068
|
+
if (result.ok) {
|
|
9069
|
+
this.record("s2c.schedule.changed", {});
|
|
9070
|
+
return {
|
|
9071
|
+
ok: true,
|
|
9072
|
+
value: result.value,
|
|
9073
|
+
replayed: result.replayed
|
|
9074
|
+
};
|
|
9075
|
+
}
|
|
9076
|
+
return {
|
|
9077
|
+
ok: false,
|
|
9078
|
+
code: result.code,
|
|
9079
|
+
message: result.message ?? result.code,
|
|
9080
|
+
replayed: result.replayed
|
|
9081
|
+
};
|
|
9082
|
+
}
|
|
9083
|
+
async updateSchedule(deviceId, payload) {
|
|
9084
|
+
const schedule = this.apiProxy.schedule;
|
|
9085
|
+
if (schedule === void 0) return {
|
|
9086
|
+
ok: false,
|
|
9087
|
+
code: "E_UNSUPPORTED",
|
|
9088
|
+
message: "schedules unavailable on this host version"
|
|
9089
|
+
};
|
|
9090
|
+
if (schedule.mode === "host") {
|
|
9091
|
+
const request = {
|
|
9092
|
+
sessionId: payload.sessionId,
|
|
9093
|
+
id: payload.id,
|
|
9094
|
+
expected: payload.expected,
|
|
9095
|
+
title: payload.title,
|
|
9096
|
+
prompt: payload.prompt,
|
|
9097
|
+
change: payload.change
|
|
9098
|
+
};
|
|
9099
|
+
const result = await this.scheduleMutations.dispatch(deviceId, payload.clientRequestId, request, async () => {
|
|
9100
|
+
const current = await schedule.view({
|
|
9101
|
+
rpcId: randomUUID(),
|
|
9102
|
+
payload: { id: payload.id }
|
|
9103
|
+
});
|
|
9104
|
+
if (!current.result) return {
|
|
9105
|
+
ok: false,
|
|
9106
|
+
code: "E_INTERNAL",
|
|
9107
|
+
message: "schedule view returned no result"
|
|
9108
|
+
};
|
|
9109
|
+
if (!current.result.ok) return wireErrorOf("schedule", current.result.error);
|
|
9110
|
+
if (current.result.value.kind === "not-found") return {
|
|
9111
|
+
ok: false,
|
|
9112
|
+
code: "E_NOT_FOUND",
|
|
9113
|
+
message: "schedule not found"
|
|
9114
|
+
};
|
|
9115
|
+
const currentTask = current.result.value.task;
|
|
9116
|
+
if (!legacyScheduleMatchesExpected(currentTask, payload.expected)) return {
|
|
9117
|
+
ok: false,
|
|
9118
|
+
code: "E_BUSY",
|
|
9119
|
+
message: "schedule changed since it was read"
|
|
9120
|
+
};
|
|
9121
|
+
const patch = hostPatchFromLegacyUpdate(payload);
|
|
9122
|
+
if (Object.keys(patch).length === 0) {
|
|
9123
|
+
const legacy = toLegacyScheduleView(currentTask);
|
|
9124
|
+
if (legacy === void 0) return {
|
|
9125
|
+
ok: false,
|
|
9126
|
+
code: "E_PROTOCOL",
|
|
9127
|
+
message: "schedule cannot be represented in the session-scoped view"
|
|
9128
|
+
};
|
|
9129
|
+
return {
|
|
9130
|
+
ok: true,
|
|
9131
|
+
value: legacy
|
|
9132
|
+
};
|
|
9133
|
+
}
|
|
9134
|
+
const response = await schedule.edit({
|
|
9135
|
+
rpcId: randomUUID(),
|
|
9136
|
+
payload: {
|
|
9137
|
+
id: payload.id,
|
|
9138
|
+
action: "update",
|
|
9139
|
+
patch
|
|
9140
|
+
}
|
|
9141
|
+
});
|
|
9142
|
+
if (!response.result) return {
|
|
9143
|
+
ok: false,
|
|
9144
|
+
code: "E_INTERNAL",
|
|
9145
|
+
message: "schedule edit returned no result"
|
|
9146
|
+
};
|
|
9147
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
9148
|
+
const value = response.result.value;
|
|
9149
|
+
if (value.kind === "conflict") return {
|
|
9150
|
+
ok: false,
|
|
9151
|
+
code: "E_BUSY",
|
|
9152
|
+
message: "schedule changed since it was read"
|
|
9153
|
+
};
|
|
9154
|
+
if (value.kind === "not-found") return {
|
|
9155
|
+
ok: false,
|
|
9156
|
+
code: "E_NOT_FOUND",
|
|
9157
|
+
message: "schedule not found"
|
|
9158
|
+
};
|
|
9159
|
+
if (value.kind !== "task") return {
|
|
9160
|
+
ok: false,
|
|
9161
|
+
code: "E_INTERNAL",
|
|
9162
|
+
message: "schedule edit returned an unexpected deletion"
|
|
9163
|
+
};
|
|
9164
|
+
const legacy = toLegacyScheduleView(value.task);
|
|
9165
|
+
if (legacy === void 0) return {
|
|
9166
|
+
ok: false,
|
|
9167
|
+
code: "E_PROTOCOL",
|
|
9168
|
+
message: "schedule cannot be represented in the session-scoped view"
|
|
9169
|
+
};
|
|
9170
|
+
return {
|
|
9171
|
+
ok: true,
|
|
9172
|
+
value: legacy
|
|
9173
|
+
};
|
|
9174
|
+
});
|
|
9175
|
+
if (result.ok) {
|
|
9176
|
+
this.record("s2c.schedule.changed", {});
|
|
9177
|
+
return {
|
|
9178
|
+
ok: true,
|
|
9179
|
+
value: result.value,
|
|
9180
|
+
replayed: result.replayed
|
|
9181
|
+
};
|
|
9182
|
+
}
|
|
9183
|
+
return {
|
|
9184
|
+
ok: false,
|
|
9185
|
+
code: result.code,
|
|
9186
|
+
message: result.message ?? result.code,
|
|
9187
|
+
replayed: result.replayed
|
|
9188
|
+
};
|
|
9189
|
+
}
|
|
9190
|
+
const { clientRequestId, ...request } = payload;
|
|
9191
|
+
const result = await this.scheduleMutations.dispatch(deviceId, clientRequestId, request, async () => {
|
|
9192
|
+
const response = await schedule.update({
|
|
9193
|
+
rpcId: randomUUID(),
|
|
9194
|
+
payload: request
|
|
9195
|
+
});
|
|
9196
|
+
if (!response.result) return {
|
|
9197
|
+
ok: false,
|
|
9198
|
+
code: "E_INTERNAL",
|
|
9199
|
+
message: "schedule update returned no result"
|
|
9200
|
+
};
|
|
9201
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
9202
|
+
const value = response.result.value;
|
|
9203
|
+
if (value.updated !== true || value.record === void 0) return {
|
|
9204
|
+
ok: false,
|
|
9205
|
+
code: wireCodeFor("schedule", value.code),
|
|
9206
|
+
message: value.code ?? "schedule update rejected"
|
|
9207
|
+
};
|
|
9208
|
+
return {
|
|
9209
|
+
ok: true,
|
|
9210
|
+
value: value.record
|
|
9211
|
+
};
|
|
9212
|
+
});
|
|
9213
|
+
if (result.ok) {
|
|
9214
|
+
this.record("s2c.schedule.changed", {});
|
|
9215
|
+
return {
|
|
8328
9216
|
ok: true,
|
|
8329
9217
|
value: result.value,
|
|
8330
9218
|
replayed: result.replayed
|
|
@@ -8344,6 +9232,63 @@ var HostBridge = class {
|
|
|
8344
9232
|
code: "E_UNSUPPORTED",
|
|
8345
9233
|
message: "schedules unavailable on this host version"
|
|
8346
9234
|
};
|
|
9235
|
+
if (schedule.mode === "host") {
|
|
9236
|
+
const result = await this.scheduleMutations.dispatch(deviceId, payload.clientRequestId, {
|
|
9237
|
+
sessionId: payload.sessionId,
|
|
9238
|
+
id: payload.id
|
|
9239
|
+
}, async () => {
|
|
9240
|
+
const response = await schedule.edit({
|
|
9241
|
+
rpcId: randomUUID(),
|
|
9242
|
+
payload: {
|
|
9243
|
+
id: payload.id,
|
|
9244
|
+
action: "delete"
|
|
9245
|
+
}
|
|
9246
|
+
});
|
|
9247
|
+
if (!response.result) return {
|
|
9248
|
+
ok: false,
|
|
9249
|
+
code: "E_INTERNAL",
|
|
9250
|
+
message: "schedule delete returned no result"
|
|
9251
|
+
};
|
|
9252
|
+
if (!response.result.ok) return wireErrorOf("schedule", response.result.error);
|
|
9253
|
+
const value = response.result.value;
|
|
9254
|
+
if (value.kind === "deleted") return {
|
|
9255
|
+
ok: true,
|
|
9256
|
+
value: {
|
|
9257
|
+
id: value.id,
|
|
9258
|
+
deleted: true
|
|
9259
|
+
}
|
|
9260
|
+
};
|
|
9261
|
+
if (value.kind === "not-found") return {
|
|
9262
|
+
ok: false,
|
|
9263
|
+
code: "E_NOT_FOUND",
|
|
9264
|
+
message: "schedule not found"
|
|
9265
|
+
};
|
|
9266
|
+
if (value.kind === "conflict") return {
|
|
9267
|
+
ok: false,
|
|
9268
|
+
code: "E_BUSY",
|
|
9269
|
+
message: "schedule changed since it was read"
|
|
9270
|
+
};
|
|
9271
|
+
return {
|
|
9272
|
+
ok: false,
|
|
9273
|
+
code: "E_INTERNAL",
|
|
9274
|
+
message: "schedule delete returned an updated task"
|
|
9275
|
+
};
|
|
9276
|
+
});
|
|
9277
|
+
if (result.ok) {
|
|
9278
|
+
this.record("s2c.schedule.changed", {});
|
|
9279
|
+
return {
|
|
9280
|
+
ok: true,
|
|
9281
|
+
value: result.value,
|
|
9282
|
+
replayed: result.replayed
|
|
9283
|
+
};
|
|
9284
|
+
}
|
|
9285
|
+
return {
|
|
9286
|
+
ok: false,
|
|
9287
|
+
code: result.code,
|
|
9288
|
+
message: result.message ?? result.code,
|
|
9289
|
+
replayed: result.replayed
|
|
9290
|
+
};
|
|
9291
|
+
}
|
|
8347
9292
|
const result = await this.scheduleMutations.dispatch(deviceId, payload.clientRequestId, {
|
|
8348
9293
|
sessionId: payload.sessionId,
|
|
8349
9294
|
id: payload.id
|
|
@@ -8728,8 +9673,236 @@ function projectSessionModels(value) {
|
|
|
8728
9673
|
};
|
|
8729
9674
|
}
|
|
8730
9675
|
/** Project a history page (raw events) into MessageProjection rows. */
|
|
8731
|
-
|
|
8732
|
-
|
|
9676
|
+
/** 宿主级列表全量拉取时的页大小(Host 允许的最大值)。 */
|
|
9677
|
+
const HOST_SCHEDULE_PAGE_SIZE = 500;
|
|
9678
|
+
/** 参与旧帧冲突比较的字段:`state`/`deliveryMode` 是派生字段,不参与。 */
|
|
9679
|
+
const LEGACY_PROJECTION_FIELDS = [
|
|
9680
|
+
"id",
|
|
9681
|
+
"kind",
|
|
9682
|
+
"title",
|
|
9683
|
+
"prompt",
|
|
9684
|
+
"scheduledAt",
|
|
9685
|
+
"afterSeconds",
|
|
9686
|
+
"everySeconds",
|
|
9687
|
+
"time",
|
|
9688
|
+
"timeZone",
|
|
9689
|
+
"weekdays",
|
|
9690
|
+
"expression"
|
|
9691
|
+
];
|
|
9692
|
+
function isPlainObject$1(value) {
|
|
9693
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9694
|
+
}
|
|
9695
|
+
function legacyNumber(...values) {
|
|
9696
|
+
for (const value of values) if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
9697
|
+
}
|
|
9698
|
+
function legacyString(...values) {
|
|
9699
|
+
for (const value of values) if (typeof value === "string" && value.length > 0) return value;
|
|
9700
|
+
}
|
|
9701
|
+
function normalizeAtInput(value) {
|
|
9702
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
9703
|
+
if (!isPlainObject$1(value)) return void 0;
|
|
9704
|
+
const date = legacyString(value.date);
|
|
9705
|
+
const time = legacyString(value.time);
|
|
9706
|
+
const zone = legacyString(value.time_zone, value.timeZone);
|
|
9707
|
+
return date !== void 0 && time !== void 0 && zone !== void 0 ? {
|
|
9708
|
+
date,
|
|
9709
|
+
time,
|
|
9710
|
+
time_zone: zone
|
|
9711
|
+
} : void 0;
|
|
9712
|
+
}
|
|
9713
|
+
function normalizeLocalTime(value) {
|
|
9714
|
+
if (!isPlainObject$1(value)) return void 0;
|
|
9715
|
+
const time = legacyString(value.time);
|
|
9716
|
+
const zone = legacyString(value.time_zone, value.timeZone);
|
|
9717
|
+
return time !== void 0 && zone !== void 0 ? {
|
|
9718
|
+
time,
|
|
9719
|
+
time_zone: zone
|
|
9720
|
+
} : void 0;
|
|
9721
|
+
}
|
|
9722
|
+
function normalizeWeekdays(value) {
|
|
9723
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
9724
|
+
const days = value.filter((day) => typeof day === "number" && Number.isSafeInteger(day) && day >= 1 && day <= 7);
|
|
9725
|
+
return days.length === value.length ? days : void 0;
|
|
9726
|
+
}
|
|
9727
|
+
/** 旧创建帧的选择器 -> 宿主级 timing。旧 wire 校验保证恰好一个选择器。 */
|
|
9728
|
+
function hostTimingFromLegacySelector(selectors) {
|
|
9729
|
+
const after = legacyNumber(selectors.after_seconds);
|
|
9730
|
+
if (after !== void 0) return {
|
|
9731
|
+
kind: "after",
|
|
9732
|
+
after_seconds: after
|
|
9733
|
+
};
|
|
9734
|
+
if (selectors.at !== void 0) {
|
|
9735
|
+
const at = normalizeAtInput(selectors.at);
|
|
9736
|
+
return at === void 0 ? void 0 : {
|
|
9737
|
+
kind: "at",
|
|
9738
|
+
at
|
|
9739
|
+
};
|
|
9740
|
+
}
|
|
9741
|
+
const every = legacyNumber(selectors.every_seconds);
|
|
9742
|
+
if (every !== void 0) return {
|
|
9743
|
+
kind: "every",
|
|
9744
|
+
every_seconds: every
|
|
9745
|
+
};
|
|
9746
|
+
if (selectors.daily !== void 0) {
|
|
9747
|
+
const daily = normalizeLocalTime(selectors.daily);
|
|
9748
|
+
return daily === void 0 ? void 0 : {
|
|
9749
|
+
kind: "daily",
|
|
9750
|
+
daily
|
|
9751
|
+
};
|
|
9752
|
+
}
|
|
9753
|
+
if (selectors.weekly !== void 0) {
|
|
9754
|
+
if (!isPlainObject$1(selectors.weekly)) return void 0;
|
|
9755
|
+
const time = normalizeLocalTime(selectors.weekly);
|
|
9756
|
+
const weekdays = normalizeWeekdays(selectors.weekly.weekdays);
|
|
9757
|
+
return time === void 0 || weekdays === void 0 ? void 0 : {
|
|
9758
|
+
kind: "weekly",
|
|
9759
|
+
weekly: {
|
|
9760
|
+
...time,
|
|
9761
|
+
weekdays
|
|
9762
|
+
}
|
|
9763
|
+
};
|
|
9764
|
+
}
|
|
9765
|
+
if (selectors.cron !== void 0) {
|
|
9766
|
+
if (!isPlainObject$1(selectors.cron)) return void 0;
|
|
9767
|
+
const expression = legacyString(selectors.cron.expression);
|
|
9768
|
+
const zone = legacyString(selectors.cron.time_zone, selectors.cron.timeZone);
|
|
9769
|
+
return expression === void 0 || zone === void 0 ? void 0 : {
|
|
9770
|
+
kind: "cron",
|
|
9771
|
+
cron: {
|
|
9772
|
+
expression,
|
|
9773
|
+
time_zone: zone
|
|
9774
|
+
}
|
|
9775
|
+
};
|
|
9776
|
+
}
|
|
9777
|
+
}
|
|
9778
|
+
/**
|
|
9779
|
+
* 旧更新帧的 `change` -> 宿主级 timing。
|
|
9780
|
+
*
|
|
9781
|
+
* 两种历史写法都认:选择器式(`{kind:'daily', daily:{...}}`,与创建帧同形)与
|
|
9782
|
+
* 扁平式(`{kind:'daily', time, timeZone}` / `{kind:'cron', expression}`)。
|
|
9783
|
+
*/
|
|
9784
|
+
function hostTimingFromLegacyChange(change) {
|
|
9785
|
+
if (!isPlainObject$1(change)) return void 0;
|
|
9786
|
+
const selector = hostTimingFromLegacySelector(change);
|
|
9787
|
+
if (selector !== void 0) return selector;
|
|
9788
|
+
switch (change.kind) {
|
|
9789
|
+
case "daily": {
|
|
9790
|
+
const time = legacyString(change.time);
|
|
9791
|
+
const zone = legacyString(change.time_zone, change.timeZone);
|
|
9792
|
+
return time === void 0 || zone === void 0 ? void 0 : {
|
|
9793
|
+
kind: "daily",
|
|
9794
|
+
daily: {
|
|
9795
|
+
time,
|
|
9796
|
+
time_zone: zone
|
|
9797
|
+
}
|
|
9798
|
+
};
|
|
9799
|
+
}
|
|
9800
|
+
case "weekly": {
|
|
9801
|
+
const time = legacyString(change.time);
|
|
9802
|
+
const zone = legacyString(change.time_zone, change.timeZone);
|
|
9803
|
+
const weekdays = normalizeWeekdays(change.weekdays);
|
|
9804
|
+
return time === void 0 || zone === void 0 || weekdays === void 0 ? void 0 : {
|
|
9805
|
+
kind: "weekly",
|
|
9806
|
+
weekly: {
|
|
9807
|
+
time,
|
|
9808
|
+
time_zone: zone,
|
|
9809
|
+
weekdays
|
|
9810
|
+
}
|
|
9811
|
+
};
|
|
9812
|
+
}
|
|
9813
|
+
case "cron": {
|
|
9814
|
+
const expression = legacyString(change.expression);
|
|
9815
|
+
const zone = legacyString(change.time_zone, change.timeZone);
|
|
9816
|
+
return expression === void 0 || zone === void 0 ? void 0 : {
|
|
9817
|
+
kind: "cron",
|
|
9818
|
+
cron: {
|
|
9819
|
+
expression,
|
|
9820
|
+
time_zone: zone
|
|
9821
|
+
}
|
|
9822
|
+
};
|
|
9823
|
+
}
|
|
9824
|
+
case "rrule": {
|
|
9825
|
+
const rrule = legacyString(change.rrule);
|
|
9826
|
+
return rrule === void 0 ? void 0 : {
|
|
9827
|
+
kind: "rrule",
|
|
9828
|
+
rrule
|
|
9829
|
+
};
|
|
9830
|
+
}
|
|
9831
|
+
default: return;
|
|
9832
|
+
}
|
|
9833
|
+
}
|
|
9834
|
+
/** 旧更新帧的 title/prompt/change -> 宿主级 patch。 */
|
|
9835
|
+
function hostPatchFromLegacyUpdate(payload) {
|
|
9836
|
+
const patch = {};
|
|
9837
|
+
if (typeof payload.title === "string") patch.title = payload.title;
|
|
9838
|
+
if (typeof payload.prompt === "string") patch.prompt = payload.prompt;
|
|
9839
|
+
const timing = hostTimingFromLegacyChange(payload.change);
|
|
9840
|
+
if (timing !== void 0) patch.timing = timing;
|
|
9841
|
+
return patch;
|
|
9842
|
+
}
|
|
9843
|
+
/**
|
|
9844
|
+
* 宿主级任务视图 -> 旧会话级视图。
|
|
9845
|
+
*
|
|
9846
|
+
* `state` 只由 `scheduledAt` 是否已过推导(PROTOCOL.md 映射表);`rrule` 在旧枚举
|
|
9847
|
+
* 里没有对应 kind,返回 undefined 让调用方跳过——旧客户端按枚举解码列表,塞进
|
|
9848
|
+
* 未知 kind 会让整页解码失败,静默隐藏比让旧 App 打不开列表更安全。
|
|
9849
|
+
*/
|
|
9850
|
+
function toLegacyScheduleView(task) {
|
|
9851
|
+
const instant = Date.parse(task.scheduledAt);
|
|
9852
|
+
const base = {
|
|
9853
|
+
id: task.id,
|
|
9854
|
+
title: task.title,
|
|
9855
|
+
prompt: task.prompt,
|
|
9856
|
+
scheduledAt: task.scheduledAt,
|
|
9857
|
+
state: Number.isFinite(instant) && instant < Date.now() ? "overdue" : "scheduled",
|
|
9858
|
+
deliveryMode: "host"
|
|
9859
|
+
};
|
|
9860
|
+
switch (task.timing.kind) {
|
|
9861
|
+
case "after": return {
|
|
9862
|
+
...base,
|
|
9863
|
+
kind: "after",
|
|
9864
|
+
afterSeconds: task.timing.after_seconds
|
|
9865
|
+
};
|
|
9866
|
+
case "at": return {
|
|
9867
|
+
...base,
|
|
9868
|
+
kind: "at"
|
|
9869
|
+
};
|
|
9870
|
+
case "every": return {
|
|
9871
|
+
...base,
|
|
9872
|
+
kind: "every",
|
|
9873
|
+
everySeconds: task.timing.every_seconds
|
|
9874
|
+
};
|
|
9875
|
+
case "daily": return {
|
|
9876
|
+
...base,
|
|
9877
|
+
kind: "daily",
|
|
9878
|
+
time: task.timing.daily.time,
|
|
9879
|
+
timeZone: task.timing.daily.time_zone
|
|
9880
|
+
};
|
|
9881
|
+
case "weekly": return {
|
|
9882
|
+
...base,
|
|
9883
|
+
kind: "weekly",
|
|
9884
|
+
time: task.timing.weekly.time,
|
|
9885
|
+
timeZone: task.timing.weekly.time_zone,
|
|
9886
|
+
weekdays: [...task.timing.weekly.weekdays]
|
|
9887
|
+
};
|
|
9888
|
+
case "cron": return {
|
|
9889
|
+
...base,
|
|
9890
|
+
kind: "cron",
|
|
9891
|
+
expression: task.timing.cron.expression,
|
|
9892
|
+
timeZone: task.timing.cron.time_zone
|
|
9893
|
+
};
|
|
9894
|
+
default: return;
|
|
9895
|
+
}
|
|
9896
|
+
}
|
|
9897
|
+
/** 旧帧的冲突保护:当前任务的旧投影与客户端回传的 `expected` 逐字段相等。 */
|
|
9898
|
+
function legacyScheduleMatchesExpected(task, expected) {
|
|
9899
|
+
const view = toLegacyScheduleView(task);
|
|
9900
|
+
if (view === void 0) return false;
|
|
9901
|
+
if (!isPlainObject$1(expected)) return false;
|
|
9902
|
+
const actual = view;
|
|
9903
|
+
return LEGACY_PROJECTION_FIELDS.every((key) => JSON.stringify(actual[key]) === JSON.stringify(expected[key]));
|
|
9904
|
+
}
|
|
9905
|
+
// src/dsh-remote-interactions.ts
|
|
8733
9906
|
/**
|
|
8734
9907
|
* Resident DSH Remote Events client for phone interactions.
|
|
8735
9908
|
*
|
|
@@ -8917,8 +10090,7 @@ async function startDshRemoteInteractions(hostCtx, handlers) {
|
|
|
8917
10090
|
throw error;
|
|
8918
10091
|
}
|
|
8919
10092
|
}
|
|
8920
|
-
|
|
8921
|
-
//#region src/command-api.ts
|
|
10093
|
+
// src/command-api.ts
|
|
8922
10094
|
/**
|
|
8923
10095
|
* 给本域自造的失败带上 Host 风格的码。
|
|
8924
10096
|
*
|
|
@@ -9065,8 +10237,7 @@ var DshCommandApi = class {
|
|
|
9065
10237
|
return response;
|
|
9066
10238
|
}
|
|
9067
10239
|
};
|
|
9068
|
-
|
|
9069
|
-
//#region src/dsh-api-proxy.ts
|
|
10240
|
+
// src/dsh-api-proxy.ts
|
|
9070
10241
|
/**
|
|
9071
10242
|
* Adapter for the DSH rc.2 controller API.
|
|
9072
10243
|
*
|
|
@@ -9075,6 +10246,14 @@ var DshCommandApi = class {
|
|
|
9075
10246
|
* Session/Workspace controllers and Gateway-backed Remote Events, keeping
|
|
9076
10247
|
* the phone protocol isolated from the Host API.
|
|
9077
10248
|
*/
|
|
10249
|
+
/**
|
|
10250
|
+
* 形状探测:宿主级模型只有 `edit`(没有 `update`)。不按版本号字符串判断——
|
|
10251
|
+
* 版本号无法说明 Schedule bundle 是否挂载、也无法覆盖回退组合。
|
|
10252
|
+
*/
|
|
10253
|
+
function isHostScheduleController(controller) {
|
|
10254
|
+
const value = controller;
|
|
10255
|
+
return typeof value.edit === "function" && typeof value.update !== "function";
|
|
10256
|
+
}
|
|
9078
10257
|
/** Direct-controller adapter with the shape HostBridge consumes. */
|
|
9079
10258
|
var DshApiProxy = class {
|
|
9080
10259
|
ctx;
|
|
@@ -9145,14 +10324,16 @@ var DshApiProxy = class {
|
|
|
9145
10324
|
const controller = this.ctx.get("schedule");
|
|
9146
10325
|
if (controller === void 0) return;
|
|
9147
10326
|
this.scheduleController = controller;
|
|
9148
|
-
this.scheduleApi = this.
|
|
10327
|
+
this.scheduleApi = isHostScheduleController(controller) ? this.createHostScheduleApi(controller) : this.createLegacyScheduleApi(controller);
|
|
9149
10328
|
}
|
|
9150
10329
|
get schedule() {
|
|
9151
10330
|
this.resolveSchedule();
|
|
9152
10331
|
return this.scheduleApi;
|
|
9153
10332
|
}
|
|
9154
|
-
|
|
10333
|
+
/** 会话级适配器:行为与宿主级扩展加入前逐字节一致,只多一个判别位。 */
|
|
10334
|
+
createLegacyScheduleApi(controller) {
|
|
9155
10335
|
return {
|
|
10336
|
+
mode: "legacy",
|
|
9156
10337
|
list: async (request) => this.call(async () => ({
|
|
9157
10338
|
sessionId: request.payload.sessionId,
|
|
9158
10339
|
tasks: (await controller.list({ sessionId: request.payload.sessionId })).map(toScheduleTask)
|
|
@@ -9201,6 +10382,83 @@ var DshApiProxy = class {
|
|
|
9201
10382
|
};
|
|
9202
10383
|
}
|
|
9203
10384
|
/**
|
|
10385
|
+
* 宿主级适配器:把 DSH `ScheduleService` 的分页列表/结果对象归一化成 Bridge
|
|
10386
|
+
* 消费的稳定视图。
|
|
10387
|
+
*
|
|
10388
|
+
* 失败分两类:`view/edit/run/history` 的「找不到」是**结果对象**里的 code
|
|
10389
|
+
* (非变更 miss,不抛错),这里翻译成 outcome 或带 code 的 Error;
|
|
10390
|
+
* `create/edit/run` 的输入非法与目标已归档是 RemoteError 抛出,由
|
|
10391
|
+
* `this.call` 收成 `{code}`,再由 Bridge 按错误词表映射。
|
|
10392
|
+
*/
|
|
10393
|
+
createHostScheduleApi(controller) {
|
|
10394
|
+
return {
|
|
10395
|
+
mode: "host",
|
|
10396
|
+
list: async (request) => this.call(async () => {
|
|
10397
|
+
const payload = request.payload ?? {};
|
|
10398
|
+
const value = await controller.list({
|
|
10399
|
+
...payload.active !== void 0 ? { active: payload.active } : {},
|
|
10400
|
+
...payload.limit !== void 0 ? { limit: payload.limit } : {},
|
|
10401
|
+
...payload.offset !== void 0 ? { offset: payload.offset } : {}
|
|
10402
|
+
});
|
|
10403
|
+
const items = Array.isArray(value.items) ? value.items : [];
|
|
10404
|
+
return {
|
|
10405
|
+
tasks: items.map(toHostScheduleTask),
|
|
10406
|
+
shown: finiteCount(value.shown, items.length),
|
|
10407
|
+
total: finiteCount(value.total, items.length),
|
|
10408
|
+
limit: finiteCount(value.limit, payload.limit ?? 100),
|
|
10409
|
+
offset: finiteCount(value.offset, payload.offset ?? 0)
|
|
10410
|
+
};
|
|
10411
|
+
}),
|
|
10412
|
+
view: async (request) => this.call(async () => {
|
|
10413
|
+
return toHostScheduleViewOutcome(await controller.view({ id: request.payload.id }), request.payload.id);
|
|
10414
|
+
}),
|
|
10415
|
+
create: async (request) => this.call(async () => {
|
|
10416
|
+
const payload = request.payload;
|
|
10417
|
+
return toHostScheduleTask(await controller.create({
|
|
10418
|
+
title: payload.title,
|
|
10419
|
+
prompt: payload.prompt,
|
|
10420
|
+
timing: payload.timing,
|
|
10421
|
+
...payload.target !== void 0 ? { target: normalizeTargetRequest(payload.target) } : {}
|
|
10422
|
+
}));
|
|
10423
|
+
}),
|
|
10424
|
+
edit: async (request) => this.call(async () => {
|
|
10425
|
+
const payload = request.payload;
|
|
10426
|
+
const expected = payload.expectedUpdatedAt !== void 0 ? { expectedUpdatedAt: payload.expectedUpdatedAt } : {};
|
|
10427
|
+
const edit = payload.action === "update" ? {
|
|
10428
|
+
id: payload.id,
|
|
10429
|
+
action: "update",
|
|
10430
|
+
patch: normalizeHostPatch(payload.patch),
|
|
10431
|
+
...expected
|
|
10432
|
+
} : {
|
|
10433
|
+
id: payload.id,
|
|
10434
|
+
action: "delete",
|
|
10435
|
+
...expected
|
|
10436
|
+
};
|
|
10437
|
+
return toHostScheduleEditOutcome(await controller.edit(edit), payload.id);
|
|
10438
|
+
}),
|
|
10439
|
+
run: async (request) => this.call(async () => {
|
|
10440
|
+
return toHostScheduleRunOutcome(await controller.run({ id: request.payload.id }), request.payload.id);
|
|
10441
|
+
}),
|
|
10442
|
+
history: async (request) => this.call(async () => {
|
|
10443
|
+
const value = await controller.history({
|
|
10444
|
+
id: request.payload.id,
|
|
10445
|
+
limit: request.payload.limit,
|
|
10446
|
+
...request.payload.before !== void 0 ? { before: request.payload.before } : {}
|
|
10447
|
+
});
|
|
10448
|
+
if (typeof value.code === "string") throw Object.assign(new Error(String(value.code)), { code: value.code });
|
|
10449
|
+
return toScheduleHistory(value);
|
|
10450
|
+
}),
|
|
10451
|
+
limits: async () => this.call(async () => {
|
|
10452
|
+
const value = controller.deliveryLimits();
|
|
10453
|
+
return { newSessionIntervalSeconds: Math.max(0, Math.trunc(Number(value?.newSessionIntervalSeconds) || 0)) };
|
|
10454
|
+
}),
|
|
10455
|
+
deleteEnded: async () => this.call(async () => {
|
|
10456
|
+
const value = await controller.deleteInactive();
|
|
10457
|
+
return { deleted: Math.max(0, Math.trunc(Number(value?.deleted) || 0)) };
|
|
10458
|
+
})
|
|
10459
|
+
};
|
|
10460
|
+
}
|
|
10461
|
+
/**
|
|
9204
10462
|
* Decide whether this Gateway Client can surface a phone card. The decision
|
|
9205
10463
|
* fails open to false so a broken device registry delegates this delivery
|
|
9206
10464
|
* while the official Web Client remains able to answer.
|
|
@@ -9613,6 +10871,206 @@ function toScheduleHistory(value) {
|
|
|
9613
10871
|
...typeof row.nextBefore === "string" ? { nextBefore: row.nextBefore } : {}
|
|
9614
10872
|
};
|
|
9615
10873
|
}
|
|
10874
|
+
/**
|
|
10875
|
+
* 请求侧 target:`new_once`/`new` 的会话由 Host 预留或新建,客户端若把查看结果
|
|
10876
|
+
* 里的 `sessionId` 原样回传,不向上游透传(Host 的创建/编辑请求没有这个字段)。
|
|
10877
|
+
*/
|
|
10878
|
+
function normalizeTargetRequest(value) {
|
|
10879
|
+
if (value.kind === "session") return {
|
|
10880
|
+
kind: "session",
|
|
10881
|
+
sessionId: String(value.sessionId ?? "")
|
|
10882
|
+
};
|
|
10883
|
+
const create = toHostScheduleCreateSpec(value.create);
|
|
10884
|
+
return {
|
|
10885
|
+
kind: value.kind,
|
|
10886
|
+
...create !== void 0 ? { create } : {}
|
|
10887
|
+
};
|
|
10888
|
+
}
|
|
10889
|
+
/** 编辑补丁归一化:字段按契约逐个透传,target 走请求侧投影。 */
|
|
10890
|
+
function normalizeHostPatch(value) {
|
|
10891
|
+
return {
|
|
10892
|
+
...value.title !== void 0 ? { title: value.title } : {},
|
|
10893
|
+
...value.prompt !== void 0 ? { prompt: value.prompt } : {},
|
|
10894
|
+
...value.timing !== void 0 ? { timing: value.timing } : {},
|
|
10895
|
+
...value.target !== void 0 ? { target: normalizeTargetRequest(value.target) } : {},
|
|
10896
|
+
...value.active !== void 0 ? { active: value.active } : {}
|
|
10897
|
+
};
|
|
10898
|
+
}
|
|
10899
|
+
function isRecord(value) {
|
|
10900
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10901
|
+
}
|
|
10902
|
+
function toRecord(value) {
|
|
10903
|
+
return isRecord(value) ? value : {};
|
|
10904
|
+
}
|
|
10905
|
+
/** 非负安全整数;形状不符时退回 fallback(分页字段只用于展示)。 */
|
|
10906
|
+
function finiteCount(value, fallback) {
|
|
10907
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : fallback;
|
|
10908
|
+
}
|
|
10909
|
+
function positiveSeconds(value) {
|
|
10910
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
10911
|
+
}
|
|
10912
|
+
function hostResultCode(value) {
|
|
10913
|
+
return typeof value.code === "string" ? value.code : void 0;
|
|
10914
|
+
}
|
|
10915
|
+
/** 结果对象里的「非变更 miss」不是抛错,这里翻译成 outcome 或带 code 的 Error。 */
|
|
10916
|
+
function throwHostResultCode(code) {
|
|
10917
|
+
throw Object.assign(new Error(code), { code });
|
|
10918
|
+
}
|
|
10919
|
+
function toHostScheduleCreateSpec(value) {
|
|
10920
|
+
const row = toRecord(value);
|
|
10921
|
+
const spec = {
|
|
10922
|
+
...typeof row.cwd === "string" ? { cwd: row.cwd } : {},
|
|
10923
|
+
...typeof row.agentPreset === "string" ? { agentPreset: row.agentPreset } : {}
|
|
10924
|
+
};
|
|
10925
|
+
return Object.keys(spec).length > 0 ? spec : void 0;
|
|
10926
|
+
}
|
|
10927
|
+
/**
|
|
10928
|
+
* 交付目标投影。`session` 与 `new_once` 带 sessionId(后者是创建时 Host 预留
|
|
10929
|
+
* 的会话);`new` 每次投递新建会话。`create` 省略时按 Host 默认。
|
|
10930
|
+
*/
|
|
10931
|
+
function toHostScheduleTarget(value) {
|
|
10932
|
+
const target = toRecord(value);
|
|
10933
|
+
const create = toHostScheduleCreateSpec(target.create);
|
|
10934
|
+
if (target.kind === "session") return {
|
|
10935
|
+
kind: "session",
|
|
10936
|
+
sessionId: String(target.sessionId ?? "")
|
|
10937
|
+
};
|
|
10938
|
+
if (target.kind === "new_once") return {
|
|
10939
|
+
kind: "new_once",
|
|
10940
|
+
sessionId: String(target.sessionId ?? ""),
|
|
10941
|
+
...create !== void 0 ? { create } : {}
|
|
10942
|
+
};
|
|
10943
|
+
return {
|
|
10944
|
+
kind: "new",
|
|
10945
|
+
...create !== void 0 ? { create } : {}
|
|
10946
|
+
};
|
|
10947
|
+
}
|
|
10948
|
+
function toHostScheduleReceipt(value) {
|
|
10949
|
+
const row = toRecord(value);
|
|
10950
|
+
return {
|
|
10951
|
+
scheduledAt: String(row.scheduledAt ?? ""),
|
|
10952
|
+
deliveredAt: String(row.deliveredAt ?? ""),
|
|
10953
|
+
messageId: String(row.messageId ?? ""),
|
|
10954
|
+
targetSessionId: String(row.targetSessionId ?? "")
|
|
10955
|
+
};
|
|
10956
|
+
}
|
|
10957
|
+
/**
|
|
10958
|
+
* timing 反向投影:DSH 存储记录用 camelCase(`afterSeconds`/`timeZone`),wire
|
|
10959
|
+
* 用 PROTOCOL.md 的 snake_case 嵌套结构。
|
|
10960
|
+
*
|
|
10961
|
+
* `at` 存储后只剩 `scheduledAt`(原始绝对时刻不是独立字段),因此用排定时刻
|
|
10962
|
+
* 回填——对 active 的一次性任务它就是目标时刻。
|
|
10963
|
+
*/
|
|
10964
|
+
function toHostScheduleTiming(row) {
|
|
10965
|
+
switch (row.kind) {
|
|
10966
|
+
case "after": return {
|
|
10967
|
+
kind: "after",
|
|
10968
|
+
after_seconds: positiveSeconds(row.afterSeconds)
|
|
10969
|
+
};
|
|
10970
|
+
case "every": return {
|
|
10971
|
+
kind: "every",
|
|
10972
|
+
every_seconds: positiveSeconds(row.everySeconds)
|
|
10973
|
+
};
|
|
10974
|
+
case "daily": return {
|
|
10975
|
+
kind: "daily",
|
|
10976
|
+
daily: localTimeInput(row)
|
|
10977
|
+
};
|
|
10978
|
+
case "weekly": return {
|
|
10979
|
+
kind: "weekly",
|
|
10980
|
+
weekly: {
|
|
10981
|
+
...localTimeInput(row),
|
|
10982
|
+
weekdays: Array.isArray(row.weekdays) ? row.weekdays.filter((day) => typeof day === "number") : []
|
|
10983
|
+
}
|
|
10984
|
+
};
|
|
10985
|
+
case "cron": return {
|
|
10986
|
+
kind: "cron",
|
|
10987
|
+
cron: {
|
|
10988
|
+
expression: String(row.expression ?? ""),
|
|
10989
|
+
time_zone: String(row.timeZone ?? "")
|
|
10990
|
+
}
|
|
10991
|
+
};
|
|
10992
|
+
case "rrule": return {
|
|
10993
|
+
kind: "rrule",
|
|
10994
|
+
rrule: String(row.rrule ?? "")
|
|
10995
|
+
};
|
|
10996
|
+
default: return {
|
|
10997
|
+
kind: "at",
|
|
10998
|
+
at: String(row.scheduledAt ?? "")
|
|
10999
|
+
};
|
|
11000
|
+
}
|
|
11001
|
+
}
|
|
11002
|
+
function localTimeInput(row) {
|
|
11003
|
+
return {
|
|
11004
|
+
time: String(row.time ?? ""),
|
|
11005
|
+
time_zone: String(row.timeZone ?? "")
|
|
11006
|
+
};
|
|
11007
|
+
}
|
|
11008
|
+
/** Host `ScheduleCatalogEntry` -> wire `schedule.host.task`(丢弃 involvedSessionIds)。 */
|
|
11009
|
+
function toHostScheduleTask(value) {
|
|
11010
|
+
const row = toRecord(value);
|
|
11011
|
+
return {
|
|
11012
|
+
id: String(row.id ?? ""),
|
|
11013
|
+
title: String(row.title ?? ""),
|
|
11014
|
+
prompt: String(row.prompt ?? ""),
|
|
11015
|
+
timing: toHostScheduleTiming(row),
|
|
11016
|
+
target: toHostScheduleTarget(row.target),
|
|
11017
|
+
status: row.status === "paused" || row.status === "ended" ? row.status : "active",
|
|
11018
|
+
scheduledAt: String(row.scheduledAt ?? ""),
|
|
11019
|
+
...typeof row.createdAt === "string" ? { createdAt: row.createdAt } : {},
|
|
11020
|
+
...typeof row.updatedAt === "string" ? { updatedAt: row.updatedAt } : {},
|
|
11021
|
+
...typeof row.executionActive === "boolean" ? { executionActive: row.executionActive } : {},
|
|
11022
|
+
...typeof row.deliveryFloorEndsAt === "string" ? { deliveryFloorEndsAt: row.deliveryFloorEndsAt } : {},
|
|
11023
|
+
...isRecord(row.lastDelivery) ? { lastDelivery: toHostScheduleReceipt(row.lastDelivery) } : {}
|
|
11024
|
+
};
|
|
11025
|
+
}
|
|
11026
|
+
function toHostScheduleViewOutcome(value, id) {
|
|
11027
|
+
const code = hostResultCode(value);
|
|
11028
|
+
if (code === "schedule_not_found") return {
|
|
11029
|
+
kind: "not-found",
|
|
11030
|
+
id
|
|
11031
|
+
};
|
|
11032
|
+
if (code !== void 0) throwHostResultCode(code);
|
|
11033
|
+
return {
|
|
11034
|
+
kind: "task",
|
|
11035
|
+
task: toHostScheduleTask(value)
|
|
11036
|
+
};
|
|
11037
|
+
}
|
|
11038
|
+
function toHostScheduleEditOutcome(value, id) {
|
|
11039
|
+
const code = hostResultCode(value);
|
|
11040
|
+
if (code === "schedule_conflict") return {
|
|
11041
|
+
kind: "conflict",
|
|
11042
|
+
id
|
|
11043
|
+
};
|
|
11044
|
+
if (code === "schedule_not_found") return {
|
|
11045
|
+
kind: "not-found",
|
|
11046
|
+
id
|
|
11047
|
+
};
|
|
11048
|
+
if (code !== void 0) throwHostResultCode(code);
|
|
11049
|
+
if (value.deleted === true) return {
|
|
11050
|
+
kind: "deleted",
|
|
11051
|
+
id: String(value.id ?? id)
|
|
11052
|
+
};
|
|
11053
|
+
return {
|
|
11054
|
+
kind: "task",
|
|
11055
|
+
task: toHostScheduleTask(value)
|
|
11056
|
+
};
|
|
11057
|
+
}
|
|
11058
|
+
function toHostScheduleRunOutcome(value, id) {
|
|
11059
|
+
const code = hostResultCode(value);
|
|
11060
|
+
if (code === "schedule_execution_pending") return {
|
|
11061
|
+
kind: "pending",
|
|
11062
|
+
id
|
|
11063
|
+
};
|
|
11064
|
+
if (code === "schedule_not_found") return {
|
|
11065
|
+
kind: "not-found",
|
|
11066
|
+
id
|
|
11067
|
+
};
|
|
11068
|
+
if (code !== void 0) throwHostResultCode(code);
|
|
11069
|
+
return {
|
|
11070
|
+
kind: "task",
|
|
11071
|
+
task: toHostScheduleTask(value)
|
|
11072
|
+
};
|
|
11073
|
+
}
|
|
9616
11074
|
async function readWorkspaceBaseline(controller) {
|
|
9617
11075
|
const abort = new AbortController();
|
|
9618
11076
|
const iterator = controller.follow(abort.signal)[Symbol.asyncIterator]();
|
|
@@ -9642,8 +11100,7 @@ async function projectModels(catalog, sessionId, list) {
|
|
|
9642
11100
|
failures: Array.isArray(value.failures) ? value.failures : []
|
|
9643
11101
|
};
|
|
9644
11102
|
}
|
|
9645
|
-
|
|
9646
|
-
//#region src/report-service.ts
|
|
11103
|
+
// src/report-service.ts
|
|
9647
11104
|
/**
|
|
9648
11105
|
* The Typert receiver the Gateway resolves for the DeepPilot Bridge report.
|
|
9649
11106
|
* Snapshot data stays non-secret; the token crosses the boundary only through
|
|
@@ -9689,8 +11146,7 @@ var DeepPilotReportService = class extends TypertRemoteService {
|
|
|
9689
11146
|
return this.pushTester();
|
|
9690
11147
|
}
|
|
9691
11148
|
};
|
|
9692
|
-
|
|
9693
|
-
//#region src/report-wire.ts
|
|
11149
|
+
// src/report-wire.ts
|
|
9694
11150
|
/** The npm package identity both contribution registrations claim. */
|
|
9695
11151
|
const REPORT_REMOTE_PACKAGE = "dsh-deeppilot";
|
|
9696
11152
|
/** Canonical `<namespace>/<method>` endpoint of the report Remote. */
|
|
@@ -10075,8 +11531,7 @@ const REPORT_HOST_CONTRIBUTION = {
|
|
|
10075
11531
|
}
|
|
10076
11532
|
]
|
|
10077
11533
|
};
|
|
10078
|
-
|
|
10079
|
-
//#region src/report-remote.ts
|
|
11534
|
+
// src/report-remote.ts
|
|
10080
11535
|
/**
|
|
10081
11536
|
* Provide the report service and register its Remote descriptor. Rides an
|
|
10082
11537
|
* optional `typert` inject: profiles without the web stack never activate it.
|
|
@@ -10088,8 +11543,7 @@ function applyReportRemote(ctx, snapshot, pairingStarter, deviceRevoker, deviceR
|
|
|
10088
11543
|
remoteCtx.effect(() => () => void unregister(), "dsh-deeppilot: report remote");
|
|
10089
11544
|
});
|
|
10090
11545
|
}
|
|
10091
|
-
|
|
10092
|
-
//#region src/local-address.ts
|
|
11546
|
+
// src/local-address.ts
|
|
10093
11547
|
function octetsOf(address) {
|
|
10094
11548
|
const octets = address.split(".").map(Number);
|
|
10095
11549
|
if (octets.length !== 4 || octets.some((value) => !Number.isInteger(value) || value < 0 || value > 255)) return null;
|
|
@@ -10140,8 +11594,7 @@ function localLANIPv4Addresses() {
|
|
|
10140
11594
|
candidates.sort((left, right) => priority(left.name) - priority(right.name) || left.name.localeCompare(right.name));
|
|
10141
11595
|
return [...new Set(candidates.map(({ address }) => address))];
|
|
10142
11596
|
}
|
|
10143
|
-
|
|
10144
|
-
//#region src/update-check.ts
|
|
11597
|
+
// src/update-check.ts
|
|
10145
11598
|
/**
|
|
10146
11599
|
* Lightweight self-update check for dsh-deeppilot.
|
|
10147
11600
|
*
|
|
@@ -10337,8 +11790,7 @@ var UpdateChecker = class {
|
|
|
10337
11790
|
/** No-op kept for API symmetry with the host lifecycle wiring. */
|
|
10338
11791
|
dispose() {}
|
|
10339
11792
|
};
|
|
10340
|
-
|
|
10341
|
-
//#region src/phone-http.ts
|
|
11793
|
+
// src/phone-http.ts
|
|
10342
11794
|
const CLIENT_IP_HEADER = "x-deeppilot-client-ip";
|
|
10343
11795
|
function rejectUpgrade(socket, status, reason, retryAfterSeconds) {
|
|
10344
11796
|
const body = JSON.stringify({ error: reason });
|
|
@@ -10372,8 +11824,7 @@ function requestClientIdentity(req) {
|
|
|
10372
11824
|
}
|
|
10373
11825
|
return normalizedAddress(req.socket.remoteAddress) ?? "unknown";
|
|
10374
11826
|
}
|
|
10375
|
-
|
|
10376
|
-
//#region src/lan-tls.ts
|
|
11827
|
+
// src/lan-tls.ts
|
|
10377
11828
|
const LAN_TLS_COMMON_NAME = "dsh-deeppilot";
|
|
10378
11829
|
const LAN_TLS_KEY_FILE = "key.pem";
|
|
10379
11830
|
const LAN_TLS_CERT_FILE = "cert.pem";
|
|
@@ -10546,8 +11997,7 @@ async function writePrivate(path, contents) {
|
|
|
10546
11997
|
throw error;
|
|
10547
11998
|
}
|
|
10548
11999
|
}
|
|
10549
|
-
|
|
10550
|
-
//#region src/index.ts
|
|
12000
|
+
// src/index.ts
|
|
10551
12001
|
/**
|
|
10552
12002
|
* dsh-deeppilot — data bridge between the DSH host and DeepPilot
|
|
10553
12003
|
* clients. Owns independent, narrowly routed LAN (TLS-only, pinned by paired
|
|
@@ -11197,7 +12647,6 @@ function apply(ctx, options) {
|
|
|
11197
12647
|
await diagnostics.dispose();
|
|
11198
12648
|
}, "deeppilot: process resources");
|
|
11199
12649
|
}
|
|
11200
|
-
//#endregion
|
|
11201
12650
|
export { Config, HostBridge, apply, inject, mayReceivePush, name, shouldPrunePushToken, shouldReEnrollRelayToken };
|
|
11202
12651
|
|
|
11203
12652
|
//# sourceMappingURL=index.js.map
|