@clawos-dev/clawd 0.2.56-beta.90.047fd4f → 0.2.56-beta.91.df217af
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 +430 -188
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -4404,6 +4404,10 @@ var init_methods = __esm({
|
|
|
4404
4404
|
"persona:issueToken",
|
|
4405
4405
|
"persona:revokeToken",
|
|
4406
4406
|
"persona:listSubSessions",
|
|
4407
|
+
// owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session
|
|
4408
|
+
// 元数据。后续增量靠 persona:listenerChat:* push 维护,本 RPC 仅在 owner ws connect /
|
|
4409
|
+
// reconnect 时一次性补齐 cache。
|
|
4410
|
+
"persona:listListenerChatsForToken",
|
|
4407
4411
|
"persona:appendOwnerMessage",
|
|
4408
4412
|
"info",
|
|
4409
4413
|
"ping"
|
|
@@ -8595,7 +8599,7 @@ var init_zod = __esm({
|
|
|
8595
8599
|
});
|
|
8596
8600
|
|
|
8597
8601
|
// ../protocol/src/persona-schemas.ts
|
|
8598
|
-
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema,
|
|
8602
|
+
var PersonaTokenEntrySchema, PersonaFileSchema, PersonaInfoResponseSchema, PersonaCreateArgsSchema, PersonaIdArgsSchema, PersonaUpdateArgsSchema, PersonaIssueTokenArgsSchema, PersonaRevokeTokenArgsSchema, PersonaAppendOwnerMessageArgsSchema, FRAME_TYPE_CHAT_OPEN, FRAME_TYPE_CHAT_RENAME, FRAME_TYPE_CHAT_DELETE, FRAME_TYPE_CHAT_LIST, FRAME_TYPE_CHAT_CREATED, FRAME_TYPE_CHAT_RENAMED, FRAME_TYPE_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED, FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED, FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED, FRAME_TYPE_PERSONA_LISTENER_STATUS, ChatSummarySchema, ChatOpenRequestSchema, ChatRenameRequestSchema, ChatDeleteRequestSchema, ChatRenameResponseSchema, ChatDeleteResponseSchema, ChatListPushSchema, ChatCreatedFrameSchema, ChatRenamedFrameSchema, ChatDeletedFrameSchema, PersonaListenerChatCreatedFrameSchema, PersonaListenerChatRenamedFrameSchema, PersonaListenerChatDeletedFrameSchema, PersonaListenerStatusFrameSchema, PersonaListListenerChatsForTokenArgsSchema, PersonaListListenerChatsForTokenResponseSchema;
|
|
8599
8603
|
var init_persona_schemas = __esm({
|
|
8600
8604
|
"../protocol/src/persona-schemas.ts"() {
|
|
8601
8605
|
"use strict";
|
|
@@ -8654,47 +8658,116 @@ var init_persona_schemas = __esm({
|
|
|
8654
8658
|
subSessionId: external_exports.string().min(1),
|
|
8655
8659
|
text: external_exports.string().min(1)
|
|
8656
8660
|
});
|
|
8661
|
+
FRAME_TYPE_CHAT_OPEN = "chat:open";
|
|
8662
|
+
FRAME_TYPE_CHAT_RENAME = "chat:rename";
|
|
8663
|
+
FRAME_TYPE_CHAT_DELETE = "chat:delete";
|
|
8664
|
+
FRAME_TYPE_CHAT_LIST = "chat:list";
|
|
8665
|
+
FRAME_TYPE_CHAT_CREATED = "chat:created";
|
|
8666
|
+
FRAME_TYPE_CHAT_RENAMED = "chat:renamed";
|
|
8667
|
+
FRAME_TYPE_CHAT_DELETED = "chat:deleted";
|
|
8668
|
+
FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED = "persona:listenerChat:created";
|
|
8669
|
+
FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED = "persona:listenerChat:renamed";
|
|
8670
|
+
FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED = "persona:listenerChat:deleted";
|
|
8671
|
+
FRAME_TYPE_PERSONA_LISTENER_STATUS = "persona:listenerStatus";
|
|
8657
8672
|
ChatSummarySchema = external_exports.object({
|
|
8658
8673
|
sessionId: external_exports.string().min(1),
|
|
8659
|
-
/** 派生 sessionId 时使用的原始 chatId
|
|
8674
|
+
/** 派生 sessionId 时使用的原始 chatId(永远三元组 hash 派生,无 default 特例) */
|
|
8660
8675
|
chatId: external_exports.string().min(1),
|
|
8661
8676
|
/** sub-session label,listener 视为 chat 名 */
|
|
8662
8677
|
label: external_exports.string(),
|
|
8663
8678
|
/** SessionFile.createdAt(ISO string) */
|
|
8664
|
-
createdAt: external_exports.string()
|
|
8665
|
-
/** 是否为 default chat(sessionId 等于 ${personaId}-${tokenHash12} 时为 true) */
|
|
8666
|
-
isDefault: external_exports.boolean()
|
|
8679
|
+
createdAt: external_exports.string()
|
|
8667
8680
|
});
|
|
8668
|
-
|
|
8669
|
-
type: external_exports.literal(
|
|
8681
|
+
ChatOpenRequestSchema = external_exports.object({
|
|
8682
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_OPEN),
|
|
8683
|
+
/** 客户端生成的 chatId(uuid 推荐);daemon 用三元组派生 sub-session sessionId */
|
|
8684
|
+
chatId: external_exports.string().min(1),
|
|
8685
|
+
/** 不存在时是否自动创建。true = 创建(典型:新建对话);false/缺省 = 命中已有,否则 NOT_FOUND */
|
|
8686
|
+
autoCreate: external_exports.boolean().optional(),
|
|
8670
8687
|
requestId: external_exports.string().min(1).optional()
|
|
8671
8688
|
});
|
|
8672
|
-
ChatListResponseSchema = external_exports.object({
|
|
8673
|
-
type: external_exports.literal("chat:list"),
|
|
8674
|
-
requestId: external_exports.string().min(1).optional(),
|
|
8675
|
-
chats: external_exports.array(ChatSummarySchema)
|
|
8676
|
-
});
|
|
8677
8689
|
ChatRenameRequestSchema = external_exports.object({
|
|
8678
|
-
type: external_exports.literal(
|
|
8690
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
|
|
8679
8691
|
requestId: external_exports.string().min(1).optional(),
|
|
8680
8692
|
sessionId: external_exports.string().min(1),
|
|
8681
8693
|
label: external_exports.string().min(1)
|
|
8682
8694
|
});
|
|
8683
|
-
ChatRenameResponseSchema = external_exports.object({
|
|
8684
|
-
type: external_exports.literal("chat:rename"),
|
|
8685
|
-
requestId: external_exports.string().min(1).optional(),
|
|
8686
|
-
ok: external_exports.literal(true)
|
|
8687
|
-
});
|
|
8688
8695
|
ChatDeleteRequestSchema = external_exports.object({
|
|
8689
|
-
type: external_exports.literal(
|
|
8696
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
|
|
8690
8697
|
requestId: external_exports.string().min(1).optional(),
|
|
8691
8698
|
sessionId: external_exports.string().min(1)
|
|
8692
8699
|
});
|
|
8700
|
+
ChatRenameResponseSchema = external_exports.object({
|
|
8701
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_RENAME),
|
|
8702
|
+
requestId: external_exports.string().min(1).optional(),
|
|
8703
|
+
ok: external_exports.literal(true)
|
|
8704
|
+
});
|
|
8693
8705
|
ChatDeleteResponseSchema = external_exports.object({
|
|
8694
|
-
type: external_exports.literal(
|
|
8706
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_DELETE),
|
|
8695
8707
|
requestId: external_exports.string().min(1).optional(),
|
|
8696
8708
|
ok: external_exports.literal(true)
|
|
8697
8709
|
});
|
|
8710
|
+
ChatListPushSchema = external_exports.object({
|
|
8711
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_LIST),
|
|
8712
|
+
chats: external_exports.array(ChatSummarySchema)
|
|
8713
|
+
});
|
|
8714
|
+
ChatCreatedFrameSchema = external_exports.object({
|
|
8715
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_CREATED),
|
|
8716
|
+
sessionId: external_exports.string().min(1),
|
|
8717
|
+
chatId: external_exports.string().min(1),
|
|
8718
|
+
label: external_exports.string().optional(),
|
|
8719
|
+
/** ISO timestamp */
|
|
8720
|
+
createdAt: external_exports.string().min(1)
|
|
8721
|
+
});
|
|
8722
|
+
ChatRenamedFrameSchema = external_exports.object({
|
|
8723
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_RENAMED),
|
|
8724
|
+
sessionId: external_exports.string().min(1),
|
|
8725
|
+
label: external_exports.string().min(1)
|
|
8726
|
+
});
|
|
8727
|
+
ChatDeletedFrameSchema = external_exports.object({
|
|
8728
|
+
type: external_exports.literal(FRAME_TYPE_CHAT_DELETED),
|
|
8729
|
+
sessionId: external_exports.string().min(1)
|
|
8730
|
+
});
|
|
8731
|
+
PersonaListenerChatCreatedFrameSchema = external_exports.object({
|
|
8732
|
+
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED),
|
|
8733
|
+
personaId: external_exports.string().min(1),
|
|
8734
|
+
/** owner 同时持有 token 字符串:sidebar 已经按 token 维度渲染 listener leaf,
|
|
8735
|
+
* push 帧用 token 直接定位归属 leaf。token 不是密钥,owner 本来就持有。 */
|
|
8736
|
+
token: external_exports.string().min(1),
|
|
8737
|
+
sessionId: external_exports.string().min(1),
|
|
8738
|
+
chatId: external_exports.string().min(1),
|
|
8739
|
+
label: external_exports.string().optional(),
|
|
8740
|
+
createdAt: external_exports.string().min(1)
|
|
8741
|
+
});
|
|
8742
|
+
PersonaListenerChatRenamedFrameSchema = external_exports.object({
|
|
8743
|
+
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED),
|
|
8744
|
+
personaId: external_exports.string().min(1),
|
|
8745
|
+
token: external_exports.string().min(1),
|
|
8746
|
+
sessionId: external_exports.string().min(1),
|
|
8747
|
+
label: external_exports.string().min(1)
|
|
8748
|
+
});
|
|
8749
|
+
PersonaListenerChatDeletedFrameSchema = external_exports.object({
|
|
8750
|
+
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED),
|
|
8751
|
+
personaId: external_exports.string().min(1),
|
|
8752
|
+
token: external_exports.string().min(1),
|
|
8753
|
+
sessionId: external_exports.string().min(1),
|
|
8754
|
+
/** 原始 chatId(用于 owner UI 比对 personaView.chatId 清理 stale selection) */
|
|
8755
|
+
chatId: external_exports.string().min(1)
|
|
8756
|
+
});
|
|
8757
|
+
PersonaListenerStatusFrameSchema = external_exports.object({
|
|
8758
|
+
type: external_exports.literal(FRAME_TYPE_PERSONA_LISTENER_STATUS),
|
|
8759
|
+
personaId: external_exports.string().min(1),
|
|
8760
|
+
token: external_exports.string().min(1),
|
|
8761
|
+
status: external_exports.enum(["online", "offline"]),
|
|
8762
|
+
activeWsCount: external_exports.number().int().nonnegative()
|
|
8763
|
+
});
|
|
8764
|
+
PersonaListListenerChatsForTokenArgsSchema = external_exports.object({
|
|
8765
|
+
personaId: external_exports.string().min(1),
|
|
8766
|
+
token: external_exports.string().min(1)
|
|
8767
|
+
});
|
|
8768
|
+
PersonaListListenerChatsForTokenResponseSchema = external_exports.object({
|
|
8769
|
+
chats: external_exports.array(ChatSummarySchema)
|
|
8770
|
+
});
|
|
8698
8771
|
}
|
|
8699
8772
|
});
|
|
8700
8773
|
|
|
@@ -8798,9 +8871,10 @@ var init_schemas = __esm({
|
|
|
8798
8871
|
// owner-mode persona session 身份标识;UI 用它在 SessionList 过滤 + jump,
|
|
8799
8872
|
// daemon 用它做 idempotent dedupe + spawn 时派生 ctx.personaMode='owner'
|
|
8800
8873
|
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
8801
|
-
// listener-mode sub-session 的原始 chatId(owner-mode
|
|
8802
|
-
//
|
|
8803
|
-
//
|
|
8874
|
+
// listener-mode sub-session 的原始 chatId(owner-mode 不写;listener-scope 强制写入)。
|
|
8875
|
+
// 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
|
|
8876
|
+
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
8877
|
+
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
8804
8878
|
chatId: external_exports.string().min(1).optional(),
|
|
8805
8879
|
createdAt: external_exports.string().min(1),
|
|
8806
8880
|
updatedAt: external_exports.string().min(1)
|
|
@@ -9233,8 +9307,7 @@ var init_schemas = __esm({
|
|
|
9233
9307
|
AuthRequestFrameSchema = external_exports.object({
|
|
9234
9308
|
type: external_exports.literal("auth"),
|
|
9235
9309
|
token: external_exports.string().min(1),
|
|
9236
|
-
scheme: external_exports.literal("bearer").optional()
|
|
9237
|
-
chatId: external_exports.string().optional()
|
|
9310
|
+
scheme: external_exports.literal("bearer").optional()
|
|
9238
9311
|
});
|
|
9239
9312
|
AuthOkFrameSchema = external_exports.object({
|
|
9240
9313
|
type: external_exports.literal("auth:ok"),
|
|
@@ -17989,16 +18062,21 @@ var PersonaManager = class {
|
|
|
17989
18062
|
return updated;
|
|
17990
18063
|
}
|
|
17991
18064
|
/**
|
|
17992
|
-
* 拿到(或按需创建)该 token 对应的 sub-session。subSessionId 由 personaId + token
|
|
17993
|
-
*
|
|
18065
|
+
* 拿到(或按需创建)该 (token, chatId) 对应的 sub-session。subSessionId 由 personaId + token +
|
|
18066
|
+
* chatId 三元组 hash 派生,保证 idempotent:同一 tuple 永远复用同一个 sub-session。
|
|
18067
|
+
*
|
|
18068
|
+
* chatId 必填 —— listener 协议从 v2 起拆 phase:客户端 auth 后通过 chat:open(chatId, autoCreate?)
|
|
18069
|
+
* 显式进入某个 chat;不再有 'default' 默认值。chatId 通常是客户端生成的 uuid,落 listener 端
|
|
18070
|
+
* 持久化由客户端负责(daemon 不替客户端记忆"最近哪个 chat")。
|
|
18071
|
+
*
|
|
17994
18072
|
* 创建路径:开启 idleKillEnabled,cwd 取 persona 目录,不再硬编码 permission mode / tool allowlist
|
|
17995
18073
|
* (sandbox 约束移到 persona dir 下的 .claude/settings.json,由 OS-level Seatbelt 执行)
|
|
17996
18074
|
*/
|
|
17997
18075
|
getOrCreateSubSession(personaId, token, chatId) {
|
|
17998
18076
|
const persona = this.deps.registry.get(personaId);
|
|
17999
18077
|
if (!persona) throw new Error(`persona not found: ${personaId}`);
|
|
18000
|
-
|
|
18001
|
-
const subSessionId = this.deriveSubSessionId(personaId, token,
|
|
18078
|
+
if (!chatId) throw new Error("chatId is required");
|
|
18079
|
+
const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
|
|
18002
18080
|
const scope = { kind: "persona", personaId, mode: "listener" };
|
|
18003
18081
|
const existing = this.deps.sessionManager.readForScope(subSessionId, scope);
|
|
18004
18082
|
if (existing) {
|
|
@@ -18013,10 +18091,28 @@ var PersonaManager = class {
|
|
|
18013
18091
|
tool: "claude",
|
|
18014
18092
|
label: subLabel,
|
|
18015
18093
|
model: persona.model,
|
|
18016
|
-
chatId
|
|
18094
|
+
chatId
|
|
18017
18095
|
});
|
|
18018
18096
|
return { sessionFile, isNew: true };
|
|
18019
18097
|
}
|
|
18098
|
+
/**
|
|
18099
|
+
* 检查 (token, chatId) 对应的 sub-session 是否存在;不存在返回 null。
|
|
18100
|
+
* chat:open(autoCreate=false) 路径用:listener 想"只切已有 chat"时不能撞 auto-create。
|
|
18101
|
+
*/
|
|
18102
|
+
getSubSession(personaId, token, chatId) {
|
|
18103
|
+
if (!chatId) return null;
|
|
18104
|
+
const subSessionId = this.deriveSubSessionId(personaId, token, chatId);
|
|
18105
|
+
const scope = { kind: "persona", personaId, mode: "listener" };
|
|
18106
|
+
return this.deps.sessionManager.readForScope(subSessionId, scope);
|
|
18107
|
+
}
|
|
18108
|
+
/**
|
|
18109
|
+
* 校验 sessionId 是否属于 (personaId, token) tuple(前 12 字符 tokenHash 一致)。
|
|
18110
|
+
* chat:rename / chat:delete 用:listener 只能操作自己 tuple 内的 chat。
|
|
18111
|
+
*/
|
|
18112
|
+
tokenPrefixFor(personaId, token) {
|
|
18113
|
+
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
18114
|
+
return `${personaId}-${tokenHash}`;
|
|
18115
|
+
}
|
|
18020
18116
|
/**
|
|
18021
18117
|
* 老板插话:把"老板的话"作为 meta-text + metaSource='owner' 推到 sub-session 的事件流。
|
|
18022
18118
|
* 委托 SessionManager.injectOwnerMessage 路由到 reducer 'inject-owner-text' input
|
|
@@ -18045,13 +18141,11 @@ var PersonaManager = class {
|
|
|
18045
18141
|
throw new Error(`failed to generate unique personaId for label=${label}`);
|
|
18046
18142
|
}
|
|
18047
18143
|
/**
|
|
18048
|
-
* subSessionId
|
|
18049
|
-
*
|
|
18050
|
-
* - chatId 非 default → 三元组 `${personaId}-${tokenHash12}-${chatHash8}`
|
|
18144
|
+
* subSessionId 派生:永远三元组 `${personaId}-${tokenHash12}-${chatHash8}`,无 default 特例。
|
|
18145
|
+
* 旧 v1 实现保留 default 二元组兼容已废除。
|
|
18051
18146
|
*/
|
|
18052
18147
|
deriveSubSessionId(personaId, token, chatId) {
|
|
18053
18148
|
const tokenHash = import_node_crypto3.default.createHash("sha256").update(token).digest("hex").slice(0, 12);
|
|
18054
|
-
if (chatId === "default") return `${personaId}-${tokenHash}`;
|
|
18055
18149
|
const chatHash = import_node_crypto3.default.createHash("sha256").update(chatId).digest("hex").slice(0, 8);
|
|
18056
18150
|
return `${personaId}-${tokenHash}-${chatHash}`;
|
|
18057
18151
|
}
|
|
@@ -19180,9 +19274,14 @@ var PersonaBoundHandler = class {
|
|
|
19180
19274
|
this.deps = deps;
|
|
19181
19275
|
}
|
|
19182
19276
|
deps;
|
|
19277
|
+
// tuple fan-out 表:tokenPrefix → 同 tuple 所有 listener ws 的 send fn 集合
|
|
19278
|
+
tupleSubscribers = /* @__PURE__ */ new Map();
|
|
19279
|
+
// tuple → 当前活跃 ws 数;从 0→1 emit online;从 1→0 emit offline
|
|
19280
|
+
listenerWsCount = /* @__PURE__ */ new Map();
|
|
19183
19281
|
handle(ws, personaId) {
|
|
19282
|
+
let phase = "pre-auth";
|
|
19184
19283
|
let scope = null;
|
|
19185
|
-
let
|
|
19284
|
+
let unsubscribeSession = null;
|
|
19186
19285
|
const send = (frame) => {
|
|
19187
19286
|
try {
|
|
19188
19287
|
ws.send(JSON.stringify(frame));
|
|
@@ -19195,6 +19294,39 @@ var PersonaBoundHandler = class {
|
|
|
19195
19294
|
if (requestId) errFrame.requestId = requestId;
|
|
19196
19295
|
send(errFrame);
|
|
19197
19296
|
};
|
|
19297
|
+
const cleanupTupleRegistration = () => {
|
|
19298
|
+
if (!scope) return;
|
|
19299
|
+
const subs = this.tupleSubscribers.get(scope.tokenPrefix);
|
|
19300
|
+
if (subs) {
|
|
19301
|
+
subs.delete(send);
|
|
19302
|
+
if (subs.size === 0) this.tupleSubscribers.delete(scope.tokenPrefix);
|
|
19303
|
+
}
|
|
19304
|
+
const before = this.listenerWsCount.get(scope.tokenPrefix) ?? 0;
|
|
19305
|
+
const after = before - 1;
|
|
19306
|
+
if (after <= 0) {
|
|
19307
|
+
this.listenerWsCount.delete(scope.tokenPrefix);
|
|
19308
|
+
this.deps.ownerBroadcast(
|
|
19309
|
+
PersonaListenerStatusFrameSchema.parse({
|
|
19310
|
+
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
19311
|
+
personaId: scope.personaId,
|
|
19312
|
+
token: scope.token,
|
|
19313
|
+
status: "offline",
|
|
19314
|
+
activeWsCount: 0
|
|
19315
|
+
})
|
|
19316
|
+
);
|
|
19317
|
+
} else {
|
|
19318
|
+
this.listenerWsCount.set(scope.tokenPrefix, after);
|
|
19319
|
+
this.deps.ownerBroadcast(
|
|
19320
|
+
PersonaListenerStatusFrameSchema.parse({
|
|
19321
|
+
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
19322
|
+
personaId: scope.personaId,
|
|
19323
|
+
token: scope.token,
|
|
19324
|
+
status: "online",
|
|
19325
|
+
activeWsCount: after
|
|
19326
|
+
})
|
|
19327
|
+
);
|
|
19328
|
+
}
|
|
19329
|
+
};
|
|
19198
19330
|
ws.on("message", (raw) => {
|
|
19199
19331
|
let parsedRaw;
|
|
19200
19332
|
try {
|
|
@@ -19208,7 +19340,7 @@ var PersonaBoundHandler = class {
|
|
|
19208
19340
|
return;
|
|
19209
19341
|
}
|
|
19210
19342
|
const frame = parsedRaw;
|
|
19211
|
-
if (
|
|
19343
|
+
if (phase === "pre-auth") {
|
|
19212
19344
|
const authParse = AuthRequestFrameSchema.safeParse(frame);
|
|
19213
19345
|
if (!authParse.success) {
|
|
19214
19346
|
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
@@ -19226,30 +19358,33 @@ var PersonaBoundHandler = class {
|
|
|
19226
19358
|
ws.close(4404, "PERSONA_DELETED");
|
|
19227
19359
|
return;
|
|
19228
19360
|
}
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19361
|
+
const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
|
|
19362
|
+
scope = { personaId, token, tokenPrefix };
|
|
19363
|
+
phase = "tuple-watch";
|
|
19364
|
+
let subs = this.tupleSubscribers.get(tokenPrefix);
|
|
19365
|
+
if (!subs) {
|
|
19366
|
+
subs = /* @__PURE__ */ new Set();
|
|
19367
|
+
this.tupleSubscribers.set(tokenPrefix, subs);
|
|
19368
|
+
}
|
|
19369
|
+
subs.add(send);
|
|
19370
|
+
const nextCount = (this.listenerWsCount.get(tokenPrefix) ?? 0) + 1;
|
|
19371
|
+
this.listenerWsCount.set(tokenPrefix, nextCount);
|
|
19372
|
+
this.deps.ownerBroadcast(
|
|
19373
|
+
PersonaListenerStatusFrameSchema.parse({
|
|
19374
|
+
type: FRAME_TYPE_PERSONA_LISTENER_STATUS,
|
|
19232
19375
|
personaId,
|
|
19233
19376
|
token,
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
this.deps.logger.warn(
|
|
19239
|
-
`persona getOrCreateSubSession failed: ${err.message}`
|
|
19240
|
-
);
|
|
19241
|
-
ws.close(1011, "INTERNAL");
|
|
19242
|
-
return;
|
|
19243
|
-
}
|
|
19244
|
-
scope = { personaId, subSessionId: subSessionFile.sessionId };
|
|
19377
|
+
status: "online",
|
|
19378
|
+
activeWsCount: nextCount
|
|
19379
|
+
})
|
|
19380
|
+
);
|
|
19245
19381
|
send({ type: "auth:ok" });
|
|
19246
|
-
send(
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
});
|
|
19382
|
+
send(
|
|
19383
|
+
ChatListPushSchema.parse({
|
|
19384
|
+
type: FRAME_TYPE_CHAT_LIST,
|
|
19385
|
+
chats: this.listChatsForTuple(personaId, tokenPrefix)
|
|
19386
|
+
})
|
|
19387
|
+
);
|
|
19253
19388
|
return;
|
|
19254
19389
|
}
|
|
19255
19390
|
const type = frame.type;
|
|
@@ -19262,8 +19397,183 @@ var PersonaBoundHandler = class {
|
|
|
19262
19397
|
send({ type: "pong", at: Date.now() });
|
|
19263
19398
|
return;
|
|
19264
19399
|
}
|
|
19400
|
+
if (!scope) {
|
|
19401
|
+
ws.close(4400, "PROTOCOL_VIOLATION");
|
|
19402
|
+
return;
|
|
19403
|
+
}
|
|
19404
|
+
if (type === "chat:open") {
|
|
19405
|
+
const parsed = ChatOpenRequestSchema.safeParse(frame);
|
|
19406
|
+
if (!parsed.success) {
|
|
19407
|
+
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
19408
|
+
return;
|
|
19409
|
+
}
|
|
19410
|
+
const { chatId, autoCreate } = parsed.data;
|
|
19411
|
+
let sessionFile;
|
|
19412
|
+
let isNew = false;
|
|
19413
|
+
try {
|
|
19414
|
+
if (autoCreate) {
|
|
19415
|
+
const r = this.deps.personaManager.getOrCreateSubSession(scope.personaId, scope.token, chatId);
|
|
19416
|
+
sessionFile = r.sessionFile;
|
|
19417
|
+
isNew = r.isNew;
|
|
19418
|
+
} else {
|
|
19419
|
+
sessionFile = this.deps.personaManager.getSubSession(scope.personaId, scope.token, chatId);
|
|
19420
|
+
if (!sessionFile) {
|
|
19421
|
+
sendError(requestId, "SESSION_NOT_FOUND", `chatId not found: ${chatId}`);
|
|
19422
|
+
return;
|
|
19423
|
+
}
|
|
19424
|
+
}
|
|
19425
|
+
} catch (err) {
|
|
19426
|
+
sendError(requestId, "INTERNAL", err.message);
|
|
19427
|
+
return;
|
|
19428
|
+
}
|
|
19429
|
+
if (unsubscribeSession && scope.activeSubSessionId !== sessionFile.sessionId) {
|
|
19430
|
+
unsubscribeSession();
|
|
19431
|
+
unsubscribeSession = null;
|
|
19432
|
+
}
|
|
19433
|
+
if (!unsubscribeSession) {
|
|
19434
|
+
unsubscribeSession = this.deps.sessionManager.subscribe(
|
|
19435
|
+
sessionFile.sessionId,
|
|
19436
|
+
{ kind: "persona", personaId: scope.personaId, mode: "listener" },
|
|
19437
|
+
(eventFrame) => send(eventFrame)
|
|
19438
|
+
);
|
|
19439
|
+
}
|
|
19440
|
+
scope.activeSubSessionId = sessionFile.sessionId;
|
|
19441
|
+
phase = "chat-active";
|
|
19442
|
+
const persona = this.deps.registry.get(scope.personaId);
|
|
19443
|
+
const infoFrame = {
|
|
19444
|
+
type: "session:info",
|
|
19445
|
+
sessionId: sessionFile.sessionId,
|
|
19446
|
+
chatId,
|
|
19447
|
+
label: persona?.label ?? "",
|
|
19448
|
+
cwd: sessionFile.cwd
|
|
19449
|
+
};
|
|
19450
|
+
if (persona?.model) infoFrame.model = persona.model;
|
|
19451
|
+
if (requestId) infoFrame.requestId = requestId;
|
|
19452
|
+
send(infoFrame);
|
|
19453
|
+
send({
|
|
19454
|
+
type: "session:status",
|
|
19455
|
+
sessionId: sessionFile.sessionId,
|
|
19456
|
+
status: this.deps.sessionManager.getCurrentStatus(sessionFile.sessionId)
|
|
19457
|
+
});
|
|
19458
|
+
if (isNew) {
|
|
19459
|
+
const createdBase = {
|
|
19460
|
+
sessionId: sessionFile.sessionId,
|
|
19461
|
+
chatId,
|
|
19462
|
+
label: sessionFile.label,
|
|
19463
|
+
createdAt: sessionFile.createdAt
|
|
19464
|
+
};
|
|
19465
|
+
this.fanoutTuple(
|
|
19466
|
+
scope.tokenPrefix,
|
|
19467
|
+
ChatCreatedFrameSchema.parse({ type: FRAME_TYPE_CHAT_CREATED, ...createdBase })
|
|
19468
|
+
);
|
|
19469
|
+
this.deps.ownerBroadcast(
|
|
19470
|
+
PersonaListenerChatCreatedFrameSchema.parse({
|
|
19471
|
+
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_CREATED,
|
|
19472
|
+
personaId: scope.personaId,
|
|
19473
|
+
token: scope.token,
|
|
19474
|
+
...createdBase
|
|
19475
|
+
})
|
|
19476
|
+
);
|
|
19477
|
+
}
|
|
19478
|
+
return;
|
|
19479
|
+
}
|
|
19480
|
+
if (type === "chat:rename") {
|
|
19481
|
+
const parsed = ChatRenameRequestSchema.safeParse(frame);
|
|
19482
|
+
if (!parsed.success) {
|
|
19483
|
+
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
19484
|
+
return;
|
|
19485
|
+
}
|
|
19486
|
+
const { sessionId: targetId, label } = parsed.data;
|
|
19487
|
+
if (!this.isInTuple(targetId, scope.tokenPrefix)) {
|
|
19488
|
+
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
19489
|
+
return;
|
|
19490
|
+
}
|
|
19491
|
+
try {
|
|
19492
|
+
this.deps.sessionManager.renameForScope({
|
|
19493
|
+
sessionId: targetId,
|
|
19494
|
+
scope: { kind: "persona", personaId: scope.personaId, mode: "listener" },
|
|
19495
|
+
label
|
|
19496
|
+
});
|
|
19497
|
+
} catch (err) {
|
|
19498
|
+
const e = err;
|
|
19499
|
+
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
19500
|
+
return;
|
|
19501
|
+
}
|
|
19502
|
+
if (requestId) send({ type: FRAME_TYPE_CHAT_RENAME, requestId, ok: true });
|
|
19503
|
+
this.fanoutTuple(
|
|
19504
|
+
scope.tokenPrefix,
|
|
19505
|
+
ChatRenamedFrameSchema.parse({ type: FRAME_TYPE_CHAT_RENAMED, sessionId: targetId, label })
|
|
19506
|
+
);
|
|
19507
|
+
this.deps.ownerBroadcast(
|
|
19508
|
+
PersonaListenerChatRenamedFrameSchema.parse({
|
|
19509
|
+
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_RENAMED,
|
|
19510
|
+
personaId: scope.personaId,
|
|
19511
|
+
token: scope.token,
|
|
19512
|
+
sessionId: targetId,
|
|
19513
|
+
label
|
|
19514
|
+
})
|
|
19515
|
+
);
|
|
19516
|
+
return;
|
|
19517
|
+
}
|
|
19518
|
+
if (type === "chat:delete") {
|
|
19519
|
+
const parsed = ChatDeleteRequestSchema.safeParse(frame);
|
|
19520
|
+
if (!parsed.success) {
|
|
19521
|
+
sendError(requestId, "VALIDATION_ERROR", parsed.error.message);
|
|
19522
|
+
return;
|
|
19523
|
+
}
|
|
19524
|
+
const { sessionId: targetId } = parsed.data;
|
|
19525
|
+
if (!this.isInTuple(targetId, scope.tokenPrefix)) {
|
|
19526
|
+
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
19527
|
+
return;
|
|
19528
|
+
}
|
|
19529
|
+
const before = this.deps.sessionManager.readForScope(targetId, {
|
|
19530
|
+
kind: "persona",
|
|
19531
|
+
personaId: scope.personaId,
|
|
19532
|
+
mode: "listener"
|
|
19533
|
+
});
|
|
19534
|
+
const chatIdForFrame = before?.chatId ?? "unknown";
|
|
19535
|
+
try {
|
|
19536
|
+
this.deps.sessionManager.deleteForScope({
|
|
19537
|
+
sessionId: targetId,
|
|
19538
|
+
scope: { kind: "persona", personaId: scope.personaId, mode: "listener" }
|
|
19539
|
+
});
|
|
19540
|
+
} catch (err) {
|
|
19541
|
+
const e = err;
|
|
19542
|
+
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
19543
|
+
return;
|
|
19544
|
+
}
|
|
19545
|
+
if (requestId) send({ type: FRAME_TYPE_CHAT_DELETE, requestId, ok: true });
|
|
19546
|
+
this.fanoutTuple(
|
|
19547
|
+
scope.tokenPrefix,
|
|
19548
|
+
ChatDeletedFrameSchema.parse({ type: FRAME_TYPE_CHAT_DELETED, sessionId: targetId })
|
|
19549
|
+
);
|
|
19550
|
+
this.deps.ownerBroadcast(
|
|
19551
|
+
PersonaListenerChatDeletedFrameSchema.parse({
|
|
19552
|
+
type: FRAME_TYPE_PERSONA_LISTENER_CHAT_DELETED,
|
|
19553
|
+
personaId: scope.personaId,
|
|
19554
|
+
token: scope.token,
|
|
19555
|
+
sessionId: targetId,
|
|
19556
|
+
chatId: chatIdForFrame
|
|
19557
|
+
})
|
|
19558
|
+
);
|
|
19559
|
+
if (scope.activeSubSessionId === targetId) {
|
|
19560
|
+
unsubscribeSession?.();
|
|
19561
|
+
unsubscribeSession = null;
|
|
19562
|
+
scope.activeSubSessionId = void 0;
|
|
19563
|
+
phase = "tuple-watch";
|
|
19564
|
+
}
|
|
19565
|
+
return;
|
|
19566
|
+
}
|
|
19567
|
+
if (phase !== "chat-active" || !scope.activeSubSessionId) {
|
|
19568
|
+
sendError(
|
|
19569
|
+
requestId,
|
|
19570
|
+
"METHOD_NOT_ALLOWED",
|
|
19571
|
+
`${typeof type === "string" ? type : "unknown"} requires chat:open first`
|
|
19572
|
+
);
|
|
19573
|
+
return;
|
|
19574
|
+
}
|
|
19265
19575
|
const requireScopedSession = () => {
|
|
19266
|
-
if (frame.sessionId !== scope.
|
|
19576
|
+
if (frame.sessionId !== scope.activeSubSessionId) {
|
|
19267
19577
|
sendError(requestId, "FORBIDDEN", "sessionId out of scope");
|
|
19268
19578
|
return false;
|
|
19269
19579
|
}
|
|
@@ -19275,33 +19585,6 @@ var PersonaBoundHandler = class {
|
|
|
19275
19585
|
mode: "listener"
|
|
19276
19586
|
});
|
|
19277
19587
|
switch (type) {
|
|
19278
|
-
case "session:subscribe": {
|
|
19279
|
-
if (!requireScopedSession()) return;
|
|
19280
|
-
if (unsubscribe) unsubscribe();
|
|
19281
|
-
unsubscribe = this.deps.sessionManager.subscribe(
|
|
19282
|
-
scope.subSessionId,
|
|
19283
|
-
listenerScope(),
|
|
19284
|
-
(eventFrame) => send(eventFrame)
|
|
19285
|
-
);
|
|
19286
|
-
if (requestId)
|
|
19287
|
-
send({ type: "subscribed", requestId, sessionId: scope.subSessionId });
|
|
19288
|
-
const currentStatus = this.deps.sessionManager.getCurrentStatus(scope.subSessionId);
|
|
19289
|
-
send({
|
|
19290
|
-
type: "session:status",
|
|
19291
|
-
sessionId: scope.subSessionId,
|
|
19292
|
-
status: currentStatus
|
|
19293
|
-
});
|
|
19294
|
-
return;
|
|
19295
|
-
}
|
|
19296
|
-
case "session:unsubscribe": {
|
|
19297
|
-
if (!requireScopedSession()) return;
|
|
19298
|
-
if (unsubscribe) {
|
|
19299
|
-
unsubscribe();
|
|
19300
|
-
unsubscribe = null;
|
|
19301
|
-
}
|
|
19302
|
-
if (requestId) send({ type: "unsubscribed", requestId, sessionId: scope.subSessionId });
|
|
19303
|
-
return;
|
|
19304
|
-
}
|
|
19305
19588
|
case "session:send": {
|
|
19306
19589
|
if (!requireScopedSession()) return;
|
|
19307
19590
|
const text = frame.text;
|
|
@@ -19311,7 +19594,7 @@ var PersonaBoundHandler = class {
|
|
|
19311
19594
|
}
|
|
19312
19595
|
try {
|
|
19313
19596
|
this.deps.sessionManager.sendForScope({
|
|
19314
|
-
sessionId: scope.
|
|
19597
|
+
sessionId: scope.activeSubSessionId,
|
|
19315
19598
|
scope: listenerScope(),
|
|
19316
19599
|
text
|
|
19317
19600
|
});
|
|
@@ -19326,10 +19609,11 @@ var PersonaBoundHandler = class {
|
|
|
19326
19609
|
if (!requireScopedSession()) return;
|
|
19327
19610
|
try {
|
|
19328
19611
|
this.deps.sessionManager.resetForScope({
|
|
19329
|
-
sessionId: scope.
|
|
19612
|
+
sessionId: scope.activeSubSessionId,
|
|
19330
19613
|
scope: listenerScope()
|
|
19331
19614
|
});
|
|
19332
|
-
if (requestId)
|
|
19615
|
+
if (requestId)
|
|
19616
|
+
send({ type: "session:info", sessionId: scope.activeSubSessionId, requestId });
|
|
19333
19617
|
} catch (err) {
|
|
19334
19618
|
const e = err;
|
|
19335
19619
|
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
@@ -19342,7 +19626,7 @@ var PersonaBoundHandler = class {
|
|
|
19342
19626
|
const offset = typeof frame.offset === "number" && frame.offset >= 0 ? frame.offset : 0;
|
|
19343
19627
|
try {
|
|
19344
19628
|
const page = this.deps.sessionManager.readHistoryPageForScope(
|
|
19345
|
-
scope.
|
|
19629
|
+
scope.activeSubSessionId,
|
|
19346
19630
|
listenerScope(),
|
|
19347
19631
|
limit,
|
|
19348
19632
|
offset
|
|
@@ -19362,94 +19646,6 @@ var PersonaBoundHandler = class {
|
|
|
19362
19646
|
}
|
|
19363
19647
|
return;
|
|
19364
19648
|
}
|
|
19365
|
-
case "chat:list": {
|
|
19366
|
-
const tokenPart = scope.subSessionId.slice(
|
|
19367
|
-
scope.personaId.length + 1,
|
|
19368
|
-
scope.personaId.length + 1 + 12
|
|
19369
|
-
);
|
|
19370
|
-
const tokenPrefix = `${scope.personaId}-${tokenPart}`;
|
|
19371
|
-
try {
|
|
19372
|
-
const all = this.deps.sessionManager.listPersonaSessions(scope.personaId, "listener");
|
|
19373
|
-
const sameTuple = all.filter(
|
|
19374
|
-
(s) => s.sessionId === tokenPrefix || s.sessionId.startsWith(`${tokenPrefix}-`)
|
|
19375
|
-
);
|
|
19376
|
-
const chats = sameTuple.map((s) => ({
|
|
19377
|
-
sessionId: s.sessionId,
|
|
19378
|
-
chatId: s.chatId ?? (s.sessionId === tokenPrefix ? "default" : "unknown"),
|
|
19379
|
-
label: s.label ?? "",
|
|
19380
|
-
createdAt: s.createdAt,
|
|
19381
|
-
isDefault: s.sessionId === tokenPrefix
|
|
19382
|
-
}));
|
|
19383
|
-
if (requestId) send({ type: "chat:list", requestId, chats });
|
|
19384
|
-
else send({ type: "chat:list", chats });
|
|
19385
|
-
} catch (err) {
|
|
19386
|
-
const e = err;
|
|
19387
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
19388
|
-
}
|
|
19389
|
-
return;
|
|
19390
|
-
}
|
|
19391
|
-
case "chat:rename": {
|
|
19392
|
-
const targetId = frame.sessionId;
|
|
19393
|
-
const newLabel = frame.label;
|
|
19394
|
-
if (typeof targetId !== "string" || targetId.length === 0) {
|
|
19395
|
-
sendError(requestId, "VALIDATION_ERROR", "sessionId must be non-empty string");
|
|
19396
|
-
return;
|
|
19397
|
-
}
|
|
19398
|
-
if (typeof newLabel !== "string" || newLabel.length === 0) {
|
|
19399
|
-
sendError(requestId, "VALIDATION_ERROR", "label must be non-empty string");
|
|
19400
|
-
return;
|
|
19401
|
-
}
|
|
19402
|
-
const tokenPart = scope.subSessionId.slice(
|
|
19403
|
-
scope.personaId.length + 1,
|
|
19404
|
-
scope.personaId.length + 1 + 12
|
|
19405
|
-
);
|
|
19406
|
-
const tokenPrefix = `${scope.personaId}-${tokenPart}`;
|
|
19407
|
-
const sameTuple = targetId === tokenPrefix || targetId.startsWith(`${tokenPrefix}-`);
|
|
19408
|
-
if (!sameTuple) {
|
|
19409
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
19410
|
-
return;
|
|
19411
|
-
}
|
|
19412
|
-
try {
|
|
19413
|
-
this.deps.sessionManager.renameForScope({
|
|
19414
|
-
sessionId: targetId,
|
|
19415
|
-
scope: listenerScope(),
|
|
19416
|
-
label: newLabel
|
|
19417
|
-
});
|
|
19418
|
-
if (requestId) send({ type: "chat:rename", requestId, ok: true });
|
|
19419
|
-
} catch (err) {
|
|
19420
|
-
const e = err;
|
|
19421
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
19422
|
-
}
|
|
19423
|
-
return;
|
|
19424
|
-
}
|
|
19425
|
-
case "chat:delete": {
|
|
19426
|
-
const targetId = frame.sessionId;
|
|
19427
|
-
if (typeof targetId !== "string" || targetId.length === 0) {
|
|
19428
|
-
sendError(requestId, "VALIDATION_ERROR", "sessionId must be non-empty string");
|
|
19429
|
-
return;
|
|
19430
|
-
}
|
|
19431
|
-
const tokenPart = scope.subSessionId.slice(
|
|
19432
|
-
scope.personaId.length + 1,
|
|
19433
|
-
scope.personaId.length + 1 + 12
|
|
19434
|
-
);
|
|
19435
|
-
const tokenPrefix = `${scope.personaId}-${tokenPart}`;
|
|
19436
|
-
const sameTuple = targetId === tokenPrefix || targetId.startsWith(`${tokenPrefix}-`);
|
|
19437
|
-
if (!sameTuple) {
|
|
19438
|
-
sendError(requestId, "FORBIDDEN", "sessionId out of (persona, token) scope");
|
|
19439
|
-
return;
|
|
19440
|
-
}
|
|
19441
|
-
try {
|
|
19442
|
-
this.deps.sessionManager.deleteForScope({
|
|
19443
|
-
sessionId: targetId,
|
|
19444
|
-
scope: listenerScope()
|
|
19445
|
-
});
|
|
19446
|
-
if (requestId) send({ type: "chat:delete", requestId, ok: true });
|
|
19447
|
-
} catch (err) {
|
|
19448
|
-
const e = err;
|
|
19449
|
-
sendError(requestId, e.code ?? "INTERNAL", e.message ?? String(err));
|
|
19450
|
-
}
|
|
19451
|
-
return;
|
|
19452
|
-
}
|
|
19453
19649
|
default:
|
|
19454
19650
|
sendError(
|
|
19455
19651
|
requestId,
|
|
@@ -19460,10 +19656,43 @@ var PersonaBoundHandler = class {
|
|
|
19460
19656
|
}
|
|
19461
19657
|
});
|
|
19462
19658
|
ws.on("close", () => {
|
|
19463
|
-
|
|
19464
|
-
|
|
19659
|
+
unsubscribeSession?.();
|
|
19660
|
+
unsubscribeSession = null;
|
|
19661
|
+
cleanupTupleRegistration();
|
|
19465
19662
|
});
|
|
19466
19663
|
}
|
|
19664
|
+
// ---- helpers ----
|
|
19665
|
+
fanoutTuple(tokenPrefix, frame) {
|
|
19666
|
+
const subs = this.tupleSubscribers.get(tokenPrefix);
|
|
19667
|
+
if (!subs) return;
|
|
19668
|
+
for (const fn of subs) {
|
|
19669
|
+
try {
|
|
19670
|
+
fn(frame);
|
|
19671
|
+
} catch {
|
|
19672
|
+
}
|
|
19673
|
+
}
|
|
19674
|
+
}
|
|
19675
|
+
isInTuple(sessionId, tokenPrefix) {
|
|
19676
|
+
return sessionId === tokenPrefix || sessionId.startsWith(`${tokenPrefix}-`);
|
|
19677
|
+
}
|
|
19678
|
+
listChatsForTuple(personaId, tokenPrefix) {
|
|
19679
|
+
const all = this.deps.sessionManager.listPersonaSessions(personaId, "listener");
|
|
19680
|
+
return all.filter((s) => s.sessionId === tokenPrefix || s.sessionId.startsWith(`${tokenPrefix}-`)).map((s) => ({
|
|
19681
|
+
sessionId: s.sessionId,
|
|
19682
|
+
chatId: s.chatId ?? "unknown",
|
|
19683
|
+
label: s.label ?? "",
|
|
19684
|
+
createdAt: s.createdAt
|
|
19685
|
+
}));
|
|
19686
|
+
}
|
|
19687
|
+
// ---------------- owner-side helpers ----------------
|
|
19688
|
+
/**
|
|
19689
|
+
* owner-side bootstrap RPC:拉 (personaId, token) tuple 下所有 listener sub-session 元数据。
|
|
19690
|
+
* 由 handlers/persona.ts persona:listListenerChatsForToken 调用。
|
|
19691
|
+
*/
|
|
19692
|
+
listChatsForOwner(personaId, token) {
|
|
19693
|
+
const tokenPrefix = this.deps.personaManager.tokenPrefixFor(personaId, token);
|
|
19694
|
+
return this.listChatsForTuple(personaId, tokenPrefix);
|
|
19695
|
+
}
|
|
19467
19696
|
};
|
|
19468
19697
|
|
|
19469
19698
|
// src/transport/auth.ts
|
|
@@ -20882,7 +21111,7 @@ function buildMetaHandlers(deps) {
|
|
|
20882
21111
|
// src/handlers/persona.ts
|
|
20883
21112
|
init_protocol();
|
|
20884
21113
|
function buildPersonaHandlers(deps) {
|
|
20885
|
-
const { personaManager, personaRegistry, sessionManager } = deps;
|
|
21114
|
+
const { personaManager, personaRegistry, sessionManager, personaBoundHandler } = deps;
|
|
20886
21115
|
const create = async (frame) => {
|
|
20887
21116
|
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
20888
21117
|
const args = PersonaCreateArgsSchema.parse(rest);
|
|
@@ -20945,6 +21174,13 @@ function buildPersonaHandlers(deps) {
|
|
|
20945
21174
|
response: { type: "persona:appendOwnerMessage", ok: true }
|
|
20946
21175
|
};
|
|
20947
21176
|
};
|
|
21177
|
+
const listListenerChatsForToken = async (frame) => {
|
|
21178
|
+
const args = PersonaListListenerChatsForTokenArgsSchema.parse(frame);
|
|
21179
|
+
const chats = personaBoundHandler.listChatsForOwner(args.personaId, args.token);
|
|
21180
|
+
return {
|
|
21181
|
+
response: { type: "persona:listListenerChatsForToken", chats }
|
|
21182
|
+
};
|
|
21183
|
+
};
|
|
20948
21184
|
return {
|
|
20949
21185
|
"persona:create": create,
|
|
20950
21186
|
"persona:list": list,
|
|
@@ -20954,6 +21190,7 @@ function buildPersonaHandlers(deps) {
|
|
|
20954
21190
|
"persona:issueToken": issueToken,
|
|
20955
21191
|
"persona:revokeToken": revokeToken,
|
|
20956
21192
|
"persona:listSubSessions": listSubSessions,
|
|
21193
|
+
"persona:listListenerChatsForToken": listListenerChatsForToken,
|
|
20957
21194
|
"persona:appendOwnerMessage": appendOwnerMessage
|
|
20958
21195
|
};
|
|
20959
21196
|
}
|
|
@@ -20971,7 +21208,8 @@ function buildMethodHandlers(deps) {
|
|
|
20971
21208
|
...buildPersonaHandlers({
|
|
20972
21209
|
personaManager: deps.personaManager,
|
|
20973
21210
|
personaRegistry: deps.personaRegistry,
|
|
20974
|
-
sessionManager: deps.manager
|
|
21211
|
+
sessionManager: deps.manager,
|
|
21212
|
+
personaBoundHandler: deps.personaBoundHandler
|
|
20975
21213
|
})
|
|
20976
21214
|
};
|
|
20977
21215
|
}
|
|
@@ -21079,6 +21317,16 @@ async function startDaemon(config) {
|
|
|
21079
21317
|
sessionManager: manager
|
|
21080
21318
|
});
|
|
21081
21319
|
let currentTunnelUrl = null;
|
|
21320
|
+
const personaBoundHandler = new PersonaBoundHandler({
|
|
21321
|
+
registry: personaRegistry,
|
|
21322
|
+
personaManager,
|
|
21323
|
+
personaStore,
|
|
21324
|
+
sessionManager: manager,
|
|
21325
|
+
logger,
|
|
21326
|
+
ownerBroadcast: (frame) => {
|
|
21327
|
+
transport?.broadcastAll(frame);
|
|
21328
|
+
}
|
|
21329
|
+
});
|
|
21082
21330
|
const handlers = buildMethodHandlers({
|
|
21083
21331
|
manager,
|
|
21084
21332
|
workspace,
|
|
@@ -21090,15 +21338,9 @@ async function startDaemon(config) {
|
|
|
21090
21338
|
store,
|
|
21091
21339
|
personaManager,
|
|
21092
21340
|
personaRegistry,
|
|
21341
|
+
personaBoundHandler,
|
|
21093
21342
|
getTunnelUrl: () => currentTunnelUrl
|
|
21094
21343
|
});
|
|
21095
|
-
const personaBoundHandler = new PersonaBoundHandler({
|
|
21096
|
-
registry: personaRegistry,
|
|
21097
|
-
personaManager,
|
|
21098
|
-
personaStore,
|
|
21099
|
-
sessionManager: manager,
|
|
21100
|
-
logger
|
|
21101
|
-
});
|
|
21102
21344
|
wsServer = new LocalWsServer({
|
|
21103
21345
|
host: config.host,
|
|
21104
21346
|
port: config.port,
|
package/package.json
CHANGED