local-operator-ui 0.25.7 → 0.25.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.
- package/out/main/index.js +62 -36
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -8125,9 +8125,6 @@ function receiptFor(entry, state, now) {
|
|
|
8125
8125
|
expiresAt: now + ACCESS_RESULT_TTL_MS
|
|
8126
8126
|
};
|
|
8127
8127
|
}
|
|
8128
|
-
function receiptForRequester(results, origin, requester, now) {
|
|
8129
|
-
return Object.values(cleanResults(results, now)).filter((receipt) => receipt.origin === origin && receipt.requester === requester).sort((a, b) => b.decidedAt - a.decidedAt)[0];
|
|
8130
|
-
}
|
|
8131
8128
|
const APPROVALS_FILENAME = "approvals.json";
|
|
8132
8129
|
function emptyStore() {
|
|
8133
8130
|
return {
|
|
@@ -8244,17 +8241,13 @@ class ApprovalStore {
|
|
|
8244
8241
|
* `request_access` → tells the user → `await_access`.
|
|
8245
8242
|
*/
|
|
8246
8243
|
ensureTopLevelAccess(url, requester) {
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
if (grant) {
|
|
8255
|
-
delete this.onceGrants[url.origin];
|
|
8256
|
-
this.persistIfChanged();
|
|
8257
|
-
return { allowed: true, viaOnceGrant: true };
|
|
8244
|
+
const authority = this.liveAuthority(url, requester);
|
|
8245
|
+
if (authority) {
|
|
8246
|
+
if (authority.viaOnceGrant) {
|
|
8247
|
+
delete this.onceGrants[url.origin];
|
|
8248
|
+
this.persistIfChanged();
|
|
8249
|
+
}
|
|
8250
|
+
return { allowed: true, viaOnceGrant: authority.viaOnceGrant };
|
|
8258
8251
|
}
|
|
8259
8252
|
throw new BridgeCommandError(
|
|
8260
8253
|
"origin_not_allowed",
|
|
@@ -8266,6 +8259,35 @@ class ApprovalStore {
|
|
|
8266
8259
|
}
|
|
8267
8260
|
);
|
|
8268
8261
|
}
|
|
8262
|
+
/**
|
|
8263
|
+
* The live authority for one origin and requester, WITHOUT consuming it.
|
|
8264
|
+
*
|
|
8265
|
+
* THE SINGLE SOURCE OF TRUTH, and the fix for a shipped consent defect: the
|
|
8266
|
+
* gate and `request_access` used to answer from two different places —
|
|
8267
|
+
* `ensureTopLevelAccess` from the grant, `requestAccess` from a resolved
|
|
8268
|
+
* RECEIPT — and a receipt outlives the grant it was written for (its 15-minute
|
|
8269
|
+
* `ACCESS_RESULT_TTL_MS` against the 10-minute once-grant TTL), so
|
|
8270
|
+
* `request_access` answered `allowed` for a navigation the very next `open`
|
|
8271
|
+
* refused with `origin_not_allowed`, and raised no band while doing it: the
|
|
8272
|
+
* request was dropped without ever being put back to the user. Same pair
|
|
8273
|
+
* after a revocation, where the receipt outlives the grant by however long it
|
|
8274
|
+
* had left to run.
|
|
8275
|
+
*
|
|
8276
|
+
* `originAllowed` is the durable verdict plus the session grants; the second
|
|
8277
|
+
* term is an UNSPENT `once` grant bound to this requester, which the gate
|
|
8278
|
+
* consumes and this does not. Everything else is not authority: a spent grant,
|
|
8279
|
+
* a revoked one, a receipt for either.
|
|
8280
|
+
*/
|
|
8281
|
+
liveAuthority(url, requester) {
|
|
8282
|
+
if (this.originAllowed(url)) return { viaOnceGrant: false };
|
|
8283
|
+
const grant = consumableGrant(
|
|
8284
|
+
this.onceGrants,
|
|
8285
|
+
url.origin,
|
|
8286
|
+
requester,
|
|
8287
|
+
this.now()
|
|
8288
|
+
);
|
|
8289
|
+
return grant ? { viaOnceGrant: true } : null;
|
|
8290
|
+
}
|
|
8269
8291
|
/** Admission is captured once, but revocation remains authoritative while an
|
|
8270
8292
|
* asynchronous navigation is in flight. A receipt is never transferable. */
|
|
8271
8293
|
admit(url, requester) {
|
|
@@ -8318,29 +8340,24 @@ class ApprovalStore {
|
|
|
8318
8340
|
* The rules are `vendor/driver/access-flow.ts`'s, applied to the queue: a repeat
|
|
8319
8341
|
* request for the same origin by the same requester is idempotent with its
|
|
8320
8342
|
* original TTL, a request for a different origin REPLACES the live one (with
|
|
8321
|
-
* a tombstone for the displaced requester), and a
|
|
8322
|
-
* "denied" without re-prompting
|
|
8343
|
+
* a tombstone for the displaced requester), and a DURABLE denial answers
|
|
8344
|
+
* "denied" without re-prompting, because that is what the button promises —
|
|
8345
|
+
* "Don't allow" says the agent stops asking about this site until the denial is
|
|
8346
|
+
* revoked.
|
|
8323
8347
|
*/
|
|
8324
8348
|
requestAccess(rawUrl, requester, kind = "async", commandId) {
|
|
8325
8349
|
const url = safeHttpUrl(rawUrl);
|
|
8326
8350
|
const now = this.now();
|
|
8327
|
-
if (this.
|
|
8351
|
+
if (this.liveAuthority(url, requester)) {
|
|
8328
8352
|
return { origin: url.origin, state: "allowed" };
|
|
8329
8353
|
}
|
|
8354
|
+
if (this.refusalReason(url) === "denied") {
|
|
8355
|
+
return { origin: url.origin, state: "denied" };
|
|
8356
|
+
}
|
|
8330
8357
|
const existing = findPending(this.queue, url.origin, requester, kind, now);
|
|
8331
8358
|
if (existing) {
|
|
8332
8359
|
return this.entryResponse(existing, "pending");
|
|
8333
8360
|
}
|
|
8334
|
-
const receipt = receiptForRequester(
|
|
8335
|
-
this.results,
|
|
8336
|
-
url.origin,
|
|
8337
|
-
requester,
|
|
8338
|
-
now
|
|
8339
|
-
);
|
|
8340
|
-
if (receipt && receipt.state !== "superseded") {
|
|
8341
|
-
const state = receipt.state === "denied" ? "denied" : "allowed";
|
|
8342
|
-
return { origin: url.origin, state, entry_id: receipt.entryId };
|
|
8343
|
-
}
|
|
8344
8361
|
this.sweep(now);
|
|
8345
8362
|
if (liveQueue(this.queue, now).length >= ACCESS_QUEUE_CAP) {
|
|
8346
8363
|
throw new BridgeCommandError(
|
|
@@ -8396,8 +8413,21 @@ class ApprovalStore {
|
|
|
8396
8413
|
hostname: entry.displayAuthority,
|
|
8397
8414
|
requester: entry.requester,
|
|
8398
8415
|
requestedAt: entry.requestedAt,
|
|
8399
|
-
expiresAt: entry.expiresAt
|
|
8400
|
-
|
|
8416
|
+
expiresAt: entry.expiresAt
|
|
8417
|
+
/*
|
|
8418
|
+
* NO `decision` TERM, and the removal is the point. The vendored
|
|
8419
|
+
* `accessState()` takes an optional decision so that a LIVE record can read
|
|
8420
|
+
* `allowed`/`denied` before the grant lands, and this host fed it from the
|
|
8421
|
+
* decision receipts — but every arm that writes a receipt removes its entry
|
|
8422
|
+
* from the queue FIRST (`respond` filters, `cancelAccess` splices), entry ids
|
|
8423
|
+
* are minted per entry and nothing here is restored from disk, so a record
|
|
8424
|
+
* with a decision can never be the record this reads. It was unreachable code
|
|
8425
|
+
* carrying the residue of the defect this fix closes, and a reader would
|
|
8426
|
+
* reasonably infer from it that a receipt can still answer. It cannot:
|
|
8427
|
+
* `requestAccess` answers from `liveAuthority` and from a durable verdict, and
|
|
8428
|
+
* the receipts are the store's record of what was decided (swept by TTL)
|
|
8429
|
+
* rather than a reader's source.
|
|
8430
|
+
*/
|
|
8401
8431
|
} : void 0,
|
|
8402
8432
|
this.tombstones,
|
|
8403
8433
|
this.originAllowed(url),
|
|
@@ -8406,6 +8436,9 @@ class ApprovalStore {
|
|
|
8406
8436
|
requester,
|
|
8407
8437
|
now
|
|
8408
8438
|
);
|
|
8439
|
+
if (state === "none" && this.refusalReason(url) === "denied") {
|
|
8440
|
+
return { origin: url.origin, state: "denied" };
|
|
8441
|
+
}
|
|
8409
8442
|
return {
|
|
8410
8443
|
origin: url.origin,
|
|
8411
8444
|
state,
|
|
@@ -8668,13 +8701,6 @@ class ApprovalStore {
|
|
|
8668
8701
|
this.onChanged();
|
|
8669
8702
|
}
|
|
8670
8703
|
// ---- internals -----------------------------------------------------------
|
|
8671
|
-
decisionFor(entry) {
|
|
8672
|
-
const receipt = this.results[resultKey(entry.entryId, entry.requester)];
|
|
8673
|
-
if (!receipt) return void 0;
|
|
8674
|
-
if (receipt.state === "denied") return "deny";
|
|
8675
|
-
if (receipt.state === "allowed") return "site";
|
|
8676
|
-
return void 0;
|
|
8677
|
-
}
|
|
8678
8704
|
/** Replace-don't-queue: the live entry for another origin (or another
|
|
8679
8705
|
* requester) is displaced, and its requester gets a tombstone so its next poll
|
|
8680
8706
|
* reads "superseded" rather than the neutral "none". */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-operator-ui",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.8",
|
|
4
4
|
"productName": "Local Operator",
|
|
5
5
|
"description": "User interface for AI agent assistants that run Python code safely on your device through an intuitive chat interface. Supports local and cloud LLM models with built-in security features.",
|
|
6
6
|
"main": "./out/main/index.js",
|