agentxjs 2.9.0-dev-20260317060607 → 2.9.0-dev-20260317112104
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/dist/{chunk-SFWLYRLB.js → chunk-TKYJYJS5.js} +21 -21
- package/dist/{chunk-SFWLYRLB.js.map → chunk-TKYJYJS5.js.map} +1 -1
- package/dist/index.d.ts +15 -15
- package/dist/index.js +28 -28
- package/dist/index.js.map +1 -1
- package/dist/{server-CFC6ONC6.js → server-N3SRHICW.js} +2 -2
- package/package.json +3 -3
- package/src/LocalClient.ts +10 -10
- package/src/handlers/__tests__/workspace-handler.test.ts +34 -34
- package/src/handlers/index.ts +2 -2
- package/src/handlers/workspace.ts +20 -20
- package/src/index.ts +3 -3
- package/src/namespaces/presentations.ts +7 -7
- package/src/presentation/Presentation.ts +16 -16
- package/src/presentation/__tests__/workspace-init.test.ts +20 -20
- package/src/presentation/__tests__/workspace.test.ts +17 -17
- package/src/presentation/index.ts +3 -3
- package/src/presentation/reducer.ts +1 -1
- package/src/presentation/types.ts +10 -10
- /package/dist/{server-CFC6ONC6.js.map → server-N3SRHICW.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* OS RPC Handler Tests
|
|
3
3
|
*
|
|
4
|
-
* Verifies the server-side
|
|
4
|
+
* Verifies the server-side os.list/read/write handlers
|
|
5
5
|
* work correctly through the RpcHandlerRegistry.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -10,23 +10,23 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { RpcHandlerRegistry } from "../../RpcHandlerRegistry";
|
|
13
|
-
import {
|
|
13
|
+
import { registerOSHandlers } from "../workspace";
|
|
14
14
|
|
|
15
15
|
let tempDir: string;
|
|
16
16
|
let registry: RpcHandlerRegistry;
|
|
17
17
|
|
|
18
|
-
// Minimal runtime mock with real
|
|
19
|
-
function createMockRuntime(
|
|
20
|
-
const {
|
|
21
|
-
const
|
|
18
|
+
// Minimal runtime mock with real OS
|
|
19
|
+
function createMockRuntime(basePath: string) {
|
|
20
|
+
const { LocalOSProvider } = require("@agentxjs/node-platform");
|
|
21
|
+
const osProvider = new LocalOSProvider(basePath);
|
|
22
22
|
|
|
23
23
|
return {
|
|
24
24
|
platform: {
|
|
25
|
-
|
|
25
|
+
osProvider,
|
|
26
26
|
imageRepository: {
|
|
27
27
|
findImageById: async (imageId: string) => {
|
|
28
28
|
if (imageId === "img_test") {
|
|
29
|
-
return { imageId: "img_test",
|
|
29
|
+
return { imageId: "img_test", osId: "os_test" };
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
},
|
|
@@ -36,26 +36,26 @@ function createMockRuntime(workspacesDir: string) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
beforeEach(async () => {
|
|
39
|
-
tempDir = await mkdtemp(join(tmpdir(), "agentx-
|
|
39
|
+
tempDir = await mkdtemp(join(tmpdir(), "agentx-os-handler-test-"));
|
|
40
40
|
registry = new RpcHandlerRegistry();
|
|
41
|
-
|
|
41
|
+
registerOSHandlers(registry);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
afterEach(async () => {
|
|
45
45
|
await rm(tempDir, { recursive: true, force: true });
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
describe("
|
|
49
|
-
test("returns files from
|
|
50
|
-
// Create
|
|
51
|
-
const
|
|
48
|
+
describe("os.list RPC handler", () => {
|
|
49
|
+
test("returns files from OS directory", async () => {
|
|
50
|
+
// Create OS dir and files
|
|
51
|
+
const osDir = join(tempDir, "os_test");
|
|
52
52
|
const { mkdir } = await import("node:fs/promises");
|
|
53
|
-
await mkdir(
|
|
54
|
-
await writeFile(join(
|
|
55
|
-
await writeFile(join(
|
|
53
|
+
await mkdir(osDir, { recursive: true });
|
|
54
|
+
await writeFile(join(osDir, "hello.txt"), "world");
|
|
55
|
+
await writeFile(join(osDir, "test.js"), "console.log('hi')");
|
|
56
56
|
|
|
57
57
|
const runtime = createMockRuntime(tempDir);
|
|
58
|
-
const result = await registry.handle(runtime, "
|
|
58
|
+
const result = await registry.handle(runtime, "os.list", {
|
|
59
59
|
imageId: "img_test",
|
|
60
60
|
path: ".",
|
|
61
61
|
});
|
|
@@ -69,13 +69,13 @@ describe("workspace.list RPC handler", () => {
|
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
test("returns empty array for empty
|
|
73
|
-
const
|
|
72
|
+
test("returns empty array for empty OS directory", async () => {
|
|
73
|
+
const osDir = join(tempDir, "os_test");
|
|
74
74
|
const { mkdir } = await import("node:fs/promises");
|
|
75
|
-
await mkdir(
|
|
75
|
+
await mkdir(osDir, { recursive: true });
|
|
76
76
|
|
|
77
77
|
const runtime = createMockRuntime(tempDir);
|
|
78
|
-
const result = await registry.handle(runtime, "
|
|
78
|
+
const result = await registry.handle(runtime, "os.list", {
|
|
79
79
|
imageId: "img_test",
|
|
80
80
|
});
|
|
81
81
|
|
|
@@ -87,7 +87,7 @@ describe("workspace.list RPC handler", () => {
|
|
|
87
87
|
|
|
88
88
|
test("returns error for unknown imageId", async () => {
|
|
89
89
|
const runtime = createMockRuntime(tempDir);
|
|
90
|
-
const result = await registry.handle(runtime, "
|
|
90
|
+
const result = await registry.handle(runtime, "os.list", {
|
|
91
91
|
imageId: "img_unknown",
|
|
92
92
|
});
|
|
93
93
|
|
|
@@ -95,15 +95,15 @@ describe("workspace.list RPC handler", () => {
|
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
describe("
|
|
98
|
+
describe("os.read RPC handler", () => {
|
|
99
99
|
test("reads file content", async () => {
|
|
100
|
-
const
|
|
100
|
+
const osDir = join(tempDir, "os_test");
|
|
101
101
|
const { mkdir } = await import("node:fs/promises");
|
|
102
|
-
await mkdir(
|
|
103
|
-
await writeFile(join(
|
|
102
|
+
await mkdir(osDir, { recursive: true });
|
|
103
|
+
await writeFile(join(osDir, "data.txt"), "hello world");
|
|
104
104
|
|
|
105
105
|
const runtime = createMockRuntime(tempDir);
|
|
106
|
-
const result = await registry.handle(runtime, "
|
|
106
|
+
const result = await registry.handle(runtime, "os.read", {
|
|
107
107
|
imageId: "img_test",
|
|
108
108
|
path: "data.txt",
|
|
109
109
|
});
|
|
@@ -115,16 +115,16 @@ describe("workspace.read RPC handler", () => {
|
|
|
115
115
|
});
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
describe("
|
|
118
|
+
describe("os.write RPC handler", () => {
|
|
119
119
|
test("writes file and can read it back", async () => {
|
|
120
|
-
const
|
|
120
|
+
const osDir = join(tempDir, "os_test");
|
|
121
121
|
const { mkdir } = await import("node:fs/promises");
|
|
122
|
-
await mkdir(
|
|
122
|
+
await mkdir(osDir, { recursive: true });
|
|
123
123
|
|
|
124
124
|
const runtime = createMockRuntime(tempDir);
|
|
125
125
|
|
|
126
126
|
// Write
|
|
127
|
-
const writeResult = await registry.handle(runtime, "
|
|
127
|
+
const writeResult = await registry.handle(runtime, "os.write", {
|
|
128
128
|
imageId: "img_test",
|
|
129
129
|
path: "output.txt",
|
|
130
130
|
content: "written via RPC",
|
|
@@ -132,7 +132,7 @@ describe("workspace.write RPC handler", () => {
|
|
|
132
132
|
expect(writeResult.success).toBe(true);
|
|
133
133
|
|
|
134
134
|
// Read back
|
|
135
|
-
const readResult = await registry.handle(runtime, "
|
|
135
|
+
const readResult = await registry.handle(runtime, "os.read", {
|
|
136
136
|
imageId: "img_test",
|
|
137
137
|
path: "output.txt",
|
|
138
138
|
});
|
package/src/handlers/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { registerImageHandlers } from "./image";
|
|
|
12
12
|
import { registerInstanceHandlers } from "./instance";
|
|
13
13
|
import { registerLLMHandlers } from "./llm";
|
|
14
14
|
import { registerMessageHandlers } from "./message";
|
|
15
|
-
import {
|
|
15
|
+
import { registerOSHandlers } from "./workspace";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Register all RPC handlers on the registry
|
|
@@ -22,5 +22,5 @@ export function registerAll(registry: RpcHandlerRegistry): void {
|
|
|
22
22
|
registerInstanceHandlers(registry);
|
|
23
23
|
registerMessageHandlers(registry);
|
|
24
24
|
registerLLMHandlers(registry);
|
|
25
|
-
|
|
25
|
+
registerOSHandlers(registry);
|
|
26
26
|
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* OS RPC Handlers — exposes AgentOS file operations via RPC
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { RpcHandlerRegistry } from "../RpcHandlerRegistry";
|
|
6
6
|
import { err, ok } from "../RpcHandlerRegistry";
|
|
7
7
|
|
|
8
|
-
export function
|
|
9
|
-
registry.register("
|
|
8
|
+
export function registerOSHandlers(registry: RpcHandlerRegistry): void {
|
|
9
|
+
registry.register("os.read", async (runtime, params) => {
|
|
10
10
|
const { imageId, path } = params as { imageId: string; path: string };
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
11
|
+
const op = runtime.platform.osProvider;
|
|
12
|
+
if (!op) return err(-32000, "OS not available");
|
|
13
13
|
|
|
14
14
|
const img = await runtime.platform.imageRepository.findImageById(imageId);
|
|
15
|
-
if (!img?.
|
|
15
|
+
if (!img?.osId) return err(404, "Image has no OS");
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
const content = await
|
|
17
|
+
const os = await op.create(img.osId);
|
|
18
|
+
const content = await os.fs.read(path);
|
|
19
19
|
return ok({ content });
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
registry.register("
|
|
22
|
+
registry.register("os.list", async (runtime, params) => {
|
|
23
23
|
const { imageId, path } = params as { imageId: string; path?: string };
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
24
|
+
const op = runtime.platform.osProvider;
|
|
25
|
+
if (!op) return err(-32000, "OS not available");
|
|
26
26
|
|
|
27
27
|
const img = await runtime.platform.imageRepository.findImageById(imageId);
|
|
28
|
-
if (!img?.
|
|
28
|
+
if (!img?.osId) return err(404, "Image has no OS");
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
const files = await
|
|
30
|
+
const os = await op.create(img.osId);
|
|
31
|
+
const files = await os.fs.list(path ?? ".");
|
|
32
32
|
return ok({ files });
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
registry.register("
|
|
35
|
+
registry.register("os.write", async (runtime, params) => {
|
|
36
36
|
const { imageId, path, content } = params as { imageId: string; path: string; content: string };
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
37
|
+
const op = runtime.platform.osProvider;
|
|
38
|
+
if (!op) return err(-32000, "OS not available");
|
|
39
39
|
|
|
40
40
|
const img = await runtime.platform.imageRepository.findImageById(imageId);
|
|
41
|
-
if (!img?.
|
|
41
|
+
if (!img?.osId) return err(404, "Image has no OS");
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
await
|
|
43
|
+
const os = await op.create(img.osId);
|
|
44
|
+
await os.fs.write(path, content);
|
|
45
45
|
return ok({ success: true });
|
|
46
46
|
});
|
|
47
47
|
}
|
package/src/index.ts
CHANGED
|
@@ -227,16 +227,16 @@ export type {
|
|
|
227
227
|
FileBlock,
|
|
228
228
|
FileTreeEntry,
|
|
229
229
|
ImageBlock,
|
|
230
|
+
OS,
|
|
231
|
+
OSState,
|
|
230
232
|
PresentationMetrics,
|
|
231
233
|
PresentationOptions,
|
|
234
|
+
PresentationOS,
|
|
232
235
|
PresentationState,
|
|
233
|
-
PresentationWorkspace,
|
|
234
236
|
TextBlock,
|
|
235
237
|
ThinkingBlock,
|
|
236
238
|
ToolBlock,
|
|
237
239
|
UserConversation,
|
|
238
|
-
Workspace,
|
|
239
|
-
WorkspaceState,
|
|
240
240
|
} from "./presentation";
|
|
241
241
|
export {
|
|
242
242
|
addUserConversation,
|
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { messagesToConversations, Presentation, type PresentationOptions } from "../presentation";
|
|
10
|
-
import type {
|
|
10
|
+
import type { PresentationOS } from "../presentation/types";
|
|
11
11
|
import type { AgentX, PresentationNamespace, SessionNamespace } from "../types";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* OS resolver — given an imageId, returns a PresentationOS or null.
|
|
15
15
|
* Provided by the client (local or remote) to decouple from runtime internals.
|
|
16
16
|
*/
|
|
17
|
-
export type
|
|
17
|
+
export type OSResolver = (imageId: string) => Promise<PresentationOS | null>;
|
|
18
18
|
|
|
19
19
|
export function createPresentations(
|
|
20
20
|
agentx: AgentX,
|
|
21
21
|
sessionNs: SessionNamespace,
|
|
22
|
-
|
|
22
|
+
osResolver?: OSResolver
|
|
23
23
|
): PresentationNamespace {
|
|
24
24
|
const instances = new Map<string, Presentation>();
|
|
25
25
|
|
|
@@ -34,13 +34,13 @@ export function createPresentations(
|
|
|
34
34
|
return existing;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// Resolve
|
|
38
|
-
const
|
|
37
|
+
// Resolve OS for this image
|
|
38
|
+
const os = osResolver ? await osResolver(imageId) : null;
|
|
39
39
|
|
|
40
40
|
// Create new from history
|
|
41
41
|
const messages = await sessionNs.getMessages(imageId);
|
|
42
42
|
const conversations = messagesToConversations(messages);
|
|
43
|
-
const presentation = new Presentation(agentx, imageId, options, conversations,
|
|
43
|
+
const presentation = new Presentation(agentx, imageId, options, conversations, os);
|
|
44
44
|
|
|
45
45
|
instances.set(imageId, presentation);
|
|
46
46
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* // Read state
|
|
10
10
|
* pres.conversations // message history
|
|
11
11
|
* pres.status // "idle" | "submitted" | "thinking" | "responding" | "executing"
|
|
12
|
-
* pres.
|
|
12
|
+
* pres.os // { files, read, write, list } or null
|
|
13
13
|
*
|
|
14
14
|
* // Subscribe (works with useSyncExternalStore)
|
|
15
15
|
* const unsub = pres.subscribe(() => rerender());
|
|
@@ -30,8 +30,8 @@ import type {
|
|
|
30
30
|
ConnectionState,
|
|
31
31
|
Conversation,
|
|
32
32
|
PresentationMetrics,
|
|
33
|
+
PresentationOS,
|
|
33
34
|
PresentationState,
|
|
34
|
-
PresentationWorkspace,
|
|
35
35
|
} from "./types";
|
|
36
36
|
import { initialPresentationState } from "./types";
|
|
37
37
|
|
|
@@ -40,9 +40,9 @@ import { initialPresentationState } from "./types";
|
|
|
40
40
|
// ============================================================================
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* OS view — file tree + operations, unified.
|
|
44
44
|
*/
|
|
45
|
-
export interface
|
|
45
|
+
export interface OS {
|
|
46
46
|
/** Current file tree (real-time updates) */
|
|
47
47
|
readonly files: readonly import("./types").FileTreeEntry[];
|
|
48
48
|
/** Read file content */
|
|
@@ -72,18 +72,18 @@ export class Presentation {
|
|
|
72
72
|
private _listeners = new Set<() => void>();
|
|
73
73
|
private _legacyHandlers = new Set<(state: PresentationState) => void>();
|
|
74
74
|
private _eventUnsubscribe: (() => void) | null = null;
|
|
75
|
-
private
|
|
75
|
+
private _osOps: PresentationOS | null;
|
|
76
76
|
|
|
77
77
|
constructor(
|
|
78
78
|
agentx: AgentX,
|
|
79
79
|
instanceId: string,
|
|
80
80
|
options?: PresentationOptions,
|
|
81
81
|
initialConversations?: Conversation[],
|
|
82
|
-
|
|
82
|
+
os?: PresentationOS | null
|
|
83
83
|
) {
|
|
84
84
|
this._agentx = agentx;
|
|
85
85
|
this._instanceId = instanceId;
|
|
86
|
-
this.
|
|
86
|
+
this._osOps = os ?? null;
|
|
87
87
|
this._state = initialConversations?.length
|
|
88
88
|
? { ...initialPresentationState, conversations: initialConversations }
|
|
89
89
|
: createInitialState();
|
|
@@ -97,9 +97,9 @@ export class Presentation {
|
|
|
97
97
|
this._subscribeToEvents();
|
|
98
98
|
|
|
99
99
|
// Load initial workspace file tree
|
|
100
|
-
if (this.
|
|
101
|
-
this.
|
|
102
|
-
this._state = { ...this._state,
|
|
100
|
+
if (this._osOps) {
|
|
101
|
+
this._osOps.list(".").then((files) => {
|
|
102
|
+
this._state = { ...this._state, os: { files } };
|
|
103
103
|
this._notify();
|
|
104
104
|
});
|
|
105
105
|
}
|
|
@@ -127,14 +127,14 @@ export class Presentation {
|
|
|
127
127
|
return this._state.metrics;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
/**
|
|
131
|
-
get
|
|
132
|
-
if (!this.
|
|
133
|
-
const ops = this.
|
|
134
|
-
const
|
|
130
|
+
/** OS — file tree + operations. null if agent has no OS. */
|
|
131
|
+
get os(): OS | null {
|
|
132
|
+
if (!this._osOps) return null;
|
|
133
|
+
const ops = this._osOps;
|
|
134
|
+
const osState = this._state.os;
|
|
135
135
|
return {
|
|
136
136
|
get files() {
|
|
137
|
-
return
|
|
137
|
+
return osState?.files ?? [];
|
|
138
138
|
},
|
|
139
139
|
read: (path: string) => ops.read(path),
|
|
140
140
|
write: (path: string, content: string) => ops.write(path, content),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* OS Initialization Tests — New Presentation API
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, mock, test } from "bun:test";
|
|
6
6
|
import { Presentation } from "../Presentation";
|
|
7
|
-
import type {
|
|
7
|
+
import type { PresentationOS } from "../types";
|
|
8
8
|
|
|
9
9
|
function createMockAgentX() {
|
|
10
10
|
return {
|
|
@@ -19,41 +19,41 @@ function createMockAgentX() {
|
|
|
19
19
|
} as any;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
describe("
|
|
23
|
-
test("constructor calls
|
|
22
|
+
describe("OS initialization in Presentation", () => {
|
|
23
|
+
test("constructor calls os.list('.') and populates os.files", async () => {
|
|
24
24
|
const mockFiles = [
|
|
25
25
|
{ name: "src", path: "src", type: "directory" as const },
|
|
26
26
|
{ name: "package.json", path: "package.json", type: "file" as const },
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
const mockOS: PresentationOS = {
|
|
30
30
|
read: mock(() => Promise.resolve("")),
|
|
31
31
|
write: mock(() => Promise.resolve()),
|
|
32
32
|
list: mock(() => Promise.resolve(mockFiles)),
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
const ax = createMockAgentX();
|
|
36
|
-
const pres = new Presentation(ax, "img_test", undefined, undefined,
|
|
36
|
+
const pres = new Presentation(ax, "img_test", undefined, undefined, mockOS);
|
|
37
37
|
|
|
38
38
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
39
39
|
|
|
40
|
-
expect(
|
|
41
|
-
expect(pres.
|
|
42
|
-
expect(pres.
|
|
40
|
+
expect(mockOS.list).toHaveBeenCalledWith(".");
|
|
41
|
+
expect(pres.os).not.toBeNull();
|
|
42
|
+
expect(pres.os!.files).toEqual(mockFiles);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
test("
|
|
45
|
+
test("os is null when no OS provided", async () => {
|
|
46
46
|
const ax = createMockAgentX();
|
|
47
47
|
const pres = new Presentation(ax, "img_test", undefined, undefined, null);
|
|
48
48
|
|
|
49
49
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
50
50
|
|
|
51
|
-
expect(pres.
|
|
51
|
+
expect(pres.os).toBeNull();
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
test("subscribe fires on
|
|
54
|
+
test("subscribe fires on OS update", async () => {
|
|
55
55
|
const mockFiles = [{ name: "test.txt", path: "test.txt", type: "file" as const }];
|
|
56
|
-
const
|
|
56
|
+
const mockOS: PresentationOS = {
|
|
57
57
|
read: mock(() => Promise.resolve("")),
|
|
58
58
|
write: mock(() => Promise.resolve()),
|
|
59
59
|
list: mock(() => Promise.resolve(mockFiles)),
|
|
@@ -61,7 +61,7 @@ describe("Workspace initialization in Presentation", () => {
|
|
|
61
61
|
|
|
62
62
|
let notified = false;
|
|
63
63
|
const ax = createMockAgentX();
|
|
64
|
-
const pres = new Presentation(ax, "img_test", undefined, undefined,
|
|
64
|
+
const pres = new Presentation(ax, "img_test", undefined, undefined, mockOS);
|
|
65
65
|
pres.subscribe(() => {
|
|
66
66
|
notified = true;
|
|
67
67
|
});
|
|
@@ -69,12 +69,12 @@ describe("Workspace initialization in Presentation", () => {
|
|
|
69
69
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
70
70
|
|
|
71
71
|
expect(notified).toBe(true);
|
|
72
|
-
expect(pres.
|
|
72
|
+
expect(pres.os!.files).toEqual(mockFiles);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
test("workspace_tree event updates
|
|
75
|
+
test("workspace_tree event updates os.files", async () => {
|
|
76
76
|
const initialFiles = [{ name: "init.txt", path: "init.txt", type: "file" as const }];
|
|
77
|
-
const
|
|
77
|
+
const mockOS: PresentationOS = {
|
|
78
78
|
read: mock(() => Promise.resolve("")),
|
|
79
79
|
write: mock(() => Promise.resolve()),
|
|
80
80
|
list: mock(() => Promise.resolve(initialFiles)),
|
|
@@ -87,10 +87,10 @@ describe("Workspace initialization in Presentation", () => {
|
|
|
87
87
|
return () => {};
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
const pres = new Presentation(ax, "img_test", undefined, undefined,
|
|
90
|
+
const pres = new Presentation(ax, "img_test", undefined, undefined, mockOS);
|
|
91
91
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
92
92
|
|
|
93
|
-
expect(pres.
|
|
93
|
+
expect(pres.os!.files).toEqual(initialFiles);
|
|
94
94
|
|
|
95
95
|
// Simulate workspace_tree event
|
|
96
96
|
const updatedFiles = [
|
|
@@ -99,6 +99,6 @@ describe("Workspace initialization in Presentation", () => {
|
|
|
99
99
|
];
|
|
100
100
|
eventHandler!({ type: "workspace_tree", timestamp: Date.now(), data: { files: updatedFiles } });
|
|
101
101
|
|
|
102
|
-
expect(pres.
|
|
102
|
+
expect(pres.os!.files).toEqual(updatedFiles);
|
|
103
103
|
});
|
|
104
104
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Presentation
|
|
2
|
+
* Presentation OS Tests
|
|
3
3
|
*
|
|
4
|
-
* Tests the
|
|
5
|
-
* 1. Reducer handles workspace_tree events → updates PresentationState.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3. Initial state has
|
|
4
|
+
* Tests the OS integration in the Presentation layer:
|
|
5
|
+
* 1. Reducer handles workspace_tree events → updates PresentationState.os
|
|
6
|
+
* 2. PresentationOS operations (read/write/list)
|
|
7
|
+
* 3. Initial state has os: null
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, expect, test } from "bun:test";
|
|
@@ -15,13 +15,13 @@ import type { FileTreeEntry, PresentationState } from "../types";
|
|
|
15
15
|
// Reducer: workspace_tree event
|
|
16
16
|
// ============================================================================
|
|
17
17
|
|
|
18
|
-
describe("Presentation
|
|
19
|
-
test("initial state has
|
|
18
|
+
describe("Presentation OS Reducer", () => {
|
|
19
|
+
test("initial state has os: null", () => {
|
|
20
20
|
const state = createInitialState();
|
|
21
|
-
expect(state.
|
|
21
|
+
expect(state.os).toBeNull();
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
test("workspace_tree event sets
|
|
24
|
+
test("workspace_tree event sets os.files", () => {
|
|
25
25
|
const state = createInitialState();
|
|
26
26
|
const files: FileTreeEntry[] = [
|
|
27
27
|
{ name: "src", path: "src", type: "directory" },
|
|
@@ -35,14 +35,14 @@ describe("Presentation Workspace Reducer", () => {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const newState = presentationReducer(state, event as any);
|
|
38
|
-
expect(newState.
|
|
39
|
-
expect(newState.
|
|
38
|
+
expect(newState.os).not.toBeNull();
|
|
39
|
+
expect(newState.os!.files).toEqual(files);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test("workspace_tree event replaces previous files", () => {
|
|
43
43
|
const state: PresentationState = {
|
|
44
44
|
...createInitialState(),
|
|
45
|
-
|
|
45
|
+
os: {
|
|
46
46
|
files: [{ name: "old.txt", path: "old.txt", type: "file" }],
|
|
47
47
|
},
|
|
48
48
|
};
|
|
@@ -59,8 +59,8 @@ describe("Presentation Workspace Reducer", () => {
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
const newState = presentationReducer(state, event as any);
|
|
62
|
-
expect(newState.
|
|
63
|
-
expect(newState.
|
|
62
|
+
expect(newState.os!.files).toEqual(newFiles);
|
|
63
|
+
expect(newState.os!.files).not.toContainEqual({
|
|
64
64
|
name: "old.txt",
|
|
65
65
|
path: "old.txt",
|
|
66
66
|
type: "file",
|
|
@@ -80,10 +80,10 @@ describe("Presentation Workspace Reducer", () => {
|
|
|
80
80
|
const newState = presentationReducer(state, event as any);
|
|
81
81
|
expect(newState.status).toBe("responding");
|
|
82
82
|
expect(newState.conversations).toEqual([]);
|
|
83
|
-
expect(newState.
|
|
83
|
+
expect(newState.os).toEqual({ files: [] });
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
test("unknown event does not create
|
|
86
|
+
test("unknown event does not create os state", () => {
|
|
87
87
|
const state = createInitialState();
|
|
88
88
|
const event = {
|
|
89
89
|
type: "some_unknown_event",
|
|
@@ -92,7 +92,7 @@ describe("Presentation Workspace Reducer", () => {
|
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
const newState = presentationReducer(state, event as any);
|
|
95
|
-
expect(newState.
|
|
95
|
+
expect(newState.os).toBeNull();
|
|
96
96
|
expect(newState).toBe(state); // Same reference = no change
|
|
97
97
|
});
|
|
98
98
|
});
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export {
|
|
8
|
+
type OS,
|
|
8
9
|
Presentation,
|
|
9
10
|
type PresentationOptions,
|
|
10
|
-
type Workspace,
|
|
11
11
|
} from "./Presentation";
|
|
12
12
|
export {
|
|
13
13
|
addUserConversation,
|
|
@@ -24,9 +24,10 @@ export type {
|
|
|
24
24
|
FileBlock,
|
|
25
25
|
FileTreeEntry,
|
|
26
26
|
ImageBlock,
|
|
27
|
+
OSState,
|
|
27
28
|
PresentationMetrics,
|
|
29
|
+
PresentationOS,
|
|
28
30
|
PresentationState,
|
|
29
|
-
PresentationWorkspace,
|
|
30
31
|
SessionMetrics,
|
|
31
32
|
TextBlock,
|
|
32
33
|
ThinkingBlock,
|
|
@@ -34,6 +35,5 @@ export type {
|
|
|
34
35
|
ToolBlock,
|
|
35
36
|
TurnMetrics,
|
|
36
37
|
UserConversation,
|
|
37
|
-
WorkspaceState,
|
|
38
38
|
} from "./types";
|
|
39
39
|
export { initialMetrics, initialPresentationState, initialSessionMetrics } from "./types";
|