refactor: keep runtime context in memory
This commit is contained in:
@@ -2,7 +2,10 @@ import { randomUUID } from "node:crypto";
|
||||
|
||||
import { logger } from "../logger.js";
|
||||
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
|
||||
import { SessionRuntimeContextStore } from "../sessions/runtimeContextStore.js";
|
||||
import {
|
||||
removeRuntimeSessionContext,
|
||||
setRuntimeSessionContext,
|
||||
} from "../runtime/sessionContext.js";
|
||||
import { toActorKey, toProjectKey } from "../utils/fileStore.js";
|
||||
|
||||
export type SessionBinding = {
|
||||
@@ -26,7 +29,6 @@ export type ChatRequestContext = SessionContext & {
|
||||
|
||||
export class ChatSessionBridge {
|
||||
private readonly abortControllers = new Map<string, AbortController>();
|
||||
private readonly sessionRuntimeContextStore = new SessionRuntimeContextStore();
|
||||
|
||||
constructor(private readonly runtime: OpencodeRuntimeAdapter) {}
|
||||
|
||||
@@ -61,7 +63,7 @@ export class ChatSessionBridge {
|
||||
sessionId,
|
||||
startedAt: Date.now(),
|
||||
};
|
||||
await this.sessionRuntimeContextStore.write({
|
||||
setRuntimeSessionContext({
|
||||
accessToken: requestContext.accessToken,
|
||||
actorKey: requestContext.actorKey,
|
||||
allowLearningWrite: true,
|
||||
@@ -133,9 +135,7 @@ export class ChatSessionBridge {
|
||||
"failed while waiting for runtime session to become idle",
|
||||
);
|
||||
});
|
||||
await this.sessionRuntimeContextStore.remove(sessionId).catch((error) => {
|
||||
logger.debug({ sessionId, err: error }, "failed to cleanup runtime tool context");
|
||||
});
|
||||
removeRuntimeSessionContext(sessionId);
|
||||
}
|
||||
|
||||
private buildRequestContext(context: {
|
||||
|
||||
@@ -47,8 +47,6 @@ const envSchema = z
|
||||
OPENCODE_SKILLS_ROOT_DIR: z.string().default("./.opencode/skills"),
|
||||
// client 模式下,目标 opencode server 的基础地址。
|
||||
OPENCODE_CLIENT_BASE_URL: z.string().url().optional(),
|
||||
// 提供给本地 opencode tools 读取的会话上下文目录。
|
||||
SESSION_RUNTIME_CONTEXT_STORAGE_DIR: z.string().default("./data/session-runtime-contexts"),
|
||||
// tjwater-cli 可执行文件路径。
|
||||
TJWATER_CLI_PATH: z.string().default("./cli/tjwater-cli"),
|
||||
// TJWater 后端 API 的基础地址。
|
||||
|
||||
@@ -9,7 +9,10 @@ import { SessionLearningStateStore } from "./sessionStateStore.js";
|
||||
import { MemoryStore, type MemoryScope } from "../memory/store.js";
|
||||
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
|
||||
import { SkillStore } from "../skills/store.js";
|
||||
import { SessionRuntimeContextStore } from "../sessions/runtimeContextStore.js";
|
||||
import {
|
||||
removeRuntimeSessionContext,
|
||||
setRuntimeSessionContext,
|
||||
} from "../runtime/sessionContext.js";
|
||||
import {
|
||||
sanitizePersistentDocument,
|
||||
sanitizePersistentLine,
|
||||
@@ -76,7 +79,6 @@ export class LearningOrchestrator {
|
||||
private readonly activeReviews = new Set<string>();
|
||||
private readonly sessionLearningStateStore = new SessionLearningStateStore();
|
||||
private readonly skillStore = new SkillStore();
|
||||
private readonly sessionRuntimeContextStore = new SessionRuntimeContextStore();
|
||||
|
||||
constructor(
|
||||
private readonly runtime: OpencodeRuntimeAdapter,
|
||||
@@ -85,10 +87,7 @@ export class LearningOrchestrator {
|
||||
) {}
|
||||
|
||||
async initialize() {
|
||||
await Promise.all([
|
||||
this.sessionLearningStateStore.initialize(),
|
||||
this.sessionRuntimeContextStore.initialize(),
|
||||
]);
|
||||
await this.sessionLearningStateStore.initialize();
|
||||
}
|
||||
|
||||
async onTurnCompleted(input: TurnReviewInput) {
|
||||
@@ -147,7 +146,7 @@ export class LearningOrchestrator {
|
||||
`learning-gate-${input.requestContext.clientSessionId}`,
|
||||
);
|
||||
gateSessionId = gateSession.id;
|
||||
await this.sessionRuntimeContextStore.write({
|
||||
setRuntimeSessionContext({
|
||||
actorKey: input.requestContext.actorKey,
|
||||
allowLearningWrite: false,
|
||||
clientSessionId: `gate-${input.requestContext.clientSessionId}`,
|
||||
@@ -215,7 +214,7 @@ export class LearningOrchestrator {
|
||||
});
|
||||
} finally {
|
||||
if (gateSessionId) {
|
||||
await this.sessionRuntimeContextStore.remove(gateSessionId).catch(() => undefined);
|
||||
removeRuntimeSessionContext(gateSessionId);
|
||||
await this.runtime.abortSession(gateSessionId).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
@@ -235,7 +234,7 @@ export class LearningOrchestrator {
|
||||
const reviewSession = await this.runtime.createSession(
|
||||
`learning-review-${input.requestContext.clientSessionId}`,
|
||||
);
|
||||
await this.sessionRuntimeContextStore.write({
|
||||
setRuntimeSessionContext({
|
||||
actorKey: input.requestContext.actorKey,
|
||||
allowLearningWrite: false,
|
||||
clientSessionId: `review-${input.requestContext.clientSessionId}`,
|
||||
@@ -283,7 +282,7 @@ export class LearningOrchestrator {
|
||||
traceId: input.requestContext.traceId,
|
||||
});
|
||||
} finally {
|
||||
await this.sessionRuntimeContextStore.remove(reviewSession.id).catch(() => undefined);
|
||||
removeRuntimeSessionContext(reviewSession.id);
|
||||
await this.runtime.abortSession(reviewSession.id).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export type RuntimeSessionContext = {
|
||||
accessToken?: string;
|
||||
actorKey: string;
|
||||
allowLearningWrite?: boolean;
|
||||
clientSessionId: string;
|
||||
learningMode?: "interactive" | "review";
|
||||
memoryListReadScopes?: Partial<Record<"user" | "workspace", boolean>>;
|
||||
projectId?: string;
|
||||
projectKey: string;
|
||||
sessionId: string;
|
||||
traceId: string;
|
||||
};
|
||||
|
||||
const contexts = new Map<string, RuntimeSessionContext>();
|
||||
|
||||
export const setRuntimeSessionContext = (context: RuntimeSessionContext) => {
|
||||
contexts.set(context.sessionId, { ...context });
|
||||
};
|
||||
|
||||
export const getRuntimeSessionContext = (sessionId: string) => {
|
||||
const context = contexts.get(sessionId);
|
||||
return context ? { ...context } : null;
|
||||
};
|
||||
|
||||
export const removeRuntimeSessionContext = (sessionId: string) => {
|
||||
contexts.delete(sessionId);
|
||||
};
|
||||
+4
-6
@@ -18,7 +18,7 @@ import {
|
||||
} from "./results/store.js";
|
||||
import { buildChatRouter } from "./routes/chat.js";
|
||||
import { opencodeRuntime } from "./runtime/opencode.js";
|
||||
import { SessionRuntimeContextStore } from "./sessions/runtimeContextStore.js";
|
||||
import { getRuntimeSessionContext } from "./runtime/sessionContext.js";
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -28,7 +28,6 @@ const sessionMetadataStore = new SessionMetadataStore();
|
||||
const sessionUiStateStore = new SessionUiStateStore();
|
||||
const memoryStore = new MemoryStore();
|
||||
const sessionTranscriptStore = new SessionTranscriptStore();
|
||||
const sessionRuntimeContextStore = new SessionRuntimeContextStore();
|
||||
const learningOrchestrator = new LearningOrchestrator(
|
||||
opencodeRuntime,
|
||||
memoryStore,
|
||||
@@ -71,7 +70,7 @@ app.post("/internal/tools/tjwater-cli-call", async (req, res) => {
|
||||
|
||||
const sessionId =
|
||||
typeof req.body?.session_id === "string" ? req.body.session_id.trim() : "";
|
||||
const context = sessionId ? await sessionRuntimeContextStore.read(sessionId) : null;
|
||||
const context = sessionId ? getRuntimeSessionContext(sessionId) : null;
|
||||
if (!context) {
|
||||
res.status(404).json({
|
||||
message: "session context not found",
|
||||
@@ -175,7 +174,7 @@ app.post("/internal/tools/store-render-ref", async (req, res) => {
|
||||
const sessionId =
|
||||
typeof req.body?.session_id === "string" ? req.body.session_id.trim() : "";
|
||||
const filePath = typeof req.body?.file_path === "string" ? req.body.file_path.trim() : "";
|
||||
const context = sessionId ? await sessionRuntimeContextStore.read(sessionId) : null;
|
||||
const context = sessionId ? getRuntimeSessionContext(sessionId) : null;
|
||||
if (!context) {
|
||||
res.status(404).json({
|
||||
message: "session context not found",
|
||||
@@ -225,7 +224,7 @@ app.post("/internal/tools/session-search", async (req, res) => {
|
||||
const sessionId =
|
||||
typeof req.body?.session_id === "string" ? req.body.session_id.trim() : "";
|
||||
const query = typeof req.body?.query === "string" ? req.body.query : "";
|
||||
const context = sessionId ? await sessionRuntimeContextStore.read(sessionId) : null;
|
||||
const context = sessionId ? getRuntimeSessionContext(sessionId) : null;
|
||||
if (!context) {
|
||||
res.status(404).json({
|
||||
message: "session context not found",
|
||||
@@ -273,7 +272,6 @@ const bootstrap = async () => {
|
||||
memoryStore.initialize(),
|
||||
resultReferenceStore.initialize(),
|
||||
sessionTranscriptStore.initialize(),
|
||||
sessionRuntimeContextStore.initialize(),
|
||||
]);
|
||||
resultReferenceStore.startCleanupLoop();
|
||||
};
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { join } from "node:path";
|
||||
|
||||
import { config } from "../config.js";
|
||||
import {
|
||||
atomicWriteJson,
|
||||
ensureDirectory,
|
||||
readJsonFile,
|
||||
removeFileIfExists,
|
||||
} from "../utils/fileStore.js";
|
||||
|
||||
export type SessionRuntimeContext = {
|
||||
accessToken?: string;
|
||||
actorKey: string;
|
||||
allowLearningWrite?: boolean;
|
||||
clientSessionId: string;
|
||||
learningMode?: "interactive" | "review";
|
||||
memoryListReadScopes?: Partial<Record<"user" | "workspace", boolean>>;
|
||||
projectId?: string;
|
||||
projectKey: string;
|
||||
sessionId: string;
|
||||
traceId: string;
|
||||
};
|
||||
|
||||
export class SessionRuntimeContextStore {
|
||||
constructor(private readonly baseDir = config.SESSION_RUNTIME_CONTEXT_STORAGE_DIR) {}
|
||||
|
||||
async initialize() {
|
||||
await ensureDirectory(this.baseDir);
|
||||
}
|
||||
|
||||
async write(context: SessionRuntimeContext) {
|
||||
await atomicWriteJson(this.filePath(context.sessionId), context);
|
||||
}
|
||||
|
||||
async read(sessionId: string) {
|
||||
return await readJsonFile<SessionRuntimeContext>(this.filePath(sessionId));
|
||||
}
|
||||
|
||||
async remove(sessionId: string) {
|
||||
await removeFileIfExists(this.filePath(sessionId));
|
||||
}
|
||||
|
||||
private filePath(sessionId: string) {
|
||||
return join(this.baseDir, `${sessionId}.json`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user