feat(ai): session harness contract — incomplete, run_id, confirm bind

Add docs/16 for session state machine, incomplete/resume, queue semantics,
and agent-loop/harness/MCP gap review. Wire RunOutcome on max-rounds,
run_id through audit, TrimMessages/Redact hooks, ConfirmBindHash on write
confirm path; session file skeleton without HTTP yet. No push.
This commit is contained in:
logan committed 2026-10-15 13:58:10 +08:00
1 parent 0aeee2b692
commit 5416e86379
17 files changed
+1041 -61

No files matched your search

+20 -3
View File
@@ -43,9 +43,17 @@ function splitContent(content: string): ContentSeg[] {
/** R2-F7:等宽代码块 + 顶部「复制」「插入编辑器」小按钮 */
function CodeBlock({ code }: { code: string }) {
const notify = useWorkspaceStore((s) => s.notify);
const notifyError = useWorkspaceStore((s) => s.notifyError);
const [copied, setCopied] = useState(false);
const copiedTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => () => { if (copiedTimer.current) clearTimeout(copiedTimer.current); }, []);
const copy = async () => {
await navigator.clipboard.writeText(code);
notify({ kind: "success", title: "已复制", message: "SQL 已复制", mode: "toast", durationMs: 1200 });
try {
await navigator.clipboard.writeText(code);
setCopied(true);
if (copiedTimer.current) clearTimeout(copiedTimer.current);
copiedTimer.current = setTimeout(() => setCopied(false), 1200);
} catch (error) { notifyError(error, "toast"); }
};
const insertToEditor = () => {
const st = useWorkspaceStore.getState();
@@ -61,7 +69,7 @@ function CodeBlock({ code }: { code: string }) {
style={{ borderColor: "var(--ops-border-soft)" }}
>
<button type="button" className="text-[10px] hover:underline" style={{ color: "var(--ops-accent)" }} onClick={() => void copy()}>
复制
{copied ? "已复制 ✓" : "复制"}
</button>
<button type="button" className="text-[10px] hover:underline" style={{ color: "var(--ops-accent)" }} onClick={insertToEditor}>
插入编辑器
@@ -161,12 +169,16 @@ function DangerConfirmBar({
connectionId,
database,
kind,
confirmBind,
runId,
onDone,
}: {
sql: string;
connectionId?: string;
database?: string;
kind: "query" | "redis";
confirmBind?: string;
runId?: string;
onDone: (ok: boolean, msg?: string) => void;
}) {
const [busy, setBusy] = useState(false);
@@ -180,6 +192,8 @@ function DangerConfirmBar({
command: kind === "redis" ? sql : undefined,
database,
kind,
confirmBind,
runId,
});
onDone(true);
} catch (e) {
@@ -528,6 +542,7 @@ export function AiPanel() {
content: final.content,
steps: final.steps || [],
streaming: false,
runId: final.runId,
});
if (final.sql && tab) updateTabSqlIfUnchanged(tab.id, sql, tab.connectionId, tab.database, final.sql);
},
@@ -674,6 +689,8 @@ export function AiPanel() {
connectionId={conn?.id}
database={database || undefined}
kind={pendingConfirm.step.tool?.includes("redis") ? "redis" : "query"}
confirmBind={pendingConfirm.step.confirmBind}
runId={m.runId}
onDone={(ok, msg) => {
if (ok) {
patchAiMessage(m.id, (cur) => ({