@evomap/evolver-runtime-adapters 2.0.0-beta.1 → 2.0.0-beta.4

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.
@@ -3,6 +3,7 @@ export declare const claudeCodeAdapter: SessionLogAdapter;
3
3
  export declare const cursorAdapter: SessionLogAdapter;
4
4
  export declare const codexAdapter: SessionLogAdapter;
5
5
  export declare const geminiAdapter: SessionLogAdapter;
6
+ export declare const antigravityAdapter: SessionLogAdapter;
6
7
  export declare const genericChatAdapter: SessionLogAdapter;
7
8
  export declare const kimiAdapter: SessionLogAdapter;
8
9
  export declare const ADAPTERS: readonly SessionLogAdapter[];
package/dist/adapters.js CHANGED
@@ -1,4 +1,4 @@
1
- import { parseJsonlLines, extractContent, isMetaText, correlateToolNames } from './types.js';
1
+ import { parseJsonlLines, extractContent, isMetaText, correlateToolNames, stripUtf8Bom } from './types.js';
2
2
  function isRecord(value) {
3
3
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
4
4
  }
@@ -24,7 +24,7 @@ function firstString(record, keys) {
24
24
  function parseJsonish(value) {
25
25
  if (typeof value !== 'string')
26
26
  return value;
27
- const trimmed = value.trim();
27
+ const trimmed = stripUtf8Bom(value).trim();
28
28
  if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('[')))
29
29
  return value;
30
30
  try {
@@ -142,9 +142,9 @@ function withSourceMetadata(turns, source) {
142
142
  }
143
143
  // VERIFICATION BAR: an adapter only ships once its parse() is checked against a REAL session log from that tool
144
144
  // — a trimmed, sanitized sample + a golden test (see the golden tests in adapters.test.ts). A guessed schema is
145
- // worse than no adapter: it silently yields 0 turns on real logs while looking supported. Today: claude-code +
146
- // codex + cursor are verified (codex against codex-cli 0.137.0 rollout logs; cursor against real
147
- // ~/.cursor/projects/*/agent-transcripts/*.jsonl). opencode/kiro stay removed until each has a real-log golden test.
145
+ // worse than no adapter: it silently yields 0 turns on real logs while looking supported. Today: claude-code,
146
+ // codex, cursor, Gemini, and Antigravity are verified against trimmed sanitized real-log samples. opencode/kiro
147
+ // stay removed until each has a real-log golden test.
148
148
  // claude-code AND cursor share the Anthropic content-block transcript shape: one JSONL record per turn,
149
149
  // { role|type: 'user'|'assistant', message: { content: [ {type:'text',text} | {type:'tool_use',name,id?} |
150
150
  // {type:'tool_result',...} ] } }. correlateToolNames backfills a tool_result's tool name from its tool_use id.
@@ -559,7 +559,7 @@ function geminiSessionFromValue(value) {
559
559
  };
560
560
  }
