fix(subagent-acp): reject an empty config cwd at load
path.resolve('') is the process cwd, so an empty configured cwd would
silently reintroduce the launch-directory fallback the parent-session
cwd resolution removed. Fail at plugin load with an actionable message
instead.
This commit is contained in:
@@ -896,11 +896,11 @@ export interface Config {
|
|||||||
/** Arguments passed to {@link command}. */
|
/** Arguments passed to {@link command}. */
|
||||||
args: string[]
|
args: string[]
|
||||||
/**
|
/**
|
||||||
* Working directory override for the child process and its ACP session. A
|
* Working directory override for the child process and its ACP session.
|
||||||
* relative path resolves against the harness launch directory at load, and
|
* Must be non-empty; a relative path resolves against the harness launch
|
||||||
* the result must be an existing directory. When omitted, each child
|
* directory at load, and the result must be an existing directory. When
|
||||||
* inherits its delegating parent session's cwd — and starting one from a
|
* omitted, each child inherits its delegating parent session's cwd — and
|
||||||
* parent session that has no cwd fails.
|
* starting one from a parent session that has no cwd fails.
|
||||||
*/
|
*/
|
||||||
cwd?: string
|
cwd?: string
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ ACP advertises no start-time capabilities because this process cannot enforce th
|
|||||||
| `providerName` | `acp` | Registry name on `ctx.subagents`. |
|
| `providerName` | `acp` | Registry name on `ctx.subagents`. |
|
||||||
| `command` | required | Executable spawned for each run. |
|
| `command` | required | Executable spawned for each run. |
|
||||||
| `args` | `[]` | Command arguments. |
|
| `args` | `[]` | Command arguments. |
|
||||||
| `cwd` | parent session cwd | Working-directory override for the child process and its ACP session; a relative value resolves against the harness launch directory at load and must name an existing directory. |
|
| `cwd` | parent session cwd | Working-directory override for the child process and its ACP session; must be non-empty, a relative value resolves against the harness launch directory at load, and the result must name an existing directory. |
|
||||||
| `permission` | `reject` | Auto-answer permission requests by rejecting or choosing the first allow-shaped option. |
|
| `permission` | `reject` | Auto-answer permission requests by rejecting or choosing the first allow-shaped option. |
|
||||||
| `env` | `{}` | Explicit child environment layered over a credential-scrubbed parent environment. |
|
| `env` | `{}` | Explicit child environment layered over a credential-scrubbed parent environment. |
|
||||||
| `disposeEofGraceMs` | `6000` | Grace after stdin EOF before SIGTERM. |
|
| `disposeEofGraceMs` | `6000` | Grace after stdin EOF before SIGTERM. |
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ export interface Config {
|
|||||||
/** Arguments passed to {@link command}. */
|
/** Arguments passed to {@link command}. */
|
||||||
args: string[]
|
args: string[]
|
||||||
/**
|
/**
|
||||||
* Working directory override for the child process and its ACP session. A
|
* Working directory override for the child process and its ACP session.
|
||||||
* relative path resolves against the harness launch directory at load, and
|
* Must be non-empty; a relative path resolves against the harness launch
|
||||||
* the result must be an existing directory. When omitted, each child
|
* directory at load, and the result must be an existing directory. When
|
||||||
* inherits its delegating parent session's cwd — and starting one from a
|
* omitted, each child inherits its delegating parent session's cwd — and
|
||||||
* parent session that has no cwd fails.
|
* starting one from a parent session that has no cwd fails.
|
||||||
*/
|
*/
|
||||||
cwd?: string
|
cwd?: string
|
||||||
/**
|
/**
|
||||||
@@ -160,6 +160,11 @@ export function apply(ctx: Context, config: Config): void {
|
|||||||
const resolved = config as ResolvedConfig
|
const resolved = config as ResolvedConfig
|
||||||
assertPositiveFinite('disposeEofGraceMs', resolved.disposeEofGraceMs)
|
assertPositiveFinite('disposeEofGraceMs', resolved.disposeEofGraceMs)
|
||||||
assertPositiveFinite('disposeGraceMs', resolved.disposeGraceMs)
|
assertPositiveFinite('disposeGraceMs', resolved.disposeGraceMs)
|
||||||
|
// `path.resolve('')` is the process cwd — an empty string would silently
|
||||||
|
// reintroduce the launch-directory fallback this resolution removed.
|
||||||
|
if (resolved.cwd === '') {
|
||||||
|
throw new Error('subagent-acp: config cwd must not be empty — omit the key to inherit the parent session cwd')
|
||||||
|
}
|
||||||
// Interpret a relative configured cwd against the harness launch directory
|
// Interpret a relative configured cwd against the harness launch directory
|
||||||
// ONCE, at load, and fail a misconfigured directory here — not per start.
|
// ONCE, at load, and fail a misconfigured directory here — not per start.
|
||||||
const validated: ResolvedConfig = resolved.cwd === undefined
|
const validated: ResolvedConfig = resolved.cwd === undefined
|
||||||
|
|||||||
@@ -199,6 +199,22 @@ describe('cwd resolution', () => {
|
|||||||
expect(text(result.output)).toBe(`${realpathSync(absolute)}\n${absolute}`)
|
expect(text(result.output)).toBe(`${realpathSync(absolute)}\n${absolute}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('rejects an empty config cwd at load', async () => {
|
||||||
|
// `path.resolve('')` is the process cwd, so an empty string would silently
|
||||||
|
// reintroduce the launch-directory fallback this resolution removed.
|
||||||
|
const ctx = new Context()
|
||||||
|
await ctx.plugin(SubagentService)
|
||||||
|
await expect(ctx.plugin(acp, {
|
||||||
|
providerName: 'acp',
|
||||||
|
command: 'true',
|
||||||
|
args: [],
|
||||||
|
cwd: '',
|
||||||
|
permission: 'reject',
|
||||||
|
env: {},
|
||||||
|
})).rejects.toThrow('config cwd must not be empty')
|
||||||
|
await ctx.fiber.dispose()
|
||||||
|
})
|
||||||
|
|
||||||
it('rejects a config cwd that is not an accessible directory at load', async () => {
|
it('rejects a config cwd that is not an accessible directory at load', async () => {
|
||||||
const ctx = new Context()
|
const ctx = new Context()
|
||||||
await ctx.plugin(SubagentService)
|
await ctx.plugin(SubagentService)
|
||||||
|
|||||||
Reference in New Issue
Block a user