aicodeman 1.5.1 → 1.6.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/README.md +48 -12
- package/README.zh-CN.md +68 -12
- package/dist/remote-hosts.d.ts +61 -1
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +142 -0
- package/dist/remote-hosts.js.map +1 -1
- package/dist/remote-reconnect.d.ts +129 -0
- package/dist/remote-reconnect.d.ts.map +1 -0
- package/dist/remote-reconnect.js +119 -0
- package/dist/remote-reconnect.js.map +1 -0
- package/dist/services/unified-session-service.d.ts +13 -1
- package/dist/services/unified-session-service.d.ts.map +1 -1
- package/dist/services/unified-session-service.js +88 -3
- package/dist/services/unified-session-service.js.map +1 -1
- package/dist/session-order.d.ts +40 -0
- package/dist/session-order.d.ts.map +1 -0
- package/dist/session-order.js +67 -0
- package/dist/session-order.js.map +1 -0
- package/dist/session.d.ts +39 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +87 -19
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +12 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +33 -0
- package/dist/state-store.js.map +1 -1
- package/dist/tmux-manager.d.ts +66 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +226 -2
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/app-state.d.ts +2 -0
- package/dist/types/app-state.d.ts.map +1 -1
- package/dist/types/app-state.js.map +1 -1
- package/dist/types/session.d.ts +42 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/public/admin-ui.js +303 -6
- package/dist/web/public/admin-ui.js.br +0 -0
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.9367fd20.js +38 -0
- package/dist/web/public/app.9367fd20.js.br +0 -0
- package/dist/web/public/app.9367fd20.js.gz +0 -0
- package/dist/web/public/{constants.ad3a8031.js → constants.21a0a83e.js} +9 -0
- package/dist/web/public/constants.21a0a83e.js.br +0 -0
- package/dist/web/public/{constants.ad3a8031.js.gz → constants.21a0a83e.js.gz} +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +34 -11
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
- package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
- package/dist/web/public/mobile.6a53092b.css +1 -0
- package/dist/web/public/mobile.6a53092b.css.br +0 -0
- package/dist/web/public/mobile.6a53092b.css.gz +0 -0
- package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.3c9b0465.js → panels-ui.58582d4c.js} +2 -2
- package/dist/web/public/panels-ui.58582d4c.js.br +0 -0
- package/dist/web/public/panels-ui.58582d4c.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/{session-ui.e5d4f9b5.js → session-ui.0cf86dd4.js} +30 -22
- package/dist/web/public/session-ui.0cf86dd4.js.br +0 -0
- package/dist/web/public/session-ui.0cf86dd4.js.gz +0 -0
- package/dist/web/public/{settings-ui.29214cf1.js → settings-ui.bc5f4792.js} +2 -2
- package/dist/web/public/settings-ui.bc5f4792.js.br +0 -0
- package/dist/web/public/settings-ui.bc5f4792.js.gz +0 -0
- package/dist/web/public/styles.e9dc275f.css +1 -0
- package/dist/web/public/styles.e9dc275f.css.br +0 -0
- package/dist/web/public/styles.e9dc275f.css.gz +0 -0
- package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.9ea677e9.js +3 -0
- package/dist/web/public/terminal-ui.9ea677e9.js.br +0 -0
- package/dist/web/public/terminal-ui.9ea677e9.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/routes/admin-routes.d.ts +2 -0
- package/dist/web/routes/admin-routes.d.ts.map +1 -1
- package/dist/web/routes/admin-routes.js +91 -2
- package/dist/web/routes/admin-routes.js.map +1 -1
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +19 -1
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +140 -3
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +13 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +31 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +9 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +57 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +15 -0
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +18 -0
- package/dist/web/sse-events.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/app.1ccca158.js +0 -38
- package/dist/web/public/app.1ccca158.js.br +0 -0
- package/dist/web/public/app.1ccca158.js.gz +0 -0
- package/dist/web/public/constants.ad3a8031.js.br +0 -0
- package/dist/web/public/mobile.597136a6.css +0 -1
- package/dist/web/public/mobile.597136a6.css.br +0 -0
- package/dist/web/public/mobile.597136a6.css.gz +0 -0
- package/dist/web/public/panels-ui.3c9b0465.js.br +0 -0
- package/dist/web/public/panels-ui.3c9b0465.js.gz +0 -0
- package/dist/web/public/session-ui.e5d4f9b5.js.br +0 -0
- package/dist/web/public/session-ui.e5d4f9b5.js.gz +0 -0
- package/dist/web/public/settings-ui.29214cf1.js.br +0 -0
- package/dist/web/public/settings-ui.29214cf1.js.gz +0 -0
- package/dist/web/public/styles.9e6cdb9d.css +0 -1
- package/dist/web/public/styles.9e6cdb9d.css.br +0 -0
- package/dist/web/public/styles.9e6cdb9d.css.gz +0 -0
- package/dist/web/public/terminal-ui.efda6083.js +0 -3
- package/dist/web/public/terminal-ui.efda6083.js.br +0 -0
- package/dist/web/public/terminal-ui.efda6083.js.gz +0 -0
|
@@ -12,7 +12,8 @@ import { randomBytes } from 'node:crypto';
|
|
|
12
12
|
import { ApiErrorCode, createErrorResponse, getErrorMessage, } from '../../types.js';
|
|
13
13
|
import { Session, isAltScreenStripMode } from '../../session.js';
|
|
14
14
|
import { SseEvent } from '../sse-events.js';
|
|
15
|
-
import { CreateSessionSchema, SessionNameSchema, SessionColorSchema, RunPromptSchema, SessionInputWithLimitSchema, ResizeSchema, AutoClearSchema, AutoCompactSchema, AutoResumeSchema, ImageWatcherSchema, FlickerFilterSchema, QuickRunSchema, QuickStartSchema, InteractiveStartSchema, } from '../schemas.js';
|
|
15
|
+
import { CreateSessionSchema, SessionNameSchema, SessionColorSchema, RunPromptSchema, SessionInputWithLimitSchema, ResizeSchema, AutoClearSchema, AutoCompactSchema, AutoResumeSchema, PinSessionSchema, ImageWatcherSchema, FlickerFilterSchema, QuickRunSchema, QuickStartSchema, InteractiveStartSchema, SessionOrderUpdateSchema, } from '../schemas.js';
|
|
16
|
+
import { mergeSessionOrder } from '../../session-order.js';
|
|
16
17
|
import { autoConfigureRalph, canAccessOwned, CASES_DIR, findSessionOrFail, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, parseBody, persistAndBroadcastSession, resolveCasesDir, sessionCapacityMessage, SETTINGS_PATH, validatePathWithinBase, } from '../route-helpers.js';
|
|
17
18
|
import { canUsernameRunPrivilegedCommands, resolveClaudeModeForUsername } from '../../user-store.js';
|
|
18
19
|
import { isMultiUserMode } from '../../config/multiuser.js';
|
|
@@ -27,7 +28,7 @@ import { RunSummaryTracker } from '../../run-summary.js';
|
|
|
27
28
|
import { MAX_INPUT_LENGTH, MAX_SESSION_NAME_LENGTH } from '../../config/terminal-limits.js';
|
|
28
29
|
import { MAX_PASTE_IMAGE_BYTES } from '../../config/buffer-limits.js';
|
|
29
30
|
import { dataPath, getDataDir } from '../../config/instance.js';
|
|
30
|
-
import { checkRemoteTmuxAvailable, readRemoteCases, readRemoteHosts, toSessionRemote } from '../../remote-hosts.js';
|
|
31
|
+
import { checkRemoteTmuxAvailable, readRemoteCases, readRemoteHosts, toAttachedSessionRemote, toSessionRemote, } from '../../remote-hosts.js';
|
|
31
32
|
import { checkDockerAvailable, checkDockerConfigDrift, checkDockerTmuxAvailable, ensureAgentBaseImage, DEFAULT_AGENT_IMAGE, persistDockerCaseClaudeSessionId, readDockerCases, readDockerHosts, toSessionDocker, } from '../../docker-hosts.js';
|
|
32
33
|
import { LRUMap } from '../../utils/lru-map.js';
|
|
33
34
|
// Path to linked-cases registry (same file used by case-routes resolveCasePath)
|
|
@@ -244,6 +245,16 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
244
245
|
return list;
|
|
245
246
|
return list.filter((s) => canAccessOwned(user, s.owner));
|
|
246
247
|
});
|
|
248
|
+
// ========== Session Tab Order (global sync, COD-131) ==========
|
|
249
|
+
app.put('/api/session-order', async (req) => {
|
|
250
|
+
const { order } = parseBody(SessionOrderUpdateSchema, req.body, 'Invalid session order');
|
|
251
|
+
// Server is authoritative but never drops ids it knows about that the
|
|
252
|
+
// pushing device hadn't loaded yet — those fall to the end (mergeSessionOrder).
|
|
253
|
+
const merged = mergeSessionOrder(order, ctx.store.getSessionOrder());
|
|
254
|
+
ctx.store.setSessionOrder(merged);
|
|
255
|
+
ctx.broadcast(SseEvent.SessionOrderChanged, { order: merged });
|
|
256
|
+
return { success: true, data: { order: merged } };
|
|
257
|
+
});
|
|
247
258
|
// ========== Session Creation ==========
|
|
248
259
|
app.post('/api/sessions', async (req) => {
|
|
249
260
|
const owner = ownerFor(req);
|
|
@@ -252,7 +263,21 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
252
263
|
if (capMsg)
|
|
253
264
|
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, capMsg);
|
|
254
265
|
const body = parseBody(CreateSessionSchema, req.body);
|
|
255
|
-
|
|
266
|
+
let workingDir = body.workingDir || process.cwd();
|
|
267
|
+
let remote = undefined;
|
|
268
|
+
// COD-105 — attach to a discovered (non-owned) remote tmux session. The
|
|
269
|
+
// remote session is already running, so we skip the tmux-prereq probe and
|
|
270
|
+
// build a NON-owned SessionRemote (detach-not-kill on close). Remote CASE
|
|
271
|
+
// creation (owned durable sessions) is handled by the dedicated case-create
|
|
272
|
+
// endpoint below, which #145 consolidated remote-host resolution into.
|
|
273
|
+
if (body.attachRemoteSession) {
|
|
274
|
+
const { hostId, remoteSessionName } = body.attachRemoteSession;
|
|
275
|
+
const host = (await readRemoteHosts(CODEMAN_CONFIG_DIR)).find((item) => item.id === hostId);
|
|
276
|
+
if (!host)
|
|
277
|
+
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Remote host not found');
|
|
278
|
+
workingDir = `${host.username}@${host.host}:${remoteSessionName}`;
|
|
279
|
+
remote = toAttachedSessionRemote(host, remoteSessionName, workingDir);
|
|
280
|
+
}
|
|
256
281
|
// Multi-user: shell mode is arbitrary command execution as the host account,
|
|
257
282
|
// gated behind the same grant as bypass (section 6.3). Resolve the owner's grant
|
|
258
283
|
// from the store so a GRANTED regular user is not wrongly denied (AuthUser role alone can't tell).
|
|
@@ -407,6 +432,7 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
407
432
|
envOverrides: body.envOverrides,
|
|
408
433
|
effort: body.effort,
|
|
409
434
|
tmuxHistoryLimit: terminalHistoryConfig.tmuxHistoryLimit,
|
|
435
|
+
remote,
|
|
410
436
|
owner,
|
|
411
437
|
});
|
|
412
438
|
ctx.addSession(session);
|
|
@@ -1403,6 +1429,45 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
1403
1429
|
},
|
|
1404
1430
|
};
|
|
1405
1431
|
});
|
|
1432
|
+
// ========== Pin (float to top of the session manager list, COD-139) ==========
|
|
1433
|
+
app.post('/api/sessions/:id/pin', async (req) => {
|
|
1434
|
+
const { id } = req.params;
|
|
1435
|
+
const body = parseBody(PinSessionSchema, req.body, 'Invalid request body');
|
|
1436
|
+
const session = ctx.sessions.get(id);
|
|
1437
|
+
if (session) {
|
|
1438
|
+
if (!canAccessOwned(getAuthUser(req), session.owner)) {
|
|
1439
|
+
return createErrorResponse(ApiErrorCode.NOT_FOUND, `Session ${id} not found`);
|
|
1440
|
+
}
|
|
1441
|
+
session.setPinned(body.pinned);
|
|
1442
|
+
// Persist + broadcast session:updated (keeps tabs/state consistent), then a
|
|
1443
|
+
// dedicated session:pinned event so the session manager list re-sorts live.
|
|
1444
|
+
persistAndBroadcastSession(ctx, session);
|
|
1445
|
+
ctx.broadcast(SseEvent.SessionPinned, {
|
|
1446
|
+
id,
|
|
1447
|
+
pinned: session.pinned,
|
|
1448
|
+
pinnedAt: session.pinnedAt ?? undefined,
|
|
1449
|
+
});
|
|
1450
|
+
return {
|
|
1451
|
+
success: true,
|
|
1452
|
+
data: {
|
|
1453
|
+
pinned: session.pinned,
|
|
1454
|
+
pinnedAt: session.pinnedAt ?? undefined,
|
|
1455
|
+
},
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
// COD-142 keeps a pinned session's record after kill (demoteOrRemoveSession),
|
|
1459
|
+
// so pin toggles must also work WITHOUT a live Session — otherwise a
|
|
1460
|
+
// pinned-then-killed record could never be unpinned (cleanup skips pinned
|
|
1461
|
+
// records, and the record has no live session to route through).
|
|
1462
|
+
const persisted = ctx.store.getSession(id);
|
|
1463
|
+
if (!persisted || !canAccessOwned(getAuthUser(req), persisted.owner)) {
|
|
1464
|
+
return createErrorResponse(ApiErrorCode.NOT_FOUND, `Session ${id} not found`);
|
|
1465
|
+
}
|
|
1466
|
+
const pinnedAt = body.pinned ? Date.now() : undefined;
|
|
1467
|
+
ctx.store.setSession(id, { ...persisted, pinned: body.pinned || undefined, pinnedAt });
|
|
1468
|
+
ctx.broadcast(SseEvent.SessionPinned, { id, pinned: body.pinned, pinnedAt });
|
|
1469
|
+
return { success: true, data: { pinned: body.pinned, pinnedAt } };
|
|
1470
|
+
});
|
|
1406
1471
|
// ========== Image Watcher ==========
|
|
1407
1472
|
app.post('/api/sessions/:id/image-watcher', async (req) => {
|
|
1408
1473
|
const { id } = req.params;
|
|
@@ -1919,6 +1984,63 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
1919
1984
|
}
|
|
1920
1985
|
return undefined;
|
|
1921
1986
|
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Extract the text of the LAST user message from a JSONL transcript chunk
|
|
1989
|
+
* (COD-145). Mirrors `extractFirstUserPrompt` exactly — same user-message
|
|
1990
|
+
* detection, same noise/secret/slash-command filters, same 120-char cap — but
|
|
1991
|
+
* keeps the last qualifying match instead of returning on the first. Scan the
|
|
1992
|
+
* file tail for this (the most recent prompt lives near the end).
|
|
1993
|
+
*/
|
|
1994
|
+
function extractLastUserPrompt(text) {
|
|
1995
|
+
const MAX_PROMPT_LEN = 120;
|
|
1996
|
+
let result;
|
|
1997
|
+
let start = 0;
|
|
1998
|
+
while (start < text.length) {
|
|
1999
|
+
const end = text.indexOf('\n', start);
|
|
2000
|
+
const line = end === -1 ? text.slice(start) : text.slice(start, end);
|
|
2001
|
+
start = end === -1 ? text.length : end + 1;
|
|
2002
|
+
if (!line.includes('"type":"user"'))
|
|
2003
|
+
continue;
|
|
2004
|
+
try {
|
|
2005
|
+
const entry = JSON.parse(line);
|
|
2006
|
+
if (entry.type !== 'user' || !entry.message)
|
|
2007
|
+
continue;
|
|
2008
|
+
const content = entry.message.content;
|
|
2009
|
+
let msgText;
|
|
2010
|
+
if (typeof content === 'string') {
|
|
2011
|
+
msgText = content;
|
|
2012
|
+
}
|
|
2013
|
+
else if (Array.isArray(content)) {
|
|
2014
|
+
const textBlock = content.find((b) => b.type === 'text');
|
|
2015
|
+
if (textBlock)
|
|
2016
|
+
msgText = textBlock.text;
|
|
2017
|
+
}
|
|
2018
|
+
if (!msgText)
|
|
2019
|
+
continue;
|
|
2020
|
+
msgText = msgText
|
|
2021
|
+
.replace(/<[^>]+>/g, '')
|
|
2022
|
+
.replace(new RegExp(String.raw `\x1b\[[0-9;]*[a-zA-Z]`, 'g'), '')
|
|
2023
|
+
.trim()
|
|
2024
|
+
.replace(/\s+/g, ' ');
|
|
2025
|
+
if (!msgText)
|
|
2026
|
+
continue;
|
|
2027
|
+
if (/^(Caveat:|init\b|clear\b|resume\b|\/[a-z][\w-]*\b|You are a |\[Request |Set model to )/i.test(msgText) ||
|
|
2028
|
+
/^(Please )?(analyze|review) this codebase/i.test(msgText) ||
|
|
2029
|
+
/^(Read|Implement the following) .+, then (search|list|check) /i.test(msgText) ||
|
|
2030
|
+
/^\d+ vulnerabilit/i.test(msgText) ||
|
|
2031
|
+
/\btoolu_/.test(msgText) ||
|
|
2032
|
+
/^[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+/.test(msgText) ||
|
|
2033
|
+
/\b(sk-ant-|ANTHROPIC_API_KEY|API_KEY=|SECRET|TOKEN=)/i.test(msgText) ||
|
|
2034
|
+
msgText.length < 8)
|
|
2035
|
+
continue;
|
|
2036
|
+
result = msgText.length > MAX_PROMPT_LEN ? msgText.slice(0, MAX_PROMPT_LEN) + '…' : msgText;
|
|
2037
|
+
}
|
|
2038
|
+
catch {
|
|
2039
|
+
// Malformed line — skip
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return result;
|
|
2043
|
+
}
|
|
1922
2044
|
/**
|
|
1923
2045
|
* Decode a Claude project key (e.g. "-Users-teigen-Documents-Workspace-AI-project-Mirror")
|
|
1924
2046
|
* back to a filesystem path ("/Users/teigen/Documents/Workspace/AI_project/Mirror").
|
|
@@ -2096,6 +2218,15 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2096
2218
|
if (tail)
|
|
2097
2219
|
firstPrompt = extractFirstUserPrompt(tail);
|
|
2098
2220
|
}
|
|
2221
|
+
// COD-145: last (most recent) user prompt lives near the END of the file, so
|
|
2222
|
+
// prefer the tail. For large files where no tail was read yet, read one
|
|
2223
|
+
// (mirrors the firstPrompt > 65536 block). Small files fit in `head`, which
|
|
2224
|
+
// then contains the whole transcript — scan it for the last match instead.
|
|
2225
|
+
if (!tail && fileStat.size > 65536) {
|
|
2226
|
+
const tailBuf = Buffer.alloc(32768);
|
|
2227
|
+
tail = await readFileTail(filePath, tailBuf, fileStat.size);
|
|
2228
|
+
}
|
|
2229
|
+
const lastPrompt = (tail ? extractLastUserPrompt(tail) : undefined) ?? (head ? extractLastUserPrompt(head) : undefined);
|
|
2099
2230
|
out.push({
|
|
2100
2231
|
sessionId,
|
|
2101
2232
|
workingDir,
|
|
@@ -2103,6 +2234,7 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2103
2234
|
sizeBytes: fileStat.size,
|
|
2104
2235
|
lastModified: fileStat.mtime.toISOString(),
|
|
2105
2236
|
firstPrompt,
|
|
2237
|
+
lastPrompt,
|
|
2106
2238
|
});
|
|
2107
2239
|
}
|
|
2108
2240
|
return out;
|
|
@@ -2174,6 +2306,8 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2174
2306
|
createdAt: st.createdAt,
|
|
2175
2307
|
lastActivityAt: st.lastActivityAt,
|
|
2176
2308
|
claudeSessionId: s.claudeSessionId ?? undefined,
|
|
2309
|
+
pinned: st.pinned,
|
|
2310
|
+
pinnedAt: st.pinnedAt,
|
|
2177
2311
|
};
|
|
2178
2312
|
});
|
|
2179
2313
|
// Persisted sessions (state.json). resumeSessionId is the Claude
|
|
@@ -2188,6 +2322,8 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2188
2322
|
createdAt: p.createdAt,
|
|
2189
2323
|
lastActivityAt: p.lastActivityAt,
|
|
2190
2324
|
claudeSessionId: p.resumeSessionId,
|
|
2325
|
+
pinned: p.pinned,
|
|
2326
|
+
pinnedAt: p.pinnedAt,
|
|
2191
2327
|
}));
|
|
2192
2328
|
// Lifecycle audit log (newest-first, capped).
|
|
2193
2329
|
let lifecycle = [];
|
|
@@ -2220,6 +2356,7 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2220
2356
|
sizeBytes: h.sizeBytes,
|
|
2221
2357
|
lastModified: h.lastModified,
|
|
2222
2358
|
firstPrompt: h.firstPrompt,
|
|
2359
|
+
lastPrompt: h.lastPrompt,
|
|
2223
2360
|
projectKey: h.projectKey,
|
|
2224
2361
|
});
|
|
2225
2362
|
}
|