@roj-ai/sdk 0.2.8 → 0.2.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.
Files changed (55) hide show
  1. package/dist/bootstrap.d.ts +53 -2
  2. package/dist/bootstrap.d.ts.map +1 -1
  3. package/dist/bootstrap.js +3 -2
  4. package/dist/bootstrap.js.map +1 -1
  5. package/dist/bun-platform/shell.d.ts.map +1 -1
  6. package/dist/bun-platform/shell.js +1 -0
  7. package/dist/bun-platform/shell.js.map +1 -1
  8. package/dist/core/events/index.d.ts +2 -0
  9. package/dist/core/events/index.d.ts.map +1 -1
  10. package/dist/core/events/index.js +2 -0
  11. package/dist/core/events/index.js.map +1 -1
  12. package/dist/core/llm/anthropic.js +1 -1
  13. package/dist/core/llm/anthropic.js.map +1 -1
  14. package/dist/core/llm/openrouter.js +1 -1
  15. package/dist/core/llm/openrouter.js.map +1 -1
  16. package/dist/index.d.ts +3 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/platform/index.d.ts +8 -0
  21. package/dist/platform/index.d.ts.map +1 -1
  22. package/dist/platform/index.js.map +1 -1
  23. package/dist/platform/shell.d.ts +3 -0
  24. package/dist/platform/shell.d.ts.map +1 -1
  25. package/dist/plugins/mailbox/state.d.ts +1 -1
  26. package/dist/plugins/services/plugin.d.ts.map +1 -1
  27. package/dist/plugins/services/plugin.js +6 -5
  28. package/dist/plugins/services/plugin.js.map +1 -1
  29. package/dist/plugins/services/service.d.ts +2 -0
  30. package/dist/plugins/services/service.d.ts.map +1 -1
  31. package/dist/plugins/services/service.js.map +1 -1
  32. package/dist/plugins/shell/executor.d.ts +1 -0
  33. package/dist/plugins/shell/executor.d.ts.map +1 -1
  34. package/dist/plugins/shell/executor.js +6 -14
  35. package/dist/plugins/shell/executor.js.map +1 -1
  36. package/dist/testing/conformance.d.ts +3 -2
  37. package/dist/testing/conformance.d.ts.map +1 -1
  38. package/dist/testing/conformance.js +36 -27
  39. package/dist/testing/conformance.js.map +1 -1
  40. package/dist/transport/http/context.d.ts +2 -2
  41. package/dist/transport/http/context.d.ts.map +1 -1
  42. package/package.json +2 -2
  43. package/src/bootstrap.ts +3 -2
  44. package/src/bun-platform/shell.ts +1 -0
  45. package/src/core/events/index.ts +2 -0
  46. package/src/core/llm/anthropic.ts +1 -1
  47. package/src/core/llm/openrouter.ts +1 -1
  48. package/src/index.ts +3 -2
  49. package/src/platform/index.ts +9 -0
  50. package/src/platform/shell.ts +3 -0
  51. package/src/plugins/services/plugin.ts +7 -5
  52. package/src/plugins/services/service.ts +16 -0
  53. package/src/plugins/shell/executor.ts +13 -24
  54. package/src/testing/conformance.ts +75 -59
  55. package/src/transport/http/context.ts +2 -2
