@roj-ai/sdk 0.1.32 → 0.1.34

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.
Files changed (53) hide show
  1. package/dist/bootstrap.d.ts +3 -0
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +5 -0
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/core/sessions/session-manager.d.ts +3 -0
  6. package/dist/core/sessions/session-manager.d.ts.map +1 -1
  7. package/dist/core/sessions/session-manager.js +3 -1
  8. package/dist/core/sessions/session-manager.js.map +1 -1
  9. package/dist/core/system.d.ts +2 -0
  10. package/dist/core/system.d.ts.map +1 -1
  11. package/dist/core/system.js +2 -1
  12. package/dist/core/system.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/plugins/services/pid-registry.d.ts +72 -0
  16. package/dist/plugins/services/pid-registry.d.ts.map +1 -0
  17. package/dist/plugins/services/pid-registry.js +171 -0
  18. package/dist/plugins/services/pid-registry.js.map +1 -0
  19. package/dist/plugins/services/pid-registry.test.d.ts +2 -0
  20. package/dist/plugins/services/pid-registry.test.d.ts.map +1 -0
  21. package/dist/plugins/services/pid-registry.test.js +161 -0
  22. package/dist/plugins/services/pid-registry.test.js.map +1 -0
  23. package/dist/plugins/services/plugin.d.ts +3 -0
  24. package/dist/plugins/services/plugin.d.ts.map +1 -1
  25. package/dist/plugins/services/plugin.js +5 -1
  26. package/dist/plugins/services/plugin.js.map +1 -1
  27. package/dist/plugins/services/service.d.ts +4 -0
  28. package/dist/plugins/services/service.d.ts.map +1 -1
  29. package/dist/plugins/services/service.js +8 -0
  30. package/dist/plugins/services/service.js.map +1 -1
  31. package/dist/plugins/session-stats/index.d.ts +1 -1
  32. package/dist/plugins/session-stats/index.d.ts.map +1 -1
  33. package/dist/plugins/session-stats/plugin.d.ts +16 -0
  34. package/dist/plugins/session-stats/plugin.d.ts.map +1 -1
  35. package/dist/plugins/session-stats/plugin.js +29 -2
  36. package/dist/plugins/session-stats/plugin.js.map +1 -1
  37. package/dist/plugins/session-stats/plugin.test.d.ts +2 -0
  38. package/dist/plugins/session-stats/plugin.test.d.ts.map +1 -0
  39. package/dist/plugins/session-stats/plugin.test.js +145 -0
  40. package/dist/plugins/session-stats/plugin.test.js.map +1 -0
  41. package/dist/transport/http/middleware/error-handler.d.ts +1 -1
  42. package/package.json +2 -2
  43. package/src/bootstrap.ts +7 -0
  44. package/src/core/sessions/session-manager.ts +5 -1
  45. package/src/core/system.ts +4 -0
  46. package/src/index.ts +1 -1
  47. package/src/plugins/services/pid-registry.test.ts +180 -0
  48. package/src/plugins/services/pid-registry.ts +191 -0
  49. package/src/plugins/services/plugin.ts +8 -1
  50. package/src/plugins/services/service.ts +12 -0
  51. package/src/plugins/session-stats/index.ts +1 -1
  52. package/src/plugins/session-stats/plugin.test.ts +160 -0
  53. package/src/plugins/session-stats/plugin.ts +48 -2
@@ -1,21 +1,28 @@
1
1
  import { agentEvents } from '../../core/agents/state.js';
2
+ import { contextEvents } from '../../core/context/state.js';
2
3
  import { llmEvents } from '../../core/llm/state.js';
3
4
  import { definePlugin } from '../../core/plugins/plugin-builder.js';
4
5
  import { selectPluginState } from '../../core/sessions/reducer.js';
5
6
  import { toolEvents } from '../../core/tools/state.js';
