@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.
- package/dist/bootstrap.d.ts +2 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/plugins/resources/index.d.ts +1 -1
- package/dist/plugins/resources/index.d.ts.map +1 -1
- package/dist/plugins/resources/plugin.d.ts +7 -1
- package/dist/plugins/resources/plugin.d.ts.map +1 -1
- package/dist/plugins/resources/plugin.js +25 -5
- package/dist/plugins/resources/plugin.js.map +1 -1
- package/dist/plugins/resources/resources.integration.test.d.ts +2 -0
- package/dist/plugins/resources/resources.integration.test.d.ts.map +1 -0
- package/dist/plugins/resources/resources.integration.test.js +62 -0
- package/dist/plugins/resources/resources.integration.test.js.map +1 -0
- package/dist/plugins/resources/state.d.ts +2 -0
- package/dist/plugins/resources/state.d.ts.map +1 -1
- package/dist/plugins/resources/state.js +1 -0
- package/dist/plugins/resources/state.js.map +1 -1
- package/dist/plugins/services/plugin.d.ts +4 -0
- package/dist/plugins/services/plugin.d.ts.map +1 -1
- package/dist/plugins/services/plugin.js +34 -9
- package/dist/plugins/services/plugin.js.map +1 -1
- package/dist/plugins/services/prompt.d.ts.map +1 -1
- package/dist/plugins/services/prompt.js +3 -0
- package/dist/plugins/services/prompt.js.map +1 -1
- package/dist/plugins/services/schema.d.ts +38 -4
- package/dist/plugins/services/schema.d.ts.map +1 -1
- package/dist/plugins/services/service.d.ts +11 -1
- package/dist/plugins/services/service.d.ts.map +1 -1
- package/dist/plugins/services/service.js +182 -46
- package/dist/plugins/services/service.js.map +1 -1
- package/dist/plugins/services/services.integration.test.js +128 -3
- package/dist/plugins/services/services.integration.test.js.map +1 -1
- package/dist/testing/preset-helpers.d.ts +1 -0
- package/dist/testing/preset-helpers.d.ts.map +1 -1
- package/dist/testing/preset-helpers.js +1 -0
- package/dist/testing/preset-helpers.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +8 -2
- package/src/plugins/resources/index.ts +1 -1
- package/src/plugins/resources/plugin.ts +35 -6
- package/src/plugins/resources/resources.integration.test.ts +68 -0
- package/src/plugins/resources/state.ts +2 -0
- package/src/plugins/services/plugin.ts +32 -9
- package/src/plugins/services/prompt.ts +3 -0
- package/src/plugins/services/schema.ts +40 -4
- package/src/plugins/services/service.ts +202 -52
- package/src/plugins/services/services.integration.test.ts +143 -4
- 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,
|
|
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'),
|