@@ -37,6 +37,7 @@ export interface ShellRunOptions {
37
37
  cwd: string
38
38
  env?: Record<string, string>
39
39
  stdin?: string
40
+ /** Wall-clock deadline; ignored when `supportsTimeout` is false. */
40
41
  timeoutMs: number
41
42
  /**
42
43
  * What the command may reach. Meaningful only under `paths` confinement, and
@@ -78,6 +79,8 @@ export interface ShellRunResult {
78
79
  export interface ShellRunner {
79
80
  /** Declared, not assumed: a caller that needs confinement checks it before running. */
80
81
  readonly confinement: ShellConfinement
82
+ /** Whether `timeoutMs` terminates the run before it resolves with `timedOut: true`. Omitted means true for legacy runners. */
83
+ readonly supportsTimeout?: boolean
81
84
 
82
85
  /**
83
86
  * Run one command line to completion and buffer its output.
@@ -201,11 +201,13 @@ export const servicePlugin = definePlugin('services')
201
201
  const statusEffects = new Set<Promise<void>>()
202
202
  let statusEffectTail = Promise.resolve()
203
203
  let publicationEnabled = true
204
- const executor = new ServiceExecutor(logger, pluginConfig.portPool, {
205
- fs: ctx.platform.fs,
206
- process: ctx.platform.process,
207
- pidRegistry: pluginConfig.pidRegistry,
208
- })
204
+ const executor =
205
+ ctx.platform.createServiceExecutor?.(logger, pluginConfig.portPool) ??
206
+ new ServiceExecutor(logger, pluginConfig.portPool, {
207
+ fs: ctx.platform.fs,
208
+ process: ctx.platform.process,
209
+ pidRegistry: pluginConfig.pidRegistry,
210
+ })
209
211
  const beginLifecycle = (serviceType: string): ServiceLease | undefined => {
210
212
  const existing = serviceLeases.get(serviceType)
211
213
  if (existing && !existing.terminalPending) return existing
@@ -149,6 +149,22 @@ export interface ServiceExecutorDeps {
149
149
  hookTimeoutMs?: number
150
150
  }
151
151
 
152
+ /** Lifecycle used by the services plugin, independently of how a host runs it. */
153
+ export type ServiceExecutorPort = Pick<
154
+ ServiceExecutor,
155
+ | 'onStatusChanged'
156
+ | 'onStartSettled'
157
+ | 'start'
158
+ | 'stop'
159
+ | 'restart'
160
+ | 'close'
161
+ | 'getStatus'
162
+ | 'getLogs'
163
+ | 'waitForReady'
164
+ | 'hasScheduledRestart'
165
+ | 'reapProcessGroup'
166
+ >
167
+
152
168
  type ProcessGroupProbe = { state: 'alive' | 'gone' } | { state: 'error'; error: Error }
153
169
 
154
170
  let serviceExecutorObserverForTesting: ((executor: ServiceExecutor) => void) | undefined
@@ -92,9 +92,7 @@ async function resolveAgentPath(
92
92
  }
93
93
  return Ok(absolutePath)
94
94
  }
95
- const validPrefixes = workspaceDir
96
- ? `${VIRTUAL_SESSION}/ or ${VIRTUAL_WORKSPACE}/`
97
- : `${VIRTUAL_SESSION}/`
95
+ const validPrefixes = workspaceDir ? `${VIRTUAL_SESSION}/ or ${VIRTUAL_WORKSPACE}/` : `${VIRTUAL_SESSION}/`
98
96
  return Err({ message: `Path must start with ${validPrefixes}. Got: '${agentPath}'`, recoverable: false })
99
97
  }
100
98
  // Non-sandboxed: validate within allowed dirs
