@roj-ai/sdk 0.1.27 → 0.1.28

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 (49) hide show
  1. package/dist/bootstrap.d.ts +2 -0
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/plugins/resources/index.d.ts +1 -1
  6. package/dist/plugins/resources/index.d.ts.map +1 -1
  7. package/dist/plugins/resources/plugin.d.ts +7 -1
  8. package/dist/plugins/resources/plugin.d.ts.map +1 -1
  9. package/dist/plugins/resources/plugin.js +25 -5
  10. package/dist/plugins/resources/plugin.js.map +1 -1
  11. package/dist/plugins/resources/resources.integration.test.d.ts +2 -0
  12. package/dist/plugins/resources/resources.integration.test.d.ts.map +1 -0
  13. package/dist/plugins/resources/resources.integration.test.js +62 -0
  14. package/dist/plugins/resources/resources.integration.test.js.map +1 -0
  15. package/dist/plugins/resources/state.d.ts +2 -0
  16. package/dist/plugins/resources/state.d.ts.map +1 -1
  17. package/dist/plugins/resources/state.js +1 -0
  18. package/dist/plugins/resources/state.js.map +1 -1
  19. package/dist/plugins/services/plugin.d.ts +4 -0
  20. package/dist/plugins/services/plugin.d.ts.map +1 -1
  21. package/dist/plugins/services/plugin.js +34 -9
  22. package/dist/plugins/services/plugin.js.map +1 -1
  23. package/dist/plugins/services/prompt.d.ts.map +1 -1
  24. package/dist/plugins/services/prompt.js +3 -0
  25. package/dist/plugins/services/prompt.js.map +1 -1
  26. package/dist/plugins/services/schema.d.ts +38 -4
  27. package/dist/plugins/services/schema.d.ts.map +1 -1
  28. package/dist/plugins/services/service.d.ts +11 -1
  29. package/dist/plugins/services/service.d.ts.map +1 -1
  30. package/dist/plugins/services/service.js +182 -46
  31. package/dist/plugins/services/service.js.map +1 -1
  32. package/dist/plugins/services/services.integration.test.js +128 -3
  33. package/dist/plugins/services/services.integration.test.js.map +1 -1
  34. package/dist/testing/preset-helpers.d.ts +1 -0
  35. package/dist/testing/preset-helpers.d.ts.map +1 -1
  36. package/dist/testing/preset-helpers.js +1 -0
  37. package/dist/testing/preset-helpers.js.map +1 -1
  38. package/package.json +2 -2
  39. package/src/index.ts +8 -2
  40. package/src/plugins/resources/index.ts +1 -1
  41. package/src/plugins/resources/plugin.ts +35 -6
  42. package/src/plugins/resources/resources.integration.test.ts +68 -0
  43. package/src/plugins/resources/state.ts +2 -0
  44. package/src/plugins/services/plugin.ts +32 -9
  45. package/src/plugins/services/prompt.ts +3 -0
  46. package/src/plugins/services/schema.ts +40 -4
  47. package/src/plugins/services/service.ts +202 -52
  48. package/src/plugins/services/services.integration.test.ts +143 -4
  49. package/src/testing/preset-helpers.ts +2 -0
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, it } from 'bun:test'
2
- import { readFile, rm } from 'node:fs/promises'
2
+ import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises'
3
3
  import { tmpdir } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
  import { MemoryEventStore } from '~/core/events/memory.js'
@@ -13,7 +13,7 @@ import { createTestPreset, TestHarness } from '~/testing/index.js'
13
13
  import { serviceEvents, servicePlugin } from './plugin.js'
14
14
  import type { ServiceAgentConfig, ServicePluginConfig } from './plugin.js'
15
15
  import { PortPool } from './port-pool.js'
16
- import type { ServiceConfig, ServiceEntry, ServiceStatus } from './schema.js'
16
+ import type { ServiceCommandArgs, ServiceConfig, ServiceCwdArgs, ServiceEntry, ServiceStatus } from './schema.js'
17
17
  import { ServiceExecutor } from './service.js'
18
18
 
19
19
  // ============================================================================
@@ -278,6 +278,84 @@ describe('services plugin', () => {
278
278
  const serviceState = selectPluginState<Map<string, ServiceEntry>>(session.state, 'services')?.get('callback-service')
279
279
  expect(serviceState!.port).toBeGreaterThanOrEqual(10000)
280
280
  })
