switchroom 0.14.6 → 0.14.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,6 +64,28 @@ export interface ConfigApprovalHandlerDeps {
64
64
  content: string;
65
65
  }) => Promise<void>;
66
66
  log?: (msg: string) => void;
67
+ /**
68
+ * Single-tap correlation auto-resolve (#1977, security-critical).
69
+ *
70
+ * The durable "🔁 Always allow" flow has the gateway itself call
71
+ * hostd's `config_propose_edit`. hostd then calls BACK to the gateway
72
+ * asking for operator approval — but the operator already tapped the
73
+ * permission card, so posting a SECOND approval card would be a
74
+ * confusing double-tap.
75
+ *
76
+ * When this hook returns `'approve'`, the handler resolves the
77
+ * request immediately WITHOUT posting a card, creating a pending
78
+ * entry, or arming a timer. Returning `null` (the default / no-hook
79
+ * behaviour) falls through to the normal operator-approval card —
80
+ * which is what ANY uncorrelated edit (e.g. an agent-forged config
81
+ * change) gets, preserving the human-in-the-loop control.
82
+ *
83
+ * Forge-resistance lives in the caller's implementation: it must
84
+ * require the rule the diff *adds* to match a rule the gateway just
85
+ * queued, so a forged edit touching any other field finds no
86
+ * correlation and gets a real card.
87
+ */
88
+ tryAutoResolve?: (msg: RequestConfigApprovalMessage) => "approve" | null;
67
89
  }
68
90
 
69
91
  /**
@@ -212,6 +234,20 @@ export async function handleRequestConfigApproval(
212
234
  return;
213
235
  }
214
236
 
237
+ // Single-tap correlation (#1977): if THIS edit was initiated by the
238
+ // gateway itself in response to an operator tap on the permission
239
+ // card, auto-approve without posting a second card. Forge-resistance
240
+ // is the caller's job — it correlates on the rule the diff adds. Any
241
+ // uncorrelated (e.g. agent-forged) edit returns null here and falls
242
+ // through to the real operator-approval card below.
243
+ if (deps.tryAutoResolve?.(msg) === "approve") {
244
+ deps.log?.(
245
+ `config_approval_auto_resolved requestId=${msg.requestId} agent=${msg.agentName} (operator-tapped always-allow correlation)`,
246
+ );
247
+ reply("approve");
248
+ return;
249
+ }
250
+
215
251
  const target = deps.loadTargetChat();
216
252
  if (target === null) {
217
253
  reply(