@@ -102,8 +100,7 @@ async function resolveAgentPath(
102
100
  const normalizedSession = resolve(sessionDir)
103
101
  const normalizedWorkspace = workspaceDir ? resolve(workspaceDir) : null
104
102
  const isInSession = absolutePath === normalizedSession || absolutePath.startsWith(normalizedSession + '/')
105
- const isInWorkspace = normalizedWorkspace
106
- && (absolutePath === normalizedWorkspace || absolutePath.startsWith(normalizedWorkspace + '/'))
103
+ const isInWorkspace = normalizedWorkspace && (absolutePath === normalizedWorkspace || absolutePath.startsWith(normalizedWorkspace + '/'))
107
104
  if (!isInSession && !isInWorkspace) {
108
105
  return Err({ message: `Path '${agentPath}' is outside allowed directories`, recoverable: false })
109
106
  }
@@ -174,6 +171,7 @@ export interface ShellResult {
174
171
  exitCode: number
175
172
  signal?: string
176
173
  timedOut: boolean
174
+ truncated?: boolean
177
175
  durationMs: number
178
176
  }
179
177
 
@@ -187,19 +185,17 @@ export class ShellExecutor {
187
185
  private readonly fs: FileSystem
188
186
  private readonly shell?: ShellRunner
189
187
 
190
- constructor(private config: ShellConfig, deps: ShellExecutorDeps) {
188
+ constructor(
189
+ private config: ShellConfig,
190
+ deps: ShellExecutorDeps,
191
+ ) {
191
192
  this.fs = deps.fs
192
193
  this.shell = deps.shell
193
194
  }
194
195
 
195
- async execute(
196
- input: RunCommandInput,
197
- environment: SessionEnvironment,
198
- ): Promise<Result<ShellResult, ToolError>> {
196
+ async execute(input: RunCommandInput, environment: SessionEnvironment): Promise<Result<ShellResult, ToolError>> {
199
197
  const args = typeof input.args === 'string' ? [input.args] : input.args
200
- const fullCommand = args
201
- ? `${shellEscape(input.command)} ${args.map(shellEscape).join(' ')}`
202
- : input.command
198
+ const fullCommand = args ? `${shellEscape(input.command)} ${args.map(shellEscape).join(' ')}` : input.command
203
199
 
204
200
  const timeout = input.timeout ?? this.config.timeout ?? 30000
205
201
  const startTime = Date.now()
@@ -279,6 +275,7 @@ export class ShellExecutor {
279
275
  exitCode: result.exitCode,
280
276
  signal: result.signal,
281
277
  timedOut: result.timedOut,
278
+ ...(result.truncated === undefined ? {} : { truncated: result.truncated }),
282
279
  durationMs: Date.now() - startTime,
283
280
  })
284
281
  } catch (error) {
@@ -297,11 +294,7 @@ export class ShellExecutor {
297
294
  * Paths under the hidden home roots exist only where a grant mounts them back; the rest of
298
295
  * the tree the sandbox binds read-only, so a directory there grants nothing.
299
296
  */
300
- private async resolveSandboxCwd(
301
- agentCwd: string,
302
- sessionDir: string,
303
- workspaceDir: string | undefined,
304
- ): Promise<Result<string, ToolError>> {
297
+ private async resolveSandboxCwd(agentCwd: string, sessionDir: string, workspaceDir: string | undefined): Promise<Result<string, ToolError>> {
305
298
  if (!isAbsolute(agentCwd)) {
306
299
  return Err({ message: `Working directory '${agentCwd}' must be an absolute path`, recoverable: false })
307
300
  }
@@ -323,11 +316,7 @@ export class ShellExecutor {
323
316
  }
324
317
 
325
318
  if (HIDDEN_ROOTS.some((root) => isWithin(normalized, root))) {
326
- const mounted = [
327
- VIRTUAL_SESSION,
328
- ...(workspaceDir ? [VIRTUAL_WORKSPACE] : []),
329
- ...this.boundRoots().map((root) => root.seen),
330
- ]
319
+ const mounted = [VIRTUAL_SESSION, ...(workspaceDir ? [VIRTUAL_WORKSPACE] : []), ...this.boundRoots().map((root) => root.seen)]
331
320
  return Err({
332
321
  message: `Path '${agentCwd}' is not mounted in this sandbox. Mounted roots: ${mounted.join(', ')}`,
333
322
  recoverable: false,
@@ -338,7 +327,7 @@ export class ShellExecutor {
338
327
  }
339
328
 
340
329
  private async requireExists(hostPath: string, agentCwd: string, seen: string): Promise<Result<string, ToolError>> {
341
- if (await this.fs.exists(hostPath)) return Ok(seen)
330
+ if (await this.fs.exists(hostPath)) return Ok(this.shell?.confinement === 'paths' ? seen : hostPath)
342
331
  return Err({ message: `Working directory '${agentCwd}' does not exist`, recoverable: false })
343
332
  }
344
333
 
@@ -93,7 +93,8 @@ export const GIT_FIXTURE: {
93
93
  /**
94
94
  * What the suite reports on. Most are ports; the dotted ones are facets a port
95
95
  * either has or does not — a scheduler that delivers its own wakes, a shell that
96
- * confines by paths — and they are named so that not exercising one is visible.
96
+ * terminates on timeout or confines by paths — and they are named so that not
97
+ * exercising one is visible.
97
98
  */
98
99
  export type ConformancePort =
99
100
  | 'fs'
@@ -106,6 +107,7 @@ export type ConformancePort =
106
107
  | 'scheduler'
107
108
  | 'scheduler.live'
108
109
  | 'shell'
110
+ | 'shell.timeout'
109
111
  | 'shell.paths'
110
112
  | 'shell.host'
111
113
  | 'git'
@@ -126,6 +128,7 @@ export const CONFORMANCE_PORTS: readonly ConformancePort[] = [
126
128
  'scheduler',
127
129
  'scheduler.live',
128
130
  'shell',
131
+ 'shell.timeout',
129
132
  'shell.paths',
130
133
  'shell.host',
131
134
  'git',
@@ -230,9 +233,7 @@ interface WalkShape {
230
233
  }
231
234
 
232
235
  function walkShapes(base: string, entries: readonly WalkEntry[]): WalkShape[] {
233
- return entries
234
- .map((entry) => ({ path: relativeTo(base, entry.path), type: entry.type, size: entry.size }))
235
- .sort((a, b) => a.path.localeCompare(b.path))
236
+ return entries.map((entry) => ({ path: relativeTo(base, entry.path), type: entry.type, size: entry.size })).sort((a, b) => a.path.localeCompare(b.path))
236
237
  }
237
238
 
238
239
  /**
@@ -251,16 +252,13 @@ async function walkWithReaddir(fs: FileSystem, dir: string, options?: WalkOption
251
252
  if (options?.excludeHidden && dirent.name.startsWith('.')) continue
252
253
 
253
254
  const path = joinPath(current, [dirent.name])
254
- const type: WalkEntry['type'] = dirent.isSymbolicLink()
255
- ? 'symlink'
256
- : dirent.isDirectory()
257
- ? 'directory'
258
- : dirent.isFile()
259
- ? 'file'
260
- : 'other'
261
-
262
- const stats = await fs.stat(path).then((value) => value, () => undefined)
263
- const size = type === 'directory' ? 0 : stats?.size ?? 0
255
+ const type: WalkEntry['type'] = dirent.isSymbolicLink() ? 'symlink' : dirent.isDirectory() ? 'directory' : dirent.isFile() ? 'file' : 'other'
256
+
257
+ const stats = await fs.stat(path).then(
258
+ (value) => value,
259
+ () => undefined,
260
+ )
261
+ const size = type === 'directory' ? 0 : (stats?.size ?? 0)
264
262
  const mtime = stats?.mtimeMs ?? 0
265
263
 
266
264
  out.push({ path, type, size, mtime })
@@ -274,13 +272,15 @@ async function walkWithReaddir(fs: FileSystem, dir: string, options?: WalkOption
274
272
 
275
273
  /** The per-path `readFile` loop `FileSystem.readFiles` replaces. */
276
274
  async function readFilesWithLoop(fs: FileSystem, paths: readonly string[]): Promise<ReadFilesEntry[]> {
277
- return Promise.all(paths.map(async (path): Promise<ReadFilesEntry> => {
278
- try {
279
- return { path, content: await fs.readFile(path) }
280
- } catch (error) {
281
- return { path, error: errorCode(error) ?? String(error) }
282
- }
283
- }))
275
+ return Promise.all(
276
+ paths.map(async (path): Promise<ReadFilesEntry> => {
277
+ try {
278
+ return { path, content: await fs.readFile(path) }
279
+ } catch (error) {
280
+ return { path, error: errorCode(error) ?? String(error) }
281
+ }
282
+ }),
283
+ )
284
284
  }
285
285
 
286
286
  /**
@@ -335,12 +335,15 @@ function contextFor(target: ConformanceTarget, instance: PlatformInstance): Conf
335
335
  if (!repo) {
336
336
  const dir = joinPath(instance.root, ['fixture-repo'])
337
337
  const build = target.buildGitRepo ?? defaultBuildGitRepo
338
- repo = build(instance, dir).then(() => dir, (error) => {
339
- throw new Error(
340
- `could not build the fixture repository: ${error instanceof Error ? error.message : String(error)}. `
341
- + 'A host that cannot run `git` supplies ConformanceTarget.buildGitRepo.',
342
- )
343
- })
338
+ repo = build(instance, dir).then(
339
+ () => dir,
340
+ (error) => {
341
+ throw new Error(
342
+ `could not build the fixture repository: ${error instanceof Error ? error.message : String(error)}. ` +
343
+ 'A host that cannot run `git` supplies ConformanceTarget.buildGitRepo.',
344
+ )
345
+ },
346
+ )
344
347
  }
345
348
  return repo
346
349
  },
@@ -416,9 +419,7 @@ const fsChecks: ConformanceCheck[] = [
416
419
  expect((await fs.readdir(path())).sort()).toEqual(['a.txt', 'b.txt', 'dir'])
417
420
 
418
421
  const dirents = await fs.readdir(path(), { withFileTypes: true })
419
- const classified = dirents
420
- .map((dirent) => [dirent.name, dirent.isDirectory(), dirent.isFile()])
421
- .sort()
422
+ const classified = dirents.map((dirent) => [dirent.name, dirent.isDirectory(), dirent.isFile()]).sort()
422
423
  expect(classified).toEqual([
423
424
  ['a.txt', false, true],
424
425
  ['b.txt', false, true],
@@ -763,8 +764,9 @@ const readFilesChecks: ConformanceCheck[] = [
763
764
  const looped = await readFilesWithLoop(fs, paths)
764
765
 
765
766
  expect(batched.map((entry) => entry.path)).toEqual(paths)
766
- expect(batched.map((entry) => (entry.content ? textOf(entry.content) : undefined)))
767
- .toEqual(looped.map((entry) => (entry.content ? textOf(entry.content) : undefined)))
767
+ expect(batched.map((entry) => (entry.content ? textOf(entry.content) : undefined))).toEqual(
768
+ looped.map((entry) => (entry.content ? textOf(entry.content) : undefined)),
769
+ )
768
770
  expect(batched.map((entry) => entry.error)).toEqual([undefined, undefined, undefined])
769
771
  },
770
772
  },
@@ -869,10 +871,7 @@ const rmFilesChecks: ConformanceCheck[] = [
869
871
  const fs = platform.fs
870
872
  await fs.writeFile(path('a.txt'), 'a')
871
873
 
872
- await rejection(
873
- Promise.resolve(fs.rmFiles?.([path('missing.txt')])),
874
- 'rmFiles of a missing path without force',
875
- )
874
+ await rejection(Promise.resolve(fs.rmFiles?.([path('missing.txt')])), 'rmFiles of a missing path without force')
876
875
  await fs.rmFiles?.([path('missing.txt'), path('a.txt')], { force: true })
877
876
  expect(await fs.exists(path('a.txt'))).toBe(false)
878
877
  },
@@ -885,10 +884,7 @@ const rmFilesChecks: ConformanceCheck[] = [
885
884
  await fs.mkdir(path('tree', 'nested'), { recursive: true })
886
885
  await fs.writeFile(path('tree', 'nested', 'leaf.txt'), 'leaf')
887
886
 
888
- await rejection(
889
- Promise.resolve(fs.rmFiles?.([path('tree')])),
890
- 'rmFiles of a populated directory without recursive',
891
- )
887
+ await rejection(Promise.resolve(fs.rmFiles?.([path('tree')])), 'rmFiles of a populated directory without recursive')
892
888
  expect(await fs.exists(path('tree', 'nested', 'leaf.txt'))).toBe(true)
893
889
 
894
890
  await fs.rmFiles?.([path('tree')], { recursive: true })
@@ -908,10 +904,7 @@ const scopeReadsChecks: ConformanceCheck[] = [
908
904
  await fs.writeFile(path('sub', 'b.txt'), 'bb')
909
905
 
910
906
  const outside = [await fs.readFile(path('a.txt'), 'utf-8'), (await fs.readdir(path())).sort().join(',')]
911
- const inside = await fs.scopeReads?.(async () => [
912
- await fs.readFile(path('a.txt'), 'utf-8'),
913
- (await fs.readdir(path())).sort().join(','),
914
- ])
907
+ const inside = await fs.scopeReads?.(async () => [await fs.readFile(path('a.txt'), 'utf-8'), (await fs.readdir(path())).sort().join(',')])
915
908
 
916
909
  expect(inside).toEqual(outside)
917
910
  },
@@ -922,9 +915,11 @@ const scopeReadsChecks: ConformanceCheck[] = [
922
915
  async run({ platform }) {
923
916
  const marker = new Error('block failed')
924
917
  const caught = await rejection(
925
- Promise.resolve(platform.fs.scopeReads?.(async () => {
926
- throw marker
927
- })),
918
+ Promise.resolve(
919
+ platform.fs.scopeReads?.(async () => {
920
+ throw marker
921
+ }),
922
+ ),
928
923
  'scopeReads over a block that throws',
929
924
  )
930
925
  expect(caught).toBe(marker)
@@ -1069,6 +1064,14 @@ const shellChecks: ConformanceCheck[] = [
1069
1064
  expect(declared).toContain(platform.shell?.confinement)
1070
1065
  },
1071
1066
  },
1067
+ {
1068
+ port: 'shell',
1069
+ name: 'declares a boolean timeout capability or omits it for legacy support',
1070
+ async run({ platform }) {
1071
+ const capability = platform.shell?.supportsTimeout
1072
+ expect(capability === undefined || typeof capability === 'boolean').toBe(true)
1073
+ },
1074
+ },
1072
1075
  {
1073
1076
  port: 'shell',
1074
1077
  name: 'a non-zero exit resolves rather than rejects',
@@ -1129,7 +1132,7 @@ const shellChecks: ConformanceCheck[] = [
1129
1132
  },
1130
1133
  },
1131
1134
  {
1132
- port: 'shell',
1135
+ port: 'shell.timeout',
1133
1136
  name: 'a timeout terminates the command and reports timedOut',
1134
1137
  async run({ platform, root }) {
1135
1138
  const started = Date.now()
@@ -1139,6 +1142,21 @@ const shellChecks: ConformanceCheck[] = [
1139
1142
  expect(result.exitCode).not.toBe(0)
1140
1143
  },
1141
1144
  },
1145
+ {
1146
+ port: 'shell.timeout',
1147
+ name: 'a timed-out run cannot keep mutating after it resolves',
1148
+ async run({ platform, path, root }) {
1149
+ const marker = path('late-after-timeout.txt')
1150
+ const result = await shellRun(platform, {
1151
+ command: 'sleep 1; printf late > late-after-timeout.txt',
1152
+ cwd: root,
1153
+ timeoutMs: 100,
1154
+ })
1155
+ expect(result.timedOut).toBe(true)
1156
+ await sleep(1_200)
1157
+ expect(await platform.fs.exists(marker)).toBe(false)
1158
+ },
1159
+ },
1142
1160
  {
1143
1161
  port: 'shell.paths',
1144
1162
  name: 'a granted path is reachable and one outside the grants is not',
@@ -1281,10 +1299,7 @@ const gitChecks: ConformanceCheck[] = [
1281
1299
 
1282
1300
  await rejection(Promise.resolve(platform.git?.status({ dir })), 'status outside a repository')
1283
1301
  await rejection(Promise.resolve(platform.git?.log({ dir })), 'log outside a repository')
1284
- await rejection(
1285
- Promise.resolve(platform.git?.countAhead({ dir, base: GIT_FIXTURE.base })),
1286
- 'countAhead outside a repository',
1287
- )
1302
+ await rejection(Promise.resolve(platform.git?.countAhead({ dir, base: GIT_FIXTURE.base })), 'countAhead outside a repository')
1288
1303
 
1289
1304
  // `defaultBranch` says undefined means unknown, so outside a repo it may answer either way.
1290
1305
  const branch = await platform.git?.defaultBranch({ dir }).catch(() => undefined)
@@ -1605,11 +1620,16 @@ async function probeInstance(target: ConformanceTarget, instance: PlatformInstan
1605
1620
 
1606
1621
  const confinement = platform.shell?.confinement
1607
1622
  add('shell', platform.shell !== undefined)
1623
+ add(
1624
+ 'shell.timeout',
1625
+ platform.shell !== undefined && platform.shell.supportsTimeout !== false,
1626
+ platform.shell === undefined ? 'port absent' : 'the runner declares no wall-clock timeout support',
1627
+ )
1608
1628
  const confines = await probeConfinement(instance)
1609
1629
  add('shell.paths', confines.ok, confines.note)
1610
1630
  add('shell.host', confinement === 'host', `confinement is ${confinement ?? 'absent'}`)
1611
1631
 
1612
- const buildable = platform.git !== undefined && await canBuildGitRepo(target, platform)
1632
+ const buildable = platform.git !== undefined && (await canBuildGitRepo(target, platform))
1613
1633
  add('git', buildable, platform.git === undefined ? undefined : 'no way to build a fixture repository — declare ConformanceTarget.buildGitRepo')
1614
1634
 
1615
1635
  add('fsRevision', platform.fsRevision !== undefined)
@@ -1698,9 +1718,7 @@ export async function runConformanceCheck(target: ConformanceTarget, check: Conf
1698
1718
  /** The checks a host answering `answered` should be held to. */
1699
1719
  export function checksFor(answered: Iterable<ConformancePort>): ConformanceCheck[] {
1700
1720
  const ports = new Set(answered)
1701
- return platformConformanceChecks.filter((check) =>
1702
- ports.has(check.port) && (check.needs ?? []).every((port) => ports.has(port))
1703
- )
1721
+ return platformConformanceChecks.filter((check) => ports.has(check.port) && (check.needs ?? []).every((port) => ports.has(port)))
1704
1722
  }
1705
1723
 
1706
1724
  function describeSkip(support: PortSupport): string {
@@ -1722,9 +1740,7 @@ export function runPlatformConformance(target: ConformanceTarget): void {
1722
1740
  const skipped = support.filter((entry) => !entry.answered)
1723
1741
 
1724
1742
  console.log(
1725
- `[conformance] ${target.name}\n`
1726
- + ` exercised: ${exercised.join(', ') || 'none'}\n`
1727
- + ` skipped: ${skipped.map(describeSkip).join(', ') || 'none'}`,
1743
+ `[conformance] ${target.name}\n` + ` exercised: ${exercised.join(', ') || 'none'}\n` + ` skipped: ${skipped.map(describeSkip).join(', ') || 'none'}`,
1728
1744
  )
1729
1745
 
1730
1746
  for (const port of CONFORMANCE_PORTS) {
@@ -9,13 +9,13 @@
9
9
  */
10
10
 
11
11
  import type { PreprocessorRegistry } from '~/plugins/uploads/preprocessor.js'
12
- import type { Services } from '../../bootstrap.js'
12
+ import type { PluginProfile, Services } from '../../bootstrap.js'
13
13
  import type { SessionManager } from '../../core/sessions/session-manager.js'
14
14
 
15
15
  /**
16
16
  * Extended services with SessionManager for HTTP routes.
17
17
  */
18
- export type AppServices = Services & {
18
+ export type AppServices = Services<PluginProfile> & {
19
19
  sessionRuntime: SessionManager
20
20
  /** Bearer token for authenticating HTTP requests. Optional - only used in worker mode. */
21
21
  agentToken?: string