@agent-os-lab/agent-game-sdk 0.1.7 → 0.1.9
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/README.md +32 -0
- package/USAGE.md +46 -2
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/office/core/projection.ts +14 -13
- package/src/office/mount.ts +8 -11
- package/src/office/renderers/three/mount.ts +11 -6
- package/src/runtime-agent-list.ts +86 -0
package/README.md
CHANGED
|
@@ -78,6 +78,38 @@ export function Office() {
|
|
|
78
78
|
|
|
79
79
|
`agent-game-sdk/office` is renderer and framework neutral. `agent-game-sdk/office/react` is the React-only entrypoint.
|
|
80
80
|
|
|
81
|
+
## Runtime Agent List
|
|
82
|
+
|
|
83
|
+
Use `subscribeAgentPresenceList` when an app needs the live Agent roster and status outside the 3D office view:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import {
|
|
87
|
+
AgentGameRuntimeBrowserClient,
|
|
88
|
+
formatAgentPresenceStatus,
|
|
89
|
+
subscribeAgentPresenceList,
|
|
90
|
+
} from "@agent-os-lab/agent-game-sdk";
|
|
91
|
+
|
|
92
|
+
const client = new AgentGameRuntimeBrowserClient({
|
|
93
|
+
baseUrl: "",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const subscription = await subscribeAgentPresenceList(client, {
|
|
97
|
+
onAgentsChange: (agents) => {
|
|
98
|
+
renderAgentList(agents.map((agent) => ({
|
|
99
|
+
id: agent.agentId,
|
|
100
|
+
name: agent.displayName,
|
|
101
|
+
status: formatAgentPresenceStatus(agent.status),
|
|
102
|
+
activity: agent.activity?.summary,
|
|
103
|
+
})));
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const currentAgents = subscription.getAgents();
|
|
108
|
+
subscription.close();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The helper applies runtime `snapshot` messages as the full roster and `patch` messages as incremental updates, so consumers always receive a complete sorted Agent list.
|
|
112
|
+
|
|
81
113
|
The built-in 3D renderer supports semantic room configuration. If `office` is omitted, the SDK renders the default `office + auditorium + gym` layout. To choose visible rooms, pass `office.rooms` in the order you want them assembled:
|
|
82
114
|
|
|
83
115
|
```ts
|
package/USAGE.md
CHANGED
|
@@ -32,6 +32,7 @@ Runtime requirements:
|
|
|
32
32
|
import {
|
|
33
33
|
AgentGameRuntimeBrowserClient,
|
|
34
34
|
AgentGameRuntimeServerClient,
|
|
35
|
+
subscribeAgentPresenceList,
|
|
35
36
|
} from "@agent-os-lab/agent-game-sdk";
|
|
36
37
|
import { mountAgentGameOffice } from "@agent-os-lab/agent-game-sdk/office";
|
|
37
38
|
import { AgentGameOfficeView } from "@agent-os-lab/agent-game-sdk/office/react";
|
|
@@ -91,7 +92,50 @@ The browser client strips caller-provided `authorization`, `x-hermes-tenant-id`,
|
|
|
91
92
|
|
|
92
93
|
## Runtime Subscription
|
|
93
94
|
|
|
94
|
-
Use `
|
|
95
|
+
Use `subscribeAgentPresenceList` when the application wants the current Agent roster and realtime status outside the office view.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import {
|
|
99
|
+
AgentGameRuntimeBrowserClient,
|
|
100
|
+
formatAgentPresenceStatus,
|
|
101
|
+
subscribeAgentPresenceList,
|
|
102
|
+
type AgentPresence,
|
|
103
|
+
} from "@agent-os-lab/agent-game-sdk";
|
|
104
|
+
|
|
105
|
+
const browserClient = new AgentGameRuntimeBrowserClient({
|
|
106
|
+
baseUrl: "",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
let agents: AgentPresence[] = [];
|
|
110
|
+
|
|
111
|
+
const subscription = await subscribeAgentPresenceList(browserClient, {
|
|
112
|
+
onAgentsChange: (nextAgents, message) => {
|
|
113
|
+
agents = nextAgents;
|
|
114
|
+
console.log("agent roster updated from", message.type);
|
|
115
|
+
console.table(agents.map((agent) => ({
|
|
116
|
+
name: agent.displayName,
|
|
117
|
+
status: formatAgentPresenceStatus(agent.status),
|
|
118
|
+
activity: agent.activity?.summary ?? "",
|
|
119
|
+
})));
|
|
120
|
+
},
|
|
121
|
+
onError: (error) => {
|
|
122
|
+
console.error("runtime stream error", error);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const latestAgents = subscription.getAgents();
|
|
127
|
+
subscription.refresh();
|
|
128
|
+
subscription.close();
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`subscribeAgentPresenceList` handles both runtime message shapes:
|
|
132
|
+
|
|
133
|
+
- `snapshot`: replaces the current roster with the full current presence list.
|
|
134
|
+
- `patch`: merges changed Agent presence records into the current roster.
|
|
135
|
+
|
|
136
|
+
The `onAgentsChange` callback receives a complete display-name-sorted `AgentPresence[]` after every snapshot or patch. The returned subscription extends the normal runtime subscription with `getAgents()` for synchronous reads of the latest list.
|
|
137
|
+
|
|
138
|
+
Use `subscribe` when the application needs raw runtime messages instead of the SDK-maintained Agent list.
|
|
95
139
|
|
|
96
140
|
```ts
|
|
97
141
|
const subscription = await browserClient.subscribe({
|
|
@@ -114,7 +158,7 @@ Runtime messages are either:
|
|
|
114
158
|
- `snapshot`: full current presence list for the tenant.
|
|
115
159
|
- `patch`: changed agent presence records.
|
|
116
160
|
|
|
117
|
-
Use `mergeAgentPresence` when maintaining your own
|
|
161
|
+
Use `mergeAgentPresence` only when maintaining your own custom presence reducer from snapshots and patches.
|
|
118
162
|
|
|
119
163
|
```ts
|
|
120
164
|
import { mergeAgentPresence } from "@agent-os-lab/agent-game-sdk";
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -7,16 +7,7 @@ import type {
|
|
|
7
7
|
AgentPresence,
|
|
8
8
|
AgentPresenceStatus,
|
|
9
9
|
} from "./types";
|
|
10
|
-
|
|
11
|
-
const statusLabels = {
|
|
12
|
-
working: "Working",
|
|
13
|
-
thinking: "Thinking",
|
|
14
|
-
meeting: "Meeting",
|
|
15
|
-
resting: "Resting",
|
|
16
|
-
entertaining: "Entertainment",
|
|
17
|
-
idle: "Idle",
|
|
18
|
-
offline: "Offline",
|
|
19
|
-
} satisfies Record<AgentPresenceStatus, string>;
|
|
10
|
+
import { formatAgentPresenceStatus } from "../../runtime-agent-list";
|
|
20
11
|
|
|
21
12
|
const ambientZoneIds = ["lounge", "pantry", "gym", "reading"] as const;
|
|
22
13
|
|
|
@@ -27,9 +18,9 @@ export function mapPresenceToOfficeAgents(
|
|
|
27
18
|
id: agent.agentId,
|
|
28
19
|
name: agent.displayName,
|
|
29
20
|
role: agent.scene ?? null,
|
|
30
|
-
statusLabel:
|
|
21
|
+
statusLabel: formatAgentPresenceStatus(agent.status),
|
|
31
22
|
activity: agent.activity,
|
|
32
|
-
activityLabel: sanitizeActivityLabel(agent.activity),
|
|
23
|
+
activityLabel: sanitizeActivityLabel(agent.activity, agent.status),
|
|
33
24
|
sceneState: mapStatusToSceneState(agent.status),
|
|
34
25
|
zoneId: mapStatusToZoneId(agent.status, agent.agentId),
|
|
35
26
|
updatedAt: agent.updatedAt,
|
|
@@ -68,7 +59,13 @@ function mapStatusToSceneState(status: AgentPresenceStatus): AgentGameOfficeScen
|
|
|
68
59
|
return status;
|
|
69
60
|
}
|
|
70
61
|
|
|
71
|
-
function sanitizeActivityLabel(
|
|
62
|
+
function sanitizeActivityLabel(
|
|
63
|
+
activity: AgentPresence["activity"],
|
|
64
|
+
status: AgentPresenceStatus,
|
|
65
|
+
): string | undefined {
|
|
66
|
+
if (!isActiveWorkStatus(status)) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
72
69
|
if (activity?.kind === "completed") {
|
|
73
70
|
return undefined;
|
|
74
71
|
}
|
|
@@ -108,6 +105,10 @@ function formatToolActivityLabel(label: string): string {
|
|
|
108
105
|
return label === "正在使用工具" ? "正在调用工具..." : label;
|
|
109
106
|
}
|
|
110
107
|
|
|
108
|
+
function isActiveWorkStatus(status: AgentPresenceStatus): boolean {
|
|
109
|
+
return status === "working" || status === "thinking";
|
|
110
|
+
}
|
|
111
|
+
|
|
111
112
|
function mapStatusToZoneId(status: AgentPresenceStatus, agentId: string): AgentGameOfficeZoneId {
|
|
112
113
|
switch (status) {
|
|
113
114
|
case "working":
|
package/src/office/mount.ts
CHANGED
|
@@ -2,7 +2,10 @@ import {
|
|
|
2
2
|
createSnapshotOfficeSource,
|
|
3
3
|
resolveOfficeSource,
|
|
4
4
|
} from "./core/source";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
subscribeAgentPresenceList,
|
|
7
|
+
type AgentPresenceListSubscription,
|
|
8
|
+
} from "../runtime-agent-list";
|
|
6
9
|
import { resolveOfficeLayout } from "./layout";
|
|
7
10
|
import type {
|
|
8
11
|
AgentGameOfficeMountOptions,
|
|
@@ -29,8 +32,7 @@ export async function mountAgentGameOffice(
|
|
|
29
32
|
const source = mutableSource ?? runtimeSource ?? resolveOfficeSource(options.source);
|
|
30
33
|
let controller: AgentGameOfficeRendererController | null = null;
|
|
31
34
|
let latestSnapshot: AgentGameOfficeSnapshot | null = null;
|
|
32
|
-
let runtimeSubscription:
|
|
33
|
-
let runtimeAgents: AgentPresence[] = [];
|
|
35
|
+
let runtimeSubscription: AgentPresenceListSubscription | null = null;
|
|
34
36
|
|
|
35
37
|
const unsubscribe = source.subscribe((snapshot) => {
|
|
36
38
|
latestSnapshot = snapshot;
|
|
@@ -45,14 +47,9 @@ export async function mountAgentGameOffice(
|
|
|
45
47
|
|
|
46
48
|
if (options.source.type === "runtime" && runtimeSource) {
|
|
47
49
|
try {
|
|
48
|
-
runtimeSubscription = await options.source.client
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
runtimeSource.updateAgents(runtimeAgents);
|
|
52
|
-
},
|
|
53
|
-
onPatch: (message) => {
|
|
54
|
-
runtimeAgents = mergeAgentPresence(runtimeAgents, message.agents);
|
|
55
|
-
runtimeSource.updateAgents(runtimeAgents);
|
|
50
|
+
runtimeSubscription = await subscribeAgentPresenceList(options.source.client, {
|
|
51
|
+
onAgentsChange: (agents) => {
|
|
52
|
+
runtimeSource.updateAgents(agents);
|
|
56
53
|
},
|
|
57
54
|
});
|
|
58
55
|
} catch (error) {
|
|
@@ -44,6 +44,7 @@ const WORKING_FOLLOW_RESET_DELAY_MS = 30_000;
|
|
|
44
44
|
const WORK_EXIT_WAIT_MS = 30_000;
|
|
45
45
|
const WORKING_FOLLOW_CAMERA_OFFSET = new THREE.Vector3(9, 12, 12);
|
|
46
46
|
const OFFICE_CAMERA_TRANSITION_DURATION_MS = 600;
|
|
47
|
+
const OFFICE_CAMERA_UP = new THREE.Vector3(0, 1, 0);
|
|
47
48
|
|
|
48
49
|
export type WorkingAgentFollowState = {
|
|
49
50
|
selectedAgentId: string | null;
|
|
@@ -478,14 +479,14 @@ export function createFollowOfficeCameraView(agentPosition: THREE.Vector3): Offi
|
|
|
478
479
|
const target = new THREE.Vector3(agentPosition.x, 1, agentPosition.z);
|
|
479
480
|
const position = target.clone().add(WORKING_FOLLOW_CAMERA_OFFSET);
|
|
480
481
|
const rotation = new THREE.Euler();
|
|
481
|
-
const matrix = new THREE.Matrix4().lookAt(position, target,
|
|
482
|
+
const matrix = new THREE.Matrix4().lookAt(position, target, OFFICE_CAMERA_UP);
|
|
482
483
|
rotation.setFromRotationMatrix(matrix);
|
|
483
484
|
|
|
484
485
|
return {
|
|
485
486
|
position,
|
|
486
487
|
rotation,
|
|
487
488
|
target,
|
|
488
|
-
up:
|
|
489
|
+
up: OFFICE_CAMERA_UP.clone(),
|
|
489
490
|
distance: position.distanceTo(target),
|
|
490
491
|
};
|
|
491
492
|
}
|
|
@@ -502,16 +503,20 @@ export function createInitialOfficeCameraView(
|
|
|
502
503
|
);
|
|
503
504
|
const quaternion = new THREE.Quaternion().setFromEuler(rotation);
|
|
504
505
|
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(quaternion).normalize();
|
|
505
|
-
const up = new THREE.Vector3(0, 1, 0).applyQuaternion(quaternion).normalize();
|
|
506
506
|
const target = new THREE.Vector3(layout.scene.center.x, OFFICE_CAMERA_BOUNDS_HEIGHT / 2, layout.scene.center.z);
|
|
507
|
-
const
|
|
507
|
+
const preliminaryPosition = target.clone().sub(forward.clone());
|
|
508
|
+
const orbitRotation = new THREE.Euler();
|
|
509
|
+
const matrix = new THREE.Matrix4().lookAt(preliminaryPosition, target, OFFICE_CAMERA_UP);
|
|
510
|
+
orbitRotation.setFromRotationMatrix(matrix);
|
|
511
|
+
const orbitQuaternion = new THREE.Quaternion().setFromEuler(orbitRotation);
|
|
512
|
+
const distance = resolveOfficeCameraDistance(layout, target, orbitQuaternion, aspect, fovDegrees);
|
|
508
513
|
const position = target.clone().sub(forward.multiplyScalar(distance));
|
|
509
514
|
|
|
510
515
|
return {
|
|
511
516
|
position,
|
|
512
|
-
rotation,
|
|
517
|
+
rotation: orbitRotation,
|
|
513
518
|
target,
|
|
514
|
-
up,
|
|
519
|
+
up: OFFICE_CAMERA_UP.clone(),
|
|
515
520
|
distance,
|
|
516
521
|
};
|
|
517
522
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mergeAgentPresence,
|
|
3
|
+
type AgentGameRuntimeBrowserSubscribeOptions,
|
|
4
|
+
type AgentPresence,
|
|
5
|
+
type AgentPresenceStatus,
|
|
6
|
+
type GameRuntimePatchMessage,
|
|
7
|
+
type GameRuntimeServerMessage,
|
|
8
|
+
type GameRuntimeSnapshotMessage,
|
|
9
|
+
type GameRuntimeSubscription,
|
|
10
|
+
} from "./runtime-client";
|
|
11
|
+
|
|
12
|
+
export const AGENT_PRESENCE_STATUS_LABELS = {
|
|
13
|
+
working: "Working",
|
|
14
|
+
thinking: "Thinking",
|
|
15
|
+
meeting: "Meeting",
|
|
16
|
+
resting: "Resting",
|
|
17
|
+
entertaining: "Entertainment",
|
|
18
|
+
idle: "Idle",
|
|
19
|
+
offline: "Offline",
|
|
20
|
+
} satisfies Record<AgentPresenceStatus, string>;
|
|
21
|
+
|
|
22
|
+
export type AgentPresenceListSubscription = GameRuntimeSubscription & {
|
|
23
|
+
getAgents(): AgentPresence[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type AgentPresenceListClient = {
|
|
27
|
+
subscribe(options?: AgentGameRuntimeBrowserSubscribeOptions): Promise<GameRuntimeSubscription>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type SubscribeAgentPresenceListOptions = AgentGameRuntimeBrowserSubscribeOptions & {
|
|
31
|
+
onAgentsChange?: (
|
|
32
|
+
agents: AgentPresence[],
|
|
33
|
+
message: GameRuntimeSnapshotMessage | GameRuntimePatchMessage,
|
|
34
|
+
) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function reduceAgentPresenceList(
|
|
38
|
+
current: AgentPresence[],
|
|
39
|
+
message: GameRuntimeServerMessage,
|
|
40
|
+
): AgentPresence[] {
|
|
41
|
+
if (message.type === "snapshot") {
|
|
42
|
+
return mergeAgentPresence([], message.agents);
|
|
43
|
+
}
|
|
44
|
+
return mergeAgentPresence(current, message.agents);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function formatAgentPresenceStatus(status: AgentPresenceStatus): string {
|
|
48
|
+
return AGENT_PRESENCE_STATUS_LABELS[status];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function subscribeAgentPresenceList(
|
|
52
|
+
client: AgentPresenceListClient,
|
|
53
|
+
options: SubscribeAgentPresenceListOptions = {},
|
|
54
|
+
): Promise<AgentPresenceListSubscription> {
|
|
55
|
+
let agents: AgentPresence[] = [];
|
|
56
|
+
const {
|
|
57
|
+
onAgentsChange,
|
|
58
|
+
onPatch,
|
|
59
|
+
onSnapshot,
|
|
60
|
+
...subscribeOptions
|
|
61
|
+
} = options;
|
|
62
|
+
|
|
63
|
+
function applyMessage(message: GameRuntimeSnapshotMessage | GameRuntimePatchMessage) {
|
|
64
|
+
agents = reduceAgentPresenceList(agents, message);
|
|
65
|
+
onAgentsChange?.([...agents], message);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const subscription = await client.subscribe({
|
|
69
|
+
...subscribeOptions,
|
|
70
|
+
onSnapshot: (message) => {
|
|
71
|
+
applyMessage(message);
|
|
72
|
+
onSnapshot?.(message);
|
|
73
|
+
},
|
|
74
|
+
onPatch: (message) => {
|
|
75
|
+
applyMessage(message);
|
|
76
|
+
onPatch?.(message);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...subscription,
|
|
82
|
+
getAgents() {
|
|
83
|
+
return [...agents];
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|