@keyou007/agent-bot 0.1.20-alpha.1 → 0.1.21
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/CHANGELOG.md +21 -0
- package/README.md +27 -4
- package/README.zh.md +23 -4
- package/dist/cli/AgentPrerequisites.js +11 -47
- package/dist/cli/AgentPrerequisites.js.map +1 -1
- package/dist/cli/DefaultAgentSelection.js +1 -1
- package/dist/cli/DefaultAgentSelection.js.map +1 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexEventMapper.js +21 -1
- package/dist/codex/CodexEventMapper.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +32 -10
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +56 -7
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +361 -168
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/CodexVersion.js +18 -0
- package/dist/codex/CodexVersion.js.map +1 -0
- package/dist/codex/ProviderModels.js +159 -0
- package/dist/codex/ProviderModels.js.map +1 -0
- package/dist/codex/ProviderSwitch.js +32 -0
- package/dist/codex/ProviderSwitch.js.map +1 -0
- package/dist/commands/CommandRouter.js +8 -3
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +2 -1
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +82 -27
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +17 -5
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +34 -8
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/InboundText.js +11 -0
- package/dist/feishu/InboundText.js.map +1 -1
- package/dist/feishu/MergedForwardMessage.js +17 -5
- package/dist/feishu/MergedForwardMessage.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +164 -19
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/MarkdownFileLinks.js +65 -0
- package/dist/local-files/MarkdownFileLinks.js.map +1 -0
- package/dist/local-files/TurnPreviewPage.js +725 -0
- package/dist/local-files/TurnPreviewPage.js.map +1 -0
- package/dist/presentation/OutboundRouter.js +6 -1
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +142 -3
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +573 -325
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +65 -65
- package/dist/state/StateStore.js.map +1 -1
- package/dist/utils/formatStorageSize.js +15 -0
- package/dist/utils/formatStorageSize.js.map +1 -0
- package/dist/utils/semanticVersion.js +39 -0
- package/dist/utils/semanticVersion.js.map +1 -0
- package/docs/technical-reference.md +11 -3
- package/docs/technical-reference.zh.md +11 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +11 -1
- package/src/cli/AgentPrerequisites.ts +14 -40
- package/src/cli/DefaultAgentSelection.ts +1 -1
- package/src/cli.ts +13 -0
- package/src/codex/CodexEventMapper.ts +31 -1
- package/src/codex/CodexLocalActivityDetector.ts +41 -9
- package/src/codex/CodexProcessManager.ts +48 -7
- package/src/codex/CodexRuntime.ts +397 -204
- package/src/codex/CodexVersion.ts +18 -0
- package/src/codex/ProviderModels.ts +185 -0
- package/src/codex/ProviderSwitch.ts +36 -0
- package/src/commands/CommandRouter.ts +7 -2
- package/src/commands/commandTypes.ts +1 -1
- package/src/console/ConsoleTurnPresenter.ts +3 -0
- package/src/feishu/CardRenderer.ts +84 -43
- package/src/feishu/FeishuConnector.ts +15 -5
- package/src/feishu/FeishuTurnPresenter.ts +46 -4
- package/src/feishu/InboundText.ts +11 -0
- package/src/feishu/MergedForwardMessage.ts +15 -5
- package/src/index.ts +3 -1
- package/src/local-files/LocalFileViewerServer.ts +179 -20
- package/src/local-files/MarkdownFileLinks.ts +64 -0
- package/src/local-files/TurnPreviewPage.ts +792 -0
- package/src/presentation/OutboundRouter.ts +8 -0
- package/src/presentation/TurnStateReducer.ts +175 -2
- package/src/presentation/turnViewTypes.ts +13 -0
- package/src/proxy/ProxySessionController.ts +547 -297
- package/src/runtime/types.ts +33 -3
- package/src/state/StateStore.ts +86 -90
- package/src/utils/formatStorageSize.ts +13 -0
- package/src/utils/semanticVersion.ts +28 -0
package/src/runtime/types.ts
CHANGED
|
@@ -73,6 +73,17 @@ export type AgentEvent =
|
|
|
73
73
|
turnId: string;
|
|
74
74
|
lastTokens: number;
|
|
75
75
|
cumulativeTokens: number;
|
|
76
|
+
contextTokens?: number;
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
type: "context_compaction";
|
|
80
|
+
sessionId: string;
|
|
81
|
+
turnId: string;
|
|
82
|
+
phase: "started" | "completed";
|
|
83
|
+
compactionId?: string;
|
|
84
|
+
timestampMs?: number;
|
|
85
|
+
turnCount?: number;
|
|
86
|
+
storageBytes?: number;
|
|
76
87
|
}
|
|
77
88
|
| {
|
|
78
89
|
type: "progress";
|
|
@@ -113,6 +124,11 @@ export interface RemoteCompletedTurnSummary {
|
|
|
113
124
|
completedAt?: number;
|
|
114
125
|
}
|
|
115
126
|
|
|
127
|
+
export interface RemoteTurnPage {
|
|
128
|
+
turns: RemoteCompletedTurnSummary[];
|
|
129
|
+
nextCursor?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
116
132
|
export interface RemoteSessionSummary {
|
|
117
133
|
id: string;
|
|
118
134
|
title?: string;
|
|
@@ -151,6 +167,11 @@ export interface RemoteSessionActivity {
|
|
|
151
167
|
activeTurnId?: string;
|
|
152
168
|
}
|
|
153
169
|
|
|
170
|
+
export interface RemoteSessionMetrics {
|
|
171
|
+
turnCount: number;
|
|
172
|
+
storageBytes?: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
154
175
|
export interface RuntimeSession {
|
|
155
176
|
localSessionId: string;
|
|
156
177
|
remoteSessionId: string;
|
|
@@ -163,6 +184,8 @@ export interface RuntimeSession {
|
|
|
163
184
|
reasoningEffort?: string;
|
|
164
185
|
permissionMode: PermissionMode;
|
|
165
186
|
activeTurnId?: string;
|
|
187
|
+
// Returned by reconciliation so status callers can reuse the same observation.
|
|
188
|
+
remoteSummary?: RemoteSessionSummary;
|
|
166
189
|
}
|
|
167
190
|
|
|
168
191
|
export interface CreateRuntimeSessionInput {
|
|
@@ -229,8 +252,10 @@ export interface AgentRuntime {
|
|
|
229
252
|
getSession(localSessionId: string): RuntimeSession | undefined;
|
|
230
253
|
readSessionMetadata(remoteSessionId: string): Promise<RuntimeSessionMetadata>;
|
|
231
254
|
listRemoteSessions?(input?: { searchTerm?: string; cursor?: string; limit?: number }): Promise<RemoteSessionPage>;
|
|
232
|
-
readRemoteSession?(remoteSessionId: string): Promise<RemoteSessionSummary>;
|
|
255
|
+
readRemoteSession?(remoteSessionId: string, view?: "metadata" | "latest" | "latest-full"): Promise<RemoteSessionSummary>;
|
|
256
|
+
readRemoteSessionMetrics?(remoteSessionId: string): Promise<RemoteSessionMetrics>;
|
|
233
257
|
readRemoteForkSource?(remoteSessionId: string): Promise<RemoteSessionSummary>;
|
|
258
|
+
listRemoteTurnSummaries?(remoteSessionId: string, input: { cursor?: string; limit: number }): Promise<RemoteTurnPage>;
|
|
234
259
|
inspectRemoteSessionActivity?(remoteSessionId: string): Promise<RemoteSessionActivity>;
|
|
235
260
|
synchronizeSession(sessionId: string): Promise<RuntimeSession>;
|
|
236
261
|
startTurn(sessionId: string, prompt: RuntimePrompt): Promise<string>;
|
|
@@ -246,9 +271,14 @@ export interface AgentRuntime {
|
|
|
246
271
|
setModel(sessionId: string, model: string): Promise<void>;
|
|
247
272
|
setReasoningEffort(sessionId: string, effort: string): Promise<void>;
|
|
248
273
|
setPermissionMode(sessionId: string, mode: PermissionMode): Promise<void>;
|
|
249
|
-
setExecutionSettings?(
|
|
274
|
+
setExecutionSettings?(
|
|
275
|
+
sessionId: string,
|
|
276
|
+
settings: RuntimeExecutionSettings,
|
|
277
|
+
// Called after remote verification, before releasing the task's operation lock.
|
|
278
|
+
persist?: (session: RuntimeSession) => Promise<void>,
|
|
279
|
+
): Promise<RuntimeSession>;
|
|
250
280
|
respondToApproval(sessionId: string, requestId: string, decision: ApprovalDecision): Promise<void>;
|
|
251
|
-
listModels(): Promise<ModelOption[]>;
|
|
281
|
+
listModels(modelProvider?: string, fallbackModel?: string): Promise<ModelOption[]>;
|
|
252
282
|
listModelProviders?(): Promise<ModelProviderOption[]>;
|
|
253
283
|
release?(options?: { force?: boolean }): Promise<RuntimeReleaseResult>;
|
|
254
284
|
onEvent(listener: (event: RuntimeEvent) => void): () => void;
|
package/src/state/StateStore.ts
CHANGED
|
@@ -95,6 +95,21 @@ export interface CompletedTurnSnapshotRecord {
|
|
|
95
95
|
updatedAt: string;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
export interface CompletedTurnIndexRecord {
|
|
99
|
+
turnId: string;
|
|
100
|
+
parentTurnId?: string;
|
|
101
|
+
timestamp: number;
|
|
102
|
+
updatedAt: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface TurnPromptSummary {
|
|
106
|
+
status?: string;
|
|
107
|
+
prompt?: string;
|
|
108
|
+
taskTitle?: string;
|
|
109
|
+
startedAt?: number;
|
|
110
|
+
completedAt?: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
98
113
|
export interface ImportedCompletedTurnRecord {
|
|
99
114
|
turnId: string;
|
|
100
115
|
snapshot: unknown;
|
|
@@ -1032,6 +1047,21 @@ export class StateStore {
|
|
|
1032
1047
|
return row ? JSON.parse(row.snapshot_json) : undefined;
|
|
1033
1048
|
}
|
|
1034
1049
|
|
|
1050
|
+
getTurnPromptSummary(turnId: string): TurnPromptSummary | undefined {
|
|
1051
|
+
const row = this.db.prepare(`SELECT
|
|
1052
|
+
json_extract(snapshot_json, '$.status') AS status,
|
|
1053
|
+
json_extract(snapshot_json, '$.prompt') AS prompt,
|
|
1054
|
+
json_extract(snapshot_json, '$.taskTitle') AS taskTitle,
|
|
1055
|
+
json_extract(snapshot_json, '$.startedAt') AS startedAt,
|
|
1056
|
+
json_extract(snapshot_json, '$.completedAt') AS completedAt
|
|
1057
|
+
FROM turn_snapshots WHERE turn_id = ?`).get(turnId) as TurnPromptSummary | undefined;
|
|
1058
|
+
return row ? {
|
|
1059
|
+
status: row.status ?? undefined, prompt: row.prompt ?? undefined,
|
|
1060
|
+
taskTitle: row.taskTitle ?? undefined, startedAt: row.startedAt ?? undefined,
|
|
1061
|
+
completedAt: row.completedAt ?? undefined,
|
|
1062
|
+
} : undefined;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1035
1065
|
findLatestTurnSnapshotForSession(localSessionId: string): PersistedTurnSnapshotRecord | undefined {
|
|
1036
1066
|
const row = this.db.prepare(`
|
|
1037
1067
|
SELECT turn_id, local_session_id, context_key, snapshot_json, updated_at
|
|
@@ -1096,16 +1126,16 @@ export class StateStore {
|
|
|
1096
1126
|
: undefined;
|
|
1097
1127
|
}
|
|
1098
1128
|
|
|
1099
|
-
saveTurnParent(turnId: string, localSessionId: string, parentTurnId?: string): void {
|
|
1129
|
+
saveTurnParent(turnId: string, localSessionId: string, parentTurnId?: string, replaceExisting = false): void {
|
|
1100
1130
|
this.db.prepare(`
|
|
1101
1131
|
INSERT INTO turn_parent_links (
|
|
1102
1132
|
turn_id, local_session_id, parent_turn_id, created_at
|
|
1103
1133
|
) VALUES (?, ?, ?, ?)
|
|
1104
1134
|
ON CONFLICT(turn_id) DO UPDATE SET
|
|
1105
1135
|
parent_turn_id = excluded.parent_turn_id
|
|
1106
|
-
WHERE turn_parent_links.parent_turn_id IS NULL
|
|
1136
|
+
WHERE (turn_parent_links.parent_turn_id IS NULL OR ?)
|
|
1107
1137
|
AND excluded.parent_turn_id IS NOT NULL
|
|
1108
|
-
`).run(turnId, localSessionId, parentTurnId ?? null, new Date().toISOString());
|
|
1138
|
+
`).run(turnId, localSessionId, parentTurnId ?? null, new Date().toISOString(), Number(replaceExisting));
|
|
1109
1139
|
}
|
|
1110
1140
|
|
|
1111
1141
|
importCompletedTurnHistory(input: {
|
|
@@ -1114,6 +1144,7 @@ export class StateStore {
|
|
|
1114
1144
|
agentName: string;
|
|
1115
1145
|
remoteSessionId: string;
|
|
1116
1146
|
turns: ImportedCompletedTurnRecord[];
|
|
1147
|
+
replaceParents?: boolean;
|
|
1117
1148
|
}): void {
|
|
1118
1149
|
const importHistory = this.db.transaction(() => {
|
|
1119
1150
|
let parentTurnId: string | undefined;
|
|
@@ -1140,7 +1171,7 @@ export class StateStore {
|
|
|
1140
1171
|
input.remoteSessionId,
|
|
1141
1172
|
turn.updatedAt,
|
|
1142
1173
|
);
|
|
1143
|
-
this.saveTurnParent(turn.turnId, input.localSessionId, parentTurnId);
|
|
1174
|
+
this.saveTurnParent(turn.turnId, input.localSessionId, parentTurnId, input.replaceParents);
|
|
1144
1175
|
parentTurnId = turn.turnId;
|
|
1145
1176
|
}
|
|
1146
1177
|
});
|
|
@@ -1156,13 +1187,13 @@ export class StateStore {
|
|
|
1156
1187
|
return row?.parent_turn_id ?? undefined;
|
|
1157
1188
|
}
|
|
1158
1189
|
|
|
1159
|
-
findLatestCompletedTurnId(localSessionId: string, contextKey
|
|
1190
|
+
findLatestCompletedTurnId(localSessionId: string, contextKey?: string): string | undefined {
|
|
1160
1191
|
const row = this.db
|
|
1161
1192
|
.prepare(`
|
|
1162
1193
|
SELECT turn_id
|
|
1163
1194
|
FROM turn_snapshots
|
|
1164
1195
|
WHERE local_session_id = ?
|
|
1165
|
-
AND context_key = ?
|
|
1196
|
+
AND (? IS NULL OR context_key = ?)
|
|
1166
1197
|
AND json_extract(snapshot_json, '$.status') = 'completed'
|
|
1167
1198
|
ORDER BY
|
|
1168
1199
|
coalesce(
|
|
@@ -1173,7 +1204,7 @@ export class StateStore {
|
|
|
1173
1204
|
updated_at DESC
|
|
1174
1205
|
LIMIT 1
|
|
1175
1206
|
`)
|
|
1176
|
-
.get(localSessionId, contextKey) as { turn_id: string } | undefined;
|
|
1207
|
+
.get(localSessionId, contextKey ?? null, contextKey ?? null) as { turn_id: string } | undefined;
|
|
1177
1208
|
return row?.turn_id;
|
|
1178
1209
|
}
|
|
1179
1210
|
|
|
@@ -1296,23 +1327,60 @@ export class StateStore {
|
|
|
1296
1327
|
}
|
|
1297
1328
|
|
|
1298
1329
|
listTaskTurnGraph(localSessionId: string): CompletedTurnSnapshotRecord[] {
|
|
1299
|
-
this.
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1330
|
+
return this.listTaskTurnGraphIndex(localSessionId).map((record) => ({
|
|
1331
|
+
...record, snapshot: this.getTurnSnapshot(record.turnId),
|
|
1332
|
+
}));
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
countTaskTurns(localSessionId: string): number {
|
|
1336
|
+
const turnIds = new Set(this.listTaskTurnGraphIndex(localSessionId).map((turn) => turn.turnId));
|
|
1337
|
+
const own = this.db.prepare(
|
|
1338
|
+
"SELECT turn_id AS turnId FROM turn_snapshots WHERE local_session_id = ?",
|
|
1339
|
+
).all(localSessionId) as Array<{ turnId: string }>;
|
|
1340
|
+
for (const turn of own) turnIds.add(turn.turnId);
|
|
1341
|
+
return turnIds.size;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
listTaskTurnGraphIndex(localSessionId: string): CompletedTurnIndexRecord[] {
|
|
1345
|
+
const backfilled = new Set<string>();
|
|
1346
|
+
const backfill = (id: string): void => {
|
|
1347
|
+
if (backfilled.has(id)) return;
|
|
1348
|
+
this.backfillTurnParents(id);
|
|
1349
|
+
backfilled.add(id);
|
|
1350
|
+
};
|
|
1351
|
+
backfill(localSessionId);
|
|
1352
|
+
const projection = `SELECT ts.turn_id AS turnId, tpl.parent_turn_id AS parentTurnId,
|
|
1353
|
+
ts.local_session_id AS localSessionId, ts.updated_at AS updatedAt,
|
|
1354
|
+
coalesce(json_extract(ts.snapshot_json, '$.completedAt'),
|
|
1355
|
+
json_extract(ts.snapshot_json, '$.startedAt'),
|
|
1356
|
+
cast(strftime('%s', ts.updated_at) AS INTEGER) * 1000, 0) AS timestamp
|
|
1357
|
+
FROM turn_snapshots ts LEFT JOIN turn_parent_links tpl ON tpl.turn_id = ts.turn_id
|
|
1358
|
+
WHERE json_extract(ts.snapshot_json, '$.status') = 'completed'`;
|
|
1359
|
+
type Row = CompletedTurnIndexRecord & { localSessionId: string; parentTurnId: string | undefined };
|
|
1360
|
+
const normalize = (row: Row): CompletedTurnIndexRecord => ({
|
|
1361
|
+
turnId: row.turnId, parentTurnId: row.parentTurnId ?? undefined,
|
|
1362
|
+
timestamp: row.timestamp, updatedAt: row.updatedAt,
|
|
1363
|
+
});
|
|
1364
|
+
const own = this.db.prepare(`${projection} AND ts.local_session_id = ?`).all(localSessionId) as Row[];
|
|
1365
|
+
const read = this.db.prepare(`${projection} AND ts.turn_id = ?`);
|
|
1366
|
+
const records = new Map(own.map((row) => [row.turnId, normalize(row)]));
|
|
1303
1367
|
let ancestorTurnId = this.findForkSourceTurnId(localSessionId)
|
|
1304
1368
|
?? this.findCrossSessionParentTurnId(localSessionId);
|
|
1305
1369
|
const visited = new Set<string>();
|
|
1306
1370
|
|
|
1307
1371
|
while (ancestorTurnId && !visited.has(ancestorTurnId)) {
|
|
1308
1372
|
visited.add(ancestorTurnId);
|
|
1309
|
-
|
|
1373
|
+
let ancestor = read.get(ancestorTurnId) as Row | undefined;
|
|
1310
1374
|
if (!ancestor) break;
|
|
1311
|
-
|
|
1375
|
+
if (!backfilled.has(ancestor.localSessionId)) {
|
|
1376
|
+
backfill(ancestor.localSessionId);
|
|
1377
|
+
ancestor = read.get(ancestorTurnId) as Row;
|
|
1378
|
+
}
|
|
1379
|
+
records.set(ancestor.turnId, normalize(ancestor));
|
|
1312
1380
|
ancestorTurnId = ancestor.parentTurnId;
|
|
1313
1381
|
}
|
|
1314
1382
|
|
|
1315
|
-
return [...records.values()].sort(
|
|
1383
|
+
return [...records.values()].sort((a, b) => b.timestamp - a.timestamp || b.updatedAt.localeCompare(a.updatedAt));
|
|
1316
1384
|
}
|
|
1317
1385
|
|
|
1318
1386
|
getForkHistorySource(localSessionId: string): ForkHistorySourceRecord | undefined {
|
|
@@ -1340,54 +1408,16 @@ export class StateStore {
|
|
|
1340
1408
|
};
|
|
1341
1409
|
}
|
|
1342
1410
|
|
|
1343
|
-
hasImportedForkTurnHistory(localSessionId: string): boolean {
|
|
1411
|
+
hasImportedForkTurnHistory(localSessionId: string, sourceRemoteSessionId?: string, sourceTurnId?: string): boolean {
|
|
1344
1412
|
return Boolean(this.db.prepare(`
|
|
1345
1413
|
SELECT 1
|
|
1346
1414
|
FROM audit_events
|
|
1347
1415
|
WHERE event_type = 'fork_turn_history_imported'
|
|
1348
1416
|
AND json_extract(payload_json, '$.forkedLocalSessionId') = ?
|
|
1417
|
+
AND (? IS NULL OR json_extract(payload_json, '$.sourceRemoteSessionId') = ?)
|
|
1418
|
+
AND (? IS NULL OR json_extract(payload_json, '$.sourceTurnId') = ?)
|
|
1349
1419
|
LIMIT 1
|
|
1350
|
-
`).get(localSessionId));
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
private listCompletedTurnGraphRows(localSessionId: string): CompletedTurnSnapshotRecord[] {
|
|
1354
|
-
const rows = this.db.prepare(`
|
|
1355
|
-
SELECT ts.turn_id, tpl.parent_turn_id, ts.snapshot_json, ts.updated_at
|
|
1356
|
-
FROM turn_snapshots ts
|
|
1357
|
-
LEFT JOIN turn_parent_links tpl ON tpl.turn_id = ts.turn_id
|
|
1358
|
-
WHERE ts.local_session_id = ?
|
|
1359
|
-
AND json_extract(ts.snapshot_json, '$.status') = 'completed'
|
|
1360
|
-
`).all(localSessionId) as Array<{
|
|
1361
|
-
turn_id: string;
|
|
1362
|
-
parent_turn_id: string | null;
|
|
1363
|
-
snapshot_json: string;
|
|
1364
|
-
updated_at: string;
|
|
1365
|
-
}>;
|
|
1366
|
-
return rows.map(mapCompletedTurnSnapshot);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
private getCompletedTurnGraphRecord(turnId: string): CompletedTurnSnapshotRecord | undefined {
|
|
1370
|
-
const owner = this.db.prepare(`
|
|
1371
|
-
SELECT local_session_id
|
|
1372
|
-
FROM turn_snapshots
|
|
1373
|
-
WHERE turn_id = ?
|
|
1374
|
-
AND json_extract(snapshot_json, '$.status') = 'completed'
|
|
1375
|
-
`).get(turnId) as { local_session_id: string } | undefined;
|
|
1376
|
-
if (!owner) return undefined;
|
|
1377
|
-
this.backfillTurnParents(owner.local_session_id);
|
|
1378
|
-
const row = this.db.prepare(`
|
|
1379
|
-
SELECT ts.turn_id, tpl.parent_turn_id, ts.snapshot_json, ts.updated_at
|
|
1380
|
-
FROM turn_snapshots ts
|
|
1381
|
-
LEFT JOIN turn_parent_links tpl ON tpl.turn_id = ts.turn_id
|
|
1382
|
-
WHERE ts.turn_id = ?
|
|
1383
|
-
AND json_extract(ts.snapshot_json, '$.status') = 'completed'
|
|
1384
|
-
`).get(turnId) as {
|
|
1385
|
-
turn_id: string;
|
|
1386
|
-
parent_turn_id: string | null;
|
|
1387
|
-
snapshot_json: string;
|
|
1388
|
-
updated_at: string;
|
|
1389
|
-
} | undefined;
|
|
1390
|
-
return row ? mapCompletedTurnSnapshot(row) : undefined;
|
|
1420
|
+
`).get(localSessionId, sourceRemoteSessionId ?? null, sourceRemoteSessionId ?? null, sourceTurnId ?? null, sourceTurnId ?? null));
|
|
1391
1421
|
}
|
|
1392
1422
|
|
|
1393
1423
|
private findForkSourceTurnId(localSessionId: string): string | undefined {
|
|
@@ -2289,40 +2319,6 @@ function mapTurnAttempt(row: TurnAttemptRow): TurnAttemptRecord {
|
|
|
2289
2319
|
};
|
|
2290
2320
|
}
|
|
2291
2321
|
|
|
2292
|
-
function mapCompletedTurnSnapshot(row: {
|
|
2293
|
-
turn_id: string;
|
|
2294
|
-
parent_turn_id: string | null;
|
|
2295
|
-
snapshot_json: string;
|
|
2296
|
-
updated_at: string;
|
|
2297
|
-
}): CompletedTurnSnapshotRecord {
|
|
2298
|
-
return {
|
|
2299
|
-
turnId: row.turn_id,
|
|
2300
|
-
parentTurnId: row.parent_turn_id ?? undefined,
|
|
2301
|
-
snapshot: JSON.parse(row.snapshot_json),
|
|
2302
|
-
updatedAt: row.updated_at,
|
|
2303
|
-
};
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
function compareCompletedTurnSnapshots(
|
|
2307
|
-
left: CompletedTurnSnapshotRecord,
|
|
2308
|
-
right: CompletedTurnSnapshotRecord,
|
|
2309
|
-
): number {
|
|
2310
|
-
const timeDifference = completedTurnTimestamp(right) - completedTurnTimestamp(left);
|
|
2311
|
-
return timeDifference || right.updatedAt.localeCompare(left.updatedAt);
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
function completedTurnTimestamp(record: CompletedTurnSnapshotRecord): number {
|
|
2315
|
-
const snapshot = record.snapshot && typeof record.snapshot === "object"
|
|
2316
|
-
? record.snapshot as { completedAt?: unknown; startedAt?: unknown }
|
|
2317
|
-
: undefined;
|
|
2318
|
-
const timestamp = typeof snapshot?.completedAt === "number"
|
|
2319
|
-
? snapshot.completedAt
|
|
2320
|
-
: typeof snapshot?.startedAt === "number"
|
|
2321
|
-
? snapshot.startedAt
|
|
2322
|
-
: Date.parse(record.updatedAt);
|
|
2323
|
-
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
2322
|
function stringValue(value: unknown): string | undefined {
|
|
2327
2323
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
2328
2324
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function formatStorageSize(bytes: number): string {
|
|
2
|
+
const normalized = Math.max(0, Math.round(bytes));
|
|
3
|
+
if (normalized < 1_024) return `${normalized} B`;
|
|
4
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
5
|
+
let value = normalized / 1_024;
|
|
6
|
+
let unitIndex = 0;
|
|
7
|
+
while (value >= 1_024 && unitIndex < units.length - 1) {
|
|
8
|
+
value /= 1_024;
|
|
9
|
+
unitIndex += 1;
|
|
10
|
+
}
|
|
11
|
+
const precision = value >= 100 ? 0 : value >= 10 ? 1 : 2;
|
|
12
|
+
return `${Number(value.toFixed(precision))} ${units[unitIndex]}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function compareSemanticVersions(left: string, right: string): number {
|
|
2
|
+
const parse = (value: string) => /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value);
|
|
3
|
+
const a = parse(left);
|
|
4
|
+
const b = parse(right);
|
|
5
|
+
if (!a || !b) return 0;
|
|
6
|
+
for (let i = 1; i <= 3; i += 1) {
|
|
7
|
+
const difference = Number(a[i]) - Number(b[i]);
|
|
8
|
+
if (difference) return difference;
|
|
9
|
+
}
|
|
10
|
+
if (!a[4] && !b[4]) return 0;
|
|
11
|
+
if (!a[4]) return 1;
|
|
12
|
+
if (!b[4]) return -1;
|
|
13
|
+
const aParts = a[4].split(".");
|
|
14
|
+
const bParts = b[4].split(".");
|
|
15
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i += 1) {
|
|
16
|
+
const x = aParts[i];
|
|
17
|
+
const y = bParts[i];
|
|
18
|
+
if (x === undefined) return -1;
|
|
19
|
+
if (y === undefined) return 1;
|
|
20
|
+
if (x === y) continue;
|
|
21
|
+
const nx = /^\d+$/u.test(x);
|
|
22
|
+
const ny = /^\d+$/u.test(y);
|
|
23
|
+
if (nx && ny) return Number(x) - Number(y);
|
|
24
|
+
if (nx !== ny) return nx ? -1 : 1;
|
|
25
|
+
return x.localeCompare(y);
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|