281
+
282
+ it('resolves cwd, command, and env callbacks with runtime context', async () => {
283
+ const workspaceDir = await mkdtemp(join(tmpdir(), 'roj-svc-runtime-'))
284
+ const webDir = join(workspaceDir, 'packages', 'web')
285
+ await mkdir(webDir, { recursive: true })
286
+
287
+ let cwdArgs: ServiceCwdArgs | undefined
288
+ let commandArgs: ServiceCommandArgs | undefined
289
+ let envArgs: ServiceCommandArgs | undefined
290
+
291
+ const runtimeService: ServiceConfig = {
292
+ type: 'runtime',
293
+ description: 'Service with runtime resolvers',
294
+ cwd: (args) => {
295
+ cwdArgs = args
296
+ return 'packages/web'
297
+ },
298
+ command: async (args) => {
299
+ commandArgs = args
300
+ return `printf "%s" "$RUNTIME_MARKER" > marker.txt && echo "READY ${args.port}" && sleep 60`
301
+ },
302
+ env: (args) => {
303
+ envArgs = args
304
+ return {
305
+ RUNTIME_MARKER: `${args.sessionId}|${args.workspaceDir}|${args.cwd}|${args.port}`,
306
+ }
307
+ },
308
+ readyPattern: 'READY',
309
+ }
310
+
311
+ try {
312
+ const portPool = new PortPool()
313
+ const harness = createServicesHarness({
314
+ presets: [createServicesPreset([runtimeService], ['runtime'], portPool, { workspaceDir })],
315
+ llmProvider: MockLLMProvider.withFixedResponse({ content: 'Ok', toolCalls: [] }),
316
+ })
317
+
318
+ const session = await harness.createSession('test')
319
+ const entryAgentId = session.getEntryAgentId()!
320
+ const startResult = await session.callPluginMethod('services.start', {
321
+ sessionId: String(session.sessionId),
322
+ agentId: String(entryAgentId),
323
+ serviceType: 'runtime',
324
+ })
325
+ expect(startResult.ok).toBe(true)
326
+ await waitForServiceStatus(session, 'runtime', 'ready')
327
+
328
+ const serviceState = selectPluginState<Map<string, ServiceEntry>>(session.state, 'services')?.get('runtime')
329
+ expect(cwdArgs?.workspaceDir).toBe(workspaceDir)
330
+ expect(cwdArgs?.sessionId).toBe(String(session.sessionId))
331
+ expect(commandArgs?.cwd).toBe(webDir)
332
+ expect(commandArgs?.workspaceDir).toBe(workspaceDir)
333
+ expect(envArgs?.cwd).toBe(webDir)
334
+ expect(serviceState?.cwd).toBe(webDir)
335
+ expect(serviceState?.command).toContain('RUNTIME_MARKER')
336
+
337
+ const marker = await readFile(join(webDir, 'marker.txt'), 'utf-8')
338
+ expect(marker).toContain(String(session.sessionId))
339
+ expect(marker).toContain(workspaceDir)
340
+ expect(marker).toContain(webDir)
341
+
342
+ const statusResult = await session.callPluginMethod('services.status', {
343
+ sessionId: String(session.sessionId),
344
+ agentId: String(entryAgentId),
345
+ serviceType: 'runtime',
346
+ })
347
+ expect(statusResult).toMatchObject({
348
+ ok: true,
349
+ value: {
350
+ status: 'ready',
351
+ cwd: webDir,
352
+ command: serviceState?.command,
353
+ },
354
+ })
355
+ } finally {
356
+ await rm(workspaceDir, { recursive: true, force: true })
357
+ }
358
+ })
281
359
  })
282
360
 
283
361
  // =========================================================================
@@ -379,6 +457,35 @@ describe('services plugin', () => {
379
457
  const autoEvents = events.filter((e) => e.serviceType === 'auto-start')
380
458
  expect(autoEvents.length).toBeGreaterThanOrEqual(1)
381
459
  })
