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 CHANGED
@@ -1,8 +1,14 @@
1
1
  # Change history
2
2
 
3
- ## 0.3.21-systemca.1 - 2026-10-11
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.21-project-targets.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
- ### Approve Git from CMD or PowerShell
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
- First run `npx local-commander-agent@latest workspaces` and copy the intended workspace's grant ID.
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
- Full access does not include Windows shells, script hosts or administration binaries (`cmd`, `powershell`, `pwsh`, `wscript`, `cscript`, `mshta`, `rundll32`, `regsvr32`, `certutil`, `bitsadmin`, `msiexec`, `wmic`, `reg`, `schtasks`, `sc`, `net`, `netsh`, `icacls`, `takeown`, `runas`, `psexec`, `wsl`, `bash`, `sh`, `dism`, `winrm` and similar). An assistant request for one of these, by bare name or absolute path, fails with `COMMAND_NOT_ALLOWED` until you register that exact executable yourself with `allow-command <workspace-id> <name> <absolute .exe path>` and confirm the native dialog; it then runs only under the registered name or that exact path. Ordinary developer tools (`node`, `git`, `npm`, `python`, `dotnet`, ...) continue to resolve from your absolute `PATH` entries.
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
- The MCP discovery sequence is `workspace_list`, `diagnostics`, `execution_session_open`, requested work, output/status, then session close. 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.
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.
@@ -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
- stats.length ? "Raw: " + tokens(saved + sent) + " tokens Sent: " + tokens(sent) + " Saved: " + tokens(saved) +
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.21-systemca.1",
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": {
Binary file
Binary file
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 count = units2;
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
- count--;
5078
+ count2--;
5079
5079
  i++;
5080
5080
  }
5081
5081
  }
5082
- return count;
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(count, one, few, many) {
9438
- const absCount = Math.abs(count);
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(count, one, many) {
12086
- return Math.abs(count) === 1 ? one : many;
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(count, one, few, many) {
14825
- const absCount = Math.abs(count);
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 count = 0;
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
- count++;
25107
+ count2++;
25108
25108
  }
25109
- return count;
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 count = this.entries.size, bytes3 = this.totalBytes;
25118
- if (count < this.options.maxEntries && bytes3 + MAX_ENVELOPE <= this.options.maxBytes) return true;
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
- count--;
25122
+ count2--;
25123
25123
  bytes3 -= this.sizes.get(entry.executionId);
25124
- if (count < this.options.maxEntries && bytes3 + MAX_ENVELOPE <= this.options.maxBytes) return true;
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 count = 0;
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
- count++;
25140
+ count2++;
25141
25141
  }
25142
- return count;
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 count = 0, truncated = false;
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 - count);
27431
+ const take = Math.min(b.length, 1e5 - count2);
27485
27432
  chunks.push(b.subarray(0, take));
27486
- count += take;
27487
- if (take !== b.length || count >= 1e5) {
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 count = SUMMARY_COUNT.exec(line);
28010
- if (count) {
28011
- const key = count[1];
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(count[2]));
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 count = SUMMARY_COUNT.exec(line);
28128
- if (count) {
28129
- const key = count[1];
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(count[2]));
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(date5) {
28360
- const year = date5.getFullYear();
28361
- const month = String(date5.getMonth() + 1).padStart(2, "0");
28362
- const day = String(date5.getDate()).padStart(2, "0");
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 date5 = new Date(value);
29020
- if (!Number.isFinite(date5.getTime())) fail3("RELAY_REQUEST_INVALID");
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 date5 = new Date(value);
29507
- if (!Number.isFinite(date5.getTime())) fail4(code);
29508
- return date5;
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.21-systemca.1" : "0.3.2";
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(() => options.monitor?.efficiency(input2));
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) } : {}