Repository navigation
feat: select work tools per headless run #254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2db98b3
9088fa9
ede414a
26c53b2
46f98d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,4 +1,6 @@ | ||||||
| import { outputSchema, outputResult, OUTPUT_SCHEMA_CONFIG, OUTPUT_CONFIG_EXIT, OUTPUT_FAILED_EXIT } from "./run.output" | ||||||
| import { ToolSelection } from "../../tool/selection" | ||||||
| import { Config } from "../../config/config" | ||||||
| import { OutputSchema } from "@/session/output-schema" | ||||||
| import type { Argv } from "yargs" | ||||||
| import path from "path" | ||||||
|
|
@@ -375,6 +377,12 @@ export const RunCommand = cmd({ | |||||
| .strictOptions() | ||||||
| .strictCommands(false) | ||||||
| .parserConfiguration({ "populate--": true, "parse-positional-numbers": false }) | ||||||
| .option("tools", { | ||||||
| type: "string", | ||||||
| nargs: 1, | ||||||
| describe: | ||||||
| "exact comma-separated work-tool names; use an empty string for no work tools (permissions still apply)", | ||||||
| }) | ||||||
| .option("command", { | ||||||
| describe: "the command to run, use message for args", | ||||||
| type: "string", | ||||||
|
|
@@ -488,6 +496,27 @@ export const RunCommand = cmd({ | |||||
| process.exit(exit) | ||||||
| } | ||||||
|
|
||||||
| const requested = args.tools | ||||||
| if (requested !== undefined && typeof requested !== "string") { | ||||||
| await fail( | ||||||
| "Use --tools once with one comma-separated argument (for example, --tools read,bash).", | ||||||
| "INVOCATION_INVALID_ARGUMENTS", | ||||||
| ) | ||||||
| } | ||||||
| const toolAllowlist = | ||||||
| requested === undefined | ||||||
| ? undefined | ||||||
| : await Promise.resolve() | ||||||
| .then(() => ToolSelection.parse(requested)) | ||||||
| .catch((error) => fail(error.message, "INVOCATION_INVALID_ARGUMENTS")) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Invalid --tools prints raw ZodError JSON to user. 🤖 Fix with your agentWhy this matterstoolAllowlist is computed via Promise.resolve().then(() => ToolSelection.parse(requested)).catch((error) => fail(error.message, …)). ToolSelection.parse throws a zod ZodError (Names.parse on the split array), and ZodError.message is the serialized JSON issues array, so const toolAllowlist =
requested === undefined
? undefined
: await Promise.resolve()
.then(() => ToolSelection.parse(requested))
.catch((error) => fail(error.message, "INVOCATION_INVALID_ARGUMENTS"))
if (args.attach && requested !== undefined) { |
||||||
|
|
||||||
| if (args.attach && requested !== undefined) { | ||||||
| await fail( | ||||||
| "Work-tool selection is not supported with --attach: this client cannot verify server-side enforcement. Remove --tools or run locally.", | ||||||
| "INVOCATION_INVALID_ARGUMENTS", | ||||||
| ) | ||||||
| } | ||||||
|
|
||||||
| if (args.share === true) { | ||||||
| await fail("--share was removed: session sharing is no longer supported", "INVOCATION_INVALID_ARGUMENTS") | ||||||
| } | ||||||
|
|
@@ -1145,6 +1174,7 @@ export const RunCommand = cmd({ | |||||
| if (args.command) { | ||||||
| const opts = { | ||||||
| agent, | ||||||
| toolAllowlist, | ||||||
| model: args.model, | ||||||
| command: args.command, | ||||||
| arguments: { argv: normalized.argv, ...(stdin !== undefined ? { stdin } : {}) }, | ||||||
|
|
@@ -1157,6 +1187,7 @@ export const RunCommand = cmd({ | |||||
| const model = args.model ? Provider.parseModel(args.model) : undefined | ||||||
| const opts = { | ||||||
| agent, | ||||||
| toolAllowlist, | ||||||
| model, | ||||||
| variant: args.variant, | ||||||
| parts: [...files, { type: "text", text: message }], | ||||||
|
|
@@ -1199,6 +1230,14 @@ export const RunCommand = cmd({ | |||||
|
|
||||||
| invocation.phase("bootstrap") | ||||||
| if (args.attach) { | ||||||
| // This checkout has no receiver/capability contract that can enforce selection. | ||||||
| const configured = await invocation.guard(() => Config.readToolSelection(process.cwd())) | ||||||
| if (configured !== undefined) { | ||||||
| await fail( | ||||||
| "Work-tool selection is not supported with --attach: this client cannot verify server-side enforcement. Remove --tools and local tool_allowlist or run locally.", | ||||||
| "INVOCATION_INVALID_ARGUMENTS", | ||||||
| ) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Attach preflight fetch failure crashes runs unguarded.
Suggested change
🤖 Fix with your agentWhy this mattersEvery invocation.phase("bootstrap")
if (args.attach) {
// This checkout has no receiver/capability contract that can enforce selection.
const configured = await Config.readToolSelection(process.cwd())
if (configured !== undefined) { |
||||||
| } | ||||||
| return await invocation.run(() => execute(createAictrlClient({ baseUrl: args.attach, directory }))) | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -1234,6 +1273,7 @@ export const RunCommand = cmd({ | |||||
| sessionID: opts.sessionID, | ||||||
| parts: opts.parts, | ||||||
| agent: opts.agent, | ||||||
| toolAllowlist: opts.toolAllowlist, | ||||||
| model: opts.model, | ||||||
| variant: opts.variant, | ||||||
| ...(opts.format ? { format: opts.format } : {}), | ||||||
|
|
@@ -1245,6 +1285,7 @@ export const RunCommand = cmd({ | |||||
| command: opts.command, | ||||||
| arguments: opts.arguments ?? "", | ||||||
| agent: opts.agent, | ||||||
| toolAllowlist: opts.toolAllowlist, | ||||||
| model: opts.model, | ||||||
| variant: opts.variant, | ||||||
| ...(opts.format ? { format: opts.format } : {}), | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚪ Parse error masks clearer --attach rejection.
🤖 Fix with your agent
Why this matters
ToolSelection.parse(requested) runs before the
args.attach && requested !== undefinedrejection, soaictrl run --attach URL --tools "read ,bash"reports the whitespace parse error instead of the more decisive "work-tool selection is not supported with --attach" message, which applies to any --tools value combined with --attach. Both fail before configuration discovery, so the documented ordering guarantee holds — only the reported message is suboptimal.