local-commander-agent 0.3.21-systemca.1 → 0.3.22-systemca.1
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/CHANGELOG.md +11 -2
- package/README.md +7 -6
- package/bin/command-path.mjs +1 -1
- package/bin/monitor-savings.d.mts +20 -0
- package/bin/monitor-savings.mjs +71 -0
- package/bin/monitor.mjs +7 -3
- package/package.json +1 -1
- package/runtime/JobHost.exe +0 -0
- package/runtime/LocalCommanderAgent.exe +0 -0
- package/runtime/OwnerBridge.exe +0 -0
- package/runtime/agent.mjs +97 -99
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Change history
|
|
2
2
|
|
|
3
|
-
## 0.3.
|
|
3
|
+
## 0.3.22-systemca.1 - 2026-10-11
|
|
4
4
|
|
|
5
|
-
- Fix: the agent now trusts the certificates Windows trusts, as well as its built-in list. Computers whose antivirus or company network inspects HTTPS (for example Kaspersky Endpoint Security) can pair and connect without extra setup; before this they stopped at `NETWORK_FAILED`. Certificate checking stays on. Otherwise identical to 0.3.
|
|
5
|
+
- Fix: the agent now trusts the certificates Windows trusts, as well as its built-in list. Computers whose antivirus or company network inspects HTTPS (for example Kaspersky Endpoint Security) can pair and connect without extra setup; before this they stopped at `NETWORK_FAILED`. Certificate checking stays on. Otherwise identical to 0.3.22-fullaccess.1.
|
|
6
|
+
|
|
7
|
+
## 0.3.22-fullaccess.1 - 2026-10-10
|
|
8
|
+
|
|
9
|
+
- Owner-approved Full Access no longer requires per-executable registration for Windows shells or administration tools. The Windows account rights and explicit deny-command restrictions remain enforced.
|
|
10
|
+
- Scoped grants and native authentication, target binding, journal/recovery and concurrency safeguards are unchanged.
|
|
11
|
+
- Local direct MCP and remote startup improvements remain separate issue #104 tasks; this build still uses authenticated hosted relay for ChatGPT web.
|
|
6
12
|
|
|
7
13
|
## 0.3.17-journal.1 - 2026-10-08
|
|
8
14
|
|
|
@@ -28,6 +34,9 @@ Names are display hints. IDs and permissions are unchanged. Token figures remain
|
|
|
28
34
|
- Release cloud reservations only after a rejection receipt passes the existing execution and device checks.
|
|
29
35
|
- Retain fencing for unknown executions without rejection proof.
|
|
30
36
|
- Prune acknowledged terminal journal records using the existing retention rules.
|
|
37
|
+
# 0.3.19-admission.1
|
|
38
|
+
|
|
39
|
+
New execution admission pauses when protected journal capacity is exhausted; authenticated relay status carries the pause while status, output and cancellation remain available. Retains the verified native18 default of 2,000 entries, the existing byte/retention limits and unknown-execution fencing.
|
|
31
40
|
|
|
32
41
|
# 0.3.21-project-targets.3
|
|
33
42
|
|
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ npx local-commander-agent@latest workspaces
|
|
|
74
74
|
npx local-commander-agent@latest revoke-workspace <workspaceGrantId>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`configure-access` reopens the full-access/selected-folder choice. **It replaces current grants and their restrictions.** Full access enables commands and localhost by default; selected folders start read/write with separate command/port allowlists. Choosing a restricted folder removes old broader grants. `approve-workspace` adds another read/write folder without replacing the existing set. `workspaces` shows grant IDs, full/scoped mode and explicit denials. A successful change restarts an already-running agent to synchronize grants; cancellation does not restart it. A revoked drive is not silently re-added in the background.
|
|
77
|
+
`configure-access` reopens the full-access/selected-folder choice. **It replaces the current set of grants and their restrictions.** Full access enables commands and localhost by default; selected folders start read/write with separate command/port allowlists. Choosing a restricted folder removes old broader grants. `approve-workspace` adds another read/write folder without replacing the existing set. `workspaces` shows grant IDs, full/scoped mode and explicit denials. A successful change restarts an already-running agent to synchronize grants; cancellation does not restart it. A revoked drive is not silently re-added in the background.
|
|
78
78
|
|
|
79
79
|
To enable or remove individual capabilities, replace the placeholders with a grant ID from `workspaces` and an executable path that actually exists on your PC:
|
|
80
80
|
|
|
@@ -90,13 +90,13 @@ npx local-commander-agent@latest deny-localhost <workspaceGrantId> 3000
|
|
|
90
90
|
|
|
91
91
|
`allow-command` associates an exact executable with the name the assistant uses; it does not accept a shell command string. Capability changes are local owner actions, not MCP settings the assistant can silently widen. The all-drives decision always requires the local dialog; there is no `--yes` or environment flag to approve it.
|
|
92
92
|
|
|
93
|
-
###
|
|
93
|
+
### Scoped access: approve Git from CMD or PowerShell
|
|
94
94
|
|
|
95
95
|
When giving an owner Windows commands, identify the shell before using shell-specific syntax and label the example. Prefer explicit workspace IDs and quoted absolute executable paths that work in both CMD and PowerShell; do not assume a standard installation path.
|
|
96
96
|
|
|
97
97
|
Use the example for the shell you are running. A prompt such as `C:\project>` is normally **Command Prompt (CMD)**; `PS C:\project>` is normally **PowerShell**. Windows Terminal can host either shell.
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
For selected-folders/scoped mode, first run `npx local-commander-agent@latest workspaces` and copy the intended workspace's grant ID. Full Access does not require this setup for Git.
|
|
100
100
|
|
|
101
101
|
**CMD:** run `where git.exe`. If it lists several paths, choose the Git executable you intend to approve. Replace `WORKSPACE_ID` and the example path below with those actual values; keep the path in double quotes.
|
|
102
102
|
|
|
@@ -118,9 +118,10 @@ Do not paste `(Get-Command git).Source` into CMD: CMD passes it literally instea
|
|
|
118
118
|
|
|
119
119
|
Under full access, `deny-command` and `deny-localhost` exclude the named command or port while other commands/ports remain available. Native `.exe`/`.com` commands receive argument arrays directly. `.cmd`/`.bat` launchers use a hidden PowerShell wrapper and reject cmd metacharacters in arguments; use an explicit interpreter for more complex scripts. Allowing a general interpreter is not containment against it launching a denied program.
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
Under **owner-approved Full Access**, installed Windows shells, script hosts and administration executables (including PowerShell and CMD) work without per-command registration. They execute under the Windows account's own rights, not elevated privileges. Owner-defined `deny-command` exclusions and explicit write/localhost restrictions still apply. A general interpreter can access resources beyond file-tool path checks; Full Access is not an OS sandbox. Existing scoped grants stay scoped until the owner deliberately changes access with `configure-access`.
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
|
|
124
|
+
For a remote MCP client with an unknown device/workspace, use `workspace_list`, then `execution_session_open`, perform the requested work and read results. Reuse valid, already authorized session and target context where supported. `diagnostics` is for faults, not a mandatory startup roundtrip. Diagnostics reports quota separately from the device's execution slot limit. Its optional `repairSafe` mode releases only expired reservations from this OAuth connection that never acquired a dispatch lease. It never retries work, changes permissions or clears unknown executions.
|
|
124
125
|
|
|
125
126
|
## Troubleshooting
|
|
126
127
|
|
|
@@ -207,4 +208,4 @@ Reload the personal MCP server once to load this source change, then reopen `npm
|
|
|
207
208
|
|
|
208
209
|
The view shows measured per-agent calls, mean tool-plus-queue duration, stdout bytes, sent response bytes and errors when an identified reporter has those counters. Native measured calls, status/output polls and session records are separate figures; sessions are not execution capacity. Current savings totals exclude stale native snapshots. Missing measurements remain unavailable.
|
|
209
210
|
|
|
210
|
-
See [CHANGELOG.md](CHANGELOG.md) for release changes.
|
|
211
|
+
See [CHANGELOG.md](https://github.com/netbka/local-commander-mcp/blob/main/packages/agent-cli/CHANGELOG.md) for release changes.
|
package/bin/command-path.mjs
CHANGED
|
@@ -7,7 +7,7 @@ const SHIM_MARKER = "@rem Local Commander managed command\r\n";
|
|
|
7
7
|
const WRAPPER_MARKER = "# Local Commander managed command\r\n";
|
|
8
8
|
const MONITOR_WRAPPER = "localcommander-monitor.ps1";
|
|
9
9
|
const SHORTCUT_NAME = "Local Commander Monitor";
|
|
10
|
-
const CLI_FILES = ["package.json", "bin/localcommander.mjs", "bin/monitor.mjs", "bin/monitor-agents.mjs", "bin/monitor-context.mjs", "bin/monitor-mcp.mjs", "bin/command-path.mjs"];
|
|
10
|
+
const CLI_FILES = ["package.json", "bin/localcommander.mjs", "bin/monitor.mjs", "bin/monitor-agents.mjs", "bin/monitor-context.mjs", "bin/monitor-mcp.mjs", "bin/monitor-savings.mjs", "bin/command-path.mjs"];
|
|
11
11
|
|
|
12
12
|
function validVersion(version) {
|
|
13
13
|
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version)) throw Error("PACKAGE_VERSION_INVALID");
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface LifetimeSavings {
|
|
2
|
+
scope: "all_time";
|
|
3
|
+
coverage: "recorded_native_results";
|
|
4
|
+
since: string | null;
|
|
5
|
+
asOf: string | null;
|
|
6
|
+
calls: number;
|
|
7
|
+
originalBytes: number;
|
|
8
|
+
sentBytes: number;
|
|
9
|
+
savedBytes: number;
|
|
10
|
+
}
|
|
11
|
+
export function lifetimeSavingsView(raw: unknown): LifetimeSavings | null;
|
|
12
|
+
export function summarizeLifetimeSavings(result: {
|
|
13
|
+
instances?: Array<{ lifetimeSavings?: unknown; savingsSourceId?: string }>;
|
|
14
|
+
unavailable?: number;
|
|
15
|
+
}): { available: boolean; partial: boolean; unavailable: number; sources?: number;
|
|
16
|
+
calls?: number; originalBytes?: number; sentBytes?: number; savedBytes?: number;
|
|
17
|
+
since?: string | null; asOf?: string | null; estimatedSavedTokens?: number };
|
|
18
|
+
export function formatLifetimeSavings(result: Parameters<typeof summarizeLifetimeSavings>[0] & {
|
|
19
|
+
mcp?: { instances: Array<{ instanceId: string; metrics?: Array<{ savedBytes: number }> }>; unavailable?: number };
|
|
20
|
+
}): string[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Scalar projections only. Never read a ledger, private state or provider data here.
|
|
2
|
+
const count = value => Number.isSafeInteger(value) && value >= 0 && value <= 1e15;
|
|
3
|
+
const date = value => typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
4
|
+
|
|
5
|
+
/** Invalid or older optional telemetry must not hide the rest of a computer. */
|
|
6
|
+
export function lifetimeSavingsView(raw) {
|
|
7
|
+
if (!raw || raw.scope !== "all_time" || raw.coverage !== "recorded_native_results" ||
|
|
8
|
+
![raw.calls, raw.originalBytes, raw.sentBytes, raw.savedBytes].every(count) ||
|
|
9
|
+
raw.sentBytes > raw.originalBytes || raw.savedBytes !== raw.originalBytes - raw.sentBytes ||
|
|
10
|
+
(raw.calls === 0 ? raw.since !== null || raw.asOf !== null || raw.originalBytes !== 0 :
|
|
11
|
+
!date(raw.since) || !date(raw.asOf) || raw.since > raw.asOf)) return null;
|
|
12
|
+
return { scope: "all_time", coverage: "recorded_native_results", since: raw.since, asOf: raw.asOf,
|
|
13
|
+
calls: raw.calls, originalBytes: raw.originalBytes, sentBytes: raw.sentBytes, savedBytes: raw.savedBytes };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Aggregate complete scalar native histories, even when their producers have stopped. */
|
|
17
|
+
export function summarizeLifetimeSavings(result) {
|
|
18
|
+
const sources = new Map(), rejected = new Set();
|
|
19
|
+
let unavailable = result.unavailable ?? 0;
|
|
20
|
+
// Ascending counts expose old conflicts too, regardless of the reader's file order.
|
|
21
|
+
const snapshots = (result.instances ?? []).map(instance => ({
|
|
22
|
+
id: instance.savingsSourceId, value: lifetimeSavingsView(instance.lifetimeSavings)
|
|
23
|
+
})).sort((a, b) => (a.value?.calls ?? 0) - (b.value?.calls ?? 0));
|
|
24
|
+
for (const { id, value } of snapshots) {
|
|
25
|
+
if (typeof id !== "string" || !/^[a-f0-9]{64}$/.test(id)) { unavailable++; continue; }
|
|
26
|
+
if (rejected.has(id)) continue;
|
|
27
|
+
const old = sources.get(id);
|
|
28
|
+
const conflict = old && (value?.calls === old.calls ?
|
|
29
|
+
Object.keys(old).some(key => value[key] !== old[key]) :
|
|
30
|
+
value && (["originalBytes", "sentBytes", "savedBytes"].some(key => value[key] < old[key]) ||
|
|
31
|
+
(old.since !== null && value.since > old.since) || (old.asOf !== null && value.asOf < old.asOf)));
|
|
32
|
+
if (!value || conflict) {
|
|
33
|
+
sources.delete(id); rejected.add(id); unavailable++; continue;
|
|
34
|
+
}
|
|
35
|
+
sources.set(id, value);
|
|
36
|
+
}
|
|
37
|
+
const values = [...sources.values()];
|
|
38
|
+
const totals = { calls: 0, originalBytes: 0, sentBytes: 0, savedBytes: 0 };
|
|
39
|
+
for (const value of values) for (const key of Object.keys(totals)) totals[key] += value[key];
|
|
40
|
+
if (!Object.values(totals).every(count)) return { available: false, partial: true, unavailable: unavailable + 1 };
|
|
41
|
+
const dates = values.map(value => value.since).filter(Boolean).sort();
|
|
42
|
+
const updates = values.map(value => value.asOf).filter(Boolean).sort();
|
|
43
|
+
return { available: values.length > 0, partial: unavailable > 0, unavailable, sources: values.length,
|
|
44
|
+
...totals, since: dates[0] ?? null, asOf: updates.at(-1) ?? null,
|
|
45
|
+
estimatedSavedTokens: Math.round(totals.savedBytes / 4) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Native all-time and retained personal MCP have different coverage and byte units. */
|
|
49
|
+
export function formatLifetimeSavings(result) {
|
|
50
|
+
const total = summarizeLifetimeSavings(result);
|
|
51
|
+
const tokens = bytes => "~" + Math.round(bytes / 4).toLocaleString("en-US");
|
|
52
|
+
const personal = new Map();
|
|
53
|
+
for (const instance of result.mcp?.instances ?? []) personal.set(instance.instanceId, instance);
|
|
54
|
+
const personalBytes = [...personal.values()].flatMap(instance => instance.metrics ?? [])
|
|
55
|
+
.reduce((sum, metric) => sum + metric.savedBytes, 0);
|
|
56
|
+
const personalKnown = [...personal.values()].some(instance => instance.metrics?.length) && count(personalBytes);
|
|
57
|
+
const knownBytes = (total.available ? total.savedBytes : 0) + (personalKnown ? personalBytes : 0);
|
|
58
|
+
const available = (total.available || personalKnown) && count(knownBytes);
|
|
59
|
+
const partial = total.partial || !total.available || personal.size > 0 || Boolean(result.mcp?.unavailable);
|
|
60
|
+
const lines = [available ?
|
|
61
|
+
"All-time saved" + (partial ? " (partial)" : " (native)") + ": " + tokens(knownBytes) + " tokens" :
|
|
62
|
+
"All-time saved: unavailable | historical measurements not reported"];
|
|
63
|
+
if (total.available) lines.push("Native history: Raw " + tokens(total.originalBytes) + " | Sent " + tokens(total.sentBytes) +
|
|
64
|
+
" | " + (total.originalBytes ? (100 * total.savedBytes / total.originalBytes).toFixed(1) : "0.0") + "% | " +
|
|
65
|
+
(total.since ? "since " + total.since.slice(0, 10) + " | as of " + total.asOf.slice(0, 16).replace("T", " ") + "Z" : "no recorded results"));
|
|
66
|
+
lines.push(personalKnown ?
|
|
67
|
+
"Personal MCP (partial): " + tokens(personalBytes) + " tok retained" :
|
|
68
|
+
"Personal MCP saved: unavailable (historical total not reported)");
|
|
69
|
+
lines.push("Est: bytes/4; provider tokens unknown");
|
|
70
|
+
return lines;
|
|
71
|
+
}
|
package/bin/monitor.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { lstat, open, readdir } from "node:fs/promises";
|
|
2
|
+
import { lifetimeSavingsView, formatLifetimeSavings } from "./monitor-savings.mjs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { setTimeout as delay } from "node:timers/promises";
|
|
4
5
|
import { readAgentActivity } from "./monitor-agents.mjs";
|
|
@@ -99,7 +100,9 @@ export function monitorView(raw, now = Date.now(), alive = pidAlive) {
|
|
|
99
100
|
});
|
|
100
101
|
return { instanceId: raw.instanceId, pid: raw.pid, agentVersion: raw.agentVersion, updatedAt: raw.updatedAt,
|
|
101
102
|
running, connection, origin, sessions, events, computer: raw.computer ?? null, processName: "lc-agent", executions,
|
|
102
|
-
efficiency: efficiencyView(raw.efficiency)
|
|
103
|
+
efficiency: efficiencyView(raw.efficiency),
|
|
104
|
+
savingsSourceId: typeof raw.savingsSourceId === "string" && /^[a-f0-9]{64}$/.test(raw.savingsSourceId) ? raw.savingsSourceId : null,
|
|
105
|
+
lifetimeSavings: lifetimeSavingsView(raw.lifetimeSavings) };
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
export async function readMonitors(directory) {
|
|
@@ -247,9 +250,10 @@ export function formatMonitor(result, columns = process.stdout.columns ?? 120, h
|
|
|
247
250
|
let shown = 0, shownPcs = 0, shownAgents = 0;
|
|
248
251
|
const footer = [
|
|
249
252
|
"-".repeat(width),
|
|
250
|
-
|
|
253
|
+
...formatLifetimeSavings(result),
|
|
254
|
+
stats.length ? "Native this run - Raw: " + tokens(saved + sent) + " tokens Sent: " + tokens(sent) + " Saved: " + tokens(saved) +
|
|
251
255
|
" " + (saved + sent ? Math.round(100 * saved / (saved + sent)) : 0) + "%" + (stats.length < live.length ? " (partial)" : "") :
|
|
252
|
-
"Raw: n/a Sent: n/a Saved: n/a",
|
|
256
|
+
"Native this run - Raw: n/a Sent: n/a Saved: n/a",
|
|
253
257
|
mcp.length ? "MCP this run: " + usageSummary(mcpMetrics) + " | saved stdout " + tokens(mcpMetrics.reduce((sum, row) => sum + row.savedBytes, 0)) + " tok" :
|
|
254
258
|
"~ Tokens: bytes / 4, this run | model: declared or not reported",
|
|
255
259
|
mcpMetrics.length ? "Review: " + [...mcpMetrics].sort((a, b) => b.sentBytes - a.sentBytes).slice(0, 2)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-commander-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22-systemca.1",
|
|
4
4
|
"description": "Windows companion for Local Commander: connect AI clients to local files, approved commands and localhost. Requires https://localcmd.app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/runtime/JobHost.exe
CHANGED
|
Binary file
|
|
Binary file
|
package/runtime/OwnerBridge.exe
CHANGED
|
Binary file
|
package/runtime/agent.mjs
CHANGED
|
@@ -5072,14 +5072,14 @@ function codePointLength(str) {
|
|
|
5072
5072
|
const units2 = str.length;
|
|
5073
5073
|
if (!highSurrogate.test(str))
|
|
5074
5074
|
return units2;
|
|
5075
|
-
let
|
|
5075
|
+
let count2 = units2;
|
|
5076
5076
|
for (let i = 0; i < units2 - 1; i++) {
|
|
5077
5077
|
if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {
|
|
5078
|
-
|
|
5078
|
+
count2--;
|
|
5079
5079
|
i++;
|
|
5080
5080
|
}
|
|
5081
5081
|
}
|
|
5082
|
-
return
|
|
5082
|
+
return count2;
|
|
5083
5083
|
}
|
|
5084
5084
|
function getLengthableOrigin(input2) {
|
|
5085
5085
|
if (Array.isArray(input2))
|
|
@@ -9434,8 +9434,8 @@ function az_default() {
|
|
|
9434
9434
|
}
|
|
9435
9435
|
|
|
9436
9436
|
// ../../local_commander_mcp/node_modules/zod/v4/locales/be.js
|
|
9437
|
-
function getBelarusianPlural(
|
|
9438
|
-
const absCount = Math.abs(
|
|
9437
|
+
function getBelarusianPlural(count2, one, few, many) {
|
|
9438
|
+
const absCount = Math.abs(count2);
|
|
9439
9439
|
const lastDigit = absCount % 10;
|
|
9440
9440
|
const lastTwoDigits = absCount % 100;
|
|
9441
9441
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
|
|
@@ -12082,8 +12082,8 @@ function hu_default() {
|
|
|
12082
12082
|
}
|
|
12083
12083
|
|
|
12084
12084
|
// ../../local_commander_mcp/node_modules/zod/v4/locales/hy.js
|
|
12085
|
-
function getArmenianPlural(
|
|
12086
|
-
return Math.abs(
|
|
12085
|
+
function getArmenianPlural(count2, one, many) {
|
|
12086
|
+
return Math.abs(count2) === 1 ? one : many;
|
|
12087
12087
|
}
|
|
12088
12088
|
function withDefiniteArticle(word) {
|
|
12089
12089
|
if (!word)
|
|
@@ -14821,8 +14821,8 @@ function ro_default() {
|
|
|
14821
14821
|
}
|
|
14822
14822
|
|
|
14823
14823
|
// ../../local_commander_mcp/node_modules/zod/v4/locales/ru.js
|
|
14824
|
-
function getRussianPlural(
|
|
14825
|
-
const absCount = Math.abs(
|
|
14824
|
+
function getRussianPlural(count2, one, few, many) {
|
|
14825
|
+
const absCount = Math.abs(count2);
|
|
14826
14826
|
const lastDigit = absCount % 10;
|
|
14827
14827
|
const lastTwoDigits = absCount % 100;
|
|
14828
14828
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
|
|
@@ -25084,7 +25084,7 @@ var NativeJournal = class _NativeJournal {
|
|
|
25084
25084
|
}
|
|
25085
25085
|
recoverAfterRestart() {
|
|
25086
25086
|
return this.enqueue(async () => {
|
|
25087
|
-
let
|
|
25087
|
+
let count2 = 0;
|
|
25088
25088
|
for (const original of this.entries.values()) {
|
|
25089
25089
|
if (!["accepted", "running"].includes(original.state)) continue;
|
|
25090
25090
|
const e = clone2(original);
|
|
@@ -25104,9 +25104,9 @@ var NativeJournal = class _NativeJournal {
|
|
|
25104
25104
|
} });
|
|
25105
25105
|
}
|
|
25106
25106
|
await this.persist(e);
|
|
25107
|
-
|
|
25107
|
+
count2++;
|
|
25108
25108
|
}
|
|
25109
|
-
return
|
|
25109
|
+
return count2;
|
|
25110
25110
|
});
|
|
25111
25111
|
}
|
|
25112
25112
|
eligibleForPruning(entry, cutoff) {
|
|
@@ -25114,20 +25114,20 @@ var NativeJournal = class _NativeJournal {
|
|
|
25114
25114
|
}
|
|
25115
25115
|
admissionAvailable() {
|
|
25116
25116
|
if (this.closed) return false;
|
|
25117
|
-
let
|
|
25118
|
-
if (
|
|
25117
|
+
let count2 = this.entries.size, bytes3 = this.totalBytes;
|
|
25118
|
+
if (count2 < this.options.maxEntries && bytes3 + MAX_ENVELOPE <= this.options.maxBytes) return true;
|
|
25119
25119
|
const cutoff = this.now().getTime() - this.options.retentionMs;
|
|
25120
25120
|
for (const entry of this.entries.values()) {
|
|
25121
25121
|
if (!this.eligibleForPruning(entry, cutoff)) continue;
|
|
25122
|
-
|
|
25122
|
+
count2--;
|
|
25123
25123
|
bytes3 -= this.sizes.get(entry.executionId);
|
|
25124
|
-
if (
|
|
25124
|
+
if (count2 < this.options.maxEntries && bytes3 + MAX_ENVELOPE <= this.options.maxBytes) return true;
|
|
25125
25125
|
}
|
|
25126
25126
|
return false;
|
|
25127
25127
|
}
|
|
25128
25128
|
// Called only inside the serialized queue; enqueueing prune here would deadlock.
|
|
25129
25129
|
async pruneEligible() {
|
|
25130
|
-
let
|
|
25130
|
+
let count2 = 0;
|
|
25131
25131
|
const cutoff = this.now().getTime() - this.options.retentionMs;
|
|
25132
25132
|
for (const e of [...this.entries.values()]) {
|
|
25133
25133
|
if (!this.eligibleForPruning(e, cutoff)) continue;
|
|
@@ -25137,9 +25137,9 @@ var NativeJournal = class _NativeJournal {
|
|
|
25137
25137
|
this.entries.delete(e.executionId);
|
|
25138
25138
|
this.sizes.delete(e.executionId);
|
|
25139
25139
|
this.digests.delete(e.executionId);
|
|
25140
|
-
|
|
25140
|
+
count2++;
|
|
25141
25141
|
}
|
|
25142
|
-
return
|
|
25142
|
+
return count2;
|
|
25143
25143
|
}
|
|
25144
25144
|
prune() {
|
|
25145
25145
|
return this.enqueue(() => this.pruneEligible());
|
|
@@ -27120,59 +27120,6 @@ async function runSupervised(helper, request, options = {}) {
|
|
|
27120
27120
|
}
|
|
27121
27121
|
|
|
27122
27122
|
// agent/src/tools.ts
|
|
27123
|
-
var OWNER_REGISTRATION_REQUIRED = /* @__PURE__ */ new Set([
|
|
27124
|
-
"cmd",
|
|
27125
|
-
"powershell",
|
|
27126
|
-
"powershell_ise",
|
|
27127
|
-
"pwsh",
|
|
27128
|
-
"wscript",
|
|
27129
|
-
"cscript",
|
|
27130
|
-
"mshta",
|
|
27131
|
-
"rundll32",
|
|
27132
|
-
"regsvr32",
|
|
27133
|
-
"regasm",
|
|
27134
|
-
"regsvcs",
|
|
27135
|
-
"certutil",
|
|
27136
|
-
"bitsadmin",
|
|
27137
|
-
"msiexec",
|
|
27138
|
-
"installutil",
|
|
27139
|
-
"msbuild",
|
|
27140
|
-
"cmstp",
|
|
27141
|
-
"forfiles",
|
|
27142
|
-
"hh",
|
|
27143
|
-
"wmic",
|
|
27144
|
-
"reg",
|
|
27145
|
-
"schtasks",
|
|
27146
|
-
"at",
|
|
27147
|
-
"sc",
|
|
27148
|
-
"net",
|
|
27149
|
-
"net1",
|
|
27150
|
-
"netsh",
|
|
27151
|
-
"bcdedit",
|
|
27152
|
-
"vssadmin",
|
|
27153
|
-
"diskpart",
|
|
27154
|
-
"cipher",
|
|
27155
|
-
"icacls",
|
|
27156
|
-
"takeown",
|
|
27157
|
-
"runas",
|
|
27158
|
-
"psexec",
|
|
27159
|
-
"wsl",
|
|
27160
|
-
"bash",
|
|
27161
|
-
"sh",
|
|
27162
|
-
"ftp",
|
|
27163
|
-
"tftp",
|
|
27164
|
-
"taskkill",
|
|
27165
|
-
"shutdown",
|
|
27166
|
-
"format",
|
|
27167
|
-
"fsutil",
|
|
27168
|
-
"dism",
|
|
27169
|
-
"winrm",
|
|
27170
|
-
"winrs",
|
|
27171
|
-
"eventvwr",
|
|
27172
|
-
"fodhelper",
|
|
27173
|
-
"computerdefaults",
|
|
27174
|
-
"sdclt"
|
|
27175
|
-
]);
|
|
27176
27123
|
var NativeToolError = class extends Error {
|
|
27177
27124
|
constructor(code) {
|
|
27178
27125
|
super(code);
|
|
@@ -27475,16 +27422,16 @@ var NativeTools = class {
|
|
|
27475
27422
|
if (value) selectedHeaders[name2] = value.slice(0, 2048);
|
|
27476
27423
|
}
|
|
27477
27424
|
const reader = response.body?.getReader(), chunks = [];
|
|
27478
|
-
let
|
|
27425
|
+
let count2 = 0, truncated = false;
|
|
27479
27426
|
try {
|
|
27480
27427
|
if (reader) for (; ; ) {
|
|
27481
27428
|
const part = await reader.read();
|
|
27482
27429
|
if (part.done) break;
|
|
27483
27430
|
const b = Buffer.from(part.value);
|
|
27484
|
-
const take = Math.min(b.length, 1e5 -
|
|
27431
|
+
const take = Math.min(b.length, 1e5 - count2);
|
|
27485
27432
|
chunks.push(b.subarray(0, take));
|
|
27486
|
-
|
|
27487
|
-
if (take !== b.length ||
|
|
27433
|
+
count2 += take;
|
|
27434
|
+
if (take !== b.length || count2 >= 1e5) {
|
|
27488
27435
|
truncated = true;
|
|
27489
27436
|
break;
|
|
27490
27437
|
}
|
|
@@ -27609,10 +27556,6 @@ var NativeTools = class {
|
|
|
27609
27556
|
const configured = this.options.programs[key]?.executable;
|
|
27610
27557
|
if (configured && equalPath3(executable, await realpath5(configured).catch(() => configured))) fail2("COMMAND_NOT_ALLOWED");
|
|
27611
27558
|
}
|
|
27612
|
-
if (OWNER_REGISTRATION_REQUIRED.has(commandKey(name2)) || OWNER_REGISTRATION_REQUIRED.has(commandKey(executable))) {
|
|
27613
|
-
const registered = entry && workspace.allowedCommands.some((v) => v.toLowerCase() === entry[0].toLowerCase()) && equalPath3(executable, await realpath5(entry[1].executable).catch(() => entry[1].executable));
|
|
27614
|
-
if (!registered) fail2("COMMAND_NOT_ALLOWED");
|
|
27615
|
-
}
|
|
27616
27559
|
if (/\.(cmd|bat)$/i.test(executable)) {
|
|
27617
27560
|
if ([executable, ...args].some((value) => /[&|<>^%!"\r\n]/.test(value))) fail2("INVALID_SCRIPT_ARGUMENTS");
|
|
27618
27561
|
const literal2 = (value) => "'" + value.replaceAll("'", "''") + "'";
|
|
@@ -28006,11 +27949,11 @@ function parseNodeTapShadow(input2) {
|
|
|
28006
27949
|
return passThrough(input2, "unrecognized_record", true, notices);
|
|
28007
27950
|
}
|
|
28008
27951
|
if (line === "") continue;
|
|
28009
|
-
const
|
|
28010
|
-
if (
|
|
28011
|
-
const key =
|
|
27952
|
+
const count2 = SUMMARY_COUNT.exec(line);
|
|
27953
|
+
if (count2) {
|
|
27954
|
+
const key = count2[1];
|
|
28012
27955
|
if (counts.has(key)) return passThrough(input2, "summary_inconsistent", true, notices);
|
|
28013
|
-
counts.set(key, Number(
|
|
27956
|
+
counts.set(key, Number(count2[2]));
|
|
28014
27957
|
continue;
|
|
28015
27958
|
}
|
|
28016
27959
|
const summaryDuration = SUMMARY_DURATION.exec(line);
|
|
@@ -28124,11 +28067,11 @@ function parseNodeTapFailureShadow(input2) {
|
|
|
28124
28067
|
let durationMs = null;
|
|
28125
28068
|
for (const line of lines.slice(index + 1)) {
|
|
28126
28069
|
if (!line) continue;
|
|
28127
|
-
const
|
|
28128
|
-
if (
|
|
28129
|
-
const key =
|
|
28070
|
+
const count2 = SUMMARY_COUNT.exec(line);
|
|
28071
|
+
if (count2) {
|
|
28072
|
+
const key = count2[1];
|
|
28130
28073
|
if (counts.has(key)) return fallback("summary_inconsistent");
|
|
28131
|
-
counts.set(key, Number(
|
|
28074
|
+
counts.set(key, Number(count2[2]));
|
|
28132
28075
|
} else {
|
|
28133
28076
|
const duration3 = SUMMARY_DURATION.exec(line);
|
|
28134
28077
|
if (duration3) {
|
|
@@ -28356,10 +28299,10 @@ function aggregate(events) {
|
|
|
28356
28299
|
tokenEstimateMethod: "byte-ratio-v1"
|
|
28357
28300
|
});
|
|
28358
28301
|
}
|
|
28359
|
-
function localDayKey(
|
|
28360
|
-
const year =
|
|
28361
|
-
const month = String(
|
|
28362
|
-
const day = String(
|
|
28302
|
+
function localDayKey(date6) {
|
|
28303
|
+
const year = date6.getFullYear();
|
|
28304
|
+
const month = String(date6.getMonth() + 1).padStart(2, "0");
|
|
28305
|
+
const day = String(date6.getDate()).padStart(2, "0");
|
|
28363
28306
|
return year + "-" + month + "-" + day;
|
|
28364
28307
|
}
|
|
28365
28308
|
function buildLiveEfficiencyReport(inputs, now = /* @__PURE__ */ new Date()) {
|
|
@@ -29016,8 +28959,8 @@ function epoch2(value) {
|
|
|
29016
28959
|
}
|
|
29017
28960
|
function isoDate(value) {
|
|
29018
28961
|
if (typeof value !== "string") fail3("RELAY_REQUEST_INVALID");
|
|
29019
|
-
const
|
|
29020
|
-
if (!Number.isFinite(
|
|
28962
|
+
const date6 = new Date(value);
|
|
28963
|
+
if (!Number.isFinite(date6.getTime())) fail3("RELAY_REQUEST_INVALID");
|
|
29021
28964
|
return value;
|
|
29022
28965
|
}
|
|
29023
28966
|
function agentVersion(value) {
|
|
@@ -29503,9 +29446,9 @@ function displayName(value) {
|
|
|
29503
29446
|
}
|
|
29504
29447
|
function parseDate(value, code) {
|
|
29505
29448
|
if (typeof value !== "string") fail4(code);
|
|
29506
|
-
const
|
|
29507
|
-
if (!Number.isFinite(
|
|
29508
|
-
return
|
|
29449
|
+
const date6 = new Date(value);
|
|
29450
|
+
if (!Number.isFinite(date6.getTime())) fail4(code);
|
|
29451
|
+
return date6;
|
|
29509
29452
|
}
|
|
29510
29453
|
function confirmationCode(value) {
|
|
29511
29454
|
if (typeof value !== "string" || !CONFIRMATION_CODE.test(value)) fail4("PAIRING_RESPONSE_INVALID");
|
|
@@ -30019,6 +29962,16 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
30019
29962
|
filename;
|
|
30020
29963
|
handle;
|
|
30021
29964
|
noticesFilename;
|
|
29965
|
+
lifetime = {
|
|
29966
|
+
scope: "all_time",
|
|
29967
|
+
coverage: "recorded_native_results",
|
|
29968
|
+
since: null,
|
|
29969
|
+
asOf: null,
|
|
29970
|
+
calls: 0,
|
|
29971
|
+
originalBytes: 0,
|
|
29972
|
+
sentBytes: 0,
|
|
29973
|
+
savedBytes: 0
|
|
29974
|
+
};
|
|
30022
29975
|
events = [];
|
|
30023
29976
|
seenExecutionIds = /* @__PURE__ */ new Set();
|
|
30024
29977
|
deliveredNoticeCheckpoints = /* @__PURE__ */ new Set();
|
|
@@ -30065,6 +30018,7 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
30065
30018
|
if (ledger.seenExecutionIds.has(event.executionId)) continue;
|
|
30066
30019
|
ledger.seenExecutionIds.add(event.executionId);
|
|
30067
30020
|
ledger.events.push(event);
|
|
30021
|
+
ledger.addToLifetime(event);
|
|
30068
30022
|
}
|
|
30069
30023
|
}
|
|
30070
30024
|
return ledger;
|
|
@@ -30088,12 +30042,26 @@ var OptimizationLedger = class _OptimizationLedger {
|
|
|
30088
30042
|
await this.handle.write(line);
|
|
30089
30043
|
this.seenExecutionIds.add(event.executionId);
|
|
30090
30044
|
this.events.push(event);
|
|
30045
|
+
this.addToLifetime(event);
|
|
30091
30046
|
return true;
|
|
30092
30047
|
});
|
|
30093
30048
|
this.queue = work.catch(() => {
|
|
30094
30049
|
});
|
|
30095
30050
|
return work;
|
|
30096
30051
|
}
|
|
30052
|
+
addToLifetime(event) {
|
|
30053
|
+
this.lifetime.calls++;
|
|
30054
|
+
this.lifetime.originalBytes += event.originalStdoutBytes;
|
|
30055
|
+
this.lifetime.sentBytes += event.sentStdoutBytes;
|
|
30056
|
+
this.lifetime.savedBytes += event.savedStdoutBytes;
|
|
30057
|
+
const at = new Date(event.occurredAt).toISOString();
|
|
30058
|
+
if (this.lifetime.since === null || at < this.lifetime.since) this.lifetime.since = at;
|
|
30059
|
+
if (this.lifetime.asOf === null || at > this.lifetime.asOf) this.lifetime.asOf = at;
|
|
30060
|
+
}
|
|
30061
|
+
/** Existing replay initializes this once; subsequent reads never scan history. */
|
|
30062
|
+
savingsSnapshot() {
|
|
30063
|
+
return Object.freeze({ ...this.lifetime });
|
|
30064
|
+
}
|
|
30097
30065
|
consumeNoticeForExecution(executionId) {
|
|
30098
30066
|
if (this.closed) return Promise.reject(failure4("OPTIMIZATION_LEDGER_CLOSED"));
|
|
30099
30067
|
const work = this.queue.then(async () => {
|
|
@@ -30164,6 +30132,25 @@ import { createHash as createHash12, randomUUID as randomUUID8 } from "node:cryp
|
|
|
30164
30132
|
import { lstat as lstat8, mkdir as mkdir6, open as open7, rename as rename5, rm as rm5 } from "node:fs/promises";
|
|
30165
30133
|
import path14 from "node:path";
|
|
30166
30134
|
import os4 from "node:os";
|
|
30135
|
+
|
|
30136
|
+
// packages/agent-cli/bin/monitor-savings.mjs
|
|
30137
|
+
var count = (value) => Number.isSafeInteger(value) && value >= 0 && value <= 1e15;
|
|
30138
|
+
var date5 = (value) => typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
30139
|
+
function lifetimeSavingsView(raw) {
|
|
30140
|
+
if (!raw || raw.scope !== "all_time" || raw.coverage !== "recorded_native_results" || ![raw.calls, raw.originalBytes, raw.sentBytes, raw.savedBytes].every(count) || raw.sentBytes > raw.originalBytes || raw.savedBytes !== raw.originalBytes - raw.sentBytes || (raw.calls === 0 ? raw.since !== null || raw.asOf !== null || raw.originalBytes !== 0 : !date5(raw.since) || !date5(raw.asOf) || raw.since > raw.asOf)) return null;
|
|
30141
|
+
return {
|
|
30142
|
+
scope: "all_time",
|
|
30143
|
+
coverage: "recorded_native_results",
|
|
30144
|
+
since: raw.since,
|
|
30145
|
+
asOf: raw.asOf,
|
|
30146
|
+
calls: raw.calls,
|
|
30147
|
+
originalBytes: raw.originalBytes,
|
|
30148
|
+
sentBytes: raw.sentBytes,
|
|
30149
|
+
savedBytes: raw.savedBytes
|
|
30150
|
+
};
|
|
30151
|
+
}
|
|
30152
|
+
|
|
30153
|
+
// agent/src/monitor.ts
|
|
30167
30154
|
function commandSummary2(tool) {
|
|
30168
30155
|
if (tool.name !== "command_run") return { name: tool.name, command: tool.name };
|
|
30169
30156
|
const candidate = path14.win32.basename(tool.args.command).replace(/\.(exe|cmd|bat|com)$/i, "").toLowerCase();
|
|
@@ -30203,6 +30190,7 @@ var ProductionMonitor = class {
|
|
|
30203
30190
|
freshnessMs = 1e4;
|
|
30204
30191
|
transportAt = null;
|
|
30205
30192
|
file;
|
|
30193
|
+
lifetimeSavings = null;
|
|
30206
30194
|
record(event, details = {}) {
|
|
30207
30195
|
this.events.push({ seq: ++this.sequence, at: (/* @__PURE__ */ new Date()).toISOString(), event, ...details });
|
|
30208
30196
|
if (this.events.length > 100) this.events.shift();
|
|
@@ -30333,6 +30321,10 @@ var ProductionMonitor = class {
|
|
|
30333
30321
|
requestFailed() {
|
|
30334
30322
|
this.record("request_failed");
|
|
30335
30323
|
}
|
|
30324
|
+
/** Only the existing ledger supplies lifetime evidence; no diagnostic persistence. */
|
|
30325
|
+
setLifetimeSavings(value) {
|
|
30326
|
+
this.lifetimeSavings = lifetimeSavingsView(value);
|
|
30327
|
+
}
|
|
30336
30328
|
connectionFailed(error62) {
|
|
30337
30329
|
const code = error62 instanceof Error ? error62.message : "";
|
|
30338
30330
|
this.record("connection_error", { code: [
|
|
@@ -30355,6 +30347,8 @@ var ProductionMonitor = class {
|
|
|
30355
30347
|
schemaVersion: 1,
|
|
30356
30348
|
instanceId: this.instanceId,
|
|
30357
30349
|
pid: process.pid,
|
|
30350
|
+
savingsSourceId: path14.basename(this.file, ".json"),
|
|
30351
|
+
lifetimeSavings: this.lifetimeSavings && { ...this.lifetimeSavings },
|
|
30358
30352
|
computer: this.computer,
|
|
30359
30353
|
processName: "lc-agent",
|
|
30360
30354
|
agentVersion: this.agentVersion,
|
|
@@ -31790,7 +31784,7 @@ async function readFabricRequest(input2 = process.stdin) {
|
|
|
31790
31784
|
}
|
|
31791
31785
|
|
|
31792
31786
|
// agent/src/main.ts
|
|
31793
|
-
var AGENT_VERSION2 = true ? "0.3.
|
|
31787
|
+
var AGENT_VERSION2 = true ? "0.3.22-systemca.1" : "0.3.2";
|
|
31794
31788
|
function processAlive(pid) {
|
|
31795
31789
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
31796
31790
|
try {
|
|
@@ -31867,10 +31861,14 @@ async function runAgent(options) {
|
|
|
31867
31861
|
throw error62;
|
|
31868
31862
|
}
|
|
31869
31863
|
const ledger = options.efficiencyLedger;
|
|
31864
|
+
monitor(() => options.monitor?.setLifetimeSavings(ledger?.savingsSnapshot?.()));
|
|
31870
31865
|
const efficiencyLedger = ledger && {
|
|
31871
31866
|
record: async (input2) => {
|
|
31872
31867
|
const recorded = await ledger.record(input2);
|
|
31873
|
-
if (recorded) monitor(() =>
|
|
31868
|
+
if (recorded) monitor(() => {
|
|
31869
|
+
options.monitor?.efficiency(input2);
|
|
31870
|
+
options.monitor?.setLifetimeSavings(ledger.savingsSnapshot?.());
|
|
31871
|
+
});
|
|
31874
31872
|
return recorded;
|
|
31875
31873
|
},
|
|
31876
31874
|
...ledger.consumeNoticeForExecution ? { consumeNoticeForExecution: ledger.consumeNoticeForExecution.bind(ledger) } : {}
|