Skip to content

Commit bbe7835

Browse files
committed
refactor: inline session runtime from playbooks into server
The playbook session-runtime adapter layer (createPlaybookSessionRuntime, pickPlaybookAgent, PlaybookAgentIdentityMismatchError, and ~15 input/output types) lived in @openagentpack/playbooks but had exactly one production consumer: the server's playbook-session-adapter. This commit: - Moves the runtime orchestration and agent-pick logic into a new apps/server/.../playbook-session-adapter/runtime.ts with all generic type parameters instantiated to their concrete server types. - Replaces the four adapter interfaces with a single concrete PlaybookSessionRuntimeDeps type (dependency injection preserved for testability, but no cross-package abstraction). - Migrates tests to apps/server with type-safe fake constructors. - Removes session-runtime.ts from @openagentpack/playbooks and cleans up all re-exports from the package index. - Eliminates the duplicate ListPlaybookSessionsInput definition in sessions.ts (now imported from runtime.ts). Behavioral changes: none. The PlaybookAgentIdentityMismatchError still surfaces as HTTP 500 (no route-level catch); a future PR can map it to 4xx if desired. Change-Id: Ia4276ea3626877163f60529342e7da1ba3a991bd Co-developed-by: OpenCode <noreply@opencode.ai>
1 parent 43f2bc2 commit bbe7835

7 files changed

Lines changed: 331 additions & 318 deletions

File tree

