@agent-os-lab/agent-game-sdk 0.1.8 → 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 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 `subscribe` when the application wants direct access to live runtime messages instead of using the office view.
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 local presence array from snapshots and patches.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-os-lab/agent-game-sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./runtime-client";
2
+ export * from "./runtime-agent-list";
2
3
  export * from "./office";
@@ -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,7 +18,7 @@ export function mapPresenceToOfficeAgents(
27
18
  id: agent.agentId,
28
19
  name: agent.displayName,
29
20
  role: agent.scene ?? null,
30
- statusLabel: statusLabels[agent.status],
21
+ statusLabel: formatAgentPresenceStatus(agent.status),
31
22
  activity: agent.activity,
32
23
  activityLabel: sanitizeActivityLabel(agent.activity, agent.status),
33
24
  sceneState: mapStatusToSceneState(agent.status),
@@ -2,7 +2,10 @@ import {
2
2
  createSnapshotOfficeSource,
3
3
  resolveOfficeSource,
4
4
  } from "./core/source";
5
- import { mergeAgentPresence, type GameRuntimeSubscription } from "../runtime-client";
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: GameRuntimeSubscription | null = null;
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.subscribe({
49
- onSnapshot: (message) => {
50
- runtimeAgents = message.agents;
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) {
@@ -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
+ }