dsh-dispatch 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +171 -33
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -413,7 +413,8 @@ async function prepareWorkspace(options) {
|
|
|
413
413
|
if (!await isGitRepo(options.cwd)) {
|
|
414
414
|
return {
|
|
415
415
|
cwd: options.cwd,
|
|
416
|
-
note: `\u5DF2\u964D\u7EA7\uFF1A${options.cwd} \u4E0D\u662F git \u4ED3\u5E93\uFF0C\u4EFB\u52A1\u76F4\u63A5\u5728\u8BE5\u76EE\u5F55\u8FD0\u884C\uFF08\u672A\u521B\u5EFA worktree\uFF09
|
|
416
|
+
note: `\u5DF2\u964D\u7EA7\uFF1A${options.cwd} \u4E0D\u662F git \u4ED3\u5E93\uFF0C\u4EFB\u52A1\u76F4\u63A5\u5728\u8BE5\u76EE\u5F55\u8FD0\u884C\uFF08\u672A\u521B\u5EFA worktree\uFF09`,
|
|
417
|
+
noteReason: { code: "not-a-git-repo", params: { cwd: options.cwd } }
|
|
417
418
|
};
|
|
418
419
|
}
|
|
419
420
|
const root = join2(options.dataDir, "worktrees");
|
|
@@ -456,10 +457,22 @@ function stderrOf(error) {
|
|
|
456
457
|
|
|
457
458
|
// src/dispatch.ts
|
|
458
459
|
var SEEN_LIMIT = 256;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
var
|
|
460
|
+
function complain(text, code, params) {
|
|
461
|
+
return { text, reason: params === void 0 ? { code } : { code, params } };
|
|
462
|
+
}
|
|
463
|
+
var DISPATCH_DISABLED = complain(
|
|
464
|
+
"dispatch \u672A\u542F\u7528\uFF1A\u8BF7\u5728\u63D2\u4EF6\u914D\u7F6E\u4E2D\u8BBE\u7F6E allowedRoots",
|
|
465
|
+
"dispatch-disabled"
|
|
466
|
+
);
|
|
467
|
+
var NO_REMOTE_FOLLOWUP = complain("\u8BE5\u4F1A\u8BDD\u4E0D\u652F\u6301\u8FDC\u7A0B\u8FFD\u52A0", "no-remote-followup");
|
|
468
|
+
var FULL_ACCESS_DISABLED = complain(
|
|
469
|
+
"full-access \u6D3E\u53D1\u672A\u542F\u7528\uFF1A\u8BF7\u5728\u63D2\u4EF6\u914D\u7F6E\u4E2D\u5F00\u542F allowFullAccessDispatch",
|
|
470
|
+
"full-access-disabled"
|
|
471
|
+
);
|
|
472
|
+
var FULL_ACCESS_UNAVAILABLE = complain(
|
|
473
|
+
"full-access \u4E0D\u53EF\u7528\uFF1A\u672C\u673A dsh \u672A\u7EC4\u5408 permission-presets \u63D2\u4EF6",
|
|
474
|
+
"full-access-unavailable"
|
|
475
|
+
);
|
|
463
476
|
var FULL_ACCESS_PRESET = "danger-full-access";
|
|
464
477
|
function installDispatch(hub) {
|
|
465
478
|
const state = {
|
|
@@ -525,25 +538,41 @@ async function startSession(hub, state, message) {
|
|
|
525
538
|
hub.report(`dispatch ${message.requestId}`, `\u5B8C\u5168\u8BBF\u95EE\u6743\u9650\u672A\u80FD\u5E94\u7528\uFF1A${created.fullAccessError}`);
|
|
526
539
|
}
|
|
527
540
|
const result = ok(message.requestId, created.handle.agent.id);
|
|
528
|
-
const advisory = created.fullAccessError === void 0 ? workspace.note :
|
|
529
|
-
|
|
541
|
+
const advisory = created.fullAccessError === void 0 ? workspace.note === void 0 ? void 0 : { text: workspace.note, reason: workspace.noteReason ?? { code: "not-a-git-repo" } } : complain(
|
|
542
|
+
`\u5B8C\u5168\u8BBF\u95EE\u6743\u9650\u672A\u80FD\u5E94\u7528\uFF0C\u5DF2\u5728\u6807\u51C6\u6743\u9650\u4E0B\u8FD0\u884C\uFF1A${created.fullAccessError}`,
|
|
543
|
+
"full-access-not-applied",
|
|
544
|
+
{ detail: created.fullAccessError }
|
|
545
|
+
);
|
|
546
|
+
return advisory === void 0 ? result : { ...result, note: advisory.text, reason: advisory.reason };
|
|
530
547
|
} catch (error) {
|
|
531
548
|
const detail = error instanceof WorktreeError ? error.message : String(error);
|
|
532
549
|
hub.log.error("dispatch %s failed: %s", message.requestId, detail);
|
|
533
|
-
return failure(message.requestId,
|
|
550
|
+
return failure(message.requestId, complain(
|
|
551
|
+
detail,
|
|
552
|
+
error instanceof WorktreeError ? "worktree-failed" : "dispatch-failed",
|
|
553
|
+
{ detail }
|
|
554
|
+
));
|
|
534
555
|
}
|
|
535
556
|
}
|
|
536
557
|
async function handleFollowup(hub, state, message) {
|
|
537
|
-
const oversized = overLimit("
|
|
558
|
+
const oversized = overLimit("message", message.text);
|
|
538
559
|
if (oversized !== void 0) {
|
|
539
|
-
hub.report(
|
|
560
|
+
hub.report(
|
|
561
|
+
`session.message ${message.sessionId}`,
|
|
562
|
+
oversized.text,
|
|
563
|
+
{ sessionId: message.sessionId, reason: oversized.reason }
|
|
564
|
+
);
|
|
540
565
|
return;
|
|
541
566
|
}
|
|
542
567
|
if (state.seen.get(message.requestId) !== void 0) return;
|
|
543
568
|
state.seen.set(message.requestId, null);
|
|
544
569
|
const agent = state.handles.get(message.sessionId)?.agent ?? hub.sessions.agentOf(message.sessionId);
|
|
545
570
|
if (agent === void 0) {
|
|
546
|
-
hub.report(
|
|
571
|
+
hub.report(
|
|
572
|
+
`session.message ${message.sessionId}`,
|
|
573
|
+
NO_REMOTE_FOLLOWUP.text,
|
|
574
|
+
{ sessionId: message.sessionId, reason: NO_REMOTE_FOLLOWUP.reason }
|
|
575
|
+
);
|
|
547
576
|
return;
|
|
548
577
|
}
|
|
549
578
|
agent.followup(createUserMessage({
|
|
@@ -552,13 +581,22 @@ async function handleFollowup(hub, state, message) {
|
|
|
552
581
|
}));
|
|
553
582
|
}
|
|
554
583
|
function overLimit(field, text) {
|
|
584
|
+
const label = field === "prompt" ? "\u63D0\u793A\u8BCD" : "\u6D88\u606F";
|
|
555
585
|
const tail = "\u8BF7\u5728\u624B\u673A\u7AEF\u7F29\u77ED\u540E\u91CD\u53D1\u3002";
|
|
556
586
|
if (text.length > MAX_PROMPT_CHARS) {
|
|
557
|
-
return
|
|
587
|
+
return complain(
|
|
588
|
+
`${label} \u8D85\u957F\uFF1A${String(text.length)} \u5B57\u7B26\uFF0C\u4E0A\u9650 ${String(MAX_PROMPT_CHARS)} \u5B57\u7B26\u3002${tail}`,
|
|
589
|
+
`${field}-too-long-chars`,
|
|
590
|
+
{ actual: text.length, limit: MAX_PROMPT_CHARS }
|
|
591
|
+
);
|
|
558
592
|
}
|
|
559
593
|
const bytes = utf8Length(text);
|
|
560
594
|
if (bytes > MAX_PROMPT_BYTES) {
|
|
561
|
-
return
|
|
595
|
+
return complain(
|
|
596
|
+
`${label} \u8D85\u957F\uFF1A${String(bytes)} \u5B57\u8282\uFF08UTF-8\uFF09\uFF0C\u4E0A\u9650 ${String(MAX_PROMPT_BYTES)} \u5B57\u8282\u3002${tail}`,
|
|
597
|
+
`${field}-too-long-bytes`,
|
|
598
|
+
{ actual: bytes, limit: MAX_PROMPT_BYTES }
|
|
599
|
+
);
|
|
562
600
|
}
|
|
563
601
|
return void 0;
|
|
564
602
|
}
|
|
@@ -587,7 +625,11 @@ function validate(hub, message) {
|
|
|
587
625
|
const oversized = overLimit("prompt", message.prompt);
|
|
588
626
|
if (oversized !== void 0) return oversized;
|
|
589
627
|
if (message.access !== void 0 && message.access !== "standard" && message.access !== "full") {
|
|
590
|
-
return
|
|
628
|
+
return complain(
|
|
629
|
+
`access \u53D6\u503C\u65E0\u6548\uFF1A${String(message.access)}`,
|
|
630
|
+
"bad-access-value",
|
|
631
|
+
{ value: String(message.access) }
|
|
632
|
+
);
|
|
591
633
|
}
|
|
592
634
|
if (message.access !== "full") return void 0;
|
|
593
635
|
if (!hub.config.allowFullAccessDispatch) return FULL_ACCESS_DISABLED;
|
|
@@ -623,24 +665,37 @@ function applyFullAccess(ctx, handle) {
|
|
|
623
665
|
return error instanceof Error ? error.message : String(error);
|
|
624
666
|
}
|
|
625
667
|
}
|
|
668
|
+
function resolvedRoots(hub) {
|
|
669
|
+
return hub.config.allowedRoots.map((root) => resolve2(expandHome(root)));
|
|
670
|
+
}
|
|
626
671
|
function resolveCwd(hub, requested) {
|
|
627
|
-
const roots = hub
|
|
672
|
+
const roots = resolvedRoots(hub);
|
|
628
673
|
if (roots.length === 0) return { error: DISPATCH_DISABLED };
|
|
629
674
|
const first = roots[0];
|
|
630
675
|
if (requested === void 0) return first === void 0 ? { error: DISPATCH_DISABLED } : first;
|
|
631
676
|
const target = resolve2(isAbsolute(requested) ? requested : expandHome(requested));
|
|
632
677
|
const inside = roots.some((root) => target === root || target.startsWith(root + sep));
|
|
633
|
-
return inside ? target : { error: `cwd \u4E0D\u5728\u5141\u8BB8\u76EE\u5F55\u5185\uFF1A${target}
|
|
678
|
+
return inside ? target : { error: complain(`cwd \u4E0D\u5728\u5141\u8BB8\u76EE\u5F55\u5185\uFF1A${target}`, "cwd-not-allowed", { cwd: target }) };
|
|
634
679
|
}
|
|
635
680
|
async function reportTurnFinal(hub, sessionId) {
|
|
636
681
|
const snapshot = await hub.ctx.sessionQuery.readSurface(SessionId(sessionId));
|
|
637
682
|
const ok2 = !hub.sessions.hasError(sessionId);
|
|
683
|
+
const text = lastAssistantText(snapshot.events);
|
|
684
|
+
const placeholder = text === void 0 ? complain("\uFF08\u672C\u8F6E\u6CA1\u6709\u4EA7\u751F\u52A9\u624B\u6587\u672C\uFF09", "no-assistant-text") : void 0;
|
|
638
685
|
const summary = clampBytes(
|
|
639
|
-
truncate(
|
|
686
|
+
truncate(text ?? placeholder.text, MAX_SUMMARY_CHARS),
|
|
640
687
|
MAX_FIELD_BYTES
|
|
641
688
|
);
|
|
642
689
|
hub.relay.send(
|
|
643
|
-
{
|
|
690
|
+
{
|
|
691
|
+
v: 1,
|
|
692
|
+
ts: Date.now(),
|
|
693
|
+
type: "turn.final",
|
|
694
|
+
sessionId,
|
|
695
|
+
ok: ok2,
|
|
696
|
+
summary,
|
|
697
|
+
...placeholder === void 0 ? {} : { reason: placeholder.reason }
|
|
698
|
+
},
|
|
644
699
|
{
|
|
645
700
|
v: 1,
|
|
646
701
|
ts: Date.now(),
|
|
@@ -658,13 +713,21 @@ function lastAssistantText(events) {
|
|
|
658
713
|
const text = event.data.message.content.filter((block) => block.type === "text").map((block) => block.text).join("").trim();
|
|
659
714
|
if (text !== "") return text;
|
|
660
715
|
}
|
|
661
|
-
return
|
|
716
|
+
return void 0;
|
|
662
717
|
}
|
|
663
718
|
function ok(requestId, sessionId) {
|
|
664
719
|
return { v: 1, ts: Date.now(), type: "dispatch.result", requestId, ok: true, sessionId };
|
|
665
720
|
}
|
|
666
|
-
function failure(requestId,
|
|
667
|
-
return {
|
|
721
|
+
function failure(requestId, complaint) {
|
|
722
|
+
return {
|
|
723
|
+
v: 1,
|
|
724
|
+
ts: Date.now(),
|
|
725
|
+
type: "dispatch.result",
|
|
726
|
+
requestId,
|
|
727
|
+
ok: false,
|
|
728
|
+
error: complaint.text,
|
|
729
|
+
reason: complaint.reason
|
|
730
|
+
};
|
|
668
731
|
}
|
|
669
732
|
var ResultCache = class {
|
|
670
733
|
#entries = /* @__PURE__ */ new Map();
|
|
@@ -685,14 +748,16 @@ var ResultCache = class {
|
|
|
685
748
|
// src/hub.ts
|
|
686
749
|
var ERROR_CHARS = 1e3;
|
|
687
750
|
function createHub(parts) {
|
|
688
|
-
const report = (context, message) => {
|
|
751
|
+
const report = (context, message, about = {}) => {
|
|
689
752
|
parts.log.error("%s: %s", context, message);
|
|
690
753
|
parts.relay.send({
|
|
691
754
|
v: 1,
|
|
692
755
|
ts: Date.now(),
|
|
693
756
|
type: "error",
|
|
694
757
|
message: truncate(message, ERROR_CHARS),
|
|
695
|
-
context
|
|
758
|
+
context,
|
|
759
|
+
...about.sessionId === void 0 ? {} : { sessionId: about.sessionId },
|
|
760
|
+
...about.reason === void 0 ? {} : { reason: about.reason }
|
|
696
761
|
});
|
|
697
762
|
};
|
|
698
763
|
return {
|
|
@@ -1042,7 +1107,12 @@ var RelayClient = class {
|
|
|
1042
1107
|
#onPresence(frame) {
|
|
1043
1108
|
const { role, online } = frame;
|
|
1044
1109
|
if (role !== "phone" || typeof online !== "boolean") return;
|
|
1045
|
-
|
|
1110
|
+
if (online) {
|
|
1111
|
+
this.#phoneOnline = true;
|
|
1112
|
+
this.#options.onPhonePresence(true);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
this.#setPhoneOnline(false);
|
|
1046
1116
|
}
|
|
1047
1117
|
#setPhoneOnline(online) {
|
|
1048
1118
|
if (this.#phoneOnline === online) return;
|
|
@@ -1163,12 +1233,16 @@ function isAnswerList(value) {
|
|
|
1163
1233
|
// src/sessions.ts
|
|
1164
1234
|
import { SessionId as SessionId2 } from "@deepseek-ai/dsh-session";
|
|
1165
1235
|
var TITLE_CHARS2 = 80;
|
|
1236
|
+
var ACTIVITY_CHARS = 40;
|
|
1237
|
+
var ACTIVITY_MIN_GAP_MS = 800;
|
|
1166
1238
|
var SessionTracker = class {
|
|
1167
1239
|
#ctx;
|
|
1168
1240
|
#relay;
|
|
1169
1241
|
#log;
|
|
1170
1242
|
#tracked = /* @__PURE__ */ new Map();
|
|
1171
1243
|
#published = /* @__PURE__ */ new Map();
|
|
1244
|
+
#activityAt = /* @__PURE__ */ new Map();
|
|
1245
|
+
#activityTimers = /* @__PURE__ */ new Map();
|
|
1172
1246
|
constructor(ctx, relay, log) {
|
|
1173
1247
|
this.#ctx = ctx;
|
|
1174
1248
|
this.#relay = relay;
|
|
@@ -1198,14 +1272,29 @@ var SessionTracker = class {
|
|
|
1198
1272
|
});
|
|
1199
1273
|
});
|
|
1200
1274
|
this.#ctx.on("agent/turn-stopping", ({ agent }) => {
|
|
1275
|
+
this.#update(agent.id, (entry) => {
|
|
1276
|
+
entry.activity = void 0;
|
|
1277
|
+
entry.activityCallId = void 0;
|
|
1278
|
+
});
|
|
1201
1279
|
void this.#refreshTitle(agent);
|
|
1202
1280
|
});
|
|
1203
1281
|
this.#ctx.on("agent/disposed", ({ agent }) => {
|
|
1204
1282
|
this.#update(agent.id, (entry) => {
|
|
1205
1283
|
entry.base = "done";
|
|
1284
|
+
entry.activity = void 0;
|
|
1206
1285
|
});
|
|
1207
1286
|
this.#tracked.delete(agent.id);
|
|
1208
1287
|
this.#published.delete(agent.id);
|
|
1288
|
+
this.#clearActivityTimer(agent.id);
|
|
1289
|
+
});
|
|
1290
|
+
this.#ctx.on("session/event", (session, event) => {
|
|
1291
|
+
if (event.type === "tool/call") {
|
|
1292
|
+
this.#setActivity(session.id, truncate(event.data.name, ACTIVITY_CHARS), event.data.callId);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (event.type === "tool/result") {
|
|
1296
|
+
this.#clearActivity(session.id, event.data.message.content[0].toolCallId);
|
|
1297
|
+
}
|
|
1209
1298
|
});
|
|
1210
1299
|
}
|
|
1211
1300
|
/** Mark a session as started by us, with the dispatch prompt as its title. */
|
|
@@ -1236,6 +1325,10 @@ var SessionTracker = class {
|
|
|
1236
1325
|
agentOf(sessionId) {
|
|
1237
1326
|
return this.#ctx.agents.get(SessionId2(sessionId));
|
|
1238
1327
|
}
|
|
1328
|
+
/** Whether a follow-up would land: exactly the check `session.message` makes. */
|
|
1329
|
+
#isLive(sessionId) {
|
|
1330
|
+
return this.agentOf(sessionId) !== void 0;
|
|
1331
|
+
}
|
|
1239
1332
|
/** An approval for this session is now waiting on a human. */
|
|
1240
1333
|
approvalOpened(sessionId) {
|
|
1241
1334
|
this.#update(sessionId, (entry) => {
|
|
@@ -1255,7 +1348,7 @@ var SessionTracker = class {
|
|
|
1255
1348
|
if (!record.live) continue;
|
|
1256
1349
|
const id = record.header.id;
|
|
1257
1350
|
const known = this.#tracked.get(id) ?? this.#adopt(this.#ctx.agents.get(id));
|
|
1258
|
-
wire.push(known === void 0 ? this.#fallbackWire(id, record.header.cwd ?? "", record.header.createdAt) : toWire(known));
|
|
1351
|
+
wire.push(known === void 0 ? this.#fallbackWire(id, record.header.cwd ?? "", record.header.createdAt) : toWire(known, this.#isLive(id)));
|
|
1259
1352
|
}
|
|
1260
1353
|
return wire;
|
|
1261
1354
|
}
|
|
@@ -1267,7 +1360,9 @@ var SessionTracker = class {
|
|
|
1267
1360
|
state: "idle",
|
|
1268
1361
|
lastActivity: createdAt,
|
|
1269
1362
|
dispatched: false,
|
|
1270
|
-
access: "standard"
|
|
1363
|
+
access: "standard",
|
|
1364
|
+
// listSessions() already filtered to live records.
|
|
1365
|
+
live: true
|
|
1271
1366
|
};
|
|
1272
1367
|
}
|
|
1273
1368
|
#adopt(agent) {
|
|
@@ -1283,11 +1378,51 @@ var SessionTracker = class {
|
|
|
1283
1378
|
lastActivity: Date.now(),
|
|
1284
1379
|
dispatched: false,
|
|
1285
1380
|
access: "standard",
|
|
1286
|
-
finalized: false
|
|
1381
|
+
finalized: false,
|
|
1382
|
+
activity: void 0,
|
|
1383
|
+
activityCallId: void 0
|
|
1287
1384
|
};
|
|
1288
1385
|
this.#tracked.set(agent.id, entry);
|
|
1289
1386
|
return entry;
|
|
1290
1387
|
}
|
|
1388
|
+
#setActivity(sessionId, name2, callId) {
|
|
1389
|
+
const entry = this.#tracked.get(sessionId);
|
|
1390
|
+
if (entry === void 0 || entry.activity === name2) return;
|
|
1391
|
+
entry.activity = name2;
|
|
1392
|
+
entry.activityCallId = callId;
|
|
1393
|
+
this.#publishActivity(sessionId);
|
|
1394
|
+
}
|
|
1395
|
+
/** Only the call that set the activity may clear it (tools can overlap). */
|
|
1396
|
+
#clearActivity(sessionId, callId) {
|
|
1397
|
+
const entry = this.#tracked.get(sessionId);
|
|
1398
|
+
if (entry === void 0 || entry.activityCallId !== callId) return;
|
|
1399
|
+
entry.activity = void 0;
|
|
1400
|
+
entry.activityCallId = void 0;
|
|
1401
|
+
this.#publishActivity(sessionId);
|
|
1402
|
+
}
|
|
1403
|
+
/** Trailing-edge coalescing, so a tool-heavy turn cannot flood the phone. */
|
|
1404
|
+
#publishActivity(sessionId) {
|
|
1405
|
+
const elapsed = Date.now() - (this.#activityAt.get(sessionId) ?? 0);
|
|
1406
|
+
if (elapsed >= ACTIVITY_MIN_GAP_MS) {
|
|
1407
|
+
this.#activityAt.set(sessionId, Date.now());
|
|
1408
|
+
this.#publish(sessionId);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
if (this.#activityTimers.has(sessionId)) return;
|
|
1412
|
+
const timer = setTimeout(() => {
|
|
1413
|
+
this.#activityTimers.delete(sessionId);
|
|
1414
|
+
this.#activityAt.set(sessionId, Date.now());
|
|
1415
|
+
this.#publish(sessionId);
|
|
1416
|
+
}, ACTIVITY_MIN_GAP_MS - elapsed);
|
|
1417
|
+
timer.unref?.();
|
|
1418
|
+
this.#activityTimers.set(sessionId, timer);
|
|
1419
|
+
}
|
|
1420
|
+
#clearActivityTimer(sessionId) {
|
|
1421
|
+
const timer = this.#activityTimers.get(sessionId);
|
|
1422
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1423
|
+
this.#activityTimers.delete(sessionId);
|
|
1424
|
+
this.#activityAt.delete(sessionId);
|
|
1425
|
+
}
|
|
1291
1426
|
#update(sessionId, mutate) {
|
|
1292
1427
|
const entry = this.#tracked.get(sessionId) ?? this.#adopt(this.#ctx.agents.get(SessionId2(sessionId)));
|
|
1293
1428
|
if (entry === void 0) return;
|
|
@@ -1299,8 +1434,8 @@ var SessionTracker = class {
|
|
|
1299
1434
|
#publish(sessionId) {
|
|
1300
1435
|
const entry = this.#tracked.get(sessionId);
|
|
1301
1436
|
if (entry === void 0) return;
|
|
1302
|
-
const session = toWire(entry);
|
|
1303
|
-
const fingerprint = `${session.state}|${session.title}|${session.cwd}|${String(session.dispatched)}|${String(session.access)}`;
|
|
1437
|
+
const session = toWire(entry, this.#isLive(sessionId));
|
|
1438
|
+
const fingerprint = `${session.state}|${session.title}|${session.cwd}|${String(session.dispatched)}|${String(session.access)}|${String(session.live)}|${session.activity ?? ""}`;
|
|
1304
1439
|
if (this.#published.get(sessionId) === fingerprint) return;
|
|
1305
1440
|
this.#published.set(sessionId, fingerprint);
|
|
1306
1441
|
this.#relay.send({ v: 1, ts: Date.now(), type: "session.update", session });
|
|
@@ -1319,7 +1454,7 @@ var SessionTracker = class {
|
|
|
1319
1454
|
}
|
|
1320
1455
|
}
|
|
1321
1456
|
};
|
|
1322
|
-
function toWire(entry) {
|
|
1457
|
+
function toWire(entry, live) {
|
|
1323
1458
|
return {
|
|
1324
1459
|
sessionId: entry.sessionId,
|
|
1325
1460
|
title: entry.title,
|
|
@@ -1327,7 +1462,9 @@ function toWire(entry) {
|
|
|
1327
1462
|
state: stateOf(entry),
|
|
1328
1463
|
lastActivity: entry.lastActivity,
|
|
1329
1464
|
dispatched: entry.dispatched,
|
|
1330
|
-
access: entry.access
|
|
1465
|
+
access: entry.access,
|
|
1466
|
+
live,
|
|
1467
|
+
...entry.activity === void 0 ? {} : { activity: entry.activity }
|
|
1331
1468
|
};
|
|
1332
1469
|
}
|
|
1333
1470
|
function stateOf(entry) {
|
|
@@ -1344,7 +1481,7 @@ function firstPrompt(agent) {
|
|
|
1344
1481
|
}
|
|
1345
1482
|
|
|
1346
1483
|
// src/version.ts
|
|
1347
|
-
var PLUGIN_VERSION = "0.
|
|
1484
|
+
var PLUGIN_VERSION = "0.3.0";
|
|
1348
1485
|
|
|
1349
1486
|
// src/config.ts
|
|
1350
1487
|
import { hostname } from "os";
|
|
@@ -1468,7 +1605,8 @@ function publishStatus(hub) {
|
|
|
1468
1605
|
capabilities: {
|
|
1469
1606
|
fullAccessDispatch: hub.config.allowFullAccessDispatch,
|
|
1470
1607
|
questionForwarding: true
|
|
1471
|
-
}
|
|
1608
|
+
},
|
|
1609
|
+
allowedRoots: resolvedRoots(hub)
|
|
1472
1610
|
});
|
|
1473
1611
|
}
|
|
1474
1612
|
async function publishSnapshot(hub) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/approvals.ts","../../shared/src/crypto.ts","../../shared/src/limits.ts","../src/commands.ts","../src/dispatch.ts","../src/pairing.ts","../src/worktree.ts","../src/hub.ts","../src/questions.ts","../src/relay-client.ts","../src/sessions.ts","../src/version.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["/**\n * Approval forwarding — the M1 core.\n *\n * Registers a waterfall answerer on `approval/request` and races three ways:\n * the phone's decision, a local answer arriving through `next()`, and the\n * request's own abort. A timeout is deliberately NOT one of them: docs/PROTOCOL.md\n * invariant 1 forbids auto-allow, so an unanswered question stays pending and\n * is re-pushed instead.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { Agent } from '@deepseek-ai/dsh-agent';\nimport type { CallId } from '@deepseek-ai/dsh-llm';\nimport type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval';\nimport { clampBytes, MAX_DETAIL_CHARS, MAX_FIELD_BYTES, truncate } from '@dsh-dispatch/shared';\nimport type {\n ApprovalRequestMsg,\n ApprovalRespondMsg,\n ApprovalResolution,\n PushCompactMsg,\n} from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\n\nconst TITLE_CHARS = 200;\n/** How often an unanswered approval nudges the phone again. */\nconst REMINDER_MS = 5 * 60_000;\n/** Stop nudging after this many reminders; the question still stays open. */\nconst MAX_REMINDERS = 6;\n\ntype Settle = (outcome: ApprovalOutcome, resolution: ApprovalResolution) => void;\n\ninterface Pending {\n readonly frame: ApprovalRequestMsg;\n readonly push: PushCompactMsg;\n readonly settle: Settle;\n reminders: number;\n timer: ReturnType<typeof setTimeout> | undefined;\n}\n\nexport interface ApprovalRouter {\n /** Apply a phone decision. Unknown ids are answered `expired` so cards clear. */\n respond(message: ApprovalRespondMsg): void;\n /** Re-push every still-open question after a reconnect. */\n resendPending(): void;\n openCount(): number;\n}\n\nexport function installApprovals(hub: Hub): ApprovalRouter {\n const pending = new Map<string, Pending>();\n\n hub.ctx.on('approval/request', (request, next) => {\n // Nothing to forward to: delegate immediately so an unpaired or offline\n // machine pays zero added latency on its local approval UI.\n if (!hub.pairing.everPaired || !hub.relay.connected) return next();\n if (request.signal?.aborted === true) return Promise.resolve<ApprovalOutcome>('cancelled');\n return forward(hub, pending, request, next);\n });\n\n return {\n respond(message: ApprovalRespondMsg): void {\n const entry = pending.get(message.approvalId);\n if (entry === undefined) {\n // Double-tap, or the desktop already answered. Tell the phone to clear.\n hub.relay.send(closedMsg(message.approvalId, 'expired'));\n return;\n }\n entry.settle(message.decision === 'allow' ? 'allowed-once' : 'rejected', message.decision);\n },\n resendPending(): void {\n for (const entry of pending.values()) hub.relay.send(entry.frame, entry.push);\n },\n openCount: () => pending.size,\n };\n}\n\nfunction forward(\n hub: Hub,\n pending: Map<string, Pending>,\n request: ApprovalRequest,\n next: () => Promise<ApprovalOutcome>,\n): Promise<ApprovalOutcome> {\n const approvalId = randomUUID();\n const sessionId = request.agent.id;\n const frame = buildRequestMsg(approvalId, sessionId, request);\n const push = buildPush(sessionId, frame.title);\n if (!hub.relay.send(frame, push)) {\n hub.report('approval', `审批 ${frame.title} 未能推送到手机(relay 未连接),已交回本机处理`);\n return next();\n }\n return race(hub, pending, { approvalId, sessionId, frame, push }, request, next);\n}\n\ninterface Forwarded {\n readonly approvalId: string;\n readonly sessionId: string;\n readonly frame: ApprovalRequestMsg;\n readonly push: PushCompactMsg;\n}\n\nfunction race(\n hub: Hub,\n pending: Map<string, Pending>,\n forwarded: Forwarded,\n request: ApprovalRequest,\n next: () => Promise<ApprovalOutcome>,\n): Promise<ApprovalOutcome> {\n const { approvalId, sessionId } = forwarded;\n return new Promise<ApprovalOutcome>((resolve) => {\n let settled = false;\n const settle: Settle = (outcome, resolution) => {\n if (settled) return;\n settled = true;\n const entry = pending.get(approvalId);\n if (entry?.timer !== undefined) clearTimeout(entry.timer);\n pending.delete(approvalId);\n request.signal?.removeEventListener('abort', onAbort);\n hub.sessions.approvalClosed(sessionId);\n hub.relay.send(closedMsg(approvalId, resolution));\n resolve(outcome);\n };\n function onAbort(): void {\n settle('cancelled', 'superseded');\n }\n request.signal?.addEventListener('abort', onAbort, { once: true });\n const entry: Pending = { ...forwarded, settle, reminders: 0, timer: undefined };\n pending.set(approvalId, entry);\n hub.sessions.approvalOpened(sessionId);\n armReminder(hub, entry);\n // `unavailable` means nobody downstream answered — keep waiting for the\n // phone. Any real decision means a human answered on the desktop first.\n void next().then(\n (outcome) => { if (outcome !== 'unavailable') settle(outcome, 'local'); },\n (error: unknown) => { hub.fail(`approval ${approvalId}: local answerer failed`, error); },\n );\n });\n}\n\n/** Never auto-allow; nudge instead, then go quiet while staying open. */\nfunction armReminder(hub: Hub, entry: Pending): void {\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.reminders += 1;\n hub.relay.send(entry.frame, entry.push);\n if (entry.reminders >= MAX_REMINDERS) {\n hub.log.warn(\n 'approval %s still unanswered after %d reminders; it stays open (never auto-approved)',\n entry.frame.approvalId, entry.reminders,\n );\n return;\n }\n armReminder(hub, entry);\n }, REMINDER_MS);\n entry.timer.unref?.();\n}\n\nfunction buildRequestMsg(\n approvalId: string,\n sessionId: string,\n request: ApprovalRequest,\n): ApprovalRequestMsg {\n return {\n v: 1,\n ts: Date.now(),\n type: 'approval.request',\n approvalId,\n sessionId,\n title: truncate(request.toolName, TITLE_CHARS),\n // Chars first (protocol conformance), then bytes: 8 192 chars of CJK is\n // ~24 KB of UTF-8, which base64 would expand past the 16 KB envelope.\n detail: clampBytes(truncate(buildDetail(request), MAX_DETAIL_CHARS), MAX_FIELD_BYTES),\n createdAt: Date.now(),\n };\n}\n\n/**\n * The phone must be able to see exactly what it is allowing before allowing it\n * (docs/PRODUCT.md 防呆). `ApprovalRequest` deliberately omits tool arguments —\n * they live on the already-logged `tool/call` this `callId` points at.\n */\nfunction buildDetail(request: ApprovalRequest): string {\n const parts = [`工具: ${request.toolName}`];\n if (request.reason !== undefined && request.reason !== '') parts.push(`原因: ${request.reason}`);\n const args = toolCallArguments(request.agent, request.callId);\n parts.push(args === undefined ? '参数: (调用未记录参数)' : `参数:\\n${args}`);\n return parts.join('\\n\\n');\n}\n\nfunction toolCallArguments(agent: Agent, callId: CallId | undefined): string | undefined {\n if (callId === undefined) return undefined;\n const events = agent.session.events;\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n if (event?.type === 'tool/call' && event.data.callId === callId) return event.data.arguments;\n }\n return undefined;\n}\n\nfunction buildPush(sessionId: string, title: string): PushCompactMsg {\n return { v: 1, ts: Date.now(), type: 'push', kind: 'approval', sessionId, title };\n}\n\nfunction closedMsg(approvalId: string, resolution: ApprovalResolution) {\n return { v: 1, ts: Date.now(), type: 'approval.closed', approvalId, resolution } as const;\n}\n","// E2E crypto for dsh-dispatch. Isomorphic (Node + browser) — tweetnacl only.\n// Key/room derivation uses SHA-512 (nacl.hash) truncated; see docs/PROTOCOL.md.\n\nimport nacl from 'tweetnacl';\nimport type { PairingInfo } from './types.js';\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nconst KEY_PREFIX = encoder.encode('dsh-dispatch/key');\nconst ROOM_PREFIX = encoder.encode('dsh-dispatch/room');\n\nconst NONCE_LENGTH = nacl.secretbox.nonceLength;\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const out = new Uint8Array(a.length + b.length);\n out.set(a, 0);\n out.set(b, a.length);\n return out;\n}\n\nconst B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\nexport function toBase64(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n out += B64_ALPHABET[b0 >> 2]!;\n out += B64_ALPHABET[((b0 & 3) << 4) | ((b1 ?? 0) >> 4)]!;\n out += b1 === undefined ? '=' : B64_ALPHABET[((b1 & 15) << 2) | ((b2 ?? 0) >> 6)]!;\n out += b2 === undefined ? '=' : B64_ALPHABET[b2 & 63]!;\n }\n return out;\n}\n\nexport function fromBase64(text: string): Uint8Array | null {\n const clean = text.replace(/=+$/, '');\n if (!/^[A-Za-z0-9+/]*$/.test(clean)) return null;\n const out = new Uint8Array(Math.floor((clean.length * 3) / 4));\n let bits = 0;\n let value = 0;\n let index = 0;\n for (const char of clean) {\n value = (value << 6) | B64_ALPHABET.indexOf(char);\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[index++] = (value >> bits) & 0xff;\n }\n }\n return out.slice(0, index);\n}\n\nexport function toBase64Url(bytes: Uint8Array): string {\n return toBase64(bytes).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport function fromBase64Url(text: string): Uint8Array | null {\n return fromBase64(text.replace(/-/g, '+').replace(/_/g, '/'));\n}\n\nexport function generateSecret(): Uint8Array {\n return nacl.randomBytes(32);\n}\n\nexport function deriveKey(secret: Uint8Array): Uint8Array {\n return nacl.hash(concat(KEY_PREFIX, secret)).slice(0, nacl.secretbox.keyLength);\n}\n\nexport function deriveRoomId(secret: Uint8Array): string {\n return toBase64Url(nacl.hash(concat(ROOM_PREFIX, secret)).slice(0, 16));\n}\n\nexport function seal(message: unknown, key: Uint8Array): string {\n const nonce = nacl.randomBytes(NONCE_LENGTH);\n const box = nacl.secretbox(encoder.encode(JSON.stringify(message)), nonce, key);\n return toBase64(concat(nonce, box));\n}\n\n// Returns null on any tamper/garbage/wrong-key input — caller MUST surface it, not drop it.\nexport function open(payload: string, key: Uint8Array): unknown | null {\n const bytes = fromBase64(payload);\n if (bytes === null || bytes.length < NONCE_LENGTH + nacl.secretbox.overheadLength) return null;\n const box = nacl.secretbox.open(bytes.slice(NONCE_LENGTH), bytes.slice(0, NONCE_LENGTH), key);\n if (box === null) return null;\n try {\n return JSON.parse(decoder.decode(box));\n } catch {\n return null;\n }\n}\n\nexport function encodePairing(info: PairingInfo): string {\n const json = JSON.stringify({\n v: 1,\n relay: info.relay,\n secret: toBase64Url(info.secret),\n machine: info.machine,\n });\n return toBase64Url(encoder.encode(json));\n}\n\nexport function decodePairing(code: string): PairingInfo | null {\n const bytes = fromBase64Url(code.trim());\n if (bytes === null) return null;\n try {\n const parsed = JSON.parse(decoder.decode(bytes)) as {\n v?: number;\n relay?: string;\n secret?: string;\n machine?: string;\n };\n if (parsed.v !== 1 || !parsed.relay || !parsed.secret || !parsed.machine) return null;\n const secret = fromBase64Url(parsed.secret);\n if (secret === null || secret.length !== 32) return null;\n return { relay: parsed.relay, secret, machine: parsed.machine };\n } catch {\n return null;\n }\n}\n","// Payload limits from docs/PROTOCOL.md. The sender truncates BEFORE encryption.\n// The relay imports THIS FILE by subpath (src/limits.js) to keep tweetnacl out of its\n// bundle — if this package ever gains a package.json \"exports\" map, add a matching entry.\n\nexport const MAX_ENVELOPE_BYTES = 16 * 1024;\nexport const MAX_DETAIL_CHARS = 8 * 1024;\nexport const MAX_SUMMARY_CHARS = 4 * 1024;\n// Char limits alone don't bound the envelope: CJK is ~3 bytes/char in UTF-8.\n// Free-text fields are clamped by chars first, then by bytes (clampBytes) before seal.\nexport const MAX_PROMPT_CHARS = 8 * 1024;\nexport const MAX_PROMPT_BYTES = 10 * 1024;\nexport const MAX_FIELD_BYTES = 10 * 1024;\nexport const MAX_PUSH_BYTES = 3 * 1024;\nexport const TRUNCATION_SUFFIX = '…[truncated]';\n\nexport function truncate(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text;\n return text.slice(0, maxChars - TRUNCATION_SUFFIX.length) + TRUNCATION_SUFFIX;\n}\n\nconst byteEncoder = new TextEncoder();\n\nexport function utf8Length(text: string): number {\n return byteEncoder.encode(text).length;\n}\n\n// Clamp to a UTF-8 byte budget without splitting a multi-byte char or surrogate pair.\nexport function clampBytes(text: string, maxBytes: number): string {\n if (utf8Length(text) <= maxBytes) return text;\n const budget = maxBytes - utf8Length(TRUNCATION_SUFFIX);\n let low = 0;\n let high = text.length;\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if (utf8Length(text.slice(0, mid)) <= budget) low = mid;\n else high = mid - 1;\n }\n const tail = text.charCodeAt(low - 1);\n if (tail >= 0xd800 && tail <= 0xdbff) low -= 1;\n return text.slice(0, low) + TRUNCATION_SUFFIX;\n}\n","/**\n * The desktop-side surfaces: pairing, re-pairing, and the status line that\n * makes a broken link visible without leaving dsh (docs/PRODUCT.md 失败可见).\n */\n\nimport type { CommandResult } from '@deepseek-ai/dsh-commands';\nimport type { Hub } from './hub.js';\nimport type { Routers } from './index.js';\n\nexport type CommandDeps = Routers;\n\nexport function installCommands(hub: Hub, deps: CommandDeps): void {\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-pair',\n description: 'Show the dsh-dispatch pairing code for this machine',\n handler: () => pairingResult(hub),\n }), 'dsh-dispatch: /dispatch-pair');\n\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-repair',\n description: 'Generate a new dsh-dispatch pairing secret (invalidates every paired phone)',\n handler: () => {\n hub.relay.rekey(hub.pairing.regenerate());\n hub.log.warn('pairing secret regenerated; every previously paired phone is now locked out');\n return pairingResult(hub, '旧配对码已作废,请在每台手机上重新扫码。\\n\\n');\n },\n }), 'dsh-dispatch: /dispatch-repair');\n\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-status',\n description: 'Show the dsh-dispatch relay connection and remote session state',\n handler: () => ({ kind: 'success', text: statusText(hub, deps) }),\n }), 'dsh-dispatch: /dispatch-status');\n}\n\nfunction pairingResult(hub: Hub, prefix = ''): CommandResult {\n if (hub.config.relay === '') {\n return {\n kind: 'error',\n text: 'relay 未配置:请在 profile 的 cordis.patch.yml 中为 dsh-dispatch 设置 relay: \\'wss://…\\'',\n };\n }\n const code = hub.pairing.pairingCode(hub.config.relay, hub.config.machineName);\n return {\n kind: 'success',\n text: `${prefix}配对码(${hub.config.machineName}):\\n${code}\\n\\n`\n + `或在手机上打开:\\n${hub.config.pwaUrl}/#pair=${code}\\n\\n`\n + '⚠️ 此码等同于本机的控制权:持有者可以批准工具调用并在允许目录中派发任务。'\n + '不要发到群里或截图外传;一旦泄露立即运行 /dispatch-repair。',\n };\n}\n\nfunction statusText(hub: Hub, deps: CommandDeps): string {\n const status = hub.relay.status();\n const link = hub.config.relay === ''\n ? '未配置(请设置 relay)'\n : status.connected\n ? `已连接 ${hub.config.relay}`\n : status.gaveUp\n ? `连接失败 ${String(status.consecutiveFailures)} 次,仍在每 60s 重试 — 请检查 relay`\n : `未连接(重试中,已失败 ${String(status.consecutiveFailures)} 次)`;\n return [\n `relay: ${link}`,\n `房间: ${status.room.slice(0, 8)}…`,\n `已配对: ${hub.pairing.everPaired ? 'yes' : 'no'}`\n + `${status.phoneOnline ? '(手机在线)' : '(手机离线)'}`,\n `待批审批: ${String(deps.approvals.openCount())}`,\n `待答提问: ${String(deps.questions.openCount())}`,\n `派发会话: ${String(deps.dispatch.dispatchedCount())}`,\n `full-access 派发: ${hub.config.allowFullAccessDispatch ? '已开启 ⚠️' : '已禁用'}`,\n `解密失败: ${String(status.decryptFailures)}`\n + `${status.decryptFailures > 0 ? ' — 密钥不匹配,请重新配对' : ''}`,\n `allowedRoots: ${hub.config.allowedRoots.length === 0\n ? '(空 — 远程派任务已禁用)'\n : hub.config.allowedRoots.join(', ')}`,\n ].join('\\n');\n}\n","/**\n * Remote dispatch — the M2 core. Starts agent sessions on request from the\n * phone, guards the working directory, and reports the final assistant text\n * when a dispatched turn closes.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { isAbsolute, resolve, sep } from 'node:path';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { installModelSelection } from '@deepseek-ai/dsh-agent';\nimport type { AgentHandle, ModelSelection } from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-agent-default-model';\n// Carries the `ctx.agentPresets` Context merge; the roster is optional at\n// runtime, so it is read with ctx.get rather than declared in `inject`.\nimport type {} from '@deepseek-ai/dsh-agent-presets';\n// Carries the `ctx.permissionPresets` Context merge.\nimport type {} from '@deepseek-ai/dsh-permission-presets';\nimport { createUserMessage } from '@deepseek-ai/dsh-llm';\nimport { SessionId } from '@deepseek-ai/dsh-session';\nimport type { SurfaceEvent } from '@deepseek-ai/dsh-session';\nimport {\n clampBytes,\n MAX_FIELD_BYTES,\n MAX_PROMPT_BYTES,\n MAX_PROMPT_CHARS,\n MAX_SUMMARY_CHARS,\n truncate,\n utf8Length,\n} from '@dsh-dispatch/shared';\nimport type { DispatchRequestMsg, DispatchResultMsg, SessionMessageMsg } from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\nimport { expandHome } from './pairing.js';\nimport { prepareWorkspace, WorktreeError } from './worktree.js';\n\n/** docs/PROTOCOL.md: LRU of seen request ids, for double-tap safety. */\nconst SEEN_LIMIT = 256;\n\nconst DISPATCH_DISABLED = 'dispatch 未启用:请在插件配置中设置 allowedRoots';\nconst NO_REMOTE_FOLLOWUP = '该会话不支持远程追加';\nconst FULL_ACCESS_DISABLED = 'full-access 派发未启用:请在插件配置中开启 allowFullAccessDispatch';\nconst FULL_ACCESS_UNAVAILABLE = 'full-access 不可用:本机 dsh 未组合 permission-presets 插件';\n/** dsh's shipped preset table key (permission-presets/src/index.ts:188). */\nconst FULL_ACCESS_PRESET = 'danger-full-access';\n\nexport interface DispatchRouter {\n request(message: DispatchRequestMsg): Promise<void>;\n followup(message: SessionMessageMsg): Promise<void>;\n dispatchedCount(): number;\n}\n\n/** Per-plugin dispatch state; kept in one object so the handlers stay flat. */\ninterface DispatchState {\n readonly handles: Map<string, AgentHandle>;\n readonly seen: ResultCache;\n readonly inflight: Map<string, Promise<DispatchResultMsg>>;\n loaderReady: Promise<void> | undefined;\n}\n\nexport function installDispatch(hub: Hub): DispatchRouter {\n const state: DispatchState = {\n handles: new Map(),\n seen: new ResultCache(),\n inflight: new Map(),\n loaderReady: undefined,\n };\n hub.ctx.on('agent/turn-stopping', ({ agent }) => {\n if (!hub.sessions.isDispatched(agent.id)) return;\n // Latch the card synchronously: the idle status that closes this turn\n // races the async surface read below and would otherwise land first.\n hub.sessions.markTurnComplete(agent.id);\n void reportTurnFinal(hub, agent.id).catch((error: unknown) => {\n hub.fail(`turn.final for ${agent.id}`, error);\n });\n });\n hub.ctx.on('agent/disposed', ({ agent }) => { state.handles.delete(agent.id); });\n return {\n request: message => handleRequest(hub, state, message),\n followup: message => handleFollowup(hub, state, message),\n dispatchedCount: () => state.handles.size,\n };\n}\n\n/** Answer exactly once per requestId, even under a double-tap. */\nasync function handleRequest(\n hub: Hub,\n state: DispatchState,\n message: DispatchRequestMsg,\n): Promise<void> {\n // Pure validation runs BEFORE the idempotency mark: malformed input can\n // never half-apply, so a retry deserves the same visible complaint rather\n // than a cached one.\n const rejected = validate(hub, message);\n if (rejected !== undefined) {\n hub.relay.send(failure(message.requestId, rejected));\n return;\n }\n const replay = state.seen.get(message.requestId);\n if (replay !== undefined) {\n if (replay !== null) hub.relay.send(replay);\n return;\n }\n const pending = state.inflight.get(message.requestId) ?? startSession(hub, state, message);\n state.inflight.set(message.requestId, pending);\n try {\n const result = await pending;\n state.seen.set(message.requestId, result);\n hub.relay.send(result);\n } finally {\n state.inflight.delete(message.requestId);\n }\n}\n\nasync function startSession(\n hub: Hub,\n state: DispatchState,\n message: DispatchRequestMsg,\n): Promise<DispatchResultMsg> {\n const prompt = message.prompt;\n const target = resolveCwd(hub, message.cwd);\n if (typeof target !== 'string') return failure(message.requestId, target.error);\n const full = message.access === 'full';\n try {\n const workspace = await prepareWorkspace({\n cwd: target,\n dataDir: hub.pairing.dataDir,\n worktree: message.worktree,\n });\n // Loader siblings mount concurrently; never create an agent into a\n // half-composed application (dossier §3.2).\n state.loaderReady ??= Promise.resolve(hub.ctx.get('loader')?.await()).then(() => undefined);\n await state.loaderReady;\n const created = await createSession(hub.ctx, workspace.cwd, prompt, full);\n state.handles.set(created.handle.agent.id, created.handle);\n // Mark access from the REQUEST, never gated behind the preset call that may\n // have thrown: an audit mark that vanishes on failure is worse than useless.\n hub.sessions.markDispatched(created.handle.agent.id, prompt, full ? 'full' : 'standard');\n if (created.fullAccessError !== undefined) {\n // The turn still started (fail-open), but the preset did not stick — say\n // so loudly rather than run at standard while the card claims full.\n hub.report(`dispatch ${message.requestId}`, `完全访问权限未能应用:${created.fullAccessError}`);\n }\n const result = ok(message.requestId, created.handle.agent.id);\n // `note` is advisory on a successful result; `error` never appears with\n // ok: true, so the phone can key its failure state on `ok` alone.\n const advisory = created.fullAccessError === undefined\n ? workspace.note\n : `完全访问权限未能应用,已在标准权限下运行:${created.fullAccessError}`;\n return advisory === undefined ? result : { ...result, note: advisory };\n } catch (error) {\n const detail = error instanceof WorktreeError ? error.message : String(error);\n hub.log.error('dispatch %s failed: %s', message.requestId, detail);\n return failure(message.requestId, detail);\n }\n}\n\n/**\n * Append a phone-typed message to a live session. Cold (persisted-only)\n * sessions are refused out loud rather than silently resumed.\n */\nasync function handleFollowup(\n hub: Hub,\n state: DispatchState,\n message: SessionMessageMsg,\n): Promise<void> {\n // Malformed input can never double-apply, so it is answered before the\n // idempotency mark: a retry deserves the same visible complaint.\n const oversized = overLimit('消息', message.text);\n if (oversized !== undefined) {\n hub.report(`session.message ${message.sessionId}`, oversized);\n return;\n }\n if (state.seen.get(message.requestId) !== undefined) return;\n state.seen.set(message.requestId, null);\n const agent = state.handles.get(message.sessionId)?.agent\n ?? hub.sessions.agentOf(message.sessionId);\n if (agent === undefined) {\n hub.report(`session.message ${message.sessionId}`, NO_REMOTE_FOLLOWUP);\n return;\n }\n agent.followup(createUserMessage({\n content: [{ type: 'text', text: message.text }],\n source: { kind: 'user' },\n }));\n}\n\n/**\n * Reject over-limit inbound text instead of clamping it. A well-behaved phone\n * clamps client-side (docs/PROTOCOL.md), so an oversized prompt is malformed\n * input — silently trimming it would run a task the user never wrote.\n * @returns the visible complaint, or undefined when the text is within limits.\n */\nfunction overLimit(field: string, text: string): string | undefined {\n const tail = '请在手机端缩短后重发。';\n if (text.length > MAX_PROMPT_CHARS) {\n return `${field} 超长:${String(text.length)} 字符,上限 ${String(MAX_PROMPT_CHARS)} 字符。${tail}`;\n }\n const bytes = utf8Length(text);\n if (bytes > MAX_PROMPT_BYTES) {\n return `${field} 超长:${String(bytes)} 字节(UTF-8),上限 ${String(MAX_PROMPT_BYTES)} 字节。${tail}`;\n }\n return undefined;\n}\n\n/**\n * Compose the agent's model-facing world: the model selection, and — when the\n * deployment runs a preset roster — the preset that carries its tools.\n *\n * Mirrors `composeAgent()` in packages/host/apiproxy/src/api-proxy.ts:1168,\n * which is what a user-created web session goes through. Skipping the mount is\n * not a SMALLER toolset, it is NO toolset: the web bundle disables every\n * model-facing row in the host plane (`tool-bash`, `tool-fs`, `tool-todo`, …\n * all `disabled: true` in packages/bundle/web-app/cordis.patch.yml) and moves\n * them into each preset's own scope layer, so an agent that joins no preset\n * reaches the model with an empty tool registry and no persona sections.\n */\nasync function composeAgent(ctx: Context, selection: ModelSelection | undefined): Promise<{\n agentPreset?: string;\n setup: (agentCtx: Context) => Promise<void>;\n}> {\n const install = (agentCtx: Context): void => {\n if (selection === undefined) return;\n installModelSelection(agentCtx, { current: selection, assembled: undefined });\n };\n const presets = ctx.get('agentPresets');\n // No roster composed (the headless shape): model-facing rows sit in the host\n // plane and the agent reads them from the global layer.\n if (presets === undefined) {\n return { setup: (agentCtx: Context) => { install(agentCtx); return Promise.resolve(); } };\n }\n // Name no preset, exactly as when a web client creates a session without\n // picking one: the roster's configured default wins.\n const resolvedId = (await presets.resolve()).id;\n return {\n agentPreset: resolvedId,\n setup: async (agentCtx: Context) => {\n install(agentCtx);\n await presets.mount(agentCtx, resolvedId);\n },\n };\n}\n\n/**\n * Everything we can refuse without touching the filesystem or the registry.\n * @returns the visible complaint, or undefined when the request is well formed.\n */\nfunction validate(hub: Hub, message: DispatchRequestMsg): string | undefined {\n const oversized = overLimit('prompt', message.prompt);\n if (oversized !== undefined) return oversized;\n if (message.access !== undefined && message.access !== 'standard' && message.access !== 'full') {\n return `access 取值无效:${String(message.access)}`;\n }\n if (message.access !== 'full') return undefined;\n if (!hub.config.allowFullAccessDispatch) return FULL_ACCESS_DISABLED;\n // Refusing here beats running the task at standard permissions while the\n // phone believes it asked for full access.\n if (hub.ctx.get('permissionPresets') === undefined) return FULL_ACCESS_UNAVAILABLE;\n return undefined;\n}\n\n/** Outcome of one session creation: the handle, and any full-access snag. */\ninterface CreatedSession {\n readonly handle: AgentHandle;\n /** Set when the danger-full-access preset could not be applied. */\n readonly fullAccessError?: string;\n}\n\n/** Create a session in `cwd` with the same composition a web session gets. */\nasync function createSession(\n ctx: Context,\n cwd: string,\n prompt: string,\n fullAccess: boolean,\n): Promise<CreatedSession> {\n const selection = ctx.get('agentDefaultModel')?.currentSelection();\n const composition = await composeAgent(ctx, selection);\n const handle = await ctx.agents.create({\n sessionId: SessionId(`session-${randomUUID()}`),\n // The preset is recorded on the header so a later cold resume rebuilds the\n // composition this session's history was actually produced under.\n meta: composition.agentPreset === undefined\n ? { cwd }\n : { cwd, agentPreset: composition.agentPreset },\n agentOptions: selection === undefined\n ? undefined\n : { provider: selection.provider, model: selection.model },\n setup: composition.setup,\n });\n await handle.agent.whenIdle();\n // Apply the tier BEFORE the first turn — but never let it skip the followup:\n // a thrown set() previously wedged the session with only setup events and no\n // turn (the acceptance-test repro). Fail-open on the turn.\n const fullAccessError = fullAccess ? applyFullAccess(ctx, handle) : undefined;\n handle.agent.followup(createUserMessage({\n content: [{ type: 'text', text: prompt }],\n source: { kind: 'user' },\n }));\n return fullAccessError === undefined ? { handle } : { handle, fullAccessError };\n}\n\n/**\n * Switch a freshly created session to danger-full-access.\n *\n * Resolved via `ctx.get()`, NOT the `ctx.permissionPresets` property proxy: an\n * un-injected service read through the proxy from this nested fiber throws\n * `cannot get property \"permissionPresets\" without inject`\n * (vendor/cordis/src/reflect.ts:144), while `get()` reads the store directly\n * (reflect.ts:233). `set()` then appends the durable `permission/preset` event\n * and drives the sandbox + approval knobs\n * (packages/interaction/permission-presets/src/index.ts:391).\n * @returns undefined on success, or a message describing why it could not apply.\n */\nfunction applyFullAccess(ctx: Context, handle: AgentHandle): string | undefined {\n try {\n const presets = ctx.get('permissionPresets');\n if (presets === undefined) return 'permission-presets 插件未组合';\n presets.set(handle.agent.session, FULL_ACCESS_PRESET);\n return undefined;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n\n/**\n * Validate the requested cwd against the configured roots.\n * @returns the absolute directory, or the visible failure to send back.\n */\nfunction resolveCwd(hub: Hub, requested: string | undefined): string | { error: string } {\n const roots = hub.config.allowedRoots.map(root => resolve(expandHome(root)));\n if (roots.length === 0) return { error: DISPATCH_DISABLED };\n const first = roots[0];\n if (requested === undefined) return first === undefined ? { error: DISPATCH_DISABLED } : first;\n const target = resolve(isAbsolute(requested) ? requested : expandHome(requested));\n const inside = roots.some(root => target === root || target.startsWith(root + sep));\n return inside ? target : { error: `cwd 不在允许目录内:${target}` };\n}\n\nasync function reportTurnFinal(hub: Hub, sessionId: string): Promise<void> {\n const snapshot = await hub.ctx.sessionQuery.readSurface(SessionId(sessionId));\n const ok = !hub.sessions.hasError(sessionId);\n const summary = clampBytes(\n truncate(lastAssistantText(snapshot.events), MAX_SUMMARY_CHARS),\n MAX_FIELD_BYTES,\n );\n hub.relay.send(\n { v: 1, ts: Date.now(), type: 'turn.final', sessionId, ok, summary },\n {\n v: 1,\n ts: Date.now(),\n type: 'push',\n kind: ok ? 'done' : 'error',\n sessionId,\n title: truncate(summary.split('\\n', 1)[0] ?? '', 120),\n },\n );\n}\n\nfunction lastAssistantText(events: readonly SurfaceEvent[]): string {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n if (event?.type !== 'assistant/message') continue;\n const text = event.data.message.content\n .filter(block => block.type === 'text')\n .map(block => block.text)\n .join('')\n .trim();\n if (text !== '') return text;\n }\n return '(本轮没有产生助手文本)';\n}\n\nfunction ok(requestId: string, sessionId: string): DispatchResultMsg {\n return { v: 1, ts: Date.now(), type: 'dispatch.result', requestId, ok: true, sessionId };\n}\n\nfunction failure(requestId: string, error: string): DispatchResultMsg {\n return { v: 1, ts: Date.now(), type: 'dispatch.result', requestId, ok: false, error };\n}\n\n/** Bounded replay cache. `null` marks a request that succeeded with no reply frame. */\nclass ResultCache {\n readonly #entries = new Map<string, DispatchResultMsg | null>();\n\n get(requestId: string): DispatchResultMsg | null | undefined {\n return this.#entries.get(requestId);\n }\n\n set(requestId: string, value: DispatchResultMsg | null): void {\n this.#entries.delete(requestId);\n this.#entries.set(requestId, value);\n while (this.#entries.size > SEEN_LIMIT) {\n const oldest = this.#entries.keys().next();\n if (oldest.done === true) break;\n this.#entries.delete(oldest.value);\n }\n }\n}\n","/**\n * Pairing secret lifecycle: the 32 random bytes that are the ONLY thing\n * standing between a stranger and control of this machine. Stored at\n * `<dataDir>/secret` with mode 0600 and never sent to the relay.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { encodePairing, fromBase64, generateSecret, toBase64 } from '@dsh-dispatch/shared';\nimport type { Log } from './log.js';\n\nconst SECRET_FILE = 'secret';\nconst PAIRED_FILE = 'paired.json';\nconst SECRET_BYTES = 32;\n\n/** Expand a leading `~` so a configured `~/.dsh-dispatch` resolves to a real path. */\nexport function expandHome(input: string): string {\n if (input === '~') return homedir();\n if (input.startsWith('~/')) return join(homedir(), input.slice(2));\n return resolve(input);\n}\n\n/** Everything the plugin needs to prove a phone is allowed to talk to it. */\nexport class PairingStore {\n readonly dataDir: string;\n #secret: Uint8Array;\n #pairedAt: number | undefined;\n\n private constructor(dataDir: string, secret: Uint8Array, pairedAt: number | undefined) {\n this.dataDir = dataDir;\n this.#secret = secret;\n this.#pairedAt = pairedAt;\n }\n\n /** Load the stored secret, generating and persisting one on first start. */\n static open(dataDir: string, log: Log): PairingStore {\n const dir = expandHome(dataDir);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n const secretPath = join(dir, SECRET_FILE);\n let secret: Uint8Array | null = null;\n if (existsSync(secretPath)) {\n secret = fromBase64(readFileSync(secretPath, 'utf8').trim());\n if (secret === null || secret.length !== SECRET_BYTES) {\n throw new Error(\n `dsh-dispatch: ${secretPath} is not a valid pairing secret. `\n + 'Delete the file to generate a new one (this invalidates existing pairings).',\n );\n }\n } else {\n secret = generateSecret();\n writeSecret(secretPath, secret);\n }\n return new PairingStore(dir, secret, readPairedAt(join(dir, PAIRED_FILE), log));\n }\n\n get secret(): Uint8Array {\n return this.#secret;\n }\n\n /**\n * Whether a phone has ever completed a pairing with this machine. Approval\n * forwarding stays out of the way entirely until this is true, so an\n * unpaired machine pays zero added approval latency.\n */\n get everPaired(): boolean {\n return this.#pairedAt !== undefined;\n }\n\n get pairedAt(): number | undefined {\n return this.#pairedAt;\n }\n\n /** Record the first sighting of a phone in this room. Idempotent. */\n markPaired(): void {\n if (this.#pairedAt !== undefined) return;\n this.#pairedAt = Date.now();\n writeFileSync(join(this.dataDir, PAIRED_FILE), JSON.stringify({ pairedAt: this.#pairedAt }), {\n mode: 0o600,\n });\n }\n\n /** Replace the secret, invalidating every existing pairing. */\n regenerate(): Uint8Array {\n this.#secret = generateSecret();\n writeSecret(join(this.dataDir, SECRET_FILE), this.#secret);\n this.#pairedAt = undefined;\n rmSync(join(this.dataDir, PAIRED_FILE), { force: true });\n return this.#secret;\n }\n\n /** The out-of-band pairing payload: relay + secret + machine name. */\n pairingCode(relay: string, machine: string): string {\n return encodePairing({ relay, secret: this.#secret, machine });\n }\n}\n\nfunction writeSecret(path: string, secret: Uint8Array): void {\n writeFileSync(path, toBase64(secret), { mode: 0o600 });\n // writeFileSync only applies `mode` when it creates the file; a rewrite of an\n // existing loose-permission file would otherwise stay world-readable.\n chmodSync(path, 0o600);\n}\n\nfunction readPairedAt(path: string, log: Log): number | undefined {\n if (!existsSync(path)) return undefined;\n try {\n const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));\n const value = (parsed as { pairedAt?: unknown }).pairedAt;\n return typeof value === 'number' ? value : undefined;\n } catch (error) {\n // A corrupt marker only costs us the \"have we ever paired\" shortcut; the\n // next phone connection rewrites it. Loud, then treat as never paired.\n log.error('pairing: %s is unreadable, treating this machine as never paired: %s', path, error);\n return undefined;\n }\n}\n","/**\n * Git worktree preparation for dispatched tasks.\n *\n * v0 never deletes a worktree (docs/PRODUCT.md 防呆): cleanup is the user's\n * call, because guessing wrong destroys real work.\n */\n\nimport { execFile } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst run = promisify(execFile);\n\n/** docs/PROTOCOL.md: a failure result carries at most 500 chars of stderr. */\nconst STDERR_TAIL = 500;\n\nexport class WorktreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'WorktreeError';\n }\n}\n\nexport interface Workspace {\n /** Directory the agent will actually run in. */\n readonly cwd: string;\n /** Branch created for this task, absent when running in `cwd` directly. */\n readonly branch?: string;\n /** Human-readable degradation note the phone must show, when degraded. */\n readonly note?: string;\n}\n\nexport interface PrepareOptions {\n readonly cwd: string;\n readonly dataDir: string;\n readonly worktree: boolean;\n}\n\n/**\n * Resolve the directory a dispatched task runs in.\n * @throws WorktreeError when `git worktree add` fails; the message carries the stderr tail.\n */\nexport async function prepareWorkspace(options: PrepareOptions): Promise<Workspace> {\n if (!options.worktree) return { cwd: options.cwd };\n if (!await isGitRepo(options.cwd)) {\n return {\n cwd: options.cwd,\n note: `已降级:${options.cwd} 不是 git 仓库,任务直接在该目录运行(未创建 worktree)`,\n };\n }\n const root = join(options.dataDir, 'worktrees');\n try {\n return await addWorktree(options.cwd, root, freshSlug());\n } catch (error) {\n const stderr = stderrOf(error);\n if (!isCollision(stderr)) throw new WorktreeError(stderr);\n return await addWorktree(options.cwd, root, freshSlug()).catch((retry: unknown) => {\n throw new WorktreeError(stderrOf(retry));\n });\n }\n}\n\nasync function addWorktree(cwd: string, root: string, slug: string): Promise<Workspace> {\n const target = join(root, slug);\n const branch = `dsh-dispatch/${slug}`;\n await run('git', ['-C', cwd, 'worktree', 'add', target, '-b', branch]);\n return { cwd: target, branch };\n}\n\nasync function isGitRepo(cwd: string): Promise<boolean> {\n try {\n const { stdout } = await run('git', ['-C', cwd, 'rev-parse', '--is-inside-work-tree']);\n return stdout.trim() === 'true';\n } catch {\n // Not a repo, or git is missing. Both mean \"no worktree here\"; the caller\n // reports the degradation to the phone, so this is not a silent swallow.\n return false;\n }\n}\n\n/** `timestamp + short random` — sortable, collision-resistant, readable in `git worktree list`. */\nfunction freshSlug(): string {\n const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\\..+$/, '');\n return `${stamp}-${randomBytes(3).toString('hex')}`;\n}\n\nfunction isCollision(stderr: string): boolean {\n return /already exists|already used by worktree|already checked out/i.test(stderr);\n}\n\nfunction stderrOf(error: unknown): string {\n const raw = (error as { stderr?: unknown }).stderr;\n const text = typeof raw === 'string' && raw.trim() !== ''\n ? raw.trim()\n : error instanceof Error ? error.message : String(error);\n return text.length <= STDERR_TAIL ? text : text.slice(-STDERR_TAIL);\n}\n","/**\n * The shared runtime handle every feature module receives. Also the single\n * place a failure becomes visible: `fail()` logs on the desktop AND pushes an\n * `error` message to the phone, so no catch can quietly swallow anything.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\nimport { truncate } from '@dsh-dispatch/shared';\nimport type { Config } from './config.js';\nimport type { Log } from './log.js';\nimport type { PairingStore } from './pairing.js';\nimport type { RelayClient } from './relay-client.js';\nimport type { SessionTracker } from './sessions.js';\n\n/** Phone screens are small; a wall of stack trace helps nobody there. */\nconst ERROR_CHARS = 1_000;\n\nexport interface Hub {\n readonly ctx: Context;\n readonly config: Config;\n readonly log: Log;\n readonly relay: RelayClient;\n readonly sessions: SessionTracker;\n readonly pairing: PairingStore;\n /** Report a caught error on both surfaces. */\n fail(context: string, error: unknown): void;\n /** Report an already human-readable failure on both surfaces. */\n report(context: string, message: string): void;\n}\n\nexport function createHub(parts: Omit<Hub, 'fail' | 'report'>): Hub {\n const report = (context: string, message: string): void => {\n parts.log.error('%s: %s', context, message);\n parts.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'error',\n message: truncate(message, ERROR_CHARS),\n context,\n });\n };\n return {\n ...parts,\n report,\n fail(context: string, error: unknown): void {\n report(context, error instanceof Error ? error.message : String(error));\n },\n };\n}\n","/**\n * `ask_user_question` forwarding.\n *\n * Seam: the `tools/execute` around-dispatch waterfall, NOT `tools/pre-execute`.\n * `PreToolDecision` is only allow/deny/ask (packages/core/tools/src/index.ts:588)\n * and cannot carry a tool result, so it can never answer the question — while a\n * `tools/execute` listener returns a `ToolExecutionResult` directly\n * (index.ts:163) and its `next()` runs the real tool body, which is exactly the\n * approval race shape.\n *\n * Authoring only `value` is enough: `normalizeDispatchResult`\n * (packages/core/tools/src/index.ts:1401) pushes a wrapper's result back\n * through the owning tool's `output.schema` and `output.render`, so a\n * phone-answered call is indistinguishable from a locally answered one.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { ToolDispatchExecution, ToolExecutionResult } from '@deepseek-ai/dsh-tools';\nimport { clampBytes, truncate } from '@dsh-dispatch/shared';\nimport type {\n PushCompactMsg,\n QuestionAnswer,\n QuestionItem,\n QuestionOption,\n QuestionRequestMsg,\n QuestionRespondMsg,\n QuestionResolution,\n} from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\n\n/** Registered name of the tool this module answers for. */\nconst ASK_USER_QUESTION = 'ask_user_question';\n\nconst PROMPT_CHARS = 2_000;\nconst PROMPT_BYTES = 4_000;\nconst LABEL_CHARS = 300;\nconst LABEL_BYTES = 600;\nconst REMINDER_MS = 5 * 60_000;\nconst MAX_REMINDERS = 6;\n\n/** The dsh tool's argument shape (packages/interaction/tool-ask-user/src/index.ts). */\ninterface RawOption {\n label: string;\n description?: string;\n}\ninterface RawQuestion {\n id: string;\n question: string;\n header?: string;\n options?: RawOption[];\n multi_select?: boolean;\n}\n\n/**\n * The tool's canonical return value; `selected` holds option LABELS.\n * A type alias, not an interface, so it satisfies `JsonValue`'s index\n * signature when handed back as a `ToolExecutionSuccess.value`.\n */\ntype AnswerValue = {\n answers: { id: string; selected: string[]; custom?: string }[];\n};\n\ntype Settle = (result: ToolExecutionResult, resolution: QuestionResolution) => void;\n\ninterface Forwarded {\n readonly questionId: string;\n readonly sessionId: string;\n readonly frame: QuestionRequestMsg;\n readonly push: PushCompactMsg;\n /** itemId → wire optionId → the exact dsh label to echo back. */\n readonly labels: Map<string, Map<string, string>>;\n}\n\ninterface Pending extends Forwarded {\n readonly settle: Settle;\n reminders: number;\n timer: ReturnType<typeof setTimeout> | undefined;\n}\n\nexport interface QuestionRouter {\n respond(message: QuestionRespondMsg): void;\n resendPending(): void;\n openCount(): number;\n}\n\nexport function installQuestions(hub: Hub): QuestionRouter {\n const pending = new Map<string, Pending>();\n\n hub.ctx.on('tools/execute', (exec, next) => {\n if (exec.name !== ASK_USER_QUESTION || exec.agent === undefined) return next();\n if (!hub.pairing.everPaired || !hub.relay.connected) return next();\n if (exec.signal.aborted) return next();\n return forward(hub, pending, exec, next);\n });\n\n return {\n respond(message: QuestionRespondMsg): void {\n const entry = pending.get(message.questionId);\n if (entry === undefined) {\n hub.relay.send(closedMsg(message.questionId, 'expired'));\n return;\n }\n const value = toAnswerValue(entry, message.answers);\n if (typeof value === 'string') {\n // Malformed input: the question stays OPEN so a corrected answer can\n // still land, and the phone is told exactly what was wrong.\n hub.report(`question ${entry.questionId}`, value);\n return;\n }\n entry.settle(successResult(value), 'phone');\n },\n resendPending(): void {\n for (const entry of pending.values()) hub.relay.send(entry.frame, entry.push);\n },\n openCount: () => pending.size,\n };\n}\n\nfunction forward(\n hub: Hub,\n pending: Map<string, Pending>,\n exec: ToolDispatchExecution,\n next: () => Promise<ToolExecutionResult>,\n): Promise<ToolExecutionResult> {\n const normalized = normalize(exec.arguments);\n if (normalized === null) {\n // Not a shape we can represent: let the real tool run and complain, rather\n // than answering a question we did not understand.\n hub.report('question', 'ask_user_question 参数无法解析,已交回本机处理');\n return next();\n }\n const questionId = randomUUID();\n const sessionId = exec.agent?.id ?? '';\n const frame: QuestionRequestMsg = {\n v: 1, ts: Date.now(), type: 'question.request',\n questionId, sessionId, items: normalized.items, createdAt: Date.now(),\n };\n const push: PushCompactMsg = {\n v: 1, ts: Date.now(), type: 'push', kind: 'question', sessionId,\n title: normalized.items[0]?.prompt.slice(0, 120) ?? '需要你回答一个问题',\n };\n if (!hub.relay.send(frame, push)) {\n hub.report('question', '提问未能推送到手机(relay 未连接或内容过大),已交回本机处理');\n return next();\n }\n const forwarded: Forwarded = { questionId, sessionId, frame, push, labels: normalized.labels };\n return race(hub, pending, forwarded, exec, next);\n}\n\nfunction race(\n hub: Hub,\n pending: Map<string, Pending>,\n forwarded: Forwarded,\n exec: ToolDispatchExecution,\n next: () => Promise<ToolExecutionResult>,\n): Promise<ToolExecutionResult> {\n const { questionId } = forwarded;\n return new Promise<ToolExecutionResult>((resolve) => {\n let settled = false;\n let localFailure: ToolExecutionResult | undefined;\n const settle: Settle = (result, resolution) => {\n if (settled) return;\n settled = true;\n const entry = pending.get(questionId);\n if (entry?.timer !== undefined) clearTimeout(entry.timer);\n pending.delete(questionId);\n exec.signal.removeEventListener('abort', onAbort);\n hub.relay.send(closedMsg(questionId, resolution));\n resolve(result);\n };\n function onAbort(): void {\n settle(localFailure ?? abortedResult(), 'cancelled');\n }\n exec.signal.addEventListener('abort', onAbort, { once: true });\n const entry: Pending = { ...forwarded, settle, reminders: 0, timer: undefined };\n pending.set(questionId, entry);\n armReminder(hub, entry);\n // Only a SUCCESS counts as a local answer. The tool body never rejects —\n // it returns an error result (packages/core/tools/src/index.ts:1554) — so a\n // missing provider (NO_PROVIDER, upstream #2544) or an abort lands here as\n // isError, and the phone stays the live answerer.\n void next().then((result) => {\n if (!result.isError) settle(result, 'local');\n else localFailure = result;\n }, (error: unknown) => {\n hub.fail(`question ${questionId}: local provider failed`, error);\n });\n });\n}\n\n/** Never auto-answer; nudge, then go quiet while staying open. */\nfunction armReminder(hub: Hub, entry: Pending): void {\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.reminders += 1;\n hub.relay.send(entry.frame, entry.push);\n if (entry.reminders >= MAX_REMINDERS) {\n hub.log.warn(\n 'question %s still unanswered after %d reminders; it stays open (never auto-answered)',\n entry.questionId, entry.reminders,\n );\n return;\n }\n armReminder(hub, entry);\n }, REMINDER_MS);\n entry.timer.unref?.();\n}\n\n/**\n * Project the tool's arguments onto the wire form.\n *\n * `header` and an option's `description` have no wire slot, so they are folded\n * into the text a human reads rather than dropped — a visible degradation, per\n * docs/PRODUCT.md 失败可见.\n */\nfunction normalize(args: unknown): { items: QuestionItem[]; labels: Forwarded['labels'] } | null {\n const questions = (args as { questions?: unknown } | null)?.questions;\n if (!Array.isArray(questions) || questions.length === 0) return null;\n const items: QuestionItem[] = [];\n const labels: Forwarded['labels'] = new Map();\n for (const raw of questions as RawQuestion[]) {\n if (typeof raw?.id !== 'string' || typeof raw.question !== 'string') return null;\n const options: QuestionOption[] = [];\n const byId = new Map<string, string>();\n (raw.options ?? []).forEach((option, index) => {\n if (typeof option?.label !== 'string') return;\n const id = `o${String(index)}`;\n byId.set(id, option.label);\n options.push({ id, label: clamp(decorate(option.label, option.description), LABEL_CHARS, LABEL_BYTES) });\n });\n labels.set(raw.id, byId);\n items.push({\n id: raw.id,\n prompt: clamp(prefix(raw.header, raw.question), PROMPT_CHARS, PROMPT_BYTES),\n options,\n multiSelect: raw.multi_select === true,\n // The tool's output schema always permits `custom`, so free text is\n // always a valid answer — and the only answer when there are no options.\n allowFreeText: true,\n });\n }\n return items.length === 0 ? null : { items, labels };\n}\n\nconst decorate = (label: string, description?: string): string =>\n description === undefined || description === '' ? label : `${label} — ${description}`;\n\nconst prefix = (header: string | undefined, question: string): string =>\n header === undefined || header === '' ? question : `${header}\\n\\n${question}`;\n\nconst clamp = (text: string, chars: number, bytes: number): string =>\n clampBytes(truncate(text, chars), bytes);\n\n/**\n * Validate a phone answer and project it onto the tool's canonical value.\n * `selected` carries option LABELS, not ids\n * (packages/interaction/user-questions/src/types.ts, AskUserQuestionAnswerItem).\n * @returns the value to return from the tool, or a human-readable complaint.\n */\nfunction toAnswerValue(entry: Forwarded, answers: readonly QuestionAnswer[]): AnswerValue | string {\n if (!Array.isArray(answers)) return '回答格式无效:answers 不是数组';\n const seen = new Set<string>();\n const projected: AnswerValue['answers'] = [];\n for (const answer of answers) {\n const options = entry.labels.get(answer.itemId);\n if (options === undefined) return `回答引用了未知的问题 id:${String(answer.itemId)}`;\n if (seen.has(answer.itemId)) return `问题 ${answer.itemId} 被回答了多次`;\n seen.add(answer.itemId);\n const selected: string[] = [];\n for (const optionId of answer.optionIds ?? []) {\n const label = options.get(optionId);\n if (label === undefined) return `问题 ${answer.itemId} 的选项 id 未知:${String(optionId)}`;\n selected.push(label);\n }\n const item = entry.frame.items.find(candidate => candidate.id === answer.itemId);\n if (item !== undefined && !item.multiSelect && selected.length > 1) {\n return `问题 ${answer.itemId} 是单选,却收到 ${String(selected.length)} 个选项`;\n }\n const custom = answer.freeText;\n if (selected.length === 0 && (custom === undefined || custom === '')) {\n return `问题 ${answer.itemId} 既没有选项也没有文字回答`;\n }\n projected.push({ id: answer.itemId, selected, ...custom === undefined || custom === '' ? {} : { custom } });\n }\n const missing = entry.frame.items.filter(item => !seen.has(item.id)).map(item => item.id);\n if (missing.length > 0) return `还有问题未回答:${missing.join(', ')}`;\n return { answers: projected };\n}\n\n/**\n * `content` here is a placeholder: the registry re-renders it through the\n * tool's own `output.render` in `normalizeDispatchResult`.\n */\nfunction successResult(value: AnswerValue): ToolExecutionResult {\n return { isError: false, value, content: [{ type: 'text', text: JSON.stringify(value) }] };\n}\n\n/** Same shape as the registry's own `toolErrorResult` (tools/src/index.ts:1870). */\nfunction abortedResult(): ToolExecutionResult {\n const message = 'ask_user_question was cancelled before the user answered';\n return { isError: true, error: { message }, content: [{ type: 'text', text: `Error: ${message}` }] };\n}\n\nfunction closedMsg(questionId: string, resolution: QuestionResolution) {\n return { v: 1, ts: Date.now(), type: 'question.closed', questionId, resolution } as const;\n}\n","/**\n * Outbound wss client to the dsh-dispatch relay. Uses Node's global\n * `WebSocket` (Node >= 22), so the plugin adds no transport dependency.\n *\n * Everything crossing this boundary is sealed with the pairing key: the relay\n * routes ciphertext and learns nothing but room id, role and message size.\n */\n\nimport {\n deriveKey,\n deriveRoomId,\n MAX_ENVELOPE_BYTES,\n open,\n seal,\n} from '@dsh-dispatch/shared';\nimport type {\n MachineToPhoneMsg,\n MsgFrame,\n PhoneToMachineMsg,\n PushCompactMsg,\n} from '@dsh-dispatch/shared';\nimport type { Log } from './log.js';\n\nconst BACKOFF_BASE_MS = 500;\nconst BACKOFF_MAX_MS = 30_000;\n/** Consecutive connect failures after which we surface a visible error state. */\nconst GIVE_UP_AFTER = 10;\n/** Retry interval once we have surfaced the error; we never stop trying. */\nconst GIVE_UP_RETRY_MS = 60_000;\n/** Decryption failures inside this window collapse into one log line. */\nconst TAMPER_LOG_WINDOW_MS = 30_000;\n\n/** Everything `/dispatch-status` needs to describe the link in one line. */\nexport interface RelayStatus {\n readonly connected: boolean;\n readonly phoneOnline: boolean;\n readonly consecutiveFailures: number;\n readonly gaveUp: boolean;\n readonly room: string;\n readonly decryptFailures: number;\n}\n\nexport interface RelayClientOptions {\n readonly url: string;\n readonly secret: Uint8Array;\n readonly log: Log;\n /** A validated inner message arrived from the phone. */\n onMessage(message: PhoneToMachineMsg): void;\n /** The relay accepted our hello — time to publish status and a snapshot. */\n onConnected(): void;\n /** A phone joined or left this room. */\n onPhonePresence(online: boolean): void;\n}\n\nexport class RelayClient {\n readonly #options: RelayClientOptions;\n #key: Uint8Array;\n #room: string;\n #socket: WebSocket | undefined;\n #retry: ReturnType<typeof setTimeout> | undefined;\n #stopped = true;\n #connected = false;\n #phoneOnline = false;\n #failures = 0;\n #gaveUp = false;\n #tamperBurst = 0;\n #tamperWindowStart = 0;\n #tamperTotal = 0;\n\n constructor(options: RelayClientOptions) {\n this.#options = options;\n this.#key = deriveKey(options.secret);\n this.#room = deriveRoomId(options.secret);\n }\n\n get connected(): boolean {\n return this.#connected;\n }\n\n get phoneOnline(): boolean {\n return this.#phoneOnline;\n }\n\n status(): RelayStatus {\n return {\n connected: this.#connected,\n phoneOnline: this.#phoneOnline,\n consecutiveFailures: this.#failures,\n gaveUp: this.#gaveUp,\n room: this.#room,\n decryptFailures: this.#tamperTotal,\n };\n }\n\n start(): void {\n if (!this.#stopped) return;\n this.#stopped = false;\n this.#connect();\n }\n\n stop(): void {\n this.#stopped = true;\n if (this.#retry !== undefined) clearTimeout(this.#retry);\n this.#retry = undefined;\n this.#teardownSocket();\n this.#connected = false;\n this.#phoneOnline = false;\n }\n\n /** Adopt a freshly generated secret: new room, new key, new connection. */\n rekey(secret: Uint8Array): void {\n this.#key = deriveKey(secret);\n this.#room = deriveRoomId(secret);\n this.#failures = 0;\n this.#gaveUp = false;\n if (this.#stopped) return;\n this.stop();\n this.start();\n }\n\n /**\n * Seal and send one inner message. `push` rides along so the relay can wake\n * an offline phone through Web Push without ever seeing the plaintext.\n * @returns whether the frame reached the socket.\n */\n send(message: MachineToPhoneMsg, push?: PushCompactMsg): boolean {\n const socket = this.#socket;\n if (socket === undefined || !this.#connected) return false;\n const frame: MsgFrame = {\n kind: 'msg',\n room: this.#room,\n payload: seal(message, this.#key),\n push: push === undefined ? null : { payload: seal(push, this.#key), tag: push.kind },\n };\n const text = JSON.stringify(frame);\n if (text.length > MAX_ENVELOPE_BYTES) {\n this.#options.log.error(\n 'relay: refusing to send an oversized %s envelope (%d bytes > %d); this is a truncation bug',\n message.type, text.length, MAX_ENVELOPE_BYTES,\n );\n return false;\n }\n socket.send(text);\n return true;\n }\n\n #connect(): void {\n if (this.#stopped) return;\n let socket: WebSocket;\n try {\n socket = new WebSocket(this.#options.url);\n } catch (error) {\n this.#options.log.error('relay: cannot open %s: %s', this.#options.url, error);\n this.#scheduleRetry();\n return;\n }\n this.#socket = socket;\n socket.addEventListener('open', () => {\n socket.send(JSON.stringify({ kind: 'hello', room: this.#room, role: 'machine' }));\n });\n socket.addEventListener('message', (event: MessageEvent) => {\n this.#onFrame(event.data);\n });\n socket.addEventListener('error', () => {\n // 'close' always follows; the event itself carries no useful detail.\n });\n socket.addEventListener('close', (event) => {\n if (this.#socket !== socket) return;\n const code = (event as unknown as { code?: number }).code;\n this.#onDown(`socket closed (${String(code ?? 'no code')})`);\n });\n }\n\n #onFrame(data: unknown): void {\n if (typeof data !== 'string') return;\n let frame: unknown;\n try {\n frame = JSON.parse(data);\n } catch (error) {\n this.#options.log.error('relay: dropped an unparseable frame: %s', error);\n return;\n }\n const kind = (frame as { kind?: unknown }).kind;\n if (kind === 'hello-ok') this.#onHelloOk(frame);\n else if (kind === 'presence') this.#onPresence(frame);\n else if (kind === 'msg') this.#onData(frame);\n else if (kind === 'error') {\n const code = String((frame as { code?: unknown }).code ?? 'unknown');\n this.#options.log.error('relay: rejected our frame with code \"%s\"', code);\n } else {\n this.#options.log.warn('relay: ignoring unknown frame kind \"%s\"', String(kind));\n }\n }\n\n #onHelloOk(frame: unknown): void {\n this.#connected = true;\n this.#failures = 0;\n this.#gaveUp = false;\n const peers = (frame as { peers?: { phone?: unknown } }).peers;\n const phones = typeof peers?.phone === 'number' ? peers.phone : 0;\n this.#options.log.info('relay: connected (room %s…, phones online: %d)', this.#room.slice(0, 8), phones);\n this.#options.onConnected();\n if (phones > 0) this.#setPhoneOnline(true);\n }\n\n #onPresence(frame: unknown): void {\n const { role, online } = frame as { role?: unknown; online?: unknown };\n if (role !== 'phone' || typeof online !== 'boolean') return;\n this.#setPhoneOnline(online);\n }\n\n #setPhoneOnline(online: boolean): void {\n if (this.#phoneOnline === online) return;\n this.#phoneOnline = online;\n this.#options.onPhonePresence(online);\n }\n\n #onData(frame: unknown): void {\n const payload = (frame as { payload?: unknown }).payload;\n if (typeof payload !== 'string') {\n this.#options.log.error('relay: dropped a msg frame with no payload');\n return;\n }\n const plain = open(payload, this.#key);\n if (plain === null) {\n this.#onTamper();\n return;\n }\n const message = parseInbound(plain);\n if (message === null) {\n this.#options.log.warn(\n 'relay: ignoring inner message of type \"%s\" — unknown type or missing/invalid fields '\n + '(the phone may be newer than this plugin)',\n String((plain as { type?: unknown }).type),\n );\n return;\n }\n this.#options.onMessage(message);\n }\n\n /** Decryption failure is never silent: it means tampering or a stale pairing. */\n #onTamper(): void {\n this.#tamperTotal += 1;\n this.#tamperBurst += 1;\n const now = Date.now();\n if (now - this.#tamperWindowStart < TAMPER_LOG_WINDOW_MS) return;\n this.#tamperWindowStart = now;\n this.#options.log.error(\n 'relay: %d message(s) failed to decrypt — wrong key or tampering. Re-pair with /dispatch-repair '\n + 'and re-scan the QR on every phone.',\n this.#tamperBurst,\n );\n this.#tamperBurst = 0;\n }\n\n #onDown(reason: string): void {\n this.#teardownSocket();\n if (this.#stopped) return;\n this.#connected = false;\n this.#setPhoneOnline(false);\n this.#failures += 1;\n this.#options.log.debug('relay: %s, attempt %d', reason, this.#failures);\n this.#scheduleRetry();\n }\n\n #scheduleRetry(): void {\n if (this.#stopped || this.#retry !== undefined) return;\n if (this.#failures >= GIVE_UP_AFTER && !this.#gaveUp) {\n this.#gaveUp = true;\n this.#options.log.error(\n 'relay: %d consecutive failures connecting to %s — check that the relay is reachable. '\n + 'Still retrying every %ds.',\n this.#failures, this.#options.url, GIVE_UP_RETRY_MS / 1000,\n );\n }\n this.#retry = setTimeout(() => {\n this.#retry = undefined;\n this.#connect();\n }, this.#backoffMs());\n this.#retry.unref?.();\n }\n\n #backoffMs(): number {\n if (this.#gaveUp) return GIVE_UP_RETRY_MS;\n const exponential = Math.min(BACKOFF_BASE_MS * 2 ** this.#failures, BACKOFF_MAX_MS);\n return Math.round(exponential * (0.75 + Math.random() * 0.5));\n }\n\n #teardownSocket(): void {\n const socket = this.#socket;\n this.#socket = undefined;\n if (socket === undefined) return;\n try {\n socket.close();\n } catch (error) {\n this.#options.log.debug('relay: error closing socket: %s', error);\n }\n }\n}\n\nconst INBOUND_FIELDS: Record<PhoneToMachineMsg['type'], readonly string[]> = {\n 'sessions.get': [],\n 'approval.respond': ['requestId', 'approvalId', 'decision'],\n 'question.respond': ['requestId', 'questionId'],\n 'dispatch.request': ['requestId', 'prompt'],\n 'session.message': ['requestId', 'sessionId', 'text'],\n};\n\n/**\n * Accept only inner messages this plugin understands, with their required\n * string fields present. Unknown `type`/`v` returns null so the caller logs it\n * (forward compatibility per docs/PROTOCOL.md) rather than crashing.\n */\nexport function parseInbound(value: unknown): PhoneToMachineMsg | null {\n if (typeof value !== 'object' || value === null) return null;\n const record = value as Record<string, unknown>;\n if (record['v'] !== 1) return null;\n const type = record['type'];\n if (typeof type !== 'string' || !(type in INBOUND_FIELDS)) return null;\n const required = INBOUND_FIELDS[type as PhoneToMachineMsg['type']];\n for (const field of required) {\n if (typeof record[field] !== 'string' || record[field] === '') return null;\n }\n if (type === 'approval.respond' && record['decision'] !== 'allow' && record['decision'] !== 'deny') {\n return null;\n }\n if (type === 'dispatch.request' && typeof record['worktree'] !== 'boolean') return null;\n // Shape only — an unknown `access` value is a protocol-level complaint the\n // dispatch handler answers visibly, not something to drop on the floor here.\n if (type === 'question.respond' && !isAnswerList(record['answers'])) return null;\n return record as unknown as PhoneToMachineMsg;\n}\n\n/** `answers: [{ itemId, optionIds, freeText? }]`, checked structurally. */\nfunction isAnswerList(value: unknown): boolean {\n if (!Array.isArray(value) || value.length === 0) return false;\n return value.every((entry: unknown) => {\n const answer = entry as { itemId?: unknown; optionIds?: unknown; freeText?: unknown };\n if (typeof answer?.itemId !== 'string') return false;\n if (!Array.isArray(answer.optionIds)) return false;\n if (!answer.optionIds.every(id => typeof id === 'string')) return false;\n return answer.freeText === undefined || typeof answer.freeText === 'string';\n });\n}\n","/**\n * Live session board: maps dsh agent lifecycle events onto the protocol's\n * `Session` objects and pushes `session.update` / `session.snapshot` to the\n * phone.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { Agent } from '@deepseek-ai/dsh-agent';\nimport { SessionId } from '@deepseek-ai/dsh-session';\nimport type {} from '@deepseek-ai/dsh-session-query';\nimport { truncate } from '@dsh-dispatch/shared';\nimport type { Session as WireSession, SessionState } from '@dsh-dispatch/shared';\n\n/** Permission tier as it appears on the wire. */\ntype SessionAccess = NonNullable<WireSession['access']>;\nimport type { Log } from './log.js';\nimport type { RelayClient } from './relay-client.js';\n\n/** docs/PROTOCOL.md: `title` = first 80 chars of the initial prompt. */\nconst TITLE_CHARS = 80;\n\n/** Lifecycle state before the approval overlay is applied. */\ntype BaseState = 'idle' | 'running' | 'done' | 'error';\n\ninterface Tracked {\n sessionId: string;\n title: string;\n cwd: string;\n base: BaseState;\n openApprovals: number;\n lastActivity: number;\n dispatched: boolean;\n /** Permission tier, for audit. Once 'full' it is never downgraded. */\n access: SessionAccess;\n /**\n * A dispatched session whose `turn.final` has been sent. It stays `done` on\n * the phone until a NEW turn starts — the idle status that follows every\n * turn must not demote the card back to 空闲.\n */\n finalized: boolean;\n}\n\nexport class SessionTracker {\n readonly #ctx: Context;\n readonly #relay: RelayClient;\n readonly #log: Log;\n readonly #tracked = new Map<string, Tracked>();\n readonly #published = new Map<string, string>();\n\n constructor(ctx: Context, relay: RelayClient, log: Log) {\n this.#ctx = ctx;\n this.#relay = relay;\n this.#log = log;\n }\n\n /** Subscribe the agent lifecycle. Registrations unwind with the plugin. */\n install(): void {\n this.#ctx.on('agent/created', ({ agent }) => {\n this.#adopt(agent);\n this.#publish(agent.id);\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/session-start', ({ agent }) => {\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/status', ({ agent, status }) => {\n this.#update(agent.id, (entry) => {\n // The trailing idle of a finished turn is not new information; only a\n // fresh `running` reopens a finalized session.\n if (status === 'idle' && entry.finalized) return;\n entry.finalized = false;\n entry.base = status;\n });\n });\n this.#ctx.on('agent/error', ({ agent, error }) => {\n this.#log.error('session %s: agent error: %s', agent.id, error);\n this.#update(agent.id, entry => { entry.base = 'error'; });\n });\n this.#ctx.on('agent/turn-stopping', ({ agent }) => {\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/disposed', ({ agent }) => {\n this.#update(agent.id, entry => { entry.base = 'done'; });\n this.#tracked.delete(agent.id);\n this.#published.delete(agent.id);\n });\n }\n\n /** Mark a session as started by us, with the dispatch prompt as its title. */\n markDispatched(sessionId: string, prompt: string, access: SessionAccess = 'standard'): void {\n this.#update(sessionId, (entry) => {\n entry.dispatched = true;\n entry.title = truncate(prompt.trim().split('\\n', 1)[0] ?? '', TITLE_CHARS);\n // Full access is an audit fact: recorded permanently, never downgraded.\n if (access === 'full') entry.access = 'full';\n });\n }\n\n isDispatched(sessionId: string): boolean {\n return this.#tracked.get(sessionId)?.dispatched ?? false;\n }\n\n /**\n * A dispatched turn has closed and its `turn.final` is on its way. Latch the\n * card at done/error so the trailing idle status cannot demote it.\n */\n markTurnComplete(sessionId: string): void {\n this.#update(sessionId, (entry) => {\n entry.finalized = true;\n if (entry.base !== 'error') entry.base = 'done';\n });\n }\n\n hasError(sessionId: string): boolean {\n return this.#tracked.get(sessionId)?.base === 'error';\n }\n\n /** The live agent for a session id, or undefined when it is not live. */\n agentOf(sessionId: string): Agent | undefined {\n return this.#ctx.agents.get(SessionId(sessionId));\n }\n\n /** An approval for this session is now waiting on a human. */\n approvalOpened(sessionId: string): void {\n this.#update(sessionId, entry => { entry.openApprovals += 1; });\n }\n\n approvalClosed(sessionId: string): void {\n this.#update(sessionId, entry => {\n entry.openApprovals = Math.max(0, entry.openApprovals - 1);\n });\n }\n\n /** Active sessions only — the phone board never shows cold history. */\n async snapshot(): Promise<WireSession[]> {\n const records = await this.#ctx.sessionQuery.listSessions();\n const wire: WireSession[] = [];\n for (const record of records) {\n if (!record.live) continue;\n const id = record.header.id;\n const known = this.#tracked.get(id) ?? this.#adopt(this.#ctx.agents.get(id));\n wire.push(known === undefined\n ? this.#fallbackWire(id, record.header.cwd ?? '', record.header.createdAt)\n : toWire(known));\n }\n return wire;\n }\n\n #fallbackWire(sessionId: string, cwd: string, createdAt: number): WireSession {\n return {\n sessionId,\n title: truncate(sessionId, TITLE_CHARS),\n cwd,\n state: 'idle',\n lastActivity: createdAt,\n dispatched: false,\n access: 'standard',\n };\n }\n\n #adopt(agent: Agent | undefined): Tracked | undefined {\n if (agent === undefined) return undefined;\n const existing = this.#tracked.get(agent.id);\n if (existing !== undefined) return existing;\n const entry: Tracked = {\n sessionId: agent.id,\n title: truncate(firstPrompt(agent) ?? agent.id, TITLE_CHARS),\n cwd: agent.session.header.cwd ?? '',\n base: agent.status,\n openApprovals: 0,\n lastActivity: Date.now(),\n dispatched: false,\n access: 'standard',\n finalized: false,\n };\n this.#tracked.set(agent.id, entry);\n return entry;\n }\n\n #update(sessionId: string, mutate: (entry: Tracked) => void): void {\n const entry = this.#tracked.get(sessionId) ?? this.#adopt(this.#ctx.agents.get(SessionId(sessionId)));\n if (entry === undefined) return;\n mutate(entry);\n entry.lastActivity = Date.now();\n this.#publish(sessionId);\n }\n\n /** Emit `session.update` only when the phone-visible projection changed. */\n #publish(sessionId: string): void {\n const entry = this.#tracked.get(sessionId);\n if (entry === undefined) return;\n const session = toWire(entry);\n const fingerprint = `${session.state}|${session.title}|${session.cwd}`\n + `|${String(session.dispatched)}|${String(session.access)}`;\n if (this.#published.get(sessionId) === fingerprint) return;\n this.#published.set(sessionId, fingerprint);\n this.#relay.send({ v: 1, ts: Date.now(), type: 'session.update', session });\n }\n\n async #refreshTitle(agent: Agent): Promise<void> {\n const entry = this.#tracked.get(agent.id);\n if (entry === undefined || entry.dispatched) return;\n try {\n const snapshot = await this.#ctx.sessionQuery.readTitle(SessionId(agent.id));\n const title = snapshot?.title ?? firstPrompt(agent);\n if (title === undefined || title === '') return;\n entry.title = truncate(title, TITLE_CHARS);\n this.#publish(agent.id);\n } catch (error) {\n this.#log.error('session %s: could not read title: %s', agent.id, error);\n }\n }\n}\n\nfunction toWire(entry: Tracked): WireSession {\n return {\n sessionId: entry.sessionId,\n title: entry.title,\n cwd: entry.cwd,\n state: stateOf(entry),\n lastActivity: entry.lastActivity,\n dispatched: entry.dispatched,\n access: entry.access,\n };\n}\n\n/**\n * `awaiting_approval` is an overlay, not a lifecycle state: it holds exactly\n * while our answerer has an open question for this session.\n */\nfunction stateOf(entry: Tracked): SessionState {\n if (entry.base === 'done' || entry.base === 'error') return entry.base;\n return entry.openApprovals > 0 ? 'awaiting_approval' : entry.base;\n}\n\n/** Fallback title source: the first human prompt in the session log. */\nfunction firstPrompt(agent: Agent): string | undefined {\n for (const event of agent.session.events) {\n if (event.type !== 'user/message') continue;\n const text = event.data.content\n .filter(block => block.type === 'text')\n .map(block => block.text)\n .join('')\n .trim();\n if (text !== '') return text.split('\\n', 1)[0];\n }\n return undefined;\n}\n","/**\n * Plugin version reported in `machine.status`.\n *\n * Kept as a literal rather than read from package.json so the bundled ESM\n * output has no runtime filesystem dependency. `tests/version.test.ts` fails\n * the build if this drifts from package.json.\n */\nexport const PLUGIN_VERSION = '0.2.0';\n","/**\n * Plugin configuration. Validated by Schemastery before `apply` runs, so every\n * field below is present with its default already filled in.\n */\n\nimport { hostname } from 'node:os';\nimport z from '@deepseek-ai/schemastery';\n\nexport interface Config {\n /** Relay websocket URL, e.g. `wss://relay.example.com`. */\n relay: string;\n /** Name shown on the phone's machine card. */\n machineName: string;\n /**\n * Absolute directories a dispatched agent may run in. Empty (the default)\n * keeps remote dispatch off — approval forwarding still works.\n */\n allowedRoots: string[];\n /**\n * Whether the phone may request `access: 'full'`, which runs the session\n * under dsh's `danger-full-access` permission preset — full file access with\n * no approval prompts. Off by default; turning it on is a deliberate choice.\n */\n allowFullAccessDispatch: boolean;\n /** Where the pairing secret and dispatch worktrees live. */\n dataDir: string;\n /** Base URL of the PWA, used only to print the pairing convenience link. */\n pwaUrl: string;\n}\n\nexport const Config: z<Config> = z.object({\n relay: z.string().required(),\n machineName: z.string().default(hostname()),\n allowedRoots: z.array(z.string()).default([]),\n allowFullAccessDispatch: z.boolean().default(false),\n dataDir: z.string().default('~/.dsh-dispatch'),\n pwaUrl: z.string().default('http://localhost:5173'),\n});\n","/**\n * dsh-dispatch — command your DeepSeek Harness machines from your phone.\n *\n * Approvals raised anywhere in this dsh instance are forwarded to a paired\n * phone and answered there; the phone can also start new agent sessions in\n * configured directories and follow up on live ones. Everything between this\n * plugin and the phone is end-to-end encrypted; the relay only routes\n * ciphertext.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\n// Empty type imports carry the Context/Events declaration merges this plugin\n// listens on and calls into.\nimport type {} from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-commands';\nimport type {} from '@deepseek-ai/dsh-session-query';\nimport type {} from '@deepseek-ai/dsh-user-approval';\nimport type { PhoneToMachineMsg } from '@dsh-dispatch/shared';\nimport { installApprovals } from './approvals.js';\nimport type { ApprovalRouter } from './approvals.js';\nimport { installCommands } from './commands.js';\nimport type { Config } from './config.js';\nimport { installDispatch } from './dispatch.js';\nimport type { DispatchRouter } from './dispatch.js';\nimport { createHub } from './hub.js';\nimport type { Hub } from './hub.js';\nimport { PairingStore } from './pairing.js';\nimport { installQuestions } from './questions.js';\nimport type { QuestionRouter } from './questions.js';\nimport { RelayClient } from './relay-client.js';\nimport { SessionTracker } from './sessions.js';\nimport { PLUGIN_VERSION } from './version.js';\n\nexport { Config } from './config.js';\n\nexport const name = 'dsh-dispatch';\n\n/**\n * `approval` is deliberately absent: the plugin is useful (dispatch, session\n * board) in a composition without it, and the dossier's apiproxy precedent\n * guards the service rather than requiring it.\n */\nexport const inject = ['agents', 'commands', 'sessionQuery'];\n\n/** docs/PROTOCOL.md: machine.status on connect and every 60s. */\nconst STATUS_INTERVAL_MS = 60_000;\n\n/** The three inbound feature handlers, passed around as one bundle. */\nexport interface Routers {\n readonly approvals: ApprovalRouter;\n readonly questions: QuestionRouter;\n readonly dispatch: DispatchRouter;\n}\n\nexport function apply(ctx: Context, config: Config): void {\n const log = ctx.logger('dsh-dispatch');\n const pairing = PairingStore.open(config.dataDir, log);\n // The router is built from the relay, and the relay calls into the router:\n // late-bind the two callbacks rather than smuggling a half-built object.\n const deferred = { message(_: PhoneToMachineMsg): void {}, hello(): void {} };\n const relay = new RelayClient({\n url: config.relay,\n secret: pairing.secret,\n log,\n onMessage: message => { deferred.message(message); },\n onConnected: () => { deferred.hello(); },\n onPhonePresence: (online) => {\n if (!online) {\n log.info('relay: phone left the room');\n return;\n }\n pairing.markPaired();\n log.info('relay: phone joined the room');\n deferred.hello();\n },\n });\n const sessions = new SessionTracker(ctx, relay, log);\n const hub = createHub({ ctx, config, log, relay, sessions, pairing });\n const approvals = installApprovals(hub);\n const questions = installQuestions(hub);\n const dispatch = installDispatch(hub);\n const routers: Routers = { approvals, questions, dispatch };\n deferred.message = message => { route(hub, routers, message); };\n deferred.hello = () => {\n publishStatus(hub);\n approvals.resendPending();\n questions.resendPending();\n void publishSnapshot(hub);\n };\n sessions.install();\n installCommands(hub, routers);\n startRelay(hub);\n}\n\n/** Bring the link up, unless it cannot possibly work — say so once, loudly. */\nfunction startRelay(hub: Hub): void {\n const url = hub.config.relay;\n if (url === '') {\n hub.log.error(\n 'relay is not configured: set `relay: \\'wss://…\\'` on the dsh-dispatch row in your profile\\'s '\n + 'cordis.patch.yml. Approval forwarding and dispatch stay off until then.',\n );\n return;\n }\n if (!/^wss?:\\/\\//.test(url)) {\n hub.log.error('relay \"%s\" is not a ws:// or wss:// URL; refusing to connect', url);\n return;\n }\n hub.ctx.effect(() => {\n hub.relay.start();\n return () => { hub.relay.stop(); };\n }, 'dsh-dispatch: relay client');\n hub.ctx.effect(() => {\n const timer = setInterval(() => { publishStatus(hub); }, STATUS_INTERVAL_MS);\n timer.unref?.();\n return () => { clearInterval(timer); };\n }, 'dsh-dispatch: status heartbeat');\n}\n\nfunction route(hub: Hub, routers: Routers, message: PhoneToMachineMsg): void {\n const { approvals, questions, dispatch } = routers;\n switch (message.type) {\n case 'sessions.get':\n void publishSnapshot(hub);\n return;\n case 'approval.respond':\n approvals.respond(message);\n return;\n case 'question.respond':\n questions.respond(message);\n return;\n case 'dispatch.request':\n void dispatch.request(message).catch((error: unknown) => {\n hub.fail(`dispatch.request ${message.requestId}`, error);\n });\n return;\n case 'session.message':\n void dispatch.followup(message).catch((error: unknown) => {\n hub.fail(`session.message ${message.requestId}`, error);\n });\n }\n}\n\nfunction publishStatus(hub: Hub): void {\n hub.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'machine.status',\n machine: hub.config.machineName,\n pluginVersion: PLUGIN_VERSION,\n capabilities: {\n fullAccessDispatch: hub.config.allowFullAccessDispatch,\n questionForwarding: true,\n },\n });\n}\n\nasync function publishSnapshot(hub: Hub): Promise<void> {\n try {\n hub.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'session.snapshot',\n sessions: await hub.sessions.snapshot(),\n });\n } catch (error) {\n hub.fail('session.snapshot', error);\n }\n}\n"],"mappings":"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","names":["resolve","entry","prefix","randomUUID","resolve","join","randomUUID","resolve","ok","randomUUID","REMINDER_MS","MAX_REMINDERS","forward","closedMsg","randomUUID","race","resolve","entry","armReminder","SessionId","TITLE_CHARS","SessionId"]}
|
|
1
|
+
{"version":3,"sources":["../src/approvals.ts","../../shared/src/crypto.ts","../../shared/src/limits.ts","../src/commands.ts","../src/dispatch.ts","../src/pairing.ts","../src/worktree.ts","../src/hub.ts","../src/questions.ts","../src/relay-client.ts","../src/sessions.ts","../src/version.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["/**\n * Approval forwarding — the M1 core.\n *\n * Registers a waterfall answerer on `approval/request` and races three ways:\n * the phone's decision, a local answer arriving through `next()`, and the\n * request's own abort. A timeout is deliberately NOT one of them: docs/PROTOCOL.md\n * invariant 1 forbids auto-allow, so an unanswered question stays pending and\n * is re-pushed instead.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { Agent } from '@deepseek-ai/dsh-agent';\nimport type { CallId } from '@deepseek-ai/dsh-llm';\nimport type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval';\nimport { clampBytes, MAX_DETAIL_CHARS, MAX_FIELD_BYTES, truncate } from '@dsh-dispatch/shared';\nimport type {\n ApprovalRequestMsg,\n ApprovalRespondMsg,\n ApprovalResolution,\n PushCompactMsg,\n} from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\n\nconst TITLE_CHARS = 200;\n/** How often an unanswered approval nudges the phone again. */\nconst REMINDER_MS = 5 * 60_000;\n/** Stop nudging after this many reminders; the question still stays open. */\nconst MAX_REMINDERS = 6;\n\ntype Settle = (outcome: ApprovalOutcome, resolution: ApprovalResolution) => void;\n\ninterface Pending {\n readonly frame: ApprovalRequestMsg;\n readonly push: PushCompactMsg;\n readonly settle: Settle;\n reminders: number;\n timer: ReturnType<typeof setTimeout> | undefined;\n}\n\nexport interface ApprovalRouter {\n /** Apply a phone decision. Unknown ids are answered `expired` so cards clear. */\n respond(message: ApprovalRespondMsg): void;\n /** Re-push every still-open question after a reconnect. */\n resendPending(): void;\n openCount(): number;\n}\n\nexport function installApprovals(hub: Hub): ApprovalRouter {\n const pending = new Map<string, Pending>();\n\n hub.ctx.on('approval/request', (request, next) => {\n // Nothing to forward to: delegate immediately so an unpaired or offline\n // machine pays zero added latency on its local approval UI.\n if (!hub.pairing.everPaired || !hub.relay.connected) return next();\n if (request.signal?.aborted === true) return Promise.resolve<ApprovalOutcome>('cancelled');\n return forward(hub, pending, request, next);\n });\n\n return {\n respond(message: ApprovalRespondMsg): void {\n const entry = pending.get(message.approvalId);\n if (entry === undefined) {\n // Double-tap, or the desktop already answered. Tell the phone to clear.\n hub.relay.send(closedMsg(message.approvalId, 'expired'));\n return;\n }\n entry.settle(message.decision === 'allow' ? 'allowed-once' : 'rejected', message.decision);\n },\n resendPending(): void {\n for (const entry of pending.values()) hub.relay.send(entry.frame, entry.push);\n },\n openCount: () => pending.size,\n };\n}\n\nfunction forward(\n hub: Hub,\n pending: Map<string, Pending>,\n request: ApprovalRequest,\n next: () => Promise<ApprovalOutcome>,\n): Promise<ApprovalOutcome> {\n const approvalId = randomUUID();\n const sessionId = request.agent.id;\n const frame = buildRequestMsg(approvalId, sessionId, request);\n const push = buildPush(sessionId, frame.title);\n if (!hub.relay.send(frame, push)) {\n hub.report('approval', `审批 ${frame.title} 未能推送到手机(relay 未连接),已交回本机处理`);\n return next();\n }\n return race(hub, pending, { approvalId, sessionId, frame, push }, request, next);\n}\n\ninterface Forwarded {\n readonly approvalId: string;\n readonly sessionId: string;\n readonly frame: ApprovalRequestMsg;\n readonly push: PushCompactMsg;\n}\n\nfunction race(\n hub: Hub,\n pending: Map<string, Pending>,\n forwarded: Forwarded,\n request: ApprovalRequest,\n next: () => Promise<ApprovalOutcome>,\n): Promise<ApprovalOutcome> {\n const { approvalId, sessionId } = forwarded;\n return new Promise<ApprovalOutcome>((resolve) => {\n let settled = false;\n const settle: Settle = (outcome, resolution) => {\n if (settled) return;\n settled = true;\n const entry = pending.get(approvalId);\n if (entry?.timer !== undefined) clearTimeout(entry.timer);\n pending.delete(approvalId);\n request.signal?.removeEventListener('abort', onAbort);\n hub.sessions.approvalClosed(sessionId);\n hub.relay.send(closedMsg(approvalId, resolution));\n resolve(outcome);\n };\n function onAbort(): void {\n settle('cancelled', 'superseded');\n }\n request.signal?.addEventListener('abort', onAbort, { once: true });\n const entry: Pending = { ...forwarded, settle, reminders: 0, timer: undefined };\n pending.set(approvalId, entry);\n hub.sessions.approvalOpened(sessionId);\n armReminder(hub, entry);\n // `unavailable` means nobody downstream answered — keep waiting for the\n // phone. Any real decision means a human answered on the desktop first.\n void next().then(\n (outcome) => { if (outcome !== 'unavailable') settle(outcome, 'local'); },\n (error: unknown) => { hub.fail(`approval ${approvalId}: local answerer failed`, error); },\n );\n });\n}\n\n/** Never auto-allow; nudge instead, then go quiet while staying open. */\nfunction armReminder(hub: Hub, entry: Pending): void {\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.reminders += 1;\n hub.relay.send(entry.frame, entry.push);\n if (entry.reminders >= MAX_REMINDERS) {\n hub.log.warn(\n 'approval %s still unanswered after %d reminders; it stays open (never auto-approved)',\n entry.frame.approvalId, entry.reminders,\n );\n return;\n }\n armReminder(hub, entry);\n }, REMINDER_MS);\n entry.timer.unref?.();\n}\n\nfunction buildRequestMsg(\n approvalId: string,\n sessionId: string,\n request: ApprovalRequest,\n): ApprovalRequestMsg {\n return {\n v: 1,\n ts: Date.now(),\n type: 'approval.request',\n approvalId,\n sessionId,\n title: truncate(request.toolName, TITLE_CHARS),\n // Chars first (protocol conformance), then bytes: 8 192 chars of CJK is\n // ~24 KB of UTF-8, which base64 would expand past the 16 KB envelope.\n detail: clampBytes(truncate(buildDetail(request), MAX_DETAIL_CHARS), MAX_FIELD_BYTES),\n createdAt: Date.now(),\n };\n}\n\n/**\n * The phone must be able to see exactly what it is allowing before allowing it\n * (docs/PRODUCT.md 防呆). `ApprovalRequest` deliberately omits tool arguments —\n * they live on the already-logged `tool/call` this `callId` points at.\n */\nfunction buildDetail(request: ApprovalRequest): string {\n const parts = [`工具: ${request.toolName}`];\n if (request.reason !== undefined && request.reason !== '') parts.push(`原因: ${request.reason}`);\n const args = toolCallArguments(request.agent, request.callId);\n parts.push(args === undefined ? '参数: (调用未记录参数)' : `参数:\\n${args}`);\n return parts.join('\\n\\n');\n}\n\nfunction toolCallArguments(agent: Agent, callId: CallId | undefined): string | undefined {\n if (callId === undefined) return undefined;\n const events = agent.session.events;\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n if (event?.type === 'tool/call' && event.data.callId === callId) return event.data.arguments;\n }\n return undefined;\n}\n\nfunction buildPush(sessionId: string, title: string): PushCompactMsg {\n return { v: 1, ts: Date.now(), type: 'push', kind: 'approval', sessionId, title };\n}\n\nfunction closedMsg(approvalId: string, resolution: ApprovalResolution) {\n return { v: 1, ts: Date.now(), type: 'approval.closed', approvalId, resolution } as const;\n}\n","// E2E crypto for dsh-dispatch. Isomorphic (Node + browser) — tweetnacl only.\n// Key/room derivation uses SHA-512 (nacl.hash) truncated; see docs/PROTOCOL.md.\n\nimport nacl from 'tweetnacl';\nimport type { PairingInfo } from './types.js';\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nconst KEY_PREFIX = encoder.encode('dsh-dispatch/key');\nconst ROOM_PREFIX = encoder.encode('dsh-dispatch/room');\n\nconst NONCE_LENGTH = nacl.secretbox.nonceLength;\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const out = new Uint8Array(a.length + b.length);\n out.set(a, 0);\n out.set(b, a.length);\n return out;\n}\n\nconst B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\nexport function toBase64(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n out += B64_ALPHABET[b0 >> 2]!;\n out += B64_ALPHABET[((b0 & 3) << 4) | ((b1 ?? 0) >> 4)]!;\n out += b1 === undefined ? '=' : B64_ALPHABET[((b1 & 15) << 2) | ((b2 ?? 0) >> 6)]!;\n out += b2 === undefined ? '=' : B64_ALPHABET[b2 & 63]!;\n }\n return out;\n}\n\nexport function fromBase64(text: string): Uint8Array | null {\n const clean = text.replace(/=+$/, '');\n if (!/^[A-Za-z0-9+/]*$/.test(clean)) return null;\n const out = new Uint8Array(Math.floor((clean.length * 3) / 4));\n let bits = 0;\n let value = 0;\n let index = 0;\n for (const char of clean) {\n value = (value << 6) | B64_ALPHABET.indexOf(char);\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[index++] = (value >> bits) & 0xff;\n }\n }\n return out.slice(0, index);\n}\n\nexport function toBase64Url(bytes: Uint8Array): string {\n return toBase64(bytes).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport function fromBase64Url(text: string): Uint8Array | null {\n return fromBase64(text.replace(/-/g, '+').replace(/_/g, '/'));\n}\n\nexport function generateSecret(): Uint8Array {\n return nacl.randomBytes(32);\n}\n\nexport function deriveKey(secret: Uint8Array): Uint8Array {\n return nacl.hash(concat(KEY_PREFIX, secret)).slice(0, nacl.secretbox.keyLength);\n}\n\nexport function deriveRoomId(secret: Uint8Array): string {\n return toBase64Url(nacl.hash(concat(ROOM_PREFIX, secret)).slice(0, 16));\n}\n\nexport function seal(message: unknown, key: Uint8Array): string {\n const nonce = nacl.randomBytes(NONCE_LENGTH);\n const box = nacl.secretbox(encoder.encode(JSON.stringify(message)), nonce, key);\n return toBase64(concat(nonce, box));\n}\n\n// Returns null on any tamper/garbage/wrong-key input — caller MUST surface it, not drop it.\nexport function open(payload: string, key: Uint8Array): unknown | null {\n const bytes = fromBase64(payload);\n if (bytes === null || bytes.length < NONCE_LENGTH + nacl.secretbox.overheadLength) return null;\n const box = nacl.secretbox.open(bytes.slice(NONCE_LENGTH), bytes.slice(0, NONCE_LENGTH), key);\n if (box === null) return null;\n try {\n return JSON.parse(decoder.decode(box));\n } catch {\n return null;\n }\n}\n\nexport function encodePairing(info: PairingInfo): string {\n const json = JSON.stringify({\n v: 1,\n relay: info.relay,\n secret: toBase64Url(info.secret),\n machine: info.machine,\n });\n return toBase64Url(encoder.encode(json));\n}\n\nexport function decodePairing(code: string): PairingInfo | null {\n const bytes = fromBase64Url(code.trim());\n if (bytes === null) return null;\n try {\n const parsed = JSON.parse(decoder.decode(bytes)) as {\n v?: number;\n relay?: string;\n secret?: string;\n machine?: string;\n };\n if (parsed.v !== 1 || !parsed.relay || !parsed.secret || !parsed.machine) return null;\n const secret = fromBase64Url(parsed.secret);\n if (secret === null || secret.length !== 32) return null;\n return { relay: parsed.relay, secret, machine: parsed.machine };\n } catch {\n return null;\n }\n}\n","// Payload limits from docs/PROTOCOL.md. The sender truncates BEFORE encryption.\n// The relay imports THIS FILE by subpath (src/limits.js) to keep tweetnacl out of its\n// bundle — if this package ever gains a package.json \"exports\" map, add a matching entry.\n\nexport const MAX_ENVELOPE_BYTES = 16 * 1024;\nexport const MAX_DETAIL_CHARS = 8 * 1024;\nexport const MAX_SUMMARY_CHARS = 4 * 1024;\n// Char limits alone don't bound the envelope: CJK is ~3 bytes/char in UTF-8.\n// Free-text fields are clamped by chars first, then by bytes (clampBytes) before seal.\nexport const MAX_PROMPT_CHARS = 8 * 1024;\nexport const MAX_PROMPT_BYTES = 10 * 1024;\nexport const MAX_FIELD_BYTES = 10 * 1024;\nexport const MAX_PUSH_BYTES = 3 * 1024;\nexport const TRUNCATION_SUFFIX = '…[truncated]';\n\nexport function truncate(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text;\n return text.slice(0, maxChars - TRUNCATION_SUFFIX.length) + TRUNCATION_SUFFIX;\n}\n\nconst byteEncoder = new TextEncoder();\n\nexport function utf8Length(text: string): number {\n return byteEncoder.encode(text).length;\n}\n\n// Clamp to a UTF-8 byte budget without splitting a multi-byte char or surrogate pair.\nexport function clampBytes(text: string, maxBytes: number): string {\n if (utf8Length(text) <= maxBytes) return text;\n const budget = maxBytes - utf8Length(TRUNCATION_SUFFIX);\n let low = 0;\n let high = text.length;\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if (utf8Length(text.slice(0, mid)) <= budget) low = mid;\n else high = mid - 1;\n }\n const tail = text.charCodeAt(low - 1);\n if (tail >= 0xd800 && tail <= 0xdbff) low -= 1;\n return text.slice(0, low) + TRUNCATION_SUFFIX;\n}\n","/**\n * The desktop-side surfaces: pairing, re-pairing, and the status line that\n * makes a broken link visible without leaving dsh (docs/PRODUCT.md 失败可见).\n */\n\nimport type { CommandResult } from '@deepseek-ai/dsh-commands';\nimport type { Hub } from './hub.js';\nimport type { Routers } from './index.js';\n\nexport type CommandDeps = Routers;\n\nexport function installCommands(hub: Hub, deps: CommandDeps): void {\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-pair',\n description: 'Show the dsh-dispatch pairing code for this machine',\n handler: () => pairingResult(hub),\n }), 'dsh-dispatch: /dispatch-pair');\n\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-repair',\n description: 'Generate a new dsh-dispatch pairing secret (invalidates every paired phone)',\n handler: () => {\n hub.relay.rekey(hub.pairing.regenerate());\n hub.log.warn('pairing secret regenerated; every previously paired phone is now locked out');\n return pairingResult(hub, '旧配对码已作废,请在每台手机上重新扫码。\\n\\n');\n },\n }), 'dsh-dispatch: /dispatch-repair');\n\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-status',\n description: 'Show the dsh-dispatch relay connection and remote session state',\n handler: () => ({ kind: 'success', text: statusText(hub, deps) }),\n }), 'dsh-dispatch: /dispatch-status');\n}\n\nfunction pairingResult(hub: Hub, prefix = ''): CommandResult {\n if (hub.config.relay === '') {\n return {\n kind: 'error',\n text: 'relay 未配置:请在 profile 的 cordis.patch.yml 中为 dsh-dispatch 设置 relay: \\'wss://…\\'',\n };\n }\n const code = hub.pairing.pairingCode(hub.config.relay, hub.config.machineName);\n return {\n kind: 'success',\n text: `${prefix}配对码(${hub.config.machineName}):\\n${code}\\n\\n`\n + `或在手机上打开:\\n${hub.config.pwaUrl}/#pair=${code}\\n\\n`\n + '⚠️ 此码等同于本机的控制权:持有者可以批准工具调用并在允许目录中派发任务。'\n + '不要发到群里或截图外传;一旦泄露立即运行 /dispatch-repair。',\n };\n}\n\nfunction statusText(hub: Hub, deps: CommandDeps): string {\n const status = hub.relay.status();\n const link = hub.config.relay === ''\n ? '未配置(请设置 relay)'\n : status.connected\n ? `已连接 ${hub.config.relay}`\n : status.gaveUp\n ? `连接失败 ${String(status.consecutiveFailures)} 次,仍在每 60s 重试 — 请检查 relay`\n : `未连接(重试中,已失败 ${String(status.consecutiveFailures)} 次)`;\n return [\n `relay: ${link}`,\n `房间: ${status.room.slice(0, 8)}…`,\n `已配对: ${hub.pairing.everPaired ? 'yes' : 'no'}`\n + `${status.phoneOnline ? '(手机在线)' : '(手机离线)'}`,\n `待批审批: ${String(deps.approvals.openCount())}`,\n `待答提问: ${String(deps.questions.openCount())}`,\n `派发会话: ${String(deps.dispatch.dispatchedCount())}`,\n `full-access 派发: ${hub.config.allowFullAccessDispatch ? '已开启 ⚠️' : '已禁用'}`,\n `解密失败: ${String(status.decryptFailures)}`\n + `${status.decryptFailures > 0 ? ' — 密钥不匹配,请重新配对' : ''}`,\n `allowedRoots: ${hub.config.allowedRoots.length === 0\n ? '(空 — 远程派任务已禁用)'\n : hub.config.allowedRoots.join(', ')}`,\n ].join('\\n');\n}\n","/**\n * Remote dispatch — the M2 core. Starts agent sessions on request from the\n * phone, guards the working directory, and reports the final assistant text\n * when a dispatched turn closes.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { isAbsolute, resolve, sep } from 'node:path';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { installModelSelection } from '@deepseek-ai/dsh-agent';\nimport type { AgentHandle, ModelSelection } from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-agent-default-model';\n// Carries the `ctx.agentPresets` Context merge; the roster is optional at\n// runtime, so it is read with ctx.get rather than declared in `inject`.\nimport type {} from '@deepseek-ai/dsh-agent-presets';\n// Carries the `ctx.permissionPresets` Context merge.\nimport type {} from '@deepseek-ai/dsh-permission-presets';\nimport { createUserMessage } from '@deepseek-ai/dsh-llm';\nimport { SessionId } from '@deepseek-ai/dsh-session';\nimport type { SurfaceEvent } from '@deepseek-ai/dsh-session';\nimport {\n clampBytes,\n MAX_FIELD_BYTES,\n MAX_PROMPT_BYTES,\n MAX_PROMPT_CHARS,\n MAX_SUMMARY_CHARS,\n truncate,\n utf8Length,\n} from '@dsh-dispatch/shared';\nimport type {\n DispatchRequestMsg,\n DispatchResultMsg,\n Reason,\n ReasonCode,\n SessionMessageMsg,\n} from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\nimport { expandHome } from './pairing.js';\nimport { prepareWorkspace, WorktreeError } from './worktree.js';\n\n/** docs/PROTOCOL.md: LRU of seen request ids, for double-tap safety. */\nconst SEEN_LIMIT = 256;\n\n/**\n * Machine-authored prose the phone shows to a human, paired with a stable code.\n * The machine cannot know the phone's language, so it sends both: its own\n * wording (which any peer can display) and a code a newer phone can translate.\n */\nexport interface Complaint {\n readonly text: string;\n readonly reason: Reason;\n}\n\nfunction complain(\n text: string,\n code: ReasonCode,\n params?: Record<string, string | number>,\n): Complaint {\n return { text, reason: params === undefined ? { code } : { code, params } };\n}\n\nconst DISPATCH_DISABLED = complain(\n 'dispatch 未启用:请在插件配置中设置 allowedRoots', 'dispatch-disabled');\nconst NO_REMOTE_FOLLOWUP = complain('该会话不支持远程追加', 'no-remote-followup');\nconst FULL_ACCESS_DISABLED = complain(\n 'full-access 派发未启用:请在插件配置中开启 allowFullAccessDispatch', 'full-access-disabled');\nconst FULL_ACCESS_UNAVAILABLE = complain(\n 'full-access 不可用:本机 dsh 未组合 permission-presets 插件', 'full-access-unavailable');\n/** dsh's shipped preset table key (permission-presets/src/index.ts:188). */\nconst FULL_ACCESS_PRESET = 'danger-full-access';\n\nexport interface DispatchRouter {\n request(message: DispatchRequestMsg): Promise<void>;\n followup(message: SessionMessageMsg): Promise<void>;\n dispatchedCount(): number;\n}\n\n/** Per-plugin dispatch state; kept in one object so the handlers stay flat. */\ninterface DispatchState {\n readonly handles: Map<string, AgentHandle>;\n readonly seen: ResultCache;\n readonly inflight: Map<string, Promise<DispatchResultMsg>>;\n loaderReady: Promise<void> | undefined;\n}\n\nexport function installDispatch(hub: Hub): DispatchRouter {\n const state: DispatchState = {\n handles: new Map(),\n seen: new ResultCache(),\n inflight: new Map(),\n loaderReady: undefined,\n };\n hub.ctx.on('agent/turn-stopping', ({ agent }) => {\n if (!hub.sessions.isDispatched(agent.id)) return;\n // Latch the card synchronously: the idle status that closes this turn\n // races the async surface read below and would otherwise land first.\n hub.sessions.markTurnComplete(agent.id);\n void reportTurnFinal(hub, agent.id).catch((error: unknown) => {\n hub.fail(`turn.final for ${agent.id}`, error);\n });\n });\n hub.ctx.on('agent/disposed', ({ agent }) => { state.handles.delete(agent.id); });\n return {\n request: message => handleRequest(hub, state, message),\n followup: message => handleFollowup(hub, state, message),\n dispatchedCount: () => state.handles.size,\n };\n}\n\n/** Answer exactly once per requestId, even under a double-tap. */\nasync function handleRequest(\n hub: Hub,\n state: DispatchState,\n message: DispatchRequestMsg,\n): Promise<void> {\n // Pure validation runs BEFORE the idempotency mark: malformed input can\n // never half-apply, so a retry deserves the same visible complaint rather\n // than a cached one.\n const rejected = validate(hub, message);\n if (rejected !== undefined) {\n hub.relay.send(failure(message.requestId, rejected));\n return;\n }\n const replay = state.seen.get(message.requestId);\n if (replay !== undefined) {\n if (replay !== null) hub.relay.send(replay);\n return;\n }\n const pending = state.inflight.get(message.requestId) ?? startSession(hub, state, message);\n state.inflight.set(message.requestId, pending);\n try {\n const result = await pending;\n state.seen.set(message.requestId, result);\n hub.relay.send(result);\n } finally {\n state.inflight.delete(message.requestId);\n }\n}\n\nasync function startSession(\n hub: Hub,\n state: DispatchState,\n message: DispatchRequestMsg,\n): Promise<DispatchResultMsg> {\n const prompt = message.prompt;\n const target = resolveCwd(hub, message.cwd);\n if (typeof target !== 'string') return failure(message.requestId, target.error);\n const full = message.access === 'full';\n try {\n const workspace = await prepareWorkspace({\n cwd: target,\n dataDir: hub.pairing.dataDir,\n worktree: message.worktree,\n });\n // Loader siblings mount concurrently; never create an agent into a\n // half-composed application (dossier §3.2).\n state.loaderReady ??= Promise.resolve(hub.ctx.get('loader')?.await()).then(() => undefined);\n await state.loaderReady;\n const created = await createSession(hub.ctx, workspace.cwd, prompt, full);\n state.handles.set(created.handle.agent.id, created.handle);\n // Mark access from the REQUEST, never gated behind the preset call that may\n // have thrown: an audit mark that vanishes on failure is worse than useless.\n hub.sessions.markDispatched(created.handle.agent.id, prompt, full ? 'full' : 'standard');\n if (created.fullAccessError !== undefined) {\n // The turn still started (fail-open), but the preset did not stick — say\n // so loudly rather than run at standard while the card claims full.\n hub.report(`dispatch ${message.requestId}`, `完全访问权限未能应用:${created.fullAccessError}`);\n }\n const result = ok(message.requestId, created.handle.agent.id);\n // `note` is advisory on a successful result; `error` never appears with\n // ok: true, so the phone can key its failure state on `ok` alone.\n const advisory: Complaint | undefined = created.fullAccessError === undefined\n ? (workspace.note === undefined\n ? undefined\n : { text: workspace.note, reason: workspace.noteReason ?? { code: 'not-a-git-repo' } })\n : complain(\n `完全访问权限未能应用,已在标准权限下运行:${created.fullAccessError}`,\n 'full-access-not-applied',\n { detail: created.fullAccessError },\n );\n return advisory === undefined\n ? result\n : { ...result, note: advisory.text, reason: advisory.reason };\n } catch (error) {\n const detail = error instanceof WorktreeError ? error.message : String(error);\n hub.log.error('dispatch %s failed: %s', message.requestId, detail);\n return failure(message.requestId, complain(\n detail, error instanceof WorktreeError ? 'worktree-failed' : 'dispatch-failed', { detail }));\n }\n}\n\n/**\n * Append a phone-typed message to a live session. Cold (persisted-only)\n * sessions are refused out loud rather than silently resumed.\n */\nasync function handleFollowup(\n hub: Hub,\n state: DispatchState,\n message: SessionMessageMsg,\n): Promise<void> {\n // Malformed input can never double-apply, so it is answered before the\n // idempotency mark: a retry deserves the same visible complaint.\n const oversized = overLimit('message', message.text);\n if (oversized !== undefined) {\n hub.report(`session.message ${message.sessionId}`, oversized.text,\n { sessionId: message.sessionId, reason: oversized.reason });\n return;\n }\n if (state.seen.get(message.requestId) !== undefined) return;\n state.seen.set(message.requestId, null);\n const agent = state.handles.get(message.sessionId)?.agent\n ?? hub.sessions.agentOf(message.sessionId);\n if (agent === undefined) {\n hub.report(`session.message ${message.sessionId}`, NO_REMOTE_FOLLOWUP.text,\n { sessionId: message.sessionId, reason: NO_REMOTE_FOLLOWUP.reason });\n return;\n }\n agent.followup(createUserMessage({\n content: [{ type: 'text', text: message.text }],\n source: { kind: 'user' },\n }));\n}\n\n/**\n * Reject over-limit inbound text instead of clamping it. A well-behaved phone\n * clamps client-side (docs/PROTOCOL.md), so an oversized prompt is malformed\n * input — silently trimming it would run a task the user never wrote.\n * @returns the visible complaint, or undefined when the text is within limits.\n */\nfunction overLimit(field: 'prompt' | 'message', text: string): Complaint | undefined {\n const label = field === 'prompt' ? '提示词' : '消息';\n const tail = '请在手机端缩短后重发。';\n if (text.length > MAX_PROMPT_CHARS) {\n return complain(\n `${label} 超长:${String(text.length)} 字符,上限 ${String(MAX_PROMPT_CHARS)} 字符。${tail}`,\n `${field}-too-long-chars`,\n { actual: text.length, limit: MAX_PROMPT_CHARS },\n );\n }\n const bytes = utf8Length(text);\n if (bytes > MAX_PROMPT_BYTES) {\n return complain(\n `${label} 超长:${String(bytes)} 字节(UTF-8),上限 ${String(MAX_PROMPT_BYTES)} 字节。${tail}`,\n `${field}-too-long-bytes`,\n { actual: bytes, limit: MAX_PROMPT_BYTES },\n );\n }\n return undefined;\n}\n\n/**\n * Compose the agent's model-facing world: the model selection, and — when the\n * deployment runs a preset roster — the preset that carries its tools.\n *\n * Mirrors `composeAgent()` in packages/host/apiproxy/src/api-proxy.ts:1168,\n * which is what a user-created web session goes through. Skipping the mount is\n * not a SMALLER toolset, it is NO toolset: the web bundle disables every\n * model-facing row in the host plane (`tool-bash`, `tool-fs`, `tool-todo`, …\n * all `disabled: true` in packages/bundle/web-app/cordis.patch.yml) and moves\n * them into each preset's own scope layer, so an agent that joins no preset\n * reaches the model with an empty tool registry and no persona sections.\n */\nasync function composeAgent(ctx: Context, selection: ModelSelection | undefined): Promise<{\n agentPreset?: string;\n setup: (agentCtx: Context) => Promise<void>;\n}> {\n const install = (agentCtx: Context): void => {\n if (selection === undefined) return;\n installModelSelection(agentCtx, { current: selection, assembled: undefined });\n };\n const presets = ctx.get('agentPresets');\n // No roster composed (the headless shape): model-facing rows sit in the host\n // plane and the agent reads them from the global layer.\n if (presets === undefined) {\n return { setup: (agentCtx: Context) => { install(agentCtx); return Promise.resolve(); } };\n }\n // Name no preset, exactly as when a web client creates a session without\n // picking one: the roster's configured default wins.\n const resolvedId = (await presets.resolve()).id;\n return {\n agentPreset: resolvedId,\n setup: async (agentCtx: Context) => {\n install(agentCtx);\n await presets.mount(agentCtx, resolvedId);\n },\n };\n}\n\n/**\n * Everything we can refuse without touching the filesystem or the registry.\n * @returns the visible complaint, or undefined when the request is well formed.\n */\nfunction validate(hub: Hub, message: DispatchRequestMsg): Complaint | undefined {\n const oversized = overLimit('prompt', message.prompt);\n if (oversized !== undefined) return oversized;\n if (message.access !== undefined && message.access !== 'standard' && message.access !== 'full') {\n return complain(`access 取值无效:${String(message.access)}`, 'bad-access-value',\n { value: String(message.access) });\n }\n if (message.access !== 'full') return undefined;\n if (!hub.config.allowFullAccessDispatch) return FULL_ACCESS_DISABLED;\n // Refusing here beats running the task at standard permissions while the\n // phone believes it asked for full access.\n if (hub.ctx.get('permissionPresets') === undefined) return FULL_ACCESS_UNAVAILABLE;\n return undefined;\n}\n\n/** Outcome of one session creation: the handle, and any full-access snag. */\ninterface CreatedSession {\n readonly handle: AgentHandle;\n /** Set when the danger-full-access preset could not be applied. */\n readonly fullAccessError?: string;\n}\n\n/** Create a session in `cwd` with the same composition a web session gets. */\nasync function createSession(\n ctx: Context,\n cwd: string,\n prompt: string,\n fullAccess: boolean,\n): Promise<CreatedSession> {\n const selection = ctx.get('agentDefaultModel')?.currentSelection();\n const composition = await composeAgent(ctx, selection);\n const handle = await ctx.agents.create({\n sessionId: SessionId(`session-${randomUUID()}`),\n // The preset is recorded on the header so a later cold resume rebuilds the\n // composition this session's history was actually produced under.\n meta: composition.agentPreset === undefined\n ? { cwd }\n : { cwd, agentPreset: composition.agentPreset },\n agentOptions: selection === undefined\n ? undefined\n : { provider: selection.provider, model: selection.model },\n setup: composition.setup,\n });\n await handle.agent.whenIdle();\n // Apply the tier BEFORE the first turn — but never let it skip the followup:\n // a thrown set() previously wedged the session with only setup events and no\n // turn (the acceptance-test repro). Fail-open on the turn.\n const fullAccessError = fullAccess ? applyFullAccess(ctx, handle) : undefined;\n handle.agent.followup(createUserMessage({\n content: [{ type: 'text', text: prompt }],\n source: { kind: 'user' },\n }));\n return fullAccessError === undefined ? { handle } : { handle, fullAccessError };\n}\n\n/**\n * Switch a freshly created session to danger-full-access.\n *\n * Resolved via `ctx.get()`, NOT the `ctx.permissionPresets` property proxy: an\n * un-injected service read through the proxy from this nested fiber throws\n * `cannot get property \"permissionPresets\" without inject`\n * (vendor/cordis/src/reflect.ts:144), while `get()` reads the store directly\n * (reflect.ts:233). `set()` then appends the durable `permission/preset` event\n * and drives the sandbox + approval knobs\n * (packages/interaction/permission-presets/src/index.ts:391).\n * @returns undefined on success, or a message describing why it could not apply.\n */\nfunction applyFullAccess(ctx: Context, handle: AgentHandle): string | undefined {\n try {\n const presets = ctx.get('permissionPresets');\n if (presets === undefined) return 'permission-presets 插件未组合';\n presets.set(handle.agent.session, FULL_ACCESS_PRESET);\n return undefined;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n\n/**\n * The allowed roots as this machine actually enforces them. `machine.status`\n * publishes exactly this list, so the phone's picker can never offer a path the\n * guard below would then reject.\n */\nexport function resolvedRoots(hub: Hub): string[] {\n return hub.config.allowedRoots.map(root => resolve(expandHome(root)));\n}\n\n/**\n * Validate the requested cwd against the configured roots.\n * @returns the absolute directory, or the visible failure to send back.\n */\nfunction resolveCwd(hub: Hub, requested: string | undefined): string | { error: Complaint } {\n const roots = resolvedRoots(hub);\n if (roots.length === 0) return { error: DISPATCH_DISABLED };\n const first = roots[0];\n if (requested === undefined) return first === undefined ? { error: DISPATCH_DISABLED } : first;\n const target = resolve(isAbsolute(requested) ? requested : expandHome(requested));\n const inside = roots.some(root => target === root || target.startsWith(root + sep));\n return inside\n ? target\n : { error: complain(`cwd 不在允许目录内:${target}`, 'cwd-not-allowed', { cwd: target }) };\n}\n\nasync function reportTurnFinal(hub: Hub, sessionId: string): Promise<void> {\n const snapshot = await hub.ctx.sessionQuery.readSurface(SessionId(sessionId));\n const ok = !hub.sessions.hasError(sessionId);\n // A turn with no assistant text still has to say something; that sentence is\n // the machine's, not the model's, so it travels with a code the phone can\n // translate instead of showing Chinese to an English reader.\n const text = lastAssistantText(snapshot.events);\n const placeholder = text === undefined\n ? complain('(本轮没有产生助手文本)', 'no-assistant-text')\n : undefined;\n const summary = clampBytes(\n truncate(text ?? placeholder!.text, MAX_SUMMARY_CHARS),\n MAX_FIELD_BYTES,\n );\n hub.relay.send(\n {\n v: 1, ts: Date.now(), type: 'turn.final', sessionId, ok, summary,\n ...(placeholder === undefined ? {} : { reason: placeholder.reason }),\n },\n {\n v: 1,\n ts: Date.now(),\n type: 'push',\n kind: ok ? 'done' : 'error',\n sessionId,\n title: truncate(summary.split('\\n', 1)[0] ?? '', 120),\n },\n );\n}\n\nfunction lastAssistantText(events: readonly SurfaceEvent[]): string | undefined {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n if (event?.type !== 'assistant/message') continue;\n const text = event.data.message.content\n .filter(block => block.type === 'text')\n .map(block => block.text)\n .join('')\n .trim();\n if (text !== '') return text;\n }\n return undefined;\n}\n\nfunction ok(requestId: string, sessionId: string): DispatchResultMsg {\n return { v: 1, ts: Date.now(), type: 'dispatch.result', requestId, ok: true, sessionId };\n}\n\nfunction failure(requestId: string, complaint: Complaint): DispatchResultMsg {\n return {\n v: 1, ts: Date.now(), type: 'dispatch.result', requestId,\n ok: false, error: complaint.text, reason: complaint.reason,\n };\n}\n\n/** Bounded replay cache. `null` marks a request that succeeded with no reply frame. */\nclass ResultCache {\n readonly #entries = new Map<string, DispatchResultMsg | null>();\n\n get(requestId: string): DispatchResultMsg | null | undefined {\n return this.#entries.get(requestId);\n }\n\n set(requestId: string, value: DispatchResultMsg | null): void {\n this.#entries.delete(requestId);\n this.#entries.set(requestId, value);\n while (this.#entries.size > SEEN_LIMIT) {\n const oldest = this.#entries.keys().next();\n if (oldest.done === true) break;\n this.#entries.delete(oldest.value);\n }\n }\n}\n","/**\n * Pairing secret lifecycle: the 32 random bytes that are the ONLY thing\n * standing between a stranger and control of this machine. Stored at\n * `<dataDir>/secret` with mode 0600 and never sent to the relay.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { encodePairing, fromBase64, generateSecret, toBase64 } from '@dsh-dispatch/shared';\nimport type { Log } from './log.js';\n\nconst SECRET_FILE = 'secret';\nconst PAIRED_FILE = 'paired.json';\nconst SECRET_BYTES = 32;\n\n/** Expand a leading `~` so a configured `~/.dsh-dispatch` resolves to a real path. */\nexport function expandHome(input: string): string {\n if (input === '~') return homedir();\n if (input.startsWith('~/')) return join(homedir(), input.slice(2));\n return resolve(input);\n}\n\n/** Everything the plugin needs to prove a phone is allowed to talk to it. */\nexport class PairingStore {\n readonly dataDir: string;\n #secret: Uint8Array;\n #pairedAt: number | undefined;\n\n private constructor(dataDir: string, secret: Uint8Array, pairedAt: number | undefined) {\n this.dataDir = dataDir;\n this.#secret = secret;\n this.#pairedAt = pairedAt;\n }\n\n /** Load the stored secret, generating and persisting one on first start. */\n static open(dataDir: string, log: Log): PairingStore {\n const dir = expandHome(dataDir);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n const secretPath = join(dir, SECRET_FILE);\n let secret: Uint8Array | null = null;\n if (existsSync(secretPath)) {\n secret = fromBase64(readFileSync(secretPath, 'utf8').trim());\n if (secret === null || secret.length !== SECRET_BYTES) {\n throw new Error(\n `dsh-dispatch: ${secretPath} is not a valid pairing secret. `\n + 'Delete the file to generate a new one (this invalidates existing pairings).',\n );\n }\n } else {\n secret = generateSecret();\n writeSecret(secretPath, secret);\n }\n return new PairingStore(dir, secret, readPairedAt(join(dir, PAIRED_FILE), log));\n }\n\n get secret(): Uint8Array {\n return this.#secret;\n }\n\n /**\n * Whether a phone has ever completed a pairing with this machine. Approval\n * forwarding stays out of the way entirely until this is true, so an\n * unpaired machine pays zero added approval latency.\n */\n get everPaired(): boolean {\n return this.#pairedAt !== undefined;\n }\n\n get pairedAt(): number | undefined {\n return this.#pairedAt;\n }\n\n /** Record the first sighting of a phone in this room. Idempotent. */\n markPaired(): void {\n if (this.#pairedAt !== undefined) return;\n this.#pairedAt = Date.now();\n writeFileSync(join(this.dataDir, PAIRED_FILE), JSON.stringify({ pairedAt: this.#pairedAt }), {\n mode: 0o600,\n });\n }\n\n /** Replace the secret, invalidating every existing pairing. */\n regenerate(): Uint8Array {\n this.#secret = generateSecret();\n writeSecret(join(this.dataDir, SECRET_FILE), this.#secret);\n this.#pairedAt = undefined;\n rmSync(join(this.dataDir, PAIRED_FILE), { force: true });\n return this.#secret;\n }\n\n /** The out-of-band pairing payload: relay + secret + machine name. */\n pairingCode(relay: string, machine: string): string {\n return encodePairing({ relay, secret: this.#secret, machine });\n }\n}\n\nfunction writeSecret(path: string, secret: Uint8Array): void {\n writeFileSync(path, toBase64(secret), { mode: 0o600 });\n // writeFileSync only applies `mode` when it creates the file; a rewrite of an\n // existing loose-permission file would otherwise stay world-readable.\n chmodSync(path, 0o600);\n}\n\nfunction readPairedAt(path: string, log: Log): number | undefined {\n if (!existsSync(path)) return undefined;\n try {\n const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));\n const value = (parsed as { pairedAt?: unknown }).pairedAt;\n return typeof value === 'number' ? value : undefined;\n } catch (error) {\n // A corrupt marker only costs us the \"have we ever paired\" shortcut; the\n // next phone connection rewrites it. Loud, then treat as never paired.\n log.error('pairing: %s is unreadable, treating this machine as never paired: %s', path, error);\n return undefined;\n }\n}\n","/**\n * Git worktree preparation for dispatched tasks.\n *\n * v0 never deletes a worktree (docs/PRODUCT.md 防呆): cleanup is the user's\n * call, because guessing wrong destroys real work.\n */\n\nimport { execFile } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { Reason } from '@dsh-dispatch/shared';\n\nconst run = promisify(execFile);\n\n/** docs/PROTOCOL.md: a failure result carries at most 500 chars of stderr. */\nconst STDERR_TAIL = 500;\n\nexport class WorktreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'WorktreeError';\n }\n}\n\nexport interface Workspace {\n /** Directory the agent will actually run in. */\n readonly cwd: string;\n /** Branch created for this task, absent when running in `cwd` directly. */\n readonly branch?: string;\n /** Human-readable degradation note the phone must show, when degraded. */\n readonly note?: string;\n /** Translatable form of `note`, so the phone can show it in its own language. */\n readonly noteReason?: Reason;\n}\n\nexport interface PrepareOptions {\n readonly cwd: string;\n readonly dataDir: string;\n readonly worktree: boolean;\n}\n\n/**\n * Resolve the directory a dispatched task runs in.\n * @throws WorktreeError when `git worktree add` fails; the message carries the stderr tail.\n */\nexport async function prepareWorkspace(options: PrepareOptions): Promise<Workspace> {\n if (!options.worktree) return { cwd: options.cwd };\n if (!await isGitRepo(options.cwd)) {\n return {\n cwd: options.cwd,\n note: `已降级:${options.cwd} 不是 git 仓库,任务直接在该目录运行(未创建 worktree)`,\n noteReason: { code: 'not-a-git-repo', params: { cwd: options.cwd } },\n };\n }\n const root = join(options.dataDir, 'worktrees');\n try {\n return await addWorktree(options.cwd, root, freshSlug());\n } catch (error) {\n const stderr = stderrOf(error);\n if (!isCollision(stderr)) throw new WorktreeError(stderr);\n return await addWorktree(options.cwd, root, freshSlug()).catch((retry: unknown) => {\n throw new WorktreeError(stderrOf(retry));\n });\n }\n}\n\nasync function addWorktree(cwd: string, root: string, slug: string): Promise<Workspace> {\n const target = join(root, slug);\n const branch = `dsh-dispatch/${slug}`;\n await run('git', ['-C', cwd, 'worktree', 'add', target, '-b', branch]);\n return { cwd: target, branch };\n}\n\nasync function isGitRepo(cwd: string): Promise<boolean> {\n try {\n const { stdout } = await run('git', ['-C', cwd, 'rev-parse', '--is-inside-work-tree']);\n return stdout.trim() === 'true';\n } catch {\n // Not a repo, or git is missing. Both mean \"no worktree here\"; the caller\n // reports the degradation to the phone, so this is not a silent swallow.\n return false;\n }\n}\n\n/** `timestamp + short random` — sortable, collision-resistant, readable in `git worktree list`. */\nfunction freshSlug(): string {\n const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\\..+$/, '');\n return `${stamp}-${randomBytes(3).toString('hex')}`;\n}\n\nfunction isCollision(stderr: string): boolean {\n return /already exists|already used by worktree|already checked out/i.test(stderr);\n}\n\nfunction stderrOf(error: unknown): string {\n const raw = (error as { stderr?: unknown }).stderr;\n const text = typeof raw === 'string' && raw.trim() !== ''\n ? raw.trim()\n : error instanceof Error ? error.message : String(error);\n return text.length <= STDERR_TAIL ? text : text.slice(-STDERR_TAIL);\n}\n","/**\n * The shared runtime handle every feature module receives. Also the single\n * place a failure becomes visible: `fail()` logs on the desktop AND pushes an\n * `error` message to the phone, so no catch can quietly swallow anything.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\nimport { truncate } from '@dsh-dispatch/shared';\nimport type { Reason } from '@dsh-dispatch/shared';\nimport type { Config } from './config.js';\nimport type { Log } from './log.js';\nimport type { PairingStore } from './pairing.js';\nimport type { RelayClient } from './relay-client.js';\nimport type { SessionTracker } from './sessions.js';\n\n/** What a reported failure is about, so the phone can place and translate it. */\nexport interface ReportAbout {\n /** Set when the failure belongs to one session; the phone pins it to that card. */\n readonly sessionId?: string;\n /** Stable code for the message, so the phone can show it in its own language. */\n readonly reason?: Reason;\n}\n\n/** Phone screens are small; a wall of stack trace helps nobody there. */\nconst ERROR_CHARS = 1_000;\n\nexport interface Hub {\n readonly ctx: Context;\n readonly config: Config;\n readonly log: Log;\n readonly relay: RelayClient;\n readonly sessions: SessionTracker;\n readonly pairing: PairingStore;\n /** Report a caught error on both surfaces. */\n fail(context: string, error: unknown): void;\n /** Report an already human-readable failure on both surfaces. */\n report(context: string, message: string, about?: ReportAbout): void;\n}\n\nexport function createHub(parts: Omit<Hub, 'fail' | 'report'>): Hub {\n const report = (context: string, message: string, about: ReportAbout = {}): void => {\n parts.log.error('%s: %s', context, message);\n parts.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'error',\n message: truncate(message, ERROR_CHARS),\n context,\n ...(about.sessionId === undefined ? {} : { sessionId: about.sessionId }),\n ...(about.reason === undefined ? {} : { reason: about.reason }),\n });\n };\n return {\n ...parts,\n report,\n fail(context: string, error: unknown): void {\n report(context, error instanceof Error ? error.message : String(error));\n },\n };\n}\n","/**\n * `ask_user_question` forwarding.\n *\n * Seam: the `tools/execute` around-dispatch waterfall, NOT `tools/pre-execute`.\n * `PreToolDecision` is only allow/deny/ask (packages/core/tools/src/index.ts:588)\n * and cannot carry a tool result, so it can never answer the question — while a\n * `tools/execute` listener returns a `ToolExecutionResult` directly\n * (index.ts:163) and its `next()` runs the real tool body, which is exactly the\n * approval race shape.\n *\n * Authoring only `value` is enough: `normalizeDispatchResult`\n * (packages/core/tools/src/index.ts:1401) pushes a wrapper's result back\n * through the owning tool's `output.schema` and `output.render`, so a\n * phone-answered call is indistinguishable from a locally answered one.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { ToolDispatchExecution, ToolExecutionResult } from '@deepseek-ai/dsh-tools';\nimport { clampBytes, truncate } from '@dsh-dispatch/shared';\nimport type {\n PushCompactMsg,\n QuestionAnswer,\n QuestionItem,\n QuestionOption,\n QuestionRequestMsg,\n QuestionRespondMsg,\n QuestionResolution,\n} from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\n\n/** Registered name of the tool this module answers for. */\nconst ASK_USER_QUESTION = 'ask_user_question';\n\nconst PROMPT_CHARS = 2_000;\nconst PROMPT_BYTES = 4_000;\nconst LABEL_CHARS = 300;\nconst LABEL_BYTES = 600;\nconst REMINDER_MS = 5 * 60_000;\nconst MAX_REMINDERS = 6;\n\n/** The dsh tool's argument shape (packages/interaction/tool-ask-user/src/index.ts). */\ninterface RawOption {\n label: string;\n description?: string;\n}\ninterface RawQuestion {\n id: string;\n question: string;\n header?: string;\n options?: RawOption[];\n multi_select?: boolean;\n}\n\n/**\n * The tool's canonical return value; `selected` holds option LABELS.\n * A type alias, not an interface, so it satisfies `JsonValue`'s index\n * signature when handed back as a `ToolExecutionSuccess.value`.\n */\ntype AnswerValue = {\n answers: { id: string; selected: string[]; custom?: string }[];\n};\n\ntype Settle = (result: ToolExecutionResult, resolution: QuestionResolution) => void;\n\ninterface Forwarded {\n readonly questionId: string;\n readonly sessionId: string;\n readonly frame: QuestionRequestMsg;\n readonly push: PushCompactMsg;\n /** itemId → wire optionId → the exact dsh label to echo back. */\n readonly labels: Map<string, Map<string, string>>;\n}\n\ninterface Pending extends Forwarded {\n readonly settle: Settle;\n reminders: number;\n timer: ReturnType<typeof setTimeout> | undefined;\n}\n\nexport interface QuestionRouter {\n respond(message: QuestionRespondMsg): void;\n resendPending(): void;\n openCount(): number;\n}\n\nexport function installQuestions(hub: Hub): QuestionRouter {\n const pending = new Map<string, Pending>();\n\n hub.ctx.on('tools/execute', (exec, next) => {\n if (exec.name !== ASK_USER_QUESTION || exec.agent === undefined) return next();\n if (!hub.pairing.everPaired || !hub.relay.connected) return next();\n if (exec.signal.aborted) return next();\n return forward(hub, pending, exec, next);\n });\n\n return {\n respond(message: QuestionRespondMsg): void {\n const entry = pending.get(message.questionId);\n if (entry === undefined) {\n hub.relay.send(closedMsg(message.questionId, 'expired'));\n return;\n }\n const value = toAnswerValue(entry, message.answers);\n if (typeof value === 'string') {\n // Malformed input: the question stays OPEN so a corrected answer can\n // still land, and the phone is told exactly what was wrong.\n hub.report(`question ${entry.questionId}`, value);\n return;\n }\n entry.settle(successResult(value), 'phone');\n },\n resendPending(): void {\n for (const entry of pending.values()) hub.relay.send(entry.frame, entry.push);\n },\n openCount: () => pending.size,\n };\n}\n\nfunction forward(\n hub: Hub,\n pending: Map<string, Pending>,\n exec: ToolDispatchExecution,\n next: () => Promise<ToolExecutionResult>,\n): Promise<ToolExecutionResult> {\n const normalized = normalize(exec.arguments);\n if (normalized === null) {\n // Not a shape we can represent: let the real tool run and complain, rather\n // than answering a question we did not understand.\n hub.report('question', 'ask_user_question 参数无法解析,已交回本机处理');\n return next();\n }\n const questionId = randomUUID();\n const sessionId = exec.agent?.id ?? '';\n const frame: QuestionRequestMsg = {\n v: 1, ts: Date.now(), type: 'question.request',\n questionId, sessionId, items: normalized.items, createdAt: Date.now(),\n };\n const push: PushCompactMsg = {\n v: 1, ts: Date.now(), type: 'push', kind: 'question', sessionId,\n title: normalized.items[0]?.prompt.slice(0, 120) ?? '需要你回答一个问题',\n };\n if (!hub.relay.send(frame, push)) {\n hub.report('question', '提问未能推送到手机(relay 未连接或内容过大),已交回本机处理');\n return next();\n }\n const forwarded: Forwarded = { questionId, sessionId, frame, push, labels: normalized.labels };\n return race(hub, pending, forwarded, exec, next);\n}\n\nfunction race(\n hub: Hub,\n pending: Map<string, Pending>,\n forwarded: Forwarded,\n exec: ToolDispatchExecution,\n next: () => Promise<ToolExecutionResult>,\n): Promise<ToolExecutionResult> {\n const { questionId } = forwarded;\n return new Promise<ToolExecutionResult>((resolve) => {\n let settled = false;\n let localFailure: ToolExecutionResult | undefined;\n const settle: Settle = (result, resolution) => {\n if (settled) return;\n settled = true;\n const entry = pending.get(questionId);\n if (entry?.timer !== undefined) clearTimeout(entry.timer);\n pending.delete(questionId);\n exec.signal.removeEventListener('abort', onAbort);\n hub.relay.send(closedMsg(questionId, resolution));\n resolve(result);\n };\n function onAbort(): void {\n settle(localFailure ?? abortedResult(), 'cancelled');\n }\n exec.signal.addEventListener('abort', onAbort, { once: true });\n const entry: Pending = { ...forwarded, settle, reminders: 0, timer: undefined };\n pending.set(questionId, entry);\n armReminder(hub, entry);\n // Only a SUCCESS counts as a local answer. The tool body never rejects —\n // it returns an error result (packages/core/tools/src/index.ts:1554) — so a\n // missing provider (NO_PROVIDER, upstream #2544) or an abort lands here as\n // isError, and the phone stays the live answerer.\n void next().then((result) => {\n if (!result.isError) settle(result, 'local');\n else localFailure = result;\n }, (error: unknown) => {\n hub.fail(`question ${questionId}: local provider failed`, error);\n });\n });\n}\n\n/** Never auto-answer; nudge, then go quiet while staying open. */\nfunction armReminder(hub: Hub, entry: Pending): void {\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.reminders += 1;\n hub.relay.send(entry.frame, entry.push);\n if (entry.reminders >= MAX_REMINDERS) {\n hub.log.warn(\n 'question %s still unanswered after %d reminders; it stays open (never auto-answered)',\n entry.questionId, entry.reminders,\n );\n return;\n }\n armReminder(hub, entry);\n }, REMINDER_MS);\n entry.timer.unref?.();\n}\n\n/**\n * Project the tool's arguments onto the wire form.\n *\n * `header` and an option's `description` have no wire slot, so they are folded\n * into the text a human reads rather than dropped — a visible degradation, per\n * docs/PRODUCT.md 失败可见.\n */\nfunction normalize(args: unknown): { items: QuestionItem[]; labels: Forwarded['labels'] } | null {\n const questions = (args as { questions?: unknown } | null)?.questions;\n if (!Array.isArray(questions) || questions.length === 0) return null;\n const items: QuestionItem[] = [];\n const labels: Forwarded['labels'] = new Map();\n for (const raw of questions as RawQuestion[]) {\n if (typeof raw?.id !== 'string' || typeof raw.question !== 'string') return null;\n const options: QuestionOption[] = [];\n const byId = new Map<string, string>();\n (raw.options ?? []).forEach((option, index) => {\n if (typeof option?.label !== 'string') return;\n const id = `o${String(index)}`;\n byId.set(id, option.label);\n options.push({ id, label: clamp(decorate(option.label, option.description), LABEL_CHARS, LABEL_BYTES) });\n });\n labels.set(raw.id, byId);\n items.push({\n id: raw.id,\n prompt: clamp(prefix(raw.header, raw.question), PROMPT_CHARS, PROMPT_BYTES),\n options,\n multiSelect: raw.multi_select === true,\n // The tool's output schema always permits `custom`, so free text is\n // always a valid answer — and the only answer when there are no options.\n allowFreeText: true,\n });\n }\n return items.length === 0 ? null : { items, labels };\n}\n\nconst decorate = (label: string, description?: string): string =>\n description === undefined || description === '' ? label : `${label} — ${description}`;\n\nconst prefix = (header: string | undefined, question: string): string =>\n header === undefined || header === '' ? question : `${header}\\n\\n${question}`;\n\nconst clamp = (text: string, chars: number, bytes: number): string =>\n clampBytes(truncate(text, chars), bytes);\n\n/**\n * Validate a phone answer and project it onto the tool's canonical value.\n * `selected` carries option LABELS, not ids\n * (packages/interaction/user-questions/src/types.ts, AskUserQuestionAnswerItem).\n * @returns the value to return from the tool, or a human-readable complaint.\n */\nfunction toAnswerValue(entry: Forwarded, answers: readonly QuestionAnswer[]): AnswerValue | string {\n if (!Array.isArray(answers)) return '回答格式无效:answers 不是数组';\n const seen = new Set<string>();\n const projected: AnswerValue['answers'] = [];\n for (const answer of answers) {\n const options = entry.labels.get(answer.itemId);\n if (options === undefined) return `回答引用了未知的问题 id:${String(answer.itemId)}`;\n if (seen.has(answer.itemId)) return `问题 ${answer.itemId} 被回答了多次`;\n seen.add(answer.itemId);\n const selected: string[] = [];\n for (const optionId of answer.optionIds ?? []) {\n const label = options.get(optionId);\n if (label === undefined) return `问题 ${answer.itemId} 的选项 id 未知:${String(optionId)}`;\n selected.push(label);\n }\n const item = entry.frame.items.find(candidate => candidate.id === answer.itemId);\n if (item !== undefined && !item.multiSelect && selected.length > 1) {\n return `问题 ${answer.itemId} 是单选,却收到 ${String(selected.length)} 个选项`;\n }\n const custom = answer.freeText;\n if (selected.length === 0 && (custom === undefined || custom === '')) {\n return `问题 ${answer.itemId} 既没有选项也没有文字回答`;\n }\n projected.push({ id: answer.itemId, selected, ...custom === undefined || custom === '' ? {} : { custom } });\n }\n const missing = entry.frame.items.filter(item => !seen.has(item.id)).map(item => item.id);\n if (missing.length > 0) return `还有问题未回答:${missing.join(', ')}`;\n return { answers: projected };\n}\n\n/**\n * `content` here is a placeholder: the registry re-renders it through the\n * tool's own `output.render` in `normalizeDispatchResult`.\n */\nfunction successResult(value: AnswerValue): ToolExecutionResult {\n return { isError: false, value, content: [{ type: 'text', text: JSON.stringify(value) }] };\n}\n\n/** Same shape as the registry's own `toolErrorResult` (tools/src/index.ts:1870). */\nfunction abortedResult(): ToolExecutionResult {\n const message = 'ask_user_question was cancelled before the user answered';\n return { isError: true, error: { message }, content: [{ type: 'text', text: `Error: ${message}` }] };\n}\n\nfunction closedMsg(questionId: string, resolution: QuestionResolution) {\n return { v: 1, ts: Date.now(), type: 'question.closed', questionId, resolution } as const;\n}\n","/**\n * Outbound wss client to the dsh-dispatch relay. Uses Node's global\n * `WebSocket` (Node >= 22), so the plugin adds no transport dependency.\n *\n * Everything crossing this boundary is sealed with the pairing key: the relay\n * routes ciphertext and learns nothing but room id, role and message size.\n */\n\nimport {\n deriveKey,\n deriveRoomId,\n MAX_ENVELOPE_BYTES,\n open,\n seal,\n} from '@dsh-dispatch/shared';\nimport type {\n MachineToPhoneMsg,\n MsgFrame,\n PhoneToMachineMsg,\n PushCompactMsg,\n} from '@dsh-dispatch/shared';\nimport type { Log } from './log.js';\n\nconst BACKOFF_BASE_MS = 500;\nconst BACKOFF_MAX_MS = 30_000;\n/** Consecutive connect failures after which we surface a visible error state. */\nconst GIVE_UP_AFTER = 10;\n/** Retry interval once we have surfaced the error; we never stop trying. */\nconst GIVE_UP_RETRY_MS = 60_000;\n/** Decryption failures inside this window collapse into one log line. */\nconst TAMPER_LOG_WINDOW_MS = 30_000;\n\n/** Everything `/dispatch-status` needs to describe the link in one line. */\nexport interface RelayStatus {\n readonly connected: boolean;\n readonly phoneOnline: boolean;\n readonly consecutiveFailures: number;\n readonly gaveUp: boolean;\n readonly room: string;\n readonly decryptFailures: number;\n}\n\nexport interface RelayClientOptions {\n readonly url: string;\n readonly secret: Uint8Array;\n readonly log: Log;\n /** A validated inner message arrived from the phone. */\n onMessage(message: PhoneToMachineMsg): void;\n /** The relay accepted our hello — time to publish status and a snapshot. */\n onConnected(): void;\n /** A phone joined or left this room. */\n onPhonePresence(online: boolean): void;\n}\n\nexport class RelayClient {\n readonly #options: RelayClientOptions;\n #key: Uint8Array;\n #room: string;\n #socket: WebSocket | undefined;\n #retry: ReturnType<typeof setTimeout> | undefined;\n #stopped = true;\n #connected = false;\n #phoneOnline = false;\n #failures = 0;\n #gaveUp = false;\n #tamperBurst = 0;\n #tamperWindowStart = 0;\n #tamperTotal = 0;\n\n constructor(options: RelayClientOptions) {\n this.#options = options;\n this.#key = deriveKey(options.secret);\n this.#room = deriveRoomId(options.secret);\n }\n\n get connected(): boolean {\n return this.#connected;\n }\n\n get phoneOnline(): boolean {\n return this.#phoneOnline;\n }\n\n status(): RelayStatus {\n return {\n connected: this.#connected,\n phoneOnline: this.#phoneOnline,\n consecutiveFailures: this.#failures,\n gaveUp: this.#gaveUp,\n room: this.#room,\n decryptFailures: this.#tamperTotal,\n };\n }\n\n start(): void {\n if (!this.#stopped) return;\n this.#stopped = false;\n this.#connect();\n }\n\n stop(): void {\n this.#stopped = true;\n if (this.#retry !== undefined) clearTimeout(this.#retry);\n this.#retry = undefined;\n this.#teardownSocket();\n this.#connected = false;\n this.#phoneOnline = false;\n }\n\n /** Adopt a freshly generated secret: new room, new key, new connection. */\n rekey(secret: Uint8Array): void {\n this.#key = deriveKey(secret);\n this.#room = deriveRoomId(secret);\n this.#failures = 0;\n this.#gaveUp = false;\n if (this.#stopped) return;\n this.stop();\n this.start();\n }\n\n /**\n * Seal and send one inner message. `push` rides along so the relay can wake\n * an offline phone through Web Push without ever seeing the plaintext.\n * @returns whether the frame reached the socket.\n */\n send(message: MachineToPhoneMsg, push?: PushCompactMsg): boolean {\n const socket = this.#socket;\n if (socket === undefined || !this.#connected) return false;\n const frame: MsgFrame = {\n kind: 'msg',\n room: this.#room,\n payload: seal(message, this.#key),\n push: push === undefined ? null : { payload: seal(push, this.#key), tag: push.kind },\n };\n const text = JSON.stringify(frame);\n if (text.length > MAX_ENVELOPE_BYTES) {\n this.#options.log.error(\n 'relay: refusing to send an oversized %s envelope (%d bytes > %d); this is a truncation bug',\n message.type, text.length, MAX_ENVELOPE_BYTES,\n );\n return false;\n }\n socket.send(text);\n return true;\n }\n\n #connect(): void {\n if (this.#stopped) return;\n let socket: WebSocket;\n try {\n socket = new WebSocket(this.#options.url);\n } catch (error) {\n this.#options.log.error('relay: cannot open %s: %s', this.#options.url, error);\n this.#scheduleRetry();\n return;\n }\n this.#socket = socket;\n socket.addEventListener('open', () => {\n socket.send(JSON.stringify({ kind: 'hello', room: this.#room, role: 'machine' }));\n });\n socket.addEventListener('message', (event: MessageEvent) => {\n this.#onFrame(event.data);\n });\n socket.addEventListener('error', () => {\n // 'close' always follows; the event itself carries no useful detail.\n });\n socket.addEventListener('close', (event) => {\n if (this.#socket !== socket) return;\n const code = (event as unknown as { code?: number }).code;\n this.#onDown(`socket closed (${String(code ?? 'no code')})`);\n });\n }\n\n #onFrame(data: unknown): void {\n if (typeof data !== 'string') return;\n let frame: unknown;\n try {\n frame = JSON.parse(data);\n } catch (error) {\n this.#options.log.error('relay: dropped an unparseable frame: %s', error);\n return;\n }\n const kind = (frame as { kind?: unknown }).kind;\n if (kind === 'hello-ok') this.#onHelloOk(frame);\n else if (kind === 'presence') this.#onPresence(frame);\n else if (kind === 'msg') this.#onData(frame);\n else if (kind === 'error') {\n const code = String((frame as { code?: unknown }).code ?? 'unknown');\n this.#options.log.error('relay: rejected our frame with code \"%s\"', code);\n } else {\n this.#options.log.warn('relay: ignoring unknown frame kind \"%s\"', String(kind));\n }\n }\n\n #onHelloOk(frame: unknown): void {\n this.#connected = true;\n this.#failures = 0;\n this.#gaveUp = false;\n const peers = (frame as { peers?: { phone?: unknown } }).peers;\n const phones = typeof peers?.phone === 'number' ? peers.phone : 0;\n this.#options.log.info('relay: connected (room %s…, phones online: %d)', this.#room.slice(0, 8), phones);\n this.#options.onConnected();\n if (phones > 0) this.#setPhoneOnline(true);\n }\n\n #onPresence(frame: unknown): void {\n const { role, online } = frame as { role?: unknown; online?: unknown };\n if (role !== 'phone' || typeof online !== 'boolean') return;\n // A join is an EVENT, not a state change: the relay broadcasts one per phone\n // socket, and every one of them is a phone that has just arrived knowing\n // nothing. Deduping on `#phoneOnline` left the second phone in a room with\n // no machine.status, no snapshot and — worst — no resend of the approvals\n // still waiting for an answer. Only \"offline\" is a state (the relay reports\n // it once the LAST phone leaves).\n if (online) {\n this.#phoneOnline = true;\n this.#options.onPhonePresence(true);\n return;\n }\n this.#setPhoneOnline(false);\n }\n\n #setPhoneOnline(online: boolean): void {\n if (this.#phoneOnline === online) return;\n this.#phoneOnline = online;\n this.#options.onPhonePresence(online);\n }\n\n #onData(frame: unknown): void {\n const payload = (frame as { payload?: unknown }).payload;\n if (typeof payload !== 'string') {\n this.#options.log.error('relay: dropped a msg frame with no payload');\n return;\n }\n const plain = open(payload, this.#key);\n if (plain === null) {\n this.#onTamper();\n return;\n }\n const message = parseInbound(plain);\n if (message === null) {\n this.#options.log.warn(\n 'relay: ignoring inner message of type \"%s\" — unknown type or missing/invalid fields '\n + '(the phone may be newer than this plugin)',\n String((plain as { type?: unknown }).type),\n );\n return;\n }\n this.#options.onMessage(message);\n }\n\n /** Decryption failure is never silent: it means tampering or a stale pairing. */\n #onTamper(): void {\n this.#tamperTotal += 1;\n this.#tamperBurst += 1;\n const now = Date.now();\n if (now - this.#tamperWindowStart < TAMPER_LOG_WINDOW_MS) return;\n this.#tamperWindowStart = now;\n this.#options.log.error(\n 'relay: %d message(s) failed to decrypt — wrong key or tampering. Re-pair with /dispatch-repair '\n + 'and re-scan the QR on every phone.',\n this.#tamperBurst,\n );\n this.#tamperBurst = 0;\n }\n\n #onDown(reason: string): void {\n this.#teardownSocket();\n if (this.#stopped) return;\n this.#connected = false;\n this.#setPhoneOnline(false);\n this.#failures += 1;\n this.#options.log.debug('relay: %s, attempt %d', reason, this.#failures);\n this.#scheduleRetry();\n }\n\n #scheduleRetry(): void {\n if (this.#stopped || this.#retry !== undefined) return;\n if (this.#failures >= GIVE_UP_AFTER && !this.#gaveUp) {\n this.#gaveUp = true;\n this.#options.log.error(\n 'relay: %d consecutive failures connecting to %s — check that the relay is reachable. '\n + 'Still retrying every %ds.',\n this.#failures, this.#options.url, GIVE_UP_RETRY_MS / 1000,\n );\n }\n this.#retry = setTimeout(() => {\n this.#retry = undefined;\n this.#connect();\n }, this.#backoffMs());\n this.#retry.unref?.();\n }\n\n #backoffMs(): number {\n if (this.#gaveUp) return GIVE_UP_RETRY_MS;\n const exponential = Math.min(BACKOFF_BASE_MS * 2 ** this.#failures, BACKOFF_MAX_MS);\n return Math.round(exponential * (0.75 + Math.random() * 0.5));\n }\n\n #teardownSocket(): void {\n const socket = this.#socket;\n this.#socket = undefined;\n if (socket === undefined) return;\n try {\n socket.close();\n } catch (error) {\n this.#options.log.debug('relay: error closing socket: %s', error);\n }\n }\n}\n\nconst INBOUND_FIELDS: Record<PhoneToMachineMsg['type'], readonly string[]> = {\n 'sessions.get': [],\n 'approval.respond': ['requestId', 'approvalId', 'decision'],\n 'question.respond': ['requestId', 'questionId'],\n 'dispatch.request': ['requestId', 'prompt'],\n 'session.message': ['requestId', 'sessionId', 'text'],\n};\n\n/**\n * Accept only inner messages this plugin understands, with their required\n * string fields present. Unknown `type`/`v` returns null so the caller logs it\n * (forward compatibility per docs/PROTOCOL.md) rather than crashing.\n */\nexport function parseInbound(value: unknown): PhoneToMachineMsg | null {\n if (typeof value !== 'object' || value === null) return null;\n const record = value as Record<string, unknown>;\n if (record['v'] !== 1) return null;\n const type = record['type'];\n if (typeof type !== 'string' || !(type in INBOUND_FIELDS)) return null;\n const required = INBOUND_FIELDS[type as PhoneToMachineMsg['type']];\n for (const field of required) {\n if (typeof record[field] !== 'string' || record[field] === '') return null;\n }\n if (type === 'approval.respond' && record['decision'] !== 'allow' && record['decision'] !== 'deny') {\n return null;\n }\n if (type === 'dispatch.request' && typeof record['worktree'] !== 'boolean') return null;\n // Shape only — an unknown `access` value is a protocol-level complaint the\n // dispatch handler answers visibly, not something to drop on the floor here.\n if (type === 'question.respond' && !isAnswerList(record['answers'])) return null;\n return record as unknown as PhoneToMachineMsg;\n}\n\n/** `answers: [{ itemId, optionIds, freeText? }]`, checked structurally. */\nfunction isAnswerList(value: unknown): boolean {\n if (!Array.isArray(value) || value.length === 0) return false;\n return value.every((entry: unknown) => {\n const answer = entry as { itemId?: unknown; optionIds?: unknown; freeText?: unknown };\n if (typeof answer?.itemId !== 'string') return false;\n if (!Array.isArray(answer.optionIds)) return false;\n if (!answer.optionIds.every(id => typeof id === 'string')) return false;\n return answer.freeText === undefined || typeof answer.freeText === 'string';\n });\n}\n","/**\n * Live session board: maps dsh agent lifecycle events onto the protocol's\n * `Session` objects and pushes `session.update` / `session.snapshot` to the\n * phone.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { Agent } from '@deepseek-ai/dsh-agent';\nimport { SessionId } from '@deepseek-ai/dsh-session';\nimport type {} from '@deepseek-ai/dsh-session-query';\nimport { truncate } from '@dsh-dispatch/shared';\nimport type { Session as WireSession, SessionState } from '@dsh-dispatch/shared';\n\n/** Permission tier as it appears on the wire. */\ntype SessionAccess = NonNullable<WireSession['access']>;\nimport type { Log } from './log.js';\nimport type { RelayClient } from './relay-client.js';\n\n/** docs/PROTOCOL.md: `title` = first 80 chars of the initial prompt. */\nconst TITLE_CHARS = 80;\n/** Tool names are short by convention; a pathological one must not widen the card. */\nconst ACTIVITY_CHARS = 40;\n/**\n * A busy turn can call tools faster than a phone can read them, and every\n * update is a frame on someone's mobile data. Activity changes coalesce to this\n * gap; state changes never wait behind them.\n */\nconst ACTIVITY_MIN_GAP_MS = 800;\n\n/** Lifecycle state before the approval overlay is applied. */\ntype BaseState = 'idle' | 'running' | 'done' | 'error';\n\ninterface Tracked {\n sessionId: string;\n title: string;\n cwd: string;\n base: BaseState;\n openApprovals: number;\n lastActivity: number;\n dispatched: boolean;\n /** Permission tier, for audit. Once 'full' it is never downgraded. */\n access: SessionAccess;\n /**\n * A dispatched session whose `turn.final` has been sent. It stays `done` on\n * the phone until a NEW turn starts — the idle status that follows every\n * turn must not demote the card back to 空闲.\n */\n finalized: boolean;\n /** Name of the tool running right now, undefined when nothing is in flight. */\n activity: string | undefined;\n /** The call `activity` belongs to, so a late sibling result cannot clear it. */\n activityCallId: string | undefined;\n}\n\nexport class SessionTracker {\n readonly #ctx: Context;\n readonly #relay: RelayClient;\n readonly #log: Log;\n readonly #tracked = new Map<string, Tracked>();\n readonly #published = new Map<string, string>();\n readonly #activityAt = new Map<string, number>();\n readonly #activityTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n constructor(ctx: Context, relay: RelayClient, log: Log) {\n this.#ctx = ctx;\n this.#relay = relay;\n this.#log = log;\n }\n\n /** Subscribe the agent lifecycle. Registrations unwind with the plugin. */\n install(): void {\n this.#ctx.on('agent/created', ({ agent }) => {\n this.#adopt(agent);\n this.#publish(agent.id);\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/session-start', ({ agent }) => {\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/status', ({ agent, status }) => {\n this.#update(agent.id, (entry) => {\n // The trailing idle of a finished turn is not new information; only a\n // fresh `running` reopens a finalized session.\n if (status === 'idle' && entry.finalized) return;\n entry.finalized = false;\n entry.base = status;\n });\n });\n this.#ctx.on('agent/error', ({ agent, error }) => {\n this.#log.error('session %s: agent error: %s', agent.id, error);\n this.#update(agent.id, entry => { entry.base = 'error'; });\n });\n this.#ctx.on('agent/turn-stopping', ({ agent }) => {\n // Nothing is running once the turn stops; a stale tool name would read as a hang.\n this.#update(agent.id, (entry) => { entry.activity = undefined; entry.activityCallId = undefined; });\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/disposed', ({ agent }) => {\n this.#update(agent.id, (entry) => { entry.base = 'done'; entry.activity = undefined; });\n this.#tracked.delete(agent.id);\n this.#published.delete(agent.id);\n this.#clearActivityTimer(agent.id);\n });\n // The only feed of what a session is actually doing: `agent/*` reports\n // status, never which tool. Filtered to the two event types the card shows.\n this.#ctx.on('session/event', (session, event) => {\n if (event.type === 'tool/call') {\n this.#setActivity(session.id, truncate(event.data.name, ACTIVITY_CHARS), event.data.callId);\n return;\n }\n // The result carries its call id one level down, on the tool-result block.\n if (event.type === 'tool/result') {\n this.#clearActivity(session.id, event.data.message.content[0].toolCallId);\n }\n });\n }\n\n /** Mark a session as started by us, with the dispatch prompt as its title. */\n markDispatched(sessionId: string, prompt: string, access: SessionAccess = 'standard'): void {\n this.#update(sessionId, (entry) => {\n entry.dispatched = true;\n entry.title = truncate(prompt.trim().split('\\n', 1)[0] ?? '', TITLE_CHARS);\n // Full access is an audit fact: recorded permanently, never downgraded.\n if (access === 'full') entry.access = 'full';\n });\n }\n\n isDispatched(sessionId: string): boolean {\n return this.#tracked.get(sessionId)?.dispatched ?? false;\n }\n\n /**\n * A dispatched turn has closed and its `turn.final` is on its way. Latch the\n * card at done/error so the trailing idle status cannot demote it.\n */\n markTurnComplete(sessionId: string): void {\n this.#update(sessionId, (entry) => {\n entry.finalized = true;\n if (entry.base !== 'error') entry.base = 'done';\n });\n }\n\n hasError(sessionId: string): boolean {\n return this.#tracked.get(sessionId)?.base === 'error';\n }\n\n /** The live agent for a session id, or undefined when it is not live. */\n agentOf(sessionId: string): Agent | undefined {\n return this.#ctx.agents.get(SessionId(sessionId));\n }\n\n /** Whether a follow-up would land: exactly the check `session.message` makes. */\n #isLive(sessionId: string): boolean {\n return this.agentOf(sessionId) !== undefined;\n }\n\n /** An approval for this session is now waiting on a human. */\n approvalOpened(sessionId: string): void {\n this.#update(sessionId, entry => { entry.openApprovals += 1; });\n }\n\n approvalClosed(sessionId: string): void {\n this.#update(sessionId, entry => {\n entry.openApprovals = Math.max(0, entry.openApprovals - 1);\n });\n }\n\n /** Active sessions only — the phone board never shows cold history. */\n async snapshot(): Promise<WireSession[]> {\n const records = await this.#ctx.sessionQuery.listSessions();\n const wire: WireSession[] = [];\n for (const record of records) {\n if (!record.live) continue;\n const id = record.header.id;\n const known = this.#tracked.get(id) ?? this.#adopt(this.#ctx.agents.get(id));\n wire.push(known === undefined\n ? this.#fallbackWire(id, record.header.cwd ?? '', record.header.createdAt)\n : toWire(known, this.#isLive(id)));\n }\n return wire;\n }\n\n #fallbackWire(sessionId: string, cwd: string, createdAt: number): WireSession {\n return {\n sessionId,\n title: truncate(sessionId, TITLE_CHARS),\n cwd,\n state: 'idle',\n lastActivity: createdAt,\n dispatched: false,\n access: 'standard',\n // listSessions() already filtered to live records.\n live: true,\n };\n }\n\n #adopt(agent: Agent | undefined): Tracked | undefined {\n if (agent === undefined) return undefined;\n const existing = this.#tracked.get(agent.id);\n if (existing !== undefined) return existing;\n const entry: Tracked = {\n sessionId: agent.id,\n title: truncate(firstPrompt(agent) ?? agent.id, TITLE_CHARS),\n cwd: agent.session.header.cwd ?? '',\n base: agent.status,\n openApprovals: 0,\n lastActivity: Date.now(),\n dispatched: false,\n access: 'standard',\n finalized: false,\n activity: undefined,\n activityCallId: undefined,\n };\n this.#tracked.set(agent.id, entry);\n return entry;\n }\n\n #setActivity(sessionId: string, name: string, callId: string): void {\n const entry = this.#tracked.get(sessionId);\n if (entry === undefined || entry.activity === name) return;\n entry.activity = name;\n entry.activityCallId = callId;\n this.#publishActivity(sessionId);\n }\n\n /** Only the call that set the activity may clear it (tools can overlap). */\n #clearActivity(sessionId: string, callId: string): void {\n const entry = this.#tracked.get(sessionId);\n if (entry === undefined || entry.activityCallId !== callId) return;\n entry.activity = undefined;\n entry.activityCallId = undefined;\n this.#publishActivity(sessionId);\n }\n\n /** Trailing-edge coalescing, so a tool-heavy turn cannot flood the phone. */\n #publishActivity(sessionId: string): void {\n const elapsed = Date.now() - (this.#activityAt.get(sessionId) ?? 0);\n if (elapsed >= ACTIVITY_MIN_GAP_MS) {\n this.#activityAt.set(sessionId, Date.now());\n this.#publish(sessionId);\n return;\n }\n if (this.#activityTimers.has(sessionId)) return;\n const timer = setTimeout(() => {\n this.#activityTimers.delete(sessionId);\n this.#activityAt.set(sessionId, Date.now());\n // #publish is fingerprint-guarded: a no-op if the burst settled back.\n this.#publish(sessionId);\n }, ACTIVITY_MIN_GAP_MS - elapsed);\n timer.unref?.();\n this.#activityTimers.set(sessionId, timer);\n }\n\n #clearActivityTimer(sessionId: string): void {\n const timer = this.#activityTimers.get(sessionId);\n if (timer !== undefined) clearTimeout(timer);\n this.#activityTimers.delete(sessionId);\n this.#activityAt.delete(sessionId);\n }\n\n #update(sessionId: string, mutate: (entry: Tracked) => void): void {\n const entry = this.#tracked.get(sessionId) ?? this.#adopt(this.#ctx.agents.get(SessionId(sessionId)));\n if (entry === undefined) return;\n mutate(entry);\n entry.lastActivity = Date.now();\n this.#publish(sessionId);\n }\n\n /** Emit `session.update` only when the phone-visible projection changed. */\n #publish(sessionId: string): void {\n const entry = this.#tracked.get(sessionId);\n if (entry === undefined) return;\n const session = toWire(entry, this.#isLive(sessionId));\n const fingerprint = `${session.state}|${session.title}|${session.cwd}`\n + `|${String(session.dispatched)}|${String(session.access)}`\n + `|${String(session.live)}|${session.activity ?? ''}`;\n if (this.#published.get(sessionId) === fingerprint) return;\n this.#published.set(sessionId, fingerprint);\n this.#relay.send({ v: 1, ts: Date.now(), type: 'session.update', session });\n }\n\n async #refreshTitle(agent: Agent): Promise<void> {\n const entry = this.#tracked.get(agent.id);\n if (entry === undefined || entry.dispatched) return;\n try {\n const snapshot = await this.#ctx.sessionQuery.readTitle(SessionId(agent.id));\n const title = snapshot?.title ?? firstPrompt(agent);\n if (title === undefined || title === '') return;\n entry.title = truncate(title, TITLE_CHARS);\n this.#publish(agent.id);\n } catch (error) {\n this.#log.error('session %s: could not read title: %s', agent.id, error);\n }\n }\n}\n\nfunction toWire(entry: Tracked, live: boolean): WireSession {\n return {\n sessionId: entry.sessionId,\n title: entry.title,\n cwd: entry.cwd,\n state: stateOf(entry),\n lastActivity: entry.lastActivity,\n dispatched: entry.dispatched,\n access: entry.access,\n live,\n ...(entry.activity === undefined ? {} : { activity: entry.activity }),\n };\n}\n\n/**\n * `awaiting_approval` is an overlay, not a lifecycle state: it holds exactly\n * while our answerer has an open question for this session.\n */\nfunction stateOf(entry: Tracked): SessionState {\n if (entry.base === 'done' || entry.base === 'error') return entry.base;\n return entry.openApprovals > 0 ? 'awaiting_approval' : entry.base;\n}\n\n/** Fallback title source: the first human prompt in the session log. */\nfunction firstPrompt(agent: Agent): string | undefined {\n for (const event of agent.session.events) {\n if (event.type !== 'user/message') continue;\n const text = event.data.content\n .filter(block => block.type === 'text')\n .map(block => block.text)\n .join('')\n .trim();\n if (text !== '') return text.split('\\n', 1)[0];\n }\n return undefined;\n}\n","/**\n * Plugin version reported in `machine.status`.\n *\n * Kept as a literal rather than read from package.json so the bundled ESM\n * output has no runtime filesystem dependency. `tests/version.test.ts` fails\n * the build if this drifts from package.json.\n */\nexport const PLUGIN_VERSION = '0.3.0';\n","/**\n * Plugin configuration. Validated by Schemastery before `apply` runs, so every\n * field below is present with its default already filled in.\n */\n\nimport { hostname } from 'node:os';\nimport z from '@deepseek-ai/schemastery';\n\nexport interface Config {\n /** Relay websocket URL, e.g. `wss://relay.example.com`. */\n relay: string;\n /** Name shown on the phone's machine card. */\n machineName: string;\n /**\n * Absolute directories a dispatched agent may run in. Empty (the default)\n * keeps remote dispatch off — approval forwarding still works.\n */\n allowedRoots: string[];\n /**\n * Whether the phone may request `access: 'full'`, which runs the session\n * under dsh's `danger-full-access` permission preset — full file access with\n * no approval prompts. Off by default; turning it on is a deliberate choice.\n */\n allowFullAccessDispatch: boolean;\n /** Where the pairing secret and dispatch worktrees live. */\n dataDir: string;\n /** Base URL of the PWA, used only to print the pairing convenience link. */\n pwaUrl: string;\n}\n\nexport const Config: z<Config> = z.object({\n relay: z.string().required(),\n machineName: z.string().default(hostname()),\n allowedRoots: z.array(z.string()).default([]),\n allowFullAccessDispatch: z.boolean().default(false),\n dataDir: z.string().default('~/.dsh-dispatch'),\n pwaUrl: z.string().default('http://localhost:5173'),\n});\n","/**\n * dsh-dispatch — command your DeepSeek Harness machines from your phone.\n *\n * Approvals raised anywhere in this dsh instance are forwarded to a paired\n * phone and answered there; the phone can also start new agent sessions in\n * configured directories and follow up on live ones. Everything between this\n * plugin and the phone is end-to-end encrypted; the relay only routes\n * ciphertext.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\n// Empty type imports carry the Context/Events declaration merges this plugin\n// listens on and calls into.\nimport type {} from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-commands';\nimport type {} from '@deepseek-ai/dsh-session-query';\nimport type {} from '@deepseek-ai/dsh-user-approval';\nimport type { PhoneToMachineMsg } from '@dsh-dispatch/shared';\nimport { installApprovals } from './approvals.js';\nimport type { ApprovalRouter } from './approvals.js';\nimport { installCommands } from './commands.js';\nimport type { Config } from './config.js';\nimport { installDispatch, resolvedRoots } from './dispatch.js';\nimport type { DispatchRouter } from './dispatch.js';\nimport { createHub } from './hub.js';\nimport type { Hub } from './hub.js';\nimport { PairingStore } from './pairing.js';\nimport { installQuestions } from './questions.js';\nimport type { QuestionRouter } from './questions.js';\nimport { RelayClient } from './relay-client.js';\nimport { SessionTracker } from './sessions.js';\nimport { PLUGIN_VERSION } from './version.js';\n\nexport { Config } from './config.js';\n\nexport const name = 'dsh-dispatch';\n\n/**\n * `approval` is deliberately absent: the plugin is useful (dispatch, session\n * board) in a composition without it, and the dossier's apiproxy precedent\n * guards the service rather than requiring it.\n */\nexport const inject = ['agents', 'commands', 'sessionQuery'];\n\n/** docs/PROTOCOL.md: machine.status on connect and every 60s. */\nconst STATUS_INTERVAL_MS = 60_000;\n\n/** The three inbound feature handlers, passed around as one bundle. */\nexport interface Routers {\n readonly approvals: ApprovalRouter;\n readonly questions: QuestionRouter;\n readonly dispatch: DispatchRouter;\n}\n\nexport function apply(ctx: Context, config: Config): void {\n const log = ctx.logger('dsh-dispatch');\n const pairing = PairingStore.open(config.dataDir, log);\n // The router is built from the relay, and the relay calls into the router:\n // late-bind the two callbacks rather than smuggling a half-built object.\n const deferred = { message(_: PhoneToMachineMsg): void {}, hello(): void {} };\n const relay = new RelayClient({\n url: config.relay,\n secret: pairing.secret,\n log,\n onMessage: message => { deferred.message(message); },\n onConnected: () => { deferred.hello(); },\n onPhonePresence: (online) => {\n if (!online) {\n log.info('relay: phone left the room');\n return;\n }\n pairing.markPaired();\n log.info('relay: phone joined the room');\n deferred.hello();\n },\n });\n const sessions = new SessionTracker(ctx, relay, log);\n const hub = createHub({ ctx, config, log, relay, sessions, pairing });\n const approvals = installApprovals(hub);\n const questions = installQuestions(hub);\n const dispatch = installDispatch(hub);\n const routers: Routers = { approvals, questions, dispatch };\n deferred.message = message => { route(hub, routers, message); };\n deferred.hello = () => {\n publishStatus(hub);\n approvals.resendPending();\n questions.resendPending();\n void publishSnapshot(hub);\n };\n sessions.install();\n installCommands(hub, routers);\n startRelay(hub);\n}\n\n/** Bring the link up, unless it cannot possibly work — say so once, loudly. */\nfunction startRelay(hub: Hub): void {\n const url = hub.config.relay;\n if (url === '') {\n hub.log.error(\n 'relay is not configured: set `relay: \\'wss://…\\'` on the dsh-dispatch row in your profile\\'s '\n + 'cordis.patch.yml. Approval forwarding and dispatch stay off until then.',\n );\n return;\n }\n if (!/^wss?:\\/\\//.test(url)) {\n hub.log.error('relay \"%s\" is not a ws:// or wss:// URL; refusing to connect', url);\n return;\n }\n hub.ctx.effect(() => {\n hub.relay.start();\n return () => { hub.relay.stop(); };\n }, 'dsh-dispatch: relay client');\n hub.ctx.effect(() => {\n const timer = setInterval(() => { publishStatus(hub); }, STATUS_INTERVAL_MS);\n timer.unref?.();\n return () => { clearInterval(timer); };\n }, 'dsh-dispatch: status heartbeat');\n}\n\nfunction route(hub: Hub, routers: Routers, message: PhoneToMachineMsg): void {\n const { approvals, questions, dispatch } = routers;\n switch (message.type) {\n case 'sessions.get':\n void publishSnapshot(hub);\n return;\n case 'approval.respond':\n approvals.respond(message);\n return;\n case 'question.respond':\n questions.respond(message);\n return;\n case 'dispatch.request':\n void dispatch.request(message).catch((error: unknown) => {\n hub.fail(`dispatch.request ${message.requestId}`, error);\n });\n return;\n case 'session.message':\n void dispatch.followup(message).catch((error: unknown) => {\n hub.fail(`session.message ${message.requestId}`, error);\n });\n }\n}\n\nfunction publishStatus(hub: Hub): void {\n hub.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'machine.status',\n machine: hub.config.machineName,\n pluginVersion: PLUGIN_VERSION,\n capabilities: {\n fullAccessDispatch: hub.config.allowFullAccessDispatch,\n questionForwarding: true,\n },\n allowedRoots: resolvedRoots(hub),\n });\n}\n\nasync function publishSnapshot(hub: Hub): Promise<void> {\n try {\n hub.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'session.snapshot',\n sessions: await hub.sessions.snapshot(),\n });\n } catch (error) {\n hub.fail('session.snapshot', error);\n }\n}\n"],"mappings":"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","names":["resolve","entry","prefix","randomUUID","resolve","join","randomUUID","resolve","ok","randomUUID","REMINDER_MS","MAX_REMINDERS","forward","closedMsg","randomUUID","race","resolve","entry","armReminder","SessionId","TITLE_CHARS","SessionId","name"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dispatch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Command your DeepSeek Harness machines from your phone: approval push, remote dispatch, session board. Built on DeepSeek Harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -33,6 +33,11 @@
|
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": "^22.19.0 || >=24.0.0"
|
|
35
35
|
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run"
|
|
40
|
+
},
|
|
36
41
|
"dependencies": {
|
|
37
42
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
38
43
|
"tweetnacl": "^1.0.3"
|
|
@@ -64,13 +69,13 @@
|
|
|
64
69
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
65
70
|
"@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2",
|
|
66
71
|
"@deepseek-ai/dsh-user-questions": "^0.1.1-rc.2",
|
|
72
|
+
"@dsh-dispatch/shared": "workspace:*",
|
|
67
73
|
"@types/node": "^22.19.0",
|
|
68
74
|
"@types/ws": "^8.18.1",
|
|
69
75
|
"tsup": "^8.5.1",
|
|
70
76
|
"typescript": "^7.0.2",
|
|
71
77
|
"vitest": "^4.1.11",
|
|
72
|
-
"ws": "^8.18.3"
|
|
73
|
-
"@dsh-dispatch/shared": "0.2.0"
|
|
78
|
+
"ws": "^8.18.3"
|
|
74
79
|
},
|
|
75
80
|
"repository": {
|
|
76
81
|
"type": "git",
|
|
@@ -78,10 +83,5 @@
|
|
|
78
83
|
"directory": "packages/plugin"
|
|
79
84
|
},
|
|
80
85
|
"homepage": "https://github.com/alextangson/dsh-dispatch#readme",
|
|
81
|
-
"bugs": "https://github.com/alextangson/dsh-dispatch/issues"
|
|
82
|
-
|
|
83
|
-
"build": "tsup",
|
|
84
|
-
"typecheck": "tsc --noEmit",
|
|
85
|
-
"test": "vitest run"
|
|
86
|
-
}
|
|
87
|
-
}
|
|
86
|
+
"bugs": "https://github.com/alextangson/dsh-dispatch/issues"
|
|
87
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 dsh-dispatch contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|