refactor(packages): dissolve ui/ and rename sdk/ to scaffold/
git mv per the regrouping RFC: the five human-collaboration seams and tui join packages/interaction/, app-boot becomes packages/boot/, and jsonrpc joins the renamed scaffold/ (formerly sdk/) as its server half beside client/protocol/create-sdk/helper/scripts/telemetry, whose folders drop the legacy sdk- prefix. Three new group README triplets replace the ui/ and sdk/ ones; tsconfig references/paths/globs, knip keys, vitest globs, gate scripts, catalogs, docs, and the lockfile follow. Adds the four settled FIXME rename markers (dsh-sdk-server, dsh-sdk-telemetry, dsh-sdk-helper, dsh-sdk-scripts). The scaffold folders diverge from their npm names until those renames land, so tsconfig.base.json maps the three affected names explicitly beside the group wildcard. Also repairs two pre-existing stale-path classes the strengthened sweep surfaced: docs/web-styling.md's retired web-ui host package and type-model spec fixture-literal joins. app-boot's three Loader-composition specs time out at the default 5s under full-suite parallel load on this filesystem (pre-existing; pass isolated with --testTimeout=30000); interaction/scaffold/boot suites otherwise green (687 passed).
This commit is contained in:
96
packages/scaffold/server/src/index.ts
Normal file
96
packages/scaffold/server/src/index.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* SDK-facing JSON-RPC plugin over stdio. An external `cordis.yml` decides
|
||||
* whether to load it; see the single-executable Agent Note and package README.
|
||||
* Stdout is reserved for protocol frames, so the tree must not load a stdout logger.
|
||||
* This plugin answers `shutdown`, disposes the complete root runtime, and exits 0; the app bin
|
||||
* owns EOF and signal exits. Keep named plugin exports with no default export so
|
||||
* Loader `unwrapExports` preserves `name`, `inject`, `Config`, and `apply`.
|
||||
*
|
||||
* FIXME: rename to `@deepseek-ai/dsh-sdk-server` before the first tagged release —
|
||||
* the current name says the wire encoding, not the role; it is the server half of
|
||||
* the SDK protocol ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)).
|
||||
*
|
||||
* @module @deepseek-ai/dsh-jsonrpc
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import type { Readable, Writable } from 'node:stream'
|
||||
import Schema from 'schemastery'
|
||||
import { JsonRpcLineTransport } from '@deepseek-ai/dsh-sdk-protocol'
|
||||
import { HarnessSdkServer } from './server.ts'
|
||||
|
||||
export * from './server.ts'
|
||||
|
||||
export const name = 'jsonrpc'
|
||||
// Only the agent factory is required; initialize reads the optional LLM seam with ctx.get().
|
||||
export const inject = ['agents']
|
||||
|
||||
/** JSON-RPC deployment config plus runtime-only test seams. */
|
||||
export interface JsonRpcConfig {
|
||||
/** Report max-token turn/subagent termination as a successful SDK result. */
|
||||
maxTokensAsSuccess?: boolean
|
||||
/** Transport input override; production uses `process.stdin`. */
|
||||
input?: Readable
|
||||
/** Transport output override; production uses `process.stdout`. */
|
||||
output?: Writable
|
||||
/** Process-exit override; production uses `process.exit`. */
|
||||
exit?: (code: number) => void
|
||||
}
|
||||
|
||||
export const Config: Schema<JsonRpcConfig> = Schema.object({
|
||||
maxTokensAsSuccess: Schema.boolean().default(false),
|
||||
})
|
||||
|
||||
/**
|
||||
* Serve SDK requests over the configured streams. Effect disposal shuts down
|
||||
* SDK-created agents and closes the transport. A `shutdown` response is flushed
|
||||
* before the root runtime is disposed and the process exits 0; the app bin
|
||||
* owns root-context disposal for EOF and signals.
|
||||
*/
|
||||
export function apply(ctx: Context, config: JsonRpcConfig): void {
|
||||
// Cordis applies the schema default before invoking the plugin.
|
||||
const resolvedConfig = config as JsonRpcConfig & { maxTokensAsSuccess: boolean }
|
||||
// Protocol shutdown owns the complete runtime process, so it must await the
|
||||
// root lifecycle (including persistence) before exiting.
|
||||
const rootFiber = ctx.root.fiber
|
||||
/* v8 ignore next -- production stdio wiring; tests always inject the runtime seams */
|
||||
const input = config.input ?? process.stdin
|
||||
/* v8 ignore next -- production stdio wiring; tests always inject the runtime seams */
|
||||
const output = config.output ?? process.stdout
|
||||
/* v8 ignore next -- production exit wiring; tests always inject the runtime seams */
|
||||
const exit = config.exit ?? ((code: number): void => { process.exit(code) })
|
||||
|
||||
const transport = new JsonRpcLineTransport(input, output)
|
||||
const server = new HarnessSdkServer(ctx, transport, {
|
||||
maxTokensAsSuccess: resolvedConfig.maxTokensAsSuccess,
|
||||
})
|
||||
|
||||
// Share one exit task so racing shutdown requests cannot dispose the root or
|
||||
// exit the process more than once.
|
||||
let exitTask: Promise<void> | undefined
|
||||
const disposeAndExit = (): Promise<void> => {
|
||||
exitTask ??= (async () => {
|
||||
await Promise.allSettled([Promise.resolve().then(() => transport.flush())])
|
||||
await Promise.allSettled([Promise.resolve().then(() => rootFiber.dispose())])
|
||||
exit(0)
|
||||
})()
|
||||
return exitTask
|
||||
}
|
||||
|
||||
transport.onRequest(async (method, params) => {
|
||||
const result = await server.handleRequest(method, params)
|
||||
if (method === 'shutdown') {
|
||||
// Run after the handler result is written; the task then flushes, disposes, and exits.
|
||||
setImmediate(() => { void disposeAndExit() })
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
ctx.effect(() => {
|
||||
transport.start()
|
||||
return async () => {
|
||||
await server.shutdown()
|
||||
transport.close()
|
||||
}
|
||||
}, 'jsonrpc.serve')
|
||||
}
|
||||
Reference in New Issue
Block a user