Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
998ffe2
feat(engine): host-armed guardrail prompts + authority-transition notice
asto Oct 30, 2026
41eab46
test(engine): pin armed guardrail faces and transition wiring
asto18089 Oct 30, 2026
04c1f78
fix(engine): scope armed faces, consume transitions once
asto18089 Oct 31, 2026
5c13fc5
fix(engine): keep arming sticky through publishes, preview the planne…
asto18089 Nov 2, 2026
10b28ef
fix(engine): drop clippy-flagged Copy clones
asto18089 Nov 2, 2026
f972884
fix(engine): honor force_prompt in the runtime pump, announce only re…
asto18089 Nov 2, 2026
7e03162
test(protocol): pin legacy ChangeMode payloads
asto18089 Nov 2, 2026
5910426
fix(engine): sync and isolated posture receipts
asto18089 Nov 2, 2026
9b1e1cf
fix(runtime): carry force_prompt on approval cards
asto18089 Nov 2, 2026
b94cc31
refactor(engine): share the armed-face predicate
asto18089 Nov 2, 2026
1785dae
fix(engine): scope host transitions to standing postures
asto18089 Nov 2, 2026
fd066bf
fix(engine): fresh text-fallback tool-call ids
asto18089 Nov 2, 2026
47ada18
fix(engine): ack approval decisions to callers
asto18089 Nov 2, 2026
20b00e0
fix(engine): settle slow approval acks before reporting refusal
asto18089 Nov 2, 2026
28c0a04
fix(engine): restore the standing posture baseline on edit_last_turn
asto18089 Nov 2, 2026
2be1ca3
fix(engine): scope the standing baseline stack to editable user turns
asto18089 Nov 2, 2026
fb4f5e6
fix(engine): pop the standing baseline only when the edit target owns…
asto18089 Nov 2, 2026
d80f1c3
fix(engine): link standing baseline entries to history positions
asto18089 Nov 3, 2026
555ab08
fix(engine): re-anchor standing baseline on history mutations
asto18089 Nov 3, 2026
3cf2cdf
fix(engine): scope child approval cache keys to the operation
asto18089 Nov 3, 2026
8c4e8e7
fix(remote): carry force_prompt on SSE surface
asto18089 Nov 3, 2026
cce7f7b
fix(engine): keep standing baseline entries across mechanical compaction
asto18089 Nov 3, 2026
746c883
fix(tui): localize the remote unknown-approval notice
asto18089 Nov 3, 2026
7c735be
fix(engine): bind child approval keys to the context
asto18089 Nov 3, 2026
5420b3e
fix(engine): keep baseline linkage across summary compaction
asto18089 Nov 3, 2026
fec1193
fix(engine): pin the prune fixture's ambient system prompt
asto18089 Nov 3, 2026
93c3aa2
fix(tui): drop the stale retained_user_messages import from last_round
asto18089 Nov 3, 2026
eea741f
fix(engine): prefer summary origins over positional layout in baselin…
asto18089 Nov 3, 2026
0583e19
fix(tui): answer session grants on the event-carried grouping key
asto18089 Nov 3, 2026
fe190d2
Merge branch 'pinvou3-clean' into feat/host-armed-guardrail-prompts
asto18089 Nov 4, 2026
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
7 changes: 5 additions & 2 deletions crates/core/src/tool_parser.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Legacy parser for text-based tool calls from DeepSeek models.
//!
//! Structured tool-call items are preferred, so the engine no longer invokes
//! this parser. It is kept for reference/debugging.
//! Structured tool-call items are preferred, but the engine still invokes
//! this parser as a fallback when a response carries tool-call markers in
//! plain text. The generated ids restart at 1 for every invocation and are
//! only unique within one parsed response; the engine rewrites them into
//! fresh incarnations before persisting or asking for approval.
//!
//! Some DeepSeek outputs tool calls as text in various formats:
//! ```text
Expand Down
59 changes: 59 additions & 0 deletions crates/protocol/src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ pub enum Op {
approval_mode: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
configured_sandbox_mode: Option<String>,
/// Host-armed interactive guardrail prompts (Pinvou tri-mode safety).
/// Absent leaves the session's current arming unchanged.
#[serde(default, skip_serializing_if = "Option::is_none")]
ask_on_guardrail: Option<bool>,
},

