@roj-ai/sdk 0.1.26 → 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 +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.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/dist/testing/test-harness.d.ts +9 -2
- package/dist/testing/test-harness.d.ts.map +1 -1
- package/dist/testing/test-harness.js +31 -3
- package/dist/testing/test-harness.js.map +1 -1
- package/dist/testing/test-harness.test.js +41 -0
- package/dist/testing/test-harness.test.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +10 -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
- package/src/testing/test-harness.test.ts +51 -0
- package/src/testing/test-harness.ts +37 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join, relative } from 'node:path'
|
|
1
|
+
import { join, relative, resolve } from 'node:path'
|
|
2
2
|
import z from 'zod/v4'
|
|
3
3
|
import { definePlugin } from '~/core/plugins/plugin-builder.js'
|
|
4
4
|
import { Ok } from '~/lib/utils/result.js'
|
|
@@ -15,8 +15,16 @@ import { type InjectedResource, resourceEvents, type ResourcesState } from './st
|
|
|
15
15
|
|
|
16
16
|
const MAX_LISTED_PATHS = 100
|
|
17
17
|
|
|
18
|
+
export interface ResourcesTargetDirArgs {
|
|
19
|
+
sessionId: string
|
|
20
|
+
sessionDir: string
|
|
21
|
+
workspaceDir?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type ResourcesTargetDir = string | ((args: ResourcesTargetDirArgs) => string | Promise<string>)
|
|
25
|
+
|
|
18
26
|
export interface ResourcesPluginConfig {
|
|
19
|
-
targetDir?:
|
|
27
|
+
targetDir?: ResourcesTargetDir
|
|
20
28
|
/**
|
|
21
29
|
* Called after a resource is written/extracted into `targetDir`, before the
|
|
22
30
|
* `resource_injected` event is emitted. Use `postInjectRules` for a declarative
|
|
@@ -61,6 +69,16 @@ async function listFiles(fs: FileSystem, dir: string, maxEntries: number): Promi
|
|
|
61
69
|
return results
|
|
62
70
|
}
|
|
63
71
|
|
|
72
|
+
async function resolveTargetDir(targetDir: ResourcesTargetDir | undefined, args: ResourcesTargetDirArgs): Promise<string> {
|
|
73
|
+
const baseDir = args.workspaceDir ?? args.sessionDir
|
|
74
|
+
if (targetDir === undefined) return baseDir
|
|
75
|
+
|
|
76
|
+
const raw = typeof targetDir === 'function'
|
|
77
|
+
? await targetDir(args)
|
|
78
|
+
: targetDir
|
|
79
|
+
return resolve(baseDir, raw)
|
|
80
|
+
}
|
|
81
|
+
|
|
64
82
|
export const resourcesPlugin = definePlugin('resources')
|
|
65
83
|
.pluginConfig<ResourcesPluginConfig>()
|
|
66
84
|
.events([resourceEvents])
|
|
@@ -76,6 +94,7 @@ export const resourcesPlugin = definePlugin('resources')
|
|
|
76
94
|
filename: event.filename,
|
|
77
95
|
mimeType: event.mimeType,
|
|
78
96
|
paths: event.paths,
|
|
97
|
+
targetDir: event.targetDir,
|
|
79
98
|
injectedAt: event.injectedAt,
|
|
80
99
|
}
|
|
81
100
|
return { resources: [...state.resources, resource] }
|
|
@@ -102,7 +121,12 @@ export const resourcesPlugin = definePlugin('resources')
|
|
|
102
121
|
handler: async (ctx, input) => {
|
|
103
122
|
const fs = ctx.platform.fs
|
|
104
123
|
const exec = makeExec(ctx.platform.process)
|
|
105
|
-
const targetDir = ctx.pluginConfig?.targetDir
|
|
124
|
+
const targetDir = await resolveTargetDir(ctx.pluginConfig?.targetDir, {
|
|
125
|
+
sessionId: String(ctx.sessionId),
|
|
126
|
+
sessionDir: ctx.environment.sessionDir,
|
|
127
|
+
workspaceDir: ctx.environment.workspaceDir,
|
|
128
|
+
})
|
|
129
|
+
await fs.mkdir(targetDir, { recursive: true })
|
|
106
130
|
const resourceId = crypto.randomUUID()
|
|
107
131
|
let paths: string[]
|
|
108
132
|
|
|
@@ -183,6 +207,7 @@ export const resourcesPlugin = definePlugin('resources')
|
|
|
183
207
|
filename: input.filename,
|
|
184
208
|
mimeType: input.mimeType,
|
|
185
209
|
paths,
|
|
210
|
+
targetDir,
|
|
186
211
|
injectedAt: Date.now(),
|
|
187
212
|
}))
|
|
188
213
|
|
|
@@ -193,12 +218,16 @@ export const resourcesPlugin = definePlugin('resources')
|
|
|
193
218
|
const { resources } = ctx.pluginState
|
|
194
219
|
if (resources.length === 0) return null
|
|
195
220
|
|
|
196
|
-
const targetDir = ctx.pluginConfig?.targetDir ?? ctx.environment.workspaceDir ?? ctx.environment.sessionDir
|
|
197
221
|
const lines = resources.map(r => {
|
|
198
222
|
const label = r.name ?? r.slug ?? r.filename
|
|
199
|
-
|
|
223
|
+
const target = r.targetDir ? ` in \`${r.targetDir}\`` : ''
|
|
224
|
+
return `- **${label}** (${r.filename}): ${r.paths.length} files${target}`
|
|
200
225
|
})
|
|
226
|
+
const targetDirs = [...new Set(resources.flatMap((r) => r.targetDir ? [r.targetDir] : []))]
|
|
227
|
+
const targetSummary = targetDirs.length === 1
|
|
228
|
+
? `\`${targetDirs[0]}\``
|
|
229
|
+
: targetDirs.length > 1 ? 'multiple target directories' : 'the configured target directory'
|
|
201
230
|
|
|
202
|
-
return `## Injected Resources\n\nThe following resources have been extracted into your workspace (
|
|
231
|
+
return `## Injected Resources\n\nThe following resources have been extracted into your workspace (${targetSummary}):\n${lines.join('\n')}\n\nThese files are ready to use. Explore them with the filesystem tools.`
|
|
203
232
|
})
|
|
204
233
|
.build()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'bun:test'
|
|
2
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { MockLLMProvider } from '~/core/llm/mock.js'
|
|
6
|
+
import { createTestPreset, TestHarness } from '~/testing/index.js'
|
|
7
|
+
import { resourcesPlugin } from './plugin.js'
|
|
8
|
+
import { resourceEvents, type ResourcesState } from './state.js'
|
|
9
|
+
|
|
10
|
+
let currentHarness: TestHarness | undefined
|
|
11
|
+
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
if (currentHarness) {
|
|
14
|
+
await currentHarness.shutdown()
|
|
15
|
+
currentHarness = undefined
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('resources plugin', () => {
|
|
20
|
+
it('resolves targetDir callback relative to workspace and records it in state', async () => {
|
|
21
|
+
const workspaceDir = await mkdtemp(join(tmpdir(), 'roj-resources-'))
|
|
22
|
+
const targetDir = join(workspaceDir, 'packages', 'web')
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const harness = new TestHarness({
|
|
26
|
+
presets: [
|
|
27
|
+
createTestPreset({
|
|
28
|
+
workspaceDir,
|
|
29
|
+
plugins: [
|
|
30
|
+
resourcesPlugin.configure({
|
|
31
|
+
targetDir: ({ sessionId, workspaceDir: resolvedWorkspaceDir }) => {
|
|
32
|
+
expect(sessionId.length).toBeGreaterThan(0)
|
|
33
|
+
expect(resolvedWorkspaceDir).toBe(workspaceDir)
|
|
34
|
+
return 'packages/web'
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
llmProvider: MockLLMProvider.withFixedResponse({ content: 'Ok', toolCalls: [] }),
|
|
41
|
+
})
|
|
42
|
+
currentHarness = harness
|
|
43
|
+
|
|
44
|
+
const session = await harness.createSession('test')
|
|
45
|
+
const result = await session.callPluginMethod('resources.inject', {
|
|
46
|
+
sessionId: String(session.sessionId),
|
|
47
|
+
filename: 'hello.txt',
|
|
48
|
+
mimeType: 'text/plain',
|
|
49
|
+
size: 5,
|
|
50
|
+
fileBuffer: Buffer.from('hello'),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
expect(result).toMatchObject({
|
|
54
|
+
ok: true,
|
|
55
|
+
value: { paths: ['hello.txt'] },
|
|
56
|
+
})
|
|
57
|
+
await expect(readFile(join(targetDir, 'hello.txt'), 'utf-8')).resolves.toBe('hello')
|
|
58
|
+
|
|
59
|
+
const resources = session.getPluginState<ResourcesState>('resources')
|
|
60
|
+
expect(resources?.resources[0]?.targetDir).toBe(targetDir)
|
|
61
|
+
|
|
62
|
+
const events = await session.getEventsByType(resourceEvents, 'resource_injected')
|
|
63
|
+
expect(events[0]?.targetDir).toBe(targetDir)
|
|
64
|
+
} finally {
|
|
65
|
+
await rm(workspaceDir, { recursive: true, force: true })
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -10,6 +10,7 @@ export const resourceEvents = createEventsFactory({
|
|
|
10
10
|
filename: z4.string(),
|
|
11
11
|
mimeType: z4.string(),
|
|
12
12
|
paths: z4.array(z4.string()),
|
|
13
|
+
targetDir: z4.string().optional(),
|
|
13
14
|
injectedAt: z4.number(),
|
|
14
15
|
}),
|
|
15
16
|
},
|
|
@@ -22,6 +23,7 @@ export interface InjectedResource {
|
|
|
22
23
|
filename: string
|
|
23
24
|
mimeType: string
|
|
24
25
|
paths: string[]
|
|
26
|
+
targetDir?: string
|
|
25
27
|
injectedAt: number
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -17,6 +17,8 @@ export const serviceEvents = createEventsFactory({
|
|
|
17
17
|
toStatus: z.enum(['stopped', 'starting', 'ready', 'stopping', 'failed', 'paused']),
|
|
18
18
|
port: z.number().optional(),
|
|
19
19
|
error: z.string().optional(),
|
|
20
|
+
cwd: z.string().optional(),
|
|
21
|
+
command: z.string().optional(),
|
|
20
22
|
pid: z.number().optional(),
|
|
21
23
|
pidStartTime: z.number().optional(),
|
|
22
24
|
}),
|
|
@@ -58,6 +60,8 @@ export const servicePlugin = definePlugin('services')
|
|
|
58
60
|
serviceType: event.serviceType,
|
|
59
61
|
status: event.toStatus,
|
|
60
62
|
port: event.port,
|
|
63
|
+
cwd: event.cwd,
|
|
64
|
+
command: event.command,
|
|
61
65
|
startedAt: event.timestamp,
|
|
62
66
|
pid: event.pid,
|
|
63
67
|
pidStartTime: event.pidStartTime,
|
|
@@ -71,6 +75,8 @@ export const servicePlugin = definePlugin('services')
|
|
|
71
75
|
updated.startedAt = event.timestamp
|
|
72
76
|
updated.error = undefined
|
|
73
77
|
updated.port = event.port
|
|
78
|
+
updated.cwd = event.cwd
|
|
79
|
+
updated.command = event.command
|
|
74
80
|
updated.pid = event.pid
|
|
75
81
|
updated.pidStartTime = event.pidStartTime
|
|
76
82
|
}
|
|
@@ -79,6 +85,12 @@ export const servicePlugin = definePlugin('services')
|
|
|
79
85
|
if (event.port !== undefined) {
|
|
80
86
|
updated.port = event.port
|
|
81
87
|
}
|
|
88
|
+
if (event.cwd !== undefined) {
|
|
89
|
+
updated.cwd = event.cwd
|
|
90
|
+
}
|
|
91
|
+
if (event.command !== undefined) {
|
|
92
|
+
updated.command = event.command
|
|
93
|
+
}
|
|
82
94
|
}
|
|
83
95
|
if (event.toStatus === 'failed' && event.error) {
|
|
84
96
|
updated.error = event.error
|
|
@@ -122,17 +134,19 @@ export const servicePlugin = definePlugin('services')
|
|
|
122
134
|
})
|
|
123
135
|
.context(async (ctx, pluginConfig) => {
|
|
124
136
|
const executor = new ServiceExecutor(ctx.logger, pluginConfig.portPool, { fs: ctx.platform.fs, process: ctx.platform.process })
|
|
125
|
-
executor.onStatusChanged = (sessionId, serviceType, status,
|
|
137
|
+
executor.onStatusChanged = (sessionId, serviceType, status, details) => {
|
|
126
138
|
void ctx.emitEvent(serviceEvents.create('service_status_changed', {
|
|
127
139
|
serviceType,
|
|
128
140
|
toStatus: status,
|
|
129
|
-
port,
|
|
130
|
-
error,
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
port: details.port,
|
|
142
|
+
error: details.error,
|
|
143
|
+
cwd: details.cwd,
|
|
144
|
+
command: details.command,
|
|
145
|
+
pid: details.pid,
|
|
146
|
+
pidStartTime: details.pidStartTime,
|
|
133
147
|
}))
|
|
134
148
|
// Broadcast service status to connected clients (DO → SPA) via WS
|
|
135
|
-
ctx.notify('serviceStatus', { sessionId: String(sessionId), serviceType, status, port })
|
|
149
|
+
ctx.notify('serviceStatus', { sessionId: String(sessionId), serviceType, status, port: details.port })
|
|
136
150
|
}
|
|
137
151
|
return { executor }
|
|
138
152
|
})
|
|
@@ -160,7 +174,8 @@ export const servicePlugin = definePlugin('services')
|
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
const preferredPort = ctx.pluginState.get(input.serviceType)?.port
|
|
163
|
-
await ctx.pluginContext.executor.start(svcConfig, ctx.sessionId, ctx.sessionState.workspaceDir, preferredPort)
|
|
177
|
+
const startResult = await ctx.pluginContext.executor.start(svcConfig, ctx.sessionId, ctx.sessionState.workspaceDir, preferredPort)
|
|
178
|
+
if (!startResult.ok) return Err(ValidationErrors.invalid(startResult.error.message))
|
|
164
179
|
started.push(input.serviceType)
|
|
165
180
|
} else {
|
|
166
181
|
for (const svcConfig of ctx.pluginConfig.services) {
|
|
@@ -174,7 +189,8 @@ export const servicePlugin = definePlugin('services')
|
|
|
174
189
|
}
|
|
175
190
|
} else {
|
|
176
191
|
const preferredPort = ctx.pluginState.get(svcConfig.type)?.port
|
|
177
|
-
await ctx.pluginContext.executor.start(svcConfig, ctx.sessionId, ctx.sessionState.workspaceDir, preferredPort)
|
|
192
|
+
const startResult = await ctx.pluginContext.executor.start(svcConfig, ctx.sessionId, ctx.sessionState.workspaceDir, preferredPort)
|
|
193
|
+
if (!startResult.ok) return Err(ValidationErrors.invalid(startResult.error.message))
|
|
178
194
|
started.push(svcConfig.type)
|
|
179
195
|
}
|
|
180
196
|
}
|
|
@@ -250,6 +266,8 @@ export const servicePlugin = definePlugin('services')
|
|
|
250
266
|
serviceType: z.string(),
|
|
251
267
|
status: z.string(),
|
|
252
268
|
port: z.number().optional(),
|
|
269
|
+
cwd: z.string().optional(),
|
|
270
|
+
command: z.string().optional(),
|
|
253
271
|
error: z.string().optional(),
|
|
254
272
|
recentLogs: z.array(z.string()),
|
|
255
273
|
}),
|
|
@@ -262,6 +280,8 @@ export const servicePlugin = definePlugin('services')
|
|
|
262
280
|
serviceType: input.serviceType,
|
|
263
281
|
status: status ?? 'stopped',
|
|
264
282
|
port: stateEntry?.port,
|
|
283
|
+
cwd: stateEntry?.cwd,
|
|
284
|
+
command: stateEntry?.command,
|
|
265
285
|
error: stateEntry?.error,
|
|
266
286
|
recentLogs: logsResult.ok ? logsResult.value : [],
|
|
267
287
|
})
|
|
@@ -340,7 +360,10 @@ export const servicePlugin = definePlugin('services')
|
|
|
340
360
|
const status = ctx.pluginContext.executor.getStatus(svcConfig.type)
|
|
341
361
|
if (status !== 'ready' && status !== 'starting') {
|
|
342
362
|
const preferredPort = ctx.pluginState.get(svcConfig.type)?.port
|
|
343
|
-
await ctx.pluginContext.executor.start(svcConfig, ctx.sessionId, ctx.sessionState.workspaceDir, preferredPort)
|
|
363
|
+
const startResult = await ctx.pluginContext.executor.start(svcConfig, ctx.sessionId, ctx.sessionState.workspaceDir, preferredPort)
|
|
364
|
+
if (!startResult.ok) {
|
|
365
|
+
ctx.logger.debug('Auto-start service skipped', { serviceType: svcConfig.type, error: startResult.error.message })
|
|
366
|
+
}
|
|
344
367
|
}
|
|
345
368
|
}
|
|
346
369
|
}
|
|
@@ -24,6 +24,12 @@ export type ServiceStatus = 'stopped' | 'starting' | 'ready' | 'stopping' | 'fai
|
|
|
24
24
|
*/
|
|
25
25
|
export interface ServiceCommandArgs {
|
|
26
26
|
port: number
|
|
27
|
+
/** Session id of the service owner. */
|
|
28
|
+
sessionId: string
|
|
29
|
+
/** Absolute path of the session workspace directory, when configured. */
|
|
30
|
+
workspaceDir?: string
|
|
31
|
+
/** Resolved working directory that will be passed to the child process. */
|
|
32
|
+
cwd?: string
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
/**
|
|
@@ -32,10 +38,30 @@ export interface ServiceCommandArgs {
|
|
|
32
38
|
* to locate the web app inside a monorepo before launching the dev server.
|
|
33
39
|
*/
|
|
34
40
|
export interface ServiceCwdArgs {
|
|
41
|
+
/** Session id of the service owner. */
|
|
42
|
+
sessionId: string
|
|
35
43
|
/** Absolute path of the session workspace directory. */
|
|
36
44
|
workspaceDir: string
|
|
37
45
|
}
|
|
38
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Arguments passed to service environment and readiness callbacks.
|
|
49
|
+
*/
|
|
50
|
+
export interface ServiceStartArgs extends ServiceCommandArgs {
|
|
51
|
+
/** Recently captured service output lines. Empty before the process writes. */
|
|
52
|
+
logs: readonly string[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Arguments passed to `availableWhen` before a service is started.
|
|
57
|
+
*/
|
|
58
|
+
export interface ServiceAvailabilityArgs {
|
|
59
|
+
/** Session id of the service owner. */
|
|
60
|
+
sessionId: string
|
|
61
|
+
/** Absolute path of the session workspace directory, when configured. */
|
|
62
|
+
workspaceDir?: string
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
/**
|
|
40
66
|
* Service configuration declared inline in presets.
|
|
41
67
|
* Defines how to spawn and monitor a background process.
|
|
@@ -45,8 +71,8 @@ export interface ServiceConfig {
|
|
|
45
71
|
type: string
|
|
46
72
|
/** Human-readable description */
|
|
47
73
|
description: string
|
|
48
|
-
/** Shell command to run, or a callback receiving
|
|
49
|
-
command: string | ((args: ServiceCommandArgs) => string)
|
|
74
|
+
/** Shell command to run, or a callback receiving runtime start context */
|
|
75
|
+
command: string | ((args: ServiceCommandArgs) => string | Promise<string>)
|
|
50
76
|
/**
|
|
51
77
|
* Working directory (defaults to the workspace dir).
|
|
52
78
|
*
|
|
@@ -57,12 +83,18 @@ export interface ServiceConfig {
|
|
|
57
83
|
* resolved the same way.
|
|
58
84
|
*/
|
|
59
85
|
cwd?: string | ((args: ServiceCwdArgs) => string | Promise<string>)
|
|
60
|
-
/** Additional environment variables */
|
|
61
|
-
env?: Record<string, string>
|
|
86
|
+
/** Additional environment variables, resolved after cwd and before spawn */
|
|
87
|
+
env?: Record<string, string> | ((args: ServiceCommandArgs) => Record<string, string> | Promise<Record<string, string>>)
|
|
88
|
+
/** Return false to make start/autoStart skip this service for the current workspace. */
|
|
89
|
+
availableWhen?: (args: ServiceAvailabilityArgs) => boolean | Promise<boolean>
|
|
62
90
|
/** Start automatically with session (default: false) */
|
|
63
91
|
autoStart?: boolean
|
|
64
92
|
/** Regex pattern to detect "ready" in stdout */
|
|
65
93
|
readyPattern?: string
|
|
94
|
+
/** Callback polled until it reports that the service is ready. */
|
|
95
|
+
readyWhen?: (args: ServiceStartArgs) => boolean | Promise<boolean>
|
|
96
|
+
/** Poll interval for readyWhen in ms (default: 250) */
|
|
97
|
+
readyCheckIntervalMs?: number
|
|
66
98
|
/** SIGTERM→SIGKILL timeout in ms (default: 5000) */
|
|
67
99
|
gracefulStopMs?: number
|
|
68
100
|
/** Ring buffer size for log lines (default: 200) */
|
|
@@ -86,6 +118,10 @@ export interface ServiceEntry {
|
|
|
86
118
|
status: ServiceStatus
|
|
87
119
|
port?: number
|
|
88
120
|
error?: string
|
|
121
|
+
/** Resolved working directory used for the current/last start. */
|
|
122
|
+
cwd?: string
|
|
123
|
+
/** Resolved command used for the current/last start. */
|
|
124
|
+
command?: string
|
|
89
125
|
startedAt?: number
|
|
90
126
|
readyAt?: number
|
|
91
127
|
stoppedAt?: number
|