7
+ import { userChatEvents } from '../../plugins/user-chat/plugin.js';
6
8
  const createInitialStats = () => ({
7
9
  totalTokens: 0,
8
10
  promptTokens: 0,
9
11
  completionTokens: 0,
12
+ cacheReadTokens: 0,
13
+ cacheWriteTokens: 0,
10
14
  totalCost: 0,
11
15
  llmCalls: 0,
12
16
  llmErrors: 0,
13
17
  toolCalls: 0,
14
18
  toolErrors: 0,
19
+ userMessages: 0,
20
+ compactions: 0,
15
21
  agentCount: 0,
16
22
  firstEventAt: null,
17
23
  lastEventAt: null,
18
24
  byProvider: {},
25
+ byAgent: {},
19
26
  });
20
27
  const PLUGIN_STATE_KEY = 'sessionStats';
21
28
  /**
@@ -28,11 +35,11 @@ export function selectSessionStats(sessionState) {
28
35
  // Plugin
29
36
  // ============================================================================
30
37
  export const sessionStatsPlugin = definePlugin('session-stats')
31
- .events([agentEvents, llmEvents, toolEvents])
38
+ .events([agentEvents, llmEvents, toolEvents, contextEvents, userChatEvents])
32
39
  .state({
33
40
  key: PLUGIN_STATE_KEY,
34
41
  initial: createInitialStats,
35
- reduce: (stats, event) => {
42
+ reduce: (stats, event, sessionState) => {
36
43
  const now = event.timestamp;
37
44
  const withTimestamp = (partial) => ({
38
45
  ...stats,
@@ -61,13 +68,29 @@ export const sessionStatsPlugin = definePlugin('session-stats')
61
68
  },
62
69
  }
63
70
  : stats.byProvider;
71
+ // Core reducer runs before plugin slices, so the spawning agent is already in state.
72
+ const definitionName = sessionState.agents.get(event.agentId)?.definitionName;
73
+ const byAgent = definitionName
74
+ ? {
75
+ ...stats.byAgent,
76
+ [definitionName]: {
77
+ llmCalls: (stats.byAgent[definitionName]?.llmCalls ?? 0) + 1,
78
+ promptTokens: (stats.byAgent[definitionName]?.promptTokens ?? 0) + event.metrics.promptTokens,
79
+ completionTokens: (stats.byAgent[definitionName]?.completionTokens ?? 0) + event.metrics.completionTokens,
80
+ totalCost: (stats.byAgent[definitionName]?.totalCost ?? 0) + (event.metrics.cost ?? 0),
81
+ },
82
+ }
83
+ : stats.byAgent;
64
84
  return withTimestamp({
65
85
  llmCalls: stats.llmCalls + 1,
66
86
  totalTokens: stats.totalTokens + event.metrics.totalTokens,
67
87
  promptTokens: stats.promptTokens + event.metrics.promptTokens,
68
88
  completionTokens: stats.completionTokens + event.metrics.completionTokens,
89
+ cacheReadTokens: stats.cacheReadTokens + (event.metrics.cachedTokens ?? 0),
90
+ cacheWriteTokens: stats.cacheWriteTokens + (event.metrics.cacheWriteTokens ?? 0),
69
91
  totalCost: stats.totalCost + (event.metrics.cost ?? 0),
70
92
  byProvider,
93
+ byAgent,
71
94
  });
72
95
  }
73
96
  case 'inference_failed':
@@ -76,6 +99,10 @@ export const sessionStatsPlugin = definePlugin('session-stats')
76
99
  return withTimestamp({ toolCalls: stats.toolCalls + 1 });
77
100
  case 'tool_failed':
78
101
  return withTimestamp({ toolErrors: stats.toolErrors + 1 });
102
+ case 'user_chat_message_received':
103
+ return withTimestamp({ userMessages: stats.userMessages + 1 });
104
+ case 'context_compacted':
105
+ return withTimestamp({ compactions: stats.compactions + 1 });
79
106
  default:
80
107
  return stats;
81
108
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/plugins/session-stats/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../src/plugins/session-stats/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plugin.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.test.d.ts","sourceRoot":"","sources":["../../../src/plugins/session-stats/plugin.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,145 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { AgentId } from '../../core/agents/schema.js';
3
+ import { agentEvents } from '../../core/agents/state.js';
4
+ import { contextEvents } from '../../core/context/state.js';
5
+ import { withSessionId } from '../../core/events/test-helpers.js';
6
+ import { llmEvents } from '../../core/llm/state.js';
7
+ import { createApplyEvent } from '../../core/sessions/apply-event.js';
8
+ import { SessionId } from '../../core/sessions/schema.js';
9
+ import { createSessionState } from '../../core/sessions/state.js';
10
+ import { ToolCallId } from '../../core/tools/schema.js';
11
+ import { toolEvents } from '../../core/tools/state.js';
12
+ import { userChatEvents } from '../../plugins/user-chat/plugin.js';
13
+ import { ChatMessageId } from '../../plugins/user-chat/schema.js';
14
+ import { selectSessionStats, sessionStatsPlugin } from './plugin.js';
15
+ const sessionId = SessionId('session-stats-test');
16
+ const orchestrator = AgentId('orchestrator-1');
17
+ const worker = AgentId('worker-1');
18
+ const applyEvent = createApplyEvent([sessionStatsPlugin.create()]);
19
+ const metrics = (overrides = {}) => ({
20
+ promptTokens: 100,
21
+ completionTokens: 20,
22
+ totalTokens: 120,
23
+ latencyMs: 10,
24
+ model: 'test-model',
25
+ provider: 'anthropic',
26
+ cost: 0.5,
27
+ ...overrides,
28
+ });
29
+ const spawn = (agentId, definitionName) => withSessionId(sessionId, agentEvents.create('agent_spawned', { agentId, definitionName, parentId: null }));
30
+ const inference = (agentId, m) => withSessionId(sessionId, llmEvents.create('inference_completed', {
31
+ agentId,
32
+ consumedMessageIds: [],
33
+ response: { content: 'ok', toolCalls: [] },
34
+ metrics: m,
35
+ }));
36
+ const reduce = (events) => selectSessionStats(events.reduce(applyEvent, createSessionState(sessionId, 'test-preset', 0)));
37
+ describe('session-stats plugin', () => {
38
+ it('starts from zeroed counters', () => {
39
+ const stats = reduce([]);
40
+ expect(stats.userMessages).toBe(0);
41
+ expect(stats.compactions).toBe(0);
42
+ expect(stats.cacheReadTokens).toBe(0);
43
+ expect(stats.cacheWriteTokens).toBe(0);
44
+ expect(stats.byAgent).toEqual({});
45
+ });
46
+ it('counts user chat messages', () => {
47
+ const stats = reduce([
48
+ spawn(orchestrator, 'orchestrator'),
49
+ withSessionId(sessionId, userChatEvents.create('user_chat_message_received', {
50
+ agentId: orchestrator,
51
+ messageId: ChatMessageId('m1'),
52
+ content: 'hello',
53
+ timestamp: 1,
54
+ })),
55
+ withSessionId(sessionId, userChatEvents.create('user_chat_message_received', {
56
+ agentId: orchestrator,
57
+ messageId: ChatMessageId('m2'),
58
+ content: 'again',
59
+ timestamp: 2,
60
+ })),
61
+ ]);
62
+ expect(stats.userMessages).toBe(2);
63
+ });
64
+ it('counts context compactions', () => {
65
+ const stats = reduce([
66
+ spawn(orchestrator, 'orchestrator'),
67
+ withSessionId(sessionId, contextEvents.create('context_compacted', {
68
+ agentId: orchestrator,
69
+ compactedContent: 'summary',
70
+ newConversationHistory: [],
71
+ originalTokens: 1000,
72
+ compactedTokens: 100,
73
+ messagesRemoved: 8,
74
+ })),
75
+ ]);
76
+ expect(stats.compactions).toBe(1);
77
+ });
78
+ it('sums cache read and write tokens across main and auxiliary inferences', () => {
79
+ const stats = reduce([
80
+ spawn(orchestrator, 'orchestrator'),
81
+ inference(orchestrator, metrics({ cachedTokens: 800, cacheWriteTokens: 200 })),
82
+ withSessionId(sessionId, llmEvents.create('auxiliary_inference_completed', {
83
+ agentId: orchestrator,
84
+ metrics: metrics({ cachedTokens: 50, cacheWriteTokens: 10 }),
85
+ })),
86
+ // Providers that report no cache usage must not break the totals.
87
+ inference(orchestrator, metrics()),
88
+ ]);
89
+ expect(stats.cacheReadTokens).toBe(850);
90
+ expect(stats.cacheWriteTokens).toBe(210);
91
+ expect(stats.llmCalls).toBe(3);
92
+ });
93
+ it('breaks LLM usage down by agent definition name', () => {
94
+ const stats = reduce([
95
+ spawn(orchestrator, 'orchestrator'),
96
+ spawn(worker, 'coder'),
97
+ inference(orchestrator, metrics({ promptTokens: 100, completionTokens: 20, cost: 0.5 })),
98
+ inference(worker, metrics({ promptTokens: 300, completionTokens: 40, cost: 1.25 })),
99
+ inference(worker, metrics({ promptTokens: 200, completionTokens: 10, cost: 0.25 })),
100
+ withSessionId(sessionId, llmEvents.create('auxiliary_inference_completed', {
101
+ agentId: worker,
102
+ metrics: metrics({ promptTokens: 10, completionTokens: 1, cost: 0.05 }),
103
+ })),
104
+ ]);
105
+ expect(stats.byAgent).toEqual({
106
+ orchestrator: { llmCalls: 1, promptTokens: 100, completionTokens: 20, totalCost: 0.5 },
107
+ coder: { llmCalls: 3, promptTokens: 510, completionTokens: 51, totalCost: 1.55 },
108
+ });
109
+ expect(stats.totalCost).toBeCloseTo(2.05, 10);
110
+ });
111
+ it('keeps existing counters working', () => {
112
+ const stats = reduce([
113
+ spawn(orchestrator, 'orchestrator'),
114
+ inference(orchestrator, metrics()),
115
+ withSessionId(sessionId, llmEvents.create('inference_failed', { agentId: orchestrator, error: 'boom' })),
116
+ withSessionId(sessionId, toolEvents.create('tool_started', {
117
+ agentId: orchestrator,
118
+ toolCallId: ToolCallId('tc-1'),
119
+ toolName: 'read_file',
120
+ input: {},
121
+ })),
122
+ withSessionId(sessionId, toolEvents.create('tool_failed', {
123
+ agentId: orchestrator,
124
+ toolCallId: ToolCallId('tc-1'),
125
+ error: 'ENOENT',
126
+ })),
127
+ ]);
128
+ expect(stats.agentCount).toBe(1);
129
+ expect(stats.llmCalls).toBe(1);
130
+ expect(stats.llmErrors).toBe(1);
131
+ expect(stats.toolCalls).toBe(1);
132
+ expect(stats.toolErrors).toBe(1);
133
+ expect(stats.totalTokens).toBe(120);
134
+ expect(stats.promptTokens).toBe(100);
135
+ expect(stats.completionTokens).toBe(20);
136
+ expect(stats.byProvider.anthropic).toEqual({
137
+ llmCalls: 1,
138
+ totalTokens: 120,
139
+ promptTokens: 100,
140
+ completionTokens: 20,
141
+ totalCost: 0.5,
142
+ });
143
+ });
144
+ });
145
+ //# sourceMappingURL=plugin.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.test.js","sourceRoot":"","sources":["../../../src/plugins/session-stats/plugin.test.ts"],"names":[],"mappings":"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"}
@@ -17,5 +17,5 @@ export declare const errorHandler: (error: Error, c: Context<AppEnv>) => Respons
17
17
  type: string;
18
18
  message: string;
19
19
  };
20
- }, 100 | 500 | 404 | 409 | 400 | 403 | -1 | 429 | 401 | 200 | 300 | 501 | 201 | 102 | 103 | 202 | 203 | 206 | 207 | 208 | 226 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 402 | 405 | 406 | 407 | 408 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 431 | 451 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511, "json">;
20
+ }, 100 | 500 | 404 | 409 | 400 | 403 | -1 | 200 | 429 | 401 | 300 | 501 | 201 | 510 | 102 | 103 | 202 | 203 | 206 | 207 | 208 | 226 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 402 | 405 | 406 | 407 | 408 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 431 | 451 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 511, "json">;
21
21
  //# sourceMappingURL=error-handler.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roj-ai/sdk",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -135,7 +135,7 @@
135
135
  "type-check": "tsc --noEmit"
136
136
  },
137
137
  "dependencies": {
138
- "@roj-ai/transport": "^0.1.32",
138
+ "@roj-ai/transport": "^0.1.34",
139
139
  "@hono/zod-validator": "0.7.6",
140
140
  "hono": "4.12.5",
141
141
  "ignore": "7.0.5",
package/src/bootstrap.ts CHANGED
@@ -37,6 +37,7 @@ import { llmDebugPlugin } from './plugins/llm-debug/plugin.js'
37
37
  import { logsPlugin } from './plugins/logs/index.js'
38
38
  import { mailboxPlugin } from './plugins/mailbox/plugin.js'
39
39
  import { servicePlugin } from './plugins/services/plugin.js'
40
+ import { ServicePidRegistry } from './plugins/services/pid-registry.js'
40
41
  import { PortPool } from './plugins/services/port-pool.js'
41
42
  import { presetsPlugin, sessionLifecyclePlugin } from './plugins/session-lifecycle/index.js'
42
43
  import { sessionStatsPlugin } from './plugins/session-stats/index.js'
@@ -89,6 +90,8 @@ export interface Services {
89
90
  config: Config
90
91
  /** Global port pool shared across all sessions */
