local-commander-agent 0.3.3 → 0.3.5
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/README.md +78 -22
- package/bin/localcommander.mjs +41 -11
- package/package.json +7 -2
- package/runtime/JobHost.exe +0 -0
- package/runtime/LocalCommanderAgent.exe +0 -0
- package/runtime/OwnerBridge.exe +0 -0
- package/runtime/agent.mjs +562 -323
package/runtime/agent.mjs
CHANGED
|
@@ -19,8 +19,8 @@ var __commonJS = (cb, mod) => function __require2() {
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
var __export = (target, all) => {
|
|
22
|
-
for (var
|
|
23
|
-
__defProp(target,
|
|
22
|
+
for (var name2 in all)
|
|
23
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
24
24
|
};
|
|
25
25
|
var __copyProps = (to, from, except, desc) => {
|
|
26
26
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -2121,9 +2121,9 @@ var require_extension = __commonJS({
|
|
|
2121
2121
|
"node_modules/ws/lib/extension.js"(exports, module) {
|
|
2122
2122
|
"use strict";
|
|
2123
2123
|
var { tokenChars } = require_validation();
|
|
2124
|
-
function push(dest,
|
|
2125
|
-
if (dest[
|
|
2126
|
-
else dest[
|
|
2124
|
+
function push(dest, name2, elem) {
|
|
2125
|
+
if (dest[name2] === void 0) dest[name2] = [elem];
|
|
2126
|
+
else dest[name2].push(elem);
|
|
2127
2127
|
}
|
|
2128
2128
|
function parse3(header) {
|
|
2129
2129
|
const offers = /* @__PURE__ */ Object.create(null);
|
|
@@ -2149,12 +2149,12 @@ var require_extension = __commonJS({
|
|
|
2149
2149
|
throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
2150
2150
|
}
|
|
2151
2151
|
if (end === -1) end = i;
|
|
2152
|
-
const
|
|
2152
|
+
const name2 = header.slice(start, end);
|
|
2153
2153
|
if (code === 44) {
|
|
2154
|
-
push(offers,
|
|
2154
|
+
push(offers, name2, params);
|
|
2155
2155
|
params = /* @__PURE__ */ Object.create(null);
|
|
2156
2156
|
} else {
|
|
2157
|
-
extensionName =
|
|
2157
|
+
extensionName = name2;
|
|
2158
2158
|
}
|
|
2159
2159
|
start = end = -1;
|
|
2160
2160
|
} else {
|
|
@@ -2278,7 +2278,7 @@ var require_websocket = __commonJS({
|
|
|
2278
2278
|
var http = __require("http");
|
|
2279
2279
|
var net = __require("net");
|
|
2280
2280
|
var tls = __require("tls");
|
|
2281
|
-
var { randomBytes: randomBytes4, createHash:
|
|
2281
|
+
var { randomBytes: randomBytes4, createHash: createHash10 } = __require("crypto");
|
|
2282
2282
|
var { Duplex, Readable } = __require("stream");
|
|
2283
2283
|
var { URL: URL2 } = __require("url");
|
|
2284
2284
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2959,7 +2959,7 @@ var require_websocket = __commonJS({
|
|
|
2959
2959
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2960
2960
|
return;
|
|
2961
2961
|
}
|
|
2962
|
-
const digest4 =
|
|
2962
|
+
const digest4 = createHash10("sha1").update(key + GUID).digest("base64");
|
|
2963
2963
|
if (res.headers["sec-websocket-accept"] !== digest4) {
|
|
2964
2964
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2965
2965
|
return;
|
|
@@ -3328,7 +3328,7 @@ var require_websocket_server = __commonJS({
|
|
|
3328
3328
|
var EventEmitter = __require("events");
|
|
3329
3329
|
var http = __require("http");
|
|
3330
3330
|
var { Duplex } = __require("stream");
|
|
3331
|
-
var { createHash:
|
|
3331
|
+
var { createHash: createHash10 } = __require("crypto");
|
|
3332
3332
|
var extension2 = require_extension();
|
|
3333
3333
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3334
3334
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3635,7 +3635,7 @@ var require_websocket_server = __commonJS({
|
|
|
3635
3635
|
);
|
|
3636
3636
|
}
|
|
3637
3637
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3638
|
-
const digest4 =
|
|
3638
|
+
const digest4 = createHash10("sha1").update(key + GUID).digest("base64");
|
|
3639
3639
|
const headers = [
|
|
3640
3640
|
"HTTP/1.1 101 Switching Protocols",
|
|
3641
3641
|
"Upgrade: websocket",
|
|
@@ -3723,7 +3723,7 @@ var require_websocket_server = __commonJS({
|
|
|
3723
3723
|
});
|
|
3724
3724
|
|
|
3725
3725
|
// agent/src/main.ts
|
|
3726
|
-
import
|
|
3726
|
+
import path12 from "node:path";
|
|
3727
3727
|
import os3 from "node:os";
|
|
3728
3728
|
import { mkdir as mkdir5, open as open6, readFile as readFile5, rm as rm4 } from "node:fs/promises";
|
|
3729
3729
|
import { fileURLToPath } from "node:url";
|
|
@@ -4674,10 +4674,10 @@ function mergeDefs(...defs) {
|
|
|
4674
4674
|
function cloneDef(schema) {
|
|
4675
4675
|
return mergeDefs(schema._zod.def);
|
|
4676
4676
|
}
|
|
4677
|
-
function getElementAtPath(obj,
|
|
4678
|
-
if (!
|
|
4677
|
+
function getElementAtPath(obj, path13) {
|
|
4678
|
+
if (!path13)
|
|
4679
4679
|
return obj;
|
|
4680
|
-
return
|
|
4680
|
+
return path13.reduce((acc, key) => acc?.[key], obj);
|
|
4681
4681
|
}
|
|
4682
4682
|
function promiseAllObject(promisesObj) {
|
|
4683
4683
|
const keys = Object.keys(promisesObj);
|
|
@@ -4976,12 +4976,12 @@ function merge(a, b) {
|
|
|
4976
4976
|
});
|
|
4977
4977
|
return clone(a, def);
|
|
4978
4978
|
}
|
|
4979
|
-
function partial(Class2, schema, mask,
|
|
4979
|
+
function partial(Class2, schema, mask, name2 = "partial") {
|
|
4980
4980
|
const currDef = schema._zod.def;
|
|
4981
4981
|
const checks = currDef.checks;
|
|
4982
4982
|
const hasChecks = checks && checks.length > 0;
|
|
4983
4983
|
if (hasChecks) {
|
|
4984
|
-
throw new Error(`.${
|
|
4984
|
+
throw new Error(`.${name2}() cannot be used on object schemas containing refinements`);
|
|
4985
4985
|
}
|
|
4986
4986
|
const selected = mask ? new Set(maskedKeys(schema, mask)) : void 0;
|
|
4987
4987
|
const newShape = {};
|
|
@@ -5017,11 +5017,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
5017
5017
|
}
|
|
5018
5018
|
return false;
|
|
5019
5019
|
}
|
|
5020
|
-
function prefixIssues(
|
|
5020
|
+
function prefixIssues(path13, issues) {
|
|
5021
5021
|
return issues.map((iss) => {
|
|
5022
5022
|
var _a3;
|
|
5023
5023
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
5024
|
-
iss.path.unshift(
|
|
5024
|
+
iss.path.unshift(path13);
|
|
5025
5025
|
return iss;
|
|
5026
5026
|
});
|
|
5027
5027
|
}
|
|
@@ -5309,7 +5309,7 @@ function newError(Definition) {
|
|
|
5309
5309
|
return new Definition();
|
|
5310
5310
|
}
|
|
5311
5311
|
// @__NO_SIDE_EFFECTS__
|
|
5312
|
-
function $constructor(
|
|
5312
|
+
function $constructor(name2, initializer3, proto, params) {
|
|
5313
5313
|
const zodProto = {};
|
|
5314
5314
|
function Internals(def) {
|
|
5315
5315
|
this.def = def;
|
|
@@ -5327,10 +5327,10 @@ function $constructor(name, initializer3, proto, params) {
|
|
|
5327
5327
|
} finally {
|
|
5328
5328
|
_zodDesc.value = void 0;
|
|
5329
5329
|
}
|
|
5330
|
-
} else if (inst._zod.traits.has(
|
|
5330
|
+
} else if (inst._zod.traits.has(name2)) {
|
|
5331
5331
|
return;
|
|
5332
5332
|
}
|
|
5333
|
-
inst._zod.traits.add(
|
|
5333
|
+
inst._zod.traits.add(name2);
|
|
5334
5334
|
initializer3(inst, def);
|
|
5335
5335
|
if (initialized) {
|
|
5336
5336
|
const own2 = Object.getPrototypeOf(inst);
|
|
@@ -5356,7 +5356,7 @@ function $constructor(name, initializer3, proto, params) {
|
|
|
5356
5356
|
const Parent = params?.Parent ?? Object;
|
|
5357
5357
|
class Definition extends Parent {
|
|
5358
5358
|
}
|
|
5359
|
-
Object.defineProperty(Definition, "name", { value:
|
|
5359
|
+
Object.defineProperty(Definition, "name", { value: name2 });
|
|
5360
5360
|
function _(def) {
|
|
5361
5361
|
const inst = params?.Parent ? newError(Definition) : this;
|
|
5362
5362
|
init(inst, def);
|
|
@@ -5377,10 +5377,10 @@ function $constructor(name, initializer3, proto, params) {
|
|
|
5377
5377
|
value: (inst) => {
|
|
5378
5378
|
if (params?.Parent && inst instanceof params.Parent)
|
|
5379
5379
|
return true;
|
|
5380
|
-
return inst?._zod?.traits?.has(
|
|
5380
|
+
return inst?._zod?.traits?.has(name2);
|
|
5381
5381
|
}
|
|
5382
5382
|
});
|
|
5383
|
-
Object.defineProperty(_, "name", { value:
|
|
5383
|
+
Object.defineProperty(_, "name", { value: name2 });
|
|
5384
5384
|
return _;
|
|
5385
5385
|
}
|
|
5386
5386
|
var $brand = /* @__PURE__ */ Symbol("zod_brand");
|
|
@@ -5390,8 +5390,8 @@ var $ZodAsyncError = class extends Error {
|
|
|
5390
5390
|
}
|
|
5391
5391
|
};
|
|
5392
5392
|
var $ZodEncodeError = class extends Error {
|
|
5393
|
-
constructor(
|
|
5394
|
-
super(`Encountered unidirectional transform during encode: ${
|
|
5393
|
+
constructor(name2) {
|
|
5394
|
+
super(`Encountered unidirectional transform during encode: ${name2}`);
|
|
5395
5395
|
this.name = "ZodEncodeError";
|
|
5396
5396
|
}
|
|
5397
5397
|
};
|
|
@@ -5471,16 +5471,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5471
5471
|
}
|
|
5472
5472
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
5473
5473
|
const fieldErrors = { _errors: [] };
|
|
5474
|
-
const processError = (error63,
|
|
5474
|
+
const processError = (error63, path13 = []) => {
|
|
5475
5475
|
for (const issue2 of error63.issues) {
|
|
5476
5476
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
5477
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
5477
|
+
issue2.errors.map((issues) => processError({ issues }, [...path13, ...issue2.path]));
|
|
5478
5478
|
} else if (issue2.code === "invalid_key") {
|
|
5479
|
-
processError({ issues: issue2.issues }, [...
|
|
5479
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5480
5480
|
} else if (issue2.code === "invalid_element") {
|
|
5481
|
-
processError({ issues: issue2.issues }, [...
|
|
5481
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5482
5482
|
} else {
|
|
5483
|
-
const fullpath = [...
|
|
5483
|
+
const fullpath = [...path13, ...issue2.path];
|
|
5484
5484
|
if (fullpath.length === 0) {
|
|
5485
5485
|
fieldErrors._errors.push(mapper(issue2));
|
|
5486
5486
|
} else {
|
|
@@ -5519,17 +5519,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5519
5519
|
}
|
|
5520
5520
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
5521
5521
|
const result2 = { errors: [] };
|
|
5522
|
-
const processError = (error63,
|
|
5522
|
+
const processError = (error63, path13 = []) => {
|
|
5523
5523
|
var _a3;
|
|
5524
5524
|
for (const issue2 of error63.issues) {
|
|
5525
5525
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
5526
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
5526
|
+
issue2.errors.map((issues) => processError({ issues }, [...path13, ...issue2.path]));
|
|
5527
5527
|
} else if (issue2.code === "invalid_key") {
|
|
5528
|
-
processError({ issues: issue2.issues }, [...
|
|
5528
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5529
5529
|
} else if (issue2.code === "invalid_element") {
|
|
5530
|
-
processError({ issues: issue2.issues }, [...
|
|
5530
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
5531
5531
|
} else {
|
|
5532
|
-
const fullpath = [...
|
|
5532
|
+
const fullpath = [...path13, ...issue2.path];
|
|
5533
5533
|
if (fullpath.length === 0) {
|
|
5534
5534
|
result2.errors.push(mapper(issue2));
|
|
5535
5535
|
continue;
|
|
@@ -5568,8 +5568,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
5568
5568
|
}
|
|
5569
5569
|
function toDotPath(_path) {
|
|
5570
5570
|
const segs = [];
|
|
5571
|
-
const
|
|
5572
|
-
for (const seg of
|
|
5571
|
+
const path13 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
5572
|
+
for (const seg of path13) {
|
|
5573
5573
|
if (typeof seg === "number")
|
|
5574
5574
|
segs.push(`[${seg}]`);
|
|
5575
5575
|
else if (typeof seg === "symbol")
|
|
@@ -16890,13 +16890,13 @@ ${code}
|
|
|
16890
16890
|
return fn;
|
|
16891
16891
|
}
|
|
16892
16892
|
function addConstant(ctx, value) {
|
|
16893
|
-
for (const [
|
|
16893
|
+
for (const [name3, v] of ctx.constants) {
|
|
16894
16894
|
if (v === value)
|
|
16895
|
-
return
|
|
16895
|
+
return name3;
|
|
16896
16896
|
}
|
|
16897
|
-
const
|
|
16898
|
-
ctx.constants.set(
|
|
16899
|
-
return
|
|
16897
|
+
const name2 = `c${ctx.constantCounter++}`;
|
|
16898
|
+
ctx.constants.set(name2, value);
|
|
16899
|
+
return name2;
|
|
16900
16900
|
}
|
|
16901
16901
|
function addUserConstant(ctx, fn) {
|
|
16902
16902
|
ctx.definite = false;
|
|
@@ -22671,13 +22671,13 @@ function resolveRef(ref, ctx) {
|
|
|
22671
22671
|
if (!ref.startsWith("#")) {
|
|
22672
22672
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
22673
22673
|
}
|
|
22674
|
-
const
|
|
22675
|
-
if (
|
|
22674
|
+
const path13 = ref.slice(1).split("/").filter(Boolean);
|
|
22675
|
+
if (path13.length === 0) {
|
|
22676
22676
|
return ctx.rootSchema;
|
|
22677
22677
|
}
|
|
22678
22678
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
22679
|
-
if (
|
|
22680
|
-
const key =
|
|
22679
|
+
if (path13[0] === defsKey) {
|
|
22680
|
+
const key = path13[1] === void 0 ? void 0 : decodeJSONPointerSegment(path13[1]);
|
|
22681
22681
|
if (!key || !ctx.defs[key]) {
|
|
22682
22682
|
throw new Error(`Reference not found: ${ref}`);
|
|
22683
22683
|
}
|
|
@@ -23643,9 +23643,9 @@ function isDisjoint(...headers) {
|
|
|
23643
23643
|
}
|
|
23644
23644
|
return true;
|
|
23645
23645
|
}
|
|
23646
|
-
function assertNotSet(value,
|
|
23646
|
+
function assertNotSet(value, name2) {
|
|
23647
23647
|
if (value !== void 0)
|
|
23648
|
-
throw new TypeError(`${
|
|
23648
|
+
throw new TypeError(`${name2} can only be called once`);
|
|
23649
23649
|
}
|
|
23650
23650
|
function decodeBase64url(value, label, ErrorClass) {
|
|
23651
23651
|
try {
|
|
@@ -23806,7 +23806,7 @@ function invalidKeyType(alg, actual, secret) {
|
|
|
23806
23806
|
const types = ["CryptoKey", "KeyObject", "JSON Web Key"];
|
|
23807
23807
|
return secret && types.push("Uint8Array"), new TypeError(message(`Key for the ${alg} algorithm must be `, actual, ...types));
|
|
23808
23808
|
}
|
|
23809
|
-
var unusable = (
|
|
23809
|
+
var unusable = (name2, prop = "algorithm.name") => new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name2}`);
|
|
23810
23810
|
function checkUsage(key, usage) {
|
|
23811
23811
|
if (usage && !key.usages.includes(usage))
|
|
23812
23812
|
throw new TypeError(`CryptoKey does not support this operation, its usages must include ${usage}.`);
|
|
@@ -24481,7 +24481,9 @@ var ClientFrameSchema = external_exports.discriminatedUnion("type", [
|
|
|
24481
24481
|
external_exports.object({ type: external_exports.literal("not_accepted"), version: external_exports.literal(1), binding: ExecutionBindingSchema, code: external_exports.string().regex(/^[A-Z0-9_]{1,40}$/), provedNotStarted: external_exports.boolean() }).strict(),
|
|
24482
24482
|
external_exports.object({ type: external_exports.literal("control_result"), version: external_exports.literal(1), requestId: uuid8, binding: ExecutionBindingSchema, state: external_exports.enum(["prepared", "accepted", "running", "finished", "failed", "cancelled", "rejected", "unknown", "missing"]), result: ResultSchema.optional() }).strict()
|
|
24483
24483
|
]);
|
|
24484
|
-
var
|
|
24484
|
+
var TerminalMetricsSchema = external_exports.object({ stdoutBytes: external_exports.number().int().min(0).max(4194304), stderrBytes: external_exports.number().int().min(0).max(4194304), outputTruncated: external_exports.boolean() }).strict();
|
|
24485
|
+
var TERMINAL_METRICS_CAPABILITY = "terminalMetrics";
|
|
24486
|
+
var TerminalEventSchema = external_exports.object({ type: external_exports.literal("executionResult"), protocolVersion: external_exports.literal(1), requestId: uuid8, tenantId: uuid8, deviceId: uuid8, connectionEpoch: epoch, binding: ExecutionBindingSchema, state: external_exports.enum(["finished", "failed", "cancelled", "unknown"]), resultCode: external_exports.string().regex(/^[A-Z0-9_]{1,40}$/), metrics: TerminalMetricsSchema.optional() }).strict();
|
|
24485
24487
|
var TerminalEventAckSchema = external_exports.object({ type: external_exports.literal("executionResultAck"), protocolVersion: external_exports.literal(1), requestId: uuid8, ok: external_exports.boolean() }).strict();
|
|
24486
24488
|
function canonicalJson(value) {
|
|
24487
24489
|
function canonical2(v) {
|
|
@@ -24547,9 +24549,9 @@ function guardAction(binding2, tool, config2, observedAt) {
|
|
|
24547
24549
|
if (!binding2.accountId || binding2.tenantId !== config2.tenantId || binding2.deviceId !== config2.deviceId || !tool.effective) {
|
|
24548
24550
|
throw Error("GUARD_IDENTITY_REQUIRED");
|
|
24549
24551
|
}
|
|
24550
|
-
const
|
|
24551
|
-
const kind =
|
|
24552
|
-
const resourceKind = kind === "execute" ? "workspace" : kind === "localhost-access" ? "localhost-service" : kind === "git-read" ? "git-repository" :
|
|
24552
|
+
const name2 = tool.call.name;
|
|
24553
|
+
const kind = name2 === "command_run" ? "execute" : name2 === "localhost_request" ? "localhost-access" : name2 === "fs_write" || name2 === "fs_replace" ? "write" : name2.startsWith("git_") ? "git-read" : "read";
|
|
24554
|
+
const resourceKind = kind === "execute" ? "workspace" : kind === "localhost-access" ? "localhost-service" : kind === "git-read" ? "git-repository" : name2 === "fs_read" || kind === "write" ? "file" : "workspace";
|
|
24553
24555
|
return { payloadHash: guardHash(tool.effective), request: BoundActionRequestSchema.parse({
|
|
24554
24556
|
protocolVersion: 1,
|
|
24555
24557
|
requestId: binding2.executionId,
|
|
@@ -24569,13 +24571,13 @@ function guardAction(binding2, tool, config2, observedAt) {
|
|
|
24569
24571
|
adapter: guardAdapter,
|
|
24570
24572
|
actor: { kind: "human", subjectRef: binding2.accountId, authenticationClass: "delegated" },
|
|
24571
24573
|
agent: { agentRef: "grant:" + binding2.grantId },
|
|
24572
|
-
tool: { namespace: "local-commander", name },
|
|
24574
|
+
tool: { namespace: "local-commander", name: name2 },
|
|
24573
24575
|
resource: { kind: resourceKind, locatorFingerprint: guardHash({
|
|
24574
24576
|
workspace: binding2.workspaceGrantId,
|
|
24575
24577
|
resources: tool.claims,
|
|
24576
|
-
...
|
|
24578
|
+
...name2 === "localhost_request" ? { url: tool.call.args.url } : {}
|
|
24577
24579
|
}) },
|
|
24578
|
-
action: { kind, verb:
|
|
24580
|
+
action: { kind, verb: name2, effect: kind === "execute" ? "external-mutation" : kind === "write" ? "local-mutation" : kind === "localhost-access" ? "external-mutation" : "read-only" },
|
|
24579
24581
|
dataClassifications: ["unknown"],
|
|
24580
24582
|
environment: config2.environment
|
|
24581
24583
|
}
|
|
@@ -24585,7 +24587,7 @@ function guardSummary(tool) {
|
|
|
24585
24587
|
if (!tool.effective) throw Error("GUARD_ACTION_INVALID");
|
|
24586
24588
|
const call = tool.call;
|
|
24587
24589
|
const args = { ...call.args };
|
|
24588
|
-
if (args.headers) args.headers = Object.keys(args.headers).map((
|
|
24590
|
+
if (args.headers) args.headers = Object.keys(args.headers).map((name2) => name2 + " (value hidden)");
|
|
24589
24591
|
return JSON.stringify({
|
|
24590
24592
|
tool: call.name,
|
|
24591
24593
|
resources: tool.claims,
|
|
@@ -24695,7 +24697,7 @@ function canonicalTime(value) {
|
|
|
24695
24697
|
}
|
|
24696
24698
|
function validateInput(value) {
|
|
24697
24699
|
if (!value || typeof value !== "object" || Object.keys(value).some((k) => !bindingKeys.includes(k))) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24698
|
-
for (const
|
|
24700
|
+
for (const name2 of bindingKeys.slice(0, 6)) if (typeof value[name2] !== "string" || !uuid11.test(value[name2])) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24699
24701
|
if (value.accountId !== void 0 && !uuid11.test(value.accountId)) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24700
24702
|
if (typeof value.connectionEpoch !== "string" || !/^[1-9]\d{0,18}$/.test(value.connectionEpoch) || BigInt(value.connectionEpoch) > 9223372036854775807n || typeof value.payloadHash !== "string" || !hash4.test(value.payloadHash) || !canonicalTime(value.deadlineAt)) throw failure2("JOURNAL_INPUT_INVALID");
|
|
24701
24703
|
if (value.workspaceGrantId === void 0 !== (value.policyVersion === void 0) || value.workspaceGrantId !== void 0 && (!uuid11.test(value.workspaceGrantId) || !Number.isInteger(value.policyVersion) || value.policyVersion < 1 || value.policyVersion > 2147483647)) throw failure2("JOURNAL_INPUT_INVALID");
|
|
@@ -24817,9 +24819,9 @@ var NativeJournal = class _NativeJournal {
|
|
|
24817
24819
|
async load() {
|
|
24818
24820
|
const names = await readdir(this.dir);
|
|
24819
24821
|
if (names.length > this.options.maxEntries + 256) throw failure2("JOURNAL_CAPACITY_EXCEEDED");
|
|
24820
|
-
for (const
|
|
24821
|
-
if (!
|
|
24822
|
-
const id =
|
|
24822
|
+
for (const name2 of names) {
|
|
24823
|
+
if (!name2.endsWith(".entry")) continue;
|
|
24824
|
+
const id = name2.slice(0, -6);
|
|
24823
24825
|
if (!uuid11.test(id)) throw failure2("JOURNAL_CORRUPT");
|
|
24824
24826
|
const envelope = await this.readOwned(id);
|
|
24825
24827
|
this.totalBytes += envelope.length;
|
|
@@ -25083,10 +25085,10 @@ var NativeJournal = class _NativeJournal {
|
|
|
25083
25085
|
|
|
25084
25086
|
// agent/src/private-store.ts
|
|
25085
25087
|
import { constants as constants2 } from "node:fs";
|
|
25086
|
-
import { lstat as
|
|
25087
|
-
import
|
|
25088
|
+
import { lstat as lstat3, open as open3, readFile as readFile2, readdir as readdir2, realpath as realpath3, rename as rename2, rm as rm2, writeFile } from "node:fs/promises";
|
|
25089
|
+
import path5 from "node:path";
|
|
25088
25090
|
import os from "node:os";
|
|
25089
|
-
import { createHash as
|
|
25091
|
+
import { createHash as createHash8, createPrivateKey, createPublicKey as createPublicKey3, generateKeyPairSync, randomBytes as randomBytes3, randomUUID as randomUUID3 } from "node:crypto";
|
|
25090
25092
|
import { spawn } from "node:child_process";
|
|
25091
25093
|
|
|
25092
25094
|
// src/child-environment.ts
|
|
@@ -25203,13 +25205,123 @@ function buildChildEnvironment(source, platform = process.platform) {
|
|
|
25203
25205
|
return result2;
|
|
25204
25206
|
}
|
|
25205
25207
|
|
|
25208
|
+
// agent/src/access-selection.ts
|
|
25209
|
+
import path3 from "node:path";
|
|
25210
|
+
function parseAccessSelection(value) {
|
|
25211
|
+
const fail5 = () => {
|
|
25212
|
+
throw Error("ACCESS_SELECTION_INVALID");
|
|
25213
|
+
};
|
|
25214
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail5();
|
|
25215
|
+
const input2 = value;
|
|
25216
|
+
if (!Array.isArray(input2.paths)) return fail5();
|
|
25217
|
+
if (input2.mode === "cancelled") {
|
|
25218
|
+
if (input2.paths.length !== 0) return fail5();
|
|
25219
|
+
return null;
|
|
25220
|
+
}
|
|
25221
|
+
if (input2.mode !== "folders" && input2.mode !== "all-drives") return fail5();
|
|
25222
|
+
if (input2.paths.length < 1 || input2.paths.length > (input2.mode === "folders" ? 1 : 26)) return fail5();
|
|
25223
|
+
const roots = [];
|
|
25224
|
+
for (const candidate of input2.paths) {
|
|
25225
|
+
if (typeof candidate !== "string" || candidate.length > 4096 || /[\u0000-\u001f]/.test(candidate) || !path3.win32.isAbsolute(candidate)) return fail5();
|
|
25226
|
+
if (input2.mode === "all-drives" && !/^[a-z]:\\$/i.test(candidate)) return fail5();
|
|
25227
|
+
const normalized = path3.win32.normalize(candidate).toLowerCase();
|
|
25228
|
+
if (roots.some((root) => path3.win32.normalize(root).toLowerCase() === normalized)) return fail5();
|
|
25229
|
+
roots.push(candidate);
|
|
25230
|
+
}
|
|
25231
|
+
return { mode: input2.mode, paths: roots };
|
|
25232
|
+
}
|
|
25233
|
+
|
|
25234
|
+
// agent/src/owner-permissions.ts
|
|
25235
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
25236
|
+
import { lstat as lstat2, realpath as realpath2 } from "node:fs/promises";
|
|
25237
|
+
import path4 from "node:path";
|
|
25238
|
+
var uuid12 = external_exports.string().uuid().toLowerCase();
|
|
25239
|
+
var name = external_exports.string().regex(/^[a-zA-Z0-9_.-]{1,64}$/).toLowerCase();
|
|
25240
|
+
var changeSchema = external_exports.discriminatedUnion("kind", [
|
|
25241
|
+
external_exports.object({ workspaceGrantId: uuid12, kind: external_exports.literal("write"), enabled: external_exports.boolean() }).strict(),
|
|
25242
|
+
external_exports.object({ workspaceGrantId: uuid12, kind: external_exports.literal("localhost"), enabled: external_exports.boolean(), port: external_exports.number().int().min(1).max(65535) }).strict(),
|
|
25243
|
+
external_exports.object({ workspaceGrantId: uuid12, kind: external_exports.literal("command"), enabled: external_exports.boolean(), name, executable: external_exports.string().min(1).max(4096).optional() }).strict()
|
|
25244
|
+
]);
|
|
25245
|
+
var PERMISSION_HISTORY_LIMIT = 64;
|
|
25246
|
+
var PermissionHistorySchema = external_exports.array(external_exports.object({
|
|
25247
|
+
id: uuid12,
|
|
25248
|
+
changedAt: external_exports.string().datetime(),
|
|
25249
|
+
workspaceGrantId: uuid12,
|
|
25250
|
+
kind: external_exports.enum(["write", "command", "localhost"]),
|
|
25251
|
+
enabled: external_exports.boolean(),
|
|
25252
|
+
resourceHash: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
25253
|
+
ownerSid: external_exports.string().max(184).regex(/^S-1-(?:[0-9]+-)+[0-9]+$/)
|
|
25254
|
+
}).strict()).max(PERMISSION_HISTORY_LIMIT);
|
|
25255
|
+
function permissionHash(value) {
|
|
25256
|
+
return createHash4("sha256").update(JSON.stringify(value)).digest("hex");
|
|
25257
|
+
}
|
|
25258
|
+
function invalid2(code = "PERMISSION_INVALID") {
|
|
25259
|
+
throw Object.assign(new Error(code), { code });
|
|
25260
|
+
}
|
|
25261
|
+
function parsePermissionChange(value) {
|
|
25262
|
+
const parsed = changeSchema.safeParse(value);
|
|
25263
|
+
if (!parsed.success) invalid2();
|
|
25264
|
+
const change = parsed.data;
|
|
25265
|
+
if (change.kind === "command" && (change.enabled ? !change.executable : change.executable !== void 0)) invalid2();
|
|
25266
|
+
return change;
|
|
25267
|
+
}
|
|
25268
|
+
function equalPath(a, b) {
|
|
25269
|
+
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
25270
|
+
}
|
|
25271
|
+
function permissionLabel(value) {
|
|
25272
|
+
return JSON.stringify(value).replace(
|
|
25273
|
+
/[\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff]/gu,
|
|
25274
|
+
(ch) => "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0")
|
|
25275
|
+
);
|
|
25276
|
+
}
|
|
25277
|
+
function nonAsciiNotice(value) {
|
|
25278
|
+
if (!value || !/[^\x20-\x7e]/.test(value)) return "";
|
|
25279
|
+
return "\nWarning: this path contains non-ASCII characters: " + value.replace(/[^\x20-\x7e]/g, (ch) => "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0"));
|
|
25280
|
+
}
|
|
25281
|
+
async function planPermissionChange(current2, programs, input2) {
|
|
25282
|
+
const change = parsePermissionChange(input2);
|
|
25283
|
+
if (current2.workspaceGrantId !== change.workspaceGrantId) invalid2("WORKSPACE_UNKNOWN");
|
|
25284
|
+
const workspace = structuredClone(current2), nextPrograms = { ...programs };
|
|
25285
|
+
let resource = { rootHash: workspace.rootHash, kind: change.kind };
|
|
25286
|
+
let detail;
|
|
25287
|
+
if (change.kind === "write") {
|
|
25288
|
+
workspace.permissions.writeFiles = change.enabled;
|
|
25289
|
+
detail = "File writes: " + (change.enabled ? "ALLOW" : "DENY");
|
|
25290
|
+
} else if (change.kind === "localhost") {
|
|
25291
|
+
workspace.localhostPorts = change.enabled ? [.../* @__PURE__ */ new Set([...workspace.localhostPorts, change.port])].sort((a, b) => a - b) : workspace.localhostPorts.filter((port) => port !== change.port);
|
|
25292
|
+
if (workspace.localhostPorts.length > 32) invalid2("PERMISSION_LIMIT");
|
|
25293
|
+
workspace.permissions.localhost = workspace.localhostPorts.length > 0;
|
|
25294
|
+
resource = { rootHash: workspace.rootHash, kind: change.kind, port: change.port };
|
|
25295
|
+
detail = "Localhost port " + change.port + ": " + (change.enabled ? "ALLOW" : "DENY");
|
|
25296
|
+
} else {
|
|
25297
|
+
let executableIdentity = null;
|
|
25298
|
+
if (change.enabled) {
|
|
25299
|
+
const executable = change.executable;
|
|
25300
|
+
if (!path4.isAbsolute(executable) || path4.extname(executable).toLowerCase() !== ".exe" || /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff]/u.test(executable)) invalid2();
|
|
25301
|
+
const stat = await lstat2(executable).catch(() => invalid2());
|
|
25302
|
+
const canonical2 = await realpath2(executable).catch(() => invalid2());
|
|
25303
|
+
if (!stat.isFile() || stat.isSymbolicLink() || !equalPath(path4.resolve(executable), canonical2)) invalid2();
|
|
25304
|
+
if (Object.hasOwn(nextPrograms, change.name) && !equalPath(nextPrograms[change.name], canonical2)) invalid2("COMMAND_CONFLICT");
|
|
25305
|
+
nextPrograms[change.name] = canonical2;
|
|
25306
|
+
workspace.allowedCommands = [.../* @__PURE__ */ new Set([...workspace.allowedCommands, change.name])].sort();
|
|
25307
|
+
if (workspace.allowedCommands.length > 32 || Object.keys(nextPrograms).length > 32) invalid2("PERMISSION_LIMIT");
|
|
25308
|
+
executableIdentity = { executable: canonical2, dev: stat.dev, ino: stat.ino, size: stat.size, mtimeMs: stat.mtimeMs, ctimeMs: stat.ctimeMs };
|
|
25309
|
+
} else workspace.allowedCommands = workspace.allowedCommands.filter((value) => value !== change.name);
|
|
25310
|
+
workspace.permissions.executeCommands = workspace.allowedCommands.length > 0;
|
|
25311
|
+
resource = { rootHash: workspace.rootHash, kind: change.kind, name: change.name, executable: nextPrograms[change.name] ?? null, executableIdentity };
|
|
25312
|
+
detail = "Command " + permissionLabel(change.name) + ": " + (change.enabled ? "ALLOW" : "DENY") + "\nExact executable: " + permissionLabel(nextPrograms[change.name] ?? "not registered") + nonAsciiNotice(nextPrograms[change.name]) + "\nA permitted program can access resources available to your Windows account, including outside this folder. This is not an OS sandbox.";
|
|
25313
|
+
}
|
|
25314
|
+
const summary = "Change Local Commander permissions\nWorkspace: " + permissionLabel(current2.displayName) + "\nWorkspace ID: " + current2.workspaceGrantId + "\nFolder: " + permissionLabel(current2.root) + "\n\n" + detail + "\n\nThis change persists until you revoke it. Other workspaces are not granted this permission.\nReview before approving. Closing this window, rejecting or timing out changes nothing.\nA running agent may restart after the change; running work can be interrupted. Revocation does not undo completed effects.";
|
|
25315
|
+
return { workspace, programs: nextPrograms, change, summary, resourceHash: permissionHash(resource) };
|
|
25316
|
+
}
|
|
25317
|
+
|
|
25206
25318
|
// packages/policy/src/signed-bundle.ts
|
|
25207
|
-
import { createHash as
|
|
25319
|
+
import { createHash as createHash6, KeyObject as KeyObject2 } from "node:crypto";
|
|
25208
25320
|
|
|
25209
25321
|
// packages/policy/src/evaluator.ts
|
|
25210
|
-
import { createHash as
|
|
25322
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
25211
25323
|
import { isDeepStrictEqual } from "node:util";
|
|
25212
|
-
var
|
|
25324
|
+
var uuid13 = external_exports.string().uuid().toLowerCase();
|
|
25213
25325
|
var slug2 = external_exports.string().regex(/^[a-z][a-z0-9._-]{0,63}$/);
|
|
25214
25326
|
var utc = external_exports.iso.datetime({ offset: false });
|
|
25215
25327
|
var risk = external_exports.enum(["low", "moderate", "high", "critical"]);
|
|
@@ -25234,7 +25346,7 @@ var PolicyRuleSchema = external_exports.object({
|
|
|
25234
25346
|
}
|
|
25235
25347
|
});
|
|
25236
25348
|
var PolicySnapshotSchema = external_exports.object({
|
|
25237
|
-
tenantId:
|
|
25349
|
+
tenantId: uuid13,
|
|
25238
25350
|
policyId: slug2,
|
|
25239
25351
|
policyVersion: external_exports.string().trim().min(1).max(64),
|
|
25240
25352
|
issuedAt: utc,
|
|
@@ -25264,7 +25376,7 @@ function matches(rule, request, requestRisk) {
|
|
|
25264
25376
|
return (!rule.actionKinds || rule.actionKinds.includes(action.kind)) && (!rule.toolNamespaces || rule.toolNamespaces.includes(tool.namespace)) && (!rule.environments || rule.environments.includes(environment.environment)) && (!rule.anyDataClassifications || rule.anyDataClassifications.some((value) => dataClassifications.includes(value))) && (!rule.resourceKinds || rule.resourceKinds.includes(resource.kind)) && (!rule.resourceFingerprints || rule.resourceFingerprints.includes(resource.locatorFingerprint)) && (!rule.riskClasses || rule.riskClasses.includes(requestRisk));
|
|
25265
25377
|
}
|
|
25266
25378
|
function decisionId(requestId, policyId, policyVersion, ruleId) {
|
|
25267
|
-
const bytes3 =
|
|
25379
|
+
const bytes3 = createHash5("sha256").update(JSON.stringify([requestId, policyId, policyVersion, ruleId])).digest().subarray(0, 16);
|
|
25268
25380
|
bytes3[6] = bytes3[6] & 15 | 64;
|
|
25269
25381
|
bytes3[8] = bytes3[8] & 63 | 128;
|
|
25270
25382
|
const hex3 = bytes3.toString("hex");
|
|
@@ -25333,7 +25445,7 @@ async function verifySignedPolicyBundle(candidate, authority, checkpointInput, n
|
|
|
25333
25445
|
if (payload.byteLength > 256 * 1024) throw new Error();
|
|
25334
25446
|
const bundle = BundleSchema.parse(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload)));
|
|
25335
25447
|
const { snapshot: snapshot2 } = bundle;
|
|
25336
|
-
const payloadSha256 =
|
|
25448
|
+
const payloadSha256 = createHash6("sha256").update(payload).digest("hex");
|
|
25337
25449
|
if (snapshot2.tenantId !== expected.tenantId || snapshot2.policyId !== expected.policyId || at < Date.parse(snapshot2.issuedAt) || at >= Date.parse(snapshot2.expiresAt) || bundle.sequence < checkpoint.sequence || bundle.sequence === checkpoint.sequence && payloadSha256 !== checkpoint.payloadSha256) throw new Error();
|
|
25338
25450
|
return { snapshot: snapshot2, checkpoint: {
|
|
25339
25451
|
...expected,
|
|
@@ -25347,15 +25459,15 @@ async function verifySignedPolicyBundle(candidate, authority, checkpointInput, n
|
|
|
25347
25459
|
}
|
|
25348
25460
|
|
|
25349
25461
|
// packages/policy/src/approvals.ts
|
|
25350
|
-
import { createHash as
|
|
25351
|
-
var
|
|
25462
|
+
import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
|
|
25463
|
+
var uuid14 = TrustedActionBindingSchema.shape.tenantId;
|
|
25352
25464
|
var utc3 = TrustedActionBindingSchema.shape.expiresAt;
|
|
25353
25465
|
var digest3 = ResourceRefSchema.shape.locatorFingerprint;
|
|
25354
25466
|
var requirement = PolicyDecisionSchema.shape.approval.unwrap();
|
|
25355
25467
|
var ApprovalActionSchema = external_exports.object({ request: BoundActionRequestSchema, payloadHash: digest3 }).strict();
|
|
25356
25468
|
var ApprovalAuthoritySchema = external_exports.object({
|
|
25357
|
-
tenantId:
|
|
25358
|
-
accountId:
|
|
25469
|
+
tenantId: uuid14,
|
|
25470
|
+
accountId: uuid14,
|
|
25359
25471
|
approvalClass: requirement.shape.approvalClass,
|
|
25360
25472
|
authenticatedAt: utc3,
|
|
25361
25473
|
expiresAt: utc3
|
|
@@ -25367,11 +25479,11 @@ var ContextSchema = external_exports.object({
|
|
|
25367
25479
|
policyExpiresAt: utc3
|
|
25368
25480
|
}).strict();
|
|
25369
25481
|
var GrantSchema = external_exports.object({
|
|
25370
|
-
approvalId:
|
|
25371
|
-
requestId:
|
|
25372
|
-
decisionId:
|
|
25482
|
+
approvalId: uuid14,
|
|
25483
|
+
requestId: uuid14,
|
|
25484
|
+
decisionId: uuid14,
|
|
25373
25485
|
bindingHash: digest3,
|
|
25374
|
-
approverAccountId:
|
|
25486
|
+
approverAccountId: uuid14,
|
|
25375
25487
|
approvalClass: requirement.shape.approvalClass,
|
|
25376
25488
|
scope: requirement.shape.scope,
|
|
25377
25489
|
issuedAt: utc3,
|
|
@@ -25380,8 +25492,8 @@ var GrantSchema = external_exports.object({
|
|
|
25380
25492
|
uses: external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
|
|
25381
25493
|
}).strict().refine((value) => Date.parse(value.issuedAt) < Date.parse(value.expiresAt) && (value.scope === "once" ? value.uses === (value.state === "consumed" ? 1 : 0) : value.state !== "consumed"));
|
|
25382
25494
|
var ApprovalLedgerSchema = external_exports.object({
|
|
25383
|
-
tenantId:
|
|
25384
|
-
deviceId:
|
|
25495
|
+
tenantId: uuid14,
|
|
25496
|
+
deviceId: uuid14,
|
|
25385
25497
|
lastChangedAt: utc3,
|
|
25386
25498
|
grants: external_exports.array(GrantSchema).max(128)
|
|
25387
25499
|
}).strict().refine((value) => new Set(value.grants.map((grant) => grant.approvalId)).size === value.grants.length && new Set(value.grants.map((grant) => grant.requestId)).size === value.grants.length && value.grants.every((grant) => Date.parse(grant.issuedAt) <= Date.parse(value.lastChangedAt)));
|
|
@@ -25402,7 +25514,7 @@ function binding(ledger, input2, now) {
|
|
|
25402
25514
|
approval.expiresAt ? Date.parse(approval.expiresAt) : Infinity
|
|
25403
25515
|
);
|
|
25404
25516
|
if (at >= deadline) throw Error();
|
|
25405
|
-
const bindingHash =
|
|
25517
|
+
const bindingHash = createHash7("sha256").update(JSON.stringify([
|
|
25406
25518
|
owner.bindingSource,
|
|
25407
25519
|
owner.tenantId,
|
|
25408
25520
|
owner.accountId,
|
|
@@ -25458,7 +25570,7 @@ function issueApproval(ledgerInput, context, authorityInput, now, expiresAt) {
|
|
|
25458
25570
|
}
|
|
25459
25571
|
function consumeApproval(ledgerInput, context, approvalId, now) {
|
|
25460
25572
|
try {
|
|
25461
|
-
const ledger = current(ledgerInput, now), bound = binding(ledger, context, now), id =
|
|
25573
|
+
const ledger = current(ledgerInput, now), bound = binding(ledger, context, now), id = uuid14.parse(approvalId);
|
|
25462
25574
|
const grant = ledger.grants.find((candidate) => candidate.approvalId === id);
|
|
25463
25575
|
if (!grant || grant.state !== "active" || grant.bindingHash !== bound.bindingHash || grant.uses === Number.MAX_SAFE_INTEGER || Date.parse(now) < Date.parse(grant.issuedAt) || grant.scope === "once" && (grant.requestId !== bound.request.requestId || grant.decisionId !== bound.decision.decisionId)) throw Error();
|
|
25464
25576
|
const consumed = { ...grant, uses: grant.uses + 1, state: grant.scope === "once" ? "consumed" : "active" };
|
|
@@ -25469,7 +25581,7 @@ function consumeApproval(ledgerInput, context, approvalId, now) {
|
|
|
25469
25581
|
}
|
|
25470
25582
|
function revokeApproval(ledgerInput, approvalId, now) {
|
|
25471
25583
|
try {
|
|
25472
|
-
const ledger = current(ledgerInput, now), id =
|
|
25584
|
+
const ledger = current(ledgerInput, now), id = uuid14.parse(approvalId);
|
|
25473
25585
|
return { ...ledger, grants: ledger.grants.map((grant) => grant.approvalId === id && grant.state === "active" ? { ...grant, state: "revoked" } : grant) };
|
|
25474
25586
|
} catch {
|
|
25475
25587
|
throw Error("APPROVAL_INVALID");
|
|
@@ -25484,12 +25596,12 @@ var MAX_STATE_BYTES = 256 * 1024;
|
|
|
25484
25596
|
function fail(code) {
|
|
25485
25597
|
return Object.assign(new Error(code), { code });
|
|
25486
25598
|
}
|
|
25487
|
-
function
|
|
25488
|
-
return
|
|
25599
|
+
function equalPath2(a, b) {
|
|
25600
|
+
return path5.resolve(a).toLowerCase() === path5.resolve(b).toLowerCase();
|
|
25489
25601
|
}
|
|
25490
25602
|
function under(child, parent) {
|
|
25491
|
-
const rel =
|
|
25492
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
25603
|
+
const rel = path5.relative(parent, child);
|
|
25604
|
+
return rel !== "" && !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
25493
25605
|
}
|
|
25494
25606
|
function canonicalPublicKey(value) {
|
|
25495
25607
|
try {
|
|
@@ -25501,7 +25613,7 @@ function canonicalPublicKey(value) {
|
|
|
25501
25613
|
}
|
|
25502
25614
|
}
|
|
25503
25615
|
function fingerprint(publicKeyPem) {
|
|
25504
|
-
return
|
|
25616
|
+
return createHash8("sha256").update(canonicalPublicKey(publicKeyPem).der).digest("hex");
|
|
25505
25617
|
}
|
|
25506
25618
|
function publicFromPrivate(privateKeyPem) {
|
|
25507
25619
|
try {
|
|
@@ -25560,55 +25672,57 @@ function validateState(value) {
|
|
|
25560
25672
|
}
|
|
25561
25673
|
const approvals = value.approvals === void 0 ? void 0 : ApprovalLedgerSchema.safeParse(value.approvals);
|
|
25562
25674
|
if (approvals && (!approvals.success || approvals.data.tenantId !== enrollment?.tenantId || approvals.data.deviceId !== enrollment.deviceId)) throw fail("STATE_CORRUPT");
|
|
25675
|
+
const permissionHistory = PermissionHistorySchema.safeParse(value.permissionHistory ?? []);
|
|
25676
|
+
if (!permissionHistory.success) throw fail("STATE_CORRUPT");
|
|
25563
25677
|
const workspaces = value.workspaces ?? [];
|
|
25564
25678
|
const programs = value.programs ?? {};
|
|
25565
25679
|
const pendingWorkspaceRevokes = value.pendingWorkspaceRevokes ?? [];
|
|
25566
25680
|
if (!Array.isArray(pendingWorkspaceRevokes) || pendingWorkspaceRevokes.length > 32 || pendingWorkspaceRevokes.some((id) => typeof id !== "string" || !UUID.test(id) || id !== id.toLowerCase()) || new Set(pendingWorkspaceRevokes).size !== pendingWorkspaceRevokes.length) throw fail("STATE_CORRUPT");
|
|
25567
|
-
if (!programs || typeof programs !== "object" || Array.isArray(programs) || Object.keys(programs).length > 32 || Object.entries(programs).some(([
|
|
25568
|
-
if (!Array.isArray(workspaces) || workspaces.length > 32 || workspaces.some((w) => !w || !UUID.test(w.workspaceGrantId) || typeof w.root !== "string" || !
|
|
25681
|
+
if (!programs || typeof programs !== "object" || Array.isArray(programs) || Object.keys(programs).length > 32 || Object.entries(programs).some(([name2, executable]) => !/^[a-z0-9_.-]{1,64}$/.test(name2) || typeof executable !== "string" || !path5.isAbsolute(executable) || executable.length > 4096 || /[\u0000-\u001f]/.test(executable))) throw fail("STATE_CORRUPT");
|
|
25682
|
+
if (!Array.isArray(workspaces) || workspaces.length > 32 || workspaces.some((w) => !w || !UUID.test(w.workspaceGrantId) || typeof w.root !== "string" || !path5.isAbsolute(w.root) || w.root.length > 4096 || /[\u0000-\u001f]/.test(w.root) || !/^[a-f0-9]{64}$/.test(w.rootHash) || typeof w.displayName !== "string" || !w.displayName || w.displayName.length > 120 || w.policyVersion !== 1 || !w.permissions || w.permissions.readFiles !== true || typeof w.permissions.writeFiles !== "boolean" || typeof w.permissions.executeCommands !== "boolean" || typeof w.permissions.localhost !== "boolean" || !Array.isArray(w.allowedCommands) || w.allowedCommands.length > 32 || w.allowedCommands.some((name2) => typeof name2 !== "string" || !Object.hasOwn(programs, name2)) || new Set(w.allowedCommands).size !== w.allowedCommands.length || w.permissions.executeCommands !== w.allowedCommands.length > 0 || !Array.isArray(w.localhostPorts) || w.localhostPorts.length > 32 || w.localhostPorts.some((port) => !Number.isInteger(port) || port < 1 || port > 65535) || new Set(w.localhostPorts).size !== w.localhostPorts.length || w.permissions.localhost !== w.localhostPorts.length > 0)) throw fail("STATE_CORRUPT");
|
|
25569
25683
|
if (new Set(workspaces.map((w) => w.workspaceGrantId)).size !== workspaces.length || new Set(workspaces.map((w) => w.rootHash)).size !== workspaces.length) throw fail("STATE_CORRUPT");
|
|
25570
25684
|
if (workspaces.some((w) => pendingWorkspaceRevokes.includes(w.workspaceGrantId))) throw fail("STATE_CORRUPT");
|
|
25571
|
-
return { version: 1, publicKeyPem: value.publicKeyPem, privateKeyPem: value.privateKeyPem, fingerprint: value.fingerprint, dataKey: value.dataKey, ...enrollment ? { enrollment } : {}, workspaces, programs, pendingWorkspaceRevokes, ...policyBundle ? { policyBundle } : {}, ...approvals?.success ? { approvals: approvals.data } : {}, ...value.guardRequired ? { guardRequired: true } : {}, ...guard ? { guard } : {} };
|
|
25685
|
+
return { version: 1, publicKeyPem: value.publicKeyPem, privateKeyPem: value.privateKeyPem, fingerprint: value.fingerprint, dataKey: value.dataKey, ...enrollment ? { enrollment } : {}, workspaces, programs, pendingWorkspaceRevokes, ...value.permissionHistory ? { permissionHistory: permissionHistory.data } : {}, ...policyBundle ? { policyBundle } : {}, ...approvals?.success ? { approvals: approvals.data } : {}, ...value.guardRequired ? { guardRequired: true } : {}, ...guard ? { guard } : {} };
|
|
25572
25686
|
}
|
|
25573
25687
|
async function noSymlinkChain(target) {
|
|
25574
|
-
if (!
|
|
25575
|
-
const absolute2 =
|
|
25576
|
-
if (absolute2 ===
|
|
25688
|
+
if (!path5.isAbsolute(target)) throw fail("STORE_DIRECTORY_INVALID");
|
|
25689
|
+
const absolute2 = path5.resolve(target);
|
|
25690
|
+
if (absolute2 === path5.parse(absolute2).root) throw fail("STORE_DIRECTORY_INVALID");
|
|
25577
25691
|
const missing = [];
|
|
25578
25692
|
let cursor = absolute2;
|
|
25579
25693
|
for (; ; ) {
|
|
25580
25694
|
try {
|
|
25581
|
-
const stat = await
|
|
25695
|
+
const stat = await lstat3(cursor);
|
|
25582
25696
|
if (stat.isSymbolicLink()) throw fail("STORE_DIRECTORY_INVALID");
|
|
25583
25697
|
break;
|
|
25584
25698
|
} catch (error62) {
|
|
25585
25699
|
if (error62.code !== "ENOENT") throw error62;
|
|
25586
25700
|
missing.push(cursor);
|
|
25587
|
-
const parent =
|
|
25701
|
+
const parent = path5.dirname(cursor);
|
|
25588
25702
|
if (parent === cursor) throw fail("STORE_DIRECTORY_INVALID");
|
|
25589
25703
|
cursor = parent;
|
|
25590
25704
|
}
|
|
25591
25705
|
}
|
|
25592
|
-
const canonicalParent = await
|
|
25593
|
-
if (!
|
|
25706
|
+
const canonicalParent = await realpath3(cursor);
|
|
25707
|
+
if (!equalPath2(cursor, canonicalParent)) throw fail("STORE_DIRECTORY_INVALID");
|
|
25594
25708
|
return absolute2;
|
|
25595
25709
|
}
|
|
25596
25710
|
function allowedDirectory(absolute2) {
|
|
25597
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
25598
|
-
const appRoot =
|
|
25599
|
-
const tempRoot =
|
|
25711
|
+
const localAppData = process.env.LOCALAPPDATA || path5.join(os.homedir(), "AppData", "Local");
|
|
25712
|
+
const appRoot = path5.resolve(localAppData, "LocalCommander");
|
|
25713
|
+
const tempRoot = path5.resolve(os.tmpdir());
|
|
25600
25714
|
if (under(absolute2, appRoot)) return;
|
|
25601
|
-
if (under(absolute2, tempRoot) && /^lc-private-[A-Za-z0-9_.-]+$/i.test(
|
|
25715
|
+
if (under(absolute2, tempRoot) && /^lc-private-[A-Za-z0-9_.-]+$/i.test(path5.basename(absolute2))) return;
|
|
25602
25716
|
throw fail("STORE_DIRECTORY_POLICY");
|
|
25603
25717
|
}
|
|
25604
25718
|
async function canonicalBridge(value) {
|
|
25605
|
-
if (typeof value !== "string" || !
|
|
25606
|
-
const stat = await
|
|
25719
|
+
if (typeof value !== "string" || !path5.isAbsolute(value) || path5.extname(value).toLowerCase() !== ".exe") throw fail("BRIDGE_INVALID");
|
|
25720
|
+
const stat = await lstat3(value).catch(() => {
|
|
25607
25721
|
throw fail("BRIDGE_INVALID");
|
|
25608
25722
|
});
|
|
25609
25723
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > 4 * 1024 * 1024) throw fail("BRIDGE_INVALID");
|
|
25610
|
-
const canonical2 = await
|
|
25611
|
-
if (!
|
|
25724
|
+
const canonical2 = await realpath3(value);
|
|
25725
|
+
if (!equalPath2(value, canonical2)) throw fail("BRIDGE_INVALID");
|
|
25612
25726
|
return canonical2;
|
|
25613
25727
|
}
|
|
25614
25728
|
function parseBridgeData(value, max = MAX_STATE_BYTES * 4) {
|
|
@@ -25640,11 +25754,11 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25640
25754
|
}
|
|
25641
25755
|
const approved = /* @__PURE__ */ new Set([MARKER, "owner-state.dat", "owner-state.lock", "journal"]);
|
|
25642
25756
|
if (names.length && !names.includes(MARKER)) throw fail("STORE_DIRECTORY_UNKNOWN");
|
|
25643
|
-
if (names.some((
|
|
25757
|
+
if (names.some((name2) => !approved.has(name2))) throw fail("STORE_DIRECTORY_UNKNOWN");
|
|
25644
25758
|
await _WindowsPrivateStore.invoke(bridge, "init-directory", { path: absolute2 }, 5e3, false);
|
|
25645
|
-
const canonical2 = await
|
|
25646
|
-
if (!
|
|
25647
|
-
const markerPath =
|
|
25759
|
+
const canonical2 = await realpath3(absolute2);
|
|
25760
|
+
if (!equalPath2(absolute2, canonical2) || (await lstat3(canonical2)).isSymbolicLink()) throw fail("STORE_DIRECTORY_INVALID");
|
|
25761
|
+
const markerPath = path5.join(canonical2, MARKER);
|
|
25648
25762
|
try {
|
|
25649
25763
|
await writeFile(markerPath, "LocalCommander private state\n", { flag: "wx", mode: 384 });
|
|
25650
25764
|
} catch (error62) {
|
|
@@ -25659,10 +25773,10 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25659
25773
|
return result2;
|
|
25660
25774
|
}
|
|
25661
25775
|
statePath() {
|
|
25662
|
-
return
|
|
25776
|
+
return path5.join(this.dir, "owner-state.dat");
|
|
25663
25777
|
}
|
|
25664
25778
|
lockPath() {
|
|
25665
|
-
return
|
|
25779
|
+
return path5.join(this.dir, "owner-state.lock");
|
|
25666
25780
|
}
|
|
25667
25781
|
static invoke(bridge, arg, body, timeoutMs2, interactive, signal) {
|
|
25668
25782
|
return new Promise((resolve, reject) => {
|
|
@@ -25757,9 +25871,21 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25757
25871
|
}
|
|
25758
25872
|
}
|
|
25759
25873
|
}
|
|
25874
|
+
/** After an owner approval, a briefly busy lock (the running agent) must not discard the decision. */
|
|
25875
|
+
async withLockWait(fn, waitMs) {
|
|
25876
|
+
const deadline = Date.now() + waitMs;
|
|
25877
|
+
for (; ; ) {
|
|
25878
|
+
try {
|
|
25879
|
+
return await this.withLock(fn);
|
|
25880
|
+
} catch (error62) {
|
|
25881
|
+
if (error62.code !== "STORE_LOCKED" || Date.now() >= deadline) throw error62;
|
|
25882
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
25883
|
+
}
|
|
25884
|
+
}
|
|
25885
|
+
}
|
|
25760
25886
|
async readState() {
|
|
25761
25887
|
try {
|
|
25762
|
-
const stat = await
|
|
25888
|
+
const stat = await lstat3(this.statePath());
|
|
25763
25889
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > MAX_STATE_BYTES * 4) throw fail("STATE_CORRUPT");
|
|
25764
25890
|
const encrypted = await readFile2(this.statePath());
|
|
25765
25891
|
const plaintext = await this.unprotect(encrypted);
|
|
@@ -25773,9 +25899,9 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25773
25899
|
}
|
|
25774
25900
|
async rawHash() {
|
|
25775
25901
|
try {
|
|
25776
|
-
const stat = await
|
|
25902
|
+
const stat = await lstat3(this.statePath());
|
|
25777
25903
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_STATE_BYTES * 4) throw fail("STATE_CORRUPT");
|
|
25778
|
-
return
|
|
25904
|
+
return createHash8("sha256").update(await readFile2(this.statePath())).digest("hex");
|
|
25779
25905
|
} catch (error62) {
|
|
25780
25906
|
if (error62.code === "ENOENT") return null;
|
|
25781
25907
|
throw error62;
|
|
@@ -25786,7 +25912,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25786
25912
|
const plaintext = Buffer.from(JSON.stringify(validateState(state)), "utf8");
|
|
25787
25913
|
if (plaintext.length > MAX_STATE_BYTES) throw fail("STATE_TOO_LARGE");
|
|
25788
25914
|
const encrypted = await this.protect(plaintext);
|
|
25789
|
-
const temp =
|
|
25915
|
+
const temp = path5.join(this.dir, "owner-state." + randomBytes3(8).toString("hex") + ".tmp");
|
|
25790
25916
|
const handle = await open3(temp, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
|
|
25791
25917
|
let closed = false;
|
|
25792
25918
|
try {
|
|
@@ -25838,6 +25964,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25838
25964
|
state.workspaces = [];
|
|
25839
25965
|
state.programs = {};
|
|
25840
25966
|
state.pendingWorkspaceRevokes = [];
|
|
25967
|
+
delete state.permissionHistory;
|
|
25841
25968
|
}
|
|
25842
25969
|
state.enrollment = next;
|
|
25843
25970
|
await this.writeState(state, before);
|
|
@@ -25855,6 +25982,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25855
25982
|
state.workspaces = [];
|
|
25856
25983
|
state.programs = {};
|
|
25857
25984
|
state.pendingWorkspaceRevokes = [];
|
|
25985
|
+
delete state.permissionHistory;
|
|
25858
25986
|
await this.writeState(state, before);
|
|
25859
25987
|
}));
|
|
25860
25988
|
}
|
|
@@ -25974,7 +26102,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
25974
26102
|
"guard-review",
|
|
25975
26103
|
{ data: Buffer.from(summary).toString("base64") },
|
|
25976
26104
|
timeoutMs2,
|
|
25977
|
-
|
|
26105
|
+
true,
|
|
25978
26106
|
signal
|
|
25979
26107
|
);
|
|
25980
26108
|
if (typeof answer?.approved !== "boolean" || typeof answer.ownerSid !== "string" || !/^S-1-(?:[0-9]+-)+[0-9]+$/.test(answer.ownerSid)) throw fail("GUARD_REVIEW_INVALID");
|
|
@@ -26016,7 +26144,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26016
26144
|
});
|
|
26017
26145
|
}
|
|
26018
26146
|
loadPrograms() {
|
|
26019
|
-
return this.run(async () => Object.fromEntries(Object.entries((await this.readState())?.programs ?? {}).map(([
|
|
26147
|
+
return this.run(async () => Object.fromEntries(Object.entries((await this.readState())?.programs ?? {}).map(([name2, executable]) => [name2, { executable }])));
|
|
26020
26148
|
}
|
|
26021
26149
|
loadPendingWorkspaceRevokes() {
|
|
26022
26150
|
return this.run(async () => [...(await this.readState())?.pendingWorkspaceRevokes ?? []]);
|
|
@@ -26036,7 +26164,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26036
26164
|
const before = await this.rawHash();
|
|
26037
26165
|
state.workspaces = state.workspaces.filter((w) => w.workspaceGrantId !== id);
|
|
26038
26166
|
state.pendingWorkspaceRevokes = [...state.pendingWorkspaceRevokes ?? [], id];
|
|
26039
|
-
for (const
|
|
26167
|
+
for (const name2 of Object.keys(state.programs ?? {})) if (!state.workspaces.some((w) => w.allowedCommands.includes(name2))) delete state.programs[name2];
|
|
26040
26168
|
await this.writeState(state, before);
|
|
26041
26169
|
}));
|
|
26042
26170
|
}
|
|
@@ -26049,51 +26177,112 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26049
26177
|
await this.writeState(state, before);
|
|
26050
26178
|
}));
|
|
26051
26179
|
}
|
|
26052
|
-
setWorkspacePermissions(change) {
|
|
26053
|
-
|
|
26054
|
-
|
|
26180
|
+
async setWorkspacePermissions(change) {
|
|
26181
|
+
const request = parsePermissionChange(change);
|
|
26182
|
+
const context = (state) => {
|
|
26055
26183
|
if (!state?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
}
|
|
26066
|
-
|
|
26067
|
-
|
|
26068
|
-
|
|
26069
|
-
|
|
26070
|
-
|
|
26071
|
-
|
|
26072
|
-
|
|
26073
|
-
|
|
26074
|
-
|
|
26075
|
-
|
|
26076
|
-
|
|
26077
|
-
|
|
26078
|
-
} else workspace.allowedCommands = workspace.allowedCommands.filter((value) => value !== name);
|
|
26079
|
-
workspace.permissions.executeCommands = workspace.allowedCommands.length > 0;
|
|
26080
|
-
} else throw fail("PERMISSION_INVALID");
|
|
26081
|
-
state.workspaces = state.workspaces.map((w) => w.workspaceGrantId === workspace.workspaceGrantId ? workspace : w);
|
|
26082
|
-
for (const name of Object.keys(programs)) if (!state.workspaces.some((w) => w.allowedCommands.includes(name))) delete programs[name];
|
|
26083
|
-
state.programs = programs;
|
|
26184
|
+
const workspace = state.workspaces?.find((w) => w.workspaceGrantId === request.workspaceGrantId);
|
|
26185
|
+
if (!workspace) throw fail("WORKSPACE_UNKNOWN");
|
|
26186
|
+
const programs = state.programs ?? {};
|
|
26187
|
+
return { workspace, programs, enrollment: state.enrollment, fingerprint: permissionHash({
|
|
26188
|
+
enrollment: state.enrollment,
|
|
26189
|
+
workspace,
|
|
26190
|
+
programs: Object.entries(programs).sort(([a], [b]) => a.localeCompare(b)),
|
|
26191
|
+
guard: state.guard ?? null,
|
|
26192
|
+
guardRequired: state.guardRequired ?? false
|
|
26193
|
+
}) };
|
|
26194
|
+
};
|
|
26195
|
+
const initial = await this.run(async () => context(await this.readState()));
|
|
26196
|
+
const proposal = await planPermissionChange(initial.workspace, initial.programs, request);
|
|
26197
|
+
const review = await this.reviewGuard("Account: " + permissionLabel(initial.enrollment.accountEmail) + "\n" + proposal.summary, void 0, 6e4);
|
|
26198
|
+
if (!review.approved) throw fail("PERMISSION_REJECTED");
|
|
26199
|
+
return this.run(() => this.withLockWait(async () => {
|
|
26200
|
+
const state = await this.readState();
|
|
26201
|
+
const current2 = context(state);
|
|
26202
|
+
if (current2.fingerprint !== initial.fingerprint) throw fail("PERMISSION_STATE_CHANGED");
|
|
26203
|
+
if (state.guard && state.guard.ownerSid !== review.ownerSid) throw fail("PERMISSION_OWNER_MISMATCH");
|
|
26204
|
+
const verified = await planPermissionChange(current2.workspace, current2.programs, request);
|
|
26205
|
+
if (verified.resourceHash !== proposal.resourceHash) throw fail("PERMISSION_STATE_CHANGED");
|
|
26084
26206
|
const before = await this.rawHash();
|
|
26207
|
+
state.workspaces = state.workspaces.map((w) => w.workspaceGrantId === verified.workspace.workspaceGrantId ? verified.workspace : w);
|
|
26208
|
+
state.programs = verified.programs;
|
|
26209
|
+
for (const name2 of Object.keys(state.programs)) if (!state.workspaces.some((w) => w.allowedCommands.includes(name2))) delete state.programs[name2];
|
|
26210
|
+
const record2 = {
|
|
26211
|
+
id: randomUUID3(),
|
|
26212
|
+
changedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26213
|
+
workspaceGrantId: request.workspaceGrantId,
|
|
26214
|
+
kind: request.kind,
|
|
26215
|
+
enabled: request.enabled,
|
|
26216
|
+
resourceHash: verified.resourceHash,
|
|
26217
|
+
ownerSid: review.ownerSid
|
|
26218
|
+
};
|
|
26219
|
+
state.permissionHistory = PermissionHistorySchema.parse([...state.permissionHistory ?? [], record2].slice(-PERMISSION_HISTORY_LIMIT));
|
|
26085
26220
|
await this.writeState(state, before);
|
|
26086
|
-
return workspace;
|
|
26221
|
+
return structuredClone(verified.workspace);
|
|
26222
|
+
}, 5e3));
|
|
26223
|
+
}
|
|
26224
|
+
/** Replace scope only after an explicit local dialog; updates never call this implicitly. */
|
|
26225
|
+
async configureAccess() {
|
|
26226
|
+
const expected = await this.run(() => this.withLock(async () => {
|
|
26227
|
+
if (!(await this.readState())?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
26228
|
+
return this.rawHash();
|
|
26229
|
+
}));
|
|
26230
|
+
const decision = await this.chooseAccess();
|
|
26231
|
+
if (decision === null) return null;
|
|
26232
|
+
const selected = parseAccessSelection(decision);
|
|
26233
|
+
if (!selected) return null;
|
|
26234
|
+
const roots = [];
|
|
26235
|
+
for (const chosen of selected.paths) {
|
|
26236
|
+
const stat = await lstat3(chosen);
|
|
26237
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw fail("WORKSPACE_INVALID");
|
|
26238
|
+
const root = await realpath3(chosen);
|
|
26239
|
+
const isDrive = equalPath2(root, path5.parse(root).root);
|
|
26240
|
+
if (!equalPath2(root, chosen) || (selected.mode === "all-drives" ? !isDrive : isDrive) || equalPath2(root, this.dir) || under(root, this.dir) || selected.mode === "folders" && under(this.dir, root)) throw fail("WORKSPACE_INVALID");
|
|
26241
|
+
roots.push(root);
|
|
26242
|
+
}
|
|
26243
|
+
return this.run(() => this.withLock(async () => {
|
|
26244
|
+
if (await this.rawHash() !== expected) throw fail("STATE_CHANGED");
|
|
26245
|
+
const state = await this.readState();
|
|
26246
|
+
if (!state?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
26247
|
+
const workspaces = roots.map((root) => {
|
|
26248
|
+
const rootHash = createHash8("sha256").update(process.platform === "win32" ? root.toLowerCase() : root).digest("hex");
|
|
26249
|
+
const existing = state.workspaces?.find((w) => w.rootHash === rootHash);
|
|
26250
|
+
return {
|
|
26251
|
+
workspaceGrantId: existing?.workspaceGrantId ?? randomUUID3(),
|
|
26252
|
+
root,
|
|
26253
|
+
rootHash,
|
|
26254
|
+
displayName: (path5.basename(root) || root).slice(0, 120),
|
|
26255
|
+
policyVersion: 1,
|
|
26256
|
+
permissions: { readFiles: true, writeFiles: true, executeCommands: false, localhost: false },
|
|
26257
|
+
allowedCommands: [],
|
|
26258
|
+
localhostPorts: []
|
|
26259
|
+
};
|
|
26260
|
+
});
|
|
26261
|
+
const retained = new Set(workspaces.map((w) => w.workspaceGrantId));
|
|
26262
|
+
const removed = (state.workspaces ?? []).filter((w) => !retained.has(w.workspaceGrantId)).map((w) => w.workspaceGrantId);
|
|
26263
|
+
const pending = [.../* @__PURE__ */ new Set([...state.pendingWorkspaceRevokes ?? [], ...removed])];
|
|
26264
|
+
if (pending.length > 32) throw fail("WORKSPACE_REVOKE_LIMIT");
|
|
26265
|
+
state.workspaces = workspaces;
|
|
26266
|
+
state.programs = {};
|
|
26267
|
+
state.pendingWorkspaceRevokes = pending;
|
|
26268
|
+
await this.writeState(state, expected);
|
|
26269
|
+
return structuredClone(workspaces);
|
|
26087
26270
|
}));
|
|
26088
26271
|
}
|
|
26272
|
+
chooseAccess() {
|
|
26273
|
+
return this.run(async () => parseAccessSelection(await _WindowsPrivateStore.invoke(this.bridge, "choose-access", {}, 24e4, true)));
|
|
26274
|
+
}
|
|
26275
|
+
loadPermissionHistory() {
|
|
26276
|
+
return this.run(async () => structuredClone((await this.readState())?.permissionHistory ?? []));
|
|
26277
|
+
}
|
|
26089
26278
|
async approveWorkspace() {
|
|
26090
26279
|
const chosen = await this.chooseFolder();
|
|
26091
26280
|
if (chosen === null) return null;
|
|
26092
|
-
const stat = await
|
|
26281
|
+
const stat = await lstat3(chosen);
|
|
26093
26282
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw fail("WORKSPACE_INVALID");
|
|
26094
|
-
const root = await
|
|
26095
|
-
if (!
|
|
26096
|
-
const rootHash =
|
|
26283
|
+
const root = await realpath3(chosen);
|
|
26284
|
+
if (!equalPath2(root, chosen) || equalPath2(root, path5.parse(root).root) || equalPath2(root, this.dir) || under(this.dir, root)) throw fail("WORKSPACE_INVALID");
|
|
26285
|
+
const rootHash = createHash8("sha256").update(process.platform === "win32" ? root.toLowerCase() : root).digest("hex");
|
|
26097
26286
|
return this.run(() => this.withLock(async () => {
|
|
26098
26287
|
const state = await this.readState();
|
|
26099
26288
|
if (!state?.enrollment) throw fail("ENROLLMENT_REQUIRED");
|
|
@@ -26101,7 +26290,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26101
26290
|
if (existing) return existing;
|
|
26102
26291
|
if ((state.workspaces?.length ?? 0) >= 32) throw fail("WORKSPACE_LIMIT");
|
|
26103
26292
|
const before = await this.rawHash();
|
|
26104
|
-
const workspace = { workspaceGrantId: randomUUID3(), root, rootHash, displayName:
|
|
26293
|
+
const workspace = { workspaceGrantId: randomUUID3(), root, rootHash, displayName: path5.basename(root).slice(0, 120), policyVersion: 1, permissions: { readFiles: true, writeFiles: true, executeCommands: false, localhost: false }, allowedCommands: [], localhostPorts: [] };
|
|
26105
26294
|
state.workspaces = [...state.workspaces ?? [], workspace];
|
|
26106
26295
|
await this.writeState(state, before);
|
|
26107
26296
|
return workspace;
|
|
@@ -26111,7 +26300,7 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26111
26300
|
return this.run(async () => {
|
|
26112
26301
|
const result2 = await _WindowsPrivateStore.invoke(this.bridge, "choose-folder", {}, 12e4, true);
|
|
26113
26302
|
if (result2.path === null) return null;
|
|
26114
|
-
if (typeof result2.path !== "string" || !
|
|
26303
|
+
if (typeof result2.path !== "string" || !path5.isAbsolute(result2.path) || result2.path.length > 4096 || /[\u0000-\u001f]/.test(result2.path)) throw fail("BRIDGE_OUTPUT_INVALID");
|
|
26115
26304
|
return result2.path;
|
|
26116
26305
|
});
|
|
26117
26306
|
}
|
|
@@ -26119,22 +26308,40 @@ var WindowsPrivateStore = class _WindowsPrivateStore {
|
|
|
26119
26308
|
|
|
26120
26309
|
// agent/src/tools.ts
|
|
26121
26310
|
import { constants as constants3 } from "node:fs";
|
|
26122
|
-
import { lstat as
|
|
26123
|
-
import
|
|
26124
|
-
import { createHash as
|
|
26311
|
+
import { lstat as lstat5, open as open4, readdir as readdir3, realpath as realpath5, rename as rename3, rm as rm3 } from "node:fs/promises";
|
|
26312
|
+
import path9 from "node:path";
|
|
26313
|
+
import { createHash as createHash9, randomUUID as randomUUID4 } from "node:crypto";
|
|
26125
26314
|
|
|
26126
26315
|
// src/guard.ts
|
|
26127
|
-
import { lstat as
|
|
26128
|
-
import
|
|
26316
|
+
import { lstat as lstat4, realpath as realpath4 } from "node:fs/promises";
|
|
26317
|
+
import path6 from "node:path";
|
|
26129
26318
|
var SENSITIVE_NAMES = /* @__PURE__ */ new Set([
|
|
26130
26319
|
".npmrc",
|
|
26131
26320
|
".pypirc",
|
|
26132
26321
|
".git-credentials",
|
|
26133
26322
|
"id_rsa",
|
|
26134
26323
|
"id_ed25519",
|
|
26135
|
-
"
|
|
26324
|
+
"id_ecdsa",
|
|
26325
|
+
"id_dsa",
|
|
26326
|
+
".netrc",
|
|
26327
|
+
"_netrc",
|
|
26328
|
+
".pgpass",
|
|
26329
|
+
"credentials.json",
|
|
26330
|
+
"application_default_credentials.json"
|
|
26136
26331
|
]);
|
|
26137
|
-
var SENSITIVE_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx"]);
|
|
26332
|
+
var SENSITIVE_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".p12", ".pfx", ".ppk", ".kdbx"]);
|
|
26333
|
+
var SENSITIVE_IN_DIRECTORY = [
|
|
26334
|
+
[".aws", "credentials"],
|
|
26335
|
+
[".kube", "config"],
|
|
26336
|
+
[".docker", "config.json"],
|
|
26337
|
+
[".claude", ".credentials.json"],
|
|
26338
|
+
[".codex", "auth.json"],
|
|
26339
|
+
[".azure", "msal_token_cache.json"],
|
|
26340
|
+
[".azure", "accesstokens.json"],
|
|
26341
|
+
["gh", "hosts.yml"],
|
|
26342
|
+
["github cli", "hosts.yml"]
|
|
26343
|
+
];
|
|
26344
|
+
var SENSITIVE_DIRECTORIES = /* @__PURE__ */ new Set([".ssh", ".gnupg"]);
|
|
26138
26345
|
var PROTECTED_FILE_NAMES = /* @__PURE__ */ new Set([
|
|
26139
26346
|
".local-commander-oauth.json",
|
|
26140
26347
|
"local-commander.config.json"
|
|
@@ -26144,12 +26351,12 @@ function comparePath(value) {
|
|
|
26144
26351
|
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
26145
26352
|
}
|
|
26146
26353
|
function isInside(root, candidate) {
|
|
26147
|
-
const relative =
|
|
26148
|
-
return relative === "" || !
|
|
26354
|
+
const relative = path6.relative(root, candidate);
|
|
26355
|
+
return relative === "" || !path6.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path6.sep}`);
|
|
26149
26356
|
}
|
|
26150
26357
|
function splitPathSegments(filePath) {
|
|
26151
|
-
const parsed =
|
|
26152
|
-
return
|
|
26358
|
+
const parsed = path6.parse(path6.resolve(filePath));
|
|
26359
|
+
return path6.relative(parsed.root, path6.resolve(filePath)).split(path6.sep).filter((segment) => segment.length > 0);
|
|
26153
26360
|
}
|
|
26154
26361
|
function isProtectedAuditFile(base) {
|
|
26155
26362
|
return base === ".local-commander-audit.jsonl" || base.startsWith(".local-commander-audit.jsonl.");
|
|
@@ -26164,37 +26371,40 @@ function looksProtected(filePath) {
|
|
|
26164
26371
|
return false;
|
|
26165
26372
|
}
|
|
26166
26373
|
function looksSensitive(filePath) {
|
|
26167
|
-
const base =
|
|
26374
|
+
const base = path6.basename(filePath).toLowerCase();
|
|
26168
26375
|
if (base === ".env" || base.startsWith(".env.") && base !== ".env.example") return true;
|
|
26169
26376
|
if (SENSITIVE_NAMES.has(base)) return true;
|
|
26170
|
-
if (SENSITIVE_EXTENSIONS.has(
|
|
26171
|
-
|
|
26377
|
+
if (SENSITIVE_EXTENSIONS.has(path6.extname(base))) return true;
|
|
26378
|
+
const segments = splitPathSegments(filePath).map((segment) => segment.toLowerCase());
|
|
26379
|
+
if (segments.slice(0, -1).some((segment) => SENSITIVE_DIRECTORIES.has(segment))) return true;
|
|
26380
|
+
const parent = segments.at(-2);
|
|
26381
|
+
return SENSITIVE_IN_DIRECTORY.some(([directory, name2]) => directory === parent && name2 === base);
|
|
26172
26382
|
}
|
|
26173
26383
|
async function canonicalizePossiblyMissing(inputPath, base) {
|
|
26174
|
-
const absolute2 =
|
|
26175
|
-
const parsed =
|
|
26384
|
+
const absolute2 = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(base, inputPath);
|
|
26385
|
+
const parsed = path6.parse(absolute2);
|
|
26176
26386
|
let current2 = absolute2;
|
|
26177
26387
|
const missing = [];
|
|
26178
26388
|
while (current2 !== parsed.root) {
|
|
26179
26389
|
try {
|
|
26180
|
-
const resolved = await
|
|
26181
|
-
return
|
|
26390
|
+
const resolved = await realpath4(current2);
|
|
26391
|
+
return path6.join(resolved, ...missing.reverse());
|
|
26182
26392
|
} catch (error62) {
|
|
26183
26393
|
if (error62.code !== "ENOENT") throw error62;
|
|
26184
26394
|
let exists = false;
|
|
26185
26395
|
try {
|
|
26186
|
-
await
|
|
26396
|
+
await lstat4(current2);
|
|
26187
26397
|
exists = true;
|
|
26188
26398
|
} catch (statError) {
|
|
26189
26399
|
if (statError.code !== "ENOENT") throw statError;
|
|
26190
26400
|
}
|
|
26191
26401
|
if (exists) throw error62;
|
|
26192
|
-
missing.push(
|
|
26193
|
-
current2 =
|
|
26402
|
+
missing.push(path6.basename(current2));
|
|
26403
|
+
current2 = path6.dirname(current2);
|
|
26194
26404
|
}
|
|
26195
26405
|
}
|
|
26196
|
-
const root = await
|
|
26197
|
-
return
|
|
26406
|
+
const root = await realpath4(parsed.root);
|
|
26407
|
+
return path6.join(root, ...missing.reverse());
|
|
26198
26408
|
}
|
|
26199
26409
|
var PathGuard = class _PathGuard {
|
|
26200
26410
|
constructor(roots, allowSensitiveFiles, protectedPaths) {
|
|
@@ -26210,11 +26420,11 @@ var PathGuard = class _PathGuard {
|
|
|
26210
26420
|
for (const root of roots) {
|
|
26211
26421
|
let resolved;
|
|
26212
26422
|
try {
|
|
26213
|
-
resolved = await
|
|
26423
|
+
resolved = await realpath4(path6.resolve(root));
|
|
26214
26424
|
} catch {
|
|
26215
26425
|
throw new Error(`Configured root does not exist: ${root}`);
|
|
26216
26426
|
}
|
|
26217
|
-
const stat = await
|
|
26427
|
+
const stat = await lstat4(resolved);
|
|
26218
26428
|
if (!stat.isDirectory()) throw new Error(`Configured root is not a directory: ${root}`);
|
|
26219
26429
|
if (!canonical2.some((existing) => comparePath(existing) === comparePath(resolved))) {
|
|
26220
26430
|
canonical2.push(resolved);
|
|
@@ -26223,8 +26433,8 @@ var PathGuard = class _PathGuard {
|
|
|
26223
26433
|
if (canonical2.length === 0) throw new Error("At least one allowed root is required");
|
|
26224
26434
|
const protectedPaths = [];
|
|
26225
26435
|
for (const protectedPath of options.protectedPaths ?? []) {
|
|
26226
|
-
const base =
|
|
26227
|
-
const absolute2 =
|
|
26436
|
+
const base = path6.isAbsolute(protectedPath) ? path6.parse(path6.resolve(protectedPath)).root : canonical2[0];
|
|
26437
|
+
const absolute2 = path6.resolve(base, protectedPath);
|
|
26228
26438
|
if (!protectedPaths.some((existing) => comparePath(existing) === comparePath(absolute2))) protectedPaths.push(absolute2);
|
|
26229
26439
|
const resolved = await canonicalizePossiblyMissing(protectedPath, base);
|
|
26230
26440
|
if (!protectedPaths.some((existing) => comparePath(existing) === comparePath(resolved))) {
|
|
@@ -26240,7 +26450,7 @@ var PathGuard = class _PathGuard {
|
|
|
26240
26450
|
}
|
|
26241
26451
|
assertNotProtected(candidate) {
|
|
26242
26452
|
if (looksProtected(candidate)) {
|
|
26243
|
-
throw new Error(`Local Commander protected state is blocked: ${
|
|
26453
|
+
throw new Error(`Local Commander protected state is blocked: ${path6.basename(candidate)}`);
|
|
26244
26454
|
}
|
|
26245
26455
|
const normalizedCandidate = comparePath(candidate);
|
|
26246
26456
|
for (const protectedPath of this.protectedPaths) {
|
|
@@ -26253,37 +26463,37 @@ var PathGuard = class _PathGuard {
|
|
|
26253
26463
|
this.assertNotProtected(filePath);
|
|
26254
26464
|
if (!this.allowSensitiveFiles && looksSensitive(filePath)) {
|
|
26255
26465
|
throw new Error(
|
|
26256
|
-
`Sensitive file access is blocked by default: ${
|
|
26466
|
+
`Sensitive file access is blocked by default: ${path6.basename(filePath)}. Enable files.allowSensitiveFiles only if you really need it.`
|
|
26257
26467
|
);
|
|
26258
26468
|
}
|
|
26259
26469
|
}
|
|
26260
26470
|
async resolveExisting(inputPath, cwd) {
|
|
26261
26471
|
const base = cwd ? await this.resolveExisting(cwd) : this.roots[0];
|
|
26262
|
-
const candidate =
|
|
26472
|
+
const candidate = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(base, inputPath);
|
|
26263
26473
|
this.assertNotProtected(candidate);
|
|
26264
|
-
const resolved = await
|
|
26474
|
+
const resolved = await realpath4(candidate);
|
|
26265
26475
|
this.assertWithinRoots(resolved);
|
|
26266
26476
|
this.assertNotProtected(resolved);
|
|
26267
26477
|
return resolved;
|
|
26268
26478
|
}
|
|
26269
26479
|
async resolveDirectory(inputPath, cwd) {
|
|
26270
26480
|
const resolved = await this.resolveExisting(inputPath, cwd);
|
|
26271
|
-
const stat = await
|
|
26481
|
+
const stat = await lstat4(resolved);
|
|
26272
26482
|
if (!stat.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
26273
26483
|
return resolved;
|
|
26274
26484
|
}
|
|
26275
26485
|
async resolveForWrite(inputPath, cwd) {
|
|
26276
26486
|
const base = cwd ? await this.resolveDirectory(cwd) : this.roots[0];
|
|
26277
|
-
const candidate =
|
|
26487
|
+
const candidate = path6.isAbsolute(inputPath) ? path6.resolve(inputPath) : path6.resolve(base, inputPath);
|
|
26278
26488
|
this.assertNotProtected(candidate);
|
|
26279
26489
|
let existing;
|
|
26280
26490
|
try {
|
|
26281
|
-
existing = await
|
|
26491
|
+
existing = await realpath4(candidate);
|
|
26282
26492
|
} catch (error62) {
|
|
26283
26493
|
if (error62.code !== "ENOENT") throw error62;
|
|
26284
26494
|
let exists = false;
|
|
26285
26495
|
try {
|
|
26286
|
-
await
|
|
26496
|
+
await lstat4(candidate);
|
|
26287
26497
|
exists = true;
|
|
26288
26498
|
} catch (statError) {
|
|
26289
26499
|
if (statError.code !== "ENOENT") throw statError;
|
|
@@ -26296,11 +26506,11 @@ var PathGuard = class _PathGuard {
|
|
|
26296
26506
|
this.assertSafeFile(existing);
|
|
26297
26507
|
return existing;
|
|
26298
26508
|
}
|
|
26299
|
-
const parent = await
|
|
26509
|
+
const parent = await realpath4(path6.dirname(candidate));
|
|
26300
26510
|
this.assertWithinRoots(parent);
|
|
26301
26511
|
this.assertNotProtected(parent);
|
|
26302
26512
|
this.assertSafeFile(candidate);
|
|
26303
|
-
const target =
|
|
26513
|
+
const target = path6.join(parent, path6.basename(candidate));
|
|
26304
26514
|
this.assertSafeFile(target);
|
|
26305
26515
|
return target;
|
|
26306
26516
|
}
|
|
@@ -26311,12 +26521,12 @@ var PathGuard = class _PathGuard {
|
|
|
26311
26521
|
|
|
26312
26522
|
// src/windows-file-read.ts
|
|
26313
26523
|
import { execFile } from "node:child_process";
|
|
26314
|
-
import
|
|
26524
|
+
import path7 from "node:path";
|
|
26315
26525
|
function readWindowsFile(bridge, file2, maxBytes, signal) {
|
|
26316
26526
|
return invokeRead(bridge, file2, maxBytes, false, signal);
|
|
26317
26527
|
}
|
|
26318
26528
|
function invokeRead(bridge, file2, maxBytes, prefix, signal) {
|
|
26319
|
-
if (process.platform !== "win32" || !
|
|
26529
|
+
if (process.platform !== "win32" || !path7.isAbsolute(bridge) || !path7.isAbsolute(file2) || !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > (prefix ? 16777216 : 1048576)) {
|
|
26320
26530
|
return Promise.reject(new Error("FILE_READ_ARGUMENT_INVALID"));
|
|
26321
26531
|
}
|
|
26322
26532
|
if (signal?.aborted) return Promise.reject(new Error("CANCELLED"));
|
|
@@ -26345,9 +26555,9 @@ function invokeRead(bridge, file2, maxBytes, prefix, signal) {
|
|
|
26345
26555
|
|
|
26346
26556
|
// agent/src/job-supervisor.ts
|
|
26347
26557
|
import { spawn as spawn2 } from "node:child_process";
|
|
26348
|
-
import
|
|
26558
|
+
import path8 from "node:path";
|
|
26349
26559
|
function absolute(value) {
|
|
26350
|
-
return typeof value === "string" && value.length > 0 && value.length < 32767 && !value.includes("\0") &&
|
|
26560
|
+
return typeof value === "string" && value.length > 0 && value.length < 32767 && !value.includes("\0") && path8.isAbsolute(value) && path8.resolve(value) === value;
|
|
26351
26561
|
}
|
|
26352
26562
|
async function runSupervised(helper, request, options = {}) {
|
|
26353
26563
|
if (process.platform !== "win32") throw Error("WINDOWS_SUPERVISOR_REQUIRED");
|
|
@@ -26363,7 +26573,7 @@ async function runSupervised(helper, request, options = {}) {
|
|
|
26363
26573
|
let pid = null, outcome = null;
|
|
26364
26574
|
let buffer = "", failure5 = null, closed = false, cancelSent = false;
|
|
26365
26575
|
let killTimer, hardTimer;
|
|
26366
|
-
const child = spawn2(helper, [], { cwd:
|
|
26576
|
+
const child = spawn2(helper, [], { cwd: path8.dirname(helper), stdio: ["pipe", "pipe", "pipe"], windowsHide: true, shell: false, env: buildChildEnvironment(process.env) });
|
|
26367
26577
|
const cancel = () => {
|
|
26368
26578
|
if (closed || cancelSent) return;
|
|
26369
26579
|
cancelSent = true;
|
|
@@ -26465,9 +26675,9 @@ function fail2(code) {
|
|
|
26465
26675
|
throw new NativeToolError(code);
|
|
26466
26676
|
}
|
|
26467
26677
|
function hash5(value) {
|
|
26468
|
-
return
|
|
26678
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
26469
26679
|
}
|
|
26470
|
-
function
|
|
26680
|
+
function equalPath3(a, b) {
|
|
26471
26681
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
26472
26682
|
}
|
|
26473
26683
|
function result(body) {
|
|
@@ -26479,7 +26689,7 @@ function stopped(signal) {
|
|
|
26479
26689
|
if (signal.aborted) fail2("CANCELLED");
|
|
26480
26690
|
}
|
|
26481
26691
|
async function readBoundedFile(file2, maxBytes, bridge, signal) {
|
|
26482
|
-
const stat = await
|
|
26692
|
+
const stat = await lstat5(file2);
|
|
26483
26693
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > maxBytes) fail2("FILE_UNSUPPORTED_OR_TOO_LARGE");
|
|
26484
26694
|
if (process.platform === "win32") return readWindowsFile(bridge, file2, maxBytes, signal);
|
|
26485
26695
|
const handle = await open4(file2, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
@@ -26517,7 +26727,7 @@ var NativeTools = class {
|
|
|
26517
26727
|
options;
|
|
26518
26728
|
async readFile(file2, maxBytes, signal) {
|
|
26519
26729
|
try {
|
|
26520
|
-
return await readBoundedFile(file2, maxBytes, this.options.ownerBridgePath ??
|
|
26730
|
+
return await readBoundedFile(file2, maxBytes, this.options.ownerBridgePath ?? path9.join(path9.dirname(this.options.helperPath), "OwnerBridge.exe"), signal);
|
|
26521
26731
|
} catch (error62) {
|
|
26522
26732
|
if (error62.code === "ENOENT") throw error62;
|
|
26523
26733
|
if (error62 instanceof Error && /^[A-Z_]{1,40}$/.test(error62.message)) fail2(error62.message);
|
|
@@ -26540,9 +26750,19 @@ var NativeTools = class {
|
|
|
26540
26750
|
if (scope2 === "files:read" && !permission.readFiles || scope2 === "files:write" && !permission.writeFiles || scope2 === "commands:execute" && !permission.executeCommands || scope2 === "localhost:access" && !permission.localhost) fail2("LOCAL_PERMISSION_REQUIRED");
|
|
26541
26751
|
let guard;
|
|
26542
26752
|
try {
|
|
26543
|
-
const canonical2 = await
|
|
26544
|
-
if (!
|
|
26545
|
-
|
|
26753
|
+
const canonical2 = await realpath5(workspace.root);
|
|
26754
|
+
if (!equalPath3(canonical2, workspace.root) || hash5(process.platform === "win32" ? canonical2.toLowerCase() : canonical2) !== workspace.rootHash) fail2("WORKSPACE_CHANGED");
|
|
26755
|
+
const runtimeDirectory = path9.dirname(this.options.helperPath);
|
|
26756
|
+
const protectedPaths = [
|
|
26757
|
+
this.options.stateDirectory,
|
|
26758
|
+
this.options.helperPath,
|
|
26759
|
+
...["agent.mjs", "node.exe", "OwnerBridge.exe", "LocalCommanderAgent.exe"].map((name2) => path9.join(runtimeDirectory, name2))
|
|
26760
|
+
];
|
|
26761
|
+
if (this.options.ownerBridgePath) protectedPaths.push(this.options.ownerBridgePath);
|
|
26762
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA && path9.isAbsolute(process.env.LOCALAPPDATA)) {
|
|
26763
|
+
protectedPaths.push(path9.join(process.env.LOCALAPPDATA, "LocalCommander"));
|
|
26764
|
+
}
|
|
26765
|
+
guard = await PathGuard.create([canonical2], false, { protectedPaths });
|
|
26546
26766
|
} catch (e) {
|
|
26547
26767
|
if (e instanceof NativeToolError) throw e;
|
|
26548
26768
|
fail2("WORKSPACE_UNAVAILABLE");
|
|
@@ -26568,11 +26788,11 @@ var NativeTools = class {
|
|
|
26568
26788
|
return { call, claims: readClaim(filename), run: async (signal) => {
|
|
26569
26789
|
stopped(signal);
|
|
26570
26790
|
const verified = await resolve(call.args.path);
|
|
26571
|
-
if (!
|
|
26791
|
+
if (!equalPath3(verified, filename)) fail2("FILE_CHANGED");
|
|
26572
26792
|
const bytes3 = await this.readFile(filename, 1048576, signal), contents = utf8(bytes3), lines = contents.split(/\r?\n/);
|
|
26573
26793
|
const selected = lines.slice(first - 1, Math.min(last, lines.length)).join("\n");
|
|
26574
26794
|
if (Buffer.byteLength(selected) > 12e4) fail2("OUTPUT_LIMIT_EXCEEDED");
|
|
26575
|
-
return result({ path:
|
|
26795
|
+
return result({ path: path9.relative(workspace.root, filename), sha256: hash5(bytes3), startLine: first, endLine: Math.min(last, lines.length), totalLines: lines.length, content: selected, truncated: last < lines.length });
|
|
26576
26796
|
} };
|
|
26577
26797
|
}
|
|
26578
26798
|
if (call.name === "fs_write" || call.name === "fs_replace") {
|
|
@@ -26587,7 +26807,7 @@ var NativeTools = class {
|
|
|
26587
26807
|
return { call, claims: writeClaim(filename), run: async (signal) => {
|
|
26588
26808
|
stopped(signal);
|
|
26589
26809
|
const target = await guard.resolveForWrite(args.path);
|
|
26590
|
-
if (!
|
|
26810
|
+
if (!equalPath3(filename, target)) fail2("FILE_CHANGED");
|
|
26591
26811
|
let old = null;
|
|
26592
26812
|
try {
|
|
26593
26813
|
old = await this.readFile(target, 1048576, signal);
|
|
@@ -26608,7 +26828,7 @@ var NativeTools = class {
|
|
|
26608
26828
|
}
|
|
26609
26829
|
const bytes3 = Buffer.from(contents, "utf8");
|
|
26610
26830
|
if (bytes3.length > MAX_FILE_WRITE_BYTES) fail2("FILE_WRITE_LIMIT");
|
|
26611
|
-
const temp =
|
|
26831
|
+
const temp = path9.join(path9.dirname(target), ".lc-write-" + randomUUID4() + ".tmp");
|
|
26612
26832
|
const handle = await open4(temp, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
|
|
26613
26833
|
try {
|
|
26614
26834
|
await handle.writeFile(bytes3);
|
|
@@ -26616,7 +26836,7 @@ var NativeTools = class {
|
|
|
26616
26836
|
await handle.close();
|
|
26617
26837
|
stopped(signal);
|
|
26618
26838
|
const again = await guard.resolveForWrite(args.path);
|
|
26619
|
-
if (!
|
|
26839
|
+
if (!equalPath3(target, again)) fail2("FILE_CHANGED");
|
|
26620
26840
|
if (old) {
|
|
26621
26841
|
if (hash5(await this.readFile(target, 1048576, signal)) !== hash5(old)) fail2("FILE_CHANGED");
|
|
26622
26842
|
await rename3(temp, target);
|
|
@@ -26625,7 +26845,7 @@ var NativeTools = class {
|
|
|
26625
26845
|
await link(temp, target);
|
|
26626
26846
|
await rm3(temp);
|
|
26627
26847
|
}
|
|
26628
|
-
return result({ path:
|
|
26848
|
+
return result({ path: path9.relative(workspace.root, target), bytes: bytes3.length, sha256: hash5(bytes3), ...call.name === "fs_replace" ? { replacements } : {} });
|
|
26629
26849
|
} catch (e) {
|
|
26630
26850
|
await handle.close().catch(() => {
|
|
26631
26851
|
});
|
|
@@ -26646,12 +26866,12 @@ var NativeTools = class {
|
|
|
26646
26866
|
stopped(signal);
|
|
26647
26867
|
if (matches2.length >= call.args.maxResults || scanned >= 300) break;
|
|
26648
26868
|
try {
|
|
26649
|
-
const filename = await guard.resolveExisting(
|
|
26869
|
+
const filename = await guard.resolveExisting(path9.join(base, entry.path));
|
|
26650
26870
|
guard.assertSafeFile(filename);
|
|
26651
26871
|
const data = utf8(await this.readFile(filename, 256e3, signal));
|
|
26652
26872
|
scanned++;
|
|
26653
26873
|
const lines = data.split(/\r?\n/);
|
|
26654
|
-
for (let i = 0; i < lines.length && matches2.length < call.args.maxResults; i++) if ((call.args.caseSensitive ? lines[i] : lines[i].toLowerCase()).includes(query)) matches2.push({ path:
|
|
26874
|
+
for (let i = 0; i < lines.length && matches2.length < call.args.maxResults; i++) if ((call.args.caseSensitive ? lines[i] : lines[i].toLowerCase()).includes(query)) matches2.push({ path: path9.relative(workspace.root, filename), line: i + 1, text: lines[i].slice(0, 500) });
|
|
26655
26875
|
} catch (error62) {
|
|
26656
26876
|
if (error62 instanceof NativeToolError && ["FILE_READ_FAILED", "CANCELLED"].includes(error62.code)) throw error62;
|
|
26657
26877
|
}
|
|
@@ -26663,9 +26883,9 @@ var NativeTools = class {
|
|
|
26663
26883
|
const url2 = new URL(call.args.url), port = Number(url2.port || (url2.protocol === "https:" ? 443 : 80));
|
|
26664
26884
|
if (!["http:", "https:"].includes(url2.protocol) || !["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname) || url2.username || url2.password || url2.hash || !workspace.localhostPorts.includes(port) || this.options.protectedPorts?.includes(port)) fail2("LOCALHOST_NOT_ALLOWED");
|
|
26665
26885
|
const headers = {};
|
|
26666
|
-
for (const [
|
|
26667
|
-
if (!/^[A-Za-z0-9-]+$/.test(
|
|
26668
|
-
headers[
|
|
26886
|
+
for (const [name2, value] of Object.entries(call.args.headers ?? {})) {
|
|
26887
|
+
if (!/^[A-Za-z0-9-]+$/.test(name2) || /[\r\n]/.test(value) || ["host", "connection", "content-length", "transfer-encoding", "upgrade", "proxy-authorization", "proxy-connection"].includes(name2.toLowerCase()) || Object.keys(headers).some((k) => k.toLowerCase() === name2.toLowerCase())) fail2("INVALID_HTTP_HEADER");
|
|
26888
|
+
headers[name2] = value;
|
|
26669
26889
|
}
|
|
26670
26890
|
if (Object.keys(headers).length > 32 || ["GET", "HEAD"].includes(call.args.method) && call.args.body !== void 0) fail2("INVALID_HTTP_ARGUMENTS");
|
|
26671
26891
|
if (url2.hostname === "localhost") url2.hostname = "127.0.0.1";
|
|
@@ -26673,9 +26893,9 @@ var NativeTools = class {
|
|
|
26673
26893
|
const combined = AbortSignal.any([signal, AbortSignal.timeout(1e4)]);
|
|
26674
26894
|
const response = await fetch(url2, { method: call.args.method, headers, body: call.args.body, redirect: "manual", signal: combined });
|
|
26675
26895
|
const selectedHeaders = {};
|
|
26676
|
-
for (const
|
|
26677
|
-
const value = response.headers.get(
|
|
26678
|
-
if (value) selectedHeaders[
|
|
26896
|
+
for (const name2 of ["content-type", "content-length", "location", "etag", "last-modified"]) {
|
|
26897
|
+
const value = response.headers.get(name2);
|
|
26898
|
+
if (value) selectedHeaders[name2] = value.slice(0, 2048);
|
|
26679
26899
|
}
|
|
26680
26900
|
const reader = response.body?.getReader(), chunks = [];
|
|
26681
26901
|
let count = 0, truncated = false;
|
|
@@ -26702,9 +26922,7 @@ var NativeTools = class {
|
|
|
26702
26922
|
}
|
|
26703
26923
|
if (call.name === "command_run") {
|
|
26704
26924
|
const cwd2 = await resolve(call.args.cwd, true), program = this.resolveProgram(call.args.command, workspace);
|
|
26705
|
-
const
|
|
26706
|
-
const claims2 = writeClaim(workspace.root);
|
|
26707
|
-
if (common2) claims2.push({ path: common2, namespace: "git", mode: "write" });
|
|
26925
|
+
const claims2 = writeClaim(cwd2);
|
|
26708
26926
|
return { call, claims: claims2, execution: { executable: program.executable, args: [...program.prefixArgs ?? [], ...call.args.args], cwd: cwd2 }, run: async (signal, onStarted, onOutput) => {
|
|
26709
26927
|
const r = await runSupervised(this.options.helperPath, { executable: program.executable, args: [...program.prefixArgs ?? [], ...call.args.args], cwd: cwd2, timeoutMs: call.args.timeoutMs, maxProcesses: 32 }, { signal, maxOutputBytes: 9e4, onStarted, onOutput });
|
|
26710
26928
|
const { pid: _pid, ...output2 } = r;
|
|
@@ -26720,9 +26938,9 @@ var NativeTools = class {
|
|
|
26720
26938
|
else if (call.name === "git_diff") {
|
|
26721
26939
|
argv = ["diff", "--no-ext-diff", "--no-textconv", ...call.args.staged ? ["--cached"] : []];
|
|
26722
26940
|
if (call.args.file) {
|
|
26723
|
-
const file2 = await resolve(
|
|
26724
|
-
const relative =
|
|
26725
|
-
if (relative.startsWith(".." +
|
|
26941
|
+
const file2 = await resolve(path9.resolve(cwd, call.args.file));
|
|
26942
|
+
const relative = path9.relative(cwd, file2);
|
|
26943
|
+
if (relative.startsWith(".." + path9.sep) || relative === ".." || path9.isAbsolute(relative)) fail2("PATH_NOT_ALLOWED");
|
|
26726
26944
|
argv.push("--", relative);
|
|
26727
26945
|
} else argv.push("--stat");
|
|
26728
26946
|
} else fail2("TOOL_MISSING");
|
|
@@ -26733,9 +26951,9 @@ var NativeTools = class {
|
|
|
26733
26951
|
const listing = await this.list(guard, cwd, 1, 150, signal);
|
|
26734
26952
|
let packageInfo = null;
|
|
26735
26953
|
try {
|
|
26736
|
-
const
|
|
26737
|
-
guard.assertSafeFile(
|
|
26738
|
-
const value = JSON.parse(utf8(await this.readFile(
|
|
26954
|
+
const name2 = await guard.resolveExisting(path9.join(cwd, "package.json"));
|
|
26955
|
+
guard.assertSafeFile(name2);
|
|
26956
|
+
const value = JSON.parse(utf8(await this.readFile(name2, 64e3, signal)));
|
|
26739
26957
|
packageInfo = { name: typeof value.name === "string" ? value.name : null, version: typeof value.version === "string" ? value.version : null, scripts: value.scripts && typeof value.scripts === "object" ? Object.keys(value.scripts).slice(0, 100) : [] };
|
|
26740
26958
|
} catch (error62) {
|
|
26741
26959
|
if (error62 instanceof NativeToolError && ["FILE_READ_FAILED", "CANCELLED"].includes(error62.code)) throw error62;
|
|
@@ -26749,14 +26967,14 @@ var NativeTools = class {
|
|
|
26749
26967
|
return { ...output2, resultCode: r.exitCode === 0 ? "SUCCESS" : "GIT_FAILED" };
|
|
26750
26968
|
} };
|
|
26751
26969
|
}
|
|
26752
|
-
resolveProgram(
|
|
26753
|
-
const entry = Object.entries(this.options.programs).find(([key, value]) => key.toLowerCase() ===
|
|
26754
|
-
if (!entry || !workspace.allowedCommands.some((v) => v.toLowerCase() === entry[0].toLowerCase()) || !
|
|
26970
|
+
resolveProgram(name2, workspace) {
|
|
26971
|
+
const entry = Object.entries(this.options.programs).find(([key, value]) => key.toLowerCase() === name2.toLowerCase() || path9.isAbsolute(name2) && equalPath3(value.executable, path9.resolve(name2)));
|
|
26972
|
+
if (!entry || !workspace.allowedCommands.some((v) => v.toLowerCase() === entry[0].toLowerCase()) || !path9.isAbsolute(entry[1].executable)) fail2("COMMAND_NOT_ALLOWED");
|
|
26755
26973
|
return { executable: entry[1].executable, prefixArgs: [...entry[1].prefixArgs ?? []] };
|
|
26756
26974
|
}
|
|
26757
26975
|
gitExecution(cwd, args) {
|
|
26758
26976
|
const program = this.options.programs.git;
|
|
26759
|
-
if (!program || !
|
|
26977
|
+
if (!program || !path9.isAbsolute(program.executable)) fail2("GIT_NOT_INSTALLED");
|
|
26760
26978
|
return { executable: program.executable, args: ["--no-pager", "--no-optional-locks", "-c", "core.fsmonitor=false", "-c", "core.hooksPath=" + (process.platform === "win32" ? "NUL" : "/dev/null"), "-c", "diff.external=", ...args], cwd };
|
|
26761
26979
|
}
|
|
26762
26980
|
async list(guard, root, depth, maxEntries, signal) {
|
|
@@ -26774,7 +26992,7 @@ var NativeTools = class {
|
|
|
26774
26992
|
break;
|
|
26775
26993
|
}
|
|
26776
26994
|
if (item.isSymbolicLink() || excluded.has(item.name.toLowerCase())) continue;
|
|
26777
|
-
const candidate =
|
|
26995
|
+
const candidate = path9.join(current2.path, item.name);
|
|
26778
26996
|
let resolved;
|
|
26779
26997
|
try {
|
|
26780
26998
|
resolved = await guard.resolveExisting(candidate);
|
|
@@ -26782,13 +27000,13 @@ var NativeTools = class {
|
|
|
26782
27000
|
} catch {
|
|
26783
27001
|
continue;
|
|
26784
27002
|
}
|
|
26785
|
-
if (!
|
|
27003
|
+
if (!equalPath3(candidate, resolved)) continue;
|
|
26786
27004
|
if (item.isDirectory()) {
|
|
26787
|
-
entries.push({ path:
|
|
27005
|
+
entries.push({ path: path9.relative(root, resolved), type: "directory" });
|
|
26788
27006
|
if (current2.level < depth) queue.push({ path: resolved, level: current2.level + 1 });
|
|
26789
27007
|
} else if (item.isFile()) {
|
|
26790
|
-
const stat = await
|
|
26791
|
-
entries.push({ path:
|
|
27008
|
+
const stat = await lstat5(resolved);
|
|
27009
|
+
entries.push({ path: path9.relative(root, resolved), type: "file", size: stat.size });
|
|
26792
27010
|
}
|
|
26793
27011
|
}
|
|
26794
27012
|
if (truncated) break;
|
|
@@ -26798,49 +27016,49 @@ var NativeTools = class {
|
|
|
26798
27016
|
async gitCommonDir(cwd, workspace, guard) {
|
|
26799
27017
|
let directory = cwd;
|
|
26800
27018
|
while (guard.isWithin(directory, workspace.root)) {
|
|
26801
|
-
const dotGit =
|
|
27019
|
+
const dotGit = path9.join(directory, ".git");
|
|
26802
27020
|
try {
|
|
26803
|
-
const stat = await
|
|
27021
|
+
const stat = await lstat5(dotGit);
|
|
26804
27022
|
if (stat.isSymbolicLink()) fail2("GIT_METADATA_CHANGED");
|
|
26805
27023
|
let gitDir;
|
|
26806
|
-
if (stat.isDirectory()) gitDir = await
|
|
27024
|
+
if (stat.isDirectory()) gitDir = await realpath5(dotGit);
|
|
26807
27025
|
else {
|
|
26808
27026
|
const content = utf8(await this.readFile(dotGit, 4096)).trim();
|
|
26809
27027
|
if (!content.startsWith("gitdir: ")) fail2("GIT_METADATA_CHANGED");
|
|
26810
|
-
gitDir = await
|
|
27028
|
+
gitDir = await realpath5(path9.resolve(directory, content.slice(8)));
|
|
26811
27029
|
}
|
|
26812
27030
|
if (guard.isWithin(gitDir, this.options.stateDirectory) || !guard.isWithin(gitDir, workspace.root) && (!workspace.gitCommonDir || !guard.isWithin(gitDir, workspace.gitCommonDir))) fail2("GIT_COMMON_DIRECTORY_NOT_APPROVED");
|
|
26813
27031
|
let common = gitDir;
|
|
26814
27032
|
try {
|
|
26815
|
-
const content = utf8(await this.readFile(
|
|
27033
|
+
const content = utf8(await this.readFile(path9.join(gitDir, "commondir"), 4096)).trim();
|
|
26816
27034
|
if (!content) fail2("GIT_METADATA_CHANGED");
|
|
26817
|
-
common = await
|
|
27035
|
+
common = await realpath5(path9.resolve(gitDir, content));
|
|
26818
27036
|
} catch (e) {
|
|
26819
27037
|
if (e.code !== "ENOENT") throw e;
|
|
26820
27038
|
}
|
|
26821
|
-
if (!(await
|
|
26822
|
-
if (!guard.isWithin(common, workspace.root) && (!workspace.gitCommonDir || !
|
|
27039
|
+
if (!(await lstat5(common)).isDirectory() || guard.isWithin(common, this.options.stateDirectory)) fail2("GIT_METADATA_CHANGED");
|
|
27040
|
+
if (!guard.isWithin(common, workspace.root) && (!workspace.gitCommonDir || !equalPath3(common, workspace.gitCommonDir))) fail2("GIT_COMMON_DIRECTORY_NOT_APPROVED");
|
|
26823
27041
|
return common;
|
|
26824
27042
|
} catch (e) {
|
|
26825
27043
|
if (e.code !== "ENOENT") throw e;
|
|
26826
27044
|
}
|
|
26827
|
-
if (
|
|
26828
|
-
directory =
|
|
27045
|
+
if (equalPath3(directory, workspace.root)) break;
|
|
27046
|
+
directory = path9.dirname(directory);
|
|
26829
27047
|
}
|
|
26830
27048
|
return null;
|
|
26831
27049
|
}
|
|
26832
27050
|
};
|
|
26833
27051
|
|
|
26834
27052
|
// agent/src/scheduler.ts
|
|
26835
|
-
import
|
|
27053
|
+
import path10 from "node:path";
|
|
26836
27054
|
import os2 from "node:os";
|
|
26837
27055
|
function canonical(value) {
|
|
26838
|
-
const p =
|
|
27056
|
+
const p = path10.resolve(value);
|
|
26839
27057
|
return process.platform === "win32" ? p.toLowerCase() : p;
|
|
26840
27058
|
}
|
|
26841
27059
|
function ancestor(a, b) {
|
|
26842
|
-
const relative =
|
|
26843
|
-
return relative === "" || !
|
|
27060
|
+
const relative = path10.relative(a, b);
|
|
27061
|
+
return relative === "" || !path10.isAbsolute(relative) && relative !== ".." && !relative.startsWith(".." + path10.sep);
|
|
26844
27062
|
}
|
|
26845
27063
|
function claimsConflict(a, b) {
|
|
26846
27064
|
return a.some((x) => b.some((y) => (x.namespace ?? "filesystem") === (y.namespace ?? "filesystem") && (x.mode === "write" || y.mode === "write") && (ancestor(x.path, y.path) || ancestor(y.path, x.path))));
|
|
@@ -26860,7 +27078,7 @@ var NativeScheduler = class {
|
|
|
26860
27078
|
schedule(id, claims, deadlineAt, run, signal) {
|
|
26861
27079
|
if (!this.accepting) return Promise.reject(Error("AGENT_PAUSED"));
|
|
26862
27080
|
if (!id || id.length > 100 || this.active.has(id) || this.waiting.some((j) => j.id === id)) return Promise.reject(Error("DUPLICATE_SCHEDULED_EXECUTION"));
|
|
26863
|
-
if (!Array.isArray(claims) || !claims.length || claims.length > 32 || claims.some((c) => !c || !
|
|
27081
|
+
if (!Array.isArray(claims) || !claims.length || claims.length > 32 || claims.some((c) => !c || !path10.isAbsolute(c.path) || !["read", "write"].includes(c.mode))) return Promise.reject(Error("INVALID_RESOURCE_CLAIM"));
|
|
26864
27082
|
const deadline = deadlineAt.getTime();
|
|
26865
27083
|
if (!Number.isFinite(deadline) || deadline <= Date.now()) return Promise.reject(Error("ADMISSION_DEADLINE_EXPIRED"));
|
|
26866
27084
|
if (signal?.aborted) return Promise.reject(Error("CANCELLED_BEFORE_ACCEPTANCE"));
|
|
@@ -27320,17 +27538,17 @@ function unshapedResultTelemetry(result2) {
|
|
|
27320
27538
|
|
|
27321
27539
|
// packages/optimization/src/live-telemetry.ts
|
|
27322
27540
|
var LIVE_EFFICIENCY_EVENT_VERSION = 1;
|
|
27323
|
-
var
|
|
27541
|
+
var uuid15 = external_exports.string().uuid().toLowerCase();
|
|
27324
27542
|
var bytes2 = external_exports.number().int().min(0).max(1e9);
|
|
27325
27543
|
var utc4 = external_exports.iso.datetime({ offset: false });
|
|
27326
27544
|
var LiveEfficiencyEventSchema = external_exports.object({
|
|
27327
27545
|
version: external_exports.literal(LIVE_EFFICIENCY_EVENT_VERSION),
|
|
27328
27546
|
occurredAt: utc4,
|
|
27329
|
-
tenantId:
|
|
27330
|
-
accountId:
|
|
27331
|
-
sessionId:
|
|
27332
|
-
operationId:
|
|
27333
|
-
executionId:
|
|
27547
|
+
tenantId: uuid15,
|
|
27548
|
+
accountId: uuid15.nullable(),
|
|
27549
|
+
sessionId: uuid15,
|
|
27550
|
+
operationId: uuid15,
|
|
27551
|
+
executionId: uuid15,
|
|
27334
27552
|
toolName: external_exports.string().trim().min(1).max(120),
|
|
27335
27553
|
outcome: external_exports.enum(["success", "failed", "timed_out", "cancelled", "unknown"]),
|
|
27336
27554
|
parserId: external_exports.string().trim().min(1).max(120).nullable(),
|
|
@@ -27729,6 +27947,11 @@ var controlPayload = external_exports.object({ binding: ExecutionBindingSchema }
|
|
|
27729
27947
|
function failure3(code) {
|
|
27730
27948
|
return Object.assign(new Error(code), { code });
|
|
27731
27949
|
}
|
|
27950
|
+
var MAX_METRIC_BYTES = 4194304;
|
|
27951
|
+
function terminalMetrics(result2) {
|
|
27952
|
+
if (!result2) return void 0;
|
|
27953
|
+
return { stdoutBytes: Math.min(Buffer.byteLength(result2.stdout, "utf8"), MAX_METRIC_BYTES), stderrBytes: Math.min(Buffer.byteLength(result2.stderr, "utf8"), MAX_METRIC_BYTES), outputTruncated: result2.stdoutTruncated || result2.stderrTruncated };
|
|
27954
|
+
}
|
|
27732
27955
|
var NativeRelayBridge = class {
|
|
27733
27956
|
constructor(runtime, sendTerminal, journal) {
|
|
27734
27957
|
this.runtime = runtime;
|
|
@@ -27764,7 +27987,7 @@ var NativeRelayBridge = class {
|
|
|
27764
27987
|
try {
|
|
27765
27988
|
const binding2 = { tenantId: event.tenantId, deviceId: event.deviceId, grantId: event.grantId, sessionId: event.sessionId, operationId: event.operationId, executionId: id, connectionEpoch: event.connectionEpoch, workspaceGrantId: event.workspaceGrantId, policyVersion: event.policyVersion, payloadHash: event.payloadHash, deadlineAt: event.deadlineAt, ...event.accountId ? { accountId: event.accountId } : {} };
|
|
27766
27989
|
if (!["finished", "failed", "cancelled", "unknown"].includes(event.state)) continue;
|
|
27767
|
-
await this.sendTerminal(binding2, event.state, event.resultCode);
|
|
27990
|
+
await this.sendTerminal(binding2, event.state, event.resultCode, terminalMetrics(event.result));
|
|
27768
27991
|
await this.journal.markReported(id, event.resultCode);
|
|
27769
27992
|
} catch {
|
|
27770
27993
|
} finally {
|
|
@@ -27812,10 +28035,10 @@ var NativeRelayBridge = class {
|
|
|
27812
28035
|
const { binding: binding2, tool } = parsed2.data;
|
|
27813
28036
|
this.binding(request, binding2);
|
|
27814
28037
|
const prepared = await this.wait(this.execution, binding2.executionId, request, () => this.runtime.receive({ type: "prepare", version: 1, binding: binding2, tool }));
|
|
27815
|
-
if (prepared.type === "not_accepted") return { accepted: false, provedNotStarted: prepared.provedNotStarted };
|
|
28038
|
+
if (prepared.type === "not_accepted") return { accepted: false, provedNotStarted: prepared.provedNotStarted, code: prepared.code };
|
|
27816
28039
|
if (prepared.type !== "prepared") throw failure3("INFRA_UNAVAILABLE");
|
|
27817
28040
|
const accepted = await this.wait(this.execution, binding2.executionId, request, () => this.runtime.receive({ type: "execute", version: 1, binding: binding2 }));
|
|
27818
|
-
if (accepted.type === "not_accepted") return { accepted: false, provedNotStarted: accepted.provedNotStarted };
|
|
28041
|
+
if (accepted.type === "not_accepted") return { accepted: false, provedNotStarted: accepted.provedNotStarted, code: accepted.code };
|
|
27819
28042
|
if (accepted.type !== "accepted") throw failure3("INFRA_UNAVAILABLE");
|
|
27820
28043
|
return { accepted: true };
|
|
27821
28044
|
}
|
|
@@ -27878,7 +28101,7 @@ function relayUrlFromEnrollment(enrollment) {
|
|
|
27878
28101
|
url2.pathname = "/api/agent/relay";
|
|
27879
28102
|
return url2.toString();
|
|
27880
28103
|
}
|
|
27881
|
-
function
|
|
28104
|
+
function uuid16(value, code) {
|
|
27882
28105
|
if (typeof value !== "string" || !UUID2.test(value)) fail3(code);
|
|
27883
28106
|
return value.toLowerCase();
|
|
27884
28107
|
}
|
|
@@ -28008,37 +28231,38 @@ function sendJson(socket, value, maxBytes) {
|
|
|
28008
28231
|
function validateChallenge(frame, expectedProtocol) {
|
|
28009
28232
|
if (frame.type !== "challenge") fail3("RELAY_HANDSHAKE_INVALID");
|
|
28010
28233
|
protocol(frame.protocolVersion, expectedProtocol);
|
|
28011
|
-
|
|
28234
|
+
uuid16(frame.gatewayId, "RELAY_HANDSHAKE_INVALID");
|
|
28012
28235
|
if (typeof frame.nonce !== "string" || frame.nonce.length < 32 || frame.nonce.length > 512 || /[\u0000-\u001f\u007f]/u.test(frame.nonce)) fail3("RELAY_HANDSHAKE_INVALID");
|
|
28013
28236
|
if (typeof frame.expiresAt !== "string" || !Number.isFinite(Date.parse(frame.expiresAt)) || Date.parse(frame.expiresAt) <= Date.now()) fail3("RELAY_HANDSHAKE_INVALID");
|
|
28014
28237
|
return {
|
|
28015
|
-
challengeId:
|
|
28238
|
+
challengeId: uuid16(frame.challengeId, "RELAY_HANDSHAKE_INVALID"),
|
|
28016
28239
|
nonce: frame.nonce
|
|
28017
28240
|
};
|
|
28018
28241
|
}
|
|
28019
28242
|
function validateReady(frame, enrollment, expectedProtocol) {
|
|
28020
28243
|
if (frame.type !== "connected") fail3("RELAY_HANDSHAKE_INVALID");
|
|
28021
28244
|
protocol(frame.protocolVersion, expectedProtocol);
|
|
28022
|
-
if (
|
|
28023
|
-
if (
|
|
28024
|
-
|
|
28245
|
+
if (uuid16(frame.tenantId, "RELAY_HANDSHAKE_INVALID") !== enrollment.tenantId.toLowerCase()) fail3("RELAY_ID_MISMATCH");
|
|
28246
|
+
if (uuid16(frame.deviceId, "RELAY_HANDSHAKE_INVALID") !== enrollment.deviceId.toLowerCase()) fail3("RELAY_ID_MISMATCH");
|
|
28247
|
+
const terminalMetrics2 = Array.isArray(frame.capabilities) && frame.capabilities.includes(TERMINAL_METRICS_CAPABILITY);
|
|
28248
|
+
return { connectionEpoch: epoch2(frame.connectionEpoch), terminalMetrics: terminalMetrics2 };
|
|
28025
28249
|
}
|
|
28026
28250
|
function validateRequest(frame, enrollment, expectedProtocol, connectionEpoch) {
|
|
28027
28251
|
if (frame.type !== "relayFrame") fail3("RELAY_REQUEST_INVALID");
|
|
28028
28252
|
protocol(frame.protocolVersion, expectedProtocol);
|
|
28029
|
-
const tenantId =
|
|
28030
|
-
const deviceId =
|
|
28253
|
+
const tenantId = uuid16(frame.tenantId, "RELAY_REQUEST_INVALID");
|
|
28254
|
+
const deviceId = uuid16(frame.deviceId, "RELAY_REQUEST_INVALID");
|
|
28031
28255
|
if (tenantId !== enrollment.tenantId.toLowerCase() || deviceId !== enrollment.deviceId.toLowerCase()) fail3("RELAY_ID_MISMATCH");
|
|
28032
28256
|
if (epoch2(frame.connectionEpoch) !== connectionEpoch) fail3("RELAY_EPOCH_MISMATCH");
|
|
28033
28257
|
const kind = frame.kind;
|
|
28034
28258
|
if (typeof kind !== "string" || !REQUEST_KINDS.has(kind)) fail3("RELAY_REQUEST_INVALID");
|
|
28035
28259
|
return {
|
|
28036
|
-
requestId:
|
|
28260
|
+
requestId: uuid16(frame.requestId, "RELAY_REQUEST_INVALID"),
|
|
28037
28261
|
tenantId,
|
|
28038
28262
|
deviceId,
|
|
28039
28263
|
connectionEpoch,
|
|
28040
|
-
operationId:
|
|
28041
|
-
executionId:
|
|
28264
|
+
operationId: uuid16(frame.operationId, "RELAY_REQUEST_INVALID"),
|
|
28265
|
+
executionId: uuid16(frame.executionId, "RELAY_REQUEST_INVALID"),
|
|
28042
28266
|
kind,
|
|
28043
28267
|
payload: frame.payload,
|
|
28044
28268
|
deadlineAt: isoDate(frame.deadlineAt)
|
|
@@ -28078,8 +28302,8 @@ async function connectOnce(options) {
|
|
|
28078
28302
|
const identity = await options.store.load();
|
|
28079
28303
|
if (!identity || !identity.enrollment) fail3("ENROLLMENT_MISSING");
|
|
28080
28304
|
const enrollment = identity.enrollment;
|
|
28081
|
-
const tenantId =
|
|
28082
|
-
const deviceId =
|
|
28305
|
+
const tenantId = uuid16(enrollment.tenantId, "ENROLLMENT_INVALID");
|
|
28306
|
+
const deviceId = uuid16(enrollment.deviceId, "ENROLLMENT_INVALID");
|
|
28083
28307
|
const url2 = relayUrlFromEnrollment(enrollment);
|
|
28084
28308
|
const socket = new wrapper_default(url2, [], {
|
|
28085
28309
|
followRedirects: false,
|
|
@@ -28114,7 +28338,7 @@ async function connectOnce(options) {
|
|
|
28114
28338
|
nonce: challenge.nonce,
|
|
28115
28339
|
signature
|
|
28116
28340
|
}, maxFrameBytes);
|
|
28117
|
-
const connectionEpoch = validateReady(await waitForMessage(socket, maxFrameBytes, connectTimeoutMs, "RELAY_HANDSHAKE_TIMEOUT"), enrollment, protocolVersion2);
|
|
28341
|
+
const { connectionEpoch, terminalMetrics: terminalMetrics2 } = validateReady(await waitForMessage(socket, maxFrameBytes, connectTimeoutMs, "RELAY_HANDSHAKE_TIMEOUT"), enrollment, protocolVersion2);
|
|
28118
28342
|
socket.off("message", bufferDuringReady);
|
|
28119
28343
|
if (handshakeOverflow) fail3("RELAY_FRAME_TOO_LARGE");
|
|
28120
28344
|
handshakeBuffer.shift();
|
|
@@ -28199,10 +28423,10 @@ async function connectOnce(options) {
|
|
|
28199
28423
|
heartbeat: () => {
|
|
28200
28424
|
if (socket.readyState !== wrapper_default.OPEN) fail3("RELAY_SOCKET_CLOSED");
|
|
28201
28425
|
},
|
|
28202
|
-
sendTerminal: (binding2, state, resultCode) => {
|
|
28426
|
+
sendTerminal: (binding2, state, resultCode, metrics) => {
|
|
28203
28427
|
if (pendingTerminal.size >= 64) return Promise.reject(new RelayClientError("RELAY_PENDING_LIMIT"));
|
|
28204
28428
|
const requestId = randomUUID5();
|
|
28205
|
-
const event = { type: "executionResult", protocolVersion: 1, requestId, tenantId, deviceId, connectionEpoch, binding: binding2, state, resultCode };
|
|
28429
|
+
const event = { type: "executionResult", protocolVersion: 1, requestId, tenantId, deviceId, connectionEpoch, binding: binding2, state, resultCode, ...terminalMetrics2 && metrics ? { metrics } : {} };
|
|
28206
28430
|
return new Promise((resolve, reject) => {
|
|
28207
28431
|
const timer = setTimeout(() => {
|
|
28208
28432
|
pendingTerminal.delete(requestId);
|
|
@@ -28299,10 +28523,10 @@ function canonicalOrigin2(value) {
|
|
|
28299
28523
|
fail4("ORIGIN_INVALID");
|
|
28300
28524
|
}
|
|
28301
28525
|
}
|
|
28302
|
-
function endpoint(origin,
|
|
28303
|
-
return origin +
|
|
28526
|
+
function endpoint(origin, path13) {
|
|
28527
|
+
return origin + path13;
|
|
28304
28528
|
}
|
|
28305
|
-
function
|
|
28529
|
+
function uuid17(value, code) {
|
|
28306
28530
|
if (typeof value !== "string" || !UUID3.test(value)) fail4(code);
|
|
28307
28531
|
return value.toLowerCase();
|
|
28308
28532
|
}
|
|
@@ -28422,7 +28646,7 @@ var PairingClient = class {
|
|
|
28422
28646
|
publicKey: identity.publicKeyPem,
|
|
28423
28647
|
agentVersion: this.version
|
|
28424
28648
|
}));
|
|
28425
|
-
const pairingId =
|
|
28649
|
+
const pairingId = uuid17(response.pairingId, "PAIRING_RESPONSE_INVALID");
|
|
28426
28650
|
const expiresAt = parseDate(response.expiresAt, "PAIRING_RESPONSE_INVALID");
|
|
28427
28651
|
const now = this.nowMs();
|
|
28428
28652
|
if (expiresAt.getTime() <= now || expiresAt.getTime() - now > 30 * 6e4) fail4("PAIRING_RESPONSE_INVALID");
|
|
@@ -28456,9 +28680,9 @@ var PairingClient = class {
|
|
|
28456
28680
|
}
|
|
28457
28681
|
async completeOnce(beginResult, approved, displayNameValue) {
|
|
28458
28682
|
const pairing = this.validateBeginResult(beginResult);
|
|
28459
|
-
const tenantId =
|
|
28683
|
+
const tenantId = uuid17(approved.tenantId, "PAIRING_RESPONSE_INVALID");
|
|
28460
28684
|
const email3 = accountEmail(approved.accountEmail);
|
|
28461
|
-
const
|
|
28685
|
+
const name2 = displayName(displayNameValue);
|
|
28462
28686
|
const identity = await this.currentIdentity();
|
|
28463
28687
|
const keyFingerprint = fingerprint2(identity.fingerprint);
|
|
28464
28688
|
if (keyFingerprint !== pairing.keyFingerprint) fail4("IDENTITY_CHANGED");
|
|
@@ -28467,18 +28691,18 @@ var PairingClient = class {
|
|
|
28467
28691
|
const response = assertJsonObject(await this.request(endpoint(this.origin, "/api/agent/pairings/" + pairing.pairingId + "/complete"), {
|
|
28468
28692
|
pairingSecret: pairing.pairingSecret,
|
|
28469
28693
|
signature,
|
|
28470
|
-
displayName:
|
|
28694
|
+
displayName: name2,
|
|
28471
28695
|
agentVersion: this.version
|
|
28472
28696
|
}));
|
|
28473
|
-
const completedTenant =
|
|
28697
|
+
const completedTenant = uuid17(response.tenantId, "PAIRING_RESPONSE_INVALID");
|
|
28474
28698
|
if (completedTenant !== tenantId) fail4("PAIRING_RESPONSE_INVALID");
|
|
28475
|
-
const deviceId =
|
|
28699
|
+
const deviceId = uuid17(response.deviceId, "PAIRING_RESPONSE_INVALID");
|
|
28476
28700
|
const enrollment = {
|
|
28477
28701
|
tenantId,
|
|
28478
28702
|
deviceId,
|
|
28479
28703
|
serverOrigin: this.origin,
|
|
28480
28704
|
accountEmail: email3,
|
|
28481
|
-
...response.accountId !== void 0 ? { accountId:
|
|
28705
|
+
...response.accountId !== void 0 ? { accountId: uuid17(response.accountId, "PAIRING_RESPONSE_INVALID") } : {}
|
|
28482
28706
|
};
|
|
28483
28707
|
await this.store.saveEnrollment(enrollment);
|
|
28484
28708
|
return { ...enrollment };
|
|
@@ -28492,7 +28716,7 @@ var PairingClient = class {
|
|
|
28492
28716
|
state: response.state,
|
|
28493
28717
|
expiresAt: parseDate(response.expiresAt, "PAIRING_RESPONSE_INVALID")
|
|
28494
28718
|
};
|
|
28495
|
-
if (response.tenantId !== void 0) result2.tenantId =
|
|
28719
|
+
if (response.tenantId !== void 0) result2.tenantId = uuid17(response.tenantId, "PAIRING_RESPONSE_INVALID");
|
|
28496
28720
|
if (response.accountEmail !== void 0) result2.accountEmail = accountEmail(response.accountEmail);
|
|
28497
28721
|
return result2;
|
|
28498
28722
|
}
|
|
@@ -28504,10 +28728,10 @@ var PairingClient = class {
|
|
|
28504
28728
|
}
|
|
28505
28729
|
validateBeginResult(value) {
|
|
28506
28730
|
return {
|
|
28507
|
-
pairingId:
|
|
28731
|
+
pairingId: uuid17(value.pairingId, "PAIRING_INVALID"),
|
|
28508
28732
|
pairingSecret: pairingSecret(value.pairingSecret),
|
|
28509
28733
|
confirmationCode: confirmationCode(value.confirmationCode),
|
|
28510
|
-
approvalUrl: approvalUrl(value.approvalUrl, this.origin,
|
|
28734
|
+
approvalUrl: approvalUrl(value.approvalUrl, this.origin, uuid17(value.pairingId, "PAIRING_INVALID")),
|
|
28511
28735
|
expiresAt: value.expiresAt instanceof Date ? new Date(value.expiresAt.getTime()) : parseDate(value.expiresAt, "PAIRING_INVALID"),
|
|
28512
28736
|
keyFingerprint: fingerprint2(value.keyFingerprint)
|
|
28513
28737
|
};
|
|
@@ -28802,30 +29026,30 @@ async function deliverGuardEvidence(target, packet, options = {}) {
|
|
|
28802
29026
|
|
|
28803
29027
|
// agent/src/optimization-ledger.ts
|
|
28804
29028
|
import { constants as constants4 } from "node:fs";
|
|
28805
|
-
import { lstat as
|
|
28806
|
-
import
|
|
29029
|
+
import { lstat as lstat6, mkdir as mkdir4, open as open5, readFile as readFile4, realpath as realpath6 } from "node:fs/promises";
|
|
29030
|
+
import path11 from "node:path";
|
|
28807
29031
|
var MAX_LEDGER_BYTES = 64 * 1024 * 1024;
|
|
28808
29032
|
var MAX_NOTICE_LEDGER_BYTES = 8 * 1024 * 1024;
|
|
28809
29033
|
function failure4(code) {
|
|
28810
29034
|
return Object.assign(new Error(code), { code });
|
|
28811
29035
|
}
|
|
28812
29036
|
async function safeDirectory2(directory) {
|
|
28813
|
-
const absolute2 =
|
|
29037
|
+
const absolute2 = path11.resolve(directory);
|
|
28814
29038
|
let current2 = absolute2;
|
|
28815
29039
|
for (; ; ) {
|
|
28816
29040
|
try {
|
|
28817
|
-
if ((await
|
|
29041
|
+
if ((await lstat6(current2)).isSymbolicLink()) throw failure4("OPTIMIZATION_LEDGER_DIRECTORY_INVALID");
|
|
28818
29042
|
} catch (error62) {
|
|
28819
29043
|
if (error62.code !== "ENOENT") throw error62;
|
|
28820
29044
|
}
|
|
28821
|
-
const parent =
|
|
29045
|
+
const parent = path11.dirname(current2);
|
|
28822
29046
|
if (parent === current2) break;
|
|
28823
29047
|
current2 = parent;
|
|
28824
29048
|
}
|
|
28825
29049
|
await mkdir4(absolute2, { recursive: true, mode: 448 });
|
|
28826
|
-
const stat = await
|
|
29050
|
+
const stat = await lstat6(absolute2);
|
|
28827
29051
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw failure4("OPTIMIZATION_LEDGER_DIRECTORY_INVALID");
|
|
28828
|
-
return
|
|
29052
|
+
return realpath6(absolute2);
|
|
28829
29053
|
}
|
|
28830
29054
|
var OptimizationLedger = class _OptimizationLedger {
|
|
28831
29055
|
constructor(filename, handle, noticesFilename) {
|
|
@@ -28843,11 +29067,11 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28843
29067
|
closed = false;
|
|
28844
29068
|
static async open(directory) {
|
|
28845
29069
|
const dir = await safeDirectory2(directory);
|
|
28846
|
-
const filename =
|
|
28847
|
-
const noticesFilename =
|
|
29070
|
+
const filename = path11.join(dir, "live-efficiency.v1.jsonl");
|
|
29071
|
+
const noticesFilename = path11.join(dir, "live-efficiency-notices.v1.jsonl");
|
|
28848
29072
|
let existing = "";
|
|
28849
29073
|
try {
|
|
28850
|
-
const stat = await
|
|
29074
|
+
const stat = await lstat6(filename);
|
|
28851
29075
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_LEDGER_BYTES) {
|
|
28852
29076
|
throw failure4("OPTIMIZATION_LEDGER_INVALID");
|
|
28853
29077
|
}
|
|
@@ -28863,7 +29087,7 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28863
29087
|
const ledger = new _OptimizationLedger(filename, handle, noticesFilename);
|
|
28864
29088
|
try {
|
|
28865
29089
|
try {
|
|
28866
|
-
const noticeStat = await
|
|
29090
|
+
const noticeStat = await lstat6(noticesFilename);
|
|
28867
29091
|
if (!noticeStat.isFile() || noticeStat.isSymbolicLink() || noticeStat.size > MAX_NOTICE_LEDGER_BYTES) throw failure4("OPTIMIZATION_NOTICE_LEDGER_INVALID");
|
|
28868
29092
|
const notices = await readFile4(noticesFilename, "utf8");
|
|
28869
29093
|
for (const line of notices.split("\n")) {
|
|
@@ -28930,7 +29154,7 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28930
29154
|
const marker = JSON.stringify({ sessionId: event.sessionId, checkpointCalls, deliveredAt: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
28931
29155
|
let currentBytes = 0;
|
|
28932
29156
|
try {
|
|
28933
|
-
const stat = await
|
|
29157
|
+
const stat = await lstat6(this.noticesFilename);
|
|
28934
29158
|
if (!stat.isFile() || stat.isSymbolicLink()) throw failure4("OPTIMIZATION_NOTICE_LEDGER_INVALID");
|
|
28935
29159
|
currentBytes = stat.size;
|
|
28936
29160
|
} catch (error62) {
|
|
@@ -28977,7 +29201,7 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
28977
29201
|
};
|
|
28978
29202
|
|
|
28979
29203
|
// agent/src/main.ts
|
|
28980
|
-
var AGENT_VERSION2 = true ? "0.3.
|
|
29204
|
+
var AGENT_VERSION2 = true ? "0.3.5" : "0.3.2";
|
|
28981
29205
|
function processAlive(pid) {
|
|
28982
29206
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
28983
29207
|
try {
|
|
@@ -28989,7 +29213,7 @@ function processAlive(pid) {
|
|
|
28989
29213
|
}
|
|
28990
29214
|
async function claimAgentProcess(stateDir, runtimeDir, version2) {
|
|
28991
29215
|
await mkdir5(stateDir, { recursive: true });
|
|
28992
|
-
const file2 =
|
|
29216
|
+
const file2 = path12.join(stateDir, "agent-process.json");
|
|
28993
29217
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
28994
29218
|
try {
|
|
28995
29219
|
const handle = await open6(file2, "wx", 384);
|
|
@@ -29045,9 +29269,9 @@ async function runAgent(options) {
|
|
|
29045
29269
|
}
|
|
29046
29270
|
const runtime = new NativeRuntime({ journal: options.journal, tools: options.tools, scheduler: new NativeScheduler(), workspaces: () => options.store.loadWorkspaces(Boolean(options.guard)), guard: options.guard, efficiencyLedger: options.efficiencyLedger });
|
|
29047
29271
|
let connection = null;
|
|
29048
|
-
const bridge = new NativeRelayBridge(runtime, (binding2, state, code) => {
|
|
29272
|
+
const bridge = new NativeRelayBridge(runtime, (binding2, state, code, metrics) => {
|
|
29049
29273
|
if (!connection) throw Error("RELAY_SOCKET_CLOSED");
|
|
29050
|
-
return connection.sendTerminal(binding2, state, code);
|
|
29274
|
+
return connection.sendTerminal(binding2, state, code, metrics);
|
|
29051
29275
|
}, options.journal);
|
|
29052
29276
|
const connect = options.connect ?? ((connectionOptions) => connectOnce(connectionOptions));
|
|
29053
29277
|
try {
|
|
@@ -29100,10 +29324,10 @@ async function runAgent(options) {
|
|
|
29100
29324
|
async function main() {
|
|
29101
29325
|
if (process.platform !== "win32") throw Error("WINDOWS_REQUIRED");
|
|
29102
29326
|
const appData = process.env.LOCALAPPDATA;
|
|
29103
|
-
if (!appData || !
|
|
29104
|
-
const stateDir = process.env.LC_AGENT_STATE_DIR ??
|
|
29105
|
-
const binaryDir =
|
|
29106
|
-
const ownerBridgePath = process.env.LC_OWNER_BRIDGE ??
|
|
29327
|
+
if (!appData || !path12.isAbsolute(appData)) throw Error("APP_DATA_MISSING");
|
|
29328
|
+
const stateDir = process.env.LC_AGENT_STATE_DIR ?? path12.join(appData, "LocalCommander", "Agent");
|
|
29329
|
+
const binaryDir = path12.dirname(process.execPath);
|
|
29330
|
+
const ownerBridgePath = process.env.LC_OWNER_BRIDGE ?? path12.join(binaryDir, "OwnerBridge.exe");
|
|
29107
29331
|
const store = await WindowsPrivateStore.open(stateDir, ownerBridgePath);
|
|
29108
29332
|
const command = process.argv[2] ?? "run";
|
|
29109
29333
|
if (command === "pair") {
|
|
@@ -29125,18 +29349,33 @@ async function main() {
|
|
|
29125
29349
|
}) + "\n");
|
|
29126
29350
|
return;
|
|
29127
29351
|
}
|
|
29352
|
+
if (command === "configure-access") {
|
|
29353
|
+
if (process.argv.length !== 3) throw Error("AGENT_COMMAND_INVALID");
|
|
29354
|
+
process.stdout.write(await store.configureAccess() ? "ACCESS_CONFIGURED\n" : "ACCESS_CANCELLED\n");
|
|
29355
|
+
return;
|
|
29356
|
+
}
|
|
29128
29357
|
if (command === "approve-workspace") {
|
|
29129
29358
|
process.stdout.write(await store.approveWorkspace() ? "WORKSPACE_APPROVED\n" : "WORKSPACE_CANCELLED\n");
|
|
29130
29359
|
return;
|
|
29131
29360
|
}
|
|
29132
29361
|
if (command === "list-workspaces") {
|
|
29133
|
-
|
|
29362
|
+
const programs = await store.loadPrograms(), history = await store.loadPermissionHistory();
|
|
29363
|
+
process.stdout.write(JSON.stringify((await store.loadWorkspaces()).map((w) => ({
|
|
29364
|
+
workspaceGrantId: w.workspaceGrantId,
|
|
29365
|
+
displayName: w.displayName,
|
|
29366
|
+
path: w.root,
|
|
29367
|
+
permissions: w.permissions,
|
|
29368
|
+
allowedCommands: w.allowedCommands,
|
|
29369
|
+
executables: Object.fromEntries(w.allowedCommands.map((name2) => [name2, programs[name2]?.executable ?? null])),
|
|
29370
|
+
localhostPorts: w.localhostPorts,
|
|
29371
|
+
permissionHistory: history.filter((entry) => entry.workspaceGrantId === w.workspaceGrantId)
|
|
29372
|
+
}))) + "\n");
|
|
29134
29373
|
return;
|
|
29135
29374
|
}
|
|
29136
29375
|
const args = process.argv.slice(3);
|
|
29137
29376
|
if (command === "efficiency-report") {
|
|
29138
29377
|
if (args.length) throw Error("AGENT_COMMAND_INVALID");
|
|
29139
|
-
const ledger = await OptimizationLedger.open(
|
|
29378
|
+
const ledger = await OptimizationLedger.open(path12.join(stateDir, "optimization"));
|
|
29140
29379
|
try {
|
|
29141
29380
|
process.stdout.write(JSON.stringify(await ledger.report(), null, 2) + "\n");
|
|
29142
29381
|
} finally {
|
|
@@ -29146,7 +29385,7 @@ async function main() {
|
|
|
29146
29385
|
}
|
|
29147
29386
|
if (command === "guard-configure") {
|
|
29148
29387
|
if (args.length !== 1) throw Error("AGENT_COMMAND_INVALID");
|
|
29149
|
-
const filename = await (await import("node:fs/promises")).realpath(
|
|
29388
|
+
const filename = await (await import("node:fs/promises")).realpath(path12.resolve(args[0]));
|
|
29150
29389
|
const input2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await readWindowsFile(ownerBridgePath, filename, 2e5)));
|
|
29151
29390
|
if (!input2 || typeof input2 !== "object" || Object.keys(input2).sort().join(",") !== "compact,settings") throw Error("GUARD_CONFIGURATION_INVALID");
|
|
29152
29391
|
await store.configureGuard(input2.settings, input2.compact);
|
|
@@ -29165,7 +29404,7 @@ async function main() {
|
|
|
29165
29404
|
}
|
|
29166
29405
|
if (command === "guard-evidence") {
|
|
29167
29406
|
if (args.length) throw Error("AGENT_COMMAND_INVALID");
|
|
29168
|
-
const journal = await NativeJournal.open(
|
|
29407
|
+
const journal = await NativeJournal.open(path12.join(stateDir, "journal"), new SecretBox({ "1": await store.loadDataKey() }));
|
|
29169
29408
|
try {
|
|
29170
29409
|
process.stdout.write(JSON.stringify(await journal.guardEvidence()) + "\n");
|
|
29171
29410
|
} finally {
|
|
@@ -29177,7 +29416,7 @@ async function main() {
|
|
|
29177
29416
|
if (args.length !== (command === "guard-export" ? 0 : 1)) throw Error("AGENT_COMMAND_INVALID");
|
|
29178
29417
|
const identity = await store.load();
|
|
29179
29418
|
if (!identity?.enrollment) throw Error("ENROLLMENT_MISSING");
|
|
29180
|
-
const journal = await NativeJournal.open(
|
|
29419
|
+
const journal = await NativeJournal.open(path12.join(stateDir, "journal"), new SecretBox({ "1": await store.loadDataKey() }));
|
|
29181
29420
|
try {
|
|
29182
29421
|
const { tenantId, deviceId } = identity.enrollment;
|
|
29183
29422
|
const records = (await journal.guardEvidence()).filter((record2) => record2.tenantId === tenantId && record2.deviceId === deviceId);
|
|
@@ -29216,10 +29455,10 @@ async function main() {
|
|
|
29216
29455
|
if (!(await store.load())?.enrollment) throw Error("ENROLLMENT_MISSING");
|
|
29217
29456
|
const releaseProcess = await claimAgentProcess(stateDir, binaryDir, AGENT_VERSION2);
|
|
29218
29457
|
try {
|
|
29219
|
-
const journal = await NativeJournal.open(
|
|
29220
|
-
const efficiencyLedger = await OptimizationLedger.open(
|
|
29458
|
+
const journal = await NativeJournal.open(path12.join(stateDir, "journal"), new SecretBox({ "1": await store.loadDataKey() }));
|
|
29459
|
+
const efficiencyLedger = await OptimizationLedger.open(path12.join(stateDir, "optimization"));
|
|
29221
29460
|
try {
|
|
29222
|
-
const tools = new NativeTools({ stateDirectory: stateDir, helperPath: process.env.LC_JOB_HOST ??
|
|
29461
|
+
const tools = new NativeTools({ stateDirectory: stateDir, helperPath: process.env.LC_JOB_HOST ?? path12.join(binaryDir, "JobHost.exe"), ownerBridgePath, programs: await store.loadPrograms() });
|
|
29223
29462
|
const stop = new AbortController();
|
|
29224
29463
|
process.once("SIGINT", () => stop.abort());
|
|
29225
29464
|
process.once("SIGTERM", () => stop.abort());
|
|
@@ -29233,7 +29472,7 @@ async function main() {
|
|
|
29233
29472
|
await releaseProcess();
|
|
29234
29473
|
}
|
|
29235
29474
|
}
|
|
29236
|
-
if (process.argv[1] &&
|
|
29475
|
+
if (process.argv[1] && path12.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
29237
29476
|
void main().catch((error62) => {
|
|
29238
29477
|
const code = error62 instanceof Error && /^[A-Z0-9_]{1,64}$/.test(error62.message) ? error62.message : "AGENT_START_FAILED";
|
|
29239
29478
|
process.stderr.write(code + "\n");
|