fix(cli): harden launcher lifecycle
This commit is contained in:
@@ -144,17 +144,9 @@ export async function runTui(
|
|||||||
const entry = process.argv[1]
|
const entry = process.argv[1]
|
||||||
const execve = process.execve?.bind(process)
|
const execve = process.execve?.bind(process)
|
||||||
const app: { current?: Context } = {}
|
const app: { current?: Context } = {}
|
||||||
// Resuming reproduces THIS invocation with a different id. Meta mode is a
|
// Resume always enters the default surface because meta rejects parent
|
||||||
// subcommand that rejects `--config`, while the default surface carries it, so
|
// options, including `--resume`. The resumed session already persists its cwd.
|
||||||
// both the in-place handoff and the printed command derive from one shape.
|
const resumeArgs = (sessionId: string, _targetCwd?: string): string[] => [
|
||||||
// `meta` is only reproducible for a target inside this checkout: it chdirs to
|
|
||||||
// SOURCE_ROOT itself, which would override any other workspace, so a
|
|
||||||
// cross-workspace resume takes the default surface and the caller supplies the
|
|
||||||
// directory instead.
|
|
||||||
const resumeArgs = (sessionId: string, targetCwd?: string): string[] =>
|
|
||||||
workspace !== undefined && (targetCwd === undefined || targetCwd === workspace)
|
|
||||||
? ['meta', `--resume=${sessionId}`]
|
|
||||||
: [
|
|
||||||
`--resume=${sessionId}`,
|
`--resume=${sessionId}`,
|
||||||
// Both config flags must survive the handoff: resuming into a different
|
// Both config flags must survive the handoff: resuming into a different
|
||||||
// tree than the session was created in would silently change the agent.
|
// tree than the session was created in would silently change the agent.
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export function assertEntriesLoaded(ctx: Context, binName: string): void {
|
|||||||
* (see {@link resolveConfigPath}).
|
* (see {@link resolveConfigPath}).
|
||||||
* @param patches - optional overlay patches applied over the included tree
|
* @param patches - optional overlay patches applied over the included tree
|
||||||
* (see {@link loadPersonalPatches}); an empty list mounts none.
|
* (see {@link loadPersonalPatches}); an empty list mounts none.
|
||||||
* @param prepare - optional host setup run against the root context before any Loader entry mounts.
|
* @param prepare - optional host setup run after Loader installation and before any config-tree entry mounts.
|
||||||
* @returns the root context once every entry has started.
|
* @returns the root context once every entry has started.
|
||||||
*/
|
*/
|
||||||
export async function boot(
|
export async function boot(
|
||||||
@@ -218,10 +218,10 @@ export async function boot(
|
|||||||
prepare?: (ctx: Context) => Promise<void> | void,
|
prepare?: (ctx: Context) => Promise<void> | void,
|
||||||
): Promise<Context> {
|
): Promise<Context> {
|
||||||
const ctx = new Context()
|
const ctx = new Context()
|
||||||
await prepare?.(ctx)
|
|
||||||
ctx.baseUrl = pathToFileURL(dirname(absoluteConfigPath)).href + '/'
|
ctx.baseUrl = pathToFileURL(dirname(absoluteConfigPath)).href + '/'
|
||||||
await ctx.plugin(Loader)
|
await ctx.plugin(Loader)
|
||||||
ctx.loader.builtins.include = Include
|
ctx.loader.builtins.include = Include
|
||||||
|
await prepare?.(ctx)
|
||||||
await ctx.loader.create({
|
await ctx.loader.create({
|
||||||
name: 'cordis:include',
|
name: 'cordis:include',
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
@@ -195,7 +195,11 @@ describe('boot', () => {
|
|||||||
writeFileSync(join(dir, 'noop.mjs'), 'export const name = "noop"\nexport function apply() {}\n')
|
writeFileSync(join(dir, 'noop.mjs'), 'export const name = "noop"\nexport function apply() {}\n')
|
||||||
writeFileSync(join(dir, 'cordis.yml'), '- id: noop\n name: ./noop.mjs\n')
|
writeFileSync(join(dir, 'cordis.yml'), '- id: noop\n name: ./noop.mjs\n')
|
||||||
const prepared: Context[] = []
|
const prepared: Context[] = []
|
||||||
const ctx = await boot(NAME, join(dir, 'cordis.yml'), undefined, (hostCtx) => { prepared.push(hostCtx) })
|
const ctx = await boot(NAME, join(dir, 'cordis.yml'), undefined, (hostCtx) => {
|
||||||
|
expect(hostCtx.loader).toBeDefined()
|
||||||
|
expect([...hostCtx.loader.entries()]).toEqual([])
|
||||||
|
prepared.push(hostCtx)
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
expect(prepared).toEqual([ctx])
|
expect(prepared).toEqual([ctx])
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1676,7 +1676,9 @@ export function apply(ctx: Context, config: Config): void {
|
|||||||
initialSkill === undefined ? {} : { initialSkill },
|
initialSkill === undefined ? {} : { initialSkill },
|
||||||
), {
|
), {
|
||||||
terminal: new ProcessTerminal(),
|
terminal: new ProcessTerminal(),
|
||||||
exit: code => process.exit(code),
|
exit: (code) => {
|
||||||
|
void ctx.fiber.dispose().finally(() => { process.exit(code) })
|
||||||
|
},
|
||||||
...resumeHost === undefined ? {} : { handoffResume: (sessionId, cwd) => resumeHost.handoff(sessionId, cwd) },
|
...resumeHost === undefined ? {} : { handoffResume: (sessionId, cwd) => resumeHost.handoff(sessionId, cwd) },
|
||||||
...goodbyeMessage === undefined ? {} : { goodbyeMessage },
|
...goodbyeMessage === undefined ? {} : { goodbyeMessage },
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user