diff --git a/CHANGELOG.md b/CHANGELOG.md index 107d4e4..7b2f8b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **opencode sub-agents can delegate privately.** `send_to_session` takes `private`, which + keeps the answer to its ticket: no note at the session's prompt, and only + `wait_for_answer` with that ticket returns it. Needs a lich with `lich send --private`. + ## [0.13.1] - 2026-09-13 ### Fixed diff --git a/opencode/lich.js b/opencode/lich.js index 2bc903d..7cb519d 100644 --- a/opencode/lich.js +++ b/opencode/lich.js @@ -171,10 +171,19 @@ async function lichTools($, helper) { prompt: s.string().describe("What to ask that session's agent to do."), project, timeout_seconds: s.number().optional().describe("Seconds to wait. Capped at 90."), + private: s + .boolean() + .optional() + .describe( + "Set this when you are a sub-agent running inside a session rather than the " + + "session's own agent. The answer is then yours alone: no note announces it at " + + "the session's prompt, and only wait_for_answer with this ticket returns it.", + ), }, execute: (args) => run([ "send", + ...(args.private === true ? ["--private"] : []), ...flag("--project", args.project), "--timeout", String(waitSeconds(args.timeout_seconds)), diff --git a/tests/opencode-tools.test.mjs b/tests/opencode-tools.test.mjs index e8c9650..a218dfb 100644 --- a/tests/opencode-tools.test.mjs +++ b/tests/opencode-tools.test.mjs @@ -154,6 +154,21 @@ test('a task carries its target, its prompt and a bounded wait', async () => { assert.equal(shell.calls[3].args[2], '90') }) +test('private is only passed when it is really true', async () => { + const shell = fakeShell() + await inLich(shell, async (tools) => { + for (const value of [undefined, false, 'true', 1]) { + await tools.send_to_session.execute({ session: 'docs', prompt: 'x', private: value }) + } + await tools.send_to_session.execute({ session: 'docs', prompt: 'x', private: true }) + }) + + for (const call of shell.calls.slice(0, -1)) { + assert.ok(!call.args.includes('--private'), `--private sent for ${JSON.stringify(call.args)}`) + } + assert.deepEqual(shell.calls.at(-1).args, ['send', '--private', '--timeout', '90', 'docs', 'x']) +}) + test('force is only passed when it is really true', async () => { const shell = fakeShell() await inLich(shell, async (tools) => {