cogpit-memory 0.4.1 → 0.4.2
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/dist/cli.js +75 -3
- package/dist/index.js +75 -3
- package/dist/lib/asyncAnswers.d.ts +10 -0
- package/dist/lib/automatedPrompts.d.ts +4 -0
- package/dist/lib/types.d.ts +9 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1871,6 +1871,62 @@ function appendAssistantText(turn, text, timestamp) {
|
|
|
1871
1871
|
turn.contentBlocks.push({ kind: "text", text: [text], timestamp });
|
|
1872
1872
|
}
|
|
1873
1873
|
|
|
1874
|
+
// src/lib/automatedPrompts.ts
|
|
1875
|
+
var CREW_CHAT_DELIVERY_RE = /^Crew chat · ([^\n]+) · message (\d+)(?:\n|$)/;
|
|
1876
|
+
var TASK_WAKEUP_START_RE = /^Delegated task \S+ (?:running|completed|error|cancelled|needs attention)\. Child session: /;
|
|
1877
|
+
function isReaderPrompt(text) {
|
|
1878
|
+
const trimmed = text.trim();
|
|
1879
|
+
if (!trimmed) return false;
|
|
1880
|
+
if (CREW_CHAT_DELIVERY_RE.test(trimmed) || TASK_WAKEUP_START_RE.test(trimmed)) return false;
|
|
1881
|
+
return parseAgentEnvelope(trimmed).sender === null;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// src/lib/asyncAnswers.ts
|
|
1885
|
+
function promptText(content) {
|
|
1886
|
+
if (content === null) return "";
|
|
1887
|
+
if (typeof content === "string") return content;
|
|
1888
|
+
return content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
1889
|
+
}
|
|
1890
|
+
function linkAsyncAnswers(turns) {
|
|
1891
|
+
const answers = /* @__PURE__ */ new Map();
|
|
1892
|
+
let open4 = [];
|
|
1893
|
+
const answer = (text) => {
|
|
1894
|
+
if (open4.length === 0 || !isReaderPrompt(text)) return;
|
|
1895
|
+
for (const id of open4) answers.set(id, text.trim());
|
|
1896
|
+
open4 = [];
|
|
1897
|
+
};
|
|
1898
|
+
for (const turn of turns) {
|
|
1899
|
+
answer(promptText(turn.userMessage));
|
|
1900
|
+
for (const block of turn.contentBlocks) {
|
|
1901
|
+
if (block.kind === "queued_prompt") answer(block.content);
|
|
1902
|
+
else if (block.kind === "tool_calls") {
|
|
1903
|
+
for (const call of block.toolCalls) if (call.asyncQuestion) open4.push(call.id);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
const stale = (call) => call.asyncQuestion === true && call.asyncAnswer !== answers.get(call.id);
|
|
1908
|
+
const relinked = /* @__PURE__ */ new Map();
|
|
1909
|
+
const relink = (call) => {
|
|
1910
|
+
if (!stale(call)) return call;
|
|
1911
|
+
let next = relinked.get(call);
|
|
1912
|
+
if (!next) {
|
|
1913
|
+
const { asyncAnswer: _previous, ...rest } = call;
|
|
1914
|
+
const reply = answers.get(call.id);
|
|
1915
|
+
next = reply === void 0 ? rest : { ...rest, asyncAnswer: reply };
|
|
1916
|
+
relinked.set(call, next);
|
|
1917
|
+
}
|
|
1918
|
+
return next;
|
|
1919
|
+
};
|
|
1920
|
+
return turns.map((turn) => {
|
|
1921
|
+
if (!turn.toolCalls.some(stale)) return turn;
|
|
1922
|
+
return {
|
|
1923
|
+
...turn,
|
|
1924
|
+
toolCalls: turn.toolCalls.map(relink),
|
|
1925
|
+
contentBlocks: turn.contentBlocks.map((block) => block.kind === "tool_calls" ? { ...block, toolCalls: block.toolCalls.map(relink) } : block)
|
|
1926
|
+
};
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1874
1930
|
// src/lib/codex-patches.ts
|
|
1875
1931
|
function isApplyPatchText(value) {
|
|
1876
1932
|
return value.includes("*** Begin Patch") && value.includes("*** End Patch");
|
|
@@ -2652,6 +2708,7 @@ function walkCodexRecords(records, options) {
|
|
|
2652
2708
|
}
|
|
2653
2709
|
let current = null;
|
|
2654
2710
|
let currentTurnId = null;
|
|
2711
|
+
let queuedPromptEcho = false;
|
|
2655
2712
|
let currentModel = metadata.model || null;
|
|
2656
2713
|
let lastTurnTimestamp = "";
|
|
2657
2714
|
let pendingCompaction = null;
|
|
@@ -2706,6 +2763,7 @@ function walkCodexRecords(records, options) {
|
|
|
2706
2763
|
recordIndex = index;
|
|
2707
2764
|
const payload = isObject3(record2.payload) ? record2.payload : void 0;
|
|
2708
2765
|
const timestamp = record2.timestamp ?? "";
|
|
2766
|
+
if (record2.type !== "event_msg") queuedPromptEcho = false;
|
|
2709
2767
|
if (record2.type === "compacted") {
|
|
2710
2768
|
current = commitTurn(current);
|
|
2711
2769
|
pendingCompaction = "Conversation compacted";
|
|
@@ -2723,11 +2781,17 @@ function walkCodexRecords(records, options) {
|
|
|
2723
2781
|
continue;
|
|
2724
2782
|
}
|
|
2725
2783
|
if (record2.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
|
|
2784
|
+
if (queuedPromptEcho) {
|
|
2785
|
+
queuedPromptEcho = false;
|
|
2786
|
+
continue;
|
|
2787
|
+
}
|
|
2726
2788
|
if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
|
|
2727
2789
|
current = commitTurn(current);
|
|
2728
2790
|
}
|
|
2791
|
+
const opensWindow = !sawTurnStart;
|
|
2729
2792
|
sawTurnStart = true;
|
|
2730
2793
|
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
2794
|
+
if (opensWindow) current.opensOnPrompt = true;
|
|
2731
2795
|
const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
|
|
2732
2796
|
(p) => typeof p === "string" && p.length > 0
|
|
2733
2797
|
);
|
|
@@ -2842,8 +2906,15 @@ function walkCodexRecords(records, options) {
|
|
|
2842
2906
|
appendAssistantText(current, extractMessageText(payload, "output_text"), timestamp);
|
|
2843
2907
|
continue;
|
|
2844
2908
|
}
|
|
2845
|
-
if (payload.type === "message" && payload.role === "user"
|
|
2909
|
+
if (payload.type === "message" && payload.role === "user") {
|
|
2846
2910
|
const text = normalizePromptText(extractMessageText(payload, "input_text"));
|
|
2911
|
+
if (current.userMessage !== null || current.contentBlocks.length > 0) {
|
|
2912
|
+
if (text) {
|
|
2913
|
+
current.contentBlocks.push({ kind: "queued_prompt", content: text, timestamp });
|
|
2914
|
+
queuedPromptEcho = true;
|
|
2915
|
+
}
|
|
2916
|
+
continue;
|
|
2917
|
+
}
|
|
2847
2918
|
const images = extractResponseMessageImages(payload);
|
|
2848
2919
|
const audio = extractResponseMessageAudio(payload);
|
|
2849
2920
|
if (text || images.length > 0 || audio.length > 0) {
|
|
@@ -3085,6 +3156,7 @@ function walkCodexRecords(records, options) {
|
|
|
3085
3156
|
}
|
|
3086
3157
|
}
|
|
3087
3158
|
commitTurn(current);
|
|
3159
|
+
const linkedTurns = linkAsyncAnswers(turns);
|
|
3088
3160
|
return {
|
|
3089
3161
|
turnStartIndices,
|
|
3090
3162
|
sessionId: metadata.sessionId,
|
|
@@ -3094,8 +3166,8 @@ function walkCodexRecords(records, options) {
|
|
|
3094
3166
|
slug: metadata.slug,
|
|
3095
3167
|
name: "",
|
|
3096
3168
|
model: metadata.model,
|
|
3097
|
-
turns,
|
|
3098
|
-
stats: options?.skipStats ? createEmptySessionStats(
|
|
3169
|
+
turns: linkedTurns,
|
|
3170
|
+
stats: options?.skipStats ? createEmptySessionStats(linkedTurns.length) : computeStats(linkedTurns),
|
|
3099
3171
|
rawMessages: options?.skipStats ? [] : records,
|
|
3100
3172
|
branchedFrom: metadata.branchedFrom,
|
|
3101
3173
|
agentKind: "codex"
|
package/dist/index.js
CHANGED
|
@@ -1880,6 +1880,62 @@ function appendAssistantText(turn, text, timestamp) {
|
|
|
1880
1880
|
turn.contentBlocks.push({ kind: "text", text: [text], timestamp });
|
|
1881
1881
|
}
|
|
1882
1882
|
|
|
1883
|
+
// src/lib/automatedPrompts.ts
|
|
1884
|
+
var CREW_CHAT_DELIVERY_RE = /^Crew chat · ([^\n]+) · message (\d+)(?:\n|$)/;
|
|
1885
|
+
var TASK_WAKEUP_START_RE = /^Delegated task \S+ (?:running|completed|error|cancelled|needs attention)\. Child session: /;
|
|
1886
|
+
function isReaderPrompt(text) {
|
|
1887
|
+
const trimmed = text.trim();
|
|
1888
|
+
if (!trimmed) return false;
|
|
1889
|
+
if (CREW_CHAT_DELIVERY_RE.test(trimmed) || TASK_WAKEUP_START_RE.test(trimmed)) return false;
|
|
1890
|
+
return parseAgentEnvelope(trimmed).sender === null;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// src/lib/asyncAnswers.ts
|
|
1894
|
+
function promptText(content) {
|
|
1895
|
+
if (content === null) return "";
|
|
1896
|
+
if (typeof content === "string") return content;
|
|
1897
|
+
return content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
1898
|
+
}
|
|
1899
|
+
function linkAsyncAnswers(turns) {
|
|
1900
|
+
const answers = /* @__PURE__ */ new Map();
|
|
1901
|
+
let open4 = [];
|
|
1902
|
+
const answer = (text) => {
|
|
1903
|
+
if (open4.length === 0 || !isReaderPrompt(text)) return;
|
|
1904
|
+
for (const id of open4) answers.set(id, text.trim());
|
|
1905
|
+
open4 = [];
|
|
1906
|
+
};
|
|
1907
|
+
for (const turn of turns) {
|
|
1908
|
+
answer(promptText(turn.userMessage));
|
|
1909
|
+
for (const block of turn.contentBlocks) {
|
|
1910
|
+
if (block.kind === "queued_prompt") answer(block.content);
|
|
1911
|
+
else if (block.kind === "tool_calls") {
|
|
1912
|
+
for (const call of block.toolCalls) if (call.asyncQuestion) open4.push(call.id);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
const stale = (call) => call.asyncQuestion === true && call.asyncAnswer !== answers.get(call.id);
|
|
1917
|
+
const relinked = /* @__PURE__ */ new Map();
|
|
1918
|
+
const relink = (call) => {
|
|
1919
|
+
if (!stale(call)) return call;
|
|
1920
|
+
let next = relinked.get(call);
|
|
1921
|
+
if (!next) {
|
|
1922
|
+
const { asyncAnswer: _previous, ...rest } = call;
|
|
1923
|
+
const reply = answers.get(call.id);
|
|
1924
|
+
next = reply === void 0 ? rest : { ...rest, asyncAnswer: reply };
|
|
1925
|
+
relinked.set(call, next);
|
|
1926
|
+
}
|
|
1927
|
+
return next;
|
|
1928
|
+
};
|
|
1929
|
+
return turns.map((turn) => {
|
|
1930
|
+
if (!turn.toolCalls.some(stale)) return turn;
|
|
1931
|
+
return {
|
|
1932
|
+
...turn,
|
|
1933
|
+
toolCalls: turn.toolCalls.map(relink),
|
|
1934
|
+
contentBlocks: turn.contentBlocks.map((block) => block.kind === "tool_calls" ? { ...block, toolCalls: block.toolCalls.map(relink) } : block)
|
|
1935
|
+
};
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1883
1939
|
// src/lib/codex-patches.ts
|
|
1884
1940
|
function isApplyPatchText(value) {
|
|
1885
1941
|
return value.includes("*** Begin Patch") && value.includes("*** End Patch");
|
|
@@ -2661,6 +2717,7 @@ function walkCodexRecords(records, options) {
|
|
|
2661
2717
|
}
|
|
2662
2718
|
let current = null;
|
|
2663
2719
|
let currentTurnId = null;
|
|
2720
|
+
let queuedPromptEcho = false;
|
|
2664
2721
|
let currentModel = metadata.model || null;
|
|
2665
2722
|
let lastTurnTimestamp = "";
|
|
2666
2723
|
let pendingCompaction = null;
|
|
@@ -2715,6 +2772,7 @@ function walkCodexRecords(records, options) {
|
|
|
2715
2772
|
recordIndex = index;
|
|
2716
2773
|
const payload = isObject3(record2.payload) ? record2.payload : void 0;
|
|
2717
2774
|
const timestamp = record2.timestamp ?? "";
|
|
2775
|
+
if (record2.type !== "event_msg") queuedPromptEcho = false;
|
|
2718
2776
|
if (record2.type === "compacted") {
|
|
2719
2777
|
current = commitTurn(current);
|
|
2720
2778
|
pendingCompaction = "Conversation compacted";
|
|
@@ -2732,11 +2790,17 @@ function walkCodexRecords(records, options) {
|
|
|
2732
2790
|
continue;
|
|
2733
2791
|
}
|
|
2734
2792
|
if (record2.type === "event_msg" && payload?.type === "user_message" && typeof payload.message === "string") {
|
|
2793
|
+
if (queuedPromptEcho) {
|
|
2794
|
+
queuedPromptEcho = false;
|
|
2795
|
+
continue;
|
|
2796
|
+
}
|
|
2735
2797
|
if (current && (current.assistantText.length > 0 || current.toolCalls.length > 0 || current.thinking.length > 0)) {
|
|
2736
2798
|
current = commitTurn(current);
|
|
2737
2799
|
}
|
|
2800
|
+
const opensWindow = !sawTurnStart;
|
|
2738
2801
|
sawTurnStart = true;
|
|
2739
2802
|
current ??= createActiveTurn(currentTurnId, timestamp, currentModel);
|
|
2803
|
+
if (opensWindow) current.opensOnPrompt = true;
|
|
2740
2804
|
const localImages = (Array.isArray(payload.local_images) ? payload.local_images : []).filter(
|
|
2741
2805
|
(p) => typeof p === "string" && p.length > 0
|
|
2742
2806
|
);
|
|
@@ -2851,8 +2915,15 @@ function walkCodexRecords(records, options) {
|
|
|
2851
2915
|
appendAssistantText(current, extractMessageText(payload, "output_text"), timestamp);
|
|
2852
2916
|
continue;
|
|
2853
2917
|
}
|
|
2854
|
-
if (payload.type === "message" && payload.role === "user"
|
|
2918
|
+
if (payload.type === "message" && payload.role === "user") {
|
|
2855
2919
|
const text = normalizePromptText(extractMessageText(payload, "input_text"));
|
|
2920
|
+
if (current.userMessage !== null || current.contentBlocks.length > 0) {
|
|
2921
|
+
if (text) {
|
|
2922
|
+
current.contentBlocks.push({ kind: "queued_prompt", content: text, timestamp });
|
|
2923
|
+
queuedPromptEcho = true;
|
|
2924
|
+
}
|
|
2925
|
+
continue;
|
|
2926
|
+
}
|
|
2856
2927
|
const images = extractResponseMessageImages(payload);
|
|
2857
2928
|
const audio = extractResponseMessageAudio(payload);
|
|
2858
2929
|
if (text || images.length > 0 || audio.length > 0) {
|
|
@@ -3094,6 +3165,7 @@ function walkCodexRecords(records, options) {
|
|
|
3094
3165
|
}
|
|
3095
3166
|
}
|
|
3096
3167
|
commitTurn(current);
|
|
3168
|
+
const linkedTurns = linkAsyncAnswers(turns);
|
|
3097
3169
|
return {
|
|
3098
3170
|
turnStartIndices,
|
|
3099
3171
|
sessionId: metadata.sessionId,
|
|
@@ -3103,8 +3175,8 @@ function walkCodexRecords(records, options) {
|
|
|
3103
3175
|
slug: metadata.slug,
|
|
3104
3176
|
name: "",
|
|
3105
3177
|
model: metadata.model,
|
|
3106
|
-
turns,
|
|
3107
|
-
stats: options?.skipStats ? createEmptySessionStats(
|
|
3178
|
+
turns: linkedTurns,
|
|
3179
|
+
stats: options?.skipStats ? createEmptySessionStats(linkedTurns.length) : computeStats(linkedTurns),
|
|
3108
3180
|
rawMessages: options?.skipStats ? [] : records,
|
|
3109
3181
|
branchedFrom: metadata.branchedFrom,
|
|
3110
3182
|
agentKind: "codex"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Turn } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Records on each async question the reader's first prompt after it.
|
|
4
|
+
*
|
|
5
|
+
* Nothing links a reply to its question, so that prompt answers every question
|
|
6
|
+
* still open when it arrives. A pure recompute over the list handed in, so the
|
|
7
|
+
* page stitcher re-runs it across a join and gets what a single parse would
|
|
8
|
+
* have produced. Turns whose answers did not change come back by reference.
|
|
9
|
+
*/
|
|
10
|
+
export declare function linkAsyncAnswers(turns: readonly Turn[]): Turn[];
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -411,6 +411,8 @@ export interface ToolCall {
|
|
|
411
411
|
* answered here.
|
|
412
412
|
*/
|
|
413
413
|
asyncQuestion?: boolean;
|
|
414
|
+
/** The reader's reply to an async question, once the transcript holds one. */
|
|
415
|
+
asyncAnswer?: string;
|
|
414
416
|
/** Set by parser when a PostToolUse hook replaced this tool's output */
|
|
415
417
|
outputReplacedByHook?: boolean;
|
|
416
418
|
/** Total duration of PostToolUse hooks attached to this call, summed in ms */
|
|
@@ -656,6 +658,13 @@ export interface Turn {
|
|
|
656
658
|
* must be stitched onto the previous turn once the older page arrives.
|
|
657
659
|
*/
|
|
658
660
|
isFragment?: boolean;
|
|
661
|
+
/**
|
|
662
|
+
* Set when the parse window opened on this turn's prompt record, with no turn
|
|
663
|
+
* context before it. An older CLI's echo of a prompt steered into the previous
|
|
664
|
+
* page's last turn looks exactly like this, so the page stitcher may fold it
|
|
665
|
+
* back into that steer.
|
|
666
|
+
*/
|
|
667
|
+
opensOnPrompt?: boolean;
|
|
659
668
|
}
|
|
660
669
|
export interface SessionStats {
|
|
661
670
|
totalInputTokens: number;
|