local-operator-ui 0.19.2 → 0.19.4
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 +95 -3
- package/package.json +2 -2
package/out/main/index.js
CHANGED
|
@@ -1336,6 +1336,81 @@ function withPythonBytecodeCache(env, userDataDir) {
|
|
|
1336
1336
|
if (existing && !insideAppBundle(existing)) return { ...env };
|
|
1337
1337
|
return { ...env, PYTHONPYCACHEPREFIX: pythonBytecodeCacheDir(userDataDir) };
|
|
1338
1338
|
}
|
|
1339
|
+
const BUNDLED_PYTHON_TREE_NAMES = ["python", "python_aarch64"];
|
|
1340
|
+
function bundledPythonTreePaths(resourcesPath) {
|
|
1341
|
+
return BUNDLED_PYTHON_TREE_NAMES.map((name) => path.join(resourcesPath, name));
|
|
1342
|
+
}
|
|
1343
|
+
const FIND = "/usr/bin/find";
|
|
1344
|
+
const CHMOD = "/bin/chmod";
|
|
1345
|
+
const DIRECTORY_ACE = "everyone deny add_file,add_subdirectory";
|
|
1346
|
+
const BYTECODE_ACE = "everyone deny write,append";
|
|
1347
|
+
const MAX_FIND_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
1348
|
+
const PARTIAL_SEAL_NAMED = 3;
|
|
1349
|
+
function describePythonTreeSeal(seal) {
|
|
1350
|
+
if (!seal.supported) {
|
|
1351
|
+
return "Bundled interpreter bytecode seal is macOS-only (there is no code seal to protect elsewhere); the bytecode cache prefix still applies";
|
|
1352
|
+
}
|
|
1353
|
+
const trees = seal.sealed.join(", ") || "(none present)";
|
|
1354
|
+
if (seal.failures.length === 0) {
|
|
1355
|
+
return `Bundled interpreter trees sealed against bytecode writes: ${trees}; ${seal.directories} directory path(s) selected and now refusing new entries, ${seal.bytecodeFiles} bytecode file(s) selected and now refusing rewrites`;
|
|
1356
|
+
}
|
|
1357
|
+
const named = seal.failures.slice(0, PARTIAL_SEAL_NAMED).join("; ");
|
|
1358
|
+
const rest = seal.failures.length - PARTIAL_SEAL_NAMED;
|
|
1359
|
+
return `Bundled interpreter bytecode seal INCOMPLETE: ${trees}; ${seal.directories} directory path(s) and ${seal.bytecodeFiles} bytecode file(s) selected, ${seal.failures.length} refused and left with the access they had - bytecode writes into the bundle are not prevented on every path. First refusals: ${named}${rest > 0 ? ` (+${rest} more)` : ""}`;
|
|
1360
|
+
}
|
|
1361
|
+
function sealPythonInterpreterTrees(trees) {
|
|
1362
|
+
const result = {
|
|
1363
|
+
sealed: [],
|
|
1364
|
+
directories: 0,
|
|
1365
|
+
bytecodeFiles: 0,
|
|
1366
|
+
failures: [],
|
|
1367
|
+
supported: process.platform === "darwin"
|
|
1368
|
+
};
|
|
1369
|
+
if (!result.supported) return result;
|
|
1370
|
+
for (const tree of trees) {
|
|
1371
|
+
let root;
|
|
1372
|
+
try {
|
|
1373
|
+
root = fs.lstatSync(tree);
|
|
1374
|
+
} catch {
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
if (!root.isDirectory()) continue;
|
|
1378
|
+
result.sealed.push(tree);
|
|
1379
|
+
result.directories += applyAce(
|
|
1380
|
+
tree,
|
|
1381
|
+
["-type", "d"],
|
|
1382
|
+
DIRECTORY_ACE,
|
|
1383
|
+
result.failures
|
|
1384
|
+
);
|
|
1385
|
+
result.bytecodeFiles += applyAce(
|
|
1386
|
+
tree,
|
|
1387
|
+
["-type", "f", "-name", "*.pyc"],
|
|
1388
|
+
BYTECODE_ACE,
|
|
1389
|
+
result.failures
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
return result;
|
|
1393
|
+
}
|
|
1394
|
+
function applyAce(tree, selector, ace, failures) {
|
|
1395
|
+
const run = node_child_process.spawnSync(
|
|
1396
|
+
FIND,
|
|
1397
|
+
[tree, ...selector, "-exec", CHMOD, "+a", ace, "{}", "+", "-print0"],
|
|
1398
|
+
{ encoding: "buffer", maxBuffer: MAX_FIND_OUTPUT_BYTES }
|
|
1399
|
+
);
|
|
1400
|
+
if (run.error) {
|
|
1401
|
+
failures.push(`${tree}: ${String(run.error)}`);
|
|
1402
|
+
return 0;
|
|
1403
|
+
}
|
|
1404
|
+
for (const line of String(run.stderr).split("\n")) {
|
|
1405
|
+
if (line.trim().length > 0) failures.push(line.trim());
|
|
1406
|
+
}
|
|
1407
|
+
if (run.status !== 0 && String(run.stderr).trim().length === 0) {
|
|
1408
|
+
failures.push(`${tree}: ${FIND} exited ${run.status} with no explanation`);
|
|
1409
|
+
}
|
|
1410
|
+
let selected = 0;
|
|
1411
|
+
for (const byte of run.stdout) if (byte === 0) selected += 1;
|
|
1412
|
+
return selected;
|
|
1413
|
+
}
|
|
1339
1414
|
var main = { exports: {} };
|
|
1340
1415
|
const version = "16.5.0";
|
|
1341
1416
|
const require$$4 = {
|
|
@@ -2878,9 +2953,15 @@ LOG_FILE="$APP_DATA_DIR/backend-install.log"
|
|
|
2878
2953
|
# in-place update with -67028 errSecCSBadBundleFormat. A file codesign reports
|
|
2879
2954
|
# as *added* can be deleted and the seal comes back; a *modified* one cannot,
|
|
2880
2955
|
# which is why nothing may ship a .pyc at all. The app sets this variable when
|
|
2881
|
-
# it spawns us; defaulting it here
|
|
2882
|
-
#
|
|
2883
|
-
#
|
|
2956
|
+
# it spawns us; defaulting it here keeps a standalone run of this script in
|
|
2957
|
+
# agreement with the app about where bytecode goes. It is not a complete answer:
|
|
2958
|
+
# the app does not spawn every python that runs this interpreter, and a python
|
|
2959
|
+
# started by something else - a shell, a CLI script, an agent - has no prefix at
|
|
2960
|
+
# all (measured: a venv over this tree wrote 25 .pyc into it that way). The half
|
|
2961
|
+
# that covers those is the app's seal on the tree itself
|
|
2962
|
+
# (src/main/python-bytecode-cache.ts), which a standalone run of this script does
|
|
2963
|
+
# not apply. $HOME is the right place for the cache for the same reason the venv
|
|
2964
|
+
# lives there: it is never inside the thing that gets signed and swapped.
|
|
2884
2965
|
: "\${PYTHONPYCACHEPREFIX:=$APP_DATA_DIR/python-bytecode-cache}"
|
|
2885
2966
|
export PYTHONPYCACHEPREFIX
|
|
2886
2967
|
|
|
@@ -3487,6 +3568,17 @@ class BackendInstaller {
|
|
|
3487
3568
|
);
|
|
3488
3569
|
}
|
|
3489
3570
|
this.resourcesPath = process.env.NODE_ENV === "development" ? path.join(process.cwd(), "resources") : path.join(process.resourcesPath);
|
|
3571
|
+
if (electron.app.isPackaged) {
|
|
3572
|
+
const seal = sealPythonInterpreterTrees(
|
|
3573
|
+
bundledPythonTreePaths(process.resourcesPath)
|
|
3574
|
+
);
|
|
3575
|
+
const message = describePythonTreeSeal(seal);
|
|
3576
|
+
if (seal.supported && seal.failures.length > 0) {
|
|
3577
|
+
logger.warn(message, LogFileType.INSTALLER);
|
|
3578
|
+
} else {
|
|
3579
|
+
logger.info(message, LogFileType.INSTALLER);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3490
3582
|
this.pythonPath = this.findPython();
|
|
3491
3583
|
logger.info(
|
|
3492
3584
|
`Backend Installer initialized. Virtual environment path: ${this.venvPath}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-operator-ui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.4",
|
|
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",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"check-types:main": "tsc --noEmit -p tsconfig.node.json",
|
|
68
68
|
"check-types:renderer": "tsc --noEmit -p tsconfig.app.json",
|
|
69
69
|
"check-types": "pnpm run check-types:main && pnpm run check-types:renderer",
|
|
70
|
-
"test:desktop": "node
|
|
70
|
+
"test:desktop": "node scripts/run-desktop-tests.mjs scripts/linux-sandbox.test.mjs scripts/capture-evidence.test.mjs scripts/desktop-contract.test.mjs scripts/transcript-reducer.test.mjs scripts/transcript-paging.test.mjs scripts/provider-state.test.mjs scripts/stale-agent.test.mjs scripts/desktop-renderer-transport.test.mjs scripts/provider-grid-error-copy.test.mjs scripts/settings-time-to-error.test.mjs scripts/backend-error-surfaces.test.mjs scripts/canonical-chat.test.mjs scripts/run-detail-model.test.mjs scripts/chat-title.test.mjs scripts/tool-row.test.mjs scripts/attachment-url.test.mjs scripts/desktop-notifier.test.mjs scripts/python-bytecode-cache.test.mjs scripts/update-robustness.test.mjs scripts/usage-view.test.mjs scripts/usage-container.test.mjs scripts/session-status.test.mjs scripts/window-mode.test.mjs scripts/evidence-manifest.test.mjs scripts/desktop-test-concurrency.test.mjs scripts/run-desktop-tests.test.mjs",
|
|
71
71
|
"check-themes": "node scripts/generate-theme-css.mjs --check && node scripts/contrast-contract.mjs",
|
|
72
72
|
"check-evidence": "node scripts/check-evidence.mjs",
|
|
73
73
|
"check-edit-diffs": "node scripts/check-edit-diffs.mjs",
|