codex-overleaf-link 1.1.1
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/LICENSE +21 -0
- package/README.md +457 -0
- package/bin/codex-overleaf-link.mjs +223 -0
- package/extension/src/shared/agentTranscript.js +1175 -0
- package/extension/src/shared/auditRecords.js +568 -0
- package/extension/src/shared/compatibility.js +372 -0
- package/extension/src/shared/compileAdapter.js +176 -0
- package/extension/src/shared/governanceRules.js +252 -0
- package/extension/src/shared/i18n.js +565 -0
- package/extension/src/shared/models.js +106 -0
- package/extension/src/shared/otText.js +505 -0
- package/extension/src/shared/projectFiles.js +180 -0
- package/extension/src/shared/reviewing.js +99 -0
- package/extension/src/shared/sensitiveScan.js +116 -0
- package/extension/src/shared/sessionState.js +1084 -0
- package/extension/src/shared/staleGuard.js +150 -0
- package/extension/src/shared/storageDb.js +986 -0
- package/extension/src/shared/storageKeys.js +29 -0
- package/extension/src/shared/storageMigration.js +168 -0
- package/extension/src/shared/summary.js +248 -0
- package/extension/src/shared/undoOperations.js +369 -0
- package/native-host/src/codexArgs.js +43 -0
- package/native-host/src/codexHome.js +538 -0
- package/native-host/src/codexModels.js +247 -0
- package/native-host/src/codexPrompt.js +192 -0
- package/native-host/src/codexPromptAssembly.js +411 -0
- package/native-host/src/codexSessionRunner.js +1247 -0
- package/native-host/src/commandApproval.js +914 -0
- package/native-host/src/debugLog.js +78 -0
- package/native-host/src/diffEngine.js +247 -0
- package/native-host/src/index.js +132 -0
- package/native-host/src/launcher.js +81 -0
- package/native-host/src/localSkills.js +476 -0
- package/native-host/src/manifest.js +226 -0
- package/native-host/src/mirrorSensitiveScan.js +119 -0
- package/native-host/src/mirrorWorkspace.js +1019 -0
- package/native-host/src/nativeDoctor.js +826 -0
- package/native-host/src/nativeEnvironment.js +315 -0
- package/native-host/src/nativeHostPlatform.js +112 -0
- package/native-host/src/nativeMessaging.js +60 -0
- package/native-host/src/nativeQuotas.js +294 -0
- package/native-host/src/nativeResponseBudget.js +194 -0
- package/native-host/src/runtimeInstaller.js +357 -0
- package/native-host/src/taskRunner.js +3 -0
- package/native-host/src/taskRunnerRuntime.js +1083 -0
- package/native-host/src/textPatch.js +287 -0
- package/package.json +40 -0
- package/scripts/codex-json-agent.mjs +269 -0
- package/scripts/install-native-host.mjs +255 -0
- package/scripts/npm-package-files-v1.1.1.txt +52 -0
- package/scripts/uninstall-native-host.mjs +298 -0
- package/scripts/verify-npm-package.mjs +296 -0
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
(function initSessionState(root, factory) {
|
|
2
|
+
if (typeof module === 'object' && module.exports) {
|
|
3
|
+
module.exports = factory();
|
|
4
|
+
} else {
|
|
5
|
+
root.CodexOverleafSessionState = factory();
|
|
6
|
+
}
|
|
7
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window, function sessionStateFactory() {
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PANEL_STATE = {
|
|
11
|
+
mode: 'confirm',
|
|
12
|
+
model: 'gpt-5.4',
|
|
13
|
+
reasoningEffort: 'high',
|
|
14
|
+
speedTier: 'standard',
|
|
15
|
+
locale: 'en',
|
|
16
|
+
requireReviewing: true,
|
|
17
|
+
autoOpen: true,
|
|
18
|
+
loadCodexLocalSkills: true,
|
|
19
|
+
loadCodexOverleafSkills: true,
|
|
20
|
+
panelWidth: 380,
|
|
21
|
+
task: '',
|
|
22
|
+
focusFiles: [],
|
|
23
|
+
session: null,
|
|
24
|
+
runs: [],
|
|
25
|
+
sessions: [],
|
|
26
|
+
activeSessionId: '',
|
|
27
|
+
customInstructionsByProject: {}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const VALID_MODES = new Set(['ask', 'confirm', 'auto']);
|
|
31
|
+
const VALID_REASONING = new Set(['low', 'medium', 'high', 'xhigh']);
|
|
32
|
+
const VALID_SPEED_TIERS = new Set(['standard', 'fast']);
|
|
33
|
+
const VALID_LOCALES = new Set(['en', 'zh']);
|
|
34
|
+
const VALID_EVENT_STATUSES = new Set(['info', 'running', 'completed', 'failed', 'warning', 'blocked', 'skipped', 'pending']);
|
|
35
|
+
const VALID_TITLE_SOURCES = new Set(['auto', 'manual']);
|
|
36
|
+
const LEGACY_DEFAULT_SESSION_TITLE = 'New task';
|
|
37
|
+
const SESSION_AUTO_TITLE_CHARS = 24;
|
|
38
|
+
const MAX_RUN_EVENTS = 300;
|
|
39
|
+
const CUSTOM_INSTRUCTIONS_MAX_CHARS = 12000;
|
|
40
|
+
const PROJECT_PREF_KEY_MAX_CHARS = 160;
|
|
41
|
+
const STORAGE_DEFAULT_LIMITS = {
|
|
42
|
+
maxSessions: 12,
|
|
43
|
+
maxRunsPerSession: 10,
|
|
44
|
+
maxEventsPerRun: 120,
|
|
45
|
+
maxUndoRunsPerSession: 2,
|
|
46
|
+
maxUndoBytesPerRun: 320 * 1024,
|
|
47
|
+
targetBytes: 4 * 1024 * 1024,
|
|
48
|
+
titleChars: 6000,
|
|
49
|
+
detailChars: 3000,
|
|
50
|
+
reportDetailChars: 64000,
|
|
51
|
+
historyChars: 1800,
|
|
52
|
+
taskChars: 12000,
|
|
53
|
+
sessionTitleChars: 80,
|
|
54
|
+
statusTextChars: 800,
|
|
55
|
+
attachmentPreviewChars: 768 * 1024
|
|
56
|
+
};
|
|
57
|
+
const STORAGE_AGGRESSIVE_LIMITS = {
|
|
58
|
+
maxSessions: 4,
|
|
59
|
+
maxRunsPerSession: 3,
|
|
60
|
+
maxEventsPerRun: 20,
|
|
61
|
+
maxUndoRunsPerSession: 1,
|
|
62
|
+
maxUndoBytesPerRun: 160 * 1024,
|
|
63
|
+
targetBytes: 768 * 1024,
|
|
64
|
+
titleChars: 1000,
|
|
65
|
+
detailChars: 300,
|
|
66
|
+
reportDetailChars: 16000,
|
|
67
|
+
historyChars: 400,
|
|
68
|
+
taskChars: 2000,
|
|
69
|
+
sessionTitleChars: 80,
|
|
70
|
+
statusTextChars: 400,
|
|
71
|
+
attachmentPreviewChars: 160 * 1024
|
|
72
|
+
};
|
|
73
|
+
const REDACTED_SECRET = '[REDACTED_SECRET]';
|
|
74
|
+
const SECRET_REDACTION_PATTERNS = [
|
|
75
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g,
|
|
76
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi,
|
|
77
|
+
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{16,}\b/g,
|
|
78
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
79
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/gi,
|
|
80
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
81
|
+
/\b(?:sk|pk)-[A-Za-z0-9][A-Za-z0-9_-]{7,}\b/g,
|
|
82
|
+
/\b(?:api[_-]?key|token|password|passwd|secret)\b\s*[:=]\s*["']?[^"'\s,;]+["']?/gi
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
function normalizePanelState(input = {}, options = {}) {
|
|
86
|
+
const state = {
|
|
87
|
+
...DEFAULT_PANEL_STATE,
|
|
88
|
+
...input
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if (!VALID_MODES.has(state.mode)) {
|
|
92
|
+
state.mode = DEFAULT_PANEL_STATE.mode;
|
|
93
|
+
}
|
|
94
|
+
if (!VALID_REASONING.has(state.reasoningEffort)) {
|
|
95
|
+
state.reasoningEffort = DEFAULT_PANEL_STATE.reasoningEffort;
|
|
96
|
+
}
|
|
97
|
+
if (!VALID_SPEED_TIERS.has(state.speedTier)) {
|
|
98
|
+
state.speedTier = DEFAULT_PANEL_STATE.speedTier;
|
|
99
|
+
}
|
|
100
|
+
if (!VALID_LOCALES.has(state.locale)) {
|
|
101
|
+
state.locale = DEFAULT_PANEL_STATE.locale;
|
|
102
|
+
}
|
|
103
|
+
state.requireReviewing = state.requireReviewing !== false;
|
|
104
|
+
state.autoOpen = state.autoOpen !== false;
|
|
105
|
+
state.loadCodexLocalSkills = state.loadCodexLocalSkills !== false;
|
|
106
|
+
state.loadCodexOverleafSkills = state.loadCodexOverleafSkills !== false;
|
|
107
|
+
state.panelWidth = normalizePanelWidth(state.panelWidth);
|
|
108
|
+
state.task = typeof state.task === 'string' ? state.task : '';
|
|
109
|
+
state.model = typeof state.model === 'string' && state.model ? state.model : DEFAULT_PANEL_STATE.model;
|
|
110
|
+
state.customInstructionsByProject = normalizeCustomInstructionsByProject(state.customInstructionsByProject);
|
|
111
|
+
state.runs = normalizeRuns(state.runs, options);
|
|
112
|
+
state.sessions = normalizeSessions(state, input, options);
|
|
113
|
+
state.activeSessionId = resolveActiveSessionId(state.sessions, input.activeSessionId);
|
|
114
|
+
|
|
115
|
+
return mirrorActiveSession(state);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizeSessions(state, input, options = {}) {
|
|
119
|
+
const explicitSessions = Array.isArray(input.sessions) ? input.sessions : [];
|
|
120
|
+
const sessions = explicitSessions
|
|
121
|
+
.filter(session => session && typeof session.id === 'string')
|
|
122
|
+
.map(session => normalizeSession(session, state, options));
|
|
123
|
+
|
|
124
|
+
if (sessions.length) {
|
|
125
|
+
return sessions.slice(-20);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const legacySession = input.session?.id ? input.session : createSession();
|
|
129
|
+
return [normalizeSession({
|
|
130
|
+
...legacySession,
|
|
131
|
+
task: state.task,
|
|
132
|
+
mode: state.mode,
|
|
133
|
+
model: state.model,
|
|
134
|
+
reasoningEffort: state.reasoningEffort,
|
|
135
|
+
speedTier: state.speedTier,
|
|
136
|
+
requireReviewing: state.requireReviewing,
|
|
137
|
+
focusFiles: normalizeFocusFiles(state.focusFiles || legacySession.focusFiles),
|
|
138
|
+
runs: state.runs,
|
|
139
|
+
title: legacySession.title || deriveSessionTitle(state.runs, state.task),
|
|
140
|
+
titleSource: legacySession.titleSource,
|
|
141
|
+
updatedAt: legacySession.updatedAt
|
|
142
|
+
}, state, options)];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeSession(session, fallbackState = DEFAULT_PANEL_STATE, options = {}) {
|
|
146
|
+
const history = Array.isArray(session.history) ? session.history.slice(-10) : [];
|
|
147
|
+
const normalizedRuns = normalizeRuns(session.runs, options);
|
|
148
|
+
const runs = normalizedRuns.length ? normalizedRuns : recoverRunsFromHistory(session, history);
|
|
149
|
+
const rawTitle = typeof session.title === 'string' ? session.title.trim() : '';
|
|
150
|
+
const derivedTitle = deriveSessionTitle(runs, session.task);
|
|
151
|
+
const titleSource = normalizeTitleSource(session.titleSource, rawTitle, derivedTitle);
|
|
152
|
+
const title = normalizeSessionTitle(rawTitle, derivedTitle, titleSource);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
id: session.id,
|
|
156
|
+
title,
|
|
157
|
+
titleSource,
|
|
158
|
+
createdAt: typeof session.createdAt === 'string' ? session.createdAt : new Date().toISOString(),
|
|
159
|
+
updatedAt: typeof session.updatedAt === 'string' ? session.updatedAt : new Date().toISOString(),
|
|
160
|
+
history,
|
|
161
|
+
runs,
|
|
162
|
+
task: typeof session.task === 'string' ? session.task : '',
|
|
163
|
+
mode: VALID_MODES.has(session.mode) ? session.mode : fallbackState.mode,
|
|
164
|
+
model: typeof session.model === 'string' && session.model ? session.model : fallbackState.model,
|
|
165
|
+
reasoningEffort: VALID_REASONING.has(session.reasoningEffort)
|
|
166
|
+
? session.reasoningEffort
|
|
167
|
+
: fallbackState.reasoningEffort,
|
|
168
|
+
speedTier: VALID_SPEED_TIERS.has(session.speedTier)
|
|
169
|
+
? session.speedTier
|
|
170
|
+
: fallbackState.speedTier,
|
|
171
|
+
requireReviewing: session.requireReviewing !== false,
|
|
172
|
+
focusFiles: normalizeFocusFiles(session.focusFiles),
|
|
173
|
+
codexThreadId: typeof session.codexThreadId === 'string' ? session.codexThreadId : ''
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function recoverRunsFromHistory(session, history) {
|
|
178
|
+
if (!Array.isArray(history) || !history.length) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
return normalizeRuns(history.map((entry, index) => {
|
|
182
|
+
const task = typeof entry?.task === 'string' && entry.task.trim()
|
|
183
|
+
? entry.task.trim()
|
|
184
|
+
: (typeof session.task === 'string' && session.task.trim() ? session.task.trim() : 'untitled task');
|
|
185
|
+
const timestamp = typeof entry?.at === 'string' ? entry.at : session.updatedAt;
|
|
186
|
+
const result = typeof entry?.result === 'string' && entry.result.trim()
|
|
187
|
+
? entry.result.trim()
|
|
188
|
+
: '这轮任务有历史记录,但没有保存详细结果。';
|
|
189
|
+
return {
|
|
190
|
+
id: `recovered_${session.id}_${index}`,
|
|
191
|
+
task,
|
|
192
|
+
mode: session.mode,
|
|
193
|
+
model: session.model,
|
|
194
|
+
reasoningEffort: session.reasoningEffort,
|
|
195
|
+
speedTier: session.speedTier,
|
|
196
|
+
status: 'completed',
|
|
197
|
+
statusText: '已处理',
|
|
198
|
+
startedAt: timestamp,
|
|
199
|
+
finishedAt: timestamp,
|
|
200
|
+
events: [{
|
|
201
|
+
title: '本轮完成报告',
|
|
202
|
+
status: 'completed',
|
|
203
|
+
kind: 'report',
|
|
204
|
+
timestamp,
|
|
205
|
+
detail: {
|
|
206
|
+
'结论': result
|
|
207
|
+
}
|
|
208
|
+
}]
|
|
209
|
+
};
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function resolveActiveSessionId(sessions, activeSessionId) {
|
|
214
|
+
if (typeof activeSessionId === 'string' && sessions.some(session => session.id === activeSessionId)) {
|
|
215
|
+
return activeSessionId;
|
|
216
|
+
}
|
|
217
|
+
return sessions[sessions.length - 1]?.id || createSession().id;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function mirrorActiveSession(state) {
|
|
221
|
+
const active = getActiveSession(state);
|
|
222
|
+
if (!active) {
|
|
223
|
+
const session = createSession({
|
|
224
|
+
mode: state.mode,
|
|
225
|
+
model: state.model,
|
|
226
|
+
reasoningEffort: state.reasoningEffort,
|
|
227
|
+
speedTier: state.speedTier,
|
|
228
|
+
requireReviewing: state.requireReviewing
|
|
229
|
+
});
|
|
230
|
+
state.sessions = [session];
|
|
231
|
+
state.activeSessionId = session.id;
|
|
232
|
+
return mirrorActiveSession(state);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
state.session = {
|
|
236
|
+
id: active.id,
|
|
237
|
+
history: Array.isArray(active.history) ? active.history.slice(-10) : [],
|
|
238
|
+
focusFiles: normalizeFocusFiles(active.focusFiles),
|
|
239
|
+
codexThreadId: active.codexThreadId || ''
|
|
240
|
+
};
|
|
241
|
+
state.runs = Array.isArray(active.runs) ? active.runs : [];
|
|
242
|
+
state.task = typeof active.task === 'string' ? active.task : '';
|
|
243
|
+
state.focusFiles = normalizeFocusFiles(active.focusFiles);
|
|
244
|
+
state.mode = VALID_MODES.has(active.mode) ? active.mode : DEFAULT_PANEL_STATE.mode;
|
|
245
|
+
state.model = typeof active.model === 'string' && active.model ? active.model : DEFAULT_PANEL_STATE.model;
|
|
246
|
+
state.reasoningEffort = VALID_REASONING.has(active.reasoningEffort)
|
|
247
|
+
? active.reasoningEffort
|
|
248
|
+
: DEFAULT_PANEL_STATE.reasoningEffort;
|
|
249
|
+
state.speedTier = VALID_SPEED_TIERS.has(active.speedTier)
|
|
250
|
+
? active.speedTier
|
|
251
|
+
: DEFAULT_PANEL_STATE.speedTier;
|
|
252
|
+
state.requireReviewing = active.requireReviewing !== false;
|
|
253
|
+
|
|
254
|
+
return state;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function createSession(overrides = {}) {
|
|
258
|
+
const runs = Array.isArray(overrides.runs) ? normalizeRuns(overrides.runs) : [];
|
|
259
|
+
const rawTitle = typeof overrides.title === 'string' ? overrides.title.trim() : '';
|
|
260
|
+
const derivedTitle = deriveSessionTitle(runs, overrides.task || rawTitle);
|
|
261
|
+
const titleSource = VALID_TITLE_SOURCES.has(overrides.titleSource)
|
|
262
|
+
? overrides.titleSource
|
|
263
|
+
: (rawTitle && rawTitle !== LEGACY_DEFAULT_SESSION_TITLE ? 'manual' : 'auto');
|
|
264
|
+
return {
|
|
265
|
+
id: `session_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
|
|
266
|
+
title: normalizeSessionTitle(rawTitle, derivedTitle, titleSource),
|
|
267
|
+
titleSource,
|
|
268
|
+
createdAt: overrides.createdAt || new Date().toISOString(),
|
|
269
|
+
updatedAt: overrides.updatedAt || new Date().toISOString(),
|
|
270
|
+
history: Array.isArray(overrides.history) ? overrides.history.slice(-10) : [],
|
|
271
|
+
runs,
|
|
272
|
+
task: typeof overrides.task === 'string' ? overrides.task : '',
|
|
273
|
+
mode: VALID_MODES.has(overrides.mode) ? overrides.mode : DEFAULT_PANEL_STATE.mode,
|
|
274
|
+
model: typeof overrides.model === 'string' && overrides.model ? overrides.model : DEFAULT_PANEL_STATE.model,
|
|
275
|
+
reasoningEffort: VALID_REASONING.has(overrides.reasoningEffort)
|
|
276
|
+
? overrides.reasoningEffort
|
|
277
|
+
: DEFAULT_PANEL_STATE.reasoningEffort,
|
|
278
|
+
speedTier: VALID_SPEED_TIERS.has(overrides.speedTier)
|
|
279
|
+
? overrides.speedTier
|
|
280
|
+
: DEFAULT_PANEL_STATE.speedTier,
|
|
281
|
+
requireReviewing: overrides.requireReviewing !== false,
|
|
282
|
+
focusFiles: normalizeFocusFiles(overrides.focusFiles),
|
|
283
|
+
codexThreadId: typeof overrides.codexThreadId === 'string' ? overrides.codexThreadId : ''
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function recordSessionResult(session, entry) {
|
|
288
|
+
const base = session?.id ? session : createSession();
|
|
289
|
+
return {
|
|
290
|
+
...base,
|
|
291
|
+
id: base.id,
|
|
292
|
+
titleSource: VALID_TITLE_SOURCES.has(base.titleSource) ? base.titleSource : 'auto',
|
|
293
|
+
history: [
|
|
294
|
+
...(Array.isArray(base.history) ? base.history : []),
|
|
295
|
+
{
|
|
296
|
+
task: entry.task || 'untitled task',
|
|
297
|
+
result: entry.result || entry.status || 'completed',
|
|
298
|
+
at: entry.at || new Date().toISOString()
|
|
299
|
+
}
|
|
300
|
+
].slice(-10),
|
|
301
|
+
updatedAt: entry.at || new Date().toISOString()
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function getActiveSession(state) {
|
|
306
|
+
return (state?.sessions || []).find(session => session.id === state.activeSessionId) || null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function updateActiveSession(state, patch) {
|
|
310
|
+
const activeSessionId = state.activeSessionId;
|
|
311
|
+
const sessions = (state.sessions || []).map(session => {
|
|
312
|
+
if (session.id !== activeSessionId) {
|
|
313
|
+
return session;
|
|
314
|
+
}
|
|
315
|
+
const next = {
|
|
316
|
+
...session,
|
|
317
|
+
...patch,
|
|
318
|
+
updatedAt: patch.updatedAt || new Date().toISOString()
|
|
319
|
+
};
|
|
320
|
+
if (
|
|
321
|
+
session.titleSource === 'manual' &&
|
|
322
|
+
patch.titleSource !== 'manual' &&
|
|
323
|
+
Object.prototype.hasOwnProperty.call(patch, 'title')
|
|
324
|
+
) {
|
|
325
|
+
next.title = session.title;
|
|
326
|
+
next.titleSource = 'manual';
|
|
327
|
+
}
|
|
328
|
+
return normalizeSession({
|
|
329
|
+
...next
|
|
330
|
+
}, state);
|
|
331
|
+
});
|
|
332
|
+
return mirrorActiveSession({
|
|
333
|
+
...state,
|
|
334
|
+
sessions
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function setActiveSession(state, sessionId) {
|
|
339
|
+
if (!(state.sessions || []).some(session => session.id === sessionId)) {
|
|
340
|
+
return mirrorActiveSession(state);
|
|
341
|
+
}
|
|
342
|
+
return mirrorActiveSession({
|
|
343
|
+
...state,
|
|
344
|
+
activeSessionId: sessionId
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function deleteSession(state, sessionId) {
|
|
349
|
+
const sessions = state.sessions || [];
|
|
350
|
+
if (!sessions.some(session => session.id === sessionId)) {
|
|
351
|
+
return mirrorActiveSession(state);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const remaining = sessions.filter(session => session.id !== sessionId);
|
|
355
|
+
if (!remaining.length) {
|
|
356
|
+
const replacement = createSession({
|
|
357
|
+
mode: state.mode,
|
|
358
|
+
model: state.model,
|
|
359
|
+
reasoningEffort: state.reasoningEffort,
|
|
360
|
+
speedTier: state.speedTier,
|
|
361
|
+
requireReviewing: state.requireReviewing
|
|
362
|
+
});
|
|
363
|
+
return mirrorActiveSession({
|
|
364
|
+
...state,
|
|
365
|
+
sessions: [replacement],
|
|
366
|
+
activeSessionId: replacement.id
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const activeSessionId = state.activeSessionId === sessionId
|
|
371
|
+
? getMostRecentSession(remaining).id
|
|
372
|
+
: state.activeSessionId;
|
|
373
|
+
|
|
374
|
+
return mirrorActiveSession({
|
|
375
|
+
...state,
|
|
376
|
+
sessions: remaining,
|
|
377
|
+
activeSessionId
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function getMostRecentSession(sessions) {
|
|
382
|
+
return sessions.reduce((latest, session) => {
|
|
383
|
+
const latestTime = Date.parse(latest.updatedAt || latest.createdAt || '');
|
|
384
|
+
const sessionTime = Date.parse(session.updatedAt || session.createdAt || '');
|
|
385
|
+
if (Number.isNaN(latestTime)) {
|
|
386
|
+
return session;
|
|
387
|
+
}
|
|
388
|
+
if (Number.isNaN(sessionTime)) {
|
|
389
|
+
return latest;
|
|
390
|
+
}
|
|
391
|
+
return sessionTime >= latestTime ? session : latest;
|
|
392
|
+
}, sessions[0]);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function deriveSessionTitle(runs, task) {
|
|
396
|
+
const taskTitle = sanitizeAutoTitle(task);
|
|
397
|
+
if (taskTitle) {
|
|
398
|
+
return taskTitle;
|
|
399
|
+
}
|
|
400
|
+
const firstRunTask = Array.isArray(runs) && runs.length ? runs[0]?.task : '';
|
|
401
|
+
return sanitizeAutoTitle(firstRunTask);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function normalizeTitleSource(value, rawTitle, derivedTitle) {
|
|
405
|
+
if (VALID_TITLE_SOURCES.has(value)) {
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
if (rawTitle && rawTitle !== LEGACY_DEFAULT_SESSION_TITLE && rawTitle !== derivedTitle) {
|
|
409
|
+
return 'manual';
|
|
410
|
+
}
|
|
411
|
+
return 'auto';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function normalizeSessionTitle(rawTitle, derivedTitle, titleSource) {
|
|
415
|
+
if (titleSource === 'manual') {
|
|
416
|
+
return normalizeTextField(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle, STORAGE_DEFAULT_LIMITS.sessionTitleChars);
|
|
417
|
+
}
|
|
418
|
+
return sanitizeAutoTitle(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle) || derivedTitle || '';
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function sanitizeAutoTitle(value) {
|
|
422
|
+
const title = String(value || '')
|
|
423
|
+
.replace(/@file:[^\s]+/g, ' ')
|
|
424
|
+
.replace(/@(context|compile-log)\b/g, ' ')
|
|
425
|
+
.replace(/\s+/g, ' ')
|
|
426
|
+
.trim();
|
|
427
|
+
if (!title) {
|
|
428
|
+
return '';
|
|
429
|
+
}
|
|
430
|
+
return title.length > SESSION_AUTO_TITLE_CHARS
|
|
431
|
+
? `${title.slice(0, SESSION_AUTO_TITLE_CHARS - 1)}…`
|
|
432
|
+
: title;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function isDisplayableSession(session) {
|
|
436
|
+
if (!session?.id) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const hasTask = typeof session.task === 'string' && session.task.trim().length > 0;
|
|
440
|
+
const hasRuns = Array.isArray(session.runs) && session.runs.length > 0;
|
|
441
|
+
const hasHistory = Array.isArray(session.history) && session.history.length > 0;
|
|
442
|
+
const title = typeof session.title === 'string' ? session.title.trim() : '';
|
|
443
|
+
const hasRealTitle = title.length > 0 && title !== LEGACY_DEFAULT_SESSION_TITLE;
|
|
444
|
+
return hasTask || hasRuns || hasHistory || hasRealTitle;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function selectVisibleSessionsForList(sessions, activeSessionId, options = {}) {
|
|
448
|
+
const maxVisible = Number.isFinite(options.maxVisible) && options.maxVisible > 0
|
|
449
|
+
? Math.floor(options.maxVisible)
|
|
450
|
+
: 3;
|
|
451
|
+
const displayable = (Array.isArray(sessions) ? sessions : []).filter(isDisplayableSession);
|
|
452
|
+
if (options.showAll || displayable.length <= maxVisible) {
|
|
453
|
+
return displayable.slice().reverse();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const pinnedSessionIds = Array.isArray(options.pinnedSessionIds)
|
|
457
|
+
? options.pinnedSessionIds.filter(Boolean)
|
|
458
|
+
: [];
|
|
459
|
+
const recent = displayable.slice(-maxVisible).reverse();
|
|
460
|
+
if (pinnedSessionIds.length) {
|
|
461
|
+
const selected = [];
|
|
462
|
+
const pushSession = session => {
|
|
463
|
+
if (session && !selected.some(item => item.id === session.id)) {
|
|
464
|
+
selected.push(session);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
pushSession(displayable.find(session => session.id === activeSessionId));
|
|
468
|
+
for (const pinnedId of pinnedSessionIds) {
|
|
469
|
+
pushSession(displayable.find(session => session.id === pinnedId));
|
|
470
|
+
}
|
|
471
|
+
for (const session of recent) {
|
|
472
|
+
pushSession(session);
|
|
473
|
+
}
|
|
474
|
+
return selected.slice(0, maxVisible);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!activeSessionId || recent.some(session => session.id === activeSessionId)) {
|
|
478
|
+
return recent;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const active = displayable.find(session => session.id === activeSessionId);
|
|
482
|
+
if (!active) {
|
|
483
|
+
return recent;
|
|
484
|
+
}
|
|
485
|
+
return [active, ...recent.filter(session => session.id !== activeSessionId).slice(0, maxVisible - 1)];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function normalizeRuns(runs, options = {}) {
|
|
489
|
+
return (Array.isArray(runs) ? runs : [])
|
|
490
|
+
.filter(run => run && typeof run.id === 'string')
|
|
491
|
+
.map(run => normalizeRun(run, options))
|
|
492
|
+
.slice(-20);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function normalizeRun(run, options = {}) {
|
|
496
|
+
const shouldStopRestoredRun = options.restoreRunningRuns === true && run.status === 'running';
|
|
497
|
+
const events = normalizeRunEvents(run.events);
|
|
498
|
+
if (shouldStopRestoredRun) {
|
|
499
|
+
events.push({
|
|
500
|
+
title: '页面刷新后已停止跟踪这轮任务',
|
|
501
|
+
status: 'failed',
|
|
502
|
+
detail: '插件重新加载时发现这轮任务还标记为处理中。为了避免继续显示过期状态,已把它标记为中断;可以重新运行任务。',
|
|
503
|
+
timestamp: new Date().toISOString()
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
id: run.id,
|
|
509
|
+
task: typeof run.task === 'string' && run.task ? run.task : 'untitled task',
|
|
510
|
+
mode: typeof run.mode === 'string' ? run.mode : '',
|
|
511
|
+
model: typeof run.model === 'string' ? run.model : '',
|
|
512
|
+
reasoningEffort: typeof run.reasoningEffort === 'string' ? run.reasoningEffort : '',
|
|
513
|
+
speedTier: typeof run.speedTier === 'string' ? run.speedTier : '',
|
|
514
|
+
status: shouldStopRestoredRun ? 'failed' : normalizeRunStatus(run.status),
|
|
515
|
+
statusText: shouldStopRestoredRun ? '页面刷新后已停止跟踪' : typeof run.statusText === 'string' ? run.statusText : '',
|
|
516
|
+
startedAt: typeof run.startedAt === 'string' ? run.startedAt : '',
|
|
517
|
+
finishedAt: shouldStopRestoredRun ? new Date().toISOString() : typeof run.finishedAt === 'string' ? run.finishedAt : '',
|
|
518
|
+
events: events.slice(-MAX_RUN_EVENTS),
|
|
519
|
+
attachments: normalizeRunAttachments(run.attachments, STORAGE_DEFAULT_LIMITS),
|
|
520
|
+
appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
|
|
521
|
+
undoOperations: Array.isArray(run.undoOperations) ? run.undoOperations : [],
|
|
522
|
+
undoBaseFiles: normalizeRunFiles(run.undoBaseFiles),
|
|
523
|
+
undoTrackedChanges: normalizeRunTrackedChanges(run.undoTrackedChanges),
|
|
524
|
+
undoExpectedFiles: normalizeRunFiles(run.undoExpectedFiles),
|
|
525
|
+
undoStatus: typeof run.undoStatus === 'string' ? redactSecretLikeText(run.undoStatus) : ''
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function normalizeRunStatus(status) {
|
|
530
|
+
return ['running', 'completed', 'failed'].includes(status) ? status : 'completed';
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function normalizeEventStatus(status) {
|
|
534
|
+
return VALID_EVENT_STATUSES.has(status) ? status : 'info';
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function normalizeRunEvents(events) {
|
|
538
|
+
return (Array.isArray(events) ? events : [])
|
|
539
|
+
.filter(event => event && typeof event.title === 'string')
|
|
540
|
+
.map(event => ({
|
|
541
|
+
title: event.title,
|
|
542
|
+
status: typeof event.status === 'string' ? event.status : 'info',
|
|
543
|
+
detail: event.detail,
|
|
544
|
+
timestamp: typeof event.timestamp === 'string' ? event.timestamp : '',
|
|
545
|
+
kind: typeof event.kind === 'string' ? event.kind : 'activity',
|
|
546
|
+
technicalDetail: event.technicalDetail,
|
|
547
|
+
streamKey: typeof event.streamKey === 'string' ? event.streamKey : '',
|
|
548
|
+
streamRole: typeof event.streamRole === 'string' ? event.streamRole : ''
|
|
549
|
+
}))
|
|
550
|
+
.slice(-MAX_RUN_EVENTS);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function normalizeRunAttachments(attachments, limits = STORAGE_DEFAULT_LIMITS) {
|
|
554
|
+
const normalized = [];
|
|
555
|
+
for (const attachment of Array.isArray(attachments) ? attachments : []) {
|
|
556
|
+
const name = normalizeAttachmentName(attachment?.name);
|
|
557
|
+
if (!name) {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const mimeType = normalizeTextField(attachment.mimeType, 120);
|
|
561
|
+
const size = Number(attachment.size);
|
|
562
|
+
const previewDataUrl = normalizeAttachmentPreviewDataUrl(attachment.previewDataUrl, limits);
|
|
563
|
+
const kind = attachment.kind === 'image' || /^image\//i.test(mimeType) || /^data:image\//i.test(previewDataUrl)
|
|
564
|
+
? 'image'
|
|
565
|
+
: 'file';
|
|
566
|
+
normalized.push({
|
|
567
|
+
name,
|
|
568
|
+
mimeType,
|
|
569
|
+
size: Number.isFinite(size) && size >= 0 ? Math.round(size) : 0,
|
|
570
|
+
kind,
|
|
571
|
+
previewDataUrl: kind === 'image' ? previewDataUrl : ''
|
|
572
|
+
});
|
|
573
|
+
if (normalized.length >= 8) {
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return normalized;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function compactRunAttachmentsForStorage(attachments, limits = STORAGE_DEFAULT_LIMITS) {
|
|
581
|
+
return normalizeRunAttachments(attachments, limits).map(attachment => ({
|
|
582
|
+
name: attachment.name,
|
|
583
|
+
mimeType: attachment.mimeType,
|
|
584
|
+
size: attachment.size,
|
|
585
|
+
kind: attachment.kind
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function normalizeAttachmentName(value) {
|
|
590
|
+
return normalizeTextField(String(value || '')
|
|
591
|
+
.replace(/\0/g, '')
|
|
592
|
+
.replace(/\\/g, '/')
|
|
593
|
+
.split('/')
|
|
594
|
+
.filter(Boolean)
|
|
595
|
+
.pop() || '', 180);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function normalizeAttachmentPreviewDataUrl(value, limits = STORAGE_DEFAULT_LIMITS) {
|
|
599
|
+
const text = String(value || '').trim();
|
|
600
|
+
if (!/^data:image\/[a-z0-9.+-]+;base64,/i.test(text)) {
|
|
601
|
+
return '';
|
|
602
|
+
}
|
|
603
|
+
return normalizeTextField(text, limits.attachmentPreviewChars || 0);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function normalizeRunFiles(files) {
|
|
607
|
+
return (Array.isArray(files) ? files : [])
|
|
608
|
+
.filter(file => typeof file?.path === 'string' && typeof file.content === 'string')
|
|
609
|
+
.map(file => ({
|
|
610
|
+
path: file.path,
|
|
611
|
+
content: file.content
|
|
612
|
+
}));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function normalizeRunTrackedChanges(changes) {
|
|
616
|
+
const seen = new Set();
|
|
617
|
+
const normalized = [];
|
|
618
|
+
for (const change of Array.isArray(changes) ? changes : []) {
|
|
619
|
+
const key = typeof change?.key === 'string' ? change.key : '';
|
|
620
|
+
if (!key || seen.has(key)) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
seen.add(key);
|
|
624
|
+
normalized.push({
|
|
625
|
+
key,
|
|
626
|
+
id: typeof change.id === 'string' ? change.id : '',
|
|
627
|
+
path: typeof change.path === 'string' ? change.path : '',
|
|
628
|
+
label: typeof change.label === 'string' ? change.label : ''
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return normalized;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function normalizeFocusFiles(value) {
|
|
635
|
+
const seen = new Set();
|
|
636
|
+
const files = [];
|
|
637
|
+
for (const item of Array.isArray(value) ? value : []) {
|
|
638
|
+
if (typeof item !== 'string') {
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const path = redactSecretLikeText(item.trim());
|
|
642
|
+
if (!path || seen.has(path)) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
seen.add(path);
|
|
646
|
+
files.push(path);
|
|
647
|
+
}
|
|
648
|
+
return files.slice(0, 5);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function normalizeCustomInstructionsByProject(value) {
|
|
652
|
+
const result = {};
|
|
653
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
654
|
+
return result;
|
|
655
|
+
}
|
|
656
|
+
const keys = Object.keys(value);
|
|
657
|
+
for (const rawKey of keys) {
|
|
658
|
+
const key = normalizeProjectPrefKey(rawKey);
|
|
659
|
+
if (!key) {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
result[key] = typeof value[rawKey] === 'string'
|
|
663
|
+
? normalizeTextField(value[rawKey], CUSTOM_INSTRUCTIONS_MAX_CHARS)
|
|
664
|
+
: '';
|
|
665
|
+
}
|
|
666
|
+
return result;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function normalizeProjectPrefKey(value) {
|
|
670
|
+
const key = typeof value === 'string' ? value.trim() : '';
|
|
671
|
+
if (!key) {
|
|
672
|
+
return '';
|
|
673
|
+
}
|
|
674
|
+
return normalizeTextField(key, PROJECT_PREF_KEY_MAX_CHARS);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function prepareStateForStorage(input = {}, options = {}) {
|
|
678
|
+
const limits = options.aggressive ? STORAGE_AGGRESSIVE_LIMITS : STORAGE_DEFAULT_LIMITS;
|
|
679
|
+
const compact = compactPanelStateForStorage(input, limits);
|
|
680
|
+
if (!options.aggressive && estimateJsonBytes(compact) > limits.targetBytes) {
|
|
681
|
+
return prepareStateForStorage(input, { aggressive: true });
|
|
682
|
+
}
|
|
683
|
+
return compact;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function compactPanelStateForStorage(input, limits) {
|
|
687
|
+
const source = {
|
|
688
|
+
...DEFAULT_PANEL_STATE,
|
|
689
|
+
...(input && typeof input === 'object' ? input : {})
|
|
690
|
+
};
|
|
691
|
+
const sourceSessions = getSourceSessions(source);
|
|
692
|
+
const activeSessionId = typeof source.activeSessionId === 'string'
|
|
693
|
+
? source.activeSessionId
|
|
694
|
+
: source.session?.id;
|
|
695
|
+
const selectedSessions = selectSessionsForStorage(sourceSessions, activeSessionId, limits.maxSessions);
|
|
696
|
+
const compactSessions = selectedSessions.map(session => compactSessionForStorage(session, source, limits));
|
|
697
|
+
const resolvedActiveId = compactSessions.some(session => session.id === activeSessionId)
|
|
698
|
+
? activeSessionId
|
|
699
|
+
: compactSessions[compactSessions.length - 1]?.id || '';
|
|
700
|
+
const active = compactSessions.find(session => session.id === resolvedActiveId) || compactSessions[0] || null;
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
mode: VALID_MODES.has(active?.mode) ? active.mode : normalizeMode(source.mode),
|
|
704
|
+
model: normalizeTextField(active?.model || source.model || DEFAULT_PANEL_STATE.model, 80),
|
|
705
|
+
reasoningEffort: VALID_REASONING.has(active?.reasoningEffort)
|
|
706
|
+
? active.reasoningEffort
|
|
707
|
+
: normalizeReasoning(source.reasoningEffort),
|
|
708
|
+
speedTier: normalizeSpeedTier(active?.speedTier || source.speedTier),
|
|
709
|
+
locale: normalizeLocale(source.locale),
|
|
710
|
+
requireReviewing: active ? active.requireReviewing !== false : source.requireReviewing !== false,
|
|
711
|
+
autoOpen: source.autoOpen !== false,
|
|
712
|
+
loadCodexLocalSkills: source.loadCodexLocalSkills !== false,
|
|
713
|
+
loadCodexOverleafSkills: source.loadCodexOverleafSkills !== false,
|
|
714
|
+
panelWidth: normalizePanelWidth(source.panelWidth),
|
|
715
|
+
task: summarizeTextForStorage(active?.task || source.task || '', 'task'),
|
|
716
|
+
focusFiles: normalizeFocusFiles(active?.focusFiles || source.focusFiles),
|
|
717
|
+
session: active ? {
|
|
718
|
+
id: active.id,
|
|
719
|
+
history: compactHistory(active.history, limits),
|
|
720
|
+
focusFiles: normalizeFocusFiles(active.focusFiles)
|
|
721
|
+
} : null,
|
|
722
|
+
runs: [],
|
|
723
|
+
sessions: compactSessions,
|
|
724
|
+
activeSessionId: active?.id || '',
|
|
725
|
+
customInstructionsByProject: normalizeCustomInstructionsByProject(source.customInstructionsByProject)
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function getSourceSessions(source) {
|
|
730
|
+
if (Array.isArray(source.sessions) && source.sessions.some(session => session?.id)) {
|
|
731
|
+
return source.sessions;
|
|
732
|
+
}
|
|
733
|
+
const legacySession = source.session?.id ? source.session : createSession({
|
|
734
|
+
mode: source.mode,
|
|
735
|
+
model: source.model,
|
|
736
|
+
reasoningEffort: source.reasoningEffort,
|
|
737
|
+
speedTier: source.speedTier,
|
|
738
|
+
requireReviewing: source.requireReviewing
|
|
739
|
+
});
|
|
740
|
+
return [{
|
|
741
|
+
...legacySession,
|
|
742
|
+
task: source.task,
|
|
743
|
+
mode: source.mode,
|
|
744
|
+
model: source.model,
|
|
745
|
+
reasoningEffort: source.reasoningEffort,
|
|
746
|
+
speedTier: source.speedTier,
|
|
747
|
+
requireReviewing: source.requireReviewing,
|
|
748
|
+
focusFiles: normalizeFocusFiles(source.focusFiles || legacySession.focusFiles),
|
|
749
|
+
runs: Array.isArray(source.runs) ? source.runs : legacySession.runs,
|
|
750
|
+
title: legacySession.title || deriveSessionTitle(source.runs, source.task),
|
|
751
|
+
titleSource: legacySession.titleSource,
|
|
752
|
+
updatedAt: legacySession.updatedAt
|
|
753
|
+
}];
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function selectSessionsForStorage(sessions, activeSessionId, maxSessions) {
|
|
757
|
+
const filtered = (Array.isArray(sessions) ? sessions : [])
|
|
758
|
+
.filter(session => session && typeof session.id === 'string');
|
|
759
|
+
if (filtered.length <= maxSessions) {
|
|
760
|
+
return filtered;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const active = filtered.find(session => session.id === activeSessionId);
|
|
764
|
+
const recent = filtered.slice(-maxSessions);
|
|
765
|
+
if (!active || recent.some(session => session.id === active.id)) {
|
|
766
|
+
return recent;
|
|
767
|
+
}
|
|
768
|
+
return [
|
|
769
|
+
active,
|
|
770
|
+
...recent.filter(session => session.id !== active.id).slice(-(maxSessions - 1))
|
|
771
|
+
];
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function compactSessionForStorage(session, fallbackState, limits) {
|
|
775
|
+
const runs = compactRunsForStorage(session.runs, limits);
|
|
776
|
+
const titleSource = VALID_TITLE_SOURCES.has(session.titleSource) ? session.titleSource : 'auto';
|
|
777
|
+
return {
|
|
778
|
+
id: session.id,
|
|
779
|
+
title: compactSessionTitleForStorage(session, titleSource, limits),
|
|
780
|
+
titleSource,
|
|
781
|
+
createdAt: typeof session.createdAt === 'string' ? session.createdAt : new Date().toISOString(),
|
|
782
|
+
updatedAt: typeof session.updatedAt === 'string' ? session.updatedAt : new Date().toISOString(),
|
|
783
|
+
history: compactHistory(session.history, limits),
|
|
784
|
+
runs,
|
|
785
|
+
task: summarizeTextForStorage(session.task, 'task'),
|
|
786
|
+
mode: normalizeMode(session.mode || fallbackState.mode),
|
|
787
|
+
model: normalizeTextField(session.model || fallbackState.model || DEFAULT_PANEL_STATE.model, 80),
|
|
788
|
+
reasoningEffort: normalizeReasoning(session.reasoningEffort || fallbackState.reasoningEffort),
|
|
789
|
+
speedTier: normalizeSpeedTier(session.speedTier || fallbackState.speedTier),
|
|
790
|
+
requireReviewing: session.requireReviewing !== false,
|
|
791
|
+
focusFiles: normalizeFocusFiles(session.focusFiles)
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function compactSessionTitleForStorage(session, titleSource, limits) {
|
|
796
|
+
const rawTitle = typeof session.title === 'string' ? session.title.trim() : '';
|
|
797
|
+
if (titleSource === 'manual') {
|
|
798
|
+
return normalizeTextField(rawTitle === LEGACY_DEFAULT_SESSION_TITLE ? '' : rawTitle, limits.sessionTitleChars);
|
|
799
|
+
}
|
|
800
|
+
if (isOmittedStorageSummary(rawTitle)) {
|
|
801
|
+
return normalizeTextField(rawTitle, limits.sessionTitleChars);
|
|
802
|
+
}
|
|
803
|
+
const firstRunTask = Array.isArray(session.runs) && session.runs.length ? session.runs[0]?.task : '';
|
|
804
|
+
const seed = rawTitle && rawTitle !== LEGACY_DEFAULT_SESSION_TITLE
|
|
805
|
+
? rawTitle
|
|
806
|
+
: (session.task || firstRunTask || '');
|
|
807
|
+
return summarizeTextForStorage(seed, 'session title');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function compactHistory(history, limits) {
|
|
811
|
+
return (Array.isArray(history) ? history : [])
|
|
812
|
+
.slice(-10)
|
|
813
|
+
.map(entry => ({
|
|
814
|
+
task: summarizeTextForStorage(entry?.task, 'history task'),
|
|
815
|
+
result: summarizeTextForStorage(entry?.result, 'history result'),
|
|
816
|
+
at: typeof entry?.at === 'string' ? entry.at : ''
|
|
817
|
+
}));
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function compactRunsForStorage(runs, limits) {
|
|
821
|
+
const selectedRuns = (Array.isArray(runs) ? runs : [])
|
|
822
|
+
.filter(run => run && typeof run.id === 'string')
|
|
823
|
+
.slice(-limits.maxRunsPerSession);
|
|
824
|
+
const undoRunIds = new Set(selectedRuns
|
|
825
|
+
.slice()
|
|
826
|
+
.reverse()
|
|
827
|
+
.filter(run => Array.isArray(run.undoOperations) && run.undoOperations.length)
|
|
828
|
+
.slice(0, limits.maxUndoRunsPerSession)
|
|
829
|
+
.map(run => run.id));
|
|
830
|
+
return selectedRuns.map(run => compactRunForStorage(run, limits, undoRunIds.has(run.id)));
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function compactRunForStorage(run, limits, keepUndoPayload) {
|
|
834
|
+
const undoPayload = compactUndoPayload(run, limits, keepUndoPayload);
|
|
835
|
+
return {
|
|
836
|
+
id: run.id,
|
|
837
|
+
task: summarizeTextForStorage(run.task || 'untitled task', 'run task'),
|
|
838
|
+
mode: typeof run.mode === 'string' ? run.mode : '',
|
|
839
|
+
model: normalizeTextField(run.model, 80),
|
|
840
|
+
reasoningEffort: typeof run.reasoningEffort === 'string' ? run.reasoningEffort : '',
|
|
841
|
+
speedTier: typeof run.speedTier === 'string' ? run.speedTier : '',
|
|
842
|
+
status: normalizeRunStatus(run.status),
|
|
843
|
+
statusText: summarizeTextForStorage(run.statusText, 'status text'),
|
|
844
|
+
startedAt: typeof run.startedAt === 'string' ? run.startedAt : '',
|
|
845
|
+
finishedAt: typeof run.finishedAt === 'string' ? run.finishedAt : '',
|
|
846
|
+
events: compactRunEvents(run.events, limits),
|
|
847
|
+
attachments: compactRunAttachmentsForStorage(run.attachments, limits),
|
|
848
|
+
appliedOperations: [],
|
|
849
|
+
undoOperations: undoPayload.undoOperations,
|
|
850
|
+
undoBaseFiles: undoPayload.undoBaseFiles,
|
|
851
|
+
undoTrackedChanges: undoPayload.undoTrackedChanges,
|
|
852
|
+
undoExpectedFiles: undoPayload.undoExpectedFiles,
|
|
853
|
+
undoStatus: summarizeTextForStorage(run.undoStatus, 'undo status')
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function compactRunEvents(events, limits) {
|
|
858
|
+
return (Array.isArray(events) ? events : [])
|
|
859
|
+
.filter(event => event && typeof event.title === 'string')
|
|
860
|
+
.slice(-limits.maxEventsPerRun)
|
|
861
|
+
.map(event => {
|
|
862
|
+
const compact = {
|
|
863
|
+
title: summarizeTextForStorage(event.title, 'event title') || 'Event',
|
|
864
|
+
status: normalizeEventStatus(event.status),
|
|
865
|
+
timestamp: typeof event.timestamp === 'string' ? event.timestamp : '',
|
|
866
|
+
kind: typeof event.kind === 'string' ? event.kind : 'activity',
|
|
867
|
+
streamKey: typeof event.streamKey === 'string' ? event.streamKey : '',
|
|
868
|
+
streamRole: typeof event.streamRole === 'string' ? event.streamRole : ''
|
|
869
|
+
};
|
|
870
|
+
const detail = compactDetailForStorage(event.detail, getEventDetailLimit(event, limits));
|
|
871
|
+
if (detail !== undefined) {
|
|
872
|
+
compact.detail = detail;
|
|
873
|
+
}
|
|
874
|
+
return compact;
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function getEventDetailLimit(event, limits) {
|
|
879
|
+
return event.kind === 'report'
|
|
880
|
+
? (limits.reportDetailChars || limits.detailChars)
|
|
881
|
+
: limits.detailChars;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function compactDetailForStorage(detail, maxChars) {
|
|
885
|
+
if (detail === undefined) {
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
888
|
+
if (typeof detail === 'string') {
|
|
889
|
+
return summarizeTextForStorage(detail, 'detail');
|
|
890
|
+
}
|
|
891
|
+
if (detail === null || typeof detail === 'number' || typeof detail === 'boolean') {
|
|
892
|
+
return detail;
|
|
893
|
+
}
|
|
894
|
+
return summarizeStructuredValueForStorage(detail, maxChars);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function summarizeStructuredValueForStorage(value, maxChars) {
|
|
898
|
+
const serialized = safeJsonStringify(value);
|
|
899
|
+
const summary = {
|
|
900
|
+
redacted: true,
|
|
901
|
+
type: Array.isArray(value) ? 'array' : 'object',
|
|
902
|
+
chars: serialized.length,
|
|
903
|
+
hash: hashString(serialized)
|
|
904
|
+
};
|
|
905
|
+
const paths = collectPathMetadata(value, maxChars);
|
|
906
|
+
if (paths.items.length) {
|
|
907
|
+
summary.paths = paths.items;
|
|
908
|
+
summary.pathCount = paths.count;
|
|
909
|
+
}
|
|
910
|
+
return summary;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function summarizeTextForStorage(value, label) {
|
|
914
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
915
|
+
return '';
|
|
916
|
+
}
|
|
917
|
+
const text = redactSecretLikeText(value);
|
|
918
|
+
if (isOmittedStorageSummary(text)) {
|
|
919
|
+
return text;
|
|
920
|
+
}
|
|
921
|
+
return `[${label} omitted; chars=${text.length}; hash=${hashString(text)}]`;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function isOmittedStorageSummary(value) {
|
|
925
|
+
return /^\[[^\]]+ omitted; chars=\d+; hash=[a-f0-9]{8}\]$/.test(String(value || '').trim());
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function collectPathMetadata(value, maxChars, state = { seen: new Set(), items: [], count: 0 }) {
|
|
929
|
+
if (!value || typeof value !== 'object') {
|
|
930
|
+
return state;
|
|
931
|
+
}
|
|
932
|
+
if (Array.isArray(value)) {
|
|
933
|
+
for (const item of value) {
|
|
934
|
+
collectPathMetadata(item, maxChars, state);
|
|
935
|
+
}
|
|
936
|
+
return state;
|
|
937
|
+
}
|
|
938
|
+
for (const key of Object.keys(value)) {
|
|
939
|
+
const item = value[key];
|
|
940
|
+
if (/(^|[A-Z_])path$/i.test(key) && typeof item === 'string') {
|
|
941
|
+
const path = redactSecretLikeText(item).replace(/\\/g, '/').replace(/^\/+/, '').trim();
|
|
942
|
+
if (path) {
|
|
943
|
+
state.count += 1;
|
|
944
|
+
if (!state.seen.has(path) && state.items.length < 5) {
|
|
945
|
+
state.seen.add(path);
|
|
946
|
+
state.items.push(normalizeTextField(path, Math.min(maxChars || 240, 240)));
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
} else {
|
|
950
|
+
collectPathMetadata(item, maxChars, state);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return state;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function compactUndoPayload(run, limits, keepUndoPayload) {
|
|
957
|
+
return {
|
|
958
|
+
undoOperations: [],
|
|
959
|
+
undoBaseFiles: [],
|
|
960
|
+
undoTrackedChanges: [],
|
|
961
|
+
undoExpectedFiles: []
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function normalizeMode(mode) {
|
|
966
|
+
return VALID_MODES.has(mode) ? mode : DEFAULT_PANEL_STATE.mode;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function normalizeReasoning(reasoningEffort) {
|
|
970
|
+
return VALID_REASONING.has(reasoningEffort)
|
|
971
|
+
? reasoningEffort
|
|
972
|
+
: DEFAULT_PANEL_STATE.reasoningEffort;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function normalizeSpeedTier(speedTier) {
|
|
976
|
+
return VALID_SPEED_TIERS.has(speedTier)
|
|
977
|
+
? speedTier
|
|
978
|
+
: DEFAULT_PANEL_STATE.speedTier;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function normalizeLocale(locale) {
|
|
982
|
+
return VALID_LOCALES.has(locale) ? locale : DEFAULT_PANEL_STATE.locale;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function normalizePanelWidth(value) {
|
|
986
|
+
const width = Number(value);
|
|
987
|
+
if (!Number.isFinite(width) || width <= 0) {
|
|
988
|
+
return DEFAULT_PANEL_STATE.panelWidth;
|
|
989
|
+
}
|
|
990
|
+
return Math.round(Math.min(760, Math.max(340, width)));
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
function normalizeTextField(value, maxChars) {
|
|
994
|
+
const text = typeof value === 'string' ? redactSecretLikeText(value) : '';
|
|
995
|
+
if (!Number.isFinite(maxChars) || maxChars <= 0 || text.length <= maxChars) {
|
|
996
|
+
return text;
|
|
997
|
+
}
|
|
998
|
+
return `${text.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function redactSecretLikeText(value) {
|
|
1002
|
+
if (typeof value !== 'string') {
|
|
1003
|
+
return '';
|
|
1004
|
+
}
|
|
1005
|
+
let redacted = value;
|
|
1006
|
+
for (const pattern of SECRET_REDACTION_PATTERNS) {
|
|
1007
|
+
pattern.lastIndex = 0;
|
|
1008
|
+
redacted = redacted.replace(pattern, REDACTED_SECRET);
|
|
1009
|
+
}
|
|
1010
|
+
return redacted;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function containsSecretLikeText(value) {
|
|
1014
|
+
if (typeof value === 'string') {
|
|
1015
|
+
return SECRET_REDACTION_PATTERNS.some(pattern => {
|
|
1016
|
+
pattern.lastIndex = 0;
|
|
1017
|
+
return pattern.test(value);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
if (Array.isArray(value)) {
|
|
1021
|
+
return value.some(containsSecretLikeText);
|
|
1022
|
+
}
|
|
1023
|
+
if (value && typeof value === 'object') {
|
|
1024
|
+
return Object.values(value).some(containsSecretLikeText);
|
|
1025
|
+
}
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function safeJsonStringify(value) {
|
|
1030
|
+
try {
|
|
1031
|
+
return JSON.stringify(value) || '';
|
|
1032
|
+
} catch (_error) {
|
|
1033
|
+
return '[unserializable]';
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function safeJsonClone(value) {
|
|
1038
|
+
try {
|
|
1039
|
+
return JSON.parse(JSON.stringify(value));
|
|
1040
|
+
} catch (_error) {
|
|
1041
|
+
return [];
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function hashString(value) {
|
|
1046
|
+
let hash = 2166136261;
|
|
1047
|
+
const text = String(value || '');
|
|
1048
|
+
for (let i = 0; i < text.length; i++) {
|
|
1049
|
+
hash ^= text.charCodeAt(i);
|
|
1050
|
+
hash = Math.imul(hash, 16777619);
|
|
1051
|
+
}
|
|
1052
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function estimateJsonBytes(value) {
|
|
1056
|
+
let serialized = '';
|
|
1057
|
+
try {
|
|
1058
|
+
serialized = JSON.stringify(value);
|
|
1059
|
+
} catch (_error) {
|
|
1060
|
+
return Infinity;
|
|
1061
|
+
}
|
|
1062
|
+
if (typeof Buffer !== 'undefined') {
|
|
1063
|
+
return Buffer.byteLength(serialized, 'utf8');
|
|
1064
|
+
}
|
|
1065
|
+
return new Blob([serialized]).size;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
return {
|
|
1069
|
+
DEFAULT_PANEL_STATE,
|
|
1070
|
+
createSession,
|
|
1071
|
+
deleteSession,
|
|
1072
|
+
getActiveSession,
|
|
1073
|
+
isDisplayableSession,
|
|
1074
|
+
normalizePanelState,
|
|
1075
|
+
deriveSessionTitle,
|
|
1076
|
+
recordSessionResult,
|
|
1077
|
+
selectVisibleSessionsForList,
|
|
1078
|
+
setActiveSession,
|
|
1079
|
+
updateActiveSession,
|
|
1080
|
+
normalizeRuns,
|
|
1081
|
+
prepareStateForStorage,
|
|
1082
|
+
estimateJsonBytes
|
|
1083
|
+
};
|
|
1084
|
+
});
|