feat(aircoding): AirCoding V2 baseline — deterministic multi-agent architecture
Forked from OpenCode v1.17.4 with multi-agent system: - 5 agents: aircoding, scheduler, worker, architect, reviewer - Deterministic DAG scheduling engine (coordinator_tick) - Tool whitelists as hard enforcement - AirCoding validation plugin - System prompt injection for routing - V1 requirements: C4 docs, ADR, AGENTS.md, debug-log.md - Design documents in docs/
This commit is contained in:
111
packages/tui/src/prompt/history.tsx
Normal file
111
packages/tui/src/prompt/history.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import path from "path"
|
||||
import { onMount } from "solid-js"
|
||||
import { createStore, produce, unwrap } from "solid-js/store"
|
||||
import type { AgentPart, FilePart, TextPart } from "@opencode-ai/sdk/v2"
|
||||
import { createSimpleContext } from "../context/helper"
|
||||
import { useTuiPaths } from "../context/runtime"
|
||||
import { appendText, readText, writeText } from "../util/persistence"
|
||||
|
||||
export type PromptInfo = {
|
||||
input: string
|
||||
mode?: "normal" | "shell"
|
||||
parts: (
|
||||
| Omit<FilePart, "id" | "messageID" | "sessionID">
|
||||
| Omit<AgentPart, "id" | "messageID" | "sessionID">
|
||||
| (Omit<TextPart, "id" | "messageID" | "sessionID"> & {
|
||||
source?: {
|
||||
text: {
|
||||
start: number
|
||||
end: number
|
||||
value: string
|
||||
}
|
||||
}
|
||||
})
|
||||
)[]
|
||||
}
|
||||
|
||||
export const MAX_HISTORY_ENTRIES = 50
|
||||
|
||||
export function parsePromptHistory(text: string) {
|
||||
return text
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((line) => {
|
||||
try {
|
||||
return JSON.parse(line) as PromptInfo
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
.filter((line): line is PromptInfo => line !== undefined)
|
||||
.slice(-MAX_HISTORY_ENTRIES)
|
||||
}
|
||||
|
||||
export function isDuplicateEntry(previous: PromptInfo | undefined, next: PromptInfo): boolean {
|
||||
if (!previous) return false
|
||||
return JSON.stringify(previous) === JSON.stringify(next)
|
||||
}
|
||||
|
||||
export const { use: usePromptHistory, provider: PromptHistoryProvider } = createSimpleContext({
|
||||
name: "PromptHistory",
|
||||
init: () => {
|
||||
const paths = useTuiPaths()
|
||||
const historyPath = path.join(paths.state, "prompt-history.jsonl")
|
||||
onMount(async () => {
|
||||
const lines = parsePromptHistory(await readText(historyPath).catch(() => ""))
|
||||
setStore("history", lines)
|
||||
|
||||
// Rewrite valid retained entries to self-heal corruption and enforce the limit.
|
||||
if (lines.length > 0)
|
||||
writeText(historyPath, lines.map((line) => JSON.stringify(line)).join("\n") + "\n").catch(() => {})
|
||||
})
|
||||
|
||||
const [store, setStore] = createStore({
|
||||
index: 0,
|
||||
history: [] as PromptInfo[],
|
||||
})
|
||||
|
||||
return {
|
||||
move(direction: 1 | -1, input: string) {
|
||||
if (!store.history.length) return undefined
|
||||
const current = store.history.at(store.index)
|
||||
if (!current) return undefined
|
||||
if (current.input !== input && input.length) return
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
const next = store.index + direction
|
||||
if (Math.abs(next) > store.history.length) return
|
||||
if (next > 0) return
|
||||
draft.index = next
|
||||
}),
|
||||
)
|
||||
if (store.index === 0) return { input: "", parts: [] }
|
||||
return store.history.at(store.index)
|
||||
},
|
||||
append(item: PromptInfo) {
|
||||
const entry = structuredClone(unwrap(item))
|
||||
if (isDuplicateEntry(store.history.at(-1), entry)) {
|
||||
setStore("index", 0)
|
||||
return
|
||||
}
|
||||
let trimmed = false
|
||||
setStore(
|
||||
produce((draft) => {
|
||||
draft.history.push(entry)
|
||||
if (draft.history.length > MAX_HISTORY_ENTRIES) {
|
||||
draft.history = draft.history.slice(-MAX_HISTORY_ENTRIES)
|
||||
trimmed = true
|
||||
}
|
||||
draft.index = 0
|
||||
}),
|
||||
)
|
||||
|
||||
if (trimmed) {
|
||||
writeText(historyPath, store.history.map((line) => JSON.stringify(line)).join("\n") + "\n").catch(() => {})
|
||||
return
|
||||
}
|
||||
appendText(historyPath, JSON.stringify(entry) + "\n").catch(() => {})
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user