apps/server/src/services/sessions/playbook-session-adapter/index.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
1-
import {
2-
createPlaybookSessionRuntime,
3-
getPlaybookAppId,
4-
getSeedPlaybookAgentName,
5-
PLAYBOOK_AGENT_NAME_PREFIX,
6-
type RemotePlaybookAgent,
7-
} from "@openagentpack/playbooks";
1+
import { getPlaybookAppId, getSeedPlaybookAgentName, PLAYBOOK_AGENT_NAME_PREFIX } from "@openagentpack/playbooks";
82
import {
93
type CloudAgent,
104
listCloudAgents,
11-
type ProviderSessionEvent,
125
readProjectRuntime,
136
resolveSessionProvider,
14-
type Session,
157
startSessionRun,
168
} from "@openagentpack/sdk";
179
import { loadAgentRuntimeInput, withAgentRuntime } from "@/services/runtime-factory";
1810
import { agentMetadataOf } from "./dto";
1911
import { ensureAgentApplied } from "./provision";
12+
import { createPlaybookSessionRuntime, type RemotePlaybookAgent } from "./runtime";
2013
import {
2114
attachLiveStream,
2215
deletePlaybookSession,
@@ -29,7 +22,7 @@ import {
2922
export type { PlaybookSessionDetail } from "./sessions";
3023

3124
export function createServerPlaybookSessionRuntime() {
32-
return createPlaybookSessionRuntime<PlaybookSessionDetail, ProviderSessionEvent, Session, RemotePlaybookAgent>({
25+
return createPlaybookSessionRuntime({
3326
identity: {
3427
appId: getPlaybookAppId(),
3528
expectedAgentName: getSeedPlaybookAgentName,
@@ -87,12 +80,10 @@ export function createServerPlaybookSessionRuntime() {
8780
onDuplicateAgent({ playbookId, winner, duplicates }) {
8881
const all = [winner, ...duplicates];
8982
console.warn(
90-
`玩法「${playbookId}」匹配到 ${all.length} active playbook Agent(${all
83+
`\u73A9\u6CD5\u300C${playbookId}\u300D\u5339\u914D\u5230 ${all.length} \u4E2A active playbook Agent(${all
9184
.map((agent) => agent.id)
92-
.join(", ")});取最近更新的 ${winner.id}`,
85+
.join(", ")})\uFF1B\u53D6\u6700\u8FD1\u66F4\u65B0\u7684 ${winner.id}\u3002`,
9386
);
9487
},
9588
});
9689
}
97-
98-
type PlaybookSessionDetail = import("./sessions").PlaybookSessionDetail;

apps/server/src/services/sessions/playbook-session-adapter/provision.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { RemotePlaybookAgent } from "@openagentpack/playbooks";
21
import type { CloudAgent } from "@openagentpack/sdk";
32
import {
43
importResource,
@@ -9,6 +8,7 @@ import {
98
writeProjectRuntime,
109
} from "@openagentpack/sdk";
1110
import { loadAgentRuntimeInput } from "@/services/runtime-factory";
11+
import type { RemotePlaybookAgent } from "./runtime";
1212

1313
/**
1414
* Ensure a compiled catalog agent is provisioned to its provider (has a remote_id in state).

packages/playbooks/tests/session-runtime.test.ts renamed to apps/server/src/services/sessions/playbook-session-adapter/runtime.test.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { describe, expect, test } from "bun:test";
2+
import { PLAYBOOK_APP_METADATA_KEY, PLAYBOOK_METADATA_KEY } from "@openagentpack/playbooks";
3+
import type { ProviderSessionEvent, Session } from "@openagentpack/sdk";
24
import {
35
createPlaybookSessionRuntime,
4-
PLAYBOOK_APP_METADATA_KEY,
5-
PLAYBOOK_METADATA_KEY,
66
PlaybookAgentIdentityMismatchError,
77
pickPlaybookAgent,
88
type RemotePlaybookAgent,
9-
} from "../src/index.ts";
9+
} from "./runtime";
10+
import type { PlaybookSessionDetail } from "./sessions";
1011

1112
const APP_ID = "agents-webui";
1213

1314
function agent(overrides: Partial<RemotePlaybookAgent> = {}): RemotePlaybookAgent {
1415
return {
1516
id: "agent_1",
16-
name: "Agents/设计师助手",
17+
name: "Agents/\u8BBE\u8BA1\u5E08\u52A9\u624B",
1718
metadata: {
1819
[PLAYBOOK_APP_METADATA_KEY]: APP_ID,
1920
[PLAYBOOK_METADATA_KEY]: "designer",
@@ -23,6 +24,22 @@ function agent(overrides: Partial<RemotePlaybookAgent> = {}): RemotePlaybookAgen
2324
};
2425
}
2526

27+
function fakeEvent(raw_type = "created"): ProviderSessionEvent {
28+
return { type: "status", raw_type, raw: {} };
29+
}
30+
31+
function fakeSession(id: string, agentId?: string): Session {
32+
return {
33+
session_id: id,
34+
status: "running",
35+
agent: agentId ? { agent_id: agentId } : undefined,
36+
};
37+
}
38+
39+
function fakeDetail(sessionId: string, agentId?: string): PlaybookSessionDetail {
40+
return { session: fakeSession(sessionId, agentId), events: [] };
41+
}
42+
2643
describe("pickPlaybookAgent", () => {
2744
test("picks the current-app playbook stamp by newest update time", () => {
2845
const older = agent({ id: "agent_old", updatedAt: "2026-06-20T00:00:00.000Z" });
@@ -43,7 +60,7 @@ describe("pickPlaybookAgent", () => {
4360
metadata: { [PLAYBOOK_APP_METADATA_KEY]: APP_ID },
4461
}),
4562
],
46-
{ playbookId: "designer", appId: APP_ID, expectedAgentName: "Agents/设计师助手" },
63+
{ playbookId: "designer", appId: APP_ID, expectedAgentName: "Agents/\u8BBE\u8BA1\u5E08\u52A9\u624B" },
4764
);
4865

4966
expect(pick.agent).toBeUndefined();
@@ -64,10 +81,10 @@ describe("createPlaybookSessionRuntime", () => {
6481
test("start ensures the selected agent, starts the provider session, attaches events, then returns detail", async () => {
6582
const calls: string[] = [];
6683
const liveEvents = (async function* () {
67-
yield { type: "created" };
84+
yield fakeEvent();
6885
})();
6986
const runtime = createPlaybookSessionRuntime({
70-
identity: { appId: APP_ID, expectedAgentName: () => "Agents/设计师助手" },
87+
identity: { appId: APP_ID, expectedAgentName: () => "Agents/\u8BBE\u8BA1\u5E08\u52A9\u624B" },
7188
agents: {
7289
async listPlaybookAgents() {
7390
calls.push("agents.list");
@@ -94,7 +111,7 @@ describe("createPlaybookSessionRuntime", () => {
94111
},
95112
async getDetail(input) {
96113
calls.push(`sessions.detail:${input.sessionId}:${input.remoteAgentId}`);
97-
return { sessionId: input.sessionId, agentId: input.remoteAgentId };
114+
return fakeDetail(input.sessionId, input.remoteAgentId);
98115
},
99116
},
100117
events: {
@@ -114,7 +131,8 @@ describe("createPlaybookSessionRuntime", () => {
114131
model: "glm-5.1",
115132
});
116133

117-
expect(detail).toEqual({ sessionId: "sess_1", agentId: "agent_existing" });
134+
expect(detail.session.session_id).toBe("sess_1");
135+
expect(detail.session.agent?.agent_id).toBe("agent_existing");
118136
expect(calls).toEqual([
119137
"agents.list",
120138
"agents.ensure:agent_existing:glm-5.1",
@@ -126,7 +144,7 @@ describe("createPlaybookSessionRuntime", () => {
126144

127145
test("start throws before ensuring when identity is blocked", async () => {
128146
const runtime = createPlaybookSessionRuntime({
129-
identity: { appId: APP_ID, expectedAgentName: () => "Agents/设计师助手" },
147+
identity: { appId: APP_ID, expectedAgentName: () => "Agents/\u8BBE\u8BA1\u5E08\u52A9\u624B" },
130148
agents: {
131149
async listPlaybookAgents() {
132150
return [agent({ metadata: { [PLAYBOOK_APP_METADATA_KEY]: APP_ID } })];
@@ -162,7 +180,7 @@ describe("createPlaybookSessionRuntime", () => {
162180
test("send appends a message, attaches events, then returns detail", async () => {
163181
const calls: string[] = [];
164182
const liveEvents = (async function* () {
165-
yield { type: "message" };
183+
yield fakeEvent("message");
166184
})();
167185
const runtime = createPlaybookSessionRuntime({
168186
identity: { appId: APP_ID },
@@ -190,7 +208,7 @@ describe("createPlaybookSessionRuntime", () => {
190208
},
191209
async getDetail(input) {
192210
calls.push(`sessions.detail:${input.sessionId}:${input.playbookId}`);
193-
return { sessionId: input.sessionId, playbookId: input.playbookId };
211+
return fakeDetail(input.sessionId);
194212
},
195213
},
196214
events: {
@@ -205,7 +223,7 @@ describe("createPlaybookSessionRuntime", () => {
205223

206224
const detail = await runtime.send({ playbookId: "designer", sessionId: "sess_1", message: "continue" });
207225

208-
expect(detail).toEqual({ sessionId: "sess_1", playbookId: "designer" });
226+
expect(detail.session.session_id).toBe("sess_1");
209227
expect(calls).toEqual([
210228
"sessions.send:sess_1:designer:continue",
211229
"events.attach:sess_1",
@@ -228,7 +246,7 @@ describe("createPlaybookSessionRuntime", () => {
228246
sessions: {
229247
async list(input) {
230248
calls.push(`sessions.list:${input.playbookId}:${input.remoteAgentId}:${input.limit}:${input.pageToken}`);
231-
return { sessions: [{ id: "sess_1" }], nextPageToken: "next" };
249+
return { sessions: [fakeSession("sess_1")], nextPageToken: "next" };
232250
},
233251
async start() {
234252
throw new Error("should not start");
@@ -252,7 +270,8 @@ describe("createPlaybookSessionRuntime", () => {
252270
pageToken: "page_1",
253271
});
254272

255-
expect(listed).toEqual({ sessions: [{ id: "sess_1" }], nextPageToken: "next" });
273+
expect(listed.sessions[0]?.session_id).toBe("sess_1");
274+
expect(listed.nextPageToken).toBe("next");
256275
expect(calls).toEqual(["sessions.list:designer:agent_1:10:page_1"]);
257276
});
258277

@@ -283,14 +302,14 @@ describe("createPlaybookSessionRuntime", () => {
283302
},
284303
async getDetail(input) {
285304
calls.push(`sessions.detail:${input.sessionId}:${input.playbookId}`);
286-
return { sessionId: input.sessionId, playbookId: input.playbookId };
305+
return fakeDetail(input.sessionId);
287306
},
288307
},
289308
});
290309

291310
const detail = await runtime.getDetail({ playbookId: "designer", sessionId: "sess_1" });
292311

293-
expect(detail).toEqual({ sessionId: "sess_1", playbookId: "designer" });
312+
expect(detail.session.session_id).toBe("sess_1");
294313
expect(calls).toEqual(["sessions.detail:sess_1:designer"]);
295314
});
296315

0 commit comments

Comments
 (0)