Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions opencode/lich.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
15 changes: 15 additions & 0 deletions tests/opencode-tools.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Loading