@rexvane/piweb 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +8 -7
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/react-loadable-manifest.json +2 -2
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/git/route.js +2 -2
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route.js +1 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route.js +6 -0
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/import/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/web-auth/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -7
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/532.js +1 -0
- package/.next/server/chunks/686.js +2 -2
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/LOFl9bqqMV2goBV8erESO/_buildManifest.js +1 -0
- package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
- package/.next/static/chunks/4686-7d694def920ff093.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/page-9a5440047c5a415c.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/{webpack-05a9e85529a18b93.js → webpack-d28e132e1b8c100c.js} +1 -1
- package/.next/types/app/api/sessions/import/route.ts +353 -0
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/README.md +6 -2
- package/package.json +1 -1
- package/src/app/api/sessions/import/route.ts +74 -0
- package/src/components/PromptPanel.tsx +33 -31
- package/src/components/SessionImportSection.tsx +370 -0
- package/src/components/SettingsPanel.tsx +4 -1
- package/src/i18n.tsx +68 -6
- package/src/lib/prompt-sources.ts +29 -1
- package/src/lib/session-import/claude.ts +229 -0
- package/src/lib/session-import/codex.ts +223 -0
- package/src/lib/session-import/dsh-zstd.ts +69 -0
- package/src/lib/session-import/dsh.ts +259 -0
- package/src/lib/session-import/files.ts +84 -0
- package/src/lib/session-import/grok.ts +169 -0
- package/src/lib/session-import/index.ts +229 -0
- package/src/lib/session-import/oc-family.ts +275 -0
- package/src/lib/session-import/opencode.ts +7 -0
- package/src/lib/session-import/paths.ts +37 -0
- package/src/lib/session-import/sqlite.ts +89 -0
- package/src/lib/session-import/types.ts +171 -0
- package/src/lib/session-import/writer.ts +259 -0
- package/src/lib/session-import/zcode.ts +8 -0
- package/src/lib/skills-service.ts +51 -14
- package/.next/server/chunks/118.js +0 -1
- package/.next/static/2kt2UiWjv_viXpNlfjvBT/_buildManifest.js +0 -1
- package/.next/static/chunks/4127.dff5c3b33c6bfd1b.js +0 -1
- package/.next/static/chunks/4686-5bbee099126af797.js +0 -1
- package/.next/static/chunks/app/_global-error/page-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/config/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/files/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/git/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/growth/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/health/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/models/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/security/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/skills/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/update/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/version/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-961c055baebb3872.js +0 -1
- package/.next/static/chunks/app/page-00c8ebf40ec5eaca.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-961c055baebb3872.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-961c055baebb3872.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-961c055baebb3872.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-961c055baebb3872.js +0 -1
- /package/.next/static/{2kt2UiWjv_viXpNlfjvBT → LOFl9bqqMV2goBV8erESO}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 设置 → 导入会话:把本机其它 AI 编程工具的对话无损导成 pi 会话。
|
|
5
|
+
*
|
|
6
|
+
* 交互上刻意保守:**默认一条都不勾**(本机 ZCode 421 条、opencode 860 条,一键全选就是事故),
|
|
7
|
+
* 已导过的行标出来并且不能再勾。写入去向由服务端决定(源项目还在就导回原工作区),
|
|
8
|
+
* 这里只负责在源项目已不在本机时给一个兜底工作区——从已知工作区里选,不允许手输路径。
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
11
|
+
import { useI18n } from "@/i18n";
|
|
12
|
+
import { IconRefreshOutline14, IconDownloadOutline16 } from "@/components/icons";
|
|
13
|
+
|
|
14
|
+
interface Summary {
|
|
15
|
+
source: string;
|
|
16
|
+
externalId: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
projectPath?: string;
|
|
19
|
+
provider?: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
createdAt?: number;
|
|
22
|
+
updatedAt?: number;
|
|
23
|
+
messageCount?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SourceError {
|
|
27
|
+
source: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ImportOutcome {
|
|
32
|
+
source: string;
|
|
33
|
+
externalId: string;
|
|
34
|
+
title?: string;
|
|
35
|
+
status: "imported" | "skipped" | "failed";
|
|
36
|
+
reason?: string;
|
|
37
|
+
messageCount?: number;
|
|
38
|
+
workspace?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ImportReport {
|
|
42
|
+
imported: number;
|
|
43
|
+
skipped: number;
|
|
44
|
+
failed: number;
|
|
45
|
+
results: ImportOutcome[];
|
|
46
|
+
workspaces: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 来源中文名/英文名放在 i18n 里,这里只保留稳定的顺序与徽标色 */
|
|
50
|
+
const SOURCE_ORDER = ["claude", "codex", "grok", "zcode", "dsh", "opencode"] as const;
|
|
51
|
+
|
|
52
|
+
/** 与 src/lib/session-import/index.ts 的 SCAN_LIMIT_PER_SOURCE 保持一致(仅用于提示文案) */
|
|
53
|
+
const SCAN_LIMIT = 15;
|
|
54
|
+
|
|
55
|
+
function dayOf(ms?: number): string {
|
|
56
|
+
if (!ms) return "";
|
|
57
|
+
const d = new Date(ms);
|
|
58
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
59
|
+
const pad = (value: number) => String(value).padStart(2, "0");
|
|
60
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
64
|
+
const { t } = useI18n();
|
|
65
|
+
const [summaries, setSummaries] = useState<Summary[]>([]);
|
|
66
|
+
const [errors, setErrors] = useState<SourceError[]>([]);
|
|
67
|
+
const [importedKeys, setImportedKeys] = useState<Set<string>>(new Set());
|
|
68
|
+
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
69
|
+
const [scanning, setScanning] = useState(false);
|
|
70
|
+
const [importing, setImporting] = useState(false);
|
|
71
|
+
const [report, setReport] = useState<ImportReport | null>(null);
|
|
72
|
+
const [failure, setFailure] = useState<string | null>(null);
|
|
73
|
+
const [workspaces, setWorkspaces] = useState<string[]>([]);
|
|
74
|
+
const [fallback, setFallback] = useState(cwd);
|
|
75
|
+
|
|
76
|
+
const scan = useCallback(async () => {
|
|
77
|
+
setScanning(true);
|
|
78
|
+
setFailure(null);
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch("/api/sessions/import");
|
|
81
|
+
const json = await response.json();
|
|
82
|
+
if (!json.success) {
|
|
83
|
+
setFailure(json.error || t.toastError);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setSummaries(json.data.sessions ?? []);
|
|
87
|
+
setErrors(json.data.errors ?? []);
|
|
88
|
+
setImportedKeys(new Set<string>(json.data.imported ?? []));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
setFailure(error instanceof Error ? error.message : String(error));
|
|
91
|
+
} finally {
|
|
92
|
+
setScanning(false);
|
|
93
|
+
}
|
|
94
|
+
}, [t]);
|
|
95
|
+
|
|
96
|
+
// 兜底工作区候选:已知工作区列表(只选不填),默认当前打开的工作区
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
void (async () => {
|
|
99
|
+
try {
|
|
100
|
+
const json = await (await fetch("/api/workspaces")).json();
|
|
101
|
+
if (json.success) setWorkspaces(json.data.workspaces ?? []);
|
|
102
|
+
} catch {
|
|
103
|
+
// 拿不到候选就把当前工作区当唯一条目,不影响导入
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
void scan();
|
|
110
|
+
}, [scan]);
|
|
111
|
+
|
|
112
|
+
const key = (summary: Summary) => `${summary.source}:${summary.externalId}`;
|
|
113
|
+
|
|
114
|
+
const groups = useMemo(() => {
|
|
115
|
+
const bySource = new Map<string, Summary[]>();
|
|
116
|
+
for (const summary of summaries) {
|
|
117
|
+
const list = bySource.get(summary.source);
|
|
118
|
+
if (list) list.push(summary);
|
|
119
|
+
else bySource.set(summary.source, [summary]);
|
|
120
|
+
}
|
|
121
|
+
return SOURCE_ORDER.filter((source) => bySource.has(source)).map((source) => {
|
|
122
|
+
const rows = [...(bySource.get(source) as Summary[])].sort((left, right) => (right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0));
|
|
123
|
+
return { source, rows, imported: rows.filter((row) => importedKeys.has(key(row))).length };
|
|
124
|
+
});
|
|
125
|
+
}, [summaries, importedKeys]);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 一次只显示一个来源:六个工具各列 15 条就是 90 行,全铺出来既难找也没必要。
|
|
129
|
+
* 默认落在第一个有会话的来源上(SOURCE_ORDER 的顺序),用户切换后不被扫描结果重置。
|
|
130
|
+
*/
|
|
131
|
+
const [activeSource, setActiveSource] = useState("");
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (activeSource && groups.some((group) => group.source === activeSource)) return;
|
|
134
|
+
setActiveSource(groups[0]?.source ?? "");
|
|
135
|
+
}, [groups, activeSource]);
|
|
136
|
+
const activeGroup = groups.find((group) => group.source === activeSource);
|
|
137
|
+
|
|
138
|
+
const toggle = (summary: Summary) => {
|
|
139
|
+
if (importedKeys.has(key(summary))) return;
|
|
140
|
+
setSelected((previous) => {
|
|
141
|
+
const next = new Set(previous);
|
|
142
|
+
if (next.has(key(summary))) next.delete(key(summary));
|
|
143
|
+
else next.add(key(summary));
|
|
144
|
+
return next;
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const selectGroup = (rows: Summary[], checked: boolean) => {
|
|
149
|
+
setSelected((previous) => {
|
|
150
|
+
const next = new Set(previous);
|
|
151
|
+
for (const row of rows) {
|
|
152
|
+
if (importedKeys.has(key(row))) continue;
|
|
153
|
+
if (checked) next.add(key(row));
|
|
154
|
+
else next.delete(key(row));
|
|
155
|
+
}
|
|
156
|
+
return next;
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const run = async () => {
|
|
161
|
+
if (!selected.size) return;
|
|
162
|
+
const items = summaries.filter((summary) => selected.has(key(summary))).map((summary) => ({ source: summary.source, externalId: summary.externalId }));
|
|
163
|
+
setImporting(true);
|
|
164
|
+
setFailure(null);
|
|
165
|
+
setReport(null);
|
|
166
|
+
try {
|
|
167
|
+
const response = await fetch("/api/sessions/import", {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: { "Content-Type": "application/json" },
|
|
170
|
+
body: JSON.stringify({ sessions: items, fallbackCwd: fallback || undefined }),
|
|
171
|
+
});
|
|
172
|
+
const json = await response.json();
|
|
173
|
+
if (!json.success) {
|
|
174
|
+
setFailure(json.error || t.toastError);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
setReport(json.data as ImportReport);
|
|
178
|
+
setSelected(new Set());
|
|
179
|
+
await scan(); // 重新扫描:已经导过的行马上变成"已导入"
|
|
180
|
+
} catch (error) {
|
|
181
|
+
setFailure(error instanceof Error ? error.message : String(error));
|
|
182
|
+
} finally {
|
|
183
|
+
setImporting(false);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const sourceLabel = (source: string) => {
|
|
188
|
+
const labels: Record<string, string> = {
|
|
189
|
+
claude: t.importSourceClaude,
|
|
190
|
+
codex: t.importSourceCodex,
|
|
191
|
+
grok: t.importSourceGrok,
|
|
192
|
+
zcode: t.importSourceZcode,
|
|
193
|
+
dsh: t.importSourceDsh,
|
|
194
|
+
opencode: t.importSourceOpencode,
|
|
195
|
+
};
|
|
196
|
+
return labels[source] ?? source;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const failures = report?.results.filter((result) => result.status === "failed") ?? [];
|
|
200
|
+
const skipped = report?.results.filter((result) => result.status === "skipped") ?? [];
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div className="flex flex-col gap-4" data-testid="session-import">
|
|
204
|
+
<div style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.importDesc}</div>
|
|
205
|
+
<div style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }} title={t.importLimitHintTooltip} data-testid="import-limit-hint">
|
|
206
|
+
{t.importLimitHint.replace("{n}", String(SCAN_LIMIT))}
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* 工具条:扫描 / 选择 / 兜底工作区 / 导入 */}
|
|
210
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
211
|
+
<button
|
|
212
|
+
className="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5"
|
|
213
|
+
style={{ fontSize: 12, border: "0.5px solid var(--dsw-border-l2)" }}
|
|
214
|
+
onClick={() => void scan()}
|
|
215
|
+
disabled={scanning}
|
|
216
|
+
data-testid="import-rescan"
|
|
217
|
+
>
|
|
218
|
+
<IconRefreshOutline14 size={13} /> {scanning ? t.importScanning : t.importRescan}
|
|
219
|
+
</button>
|
|
220
|
+
<span style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.importFallbackWorkspace}</span>
|
|
221
|
+
<select
|
|
222
|
+
className="rounded-lg px-2 py-1"
|
|
223
|
+
style={{ fontSize: 12, border: "0.5px solid var(--dsw-border-l2)", background: "transparent", maxWidth: 260 }}
|
|
224
|
+
value={fallback}
|
|
225
|
+
onChange={(event) => setFallback(event.target.value)}
|
|
226
|
+
aria-label={t.importFallbackWorkspace}
|
|
227
|
+
data-testid="import-fallback"
|
|
228
|
+
>
|
|
229
|
+
{(workspaces.includes(fallback) || !fallback ? workspaces : [fallback, ...workspaces]).map((workspace) => (
|
|
230
|
+
<option key={workspace} value={workspace}>
|
|
231
|
+
{workspace}
|
|
232
|
+
</option>
|
|
233
|
+
))}
|
|
234
|
+
</select>
|
|
235
|
+
<div className="flex-1" />
|
|
236
|
+
<button
|
|
237
|
+
className="rounded-lg px-2.5 py-1.5"
|
|
238
|
+
style={{ fontSize: 12, color: "var(--dsw-label-tertiary)" }}
|
|
239
|
+
onClick={() => setSelected(new Set())}
|
|
240
|
+
disabled={!selected.size}
|
|
241
|
+
>
|
|
242
|
+
{t.importClearSelection}
|
|
243
|
+
</button>
|
|
244
|
+
<button
|
|
245
|
+
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5"
|
|
246
|
+
style={{ fontSize: 12, background: selected.size && !importing ? "var(--dsw-accent)" : "var(--dsw-hover)", color: selected.size && !importing ? "#fff" : "var(--dsw-label-caption)" }}
|
|
247
|
+
onClick={() => void run()}
|
|
248
|
+
disabled={!selected.size || importing}
|
|
249
|
+
data-testid="import-run"
|
|
250
|
+
>
|
|
251
|
+
<IconDownloadOutline16 size={13} /> {importing ? t.importRunning : t.importRun.replace("{n}", String(selected.size))}
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{failure && (
|
|
256
|
+
<div role="alert" className="rounded-lg px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", color: "var(--dsw-danger)" }}>
|
|
257
|
+
{failure}
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{errors.map((error) => (
|
|
262
|
+
<div key={error.source} className="rounded-lg px-3 py-2" style={{ fontSize: 12, background: "var(--dsw-hover)", color: "var(--dsw-label-tertiary)" }}>
|
|
263
|
+
{t.importSourceError.replace("{source}", sourceLabel(error.source)).replace("{message}", error.message)}
|
|
264
|
+
</div>
|
|
265
|
+
))}
|
|
266
|
+
|
|
267
|
+
{report && (
|
|
268
|
+
<div className="rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }} data-testid="import-report">
|
|
269
|
+
<div style={{ fontSize: 13 }}>
|
|
270
|
+
{t.importReportSummary
|
|
271
|
+
.replace("{imported}", String(report.imported))
|
|
272
|
+
.replace("{skipped}", String(report.skipped))
|
|
273
|
+
.replace("{failed}", String(report.failed))}
|
|
274
|
+
</div>
|
|
275
|
+
{skipped.map((result) => (
|
|
276
|
+
<div key={`skip-${result.source}-${result.externalId}`} className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
277
|
+
{sourceLabel(result.source)} · {result.title || result.externalId} — {result.reason}
|
|
278
|
+
</div>
|
|
279
|
+
))}
|
|
280
|
+
{failures.map((result) => (
|
|
281
|
+
<div key={`fail-${result.source}-${result.externalId}`} className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-danger)" }}>
|
|
282
|
+
{sourceLabel(result.source)} · {result.title || result.externalId} — {result.reason}
|
|
283
|
+
</div>
|
|
284
|
+
))}
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
|
|
288
|
+
{!scanning && !summaries.length && !errors.length && <div style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}>{t.importEmpty}</div>}
|
|
289
|
+
|
|
290
|
+
{/* 来源选择器:选中哪个就只列哪个工具的会话 */}
|
|
291
|
+
{groups.length > 0 && (
|
|
292
|
+
<div className="flex flex-wrap items-center gap-1.5" role="tablist" aria-label={t.importPickSource} data-testid="import-source-tabs">
|
|
293
|
+
{groups.map((group) => {
|
|
294
|
+
const active = group.source === activeSource;
|
|
295
|
+
return (
|
|
296
|
+
<button
|
|
297
|
+
key={group.source}
|
|
298
|
+
type="button"
|
|
299
|
+
role="tab"
|
|
300
|
+
aria-selected={active}
|
|
301
|
+
className="rounded-full px-3 py-1 transition-colors"
|
|
302
|
+
style={{
|
|
303
|
+
fontSize: 12,
|
|
304
|
+
border: `0.5px solid ${active ? "var(--dsw-accent)" : "var(--dsw-border-l2)"}`,
|
|
305
|
+
background: active ? "var(--dsw-accent)" : "transparent",
|
|
306
|
+
color: active ? "#fff" : "var(--dsw-label-tertiary)",
|
|
307
|
+
}}
|
|
308
|
+
onClick={() => setActiveSource(group.source)}
|
|
309
|
+
data-testid={`import-source-${group.source}`}
|
|
310
|
+
>
|
|
311
|
+
{sourceLabel(group.source)}
|
|
312
|
+
<span style={{ marginLeft: 6, opacity: 0.75 }}>{group.rows.length}</span>
|
|
313
|
+
</button>
|
|
314
|
+
);
|
|
315
|
+
})}
|
|
316
|
+
</div>
|
|
317
|
+
)}
|
|
318
|
+
|
|
319
|
+
{activeGroup && (
|
|
320
|
+
<div className="flex flex-col gap-2" data-testid={`import-group-${activeGroup.source}`}>
|
|
321
|
+
<div className="flex items-center gap-2">
|
|
322
|
+
<span style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
323
|
+
{t.importGroupCount.replace("{n}", String(activeGroup.rows.length)).replace("{imported}", String(activeGroup.imported))}
|
|
324
|
+
</span>
|
|
325
|
+
<div className="flex-1" />
|
|
326
|
+
<button className="rounded-lg px-2 py-0.5" style={{ fontSize: 11.5, color: "var(--dsw-label-tertiary)" }} onClick={() => selectGroup(activeGroup.rows, true)}>
|
|
327
|
+
{t.importSelectAll}
|
|
328
|
+
</button>
|
|
329
|
+
<button className="rounded-lg px-2 py-0.5" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }} onClick={() => selectGroup(activeGroup.rows, false)}>
|
|
330
|
+
{t.importSelectNone}
|
|
331
|
+
</button>
|
|
332
|
+
</div>
|
|
333
|
+
<div className="flex flex-col gap-1.5">
|
|
334
|
+
{activeGroup.rows.map((row) => {
|
|
335
|
+
const rowKey = key(row);
|
|
336
|
+
const done = importedKeys.has(rowKey);
|
|
337
|
+
const checked = selected.has(rowKey);
|
|
338
|
+
return (
|
|
339
|
+
<label
|
|
340
|
+
key={rowKey}
|
|
341
|
+
className="flex cursor-pointer items-center gap-3 rounded-xl px-3 py-2"
|
|
342
|
+
style={{ border: `0.5px solid ${checked ? "var(--dsw-accent)" : "var(--dsw-border-l2)"}`, opacity: done ? 0.55 : 1 }}
|
|
343
|
+
>
|
|
344
|
+
<input type="checkbox" checked={checked} disabled={done} onChange={() => toggle(row)} />
|
|
345
|
+
<div className="min-w-0 flex-1">
|
|
346
|
+
<div className="truncate" style={{ fontSize: 12.5 }} title={row.title}>
|
|
347
|
+
{row.title || t.importUntitled}
|
|
348
|
+
</div>
|
|
349
|
+
<div className="truncate" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }} title={row.projectPath}>
|
|
350
|
+
{row.projectPath || t.importNoProject}
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
{done && (
|
|
354
|
+
<span className="rounded-full px-2 py-0.5" style={{ fontSize: 10, background: "var(--dsw-hover)", color: "var(--dsw-label-caption)" }}>
|
|
355
|
+
{t.importAlready}
|
|
356
|
+
</span>
|
|
357
|
+
)}
|
|
358
|
+
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>
|
|
359
|
+
{typeof row.messageCount === "number" ? t.importMessages.replace("{n}", String(row.messageCount)) : "—"}
|
|
360
|
+
</span>
|
|
361
|
+
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>{dayOf(row.updatedAt ?? row.createdAt)}</span>
|
|
362
|
+
</label>
|
|
363
|
+
);
|
|
364
|
+
})}
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type BuiltinProviderSetup,
|
|
32
32
|
type CustomProviderSetup,
|
|
33
33
|
} from "@/components/ProviderSetupModal";
|
|
34
|
+
import { SessionImportSection } from "@/components/SessionImportSection";
|
|
34
35
|
import { useI18n } from "@/i18n";
|
|
35
36
|
import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
|
|
36
37
|
import { modelDraftFromConfig, serializeProviderDraft, validateModelDrafts, type ModelDraft } from "@/lib/model-draft";
|
|
@@ -39,7 +40,7 @@ import { quotaWindowKind, supportsUsageProbe, type BalanceAmount, type BalanceDe
|
|
|
39
40
|
import type { ProviderView } from "@/lib/models-service";
|
|
40
41
|
import type { ToolPreset } from "@/lib/types";
|
|
41
42
|
|
|
42
|
-
type Section = "general" | "models" | "tools" | "skills" | "plugins";
|
|
43
|
+
type Section = "general" | "models" | "tools" | "skills" | "plugins" | "import";
|
|
43
44
|
|
|
44
45
|
export function SettingsPanel({
|
|
45
46
|
open,
|
|
@@ -83,6 +84,7 @@ export function SettingsPanel({
|
|
|
83
84
|
{ id: "tools", label: t.toolsSection, icon: <IconAgentPresetOutline16 size={15} /> },
|
|
84
85
|
{ id: "plugins", label: t.setPlugins, icon: <IconPluginOutline16 size={15} /> },
|
|
85
86
|
{ id: "skills", label: t.setSkills, icon: <IconSkillOutline16 size={15} /> },
|
|
87
|
+
{ id: "import", label: t.importSessions, icon: <IconDownloadOutline16 size={15} /> },
|
|
86
88
|
];
|
|
87
89
|
|
|
88
90
|
return (
|
|
@@ -149,6 +151,7 @@ export function SettingsPanel({
|
|
|
149
151
|
{section === "skills" && <SkillsSection cwd={cwd} onOpenFileContent={onOpenFileContent} />}
|
|
150
152
|
{section === "tools" && <ToolsSection toolPreset={toolPreset} onToolPresetChange={onToolPresetChange} tools={tools} onSetTools={onSetTools} />}
|
|
151
153
|
{section === "plugins" && <PluginsSection cwd={cwd} />}
|
|
154
|
+
{section === "import" && <SessionImportSection cwd={cwd} />}
|
|
152
155
|
</div>
|
|
153
156
|
</div>
|
|
154
157
|
</div>
|
package/src/i18n.tsx
CHANGED
|
@@ -298,11 +298,14 @@ const zh = {
|
|
|
298
298
|
promptKindSkill: "技能提示词",
|
|
299
299
|
promptKindTemplate: "提示模板",
|
|
300
300
|
promptKindTool: "工具提示词",
|
|
301
|
-
promptKindAssembled: "
|
|
302
|
-
|
|
301
|
+
promptKindAssembled: "系统提示词",
|
|
302
|
+
promptKindCompacted: "压缩摘要",
|
|
303
|
+
promptCompactedKey: "进入上下文的压缩摘要",
|
|
304
|
+
promptGuidelines: "提示词指南",
|
|
305
|
+
promptParameters: "参数定义",
|
|
306
|
+
promptAssembledKey: "当前生效的系统提示词",
|
|
303
307
|
promptToolBuiltin: "内置",
|
|
304
308
|
promptToolExtension: "扩展",
|
|
305
|
-
copySuccess: "已复制",
|
|
306
309
|
contextSystemPrompt: "System Prompt",
|
|
307
310
|
contextSystemTools: "System Tools",
|
|
308
311
|
contextCustomTools: "Custom Tools",
|
|
@@ -371,6 +374,34 @@ const zh = {
|
|
|
371
374
|
enterQueue: "排队:本轮结束后发送",
|
|
372
375
|
enterSteer: "插话:当前工具跑完就送达",
|
|
373
376
|
setSkills: "技能",
|
|
377
|
+
// 导入会话(其它 AI 编程工具的本地对话 → pi 会话)
|
|
378
|
+
importSessions: "导入会话",
|
|
379
|
+
importDesc: "把本机 Codex / Claude Code / Grok / ZCode / dsh / opencode 的对话无损导入成 pi 会话:文本、思考、工具调用与结果、时间戳、用量都保留。源项目目录还在本机就导回原工作区,否则落到下面选的兜底工作区。只读源数据,不修改源工具的任何文件。",
|
|
380
|
+
importRescan: "重新扫描",
|
|
381
|
+
importScanning: "扫描中…",
|
|
382
|
+
importSourceClaude: "Claude Code",
|
|
383
|
+
importSourceCodex: "Codex",
|
|
384
|
+
importSourceGrok: "Grok",
|
|
385
|
+
importSourceZcode: "ZCode",
|
|
386
|
+
importSourceDsh: "dsh",
|
|
387
|
+
importSourceOpencode: "opencode",
|
|
388
|
+
importSourceError: "{source} 扫描失败:{message}",
|
|
389
|
+
importGroupCount: "{n} 条 · 已导入 {imported}",
|
|
390
|
+
importPickSource: "选择来源",
|
|
391
|
+
importLimitHint: "每个来源只列最近 {n} 条对话(更早的会话不扫描,省时省资源)",
|
|
392
|
+
importLimitHintTooltip: "看更早的会话:把 src/lib/session-import/index.ts 里的 SCAN_LIMIT_PER_SOURCE 调大",
|
|
393
|
+
importSelectAll: "全选本组",
|
|
394
|
+
importSelectNone: "清空本组",
|
|
395
|
+
importClearSelection: "清空选择",
|
|
396
|
+
importRun: "导入所选 ({n})",
|
|
397
|
+
importRunning: "导入中…",
|
|
398
|
+
importFallbackWorkspace: "兜底工作区",
|
|
399
|
+
importAlready: "已导入",
|
|
400
|
+
importUntitled: "(无标题)",
|
|
401
|
+
importNoProject: "(无项目目录)",
|
|
402
|
+
importMessages: "{n} 条消息",
|
|
403
|
+
importEmpty: "没有找到可导入的本地会话",
|
|
404
|
+
importReportSummary: "成功 {imported} · 跳过 {skipped} · 失败 {failed}",
|
|
374
405
|
setPlugins: "插件",
|
|
375
406
|
appearance: "外观",
|
|
376
407
|
appearanceMode: "亮暗模式",
|
|
@@ -870,11 +901,14 @@ const en: Dict = {
|
|
|
870
901
|
promptKindSkill: "Skill prompts",
|
|
871
902
|
promptKindTemplate: "Prompt templates",
|
|
872
903
|
promptKindTool: "Tool prompts",
|
|
873
|
-
promptKindAssembled: "
|
|
874
|
-
|
|
904
|
+
promptKindAssembled: "System prompt",
|
|
905
|
+
promptKindCompacted: "Compaction summary",
|
|
906
|
+
promptCompactedKey: "Compaction summary in context",
|
|
907
|
+
promptGuidelines: "Prompt guidelines",
|
|
908
|
+
promptParameters: "Parameter schema",
|
|
909
|
+
promptAssembledKey: "Effective system prompt",
|
|
875
910
|
promptToolBuiltin: "builtin",
|
|
876
911
|
promptToolExtension: "extension",
|
|
877
|
-
copySuccess: "Copied",
|
|
878
912
|
contextSystemPrompt: "System Prompt",
|
|
879
913
|
contextSystemTools: "System Tools",
|
|
880
914
|
contextCustomTools: "Custom Tools",
|
|
@@ -939,6 +973,34 @@ const en: Dict = {
|
|
|
939
973
|
enterQueue: "Queue: sent after this turn ends",
|
|
940
974
|
enterSteer: "Steer: delivered after the current tool call",
|
|
941
975
|
setSkills: "Skills",
|
|
976
|
+
// Session import (other AI coding tools' local conversations → pi sessions)
|
|
977
|
+
importSessions: "Import sessions",
|
|
978
|
+
importDesc: "Losslessly import conversations from Codex / Claude Code / Grok / ZCode / dsh / opencode on this machine into pi sessions: text, thinking, tool calls and results, timestamps and usage are all kept. If the original project directory still exists locally the session lands in that workspace; otherwise it falls back to the workspace selected below. Source data is only read, never modified.",
|
|
979
|
+
importRescan: "Rescan",
|
|
980
|
+
importScanning: "Scanning…",
|
|
981
|
+
importSourceClaude: "Claude Code",
|
|
982
|
+
importSourceCodex: "Codex",
|
|
983
|
+
importSourceGrok: "Grok",
|
|
984
|
+
importSourceZcode: "ZCode",
|
|
985
|
+
importSourceDsh: "dsh",
|
|
986
|
+
importSourceOpencode: "opencode",
|
|
987
|
+
importSourceError: "{source} scan failed: {message}",
|
|
988
|
+
importGroupCount: "{n} sessions · {imported} imported",
|
|
989
|
+
importPickSource: "Choose a source",
|
|
990
|
+
importLimitHint: "Only the {n} most recent conversations per source are scanned",
|
|
991
|
+
importLimitHintTooltip: "To see older sessions, raise SCAN_LIMIT_PER_SOURCE in src/lib/session-import/index.ts",
|
|
992
|
+
importSelectAll: "Select group",
|
|
993
|
+
importSelectNone: "Clear group",
|
|
994
|
+
importClearSelection: "Clear",
|
|
995
|
+
importRun: "Import selected ({n})",
|
|
996
|
+
importRunning: "Importing…",
|
|
997
|
+
importFallbackWorkspace: "Fallback workspace",
|
|
998
|
+
importAlready: "Imported",
|
|
999
|
+
importUntitled: "(untitled)",
|
|
1000
|
+
importNoProject: "(no project directory)",
|
|
1001
|
+
importMessages: "{n} messages",
|
|
1002
|
+
importEmpty: "No importable local sessions found",
|
|
1003
|
+
importReportSummary: "Imported {imported} · skipped {skipped} · failed {failed}",
|
|
942
1004
|
setPlugins: "Plugins",
|
|
943
1005
|
appearance: "Appearance",
|
|
944
1006
|
appearanceMode: "Light / Dark",
|
|
@@ -45,8 +45,10 @@ export interface PromptToolView {
|
|
|
45
45
|
|
|
46
46
|
export interface PromptSourcesResult {
|
|
47
47
|
sources: PromptSource[];
|
|
48
|
-
/**
|
|
48
|
+
/** 热会话才有:实际发给模型的完整系统提示词(含你的 AGENTS.md、技能段、工具清单与追加段——不做任何过滤) */
|
|
49
49
|
assembledSystemPrompt?: string;
|
|
50
|
+
/** 热会话才有:压缩摘要(compact 后进入上下文的历史摘要,平时在界面上看不见) */
|
|
51
|
+
compactedSummary?: string;
|
|
50
52
|
/** 热会话才有:每个工具的定义 */
|
|
51
53
|
tools?: PromptToolView[];
|
|
52
54
|
/** 冷会话(agent 未启动)时为 false,界面据此说明为什么缺少合成项 */
|
|
@@ -63,6 +65,9 @@ export interface PromptSessionLike {
|
|
|
63
65
|
promptGuidelines?: string[];
|
|
64
66
|
sourceInfo?: { source?: string; origin?: string; scope?: string };
|
|
65
67
|
}>;
|
|
68
|
+
/** 会话条目入口:SDK 里字段名有 sm / sessionManager 两种(压缩摘要从这里取) */
|
|
69
|
+
sm?: { getEntries?: () => unknown[] };
|
|
70
|
+
sessionManager?: { getEntries?: () => unknown[] };
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
interface SourceInfoLike {
|
|
@@ -140,6 +145,26 @@ async function fileSize(filePath: string | undefined): Promise<number | undefine
|
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
147
|
|
|
148
|
+
/** 最近一次压缩的摘要(与上下文计量同一套取法:从会话条目的最后一条 compaction 往前找) */
|
|
149
|
+
export function latestCompactionSummary(session: PromptSessionLike): string | undefined {
|
|
150
|
+
try {
|
|
151
|
+
const entries = sessionEntries(session);
|
|
152
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
153
|
+
const entry = entries[i] as { type?: string; summary?: string };
|
|
154
|
+
if (entry?.type === "compaction" && typeof entry.summary === "string" && entry.summary.trim()) return entry.summary;
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
/* 条目不可用则跳过 */
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** 会话条目入口在 SDK 里有两个字段名(sm / sessionManager),两种都认 */
|
|
163
|
+
function sessionEntries(session: PromptSessionLike): unknown[] {
|
|
164
|
+
const s = session as unknown as { sm?: { getEntries?: () => unknown[] }; sessionManager?: { getEntries?: () => unknown[] } };
|
|
165
|
+
return s.sm?.getEntries?.() ?? s.sessionManager?.getEntries?.() ?? [];
|
|
166
|
+
}
|
|
167
|
+
|
|
143
168
|
/** 列出当前工作区的全部提示词来源(冷会话也能列全文件类来源) */
|
|
144
169
|
export async function listPromptSources({
|
|
145
170
|
cwd,
|
|
@@ -168,6 +193,8 @@ export async function listPromptSources({
|
|
|
168
193
|
|
|
169
194
|
const withSizes = await Promise.all(raw.map(async (entry) => ({ ...entry, bytes: await fileSize(entry.path) })));
|
|
170
195
|
const sources = buildPromptSources(withSizes);
|
|
196
|
+
// 压缩摘要:compact 之后它以摘要形式进入上下文,界面上平时看不到,这里一并列出
|
|
197
|
+
const compacted = session ? latestCompactionSummary(session) : undefined;
|
|
171
198
|
|
|
172
199
|
const tools = session
|
|
173
200
|
? session.getAllTools().map((tool) => ({
|
|
@@ -182,6 +209,7 @@ export async function listPromptSources({
|
|
|
182
209
|
return {
|
|
183
210
|
sources,
|
|
184
211
|
...(session?.systemPrompt ? { assembledSystemPrompt: session.systemPrompt } : {}),
|
|
212
|
+
...(compacted ? { compactedSummary: compacted } : {}),
|
|
185
213
|
...(tools ? { tools } : {}),
|
|
186
214
|
sessionReady: Boolean(session),
|
|
187
215
|
};
|