91
92
  portPool: PortPool
93
+ /** Durable record of spawned service processes, swept once at boot. */
94
+ pidRegistry: ServicePidRegistry
92
95
  /** Preprocessor registry for upload content extraction */
93
96
  preprocessorRegistry: PreprocessorRegistry
94
97
  /** Host-environment adapters (filesystem, process). */
@@ -118,6 +121,8 @@ export function bootstrap(config: Config, userConfig: RojConfig, platform: Platf
118
121
  const toolExecutor = new ToolExecutorImpl(logger)
119
122
  const dataFileStore = new SessionFileStore(config.dataPath, undefined, false, platform.fs, 'session')
120
123
  const portPool = new PortPool()
124
+ // Swept by the server before it serves — see ServicePidRegistry.sweepOrphans.
125
+ const pidRegistry = new ServicePidRegistry(platform.fs, logger, config.dataPath)
121
126
 
122
127
  const preprocessorRegistry = new PreprocessorRegistry()
123
128
  const imageClassifierGate = new Semaphore(config.imageClassifierConcurrency ?? 10)
@@ -148,6 +153,7 @@ export function bootstrap(config: Config, userConfig: RojConfig, platform: Platf
148
153
  dataFileStore,
149
154
  config,
150
155
  portPool,
156
+ pidRegistry,
151
157
  preprocessorRegistry,
152
158
  platform,
153
159
  }
@@ -253,6 +259,7 @@ export function createSystemFromServices(
253
259
  preprocessorRegistry: services.preprocessorRegistry,
254
260
  llmLogger: services.llmLogger,
255
261
  portPool: services.portPool,
262
+ pidRegistry: services.pidRegistry,
256
263
  platform: services.platform,
257
264
  })
