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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ export function recordChatReplySatisfaction(input: {
lastNonSlackWorkAfterSatisfactionAtMs: undefined,
}
: {}),
// A clarification reply is a terminal handoff like a closeout: the turn
// ends waiting on the user's answer, so ending after one is not silence.
...(input.tool === 'send_chat_reply' &&
input.replyPurpose === 'closeout' &&
(input.replyPurpose === 'closeout' ||
input.replyPurpose === 'clarification') &&
satisfiesCurrentTurn
? {
terminalSatisfiedTurnMessageTs: currentTurnMessageTs,
Expand Down
2 changes: 1 addition & 1 deletion apps/worker/src/mcp/roomote-mcp-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1103,7 +1103,7 @@ if (shouldRegisterSlackThreadReplyTool()) {
description:
`${chatReplySurfaceLabel}-visible: posts a lifecycle reply in the originating ${chatReplySurfaceLabel} thread. ` +
`Choose the current ${chatReplySurfaceLabel} turn purpose before writing: ack, progress, closeout, or clarification. ` +
`Use ack for the first visible response when work will continue; use progress only when the message adds new decision-useful state or prevents a 10-minute silence gap; use closeout for the answer, result, blocker, or handoff; use clarification for lightweight non-secret questions. Only closeout is terminal for final task completion; ack, progress, and clarification keep the ${chatReplySurfaceLabel} turn open. ` +
`Use ack for the first visible response when work will continue; use progress only when the message adds new decision-useful state or prevents a 10-minute silence gap; use closeout for the answer, result, blocker, or handoff; use clarification for lightweight non-secret questions. Use closeout to finish a turn with an outcome; a clarification also ends the turn when the next step depends on the user's answer — do not follow it with a separate "waiting on your answer" message. Ack and progress keep the ${chatReplySurfaceLabel} turn open. ` +
`Use it again on later ${chatReplySurfaceLabel} turns when they need another direct reply; an earlier thread reply does not count as the reply for the current turn. ` +
"For routine successful closeouts, focus on the shipped change and any blocker or delivery outcome that changes the user's next step; do not include exact validation commands, passed-check ledgers, or proof-applicability narration unless the user asked or that detail materially changes what they should do next. " +
chatReplyMarkdownGuidance +
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions apps/worker/src/run-task/slack-silence-hook-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,15 @@ function isCloseoutBookkeepingTool(input) {
const toolName =
input && typeof input.tool_name === 'string' ? input.tool_name : '';

// Todo bookkeeping (OpenCode's todowrite/todoread, legacy update_plan) does
// not add user-visible outcome, so it must not stale a terminal reply and
// force a redundant follow-up closeout.
return (
toolName.endsWith('update_plan') ||
toolName.includes('/update_plan') ||
toolName.includes('.update_plan')
toolName.includes('.update_plan') ||
toolName.endsWith('todowrite') ||
toolName.endsWith('todoread')
);
}

Expand Down Expand Up @@ -298,8 +303,14 @@ function hasCurrentTurnTerminalCloseout(state) {
return false;
}

// Clarification is a terminal handoff like closeout: the turn ends waiting
// on the user's answer (kept in sync with the stop hook's policy).
const replyPurpose = trimString(state.replyPurpose);
if (replyPurpose && replyPurpose !== 'closeout') {
if (
replyPurpose &&
replyPurpose !== 'closeout' &&
replyPurpose !== 'clarification'
) {
return false;
}

Expand Down
16 changes: 13 additions & 3 deletions apps/worker/src/run-task/slack-stop-hook-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,24 @@ function isCurrentTurnSatisfied(state) {
return trimString(state && state.satisfiedTurnMessageTs) === currentTurnMessageTs;
}

// Closeout ends the turn with an outcome; clarification ends it waiting on
// the user's answer. Both are visible handoffs, so neither is "silence" —
// blocking on a fresh clarification only forces a redundant "I'm waiting on
// your answer" echo message.
function isTerminalReplyPurpose(replyPurpose) {
return (
!replyPurpose || replyPurpose === 'closeout' || replyPurpose === 'clarification'
);
}

function getLegacyTerminalSatisfaction(state, currentTurnMessageTs) {
const tool = trimString(state && state.tool);
if (tool !== 'send_chat_reply') {
return null;
}

const replyPurpose = trimString(state && state.replyPurpose);
if (replyPurpose && replyPurpose !== 'closeout') {
if (!isTerminalReplyPurpose(replyPurpose)) {
return null;
}

Expand Down Expand Up @@ -248,7 +258,7 @@ function getCurrentTurnTerminalSatisfaction(state) {
}

const replyPurpose = trimString(state && state.replyPurpose);
if (replyPurpose && replyPurpose !== 'closeout') {
if (!isTerminalReplyPurpose(replyPurpose)) {
return null;
}

Expand Down Expand Up @@ -337,7 +347,7 @@ function getTerminalCurrentTurnFailureReason(state) {
}

const replyPurpose = trimString(state && state.replyPurpose);
if (replyPurpose && replyPurpose !== 'closeout') {
if (!isTerminalReplyPurpose(replyPurpose)) {
return 'current_turn_nonterminal_reply';
}

Expand Down