SetDisallowedTools {
Expand Down Expand Up @@ -619,6 +623,7 @@ mod tests {
auto_approve: false,
approval_mode: "bypass".into(),
configured_sandbox_mode: Some("workspace-write".into()),
ask_on_guardrail: Some(true),
},
Op::SetDisallowedTools {
tools: Some(vec!["bash".into()]),
Expand Down Expand Up @@ -760,6 +765,60 @@ mod tests {
}
}

#[test]
fn change_mode_without_ask_on_guardrail_keeps_the_current_arming() {
// Pre-arming hosts emit ChangeMode with no `ask_on_guardrail` key at
// all. The absent field must deserialize to `None` (= keep the
// session's current arming), and an unarmed op must serialize without
// the key: a `serde(default)` or `skip_serializing_if` regression
// would hard-require the field on the wire and break every legacy
// sender (or leak the key into every unarmed op).
let full = serde_json::to_value(Op::ChangeMode {
mode: "operate".into(),
allow_shell: true,
trust_mode: true,
auto_approve: false,
approval_mode: "bypass".into(),
configured_sandbox_mode: Some("workspace-write".into()),
ask_on_guardrail: Some(true),
})
.unwrap();
let legacy = {
let mut value = full.clone();
value
.as_object_mut()
.expect("ChangeMode serializes to an object")
.remove("ask_on_guardrail")
.expect("the armed op carries the field");
value
};
let op: Op = serde_json::from_value(legacy).expect("legacy ChangeMode payload must parse");
match op {
Op::ChangeMode {
ask_on_guardrail, ..
} => assert_eq!(
ask_on_guardrail, None,
"an absent arming key must decode to keep-current, not a default arm"
),
other => panic!("expected ChangeMode, got {other:?}"),
}

let unarmed = serde_json::to_value(Op::ChangeMode {
mode: "agent".into(),
allow_shell: false,
trust_mode: false,
auto_approve: false,
approval_mode: "suggest".into(),
configured_sandbox_mode: None,
ask_on_guardrail: None,
})
.unwrap();
assert!(
unarmed.get("ask_on_guardrail").is_none(),
"an unarmed op must keep the arming key off the wire: {unarmed}"
);
}

#[test]
fn every_variant_round_trips_and_tags_by_kind() {
for op in every_variant() {
Expand Down
3 changes: 2 additions & 1 deletion crates/tui/locales/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Dv",
"AutomationEditorSaturday": "Ds",
"AutomationEditorSunday": "Dg",
"AttachedRootsNotRevertedNote": "Només s'ha restaurat l'workspace primari; les arrels adjuntes del workspace no s'han revertit (els directoris exclosos de la instantània —com vendor/— i els repositoris niats conserven els canvis)."
"AttachedRootsNotRevertedNote": "Només s'ha restaurat l'workspace primari; les arrels adjuntes del workspace no s'han revertit (els directoris exclosos de la instantània —com vendor/— i els repositoris niats conserven els canvis).",
"RemoteApprovalOutcomeUnconfirmed": "El resultat de l'aprovació encara no està confirmat; la targeta roman oberta fins que el motor ho informi."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Fr",
"AutomationEditorSaturday": "Sa",
"AutomationEditorSunday": "So",
"AttachedRootsNotRevertedNote": "Nur der primäre Workspace wurde wiederhergestellt; angehängte Workspace-Roots wurden nicht zurückgerollt (vom Snapshot ausgeschlossene Verzeichnisse — etwa vendor/ — und verschachtelte Repositories behalten ihre Änderungen)."
"AttachedRootsNotRevertedNote": "Nur der primäre Workspace wurde wiederhergestellt; angehängte Workspace-Roots wurden nicht zurückgerollt (vom Snapshot ausgeschlossene Verzeichnisse — etwa vendor/ — und verschachtelte Repositories behalten ihre Änderungen).",
"RemoteApprovalOutcomeUnconfirmed": "Das Genehmigungsergebnis ist noch nicht bestätigt; die Karte bleibt offen, bis die Engine berichtet."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Fri",
"AutomationEditorSaturday": "Sat",
"AutomationEditorSunday": "Sun",
"AttachedRootsNotRevertedNote": "Only the primary workspace was reverted; attached workspace roots were not rolled back (roots excluded from the snapshot — such as vendor/ — and nested repositories keep their changes)."
"AttachedRootsNotRevertedNote": "Only the primary workspace was reverted; attached workspace roots were not rolled back (roots excluded from the snapshot — such as vendor/ — and nested repositories keep their changes).",
"RemoteApprovalOutcomeUnconfirmed": "Approval outcome not confirmed yet; the card stays open until the engine reports."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/es-419.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Vie",
"AutomationEditorSaturday": "Sáb",
"AutomationEditorSunday": "Dom",
"AttachedRootsNotRevertedNote": "Solo se revirtió el workspace primario; las raíces adjuntas del workspace no se revirtieron (los directorios excluidos de la instantánea —como vendor/— y los repositorios anidados conservan sus cambios)."
"AttachedRootsNotRevertedNote": "Solo se revirtió el workspace primario; las raíces adjuntas del workspace no se revirtieron (los directorios excluidos de la instantánea —como vendor/— y los repositorios anidados conservan sus cambios).",
"RemoteApprovalOutcomeUnconfirmed": "Resultado de aprobación aún sin confirmar; la tarjeta permanece abierta hasta que el motor reporte."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Ven",
"AutomationEditorSaturday": "Sam",
"AutomationEditorSunday": "Dim",
"AttachedRootsNotRevertedNote": "Seul l'espace de travail principal a été restauré ; les racines attachées n'ont pas été annulées (les répertoires exclus de l'instantané — comme vendor/ — et les dépôts imbriqués conservent leurs modifications)."
"AttachedRootsNotRevertedNote": "Seul l'espace de travail principal a été restauré ; les racines attachées n'ont pas été annulées (les répertoires exclus de l'instantané — comme vendor/ — et les dépôts imbriqués conservent leurs modifications).",
"RemoteApprovalOutcomeUnconfirmed": "Résultat d'approbation pas encore confirmé ; la carte reste ouverte jusqu'au rapport du moteur."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "शुक्र",
"AutomationEditorSaturday": "शनि",
"AutomationEditorSunday": "रवि",
"AttachedRootsNotRevertedNote": "केवल प्राथमिक वर्कस्पेस पूर्ववत हुआ; संलग्न वर्कस्पेस रूट वापस नहीं लपेटे गए (स्नैपशॉट से बाहर रखे गए डायरेक्टरीज़ — जैसे vendor/ — और नेस्टेड रिपॉज़िटरी अपने परिवर्तन बनाए रखती हैं)।"
"AttachedRootsNotRevertedNote": "केवल प्राथमिक वर्कस्पेस पूर्ववत हुआ; संलग्न वर्कस्पेस रूट वापस नहीं लपेटे गए (स्नैपशॉट से बाहर रखे गए डायरेक्टरीज़ — जैसे vendor/ — और नेस्टेड रिपॉज़िटरी अपने परिवर्तन बनाए रखती हैं)।",
"RemoteApprovalOutcomeUnconfirmed": "अनुमोदन का परिणाम अभी पुष्ट नहीं हुआ; इंजन के रिपोर्ट करने तक कार्ड खुला रहेगा।"
}
3 changes: 2 additions & 1 deletion crates/tui/locales/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Jum",
"AutomationEditorSaturday": "Sab",
"AutomationEditorSunday": "Min",
"AttachedRootsNotRevertedNote": "Hanya workspace utama yang dikembalikan; root workspace terlampir tidak di-rollbalik (direktori yang dikecualikan dari snapshot — seperti vendor/ — dan repositori bersarang mempertahankan perubahannya)."
"AttachedRootsNotRevertedNote": "Hanya workspace utama yang dikembalikan; root workspace terlampir tidak di-rollbalik (direktori yang dikecualikan dari snapshot — seperti vendor/ — dan repositori bersarang mempertahankan perubahannya).",
"RemoteApprovalOutcomeUnconfirmed": "Hasil persetujuan belum dikonfirmasi; kartu tetap terbuka sampai engine melaporkan."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "金",
"AutomationEditorSaturday": "土",
"AutomationEditorSunday": "日",
"AttachedRootsNotRevertedNote": "プライマリワークスペースのみ復元されました。アタッチされたワークスペースルートはロールバックされていません(スナップショットから除外されるディレクトリ(vendor/ など)やネストされたリポジトリは変更が残ります)。"
"AttachedRootsNotRevertedNote": "プライマリワークスペースのみ復元されました。アタッチされたワークスペースルートはロールバックされていません(スナップショットから除外されるディレクトリ(vendor/ など)やネストされたリポジトリは変更が残ります)。",
"RemoteApprovalOutcomeUnconfirmed": "承認結果はまだ確定していません。エンジンが報告するまでカードは開いたままです。"
}
3 changes: 2 additions & 1 deletion crates/tui/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "금",
"AutomationEditorSaturday": "토",
"AutomationEditorSunday": "일",
"AttachedRootsNotRevertedNote": "기본 워크스페이스만 되돌렸습니다; 첨부된 워크스페이스 루트는 롤백되지 않았습니다(스냅샷에서 제외되는 디렉터리 — 예: vendor/ — 및 중첩 저장소는 변경 사항이 유지됩니다)."
"AttachedRootsNotRevertedNote": "기본 워크스페이스만 되돌렸습니다; 첨부된 워크스페이스 루트는 롤백되지 않았습니다(스냅샷에서 제외되는 디렉터리 — 예: vendor/ — 및 중첩 저장소는 변경 사항이 유지됩니다).",
"RemoteApprovalOutcomeUnconfirmed": "승인 결과가 아직 확정되지 않았습니다. 엔진이 보고할 때까지 카드는 열려 있습니다."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Sex",
"AutomationEditorSaturday": "Sáb",
"AutomationEditorSunday": "Dom",
"AttachedRootsNotRevertedNote": "Apenas o workspace primário foi revertido; as raízes anexadas do workspace não foram revertidas (diretórios excluídos do snapshot — como vendor/ — e repositórios aninhados mantêm suas alterações)."
"AttachedRootsNotRevertedNote": "Apenas o workspace primário foi revertido; as raízes anexadas do workspace não foram revertidas (diretórios excluídos do snapshot — como vendor/ — e repositórios aninhados mantêm suas alterações).",
"RemoteApprovalOutcomeUnconfirmed": "Resultado da aprovação ainda não confirmado; o cartão permanece aberto até o engine reportar."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Пт",
"AutomationEditorSaturday": "Сб",
"AutomationEditorSunday": "Вс",
"AttachedRootsNotRevertedNote": "Откачено только основное рабочее пространство; прикреплённые корни не откатывались (исключённые из снимка каталоги — например vendor/ — и вложенные репозитории сохраняют свои изменения)."
"AttachedRootsNotRevertedNote": "Откачено только основное рабочее пространство; прикреплённые корни не откатывались (исключённые из снимка каталоги — например vendor/ — и вложенные репозитории сохраняют свои изменения).",
"RemoteApprovalOutcomeUnconfirmed": "Результат одобрения ещё не подтверждён; карточка остаётся открытой, пока движок не сообщит."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "Пт",
"AutomationEditorSaturday": "Сб",
"AutomationEditorSunday": "Нд",
"AttachedRootsNotRevertedNote": "Відновлено лише основний робочий простір; долучені корені не відкатувалися (виключені зі знімка каталоги — як-от vendor/ — та вкладені репозиторії зберігають свої зміни)."
"AttachedRootsNotRevertedNote": "Відновлено лише основний робочий простір; долучені корені не відкатувалися (виключені зі знімка каталоги — як-от vendor/ — та вкладені репозиторії зберігають свої зміни).",
"RemoteApprovalOutcomeUnconfirmed": "Результат схвалення ще не підтверджено; картка залишається відкритою, поки рушій не повідомить."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "T6",
"AutomationEditorSaturday": "T7",
"AutomationEditorSunday": "CN",
"AttachedRootsNotRevertedNote": "Chỉ vùng làm việc chính được khôi phục; các root workspace đính kèm không được hoàn tác (các thư mục bị loại khỏi bản chụp — như vendor/ — và các kho lồng nhau giữ nguyên thay đổi)."
"AttachedRootsNotRevertedNote": "Chỉ vùng làm việc chính được khôi phục; các root workspace đính kèm không được hoàn tác (các thư mục bị loại khỏi bản chụp — như vendor/ — và các kho lồng nhau giữ nguyên thay đổi).",
"RemoteApprovalOutcomeUnconfirmed": "Kết quả phê duyệt chưa được xác nhận; thẻ vẫn mở cho đến khi engine báo cáo."
}
3 changes: 2 additions & 1 deletion crates/tui/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "周五",
"AutomationEditorSaturday": "周六",
"AutomationEditorSunday": "周日",
"AttachedRootsNotRevertedNote": "仅回退了主工作区;附加的工作区根未回滚(快照排除的目录(如 vendor/)与嵌套仓库会保留其更改)。"
"AttachedRootsNotRevertedNote": "仅回退了主工作区;附加的工作区根未回滚(快照排除的目录(如 vendor/)与嵌套仓库会保留其更改)。",
"RemoteApprovalOutcomeUnconfirmed": "审批结果尚未确认;在引擎回报前卡片保持打开。"
}
3 changes: 2 additions & 1 deletion crates/tui/locales/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,6 @@
"AutomationEditorFriday": "週五",
"AutomationEditorSaturday": "週六",
"AutomationEditorSunday": "週日",
"AttachedRootsNotRevertedNote": "僅回退了主工作區;附加的工作區根未回滾(快照排除的目錄(如 vendor/)與嵌套倉庫會保留其更改)。"
"AttachedRootsNotRevertedNote": "僅回退了主工作區;附加的工作區根未回滾(快照排除的目錄(如 vendor/)與嵌套倉庫會保留其更改)。",
"RemoteApprovalOutcomeUnconfirmed": "審批結果尚未確認;在引擎回報前卡片保持開啟。"
}
5 changes: 5 additions & 0 deletions crates/tui/src/acp_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,13 +702,17 @@ fn prepare_acp_tool_admission(
let approval_mode = crate::tui::approval::ApprovalMode::Suggest;
let workspace = registry.context().workspace.as_path();

// ACP admissions carry no arming surface: the Suggest posture here always
// prompts through RequestPermission, so the guardrail deny face stays
// hard (`ask_on_guardrail: false`).
let typed_rule = exec_shell_ask_rule_decision_for_policy(
&config.exec_policy_engine,
&call.name,
&prepared.input,
workspace,
&registry.context().workspace_roots,
approval_mode,
false,
)
.or_else(|| {
file_tool_ask_rule_decision_for_policy(
Expand All @@ -718,6 +722,7 @@ fn prepare_acp_tool_admission(
workspace,
&registry.context().workspace_roots,
approval_mode,
false,
)
});
match typed_rule {
Expand Down
Loading
Loading