258
265
  }
@@ -34,6 +34,7 @@ import type { Result } from '~/lib/utils/result.js'
34
34
  import { Err, Ok } from '~/lib/utils/result.js'
35
35
  import type { SpawnableAgentInfo } from '~/plugins/agents/index.js'
36
36
  import { getAgentUnconsumedMailbox, selectMailboxState } from '~/plugins/mailbox/query.js'
37
+ import type { ServicePidRegistry } from '~/plugins/services/pid-registry.js'
37
38
  import type { PortPool } from '~/plugins/services/port-pool.js'
38
39
  import type { ServiceConfig } from '~/plugins/services/schema.js'
39
40
  import { selectSessionStats, type SessionStatsState } from '~/plugins/session-stats/index.js'
@@ -77,6 +78,7 @@ export interface SessionManagerOptions {
77
78
  preprocessorRegistry?: PreprocessorRegistry
78
79
  llmLogger?: LLMLogger
79
80
  portPool?: PortPool
81
+ pidRegistry?: ServicePidRegistry
80
82
  /** Host-environment adapters (filesystem, process). */
81
83
  platform: Platform
82
84
  systemPlugins?: readonly PluginDefinition<string, any, any, any, any>[]
@@ -108,6 +110,7 @@ export class SessionManager {
108
110
  private readonly preprocessorRegistry?: PreprocessorRegistry
109
111
  private readonly llmLogger?: LLMLogger
110
112
  private readonly portPool?: PortPool
113
+ private readonly pidRegistry?: ServicePidRegistry
111
114
  private readonly platform: Platform
112
115
  private readonly systemPlugins: readonly PluginDefinition<string, any, any, any, any>[]
113
116
 
@@ -124,6 +127,7 @@ export class SessionManager {
124
127
  this.preprocessorRegistry = options.preprocessorRegistry
125
128
  this.llmLogger = options.llmLogger
126
129
  this.portPool = options.portPool
130
+ this.pidRegistry = options.pidRegistry
127
131
  this.platform = options.platform
128
132
  this.systemPlugins = options.systemPlugins ?? []
129
133
  this.managerMethods = this.collectManagerMethods()
@@ -774,7 +778,7 @@ export class SessionManager {
774
778
  }
775
779
  }
776
780
  if (servicesByType.size > 0 && this.portPool) {
777
- configs.set('services', { services: [...servicesByType.values()], portPool: this.portPool })
781
+ configs.set('services', { services: [...servicesByType.values()], portPool: this.portPool, pidRegistry: this.pidRegistry })
778
782
  }
779
783
 
780
784
  return configs
@@ -10,6 +10,7 @@
10
10
  import type z4 from 'zod/v4'
11
11
  import type { Logger } from '~/lib/logger/logger.js'
12
12
  import type { Platform } from '~/platform/index.js'
13
+ import type { ServicePidRegistry } from '~/plugins/services/pid-registry.js'
13
14
  import type { PortPool } from '~/plugins/services/port-pool.js'
14
15
  import type { PreprocessorRegistry } from '~/plugins/uploads/preprocessor.js'
15
16
  import type { EventStore } from './events/event-store.js'
@@ -120,6 +121,7 @@ export interface CreateSystemOptions<TPlugins extends readonly PluginDefinition<
120
121
  preprocessorRegistry?: PreprocessorRegistry
121
122
  llmLogger?: LLMLogger
122
123
  portPool?: PortPool
124
+ pidRegistry?: ServicePidRegistry
123
125
  /** Host-environment adapters (filesystem, process). */
124
126
  platform: Platform
125
127
  }
@@ -145,6 +147,7 @@ export function createSystem<const TPlugins extends readonly PluginDefinition<st
145
147
  preprocessorRegistry,
146
148
  llmLogger,
147
149
  portPool,
150
+ pidRegistry,
148
151
  platform,
149
152
  } = options
