You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 7121ea6
Browse filesBrowse the repository at this point in the historyBrowse files
feat(app): decide User gates in the drawer with one confirmation
The request drawer offers Approve / Reject (Withdraw in the menu) for typed
User gates, previews one decision, confirms once, and reads back the
recorded outcome and its effect on waiting work. Removes the CLI copy hint
and the proposal-level reject/defer controls for decisions; run operator
gates and non-gate requests stay explanation-only.
Also repairs browser smokes that drifted on main: the attention fixture
now carries updated_at for the request-content join, and the operation
fixture uses a relative expiry, a complete Lark delivery receipt, and the
visible-timeline placement of awaiting operations.
Signed-off-by: huangruiteng <huangrt01@163.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
"drawer.gateRequiresHostDescription": "This page cannot approve this protected permission change. Nothing was written by your click.",
199
202
"drawer.goalAutoRun": "Automatic runs for this Goal",
@@ -735,6 +738,23 @@ const en = {
735
738
"proposal.impact.operationConsumed": "The authorization has been consumed. Wait for original external evidence; a retry or lost response must not grant another submission.",
736
739
"proposal.impact.operationUnknown": "Submission may have had an external effect. Reconcile the original operation using its evidence; do not resubmit or treat card delivery as execution completion.",
737
740
"proposal.primary.apply": "Confirm and apply",
741
+
"proposal.decision.approve": "Approve",
742
+
"proposal.decision.reject": "Reject",
743
+
"proposal.decision.cancel": "Withdraw",
744
+
"proposal.impact.gate.approve": "LoopX records your approval. Work waiting on this request continues unless other confirmations are still open.",
745
+
"proposal.impact.gate.reject": "LoopX records your rejection. Work waiting on this request stays paused, and the responsible Agent sees the decision.",
746
+
"proposal.impact.gate.cancel": "The request closes without approving or rejecting it. Work waiting on it stays paused.",
assert.match(page,/callbacks\.onGoalActivationStateChange\?\.\(lifecycleChange\.goalId,lifecycleChange\.next\)/,"Goal lifecycle apply projects the requested state before the server responds");
190
191
assert.match(page,/model\.goals\.find\(\(goal\)=>goal\.goalId===proposal\.goalId\)\?\.activationState/,"Goal lifecycle rollback captures the rendered state instead of assuming the operation inverse");
191
192
assert.match(page,/callbacks\.onGoalActivationStateChange\?\.\(lifecycleChange\.goalId,lifecycleChange\.previous\)/,"Rejected Goal lifecycle apply rolls back the optimistic projection");
192
-
assert.match(page,/if\(applied\.actionKind==="goal\.lifecycle"\)\{\s*voidreconcileStatus\(applied\.goalId\?\[applied\.goalId\]:undefined\)/,"Successful Goal lifecycle apply reconciles the affected Goal without blocking the sidebar");
193
+
assert.match(page,/if\(applied\.actionKind==="goal\.lifecycle"\|\|applied\.actionKind==="gate\.resolve"\)\{\s*voidreconcileStatus\(applied\.goalId\?\[applied\.goalId\]:undefined\)/,"Successful Goal lifecycle or decision apply reconciles the affected Goal without blocking the sidebar");
193
194
assert.match(dashboard,/onReconcileStatus=\{\(options\)=>loadFromUrl\([\s\S]*\{background:true,invalidateGoalIds:options\?\.invalidateGoalIds,readScope:"missing"\}/,"Lifecycle reconciliation uses the non-fatal background status path");
194
195
assert.match(dashboard,/statusRequestCanCommit\(statusRequestFenceRef\.current,request\)/,"A stale background response cannot overwrite a newer optimistic transition");
195
196
assert.match(sidebar,/Trash2/,"Stopped Goals expose a delete icon");
assert.match(workspaceTheme,/workspaceThemeStorageKey="loopx-pw-theme"/,"Theme preference persists across reloads");
287
288
assert.doesNotMatch(dashboard,/isManagerProjectionQuestion/,"Ordinary manager questions do not silently bypass the selected model by matching phrases");
288
289
assert.match(dashboard,/if\(selectedRoute\.agentId==="status-only"\|\|\(!targetGoal&&targetContextId!=="manager"\)\)/,"Projection answers require the explicit status-only route or a missing Goal fallback");
289
-
assert.match(drawer,/t\("drawer\.decisionReview"\)/,"Blocked items preview their decision boundary before any write");
290
+
assert.match(drawer,/role="group"aria-label=\{t\("drawer\.decisionGroup"\)\}/,"Blocked items expose their decisions as one labelled group that previews before any write");
290
291
assert.match(drawer,/consthasProjectedRunActivity=selection\.kind==="run"[\s\S]*selection\.item\.completedSteps>0/,"Session empty-state copy distinguishes projected progress from a truly idle run");
291
292
assert.match(drawer,/t\("drawer\.runRecordProjected"/,"A projected run does not claim that the Agent never started");
292
293
assert.match(drawer,/t\("drawer\.runRecordEmpty"\)/,"A truly empty Session still explains why there is no timeline yet");
0 commit comments