@phnx-labs/agents-cli 1.22.52 → 1.22.54
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 +336 -0
- package/README.md +42 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +220 -174
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +26 -17
- package/dist/commands/fleet-capture.js +7 -0
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +4 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +8 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions-inject.js +8 -3
- package/dist/commands/sessions-picker.js +2 -1
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +94 -40
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +52 -3
- package/dist/commands/share.js +262 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +53 -14
- package/dist/commands/status.js +14 -0
- package/dist/commands/sync.js +44 -0
- package/dist/commands/view.d.ts +3 -1
- package/dist/commands/view.js +5 -4
- package/dist/lib/account-registry.d.ts +15 -5
- package/dist/lib/account-registry.js +165 -53
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +37 -1
- package/dist/lib/accounting/usage.js +71 -6
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/answer-router.js +2 -1
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/profiles.d.ts +18 -0
- package/dist/lib/browser/profiles.js +26 -1
- package/dist/lib/browser/registry.d.ts +44 -14
- package/dist/lib/browser/registry.js +141 -45
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/daemon.js +30 -4
- package/dist/lib/daemon/runner.js +10 -2
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +5 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +8 -7
- package/dist/lib/devices/config-migration.js +147 -1
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/device-docs.d.ts +35 -0
- package/dist/lib/devices/device-docs.js +163 -0
- package/dist/lib/devices/discovery-policy.d.ts +14 -2
- package/dist/lib/devices/discovery-policy.js +31 -21
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/registry.d.ts +11 -5
- package/dist/lib/devices/registry.js +46 -18
- package/dist/lib/exec.d.ts +88 -30
- package/dist/lib/exec.js +138 -34
- package/dist/lib/feed/feed.d.ts +10 -2
- package/dist/lib/feed/feed.js +35 -2
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/dispatch.d.ts +4 -3
- package/dist/lib/hosts/dispatch.js +12 -8
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/hosts/providers/local.d.ts +9 -3
- package/dist/lib/hosts/providers/local.js +23 -12
- package/dist/lib/hosts/reconnect.d.ts +7 -4
- package/dist/lib/hosts/reconnect.js +29 -25
- package/dist/lib/hosts/registry.js +4 -1
- package/dist/lib/hosts/remote-os.js +3 -1
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.js +8 -1
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.d.ts +10 -1
- package/dist/lib/session/active.js +8 -5
- package/dist/lib/session/actor-sidecar.d.ts +7 -0
- package/dist/lib/session/actor-sidecar.js +2 -0
- package/dist/lib/session/db.d.ts +39 -4
- package/dist/lib/session/db.js +168 -31
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +126 -37
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -0
- package/dist/lib/session/pid-registry.d.ts +7 -0
- package/dist/lib/session/prompt.d.ts +15 -0
- package/dist/lib/session/prompt.js +21 -0
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +25 -0
- package/dist/lib/session/types.js +10 -0
- package/dist/lib/share/publish.d.ts +53 -5
- package/dist/lib/share/publish.js +99 -17
- package/dist/lib/share/worker-template.js +594 -64
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +24 -0
- package/dist/lib/state.js +318 -54
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/terminal/resolve.d.ts +7 -0
- package/dist/lib/terminal/resolve.js +41 -2
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/traces/insights.d.ts +67 -0
- package/dist/lib/traces/insights.js +178 -0
- package/dist/lib/traces/phenotype.d.ts +67 -0
- package/dist/lib/traces/phenotype.js +437 -0
- package/dist/lib/traces/segments.d.ts +133 -0
- package/dist/lib/traces/segments.js +301 -0
- package/dist/lib/traces/sync.d.ts +33 -0
- package/dist/lib/traces/sync.js +11 -2
- package/dist/lib/types.d.ts +47 -1
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +2 -0
- package/dist/lib/watchdog/runner.js +18 -4
- package/package.json +2 -1
|
@@ -104,6 +104,8 @@ interface ClaudeSessionScan {
|
|
|
104
104
|
entrypoint?: string;
|
|
105
105
|
/** Concatenated user message text, ready to hand to FTS5. */
|
|
106
106
|
contentText?: string;
|
|
107
|
+
/** Concatenated assistant-answer text, ready to hand to FTS5's `assistant` column. */
|
|
108
|
+
assistantText?: string;
|
|
107
109
|
/** Durable state signals persisted to the index by the session-state engine. */
|
|
108
110
|
prUrl?: string;
|
|
109
111
|
prNumber?: number;
|
|
@@ -154,6 +156,7 @@ interface CodexSessionScan {
|
|
|
154
156
|
durationMs?: number;
|
|
155
157
|
lastActivity?: string;
|
|
156
158
|
contentText?: string;
|
|
159
|
+
assistantText?: string;
|
|
157
160
|
prUrl?: string;
|
|
158
161
|
prNumber?: number;
|
|
159
162
|
worktreeSlug?: string;
|
|
@@ -347,8 +350,15 @@ export declare function looksLikeSessionId(query: string): boolean;
|
|
|
347
350
|
*/
|
|
348
351
|
export declare function resolveSessionById(sessions: SessionMeta[], idQuery: string): SessionMeta[];
|
|
349
352
|
/**
|
|
350
|
-
* Run an FTS5 search over the DB and
|
|
351
|
-
*
|
|
353
|
+
* Run an FTS5 search over the DB and union hits with the given session list.
|
|
354
|
+
*
|
|
355
|
+
* The listing pool is a minority of the index — cwd-scoped, default-capped at
|
|
356
|
+
* 50, and skipping whole classes of indexed transcript — so intersecting FTS
|
|
357
|
+
* hits with it dropped grep-visible sessions that the index already found
|
|
358
|
+
* (PHNX-2767: `agents sessions "tmux pane"` returned 0 while the project
|
|
359
|
+
* transcripts matched). Hits already in the pool keep the caller's SessionMeta;
|
|
360
|
+
* hits the pool missed are hydrated from the index so a content query returns
|
|
361
|
+
* the transcript FTS matched.
|
|
352
362
|
*/
|
|
353
363
|
export declare function searchContentIndex(sessions: SessionMeta[], query: string): Map<string, SessionMeta>;
|
|
354
364
|
/**
|
|
@@ -375,6 +385,13 @@ interface PreStatEntry {
|
|
|
375
385
|
* stat. Same debounce and change-detection as filterChangedFiles; the raw
|
|
376
386
|
* mtime is floored here so warm files match the ledger exactly as the stat path
|
|
377
387
|
* does (Math.floor(stat.mtimeMs)).
|
|
388
|
+
*
|
|
389
|
+
* A file is also treated as "changed" — independent of (mtime, size) — when
|
|
390
|
+
* its ledger row's `extractor_version` is behind {@link CONTENT_INDEX_VERSION}.
|
|
391
|
+
* This is the lever that backfills a content-extractor improvement (e.g.
|
|
392
|
+
* indexing assistant answers, not just user prompts) into every already-scanned
|
|
393
|
+
* session on its next pass, without a `DELETE FROM scan_ledger` that would also
|
|
394
|
+
* discard the Claude/Codex resumable parser_state.
|
|
378
395
|
*/
|
|
379
396
|
export declare function filterChangedEntries(entries: PreStatEntry[]): Array<{
|
|
380
397
|
filePath: string;
|
|
@@ -429,9 +446,11 @@ export declare function parseCodexThreadNameIndex(raw: string): Map<string, stri
|
|
|
429
446
|
export declare function readCodexMeta(filePath: string, resolveAccount?: () => string | undefined, currentVersion?: string, scanStamp?: ScanStamp, priorRow?: {
|
|
430
447
|
parserState: string | null;
|
|
431
448
|
fileMtimeMs: number;
|
|
449
|
+
extractorVersion?: number | null;
|
|
432
450
|
}): Promise<{
|
|
433
451
|
meta: SessionMeta;
|
|
434
452
|
content: string;
|
|
453
|
+
assistantContent: string;
|
|
435
454
|
parserState?: string;
|
|
436
455
|
contentText?: string;
|
|
437
456
|
toolCalls?: IndexedToolCall[];
|
|
@@ -469,6 +488,10 @@ interface ClaudeParseState {
|
|
|
469
488
|
lastTsMs?: number;
|
|
470
489
|
seenAssistantIds: Set<string>;
|
|
471
490
|
userTexts: string[];
|
|
491
|
+
/** Assistant-answer text (#PHNX content-search), accumulated the same way as
|
|
492
|
+
* `userTexts` but indexed into session_text's own lower-weighted `assistant`
|
|
493
|
+
* column instead of `content` — see BM25_WEIGHTS. */
|
|
494
|
+
assistantTexts: string[];
|
|
472
495
|
sawPrCreate: boolean;
|
|
473
496
|
prUrl?: string;
|
|
474
497
|
prNumber?: number;
|
|
@@ -521,7 +544,7 @@ export declare function scanClaudeSession(filePath: string): Promise<ClaudeSessi
|
|
|
521
544
|
* exact even when the recent window is smaller than the true count.
|
|
522
545
|
*/
|
|
523
546
|
export interface ClaudeParserState {
|
|
524
|
-
v:
|
|
547
|
+
v: 5;
|
|
525
548
|
/**
|
|
526
549
|
* Fan-out tallies carried across a RESUMED parse (RUSH-3091/3095). They must
|
|
527
550
|
* live in the durable blob: the resumable parser reads only new bytes, so
|
|
@@ -566,6 +589,7 @@ export interface ClaudeParserState {
|
|
|
566
589
|
spawnedTeam?: string;
|
|
567
590
|
ticketId?: string;
|
|
568
591
|
contentText?: string;
|
|
592
|
+
assistantContentText?: string;
|
|
569
593
|
checklistEvents: SessionEvent[];
|
|
570
594
|
recentDirectoriesTouched: string[];
|
|
571
595
|
toolCalls: ToolCallCollectorSnapshot;
|
|
@@ -663,6 +687,8 @@ interface CodexParseState {
|
|
|
663
687
|
firstTsMs?: number;
|
|
664
688
|
lastTsMs?: number;
|
|
665
689
|
userTexts: string[];
|
|
690
|
+
/** See ClaudeParseState.assistantTexts — same accumulate-and-lower-weight FTS treatment. */
|
|
691
|
+
assistantTexts: string[];
|
|
666
692
|
sawPrCreate: boolean;
|
|
667
693
|
prUrl?: string;
|
|
668
694
|
prNumber?: number;
|
|
@@ -693,7 +719,7 @@ export declare function initCodexParseState(): CodexParseState;
|
|
|
693
719
|
* finalize is identical after a resume.
|
|
694
720
|
*/
|
|
695
721
|
export interface CodexParserState {
|
|
696
|
-
v:
|
|
722
|
+
v: 3;
|
|
697
723
|
offset: number;
|
|
698
724
|
jsonlDroppingOversizedLine?: boolean;
|
|
699
725
|
sessionId?: string;
|
|
@@ -716,6 +742,7 @@ export interface CodexParserState {
|
|
|
716
742
|
spawnedTeam?: string;
|
|
717
743
|
ticketId?: string;
|
|
718
744
|
contentText?: string;
|
|
745
|
+
assistantContentText?: string;
|
|
719
746
|
checklistEvents: SessionEvent[];
|
|
720
747
|
recentDirectoriesTouched: string[];
|
|
721
748
|
toolCalls: ToolCallCollectorSnapshot;
|
|
@@ -772,6 +799,7 @@ export declare function __resetCodexScanBranchCountsForTest(): void;
|
|
|
772
799
|
export declare function readCursorMeta(filePath: string, currentVersion?: string): {
|
|
773
800
|
meta: SessionMeta;
|
|
774
801
|
content: string;
|
|
802
|
+
assistantContent: string;
|
|
775
803
|
events: SessionEvent[];
|
|
776
804
|
} | null;
|
|
777
805
|
/** Parse a single Kimi session state.json file to extract session metadata. */
|
|
@@ -26,7 +26,7 @@ import { getConfigSymlinkVersion } from '../installations/shims.js';
|
|
|
26
26
|
import { SESSION_AGENTS } from './types.js';
|
|
27
27
|
import { deriveShortId } from './short-id.js';
|
|
28
28
|
import { buildClaudeAccountIndex, resolveClaudeAccount } from './claude-accounts.js';
|
|
29
|
-
import { extractSessionTopic, extractSlashCommandName, extractSlashCommandFromToolInput,
|
|
29
|
+
import { extractSessionTopic, extractSlashCommandName, extractSlashCommandFromToolInput, cleanGeneratedSessionLabel } from './prompt.js';
|
|
30
30
|
import { isBackgroundShellStart, isSkillInvocation, extractSkills, extractSlashCommands, isSubAgentTool } from './highlights.js';
|
|
31
31
|
import { parseAntigravity, parseCursor, splitSessionFilePath } from './parse.js';
|
|
32
32
|
import { extractPrUrl, detectWorktree, detectTicket, isPrCreateCommand, detectSpawnedTeam, isTicketCreateTool, extractCreatedTicket, extractRecentDirectoriesTouched, extractTodoProgressFromEvents } from './state.js';
|
|
@@ -34,7 +34,7 @@ import { costOfUsage, costOfUsageNoCache } from '../pricing/index.js';
|
|
|
34
34
|
import { machineForSessionFile } from './origin-machine.js';
|
|
35
35
|
export { machineForSessionFile } from './origin-machine.js';
|
|
36
36
|
import { mapBounded } from '../concurrency.js';
|
|
37
|
-
import { getDB, getScanStampByPath, getScanStampsForPaths, getParserStatesForPaths, getDirLedgerForPaths, recordDirScans, recordScans, syncLabels, seedLabelsFromNames, syncTopics, upsertSessionsBatch, querySessions, countSessions, ftsSearch, tryClaimScan, releaseScan, scanInProgressByLivePid, cacheLinearProject, } from './db.js';
|
|
37
|
+
import { getDB, getScanStampByPath, getScanStampsForPaths, getParserStatesForPaths, getDirLedgerForPaths, recordDirScans, recordScans, syncLabels, seedLabelsFromNames, syncTopics, upsertSessionsBatch, querySessions, countSessions, ftsSearch, getSessionById, tryClaimScan, releaseScan, scanInProgressByLivePid, cacheLinearProject, CONTENT_INDEX_VERSION, } from './db.js';
|
|
38
38
|
import { buildRunNameMap } from './run-names.js';
|
|
39
39
|
import { resolveLinearApiKey } from '../linear-cache.js';
|
|
40
40
|
import { ToolCallCollector, collectClaudeToolCalls, collectCodexToolCalls, } from './tool-calls.js';
|
|
@@ -628,8 +628,15 @@ export function resolveSessionById(sessions, idQuery) {
|
|
|
628
628
|
// Content-index search (FTS5-backed)
|
|
629
629
|
// ---------------------------------------------------------------------------
|
|
630
630
|
/**
|
|
631
|
-
* Run an FTS5 search over the DB and
|
|
632
|
-
*
|
|
631
|
+
* Run an FTS5 search over the DB and union hits with the given session list.
|
|
632
|
+
*
|
|
633
|
+
* The listing pool is a minority of the index — cwd-scoped, default-capped at
|
|
634
|
+
* 50, and skipping whole classes of indexed transcript — so intersecting FTS
|
|
635
|
+
* hits with it dropped grep-visible sessions that the index already found
|
|
636
|
+
* (PHNX-2767: `agents sessions "tmux pane"` returned 0 while the project
|
|
637
|
+
* transcripts matched). Hits already in the pool keep the caller's SessionMeta;
|
|
638
|
+
* hits the pool missed are hydrated from the index so a content query returns
|
|
639
|
+
* the transcript FTS matched.
|
|
633
640
|
*/
|
|
634
641
|
export function searchContentIndex(sessions, query) {
|
|
635
642
|
if (!query.trim())
|
|
@@ -640,13 +647,14 @@ export function searchContentIndex(sessions, query) {
|
|
|
640
647
|
const byId = new Map(sessions.map(s => [s.id, s]));
|
|
641
648
|
const result = new Map();
|
|
642
649
|
for (const hit of hits) {
|
|
643
|
-
const session = byId.get(hit.sessionId);
|
|
650
|
+
const session = byId.get(hit.sessionId) ?? getSessionById(hit.sessionId);
|
|
644
651
|
if (!session)
|
|
645
652
|
continue;
|
|
646
653
|
result.set(hit.sessionId, {
|
|
647
654
|
...session,
|
|
648
655
|
_matchedTerms: hit.matchedTerms,
|
|
649
656
|
_bm25Score: hit.score,
|
|
657
|
+
snippet: hit.snippet,
|
|
650
658
|
});
|
|
651
659
|
}
|
|
652
660
|
return result;
|
|
@@ -678,6 +686,13 @@ export function filterChangedFiles(filePaths) {
|
|
|
678
686
|
* stat. Same debounce and change-detection as filterChangedFiles; the raw
|
|
679
687
|
* mtime is floored here so warm files match the ledger exactly as the stat path
|
|
680
688
|
* does (Math.floor(stat.mtimeMs)).
|
|
689
|
+
*
|
|
690
|
+
* A file is also treated as "changed" — independent of (mtime, size) — when
|
|
691
|
+
* its ledger row's `extractor_version` is behind {@link CONTENT_INDEX_VERSION}.
|
|
692
|
+
* This is the lever that backfills a content-extractor improvement (e.g.
|
|
693
|
+
* indexing assistant answers, not just user prompts) into every already-scanned
|
|
694
|
+
* session on its next pass, without a `DELETE FROM scan_ledger` that would also
|
|
695
|
+
* discard the Claude/Codex resumable parser_state.
|
|
681
696
|
*/
|
|
682
697
|
export function filterChangedEntries(entries) {
|
|
683
698
|
const ledger = getScanStampsForPaths(entries.map(e => e.filePath));
|
|
@@ -689,10 +704,11 @@ export function filterChangedEntries(entries) {
|
|
|
689
704
|
fileSize: entry.fileSize,
|
|
690
705
|
};
|
|
691
706
|
const prev = ledger.get(entry.filePath);
|
|
692
|
-
|
|
707
|
+
const contentIndexStale = prev?.extractorVersion !== CONTENT_INDEX_VERSION;
|
|
708
|
+
if (prev && prev.fileMtimeMs === scan.fileMtimeMs && prev.fileSize === scan.fileSize && !contentIndexStale) {
|
|
693
709
|
continue;
|
|
694
710
|
}
|
|
695
|
-
if (prev && shouldDeferRecentAppend(prev, scan, now)) {
|
|
711
|
+
if (prev && !contentIndexStale && shouldDeferRecentAppend(prev, scan, now)) {
|
|
696
712
|
continue;
|
|
697
713
|
}
|
|
698
714
|
out.push({ filePath: entry.filePath, scan });
|
|
@@ -1000,6 +1016,7 @@ async function scanRoutineArchivesIncremental(agent, onProgress) {
|
|
|
1000
1016
|
entries.push({
|
|
1001
1017
|
meta: result.meta,
|
|
1002
1018
|
content: result.content,
|
|
1019
|
+
assistantContent: result.assistantContent,
|
|
1003
1020
|
scan,
|
|
1004
1021
|
events: result.events,
|
|
1005
1022
|
toolCalls: result.toolCalls,
|
|
@@ -1152,6 +1169,7 @@ async function scanClaudeIncremental(onProgress) {
|
|
|
1152
1169
|
entries.push({
|
|
1153
1170
|
meta: result.meta,
|
|
1154
1171
|
content: result.content,
|
|
1172
|
+
assistantContent: result.assistantContent,
|
|
1155
1173
|
scan,
|
|
1156
1174
|
parserState: result.parserState,
|
|
1157
1175
|
contentText: result.contentText,
|
|
@@ -1185,7 +1203,12 @@ async function scanClaudeIncremental(onProgress) {
|
|
|
1185
1203
|
* next scan.
|
|
1186
1204
|
*/
|
|
1187
1205
|
async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
1188
|
-
|
|
1206
|
+
// A prior continuation extracted at an older CONTENT_INDEX_VERSION is missing
|
|
1207
|
+
// whatever the current extractor adds (e.g. assistant-answer text) — resuming
|
|
1208
|
+
// from it would fold only the NEWLY appended lines into that gap, never
|
|
1209
|
+
// backfilling the file's earlier assistant text. Treat it as absent so the
|
|
1210
|
+
// file gets one full from-offset-0 reparse instead.
|
|
1211
|
+
const prior = priorRow?.extractorVersion === CONTENT_INDEX_VERSION ? parsePriorClaudeState(priorRow) : null;
|
|
1189
1212
|
const { scan, newState, toolCalls, mode } = await scanClaudeSessionResumable(filePath, prior, scanStamp.fileMtimeMs, scanStamp.fileSize, priorRow?.fileMtimeMs);
|
|
1190
1213
|
if (mode === 'incremental')
|
|
1191
1214
|
claudeIncrementalScanCount++;
|
|
@@ -1290,6 +1313,7 @@ async function readClaudeMeta(filePath, sessionId, scanStamp, priorRow, label) {
|
|
|
1290
1313
|
return {
|
|
1291
1314
|
meta,
|
|
1292
1315
|
content: scan.contentText || '',
|
|
1316
|
+
assistantContent: scan.assistantText || '',
|
|
1293
1317
|
// Persist the continuation so the next scan of this file can resume from the
|
|
1294
1318
|
// offset instead of a full reparse. content_text is the same accumulated user
|
|
1295
1319
|
// doc, cached so the resume can hydrate userTexts without re-reading the file.
|
|
@@ -1431,6 +1455,7 @@ async function scanCodexIncremental(onProgress) {
|
|
|
1431
1455
|
entries.push({
|
|
1432
1456
|
meta: result.meta,
|
|
1433
1457
|
content: result.content,
|
|
1458
|
+
assistantContent: result.assistantContent,
|
|
1434
1459
|
scan,
|
|
1435
1460
|
parserState: result.parserState,
|
|
1436
1461
|
contentText: result.contentText,
|
|
@@ -1541,7 +1566,10 @@ export async function readCodexMeta(filePath, resolveAccount, currentVersion, sc
|
|
|
1541
1566
|
let toolCalls;
|
|
1542
1567
|
let toolIndexMode = 'replace';
|
|
1543
1568
|
if (scanStamp) {
|
|
1544
|
-
|
|
1569
|
+
// See readClaudeMeta: a prior continuation from an older content extractor
|
|
1570
|
+
// is missing what the current extractor adds, so it must not be resumed
|
|
1571
|
+
// from — treat it as absent and force one full reparse.
|
|
1572
|
+
const prior = priorRow?.extractorVersion === CONTENT_INDEX_VERSION ? parsePriorCodexState(priorRow) : null;
|
|
1545
1573
|
const result = await scanCodexSessionResumable(filePath, prior, scanStamp.fileMtimeMs, scanStamp.fileSize, priorRow?.fileMtimeMs);
|
|
1546
1574
|
if (result.mode === 'incremental')
|
|
1547
1575
|
codexIncrementalScanCount++;
|
|
@@ -1597,6 +1625,7 @@ export async function readCodexMeta(filePath, resolveAccount, currentVersion, sc
|
|
|
1597
1625
|
return {
|
|
1598
1626
|
meta,
|
|
1599
1627
|
content: scan.contentText || '',
|
|
1628
|
+
assistantContent: scan.assistantText || '',
|
|
1600
1629
|
// Persist the continuation so the next scan of this rollout resumes from the
|
|
1601
1630
|
// offset instead of a full reparse; content_text caches the accumulated user
|
|
1602
1631
|
// doc for the resume's hydrate. Absent when no stamp was supplied.
|
|
@@ -1675,7 +1704,7 @@ async function scanGeminiIncremental(onProgress) {
|
|
|
1675
1704
|
const result = readGeminiMeta(filePath, hashDir, projectMap, currentVersion);
|
|
1676
1705
|
if (result && !seen.has(result.meta.id)) {
|
|
1677
1706
|
seen.add(result.meta.id);
|
|
1678
|
-
entries.push({ meta: result.meta, content: result.content, scan });
|
|
1707
|
+
entries.push({ meta: result.meta, content: result.content, assistantContent: result.assistantContent, scan });
|
|
1679
1708
|
}
|
|
1680
1709
|
else {
|
|
1681
1710
|
// Gemini file without a sessionId — record scan so we don't re-parse it next run.
|
|
@@ -1729,6 +1758,7 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
1729
1758
|
let firstTsMs;
|
|
1730
1759
|
let lastTsMs;
|
|
1731
1760
|
const userTexts = [];
|
|
1761
|
+
const assistantTexts = [];
|
|
1732
1762
|
for (const message of messages) {
|
|
1733
1763
|
if (message.type === 'user') {
|
|
1734
1764
|
const text = extractGeminiMessageText(message.content);
|
|
@@ -1740,8 +1770,10 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
1740
1770
|
}
|
|
1741
1771
|
}
|
|
1742
1772
|
else if (message.type === 'gemini') {
|
|
1743
|
-
|
|
1773
|
+
const text = extractGeminiMessageText(message.content);
|
|
1774
|
+
if (text) {
|
|
1744
1775
|
messageCount++;
|
|
1776
|
+
assistantTexts.push(text);
|
|
1745
1777
|
}
|
|
1746
1778
|
}
|
|
1747
1779
|
// Duration: messages carry a `timestamp` on most Gemini CLI versions.
|
|
@@ -1819,7 +1851,7 @@ function readGeminiMeta(filePath, hashDir, projectMap, currentVersion) {
|
|
|
1819
1851
|
costUsdNoCache: sawCost ? costUsdNoCache : undefined,
|
|
1820
1852
|
durationMs,
|
|
1821
1853
|
};
|
|
1822
|
-
return { meta, content: userTexts.join('\n') };
|
|
1854
|
+
return { meta, content: userTexts.join('\n'), assistantContent: assistantTexts.join('\n') };
|
|
1823
1855
|
}
|
|
1824
1856
|
/** Build a hash-to-project mapping from Gemini's projects.json and history directories. */
|
|
1825
1857
|
function buildGeminiProjectMap() {
|
|
@@ -2396,7 +2428,7 @@ async function scanRushIncremental(onProgress) {
|
|
|
2396
2428
|
const sessionId = path.basename(path.dirname(filePath));
|
|
2397
2429
|
const result = await readRushMeta(filePath, sessionId);
|
|
2398
2430
|
if (result) {
|
|
2399
|
-
entries.push({ meta: result.meta, content: result.content, scan });
|
|
2431
|
+
entries.push({ meta: result.meta, content: result.content, assistantContent: result.assistantContent, scan });
|
|
2400
2432
|
}
|
|
2401
2433
|
else {
|
|
2402
2434
|
touched.push({ filePath, scan });
|
|
@@ -2428,7 +2460,7 @@ async function readRushMeta(filePath, sessionId) {
|
|
|
2428
2460
|
topic: scan.topic,
|
|
2429
2461
|
messageCount: scan.messageCount,
|
|
2430
2462
|
};
|
|
2431
|
-
return { meta, content: scan.contentText || '' };
|
|
2463
|
+
return { meta, content: scan.contentText || '', assistantContent: scan.assistantText || '' };
|
|
2432
2464
|
}
|
|
2433
2465
|
/** Stream a Rush messages.jsonl file and extract scan-level metadata. */
|
|
2434
2466
|
async function scanRushSession(filePath) {
|
|
@@ -2439,6 +2471,7 @@ async function scanRushSession(filePath) {
|
|
|
2439
2471
|
let agentId;
|
|
2440
2472
|
let messageCount = 0;
|
|
2441
2473
|
const userTexts = [];
|
|
2474
|
+
const assistantTexts = [];
|
|
2442
2475
|
try {
|
|
2443
2476
|
for await (const line of rl) {
|
|
2444
2477
|
if (!line.trim())
|
|
@@ -2475,6 +2508,9 @@ async function scanRushSession(filePath) {
|
|
|
2475
2508
|
if (!topic)
|
|
2476
2509
|
topic = extractSessionTopic(cleaned);
|
|
2477
2510
|
}
|
|
2511
|
+
else if (parsed.role === 'assistant') {
|
|
2512
|
+
assistantTexts.push(cleaned);
|
|
2513
|
+
}
|
|
2478
2514
|
}
|
|
2479
2515
|
}
|
|
2480
2516
|
finally {
|
|
@@ -2487,6 +2523,7 @@ async function scanRushSession(filePath) {
|
|
|
2487
2523
|
agentId,
|
|
2488
2524
|
messageCount,
|
|
2489
2525
|
contentText: userTexts.length > 0 ? userTexts.join('\n') : undefined,
|
|
2526
|
+
assistantText: assistantTexts.length > 0 ? assistantTexts.join('\n') : undefined,
|
|
2490
2527
|
};
|
|
2491
2528
|
}
|
|
2492
2529
|
// ---------------------------------------------------------------------------
|
|
@@ -2529,7 +2566,7 @@ async function scanHermesIncremental(onProgress) {
|
|
|
2529
2566
|
const result = readHermesMeta(filePath);
|
|
2530
2567
|
if (result && !seen.has(result.meta.id)) {
|
|
2531
2568
|
seen.add(result.meta.id);
|
|
2532
|
-
scanEntries.push({ meta: result.meta, content: result.content, scan });
|
|
2569
|
+
scanEntries.push({ meta: result.meta, content: result.content, assistantContent: result.assistantContent, scan });
|
|
2533
2570
|
}
|
|
2534
2571
|
else {
|
|
2535
2572
|
touched.push({ filePath, scan });
|
|
@@ -2558,6 +2595,7 @@ function readHermesMeta(filePath) {
|
|
|
2558
2595
|
return null;
|
|
2559
2596
|
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
2560
2597
|
const userTexts = [];
|
|
2598
|
+
const assistantTexts = [];
|
|
2561
2599
|
let topic;
|
|
2562
2600
|
let messageCount = 0;
|
|
2563
2601
|
for (const msg of messages) {
|
|
@@ -2570,6 +2608,9 @@ function readHermesMeta(filePath) {
|
|
|
2570
2608
|
if (!topic)
|
|
2571
2609
|
topic = extractSessionTopic(text);
|
|
2572
2610
|
}
|
|
2611
|
+
else if (msg?.role === 'assistant') {
|
|
2612
|
+
assistantTexts.push(text);
|
|
2613
|
+
}
|
|
2573
2614
|
}
|
|
2574
2615
|
const stat = safeStatSync(filePath);
|
|
2575
2616
|
const timestamp = typeof session.last_updated === 'string'
|
|
@@ -2592,7 +2633,7 @@ function readHermesMeta(filePath) {
|
|
|
2592
2633
|
topic,
|
|
2593
2634
|
messageCount: messageCount || (typeof session.message_count === 'number' ? session.message_count : undefined),
|
|
2594
2635
|
};
|
|
2595
|
-
return { meta, content: userTexts.join('\n') };
|
|
2636
|
+
return { meta, content: userTexts.join('\n'), assistantContent: assistantTexts.join('\n') };
|
|
2596
2637
|
}
|
|
2597
2638
|
/**
|
|
2598
2639
|
* Muse Code stores one session per directory under
|
|
@@ -2660,7 +2701,7 @@ function scanMuseIncremental(onProgress) {
|
|
|
2660
2701
|
const result = readMuseMeta(filePath);
|
|
2661
2702
|
if (result && !seen.has(result.meta.id)) {
|
|
2662
2703
|
seen.add(result.meta.id);
|
|
2663
|
-
scanEntries.push({ meta: result.meta, content: result.content, scan });
|
|
2704
|
+
scanEntries.push({ meta: result.meta, content: result.content, assistantContent: result.assistantContent, scan });
|
|
2664
2705
|
}
|
|
2665
2706
|
else {
|
|
2666
2707
|
touched.push({ filePath, scan });
|
|
@@ -2690,6 +2731,7 @@ function readMuseMeta(filePath) {
|
|
|
2690
2731
|
return null;
|
|
2691
2732
|
}
|
|
2692
2733
|
const userTexts = [];
|
|
2734
|
+
const assistantTexts = [];
|
|
2693
2735
|
let topic;
|
|
2694
2736
|
let messageCount = 0;
|
|
2695
2737
|
let model;
|
|
@@ -2734,6 +2776,9 @@ function readMuseMeta(filePath) {
|
|
|
2734
2776
|
const event = payload?.event ?? payload;
|
|
2735
2777
|
if (event?.kind === 'assistant_message_committed' && typeof event.text === 'string') {
|
|
2736
2778
|
messageCount++;
|
|
2779
|
+
const text = event.text.trim();
|
|
2780
|
+
if (text)
|
|
2781
|
+
assistantTexts.push(text);
|
|
2737
2782
|
}
|
|
2738
2783
|
if (event?.kind === 'model_request_configured' && typeof event.model === 'string') {
|
|
2739
2784
|
model = event.model;
|
|
@@ -2757,7 +2802,7 @@ function readMuseMeta(filePath) {
|
|
|
2757
2802
|
topic,
|
|
2758
2803
|
messageCount: messageCount || undefined,
|
|
2759
2804
|
};
|
|
2760
|
-
return { meta, content: userTexts.join('\n') };
|
|
2805
|
+
return { meta, content: userTexts.join('\n'), assistantContent: assistantTexts.join('\n') };
|
|
2761
2806
|
}
|
|
2762
2807
|
/** Extract plain text from a Hermes message content field (string or list of parts). */
|
|
2763
2808
|
function extractHermesMessageText(content) {
|
|
@@ -2805,7 +2850,7 @@ async function scanDroidIncremental(onProgress) {
|
|
|
2805
2850
|
const result = await readDroidMeta(filePath, currentVersion);
|
|
2806
2851
|
if (result && !seen.has(result.meta.id)) {
|
|
2807
2852
|
seen.add(result.meta.id);
|
|
2808
|
-
entries.push({ meta: result.meta, content: result.content, scan });
|
|
2853
|
+
entries.push({ meta: result.meta, content: result.content, assistantContent: result.assistantContent, scan });
|
|
2809
2854
|
}
|
|
2810
2855
|
else {
|
|
2811
2856
|
touched.push({ filePath, scan });
|
|
@@ -2868,7 +2913,7 @@ async function readDroidMeta(filePath, currentVersion) {
|
|
|
2868
2913
|
costUsdNoCache: costUsd > 0 ? costUsdNoCache : undefined,
|
|
2869
2914
|
durationMs: scan.durationMs,
|
|
2870
2915
|
};
|
|
2871
|
-
return { meta, content: scan.contentText || '' };
|
|
2916
|
+
return { meta, content: scan.contentText || '', assistantContent: scan.assistantText || '' };
|
|
2872
2917
|
}
|
|
2873
2918
|
/** Read model + token usage from a Droid `<uuid>.settings.json` sidecar. */
|
|
2874
2919
|
function readDroidSettings(settingsPath) {
|
|
@@ -2911,6 +2956,7 @@ async function scanDroidSession(filePath) {
|
|
|
2911
2956
|
let firstTsMs;
|
|
2912
2957
|
let lastTsMs;
|
|
2913
2958
|
const userTexts = [];
|
|
2959
|
+
const assistantTexts = [];
|
|
2914
2960
|
try {
|
|
2915
2961
|
for await (const line of rl) {
|
|
2916
2962
|
if (!line.trim())
|
|
@@ -2960,6 +3006,9 @@ async function scanDroidSession(filePath) {
|
|
|
2960
3006
|
if (!firstUserTopic)
|
|
2961
3007
|
firstUserTopic = extractSessionTopic(text);
|
|
2962
3008
|
}
|
|
3009
|
+
else if (msg.role === 'assistant') {
|
|
3010
|
+
assistantTexts.push(text);
|
|
3011
|
+
}
|
|
2963
3012
|
}
|
|
2964
3013
|
}
|
|
2965
3014
|
finally {
|
|
@@ -2981,6 +3030,7 @@ async function scanDroidSession(filePath) {
|
|
|
2981
3030
|
durationMs,
|
|
2982
3031
|
lastActivity: lastTsMs !== undefined ? new Date(lastTsMs).toISOString() : undefined,
|
|
2983
3032
|
contentText: userTexts.length > 0 ? userTexts.join('\n') : undefined,
|
|
3033
|
+
assistantText: assistantTexts.length > 0 ? assistantTexts.join('\n') : undefined,
|
|
2984
3034
|
};
|
|
2985
3035
|
}
|
|
2986
3036
|
/** Extract plain text from a Droid message content field (Anthropic-shaped blocks). */
|
|
@@ -3026,6 +3076,7 @@ export function initClaudeParseState() {
|
|
|
3026
3076
|
lastTsMs: undefined,
|
|
3027
3077
|
seenAssistantIds: new Set(),
|
|
3028
3078
|
userTexts: [],
|
|
3079
|
+
assistantTexts: [],
|
|
3029
3080
|
sawPrCreate: false,
|
|
3030
3081
|
prUrl: undefined,
|
|
3031
3082
|
prNumber: undefined,
|
|
@@ -3217,6 +3268,9 @@ export function applyClaudeLine(state, parsed) {
|
|
|
3217
3268
|
return;
|
|
3218
3269
|
state.seenAssistantIds.add(logicalId);
|
|
3219
3270
|
state.messageCount++;
|
|
3271
|
+
const assistantText = extractClaudeAssistantText(parsed);
|
|
3272
|
+
if (assistantText)
|
|
3273
|
+
state.assistantTexts.push(assistantText);
|
|
3220
3274
|
const usageObj = parsed.message?.usage || parsed.usage;
|
|
3221
3275
|
const usage = getClaudeUsageTotal(usageObj);
|
|
3222
3276
|
if (usage !== null) {
|
|
@@ -3268,15 +3322,10 @@ export function finalizeClaudeScan(state) {
|
|
|
3268
3322
|
// A topic is the first meaningful prompt. Harness-owned names travel in the
|
|
3269
3323
|
// separate label field so consumers can replace an early topic once Claude's
|
|
3270
3324
|
// generated title (or a later `/rename`) arrives.
|
|
3271
|
-
// Claude
|
|
3272
|
-
//
|
|
3273
|
-
//
|
|
3274
|
-
|
|
3275
|
-
// only for that injected line, so collapse it to `/<skill>`. A `/rename`
|
|
3276
|
-
// (`custom-title`) is the user's own words and is never rewritten.
|
|
3277
|
-
const generated = classifyUserPrompt(state.aiTitle ?? '');
|
|
3278
|
-
const label = state.customTitle
|
|
3279
|
-
|| (generated.kind === 'skill' ? generated.clean : state.aiTitle);
|
|
3325
|
+
// Generated titles (Claude `ai-title`, Cursor `chatMeta.title`) go through
|
|
3326
|
+
// one shared cleaner so a skill-preamble echo collapses to `/<skill>`. A
|
|
3327
|
+
// `/rename` (`custom-title`) is the user's own words and is never rewritten.
|
|
3328
|
+
const label = state.customTitle || cleanGeneratedSessionLabel(state.aiTitle);
|
|
3280
3329
|
const worktree = detectWorktree(state.cwd, state.gitBranch);
|
|
3281
3330
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3282
3331
|
return {
|
|
@@ -3300,6 +3349,7 @@ export function finalizeClaudeScan(state) {
|
|
|
3300
3349
|
durationMs,
|
|
3301
3350
|
lastActivity: state.lastTsMs !== undefined ? new Date(state.lastTsMs).toISOString() : undefined,
|
|
3302
3351
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
3352
|
+
assistantText: state.assistantTexts.length > 0 ? state.assistantTexts.join('\n') : undefined,
|
|
3303
3353
|
prUrl: state.prUrl,
|
|
3304
3354
|
prNumber: state.prNumber,
|
|
3305
3355
|
worktreeSlug: worktree?.slug,
|
|
@@ -3354,7 +3404,7 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3354
3404
|
: allIds;
|
|
3355
3405
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3356
3406
|
return {
|
|
3357
|
-
v:
|
|
3407
|
+
v: 5,
|
|
3358
3408
|
subAgents: state.subAgents,
|
|
3359
3409
|
backgroundShells: state.backgroundShells,
|
|
3360
3410
|
offset,
|
|
@@ -3396,6 +3446,7 @@ export function serializeClaudeParserState(state, offset, jsonlDroppingOversized
|
|
|
3396
3446
|
// not be persisted.
|
|
3397
3447
|
ticketId: ticket?.id,
|
|
3398
3448
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
3449
|
+
assistantContentText: state.assistantTexts.length > 0 ? state.assistantTexts.join('\n') : undefined,
|
|
3399
3450
|
checklistEvents: state.checklistEvents,
|
|
3400
3451
|
recentDirectoriesTouched: state.recentDirectoriesTouched,
|
|
3401
3452
|
toolCalls: state.toolCollector.snapshot(),
|
|
@@ -3454,6 +3505,9 @@ export function hydrateClaudeParseState(prior) {
|
|
|
3454
3505
|
lastTsMs: prior.lastTsMs,
|
|
3455
3506
|
seenAssistantIds: seen,
|
|
3456
3507
|
userTexts: prior.contentText !== undefined && prior.contentText.length > 0 ? [prior.contentText] : [],
|
|
3508
|
+
assistantTexts: prior.assistantContentText !== undefined && prior.assistantContentText.length > 0
|
|
3509
|
+
? [prior.assistantContentText]
|
|
3510
|
+
: [],
|
|
3457
3511
|
sawPrCreate: prior.sawPrCreate,
|
|
3458
3512
|
prUrl: prior.prUrl,
|
|
3459
3513
|
prNumber: prior.prNumber,
|
|
@@ -3597,7 +3651,7 @@ function parsePriorClaudeState(row) {
|
|
|
3597
3651
|
return null;
|
|
3598
3652
|
try {
|
|
3599
3653
|
const parsed = JSON.parse(row.parserState);
|
|
3600
|
-
if (parsed?.v !==
|
|
3654
|
+
if (parsed?.v !== 5 || typeof parsed.offset !== 'number' || parsed.toolCalls?.v !== 1)
|
|
3601
3655
|
return null;
|
|
3602
3656
|
return parsed;
|
|
3603
3657
|
}
|
|
@@ -3634,6 +3688,7 @@ export function initCodexParseState() {
|
|
|
3634
3688
|
firstTsMs: undefined,
|
|
3635
3689
|
lastTsMs: undefined,
|
|
3636
3690
|
userTexts: [],
|
|
3691
|
+
assistantTexts: [],
|
|
3637
3692
|
sawPrCreate: false,
|
|
3638
3693
|
prUrl: undefined,
|
|
3639
3694
|
prNumber: undefined,
|
|
@@ -3743,6 +3798,9 @@ function applyCodexLine(state, parsed) {
|
|
|
3743
3798
|
if (!state.topic)
|
|
3744
3799
|
state.topic = extractSessionTopic(text);
|
|
3745
3800
|
}
|
|
3801
|
+
else {
|
|
3802
|
+
state.assistantTexts.push(text);
|
|
3803
|
+
}
|
|
3746
3804
|
return;
|
|
3747
3805
|
}
|
|
3748
3806
|
if (parsed.type === 'event_msg' && parsed.payload?.type === 'token_count') {
|
|
@@ -3818,6 +3876,7 @@ function finalizeCodexScan(state) {
|
|
|
3818
3876
|
durationMs,
|
|
3819
3877
|
lastActivity: state.lastTsMs !== undefined ? new Date(state.lastTsMs).toISOString() : undefined,
|
|
3820
3878
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
3879
|
+
assistantText: state.assistantTexts.length > 0 ? state.assistantTexts.join('\n') : undefined,
|
|
3821
3880
|
prUrl: state.prUrl,
|
|
3822
3881
|
prNumber: state.prNumber,
|
|
3823
3882
|
worktreeSlug: worktree?.slug,
|
|
@@ -3861,7 +3920,7 @@ async function scanCodexSession(filePath) {
|
|
|
3861
3920
|
export function serializeCodexParserState(state, offset, jsonlDroppingOversizedLine = false) {
|
|
3862
3921
|
const ticket = detectTicket(state.userTexts.join('\n') || undefined, state.gitBranch);
|
|
3863
3922
|
return {
|
|
3864
|
-
v:
|
|
3923
|
+
v: 3,
|
|
3865
3924
|
offset,
|
|
3866
3925
|
jsonlDroppingOversizedLine: jsonlDroppingOversizedLine || undefined,
|
|
3867
3926
|
sessionId: state.sessionId,
|
|
@@ -3888,6 +3947,7 @@ export function serializeCodexParserState(state, offset, jsonlDroppingOversizedL
|
|
|
3888
3947
|
// be persisted.
|
|
3889
3948
|
ticketId: ticket?.id,
|
|
3890
3949
|
contentText: state.userTexts.length > 0 ? state.userTexts.join('\n') : undefined,
|
|
3950
|
+
assistantContentText: state.assistantTexts.length > 0 ? state.assistantTexts.join('\n') : undefined,
|
|
3891
3951
|
checklistEvents: state.checklistEvents,
|
|
3892
3952
|
recentDirectoriesTouched: state.recentDirectoriesTouched,
|
|
3893
3953
|
toolCalls: state.toolCollector.snapshot(),
|
|
@@ -3918,6 +3978,9 @@ function hydrateCodexParseState(prior) {
|
|
|
3918
3978
|
firstTsMs: prior.firstTsMs,
|
|
3919
3979
|
lastTsMs: prior.lastTsMs,
|
|
3920
3980
|
userTexts: prior.contentText !== undefined && prior.contentText.length > 0 ? [prior.contentText] : [],
|
|
3981
|
+
assistantTexts: prior.assistantContentText !== undefined && prior.assistantContentText.length > 0
|
|
3982
|
+
? [prior.assistantContentText]
|
|
3983
|
+
: [],
|
|
3921
3984
|
sawPrCreate: prior.sawPrCreate,
|
|
3922
3985
|
prUrl: prior.prUrl,
|
|
3923
3986
|
prNumber: prior.prNumber,
|
|
@@ -4048,7 +4111,7 @@ function parsePriorCodexState(row) {
|
|
|
4048
4111
|
return null;
|
|
4049
4112
|
try {
|
|
4050
4113
|
const parsed = JSON.parse(row.parserState);
|
|
4051
|
-
if (parsed?.v !==
|
|
4114
|
+
if (parsed?.v !== 3 || typeof parsed.offset !== 'number' || parsed.toolCalls?.v !== 1)
|
|
4052
4115
|
return null;
|
|
4053
4116
|
return parsed;
|
|
4054
4117
|
}
|
|
@@ -4149,6 +4212,29 @@ function extractClaudeUserText(parsed) {
|
|
|
4149
4212
|
function isLocalCommandMessage(text) {
|
|
4150
4213
|
return /<local-command-caveat>|<bash-(input|stdout|stderr)>/i.test(text);
|
|
4151
4214
|
}
|
|
4215
|
+
/**
|
|
4216
|
+
* Extract the assistant's own answer text from a Claude JSONL assistant event —
|
|
4217
|
+
* the reply text blocks, skipping tool_use/tool_result blocks and an
|
|
4218
|
+
* interrupted turn, mirroring {@link extractClaudeUserText}'s filters. Unlike
|
|
4219
|
+
* the user side (which keeps only the first text block), a single assistant
|
|
4220
|
+
* turn can legitimately carry multiple text blocks around tool calls, so every
|
|
4221
|
+
* one is joined.
|
|
4222
|
+
*/
|
|
4223
|
+
function extractClaudeAssistantText(parsed) {
|
|
4224
|
+
const content = parsed.message?.content;
|
|
4225
|
+
if (typeof content === 'string') {
|
|
4226
|
+
const text = content.trim();
|
|
4227
|
+
return text && !text.startsWith('[Request interrupted') ? text : undefined;
|
|
4228
|
+
}
|
|
4229
|
+
if (!Array.isArray(content))
|
|
4230
|
+
return undefined;
|
|
4231
|
+
const text = content
|
|
4232
|
+
.filter((block) => block.type === 'text')
|
|
4233
|
+
.map((block) => String(block.text || '').trim())
|
|
4234
|
+
.filter((value) => value && !value.startsWith('[Request interrupted'))
|
|
4235
|
+
.join('\n');
|
|
4236
|
+
return text || undefined;
|
|
4237
|
+
}
|
|
4152
4238
|
/** Sum all token usage fields from a Claude assistant message's usage object. */
|
|
4153
4239
|
function getClaudeUsageTotal(usage) {
|
|
4154
4240
|
if (!usage || typeof usage !== 'object')
|
|
@@ -4313,7 +4399,7 @@ async function scanCursorIncremental(onProgress) {
|
|
|
4313
4399
|
const result = readCursorMeta(filePath, currentVersion);
|
|
4314
4400
|
if (result && !seen.has(result.meta.id)) {
|
|
4315
4401
|
seen.add(result.meta.id);
|
|
4316
|
-
entries.push({ meta: result.meta, content: result.content, scan, events: result.events });
|
|
4402
|
+
entries.push({ meta: result.meta, content: result.content, assistantContent: result.assistantContent, scan, events: result.events });
|
|
4317
4403
|
}
|
|
4318
4404
|
else {
|
|
4319
4405
|
touched.push({ filePath, scan });
|
|
@@ -4390,9 +4476,12 @@ export function readCursorMeta(filePath, currentVersion) {
|
|
|
4390
4476
|
const userTexts = events
|
|
4391
4477
|
.filter((event) => event.type === 'message' && event.role === 'user' && event.content)
|
|
4392
4478
|
.map((event) => event.content);
|
|
4479
|
+
const assistantTexts = events
|
|
4480
|
+
.filter((event) => event.type === 'message' && event.role === 'assistant' && event.content)
|
|
4481
|
+
.map((event) => event.content);
|
|
4393
4482
|
const firstUserText = userTexts[0];
|
|
4394
|
-
const title = typeof chatMeta?.title === 'string'
|
|
4395
|
-
? chatMeta.title
|
|
4483
|
+
const title = typeof chatMeta?.title === 'string'
|
|
4484
|
+
? cleanGeneratedSessionLabel(chatMeta.title)
|
|
4396
4485
|
: undefined;
|
|
4397
4486
|
const meta = {
|
|
4398
4487
|
id: sessionId,
|
|
@@ -4409,7 +4498,7 @@ export function readCursorMeta(filePath, currentVersion) {
|
|
|
4409
4498
|
messageCount: events.filter((event) => event.type === 'message').length,
|
|
4410
4499
|
todos: extractTodoProgressFromEvents(events),
|
|
4411
4500
|
};
|
|
4412
|
-
return { meta, content: userTexts.join('\n'), events };
|
|
4501
|
+
return { meta, content: userTexts.join('\n'), assistantContent: assistantTexts.join('\n'), events };
|
|
4413
4502
|
}
|
|
4414
4503
|
// ---------------------------------------------------------------------------
|
|
4415
4504
|
// Kimi
|