561
561
  function geminiSessions(chunk) {
562
- const trimmed = chunk.trim();
562
+ const trimmed = stripUtf8Bom(chunk).trim();
563
563
  if (!trimmed)
564
564
  return [];
565
565
  // Gemini session files are a single JSON document. Tolerate JSONL-of-sessions too.
@@ -591,6 +591,192 @@ export const geminiAdapter = {
591
591
  parseSession: (chunk) => geminiSessions(chunk)[0] ?? { turns: [] },
592
592
  parseSessions: geminiSessions,
593
593
  };
594
+ // Antigravity persists one JSON record per event at
595
+ // ~/.gemini/{antigravity,antigravity-ide}/brain/<uuid>/.system_generated/logs/transcript.jsonl.
596
+ // Verified real records carry {type,status,source,step_index,created_at,content}; PLANNER_RESPONSE additionally
597
+ // carries plaintext `thinking` and tool_calls: [{name,args}]. The file can append a newer snapshot of an earlier
598
+ // step, so the last terminal record for each type + step_index is authoritative.
599
+ const ANTIGRAVITY_TRANSCRIPT_PATH = /(?:^|[/\\])\.gemini[/\\](?:antigravity|antigravity-ide)[/\\]brain[/\\]([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})[/\\]\.system_generated[/\\]logs[/\\]transcript\.jsonl$/i;
600
+ const ANTIGRAVITY_TERMINAL_STATUSES = new Set(['DONE', 'ERROR', 'CANCELED']);
601
+ const ANTIGRAVITY_TOOL_RESULT_TYPES = new Set([
602
+ 'ASK_QUESTION',
603
+ 'CODE_ACTION',
604
+ 'GENERIC',
605
+ 'GREP_SEARCH',
606
+ 'LIST_DIRECTORY',
607
+ 'RUN_COMMAND',
608
+ 'SEARCH_WEB',
609
+ 'VIEW_FILE',
610
+ ]);
611
+ function antigravityStatus(record) {
612
+ return typeof record['status'] === 'string' ? record['status'].toUpperCase() : '';
613
+ }
614
+ function antigravityRecords(chunk) {
615
+ const selected = new Map();
616
+ parseJsonlLines(chunk).forEach((record, rowIndex) => {
617
+ const type = typeof record['type'] === 'string' ? record['type'] : '';
618
+ const stepIndex = finiteNumber(record['step_index']);
619
+ const key = type && stepIndex !== undefined ? `${type}\u0000${stepIndex}` : `row\u0000${rowIndex}`;
620
+ const current = selected.get(key);
621
+ if (!current) {
622
+ selected.set(key, { record, rowIndex });
623
+ return;
624
+ }
625
+ const currentTerminal = ANTIGRAVITY_TERMINAL_STATUSES.has(antigravityStatus(current.record));
626
+ const nextTerminal = ANTIGRAVITY_TERMINAL_STATUSES.has(antigravityStatus(record));
627
+ if (!currentTerminal || nextTerminal) {
628
+ // Snapshot rows are partial: omitted tool_calls carry forward, while an explicit value (including []) wins.
629
+ // Keep the first logical position so a late terminal snapshot cannot move a tool call after its result.
630
+ const selectedRecord = type === 'PLANNER_RESPONSE'
631
+ && !hasOwn(record, 'tool_calls')
632
+ && hasOwn(current.record, 'tool_calls')
633
+ ? { ...record, tool_calls: current.record['tool_calls'] }
634
+ : record;
635
+ selected.set(key, { record: selectedRecord, rowIndex: current.rowIndex });
636
+ }
637
+ });
638
+ return [...selected.values()]
639
+ .sort((a, b) => a.rowIndex - b.rowIndex)
640
+ .map(({ record }) => record);
641
+ }
642
+ function antigravityRecordMetadata(record) {
643
+ const metadata = {};
644
+ for (const key of ['type', 'status', 'source', 'step_index', 'created_at']) {
645
+ if (record[key] !== undefined)
646
+ metadata[key] = record[key];
647
+ }
648
+ return metadata;
649
+ }
650
+ function antigravityRecordText(record, key = 'content') {
651
+ return typeof record[key] === 'string' ? record[key] : '';
652
+ }
653
+ function antigravityFailure(record) {
654
+ const type = typeof record['type'] === 'string' ? record['type'] : '';
655
+ const status = antigravityStatus(record);
656
+ if (type !== 'ERROR_MESSAGE' && status !== 'ERROR' && status !== 'CANCELED')
657
+ return undefined;
658
+ return antigravityRecordText(record, 'error')
659
+ || antigravityRecordText(record)
660
+ || `Antigravity ${type || 'record'} ${status || 'ERROR'}`;
661
+ }
662
+ function annotateAntigravityTurns(record, turns) {
663
+ const timestamp = typeof record['created_at'] === 'string' ? record['created_at'] : undefined;
664
+ const metadata = antigravityRecordMetadata(record);
665
+ const errorMessage = antigravityFailure(record);
666
+ return turns.map((turn) => ({
667
+ ...turn,
668
+ ...(timestamp ? { timestamp } : {}),
669
+ ...(errorMessage && !turn.errorMessage ? { errorMessage } : {}),
670
+ metadata,
671
+ sourceRecord: record,
672
+ rawRow: record,
673
+ }));
674
+ }
675
+ function antigravityResultTypeForTool(toolName) {
676
+ switch (toolName.toLowerCase()) {
677
+ case 'ask_question': return 'ASK_QUESTION';
678
+ case 'grep_search': return 'GREP_SEARCH';
679
+ case 'list_dir': return 'LIST_DIRECTORY';
680
+ case 'run_command': return 'RUN_COMMAND';
681
+ case 'search_web': return 'SEARCH_WEB';
682
+ case 'view_file': return 'VIEW_FILE';
683
+ case 'multi_replace_file_content':
684
+ case 'replace_file_content':
685
+ case 'write_to_file': return 'CODE_ACTION';
686
+ default: return 'GENERIC';
687
+ }
688
+ }
689
+ function antigravityFallbackToolName(resultType) {
690
+ switch (resultType) {
691
+ case 'ASK_QUESTION': return 'ask_question';
692
+ case 'CODE_ACTION': return 'code_action';
693
+ case 'GREP_SEARCH': return 'grep_search';
694
+ case 'LIST_DIRECTORY': return 'list_dir';
695
+ case 'RUN_COMMAND': return 'run_command';
696
+ case 'SEARCH_WEB': return 'search_web';
697
+ case 'VIEW_FILE': return 'view_file';
698
+ default: return 'generic';
699
+ }
700
+ }
701
+ function antigravityTranscriptFromRecords(records) {
702
+ const turns = [];
703
+ const pendingTools = [];
704
+ records.forEach((record, rowIndex) => {
705
+ const type = typeof record['type'] === 'string' ? record['type'] : '';
706
+ const content = antigravityRecordText(record);
707
+ const recordTurns = [];
708
+ if (type === 'USER_INPUT') {
709
+ recordTurns.push({ role: 'user', text: content, isMeta: isMetaText(content) });
710
+ }
711
+ else if (type === 'PLANNER_RESPONSE') {
712
+ const thinking = antigravityRecordText(record, 'thinking');
713
+ if (thinking)
714
+ recordTurns.push({ role: 'assistant', text: thinking, reasoning: true, isMeta: false });
715
+ if (content)
716
+ recordTurns.push({ role: 'assistant', text: content, isMeta: isMetaText(content) });
717
+ const toolCalls = Array.isArray(record['tool_calls']) ? record['tool_calls'] : [];
718
+ toolCalls.forEach((value, ordinal) => {
719
+ if (!isRecord(value))
720
+ return;
721
+ const toolName = typeof value['name'] === 'string' && value['name'] ? value['name'] : 'antigravity_tool';
722
+ const stepIndex = finiteNumber(record['step_index']);
723
+ const toolUseId = `antigravity:${stepIndex ?? `row-${rowIndex}`}:${ordinal}`;
724
+ pendingTools.push({ toolName, toolUseId, resultType: antigravityResultTypeForTool(toolName) });
725
+ recordTurns.push({
726
+ role: 'assistant',
727
+ text: '',
728
+ toolName,
729
+ toolUseId,
730
+ ...(hasOwn(value, 'args') ? { toolInput: value['args'] } : {}),
731
+ isMeta: false,
732
+ });
733
+ });
734
+ }
735
+ else if (ANTIGRAVITY_TOOL_RESULT_TYPES.has(type)) {
736
+ const pendingIndex = pendingTools.findIndex((pending) => pending.resultType === type);
737
+ const pending = pendingIndex >= 0 ? pendingTools.splice(pendingIndex, 1)[0] : undefined;
738
+ recordTurns.push({
739
+ role: 'tool',
740
+ text: '',
741
+ toolName: pending?.toolName ?? antigravityFallbackToolName(type),
742
+ ...(pending ? { toolUseId: pending.toolUseId } : {}),
743
+ toolResult: content,
744
+ isMeta: false,
745
+ });
746
+ }
747
+ else if (type === 'ERROR_MESSAGE') {
748
+ const errorMessage = antigravityFailure(record);
749
+ recordTurns.push({ role: 'system', text: content || errorMessage, errorMessage, isMeta: true });
750
+ }
751
+ else if (type === 'SYSTEM_MESSAGE' || type === 'CONVERSATION_HISTORY' || type === 'CHECKPOINT') {
752
+ recordTurns.push({ role: 'system', text: content, isMeta: true });
753
+ }
754
+ turns.push(...annotateAntigravityTurns(record, recordTurns));
755
+ });
756
+ return turns;
757
+ }
758
+ function antigravitySession(chunk) {
759
+ const records = antigravityRecords(chunk);
760
+ const startedAt = records.find((record) => typeof record['created_at'] === 'string')?.['created_at'];
761
+ return {
762
+ turns: antigravityTranscriptFromRecords(records),
763
+ provider: 'antigravity',
764
+ clientSource: 'antigravity',
765
+ ...(typeof startedAt === 'string' ? { startedAt } : {}),
766
+ rawRows: records,
767
+ };
768
+ }
769
+ export const antigravityAdapter = {
770
+ agent: 'antigravity',
771
+ detect: (path) => ANTIGRAVITY_TRANSCRIPT_PATH.test(path),
772
+ sessionIdFromPath: (path) => ANTIGRAVITY_TRANSCRIPT_PATH.exec(path)?.[1],
773
+ parse: (chunk) => antigravitySession(chunk).turns,
774
+ parseSession: antigravitySession,
775
+ parseSessions: (chunk) => {
776
+ const session = antigravitySession(chunk);
777
+ return session.turns.length > 0 ? [session] : [];
778
+ },
779
+ };
594
780
  // ── generic chat-transcript adapter ──────────────────────────────────────────
595
781
  // The per-tool adapters above are gated on a REAL private-log golden (a guessed private schema silently yields 0
596
782
  // turns). This one is different ON PURPOSE: it targets the DOCUMENTED, stable interchange format — OpenAI
@@ -935,7 +1121,7 @@ function applyGenericSessionMetadata(turns, metadata) {
935
1121
  * chat-completions request-body shape), or a SINGLE message object (incl. pretty-printed multi-line — which is
936
1122
  * not valid JSONL, so it must be handled here, not fall through). */
937
1123
  function genericChatRecords(chunk) {
938
- const trimmed = chunk.trim();
1124
+ const trimmed = stripUtf8Bom(chunk).trim();
939
1125
  if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
940
1126
  try {
941
1127
  const parsed = JSON.parse(trimmed);
@@ -970,7 +1156,7 @@ function genericChatSessionsFromValue(value) {
970
1156
  return session.turns.length > 0 ? [session] : [];
971
1157
  }
972
1158
  function genericChatSessions(chunk) {
973
- const trimmed = chunk.trim();
1159
+ const trimmed = stripUtf8Bom(chunk).trim();
974
1160
  if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
975
1161
  try {
976
1162
  const parsed = JSON.parse(trimmed);
@@ -1032,6 +1218,6 @@ export const kimiAdapter = {
1032
1218
  parseSessions: kimiSessions,
1033
1219
  };
1034
1220
  // Only verified adapters are registered. opencode/kiro live in git history — re-add with a real-log fixture.
1035
- // genericChatAdapter is LAST so any tool-specific path (claude/cursor/codex/gemini/kimi) resolves first.
1036
- export const ADAPTERS = [claudeCodeAdapter, codexAdapter, cursorAdapter, geminiAdapter, kimiAdapter, genericChatAdapter];
1221
+ // genericChatAdapter is LAST so any tool-specific path (claude/cursor/codex/gemini/antigravity/kimi) resolves first.
1222
+ export const ADAPTERS = [claudeCodeAdapter, codexAdapter, cursorAdapter, geminiAdapter, antigravityAdapter, kimiAdapter, genericChatAdapter];
1037
1223
  export function adapterForPath(path) { return ADAPTERS.find((a) => a.detect(path)); }
package/dist/types.d.ts CHANGED
@@ -70,6 +70,8 @@ export interface NormalizedSession {
70
70
  export interface SessionLogAdapter {
71
71
  readonly agent: string;
72
72
  detect(path: string): boolean;
73
+ /** Derive a stable runtime session id when the transcript itself does not carry one. */
74
+ sessionIdFromPath?(path: string): string | undefined;
73
75
  parse(rawChunk: string): NormalizedTurn[];
74
76
  parseSession?(rawChunk: string): NormalizedSession;
75
77
  parseSessions?(rawChunk: string): NormalizedSession[];
@@ -79,6 +81,7 @@ export interface JsonlParseStats {
79
81
  rowsRead: number;
80
82
  invalidJson: number;
81
83
  }
84
+ export declare function stripUtf8Bom(value: string): string;
82
85
  export declare const META_MARKERS: string[];
83
86
  export declare function isMetaText(text: string): boolean;
84
87
  export declare function parseJsonlLinesWithStats(chunk: string): {
package/dist/types.js CHANGED
@@ -1,3 +1,6 @@
1
+ export function stripUtf8Bom(value) {
2
+ return value.replace(/^\uFEFF/, '');
3
+ }
1
4
  export const META_MARKERS = ['HEARTBEAT_OK', 'NO_REPLY', 'NO_RESPONSE_NEEDED', '[META]'];
2
5
  export function isMetaText(text) {
3
6
  const t = text.trim();
@@ -7,11 +10,12 @@ export function parseJsonlLinesWithStats(chunk) {
7
10
  const out = [];
8
11
  const stats = { rowsScanned: 0, rowsRead: 0, invalidJson: 0 };
9
12
  for (const l of chunk.split('\n')) {
10
- if (!l.trim())
13
+ const line = stats.rowsScanned === 0 ? stripUtf8Bom(l) : l;
14
+ if (!line.trim())
11
15
  continue;
12
16
  stats.rowsScanned += 1;
13
17
  try {
14
- const o = JSON.parse(l);
18
+ const o = JSON.parse(line);
15
19
  if (o && typeof o === 'object') {
16
20
  out.push(o);
17
21
  stats.rowsRead += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver-runtime-adapters",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "五 runtime 会话日志适配器 (CC/codex/cursor/kiro/opencode)",
@@ -12,12 +12,17 @@
12
12
  "default": "./dist/index.js"
13
13
  }
14
14
  },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/EvoMap/evolver.git"
18
+ },
15
19
  "publishConfig": {
16
20
  "access": "public",
17
21
  "tag": "v2-beta"
18
22
  },
19
23
  "files": [
20
24
  "dist/",
25
+ "assets/",
21
26
  "README.md",
22
27
  "package.json"
23
28
  ]