@clawos-dev/clawd 0.2.276 → 0.2.278
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 +78 -2
- package/dist/dispatch/mcp-server.cjs +14 -0
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/SKILL.md +16 -7
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/scripts/link-sessions.mjs +45 -64
- package/dist/share-ui/assets/{guest-XaN4r7jJ.js → guest-DvP5myWi.js} +18 -18
- package/dist/share-ui/guest.html +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -6779,7 +6779,7 @@ var init_principal = __esm({
|
|
|
6779
6779
|
});
|
|
6780
6780
|
|
|
6781
6781
|
// ../protocol/src/schemas.ts
|
|
6782
|
-
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, ToolFeaturesSchema, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionListArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionInterruptArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionCurrentTurnSenderArgs, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, ClientInfoSchema, AuthRequestFrameSchema, AuthOkFrameSchema, DeviceConnectionEntrySchema, DeviceConnectionsResponseSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, GetProjectArgsSchema, GetProjectResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
|
|
6782
|
+
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, ToolFeaturesSchema, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionImportExternalArgs, SessionListArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionInterruptArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionCurrentTurnSenderArgs, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, ClientInfoSchema, AuthRequestFrameSchema, AuthOkFrameSchema, DeviceConnectionEntrySchema, DeviceConnectionsResponseSchema, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, PROJECT_STAGE_VALUES, ProjectStageSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, GetProjectArgsSchema, GetProjectResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema, SetProdUrlArgsSchema, SetProdUrlResultSchema, StartDevServerArgsSchema, StartDevServerResultSchema, ASSISTANT_REPORTABLE_STAGES, ReportStageArgsSchema, ReportStageResultSchema, StopDevServerArgsSchema, StopDevServerResultSchema, AppBuilderProjectUpdatedEventSchema, PublishArgsSchema, PublishResultSchema, DismissPublishJobArgsSchema, DismissPublishJobResultSchema, AppBuilderPublishProgressEventSchema, AppBuilderPublishFailedEventSchema;
|
|
6783
6783
|
var init_schemas = __esm({
|
|
6784
6784
|
"../protocol/src/schemas.ts"() {
|
|
6785
6785
|
"use strict";
|
|
@@ -7225,6 +7225,16 @@ var init_schemas = __esm({
|
|
|
7225
7225
|
}).refine((args) => args.cwd != null || args.ownerPersonaId != null, {
|
|
7226
7226
|
message: "cwd \u4E0E ownerPersonaId \u81F3\u5C11\u4F20\u4E00\u4E2A"
|
|
7227
7227
|
});
|
|
7228
|
+
SessionImportExternalArgs = external_exports.object({
|
|
7229
|
+
cwd: external_exports.string().min(1),
|
|
7230
|
+
tool: external_exports.string().min(1),
|
|
7231
|
+
toolSessionId: external_exports.string().min(1),
|
|
7232
|
+
label: external_exports.string().optional(),
|
|
7233
|
+
iconKey: external_exports.string().optional(),
|
|
7234
|
+
createdAt: external_exports.string().datetime(),
|
|
7235
|
+
updatedAt: external_exports.string().datetime(),
|
|
7236
|
+
ownerPersonaId: external_exports.string().min(1).optional()
|
|
7237
|
+
});
|
|
7228
7238
|
SessionListArgs = external_exports.object({
|
|
7229
7239
|
/** true = 只返回 mirror(originOwnerPrincipalId 非空,即「我在朋友 daemon 上创建」的本机存证)。 */
|
|
7230
7240
|
originOnly: external_exports.boolean().optional(),
|
|
@@ -7801,6 +7811,10 @@ var init_methods = __esm({
|
|
|
7801
7811
|
anyOf: [{ required: ["cwd"] }, { required: ["ownerPersonaId"] }]
|
|
7802
7812
|
}
|
|
7803
7813
|
},
|
|
7814
|
+
"session:importExternal": {
|
|
7815
|
+
summary: "\u628A Claude Code / Codex \u65E2\u6709\u5386\u53F2\u4F1A\u8BDD\u5BFC\u5165\u4E3A clawd \u5F15\u7528\uFF1A\u53EA\u5199 SessionFile\uFF0C\u4E0D\u62C9\u8D77 runner\uFF0C\u4E0D\u6539\u539F\u59CB transcript\uFF1B\u6309 toolSessionId \u8DE8 owned scope \u53BB\u91CD\uFF0C\u65B0\u5BFC\u5165\u4F1A\u5E7F\u64AD session:info",
|
|
7816
|
+
args: SessionImportExternalArgs
|
|
7817
|
+
},
|
|
7804
7818
|
"session:list": {
|
|
7805
7819
|
summary: "\u5217\u51FA\u5F53\u524D\u8EAB\u4EFD\u53EF\u89C1\u7684\u4F1A\u8BDD\uFF08owner \u5168\u91CF\uFF1Bguest \u9650\u88AB\u6388\u6743 persona\uFF09",
|
|
7806
7820
|
args: SessionListArgs
|
|
@@ -45963,6 +45977,41 @@ var SessionManager = class {
|
|
|
45963
45977
|
const written = this.storeFor(scope).write(file);
|
|
45964
45978
|
return { response: written, broadcast: [] };
|
|
45965
45979
|
}
|
|
45980
|
+
importExternal(args, creatorPrincipalId, creatorDisplayName, creatorFeishuUnionId) {
|
|
45981
|
+
const existing = this.listAllOwned().find((file2) => file2.toolSessionId === args.toolSessionId);
|
|
45982
|
+
if (existing) {
|
|
45983
|
+
return {
|
|
45984
|
+
response: { ...existing, alreadyLinked: true },
|
|
45985
|
+
broadcast: []
|
|
45986
|
+
};
|
|
45987
|
+
}
|
|
45988
|
+
try {
|
|
45989
|
+
const stat = import_node_fs9.default.statSync(args.cwd);
|
|
45990
|
+
if (!stat.isDirectory()) throw new Error("not dir");
|
|
45991
|
+
} catch {
|
|
45992
|
+
throw new ClawdError(ERROR_CODES.INVALID_CWD, `cwd not a directory: ${args.cwd}`);
|
|
45993
|
+
}
|
|
45994
|
+
this.deps.getAdapter(args.tool);
|
|
45995
|
+
const file = {
|
|
45996
|
+
sessionId: newSessionId(),
|
|
45997
|
+
cwd: args.cwd,
|
|
45998
|
+
tool: args.tool,
|
|
45999
|
+
toolSessionId: args.toolSessionId,
|
|
46000
|
+
label: args.label,
|
|
46001
|
+
iconKey: args.iconKey,
|
|
46002
|
+
ownerPersonaId: args.ownerPersonaId,
|
|
46003
|
+
creatorPrincipalId,
|
|
46004
|
+
creatorDisplayName,
|
|
46005
|
+
...creatorFeishuUnionId ? { creatorFeishuUnionId } : {},
|
|
46006
|
+
createdAt: args.createdAt,
|
|
46007
|
+
updatedAt: args.updatedAt
|
|
46008
|
+
};
|
|
46009
|
+
const written = this.writeOwned(file);
|
|
46010
|
+
return {
|
|
46011
|
+
response: written,
|
|
46012
|
+
broadcast: [{ frame: { type: "session:info", ...written }, target: "all" }]
|
|
46013
|
+
};
|
|
46014
|
+
}
|
|
45966
46015
|
pin(args) {
|
|
45967
46016
|
const existing = this.getFile(args.sessionId);
|
|
45968
46017
|
const pinnedAt = args.pinned ? (this.deps.now ?? Date.now)() : null;
|
|
@@ -56332,6 +56381,31 @@ function buildSessionHandlers(deps) {
|
|
|
56332
56381
|
);
|
|
56333
56382
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
56334
56383
|
};
|
|
56384
|
+
const importExternal = async (frame, _client, ctx) => {
|
|
56385
|
+
if (ctx && ctx.principal.kind !== "owner") {
|
|
56386
|
+
throw new ClawdError(
|
|
56387
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
56388
|
+
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot session:importExternal`
|
|
56389
|
+
);
|
|
56390
|
+
}
|
|
56391
|
+
const args = SessionImportExternalArgs.parse(frame);
|
|
56392
|
+
if (args.ownerPersonaId) {
|
|
56393
|
+
const persona = deps.personaRegistry.get(args.ownerPersonaId);
|
|
56394
|
+
if (!persona) {
|
|
56395
|
+
throw new Error(`persona not found: ${args.ownerPersonaId}`);
|
|
56396
|
+
}
|
|
56397
|
+
}
|
|
56398
|
+
const creatorPrincipalId = ctx?.principal.id ?? ownerPrincipalId;
|
|
56399
|
+
const creatorDisplayName = ctx?.principal.displayName ?? "Owner";
|
|
56400
|
+
const creatorFeishuUnionId = ctx?.principal.feishuUnionId;
|
|
56401
|
+
const { response, broadcast } = manager.importExternal(
|
|
56402
|
+
args,
|
|
56403
|
+
creatorPrincipalId,
|
|
56404
|
+
creatorDisplayName,
|
|
56405
|
+
creatorFeishuUnionId
|
|
56406
|
+
);
|
|
56407
|
+
return { response: { type: "session:info", ...response }, broadcast };
|
|
56408
|
+
};
|
|
56335
56409
|
const list = async (frame, _client, ctx) => {
|
|
56336
56410
|
const args = SessionListArgs.parse(frame ?? {});
|
|
56337
56411
|
const narrow = (sessions) => {
|
|
@@ -56650,6 +56724,7 @@ function buildSessionHandlers(deps) {
|
|
|
56650
56724
|
};
|
|
56651
56725
|
return {
|
|
56652
56726
|
"session:create": create,
|
|
56727
|
+
"session:importExternal": importExternal,
|
|
56653
56728
|
"session:list": list,
|
|
56654
56729
|
"session:get": get,
|
|
56655
56730
|
"session:update": update,
|
|
@@ -57738,6 +57813,7 @@ var METHOD_GRANT_MAP = {
|
|
|
57738
57813
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
57739
57814
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
57740
57815
|
"session:create": CAPABILITY_SCOPED,
|
|
57816
|
+
"session:importExternal": CAPABILITY_SCOPED,
|
|
57741
57817
|
"session:list": CAPABILITY_SCOPED,
|
|
57742
57818
|
"session:get": CAPABILITY_SCOPED,
|
|
57743
57819
|
"session:update": CAPABILITY_SCOPED,
|
|
@@ -57929,7 +58005,7 @@ function computeMethodAccess(args) {
|
|
|
57929
58005
|
}
|
|
57930
58006
|
|
|
57931
58007
|
// src/version.ts
|
|
57932
|
-
var version = "0.2.
|
|
58008
|
+
var version = "0.2.278".length > 0 ? "0.2.278" : "dev";
|
|
57933
58009
|
|
|
57934
58010
|
// src/cli-probe/probe.ts
|
|
57935
58011
|
var fs54 = __toESM(require("fs"), 1);
|
|
@@ -22376,6 +22376,16 @@ var SessionCreateArgs = external_exports.object({
|
|
|
22376
22376
|
}).refine((args) => args.cwd != null || args.ownerPersonaId != null, {
|
|
22377
22377
|
message: "cwd \u4E0E ownerPersonaId \u81F3\u5C11\u4F20\u4E00\u4E2A"
|
|
22378
22378
|
});
|
|
22379
|
+
var SessionImportExternalArgs = external_exports.object({
|
|
22380
|
+
cwd: external_exports.string().min(1),
|
|
22381
|
+
tool: external_exports.string().min(1),
|
|
22382
|
+
toolSessionId: external_exports.string().min(1),
|
|
22383
|
+
label: external_exports.string().optional(),
|
|
22384
|
+
iconKey: external_exports.string().optional(),
|
|
22385
|
+
createdAt: external_exports.string().datetime(),
|
|
22386
|
+
updatedAt: external_exports.string().datetime(),
|
|
22387
|
+
ownerPersonaId: external_exports.string().min(1).optional()
|
|
22388
|
+
});
|
|
22379
22389
|
var SessionListArgs = external_exports.object({
|
|
22380
22390
|
/** true = 只返回 mirror(originOwnerPrincipalId 非空,即「我在朋友 daemon 上创建」的本机存证)。 */
|
|
22381
22391
|
originOnly: external_exports.boolean().optional(),
|
|
@@ -22915,6 +22925,10 @@ var METHOD_DOCS = {
|
|
|
22915
22925
|
anyOf: [{ required: ["cwd"] }, { required: ["ownerPersonaId"] }]
|
|
22916
22926
|
}
|
|
22917
22927
|
},
|
|
22928
|
+
"session:importExternal": {
|
|
22929
|
+
summary: "\u628A Claude Code / Codex \u65E2\u6709\u5386\u53F2\u4F1A\u8BDD\u5BFC\u5165\u4E3A clawd \u5F15\u7528\uFF1A\u53EA\u5199 SessionFile\uFF0C\u4E0D\u62C9\u8D77 runner\uFF0C\u4E0D\u6539\u539F\u59CB transcript\uFF1B\u6309 toolSessionId \u8DE8 owned scope \u53BB\u91CD\uFF0C\u65B0\u5BFC\u5165\u4F1A\u5E7F\u64AD session:info",
|
|
22930
|
+
args: SessionImportExternalArgs
|
|
22931
|
+
},
|
|
22918
22932
|
"session:list": {
|
|
22919
22933
|
summary: "\u5217\u51FA\u5F53\u524D\u8EAB\u4EFD\u53EF\u89C1\u7684\u4F1A\u8BDD\uFF08owner \u5168\u91CF\uFF1Bguest \u9650\u88AB\u6388\u6743 persona\uFF09",
|
|
22920
22934
|
args: SessionListArgs
|
package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/SKILL.md
CHANGED
|
@@ -39,6 +39,7 @@ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --days 7 --ap
|
|
|
39
39
|
| `--min-turns N` | 少于 N 轮用户发言的会话跳过,默认 2(滤掉「hello」这种试水会话) |
|
|
40
40
|
| `--include-clawd` | 连 `~/.clawd` 目录下的会话也关联。默认跳过——那些本来就是 clawd 自己开的 |
|
|
41
41
|
| `--icon KEY` | 侧边栏图标:`research/code/loop/qa/reading/debug/idea/doc/assist`,默认 `reading` |
|
|
42
|
+
| `--owner-persona-id ID` | 显式挂到某个 persona。默认不传,导入普通 session,进入 Recent |
|
|
42
43
|
| `--json` | 输出 JSON,含被跳过的条目和原因,便于你自己筛 |
|
|
43
44
|
|
|
44
45
|
撤销(删掉本工具建过的全部引用,原会话文件不受影响):
|
|
@@ -48,19 +49,27 @@ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --undo
|
|
|
48
49
|
node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --undo --apply # 真删
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
##
|
|
52
|
+
## 原理
|
|
52
53
|
|
|
53
54
|
一条 clawd 会话的元数据里有 `cwd` + `toolSessionId` 两个字段,指向底层工具那条真实
|
|
54
|
-
|
|
55
|
+
对话。关联就是调用 daemon 的 `session:importExternal` RPC 建一条引用:
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
- daemon 只创建 clawd 引用,**不拉起任何进程**
|
|
58
|
+
- daemon 不复制、不移动、不修改 Claude Code / Codex 的原始 transcript
|
|
59
|
+
- daemon 按 `toolSessionId` 跨 default/persona scope 去重,已导入过就返回既有 session
|
|
60
|
+
- daemon 用原会话 `createdAt` / `updatedAt` 写入 metadata,并广播 `session:info`,已打开的
|
|
61
|
+
desktop/mobile sidepanel 会收到通知刷新列表
|
|
60
62
|
|
|
61
63
|
用户点开时,clawd 按 `(cwd, toolSessionId)` 去读原始 transcript 渲染;发消息才真正
|
|
62
64
|
把 Claude Code / Codex 进程拉起来 resume 那条对话。
|
|
63
65
|
|
|
66
|
+
归属规则:
|
|
67
|
+
|
|
68
|
+
- 默认不传 `ownerPersonaId`:导入普通 session,不挂任何 persona,出现在 Recent
|
|
69
|
+
- 显式传 `--owner-persona-id <personaId>`:导入该 persona 的 owner session,出现在对应
|
|
70
|
+
People/persona 分组
|
|
71
|
+
- 外部 cwd(例如 `~/Desktop` 项目)不会因为执行导入的是 butler 就自动挂到 butler
|
|
72
|
+
|
|
64
73
|
两边的会话数据在哪:
|
|
65
74
|
|
|
66
75
|
- Claude Code:`~/.claude/projects/<编码后的 cwd>/<会话 id>.jsonl`,文件名就是会话 id
|
|
@@ -99,7 +108,7 @@ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --undo --appl
|
|
|
99
108
|
## 注意
|
|
100
109
|
|
|
101
110
|
- clawd 桌面端得在跑,否则脚本连不上(会明确报错)
|
|
102
|
-
-
|
|
111
|
+
- 如果脚本提示 daemon 没有 `session:importExternal`,说明当前 clawd 还没升级/重启到支持后台导入的版本;脚本不会退回旧的直接改 JSON 路径
|
|
103
112
|
- Codex 会话在 clawd 里不支持回滚 / fork / 实时接管(这是 codex 侧的能力边界,
|
|
104
113
|
不是关联的问题);能看能续聊
|
|
105
114
|
- 会话很多时一次全灌会把侧边栏冲爆,建议配 `--limit` 分批,或先用 `--days 3` 试水
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// 把本机 Claude Code / Codex 的历史会话关联成 clawd session。
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// - session:
|
|
6
|
-
// -
|
|
7
|
-
// - SessionStore.list() 每次都从磁盘重读 → 事后回填 createdAt/updatedAt 即时生效
|
|
4
|
+
// 原理:
|
|
5
|
+
// - session:importExternal 只写一份 clawd SessionFile 引用,不拉起任何进程
|
|
6
|
+
// - daemon 负责去重、写入正确 scope,并广播 session:info 通知已打开的侧边栏刷新
|
|
8
7
|
// 关联完,会话出现在 clawd sidebar;点开时由 history reader 按 (cwd, toolSessionId)
|
|
9
8
|
// 渲染原始 transcript;用户发消息才真正 resume 起 CC / codex 进程接着聊。
|
|
10
9
|
//
|
|
@@ -427,62 +426,33 @@ function isDir(p) {
|
|
|
427
426
|
}
|
|
428
427
|
}
|
|
429
428
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
// 每关联一条会话调一次,--limit 50 就是 50 次全树遍历。
|
|
433
|
-
function sessionRoots() {
|
|
434
|
-
const roots = [path.join(CLAWD_DIR, 'sessions')]
|
|
435
|
-
let personas = []
|
|
429
|
+
async function link(cand, icon, ownerPersonaId = '') {
|
|
430
|
+
let imported
|
|
436
431
|
try {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function findSessionFile(sessionId) {
|
|
448
|
-
const want = `${sessionId}.json`
|
|
449
|
-
for (const root of sessionRoots()) {
|
|
450
|
-
const hit = listFiles(root).find((f) => path.basename(f) === want)
|
|
451
|
-
if (hit) return hit
|
|
452
|
-
}
|
|
453
|
-
return null
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// 把 SessionFile 的时间戳改回原始会话的,sidebar 才按真实先后排
|
|
457
|
-
function backfillTimestamps(sessionId, cand) {
|
|
458
|
-
const file = findSessionFile(sessionId)
|
|
459
|
-
if (!file) return '会话文件没找到,时间戳未回填'
|
|
460
|
-
try {
|
|
461
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
462
|
-
data.createdAt = isoOf(cand.createdMs)
|
|
463
|
-
data.updatedAt = isoOf(cand.updatedMs)
|
|
464
|
-
const tmp = `${file}.tmp-import`
|
|
465
|
-
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 })
|
|
466
|
-
fs.renameSync(tmp, file)
|
|
467
|
-
return ''
|
|
432
|
+
imported = await rpc('session:importExternal', {
|
|
433
|
+
cwd: cand.cwd,
|
|
434
|
+
tool: cand.tool,
|
|
435
|
+
toolSessionId: cand.toolSessionId,
|
|
436
|
+
label: cand.label || `${cand.tool} 会话`,
|
|
437
|
+
iconKey: icon,
|
|
438
|
+
createdAt: isoOf(cand.createdMs),
|
|
439
|
+
updatedAt: isoOf(cand.updatedMs),
|
|
440
|
+
...(ownerPersonaId ? { ownerPersonaId } : {}),
|
|
441
|
+
})
|
|
468
442
|
} catch (err) {
|
|
469
|
-
|
|
443
|
+
if (err instanceof RpcError && /session:importExternal|METHOD_NOT_ALLOWED|METHOD_NOT_IMPLEMENTED|unknown method|not implemented/.test(err.message)) {
|
|
444
|
+
throw new RpcError(
|
|
445
|
+
'当前 clawd daemon 还没有 session:importExternal RPC。请升级/重启 clawd 后再导入;脚本不会退回旧的直接改 JSON 路径。',
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
throw err
|
|
470
449
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
label: cand.label || `${cand.tool} 会话`,
|
|
478
|
-
iconKey: icon,
|
|
479
|
-
})
|
|
480
|
-
const sessionId = created.sessionId
|
|
481
|
-
if (!sessionId) throw new RpcError(`session:create 没返回 sessionId:${clip(JSON.stringify(created), 150)}`)
|
|
482
|
-
await rpc('session:resume', { sessionId, toolSessionId: cand.toolSessionId })
|
|
483
|
-
const warn = backfillTimestamps(sessionId, cand)
|
|
484
|
-
cand.result = sessionId + (warn ? `(${warn})` : '')
|
|
485
|
-
return sessionId
|
|
450
|
+
const sessionId = imported.sessionId
|
|
451
|
+
if (!sessionId) {
|
|
452
|
+
throw new RpcError(`session:importExternal 没返回 sessionId:${clip(JSON.stringify(imported), 150)}`)
|
|
453
|
+
}
|
|
454
|
+
cand.result = sessionId + (imported.alreadyLinked ? '(已存在)' : '')
|
|
455
|
+
return { sessionId, linked: !imported.alreadyLinked }
|
|
486
456
|
}
|
|
487
457
|
|
|
488
458
|
// ---------------------------------------------------------------- 台账 / 撤销
|
|
@@ -542,7 +512,7 @@ const ICON_KEYS = ['research', 'code', 'loop', 'qa', 'reading', 'debug', 'idea',
|
|
|
542
512
|
function parseArgs(argv) {
|
|
543
513
|
const a = {
|
|
544
514
|
days: 7, tool: 'both', apply: false, limit: 0, minTurns: 2,
|
|
545
|
-
includeClawd: false, icon: 'reading', undo: false, json: false,
|
|
515
|
+
includeClawd: false, icon: 'reading', ownerPersonaId: '', undo: false, json: false,
|
|
546
516
|
}
|
|
547
517
|
for (let i = 0; i < argv.length; i++) {
|
|
548
518
|
const k = argv[i]
|
|
@@ -557,6 +527,7 @@ function parseArgs(argv) {
|
|
|
557
527
|
case '--limit': a.limit = Number(next()); break
|
|
558
528
|
case '--min-turns': a.minTurns = Number(next()); break
|
|
559
529
|
case '--icon': a.icon = next(); break
|
|
530
|
+
case '--owner-persona-id': a.ownerPersonaId = next(); break
|
|
560
531
|
case '--apply': a.apply = true; break
|
|
561
532
|
case '--include-clawd': a.includeClawd = true; break
|
|
562
533
|
case '--undo': a.undo = true; break
|
|
@@ -585,6 +556,8 @@ const HELP = `把本机 Claude Code / Codex 历史会话关联进 clawd
|
|
|
585
556
|
--min-turns N 少于这么多轮用户发言的会话跳过(默认 2)
|
|
586
557
|
--include-clawd 连 ~/.clawd 目录下的会话也关联(默认跳过)
|
|
587
558
|
--icon KEY research/code/loop/qa/reading/debug/idea/doc/assist(默认 reading)
|
|
559
|
+
--owner-persona-id ID
|
|
560
|
+
显式挂到某个 persona;不传则导入为普通 session,进入 Recent
|
|
588
561
|
--undo 撤销本工具关联过的会话(配 --apply 才真删)
|
|
589
562
|
--json 输出 JSON(含被跳过的和原因)`
|
|
590
563
|
|
|
@@ -615,13 +588,21 @@ async function main() {
|
|
|
615
588
|
if (args.apply) {
|
|
616
589
|
for (const c of todo) {
|
|
617
590
|
try {
|
|
618
|
-
const
|
|
591
|
+
const linked = await link(c, args.icon, args.ownerPersonaId)
|
|
619
592
|
// 每成功一条就落台账:攒到循环结束再写的话,中途崩了 --undo 就清不掉已建的会话
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
593
|
+
if (linked.linked) {
|
|
594
|
+
ledgerWrite(
|
|
595
|
+
ledgerRead().concat([
|
|
596
|
+
{
|
|
597
|
+
sessionId: linked.sessionId,
|
|
598
|
+
tool: c.tool,
|
|
599
|
+
label: c.label,
|
|
600
|
+
toolSessionId: c.toolSessionId,
|
|
601
|
+
...(args.ownerPersonaId ? { ownerPersonaId: args.ownerPersonaId } : {}),
|
|
602
|
+
},
|
|
603
|
+
]),
|
|
604
|
+
)
|
|
605
|
+
}
|
|
625
606
|
} catch (err) {
|
|
626
607
|
c.result = `失败:${err.message}`
|
|
627
608
|
}
|