@chroxify/eunoe 1.2.1 → 1.3.0
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 +143 -5
- package/dist/core/index.js +2235 -0
- package/dist/types/context/content.d.ts +31 -0
- package/dist/types/context/fold/evidence.d.ts +20 -0
- package/dist/types/context/fold/index.d.ts +11 -0
- package/dist/types/context/fold/strategies.d.ts +20 -0
- package/dist/types/context/fold/turns.d.ts +14 -0
- package/dist/types/context/prompts.d.ts +24 -0
- package/dist/types/context/recall/index.d.ts +44 -0
- package/dist/types/context/recall/rerank.d.ts +3 -0
- package/dist/types/core/compaction.d.ts +32 -0
- package/dist/types/core/config.d.ts +33 -0
- package/dist/types/core/estimate.d.ts +9 -0
- package/dist/types/core/fingerprint.d.ts +10 -0
- package/dist/types/core/fold.d.ts +18 -0
- package/dist/types/core/forward.d.ts +24 -0
- package/dist/types/core/handoff.d.ts +10 -0
- package/dist/types/core/index.d.ts +14 -0
- package/dist/types/core/recall.d.ts +8 -0
- package/dist/types/core/rewrite.d.ts +63 -0
- package/dist/types/core/state.d.ts +22 -0
- package/dist/types/core/subagents.d.ts +10 -0
- package/dist/types/core/transcript.d.ts +9 -0
- package/dist/types/core/usage.d.ts +7 -0
- package/dist/types/core/window.d.ts +5 -0
- package/dist/types/transcript/format.d.ts +16 -0
- package/dist/types/transcript/parse.d.ts +45 -0
- package/package.json +15 -2
- package/src/cli/commands/mode.ts +2 -1
- package/src/cli/commands/serve.ts +2 -0
- package/src/cli/commands/status.ts +1 -1
- package/src/cli/proxy/stats.ts +1 -1
- package/src/cli/replay/index.ts +3 -3
- package/src/cli/sessions/index.ts +2 -2
- package/src/config/index.ts +4 -27
- package/src/context/content.ts +64 -1
- package/src/context/fold/strategies.ts +55 -10
- package/src/context/fold/turns.ts +22 -2
- package/src/context/prompts.ts +4 -3
- package/src/context/recall/index.ts +259 -35
- package/src/context/recall/rerank.ts +0 -8
- package/src/{proxy → core}/compaction.ts +74 -24
- package/src/core/config.ts +31 -0
- package/src/core/fold.ts +69 -0
- package/src/core/forward.ts +192 -0
- package/src/{proxy → core}/handoff.ts +23 -23
- package/src/core/index.ts +14 -0
- package/src/{proxy → core}/recall.ts +18 -20
- package/src/core/rewrite.ts +189 -0
- package/src/core/state.ts +40 -0
- package/src/core/subagents.ts +28 -0
- package/src/core/transcript.ts +44 -0
- package/src/{proxy → core}/usage.ts +34 -1
- package/src/core/window.ts +39 -0
- package/src/proxy/rewrite.ts +22 -139
- package/src/proxy/server.ts +24 -137
- package/src/proxy/sessions.ts +1 -1
- package/src/proxy/sources.ts +35 -0
- package/src/proxy/state.ts +32 -21
- package/src/proxy/warm.ts +68 -0
- package/src/proxy/window.ts +3 -33
- package/src/transcript/entries.ts +114 -0
- package/src/transcript/format.ts +1 -2
- package/src/transcript/messages.ts +14 -11
- package/src/transcript/render-worker.ts +24 -0
- package/src/transcript/render.ts +12 -3
- package/src/transcript/search.ts +46 -5
- package/src/transcript/subagents.ts +55 -34
- /package/src/{proxy → core}/estimate.ts +0 -0
- /package/src/{proxy → core}/fingerprint.ts +0 -0
package/src/transcript/search.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import type { SearchMode } from "../config"
|
|
4
|
-
import {
|
|
4
|
+
import { transcriptDir } from "./render"
|
|
5
|
+
import type { RenderDone, RenderJob } from "./render-worker"
|
|
5
6
|
|
|
6
7
|
export interface TranscriptRef {
|
|
7
8
|
mode: SearchMode
|
|
@@ -32,13 +33,53 @@ function indexWithQmd(dir: string) {
|
|
|
32
33
|
return next
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
const queued = new Map<string, { mode: SearchMode; sessionFile: string }>()
|
|
37
|
+
let renderer: Worker | null = null
|
|
38
|
+
let rendering: { dir: string; mode: SearchMode } | null = null
|
|
39
|
+
let drained: Array<() => void> = []
|
|
40
|
+
|
|
41
|
+
function finished(changed: boolean) {
|
|
42
|
+
if (rendering?.mode === "qmd" && (changed || !isIndexed(rendering.dir))) void indexWithQmd(rendering.dir)
|
|
43
|
+
rendering = null
|
|
44
|
+
renderNext()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function startRenderer(): Worker {
|
|
48
|
+
const worker = new Worker(new URL("./render-worker.ts", import.meta.url))
|
|
49
|
+
worker.onmessage = ({ data }: MessageEvent<RenderDone>) => finished(data.changed)
|
|
50
|
+
worker.onerror = () => {
|
|
51
|
+
renderer = null
|
|
52
|
+
finished(false)
|
|
53
|
+
}
|
|
54
|
+
;(worker as Worker & { unref(): void }).unref()
|
|
55
|
+
return worker
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderNext() {
|
|
59
|
+
const next = queued.entries().next()
|
|
60
|
+
if (next.done) {
|
|
61
|
+
const waiting = drained
|
|
62
|
+
drained = []
|
|
63
|
+
for (const resolve of waiting) resolve()
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
const [dir, { mode, sessionFile }] = next.value
|
|
67
|
+
queued.delete(dir)
|
|
68
|
+
rendering = { dir, mode }
|
|
69
|
+
renderer ??= startRenderer()
|
|
70
|
+
renderer.postMessage({ dir, sessionFile, format: mode === "xml" ? "xml" : "markdown" } satisfies RenderJob)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function flushTranscripts(): Promise<void> {
|
|
74
|
+
if (!rendering && queued.size === 0) return Promise.resolve()
|
|
75
|
+
return new Promise((resolve) => drained.push(resolve))
|
|
76
|
+
}
|
|
77
|
+
|
|
35
78
|
export function prepareTranscript(mode: SearchMode, sessionFile: string, key: string): TranscriptRef {
|
|
36
79
|
const dir = transcriptDir(key)
|
|
37
80
|
if (mode !== "jsonl") {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (mode === "qmd" && (changed || !isIndexed(dir))) void indexWithQmd(dir)
|
|
41
|
-
} catch {}
|
|
81
|
+
queued.set(dir, { mode, sessionFile })
|
|
82
|
+
if (!rendering) renderNext()
|
|
42
83
|
}
|
|
43
84
|
return { mode, jsonl: sessionFile, dir }
|
|
44
85
|
}
|
|
@@ -2,10 +2,12 @@ import { readFileSync, statSync } from "node:fs"
|
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import { blocks, clip, contentBlocks, type Message } from "../context/content"
|
|
4
4
|
import { splitTurns } from "../context/fold/turns"
|
|
5
|
-
import {
|
|
5
|
+
import type { RecallChunk } from "../context/recall"
|
|
6
|
+
import { subagentRecall } from "../core/subagents"
|
|
7
|
+
import { readSessionEntries } from "./entries"
|
|
6
8
|
import { listEntries } from "./locate"
|
|
7
9
|
import { sessionMessages } from "./messages"
|
|
8
|
-
import { entryMessages,
|
|
10
|
+
import { entryMessages, type SessionEntry } from "./parse"
|
|
9
11
|
|
|
10
12
|
export interface SubagentRun {
|
|
11
13
|
id: string
|
|
@@ -20,7 +22,7 @@ export interface SubagentRun {
|
|
|
20
22
|
|
|
21
23
|
const INLINE_ID = "sidechain"
|
|
22
24
|
|
|
23
|
-
const
|
|
25
|
+
const runCache = new Map<string, SubagentRun>()
|
|
24
26
|
const chunkCache = new Map<string, { mtimeMs: number; parentTurn: number; chunks: RecallChunk[] }>()
|
|
25
27
|
|
|
26
28
|
function mtimeOf(file: string): number | null {
|
|
@@ -31,16 +33,6 @@ function mtimeOf(file: string): number | null {
|
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
function readEntries(file: string): SessionEntry[] {
|
|
35
|
-
const mtimeMs = mtimeOf(file)
|
|
36
|
-
if (mtimeMs === null) return []
|
|
37
|
-
const hit = entryCache.get(file)
|
|
38
|
-
if (hit?.mtimeMs === mtimeMs) return hit.entries
|
|
39
|
-
const entries = parseJsonl(readFileSync(file, "utf8"))
|
|
40
|
-
entryCache.set(file, { mtimeMs, entries })
|
|
41
|
-
return entries
|
|
42
|
-
}
|
|
43
|
-
|
|
44
36
|
function firstPrompt(entries: SessionEntry[]): string {
|
|
45
37
|
for (const entry of entries) {
|
|
46
38
|
if (entry.type !== "user" || !entry.message) continue
|
|
@@ -72,10 +64,13 @@ function fileRuns(sessionFile: string): SubagentRun[] {
|
|
|
72
64
|
.filter((name) => name.startsWith("agent-") && name.endsWith(".jsonl"))
|
|
73
65
|
.map((name) => {
|
|
74
66
|
const file = path.join(dir, name)
|
|
75
|
-
const
|
|
67
|
+
const mtimeMs = mtimeOf(file) ?? 0
|
|
68
|
+
const known = runCache.get(file)
|
|
69
|
+
if (known?.mtimeMs === mtimeMs) return known
|
|
70
|
+
const entries = readSessionEntries(file)
|
|
76
71
|
const meta = readMeta(file)
|
|
77
72
|
const prompt = firstPrompt(entries)
|
|
78
|
-
|
|
73
|
+
const run: SubagentRun = {
|
|
79
74
|
id: name.slice("agent-".length, -".jsonl".length),
|
|
80
75
|
file,
|
|
81
76
|
inline: false,
|
|
@@ -83,17 +78,21 @@ function fileRuns(sessionFile: string): SubagentRun[] {
|
|
|
83
78
|
toolUseId: meta.toolUseId ? String(meta.toolUseId) : null,
|
|
84
79
|
prompt,
|
|
85
80
|
started: String(entries[0]?.timestamp ?? ""),
|
|
86
|
-
mtimeMs
|
|
81
|
+
mtimeMs,
|
|
87
82
|
}
|
|
83
|
+
runCache.set(file, run)
|
|
84
|
+
return run
|
|
88
85
|
})
|
|
89
86
|
}
|
|
90
87
|
|
|
91
88
|
function inlineRuns(sessionFile: string, known: Set<string>): SubagentRun[] {
|
|
92
89
|
const byAgent = new Map<string, SessionEntry[]>()
|
|
93
|
-
for (const entry of
|
|
90
|
+
for (const entry of readSessionEntries(sessionFile)) {
|
|
94
91
|
if (!entry.isSidechain) continue
|
|
95
92
|
const id = entry.agentId ?? INLINE_ID
|
|
96
|
-
|
|
93
|
+
const group = byAgent.get(id)
|
|
94
|
+
if (group) group.push(entry)
|
|
95
|
+
else byAgent.set(id, [entry])
|
|
97
96
|
}
|
|
98
97
|
return [...byAgent]
|
|
99
98
|
.filter(([id]) => !known.has(id))
|
|
@@ -110,7 +109,7 @@ export function subagentRuns(sessionFile: string): SubagentRun[] {
|
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
export function subagentEntries(run: SubagentRun): SessionEntry[] {
|
|
113
|
-
const entries =
|
|
112
|
+
const entries = readSessionEntries(run.file)
|
|
114
113
|
if (!run.inline) return entries
|
|
115
114
|
return entries.filter((entry) => entry.isSidechain && (entry.agentId ?? INLINE_ID) === run.id)
|
|
116
115
|
}
|
|
@@ -119,31 +118,53 @@ export function subagentMessages(run: SubagentRun): Message[] {
|
|
|
119
118
|
return entryMessages(subagentEntries(run))
|
|
120
119
|
}
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
interface Launches {
|
|
122
|
+
byId: Map<unknown, number>
|
|
123
|
+
byPrompt: Map<string, number>
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function launchesIn(parentMessages: Message[]): Launches {
|
|
127
|
+
const launches: Launches = { byId: new Map(), byPrompt: new Map() }
|
|
128
|
+
splitTurns(parentMessages).turns.forEach((turn, index) => {
|
|
129
|
+
for (const message of turn) {
|
|
130
|
+
if (message.role !== "assistant") continue
|
|
131
|
+
for (const block of blocks(message)) {
|
|
132
|
+
if (block.type !== "tool_use") continue
|
|
133
|
+
if (!launches.byId.has(block.id)) launches.byId.set(block.id, index + 1)
|
|
134
|
+
const prompt = String(block.input?.prompt ?? "")
|
|
135
|
+
if ((block.name === "Agent" || block.name === "Task") && !launches.byPrompt.has(prompt)) launches.byPrompt.set(prompt, index + 1)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
return launches
|
|
125
140
|
}
|
|
126
141
|
|
|
142
|
+
const launchTurn = (run: SubagentRun, launches: Launches) => (run.toolUseId ? launches.byId.get(run.toolUseId) : launches.byPrompt.get(run.prompt)) ?? 0
|
|
143
|
+
|
|
127
144
|
export function parentTurnOf(run: SubagentRun, parentMessages: Message[]): number {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
return launchTurn(run, launchesIn(parentMessages))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const KEPT_LAUNCHES = 8
|
|
149
|
+
const launchCache = new Map<Message[], Launches>()
|
|
150
|
+
|
|
151
|
+
function cachedLaunches(parentMessages: Message[]): Launches {
|
|
152
|
+
const known = launchCache.get(parentMessages)
|
|
153
|
+
if (known) return known
|
|
154
|
+
const launches = launchesIn(parentMessages)
|
|
155
|
+
launchCache.set(parentMessages, launches)
|
|
156
|
+
if (launchCache.size > KEPT_LAUNCHES) launchCache.delete(launchCache.keys().next().value as Message[])
|
|
157
|
+
return launches
|
|
136
158
|
}
|
|
137
159
|
|
|
138
160
|
export function subagentChunks(sessionFile: string): RecallChunk[] {
|
|
139
|
-
const
|
|
161
|
+
const launches = cachedLaunches(sessionMessages(sessionFile))
|
|
140
162
|
return subagentRuns(sessionFile).flatMap((run) => {
|
|
141
163
|
const key = `${run.file}#${run.id}`
|
|
142
|
-
const parentTurn =
|
|
164
|
+
const parentTurn = launchTurn(run, launches)
|
|
143
165
|
const hit = chunkCache.get(key)
|
|
144
166
|
if (hit && hit.mtimeMs === run.mtimeMs && hit.parentTurn === parentTurn) return hit.chunks
|
|
145
|
-
const
|
|
146
|
-
const chunks = recallChunks([...subagentMessages(run), { role: "user", content: "" }], new Map()).map((chunk) => ({ ...chunk, from, turn: parentTurn }))
|
|
167
|
+
const chunks = subagentRecall(subagentMessages(run), run.description, parentTurn)
|
|
147
168
|
chunkCache.set(key, { mtimeMs: run.mtimeMs, parentTurn, chunks })
|
|
148
169
|
return chunks
|
|
149
170
|
})
|
|
File without changes
|
|
File without changes
|