460
+
461
+ it('availableWhen false skips auto-start and explicit start returns an error', async () => {
462
+ const unavailableService: ServiceConfig = {
463
+ type: 'unavailable',
464
+ description: 'Unavailable service',
465
+ command: 'sleep 60',
466
+ autoStart: true,
467
+ availableWhen: () => false,
468
+ }
469
+ const portPool = new PortPool()
470
+ const harness = createServicesHarness({
471
+ presets: [createServicesPreset([unavailableService], ['unavailable'], portPool)],
472
+ llmProvider: MockLLMProvider.withFixedResponse({ content: 'Ok', toolCalls: [] }),
473
+ })
474
+
475
+ const session = await harness.createSession('test')
476
+ await new Promise((resolve) => setTimeout(resolve, 100))
477
+
478
+ const events = await session.getEventsByType(serviceEvents, 'service_status_changed')
479
+ expect(events.some((event) => event.serviceType === 'unavailable')).toBe(false)
480
+
481
+ const entryAgentId = session.getEntryAgentId()!
482
+ const result = await session.callPluginMethod('services.start', {
483
+ sessionId: String(session.sessionId),
484
+ agentId: String(entryAgentId),
485
+ serviceType: 'unavailable',
486
+ })
487
+ expect(result.ok).toBe(false)
488
+ })
382
489
  })
383
490
 
384
491
  // =========================================================================
@@ -439,6 +546,38 @@ describe('services plugin', () => {
439
546
  const serviceState = selectPluginState<Map<string, ServiceEntry>>(session.state, 'services')?.get('ready-service')
440
547
  expect(serviceState!.status).toBe('ready')
441
548
  })
549
+
550
+ it('readyWhen can mark a service ready without output pattern', async () => {
551
+ let checks = 0
552
+ const readyWhenService: ServiceConfig = {
553
+ type: 'ready-when',
554
+ description: 'Service with callback readiness',
555
+ command: 'sleep 60',
556
+ readyWhen: () => {
557
+ checks += 1
558
+ return checks > 1
559
+ },
560
+ readyCheckIntervalMs: 20,
561
+ startupTimeoutMs: 1000,
562
+ }
563
+ const portPool = new PortPool()
564
+ const harness = createServicesHarness({
565
+ presets: [createServicesPreset([readyWhenService], ['ready-when'], portPool)],
566
+ llmProvider: MockLLMProvider.withFixedResponse({ content: 'Ok', toolCalls: [] }),
567
+ })
568
+
569
+ const session = await harness.createSession('test')
570
+ const entryAgentId = session.getEntryAgentId()!
571
+ const result = await session.callPluginMethod('services.start', {
572
+ sessionId: String(session.sessionId),
573
+ agentId: String(entryAgentId),
574
+ serviceType: 'ready-when',
575
+ })
576
+ expect(result.ok).toBe(true)
577
+
578
+ await waitForServiceStatus(session, 'ready-when', 'ready')
579
+ expect(checks).toBeGreaterThan(1)
580
+ })
442
581
  })
443
582
 
444
583
  // =========================================================================
@@ -658,8 +797,8 @@ describe('services plugin', () => {
658
797
  }
659
798
 
660
799
  const observed: Array<{ status: ServiceStatus; port?: number }> = []
661
- executor.onStatusChanged = (_sessionId, _serviceType, status, port) => {
662
- observed.push({ status, port })
800
+ executor.onStatusChanged = (_sessionId, _serviceType, status, details) => {
801
+ observed.push({ status, port: details.port })
663
802
  }
664
803
 
665
804
  try {
@@ -12,6 +12,7 @@ export type TestAgentDefinition<TInput = unknown> = Omit<AgentDefinition<TInput>
12
12
  */
13
13
  export function createTestPreset(overrides?: {
14
14
  id?: string
15
+ workspaceDir?: string
15
16
  orchestratorSystem?: string
16
17
  agents?: TestAgentDefinition[]
17
18
  plugins?: SessionPluginConfig[]
@@ -22,6 +23,7 @@ export function createTestPreset(overrides?: {
22
23
  return {
23
24
  id: overrides?.id ?? 'test',
24
25
  name: 'Test Preset',
26
+ workspaceDir: overrides?.workspaceDir,
25
27
  orchestrator: {
26
28
  system: overrides?.orchestratorSystem ?? 'You are a test agent.',
27
29
  model: ModelId('mock'),