@clawos-dev/clawd 0.2.410 → 0.2.412
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/cli.cjs +157 -81
- package/dist/dispatch/mcp-server.cjs +10 -6
- package/dist/persona-defaults/persona-master/CLAUDE.md +12 -4
- package/dist/share-md-viewer-error.html +1 -1
- package/dist/share-md-viewer.html +1 -1
- package/dist/share-ui/assets/{guest-zY0ICv53.js → guest-BvnTjYPr.js} +130 -130
- package/dist/share-ui/assets/guest-mbQfNssK.css +32 -0
- package/dist/share-ui/guest.html +2 -2
- package/package.json +1 -1
- package/dist/share-ui/assets/guest-b29slU5m.css +0 -32
package/dist/cli.cjs
CHANGED
|
@@ -6630,19 +6630,23 @@ var init_shift_internal = __esm({
|
|
|
6630
6630
|
});
|
|
6631
6631
|
|
|
6632
6632
|
// ../protocol/src/channel.ts
|
|
6633
|
-
var MASTER_PERSONA_ID, ChannelSchema, nonBlank, ChannelCreateArgsSchema, ChannelUpdateArgsSchema, ChannelDeleteArgsSchema, ChannelListResultSchema, ChannelResultSchema, ChannelDeleteResultSchema;
|
|
6633
|
+
var MASTER_PERSONA_ID, ChannelMemberPersonaSchema, ChannelSchema, nonBlank, ChannelCreateArgsSchema, ChannelUpdateArgsSchema, ChannelDeleteArgsSchema, ChannelListResultSchema, ChannelResultSchema, ChannelDeleteResultSchema;
|
|
6634
6634
|
var init_channel = __esm({
|
|
6635
6635
|
"../protocol/src/channel.ts"() {
|
|
6636
6636
|
"use strict";
|
|
6637
6637
|
init_zod();
|
|
6638
6638
|
MASTER_PERSONA_ID = "persona-master";
|
|
6639
|
+
ChannelMemberPersonaSchema = external_exports.object({
|
|
6640
|
+
personaId: external_exports.string().min(1),
|
|
6641
|
+
deviceId: external_exports.string().min(1).optional()
|
|
6642
|
+
});
|
|
6639
6643
|
ChannelSchema = external_exports.object({
|
|
6640
6644
|
id: external_exports.string().min(1),
|
|
6641
6645
|
name: external_exports.string().min(1),
|
|
6642
|
-
/** 该频道里可被派活的 persona */
|
|
6643
|
-
|
|
6644
|
-
/** 人类成员(contact deviceId
|
|
6645
|
-
* P1 人类异步询问也用它 */
|
|
6646
|
+
/** 该频道里可被派活的 persona 全集(本机 + 远程) */
|
|
6647
|
+
memberPersonas: external_exports.array(ChannelMemberPersonaSchema),
|
|
6648
|
+
/** 人类成员(contact deviceId):可发 DM。**不隐含任何派活权限** —— 跨设备派活认的是
|
|
6649
|
+
* 上面那张 memberPersonas。P1 人类异步询问也用它 */
|
|
6646
6650
|
memberContactDeviceIds: external_exports.array(external_exports.string().min(1)),
|
|
6647
6651
|
createdAt: external_exports.string().min(1),
|
|
6648
6652
|
updatedAt: external_exports.string().min(1)
|
|
@@ -6650,13 +6654,13 @@ var init_channel = __esm({
|
|
|
6650
6654
|
nonBlank = external_exports.string().trim().min(1);
|
|
6651
6655
|
ChannelCreateArgsSchema = external_exports.object({
|
|
6652
6656
|
name: nonBlank,
|
|
6653
|
-
|
|
6657
|
+
memberPersonas: external_exports.array(ChannelMemberPersonaSchema).default([]),
|
|
6654
6658
|
memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).default([])
|
|
6655
6659
|
});
|
|
6656
6660
|
ChannelUpdateArgsSchema = external_exports.object({
|
|
6657
6661
|
channelId: external_exports.string().min(1),
|
|
6658
6662
|
name: nonBlank.optional(),
|
|
6659
|
-
|
|
6663
|
+
memberPersonas: external_exports.array(ChannelMemberPersonaSchema).optional(),
|
|
6660
6664
|
memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).optional()
|
|
6661
6665
|
});
|
|
6662
6666
|
ChannelDeleteArgsSchema = external_exports.object({ channelId: external_exports.string().min(1) });
|
|
@@ -14365,22 +14369,49 @@ var init_tool_result_extra = __esm({
|
|
|
14365
14369
|
function cwdToHashDir(cwd) {
|
|
14366
14370
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
14367
14371
|
}
|
|
14368
|
-
function
|
|
14369
|
-
const
|
|
14372
|
+
function subagentsDirCandidates(projectsRoot, cwd, toolSessionId) {
|
|
14373
|
+
const out = [];
|
|
14374
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14375
|
+
const add = (dir) => {
|
|
14376
|
+
if (seen.has(dir)) return;
|
|
14377
|
+
try {
|
|
14378
|
+
if (!import_node_fs6.default.statSync(dir).isDirectory()) return;
|
|
14379
|
+
} catch {
|
|
14380
|
+
return;
|
|
14381
|
+
}
|
|
14382
|
+
seen.add(dir);
|
|
14383
|
+
out.push(dir);
|
|
14384
|
+
};
|
|
14385
|
+
add(import_node_path5.default.join(projectsRoot, cwdToHashDir(cwd), toolSessionId, "subagents"));
|
|
14370
14386
|
let entries;
|
|
14371
14387
|
try {
|
|
14372
|
-
entries = import_node_fs6.default.readdirSync(
|
|
14388
|
+
entries = import_node_fs6.default.readdirSync(projectsRoot, { withFileTypes: true });
|
|
14373
14389
|
} catch {
|
|
14374
|
-
return
|
|
14390
|
+
return out;
|
|
14391
|
+
}
|
|
14392
|
+
for (const ent of entries) {
|
|
14393
|
+
if (!ent.isDirectory()) continue;
|
|
14394
|
+
add(import_node_path5.default.join(projectsRoot, ent.name, toolSessionId, "subagents"));
|
|
14375
14395
|
}
|
|
14396
|
+
return out;
|
|
14397
|
+
}
|
|
14398
|
+
function newestSubagentMtimeMs(projectsRoot, cwd, toolSessionId) {
|
|
14376
14399
|
let newest = null;
|
|
14377
|
-
for (const
|
|
14378
|
-
|
|
14379
|
-
if (!e.name.startsWith("agent-") || !e.name.endsWith(".jsonl")) continue;
|
|
14400
|
+
for (const dir of subagentsDirCandidates(projectsRoot, cwd, toolSessionId)) {
|
|
14401
|
+
let entries;
|
|
14380
14402
|
try {
|
|
14381
|
-
|
|
14382
|
-
if (newest === null || m2 > newest) newest = m2;
|
|
14403
|
+
entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
|
|
14383
14404
|
} catch {
|
|
14405
|
+
continue;
|
|
14406
|
+
}
|
|
14407
|
+
for (const e of entries) {
|
|
14408
|
+
if (!e.isFile()) continue;
|
|
14409
|
+
if (!e.name.startsWith("agent-") || !e.name.endsWith(".jsonl")) continue;
|
|
14410
|
+
try {
|
|
14411
|
+
const m2 = import_node_fs6.default.statSync(import_node_path5.default.join(dir, e.name)).mtimeMs;
|
|
14412
|
+
if (newest === null || m2 > newest) newest = m2;
|
|
14413
|
+
} catch {
|
|
14414
|
+
}
|
|
14384
14415
|
}
|
|
14385
14416
|
}
|
|
14386
14417
|
return newest;
|
|
@@ -14934,38 +14965,41 @@ var init_claude_history = __esm({
|
|
|
14934
14965
|
return { messages: this.readSubagentFromMainJsonl(args.cwd, args.toolSessionId, args.subagentId) };
|
|
14935
14966
|
}
|
|
14936
14967
|
// 独立目录路径:<projectsRoot>/<cwdHash>/<toolSessionId>/subagents/*.jsonl
|
|
14937
|
-
// 返回 null
|
|
14968
|
+
// 返回 null 表示一个候选目录都没有(调用方回退旧实现);返回空数组表示目录在但无 jsonl。
|
|
14969
|
+
// [cwd 漂移] 下取**所有候选目录的并集**:漂移前后的 agent 都该列出来。同名 agentId 只可能
|
|
14970
|
+
// 是同一个 agent(id 全局唯一),取先遇到的那份(byCwd 优先)。
|
|
14938
14971
|
listSubagentsFromDirectory(cwd, toolSessionId) {
|
|
14939
|
-
const
|
|
14940
|
-
|
|
14941
|
-
toolSessionId,
|
|
14942
|
-
"subagents"
|
|
14943
|
-
);
|
|
14944
|
-
let entries;
|
|
14945
|
-
try {
|
|
14946
|
-
entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
|
|
14947
|
-
} catch (err) {
|
|
14948
|
-
if (err.code === "ENOENT") return null;
|
|
14949
|
-
return null;
|
|
14950
|
-
}
|
|
14972
|
+
const dirs = subagentsDirCandidates(this.projectsRoot, cwd, toolSessionId);
|
|
14973
|
+
if (dirs.length === 0) return null;
|
|
14951
14974
|
const out = [];
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
for (const
|
|
14961
|
-
|
|
14962
|
-
if (!
|
|
14963
|
-
|
|
14964
|
-
if (
|
|
14965
|
-
|
|
14975
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14976
|
+
for (const dir of dirs) {
|
|
14977
|
+
let entries;
|
|
14978
|
+
try {
|
|
14979
|
+
entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
|
|
14980
|
+
} catch {
|
|
14981
|
+
continue;
|
|
14982
|
+
}
|
|
14983
|
+
for (const e of entries) {
|
|
14984
|
+
if (!e.isFile()) continue;
|
|
14985
|
+
if (!e.name.startsWith("agent-") || !e.name.endsWith(".jsonl")) continue;
|
|
14986
|
+
const subagentId = e.name.slice("agent-".length, -".jsonl".length);
|
|
14987
|
+
if (seen.has(subagentId)) continue;
|
|
14988
|
+
seen.add(subagentId);
|
|
14989
|
+
const filePath = import_node_path5.default.join(dir, e.name);
|
|
14990
|
+
const lines = readJsonlLines(filePath);
|
|
14991
|
+
let firstText = "";
|
|
14992
|
+
let messageCount = 0;
|
|
14993
|
+
for (const line of lines) {
|
|
14994
|
+
const msg = messageFromJsonl(line);
|
|
14995
|
+
if (!msg) continue;
|
|
14996
|
+
messageCount += 1;
|
|
14997
|
+
if (!firstText && msg.kind === "user-text" && msg.text) {
|
|
14998
|
+
firstText = msg.text.slice(0, 120);
|
|
14999
|
+
}
|
|
14966
15000
|
}
|
|
15001
|
+
out.push({ subagentId, summary: firstText, messageCount });
|
|
14967
15002
|
}
|
|
14968
|
-
out.push({ subagentId, summary: firstText, messageCount });
|
|
14969
15003
|
}
|
|
14970
15004
|
return out;
|
|
14971
15005
|
}
|
|
@@ -15002,28 +15036,26 @@ var init_claude_history = __esm({
|
|
|
15002
15036
|
messageCount: buckets.get(id)?.count ?? 0
|
|
15003
15037
|
}));
|
|
15004
15038
|
}
|
|
15005
|
-
// 独立文件路径:agent-<subagentId>.jsonl
|
|
15039
|
+
// 独立文件路径:agent-<subagentId>.jsonl;所有候选目录里都没有才返回 null,让调用方
|
|
15040
|
+
// 回退去扫主 jsonl 的 sidechain。[cwd 漂移] 下要的就是「这个文件落在哪一侧」——
|
|
15041
|
+
// 只看某一个目录在不在,会把漂移侧的 agent 判成不存在。
|
|
15006
15042
|
readSubagentFromFile(cwd, toolSessionId, subagentId) {
|
|
15007
|
-
const
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
const messages = [];
|
|
15022
|
-
for (const line of lines) {
|
|
15023
|
-
const msg = messageFromJsonl(line);
|
|
15024
|
-
if (msg) messages.push(msg);
|
|
15043
|
+
for (const dir of subagentsDirCandidates(this.projectsRoot, cwd, toolSessionId)) {
|
|
15044
|
+
const file = import_node_path5.default.join(dir, `agent-${subagentId}.jsonl`);
|
|
15045
|
+
try {
|
|
15046
|
+
if (!import_node_fs6.default.statSync(file).isFile()) continue;
|
|
15047
|
+
} catch {
|
|
15048
|
+
continue;
|
|
15049
|
+
}
|
|
15050
|
+
const lines = readJsonlLines(file);
|
|
15051
|
+
const messages = [];
|
|
15052
|
+
for (const line of lines) {
|
|
15053
|
+
const msg = messageFromJsonl(line);
|
|
15054
|
+
if (msg) messages.push(msg);
|
|
15055
|
+
}
|
|
15056
|
+
return messages;
|
|
15025
15057
|
}
|
|
15026
|
-
return
|
|
15058
|
+
return null;
|
|
15027
15059
|
}
|
|
15028
15060
|
/**
|
|
15029
15061
|
* 遍历 session JSONL 找所有 `type:'file-history-snapshot'` 条目,按 messageId 建表
|
|
@@ -52969,12 +53001,13 @@ var import_node_path20 = __toESM(require("path"), 1);
|
|
|
52969
53001
|
function createJsonStore(deps) {
|
|
52970
53002
|
let items = [];
|
|
52971
53003
|
let flushTimer = null;
|
|
53004
|
+
const version2 = deps.version ?? 1;
|
|
52972
53005
|
async function flushNow() {
|
|
52973
53006
|
if (flushTimer) {
|
|
52974
53007
|
clearTimeout(flushTimer);
|
|
52975
53008
|
flushTimer = null;
|
|
52976
53009
|
}
|
|
52977
|
-
const content = { version:
|
|
53010
|
+
const content = { version: version2, [deps.key]: items };
|
|
52978
53011
|
await import_promises2.default.mkdir(import_node_path20.default.dirname(deps.filePath), { recursive: true });
|
|
52979
53012
|
const tmp = `${deps.filePath}.tmp-${deps.now()}-${Math.floor(Math.random() * 1e6)}`;
|
|
52980
53013
|
await import_promises2.default.writeFile(tmp, JSON.stringify(content, null, 2), "utf8");
|
|
@@ -52995,10 +53028,24 @@ function createJsonStore(deps) {
|
|
|
52995
53028
|
throw e;
|
|
52996
53029
|
}
|
|
52997
53030
|
const parsed = JSON.parse(raw);
|
|
52998
|
-
|
|
52999
|
-
|
|
53031
|
+
const found = parsed.version;
|
|
53032
|
+
if (found === version2) {
|
|
53033
|
+
items = parsed[deps.key] ?? [];
|
|
53034
|
+
return;
|
|
53035
|
+
}
|
|
53036
|
+
if (typeof found === "number" && found < version2 && deps.migrate) {
|
|
53037
|
+
items = deps.migrate(parsed[deps.key] ?? [], found);
|
|
53038
|
+
await flushNow().catch((err) => deps.onFlushError?.(err));
|
|
53039
|
+
return;
|
|
53000
53040
|
}
|
|
53001
|
-
|
|
53041
|
+
if (typeof found === "number" && found > version2) {
|
|
53042
|
+
throw new Error(
|
|
53043
|
+
`${deps.key}.json \u7531\u66F4\u65B0\u7248\u672C\u7684 clawd \u5199\u5165\uFF08\u6587\u4EF6 version=${found}\uFF0C\u672C\u673A\u53EA\u8BA4\u5230 ${version2}\uFF09\uFF0C\u8BF7\u5347\u7EA7 clawd`
|
|
53044
|
+
);
|
|
53045
|
+
}
|
|
53046
|
+
throw new Error(
|
|
53047
|
+
`${deps.key}.json: unsupported version ${String(found)}\uFF08\u672C\u673A version=${version2}\uFF09`
|
|
53048
|
+
);
|
|
53002
53049
|
},
|
|
53003
53050
|
scheduleFlush() {
|
|
53004
53051
|
if (flushTimer) return;
|
|
@@ -53012,7 +53059,31 @@ function createJsonStore(deps) {
|
|
|
53012
53059
|
|
|
53013
53060
|
// src/channel/store.ts
|
|
53014
53061
|
function createChannelStore(deps) {
|
|
53015
|
-
const file = createJsonStore({
|
|
53062
|
+
const file = createJsonStore({
|
|
53063
|
+
...deps,
|
|
53064
|
+
key: "channels",
|
|
53065
|
+
version: 2,
|
|
53066
|
+
// v1 → v2(2026-08-26):成员表换形,本机与远程 persona 并进一张 memberPersonas。
|
|
53067
|
+
// memberContactDeviceIds 原样留着 —— 它的语义从「可派活给这台设备上的任意 persona」
|
|
53068
|
+
// 收窄成「可发 DM」,收窄不需要动数据。
|
|
53069
|
+
//
|
|
53070
|
+
// 存量频道因此失去跨设备派活能力,这是有意的:要补偿就得在这里去拉对方 daemon 的
|
|
53071
|
+
// persona 列表(对方不一定在线),而「他家所有 persona 自动进名单」正是要废掉的
|
|
53072
|
+
// 那个隐含推导。老板重新勾一次即可。
|
|
53073
|
+
//
|
|
53074
|
+
// ⚠️ 写法注意(给下一次 v2→v3 的人):这里是**先解构后覆盖** —— `...rest` 会把
|
|
53075
|
+
// 旧行里的同名字段一起带进来,紧跟着的赋值再盖掉它。加新字段时确认清楚是想覆盖
|
|
53076
|
+
// 还是想保留,别无意中把 rest 里已有的值清空。
|
|
53077
|
+
migrate: (rows) => rows.map((c) => {
|
|
53078
|
+
const { memberPersonaIds, ...rest } = c;
|
|
53079
|
+
return {
|
|
53080
|
+
...rest,
|
|
53081
|
+
memberPersonas: (memberPersonaIds ?? []).map((personaId2) => ({
|
|
53082
|
+
personaId: personaId2
|
|
53083
|
+
}))
|
|
53084
|
+
};
|
|
53085
|
+
})
|
|
53086
|
+
});
|
|
53016
53087
|
const get = (id) => file.all().find((c) => c.id === id);
|
|
53017
53088
|
return {
|
|
53018
53089
|
load: file.load,
|
|
@@ -53024,7 +53095,7 @@ function createChannelStore(deps) {
|
|
|
53024
53095
|
const c = {
|
|
53025
53096
|
id: deps.genId(),
|
|
53026
53097
|
name: input.name,
|
|
53027
|
-
|
|
53098
|
+
memberPersonas: input.memberPersonas,
|
|
53028
53099
|
memberContactDeviceIds: input.memberContactDeviceIds,
|
|
53029
53100
|
createdAt: iso,
|
|
53030
53101
|
updatedAt: iso
|
|
@@ -53037,7 +53108,7 @@ function createChannelStore(deps) {
|
|
|
53037
53108
|
const c = get(id);
|
|
53038
53109
|
if (!c) return void 0;
|
|
53039
53110
|
if (patch.name !== void 0) c.name = patch.name;
|
|
53040
|
-
if (patch.
|
|
53111
|
+
if (patch.memberPersonas !== void 0) c.memberPersonas = patch.memberPersonas;
|
|
53041
53112
|
if (patch.memberContactDeviceIds !== void 0) {
|
|
53042
53113
|
c.memberContactDeviceIds = patch.memberContactDeviceIds;
|
|
53043
53114
|
}
|
|
@@ -54848,9 +54919,11 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
54848
54919
|
if (!members) {
|
|
54849
54920
|
throw new ClawdError(ERROR_CODES.INVALID_PARAM, `topic not found: ${topicId}`);
|
|
54850
54921
|
}
|
|
54851
|
-
const
|
|
54852
|
-
|
|
54922
|
+
const isMember = members.personas.some(
|
|
54923
|
+
(m2) => m2.personaId === targetPersona && m2.deviceId === args.targetDeviceId
|
|
54924
|
+
);
|
|
54853
54925
|
if (!isMember) {
|
|
54926
|
+
const who = args.targetDeviceId ? `${targetPersona}@${args.targetDeviceId}` : targetPersona;
|
|
54854
54927
|
throw new ClawdError(
|
|
54855
54928
|
ERROR_CODES.INVALID_PARAM,
|
|
54856
54929
|
`${who} is not a member of the channel behind topic ${topicId} \u2014\u2014 \u5148\u628A\u5B83\u52A0\u8FDB\u9891\u9053\u518D\u6D3E\u6D3B`
|
|
@@ -62305,7 +62378,7 @@ function computeMethodAccess(args) {
|
|
|
62305
62378
|
}
|
|
62306
62379
|
|
|
62307
62380
|
// src/version.ts
|
|
62308
|
-
var version = "0.2.
|
|
62381
|
+
var version = "0.2.412".length > 0 ? "0.2.412" : "dev";
|
|
62309
62382
|
|
|
62310
62383
|
// src/cli-probe/probe.ts
|
|
62311
62384
|
var fs66 = __toESM(require("fs"), 1);
|
|
@@ -64456,8 +64529,8 @@ function buildShiftHandlers(deps) {
|
|
|
64456
64529
|
|
|
64457
64530
|
// src/handlers/channel.ts
|
|
64458
64531
|
init_protocol();
|
|
64459
|
-
function withMaster(
|
|
64460
|
-
return
|
|
64532
|
+
function withMaster(members) {
|
|
64533
|
+
return members.some((m2) => !m2.deviceId && m2.personaId === MASTER_PERSONA_ID) ? members : [{ personaId: MASTER_PERSONA_ID }, ...members];
|
|
64461
64534
|
}
|
|
64462
64535
|
function buildChannelHandlers(deps) {
|
|
64463
64536
|
return {
|
|
@@ -64468,7 +64541,7 @@ function buildChannelHandlers(deps) {
|
|
|
64468
64541
|
const args = ChannelCreateArgsSchema.parse(frame);
|
|
64469
64542
|
const channel = deps.channelStore.create({
|
|
64470
64543
|
name: args.name,
|
|
64471
|
-
|
|
64544
|
+
memberPersonas: withMaster(args.memberPersonas),
|
|
64472
64545
|
memberContactDeviceIds: args.memberContactDeviceIds
|
|
64473
64546
|
});
|
|
64474
64547
|
return { response: ChannelResultSchema.parse({ channel }) };
|
|
@@ -64478,7 +64551,7 @@ function buildChannelHandlers(deps) {
|
|
|
64478
64551
|
const channel = deps.channelStore.update(args.channelId, {
|
|
64479
64552
|
...args.name !== void 0 ? { name: args.name } : {},
|
|
64480
64553
|
// 改名单同样过 withMaster:编辑路径把 Master 摘掉,效果跟建一个没 Master 的频道一样
|
|
64481
|
-
...args.
|
|
64554
|
+
...args.memberPersonas !== void 0 ? { memberPersonas: withMaster(args.memberPersonas) } : {},
|
|
64482
64555
|
...args.memberContactDeviceIds !== void 0 ? { memberContactDeviceIds: args.memberContactDeviceIds } : {}
|
|
64483
64556
|
});
|
|
64484
64557
|
if (!channel) {
|
|
@@ -67085,14 +67158,17 @@ async function startDaemon(config) {
|
|
|
67085
67158
|
const dispatchHandlers = buildPersonaDispatchHandlers({
|
|
67086
67159
|
personaDispatchManager,
|
|
67087
67160
|
store: dispatchStore,
|
|
67088
|
-
// 话题工作线的派活边界:topicId →
|
|
67089
|
-
//
|
|
67161
|
+
// 话题工作线的派活边界:topicId → 它所属频道的 persona 成员名单。频道 / 话题任一
|
|
67162
|
+
// 不存在都返回 undefined,由 handler 判成「话题不存在」拒掉(编 topicId 绕不过去)。
|
|
67163
|
+
//
|
|
67164
|
+
// 只给 persona 名单:memberContactDeviceIds 是「可发 DM」的人类名单,不参与派活判定。
|
|
67165
|
+
// 在这里一并带上它,下一个读代码的人会以为它也是闸的一部分(P1 接 DM 时再按需要加)。
|
|
67090
67166
|
getTopicMembership: (topicId) => {
|
|
67091
67167
|
const t = topicStore.get(topicId);
|
|
67092
67168
|
if (!t) return void 0;
|
|
67093
67169
|
const ch = channelStore.get(t.channelId);
|
|
67094
67170
|
if (!ch) return void 0;
|
|
67095
|
-
return {
|
|
67171
|
+
return { personas: [...ch.memberPersonas] };
|
|
67096
67172
|
},
|
|
67097
67173
|
// 续派(spec 2026-07-21):任务包注入 worker session,复用投递同款注入路径。
|
|
67098
67174
|
sendFollowUp: (workerSessionId, text, meta) => manager.injectIntoSession(workerSessionId, text, meta),
|
|
@@ -21832,13 +21832,17 @@ var ShiftInternalHistoryArgs = external_exports.object({
|
|
|
21832
21832
|
});
|
|
21833
21833
|
|
|
21834
21834
|
// ../protocol/src/channel.ts
|
|
21835
|
+
var ChannelMemberPersonaSchema = external_exports.object({
|
|
21836
|
+
personaId: external_exports.string().min(1),
|
|
21837
|
+
deviceId: external_exports.string().min(1).optional()
|
|
21838
|
+
});
|
|
21835
21839
|
var ChannelSchema = external_exports.object({
|
|
21836
21840
|
id: external_exports.string().min(1),
|
|
21837
21841
|
name: external_exports.string().min(1),
|
|
21838
|
-
/** 该频道里可被派活的 persona */
|
|
21839
|
-
|
|
21840
|
-
/** 人类成员(contact deviceId
|
|
21841
|
-
* P1 人类异步询问也用它 */
|
|
21842
|
+
/** 该频道里可被派活的 persona 全集(本机 + 远程) */
|
|
21843
|
+
memberPersonas: external_exports.array(ChannelMemberPersonaSchema),
|
|
21844
|
+
/** 人类成员(contact deviceId):可发 DM。**不隐含任何派活权限** —— 跨设备派活认的是
|
|
21845
|
+
* 上面那张 memberPersonas。P1 人类异步询问也用它 */
|
|
21842
21846
|
memberContactDeviceIds: external_exports.array(external_exports.string().min(1)),
|
|
21843
21847
|
createdAt: external_exports.string().min(1),
|
|
21844
21848
|
updatedAt: external_exports.string().min(1)
|
|
@@ -21846,13 +21850,13 @@ var ChannelSchema = external_exports.object({
|
|
|
21846
21850
|
var nonBlank = external_exports.string().trim().min(1);
|
|
21847
21851
|
var ChannelCreateArgsSchema = external_exports.object({
|
|
21848
21852
|
name: nonBlank,
|
|
21849
|
-
|
|
21853
|
+
memberPersonas: external_exports.array(ChannelMemberPersonaSchema).default([]),
|
|
21850
21854
|
memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).default([])
|
|
21851
21855
|
});
|
|
21852
21856
|
var ChannelUpdateArgsSchema = external_exports.object({
|
|
21853
21857
|
channelId: external_exports.string().min(1),
|
|
21854
21858
|
name: nonBlank.optional(),
|
|
21855
|
-
|
|
21859
|
+
memberPersonas: external_exports.array(ChannelMemberPersonaSchema).optional(),
|
|
21856
21860
|
memberContactDeviceIds: external_exports.array(external_exports.string().min(1)).optional()
|
|
21857
21861
|
});
|
|
21858
21862
|
var ChannelDeleteArgsSchema = external_exports.object({ channelId: external_exports.string().min(1) });
|
|
@@ -62,10 +62,18 @@ watcher 有真工具——它会去 worker 的工作目录跑全量测试、导
|
|
|
62
62
|
`meta.lineId` 认领),老板在话题工作区也看不到它。
|
|
63
63
|
|
|
64
64
|
**只能派给本频道的成员。** 派活对象不在频道名单里,daemon 直接拒(`not a member of the channel`)——
|
|
65
|
-
|
|
66
|
-
`
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
频道成员就是这个话题的参与方边界,不是一张摆设名单。话题的 `channelId` 从 `topic:get` 拿,
|
|
66
|
+
名单在 `channel:list` 的两个字段里,**它们不是一回事**:
|
|
67
|
+
|
|
68
|
+
- `memberPersonas` —— 可派活的 persona **全集**。每项形如 `{ personaId, deviceId? }`:没有
|
|
69
|
+
`deviceId` 就是本机 persona,有 `deviceId` 就是那台联系人设备上公开给老板的 persona。
|
|
70
|
+
派它时把这两个值原样填进 `personaDispatch` 的 `targetPersona` / `targetDeviceId`
|
|
71
|
+
- `memberContactDeviceIds` —— **人类**成员,只表示「可以给这个人发 DM」
|
|
72
|
+
|
|
73
|
+
**联系人在名单里不等于他家的 persona 能派。** 要派某台设备上的 persona,那个 persona 必须
|
|
74
|
+
自己出现在 `memberPersonas` 里(连同它的 `deviceId`)。名单外的一律拒。
|
|
75
|
+
|
|
76
|
+
缺人手就**停下问老板**要不要把某个 persona 加进频道,不要绕道派给名单外的 persona。
|
|
69
77
|
|
|
70
78
|
打回次数**不要**自己往 `meta` 里记 —— daemon 在 `topic:reject` / `topic:accept` 里数,
|
|
71
79
|
你手记一份只会跟它漂。拿到 `dispatchId` 后立刻落账(见「落账纪律」)。
|