150
153
 
@@ -167,6 +170,7 @@ export function createSystem<const TPlugins extends readonly PluginDefinition<st
167
170
  preprocessorRegistry,
168
171
  llmLogger,
169
172
  portPool,
173
+ pidRegistry,
170
174
  platform,
171
175
  systemPlugins: [...plugins],
172
176
  })
package/src/index.ts CHANGED
@@ -99,7 +99,7 @@ export type { EvictionAgentConfig, EvictionConfig } from '~/plugins/result-evict
99
99
  export { servicePlugin } from '~/plugins/services/plugin.js'
100
100
  export type { ServiceAgentConfig, ServiceStatusChangedEvent } from '~/plugins/services/plugin.js'
101
101
  export { selectSessionStats, sessionStatsPlugin } from '~/plugins/session-stats/plugin.js'
102
- export type { SessionStatsState } from '~/plugins/session-stats/plugin.js'
102
+ export type { AgentStats, ProviderStats, SessionStatsState } from '~/plugins/session-stats/plugin.js'
103
103
  export { sessionStatePlugin } from '~/plugins/session-state/plugin.js'
104
104
  export type { SessionStatePluginConfig } from '~/plugins/session-state/plugin.js'
105
105
  export { presetsPlugin, sessionLifecyclePlugin } from '~/plugins/session-lifecycle/plugin.js'
