Skip to content

(triggers): hold a single trigger while the CLI shows a dialog (#379) - #424

Merged
devsuitup merged 6 commits into
mainfrom
fix/379-single-trigger-dialog
Oct 3, 2026
Merged

devsuitup merged 6 commits into
mainfrom
fix/379-single-trigger-dialog

Conversation

@devsuitup

@devsuitup devsuitup commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

What

A single trigger no longer lands in a dialog (permission prompt, question) that the CLI shows instead of its composer. The two wait values keep their documented meaning:

  • wait: "none" (default) writes now. A prompt written while the CLI is busy is queued by the CLI, which stays legitimate and covers sessions held busy by background agents ((triggers): chain times out after step 0 because the busy flag stays up on an idle session #360). The only new hold is a dialog: while the descriptor reads waiting, nothing is written; at timeout_ms the result is not sent with reason "the CLI reports a dialog open (waiting); nothing was written into it". busy, idle or no readable descriptor write at once, with no settle. A descriptor lost after it read waiting keeps the hold. It closes the hole in waitForIdle, which samples the descriptor only while _cliBusy is true.
  • wait: "idle" gets the chain-step rule (waitForCliIdleAfter, reused) with the trigger's own deadline: held until the descriptor reads idle, busy/waiting/other fail not sent with the matching reason.

Both paths now refuse to write once timeout_ms has passed, descriptor or not, as chains do.

Settle (review finding): an idle stamped before the settle window is ready on the first read (no flat +300 ms), and the single path caps the settle at the time left, so timeout_ms below the settle on an idle session writes. A trustIdleStamp parameter of waitForCliIdleAfter (off by default) carries the first-read rule, and only the single path turns it on: chain steps behave exactly as on main and still pay the settle on an old idle (pinned by a test and a mutation). An idle that never held long enough to settle reports a distinct reason ("the CLI was idle only briefly before the deadline; it never held long enough to settle"), never "never reported idle".

Typed input

Unchanged on purpose: terminal-input is a fire-and-forget IPC fed by the renderer, keystrokes, pastes and drops share one call with no marker, and holding input on waiting would block the keystrokes that answer the dialog. See .ai/contexts/trigger-watcher.md, "Readiness before a single trigger".

Docs

docs/automation.md (wait, "What this costs wait: none", the single-command bullets naming #360 for idle, waited_ms), the context doc, CHANGELOG.

Tests

test/trigger-single-readiness.test.js, 19 tests with a fake descriptor: none on waiting / waiting that closes (no settle) / busy / idle / no descriptor / waiting then lost / session exits; idle on waiting / dialog that closes / busy / status then lost / session exits / old idle immediate / timeout below settle / settle capped / unsettled reason; the deadline guard with and without a descriptor; the helper's first-read rule with and without trustIdleStamp.

Mutations (each made the file fail, then restored): settle cap removed, first-read stamp age removed (and, separately, the shortcut forced on for chains), sessionExited branch removed (idle path, none path, helper), deadline guard removed, none timeout branch removed, lost-descriptor hold removed, none hold bypassed.

Checks: task check exit 0 (3068 pass, 0 fail, lint 0 errors). Changed lines of trigger-watcher.js against the trigger test files under c8: 66 of 66 covered (the CI gate script itself, diff-cover, was not run). Not verified: Node 22 run, a live CLI.

Closes #379

A single trigger was written without consulting the CLI descriptor, so a
dialog (permission prompt, question) swallowed it. It now waits for the
descriptor to read idle, like a chain step, up to its own deadline, and
fails "not sent" with a dialog reason otherwise. Typed input stays as is:
the terminal-input channel cannot tell a person from a driver and its
keystrokes are what answer a dialog.

Closes #379
@devsuitup

Copy link
Copy Markdown
Owner Author

Reviewing 2429515 (adversarial review in progress).

@devsuitup devsuitup left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review at 2429515: changes requested. (B1, reproduced) An idle session fails not sent when timeout_ms is at or below the 300 ms settle window, and every single trigger pays +300 ms. (B2) docs/automation.md still describes wait:none as write-now. (B3) wait:none lost its write-mid-turn capability. Decision: none keeps write-now; its only new hold is a dialog (waiting). idle gets the full readiness rule, and an already-idle descriptor is ready at once. Also needed: a pre-write deadline guard on the single path, tests for settle, session exit and descriptor loss (two mutations survived), coverage for the Node 22 patch gate, and restoring a CR byte in the context doc.

Review of the single-trigger readiness: wait "none" keeps its write-now
meaning (the CLI queues a prompt written while busy, which also covers
sessions held busy by background agents) and holds only while the CLI
shows a dialog. wait "idle" keeps the full readiness rule.

An idle descriptor older than the settle window is ready on the first
read, and the settle is capped at the time left, so a short timeout_ms on
an idle session writes instead of reporting that it never went idle. A
pre-write deadline guard matches the chain's. Tests cover the settle, the
exited-session and lost-descriptor branches, and the deadline guard.

Closes #379
@devsuitup

Copy link
Copy Markdown
Owner Author

Reviewing fc259b3 (adversarial review in progress).

@devsuitup devsuitup left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at fc259b3 (delta from 2429515): 0 blocking. The decided semantics hold: none writes at once and holds only on a dialog, until the deadline. idle uses the readiness rule, the deadline guard has no await before the write, and the #414 post-compact anchor survives (its mutation turns the anchor test red). The merges brought nothing unintended. Being fixed: the new first-read shortcut also applies to chain steps, so it moves to the single-trigger path only and chain behaviour stays as on main. The REASON_IDLE_UNSETTLED wording also covers an idle that kept restarting.

The first-read shortcut (an idle stamped before the settle window is
ready at once) is now a parameter of waitForCliIdleAfter, off by default
and turned on only by the single-trigger path, so a chain step still pays
the settle on a stale idle. The unsettled reason now also covers an idle
that kept restarting.

Closes #379

@devsuitup devsuitup left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at 68bc9bf (delta from fc259b3): 0 blocking. The idle-stamp shortcut is behind trustIdleStamp (default off), which only the single-trigger path passes, so chain steps behave as on main. A new test pins that a chain step still pays the settle on an old idle, and forcing the shortcut on turns it red. REASON_IDLE_UNSETTLED is reworded.

The poll timer of the readiness helpers is unref'd on purpose, so a test
that only awaits it was cancelled by the Node 20/22 runner. The two
helper tests now keep a ref'd interval alive while they await.

Closes #379
@devsuitup
devsuitup merged commit 54c95cb into main Oct 3, 2026
16 of 17 checks passed
@devsuitup
devsuitup deleted the fix/379-single-trigger-dialog branch October 3, 2026 08:50
@devsuitup devsuitup mentioned this pull request Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(triggers): typing into a session is blind to CLI dialogs, and a blocked session tells its driver nothing

1 participant