@@ -0,0 +1,180 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+ import { spawn } from 'node:child_process'
3
+ import { readdir, readFile, mkdtemp, rm, writeFile, mkdir } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import { join } from 'node:path'
6
+ import { silentLogger } from '~/lib/logger/logger.js'
7
+ import { createNodePlatform } from '~/testing/node-platform.js'
8
+ import { ServicePidRegistry } from './pid-registry.js'
9
+ import { getProcessStartTime } from './service.js'
10
+
11
+ const fs = createNodePlatform().fs
12
+
13
+ async function withDataDir<T>(fn: (dataPath: string) => Promise<T>): Promise<T> {
14
+ const dataPath = await mkdtemp(join(tmpdir(), 'pid-registry-'))
15
+ try {
16
+ return await fn(dataPath)
17
+ } finally {
18
+ await rm(dataPath, { recursive: true, force: true })
19
+ }
20
+ }
21
+
22
+ /** A detached `sleep`, so `kill(-pid)` has a real process group to take down. */
23
+ function spawnVictim(): { pid: number; exited: Promise<void> } {
24
+ const child = spawn('/bin/sh', ['-c', 'sleep 60'], { detached: true, stdio: 'ignore' })
25
+ child.unref()
26
+ if (!child.pid) throw new Error('failed to spawn victim')
27
+ const exited = new Promise<void>(resolve => child.on('close', () => resolve()))
28
+ return { pid: child.pid, exited }
29
+ }
30
+
31
+ function isAlive(pid: number): boolean {
32
+ try {
33
+ process.kill(pid, 0)
34
+ return true
35
+ } catch {
36
+ return false
37
+ }
38
+ }
39
+
40
+ /** A pid the kernel cannot have handed out, so it reads as a dead agent. */
41
+ async function unusablePid(): Promise<number> {
42
+ const max = Number((await readFile('/proc/sys/kernel/pid_max', 'utf-8')).trim())
43
+ return max + 1
44
+ }
45
+
46
+ async function writeRecord(dataPath: string, record: Record<string, unknown>): Promise<string> {
47
+ const dir = join(dataPath, 'service-pids')
48
+ await mkdir(dir, { recursive: true })
49
+ const path = join(dir, `${record.sessionId}__${record.serviceType}.json`)
50
+ await writeFile(path, JSON.stringify(record))
51
+ return path
52
+ }
53
+
54
+ const makeRegistry = (dataPath: string) => new ServicePidRegistry(fs, silentLogger, dataPath)
55
+
56
+ describe('ServicePidRegistry', () => {
57
+ it('kills a service left behind by an agent that is gone, and drops the record', async () => {
58
+ await withDataDir(async dataPath => {
59
+ const victim = spawnVictim()
60
+ await writeRecord(dataPath, {
61
+ sessionId: 'sess-1',
62
+ serviceType: 'astro-dev',
63
+ pid: victim.pid,
64
+ pidStartTime: await getProcessStartTime(fs, victim.pid),
65
+ command: 'sleep 60',
66
+ agentPid: await unusablePid(),
67
+ agentStartTime: 12345,
68
+ })
69
+
70
+ await makeRegistry(dataPath).sweepOrphans()
71
+
72
+ await victim.exited
73
+ expect(isAlive(victim.pid)).toBe(false)
74
+ expect(await readdir(join(dataPath, 'service-pids'))).toEqual([])
75
+ })
76
+ })
77
+
78
+ it('leaves a service whose agent is still running', async () => {
79
+ await withDataDir(async dataPath => {
80
+ const victim = spawnVictim()
81
+ try {
82
+ // This test process stands in for a second, still-live agent.
83
+ await writeRecord(dataPath, {
84
+ sessionId: 'sess-1',
85
+ serviceType: 'astro-dev',
86
+ pid: victim.pid,
87
+ pidStartTime: await getProcessStartTime(fs, victim.pid),
88
+ command: 'sleep 60',
89
+ agentPid: process.pid,
90
+ agentStartTime: await getProcessStartTime(fs, process.pid),
91
+ })
92
+
93
+ await makeRegistry(dataPath).sweepOrphans()
94
+
95
+ expect(isAlive(victim.pid)).toBe(true)
96
+ expect(await readdir(join(dataPath, 'service-pids'))).toHaveLength(1)
97
+ } finally {
98
+ process.kill(-victim.pid, 'SIGKILL')
99
+ }
100
+ })
101
+ })
102
+
103
+ it('refuses to kill when the pid has been recycled', async () => {
104
+ await withDataDir(async dataPath => {
105
+ const victim = spawnVictim()
106
+ try {
107
+ const actual = await getProcessStartTime(fs, victim.pid)
108
+ await writeRecord(dataPath, {
109
+ sessionId: 'sess-1',
110
+ serviceType: 'astro-dev',
111
+ pid: victim.pid,
112
+ // Deliberately not the process now holding this pid.
113
+ pidStartTime: (actual ?? 0) + 999,
114
+ command: 'sleep 60',
115
+ agentPid: await unusablePid(),
116
+ })
117
+
118
+ await makeRegistry(dataPath).sweepOrphans()
119
+
120
+ expect(isAlive(victim.pid)).toBe(true)
121
+ // The record still goes — whatever holds that pid now, it is not ours to track.
122
+ expect(await readdir(join(dataPath, 'service-pids'))).toEqual([])
123
+ } finally {
124
+ process.kill(-victim.pid, 'SIGKILL')
125
+ }
126
+ })
127
+ })
128
+
129
+ it('never sweeps what this very process started', async () => {
130
+ await withDataDir(async dataPath => {
131
+ const registry = makeRegistry(dataPath)
132
+ const victim = spawnVictim()
133
+ try {
134
+ await registry.record({
135
+ sessionId: 'sess-1',
136
+ serviceType: 'astro-dev',
137
+ pid: victim.pid,
138
+ pidStartTime: await getProcessStartTime(fs, victim.pid),
139
+ command: 'sleep 60',
140
+ })
141
+
142
+ await registry.sweepOrphans()
143
+
144
+ expect(isAlive(victim.pid)).toBe(true)
145
+ } finally {
146
+ process.kill(-victim.pid, 'SIGKILL')
147
+ }
148
+ })
149
+ })
150
+
151
+ it('drops an unreadable record instead of throwing', async () => {
152
+ await withDataDir(async dataPath => {
153
+ const dir = join(dataPath, 'service-pids')
154
+ await mkdir(dir, { recursive: true })
155
+ await writeFile(join(dir, 'sess-1__astro-dev.json'), 'not json')
156
+
157
+ await makeRegistry(dataPath).sweepOrphans()
158
+
159
+ expect(await readdir(dir)).toEqual([])
160
+ })
161
+ })
162
+
163
+ it('does nothing when no service has ever been recorded', async () => {
164
+ await withDataDir(async dataPath => {
165
+ await makeRegistry(dataPath).sweepOrphans()
166
+ })
167
+ })
168
+
169
+ it('forget removes the record for a service that has exited', async () => {
170
+ await withDataDir(async dataPath => {
171
+ const registry = makeRegistry(dataPath)
172
+ await registry.record({ sessionId: 'sess-1', serviceType: 'astro-dev', pid: 1, pidStartTime: 1, command: 'x' })
173
+ expect(await readdir(join(dataPath, 'service-pids'))).toHaveLength(1)
174
+
175
+ await registry.forget('sess-1', 'astro-dev')
176
+
177
+ expect(await readdir(join(dataPath, 'service-pids'))).toEqual([])
178
+ })
179
+ })
180
+ })