@securecoms/agent-runtime 0.13.0 → 0.13.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/README.md +21 -0
- package/dist/bin.js +97 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -527,6 +527,27 @@ The paragraph is therefore an advisory configuration summary, not an authority
|
|
|
527
527
|
claim. Channel authorization and the local gate are both enforced at call
|
|
528
528
|
time, so a configured but ungranted or denied tool is still refused.
|
|
529
529
|
|
|
530
|
+
## Release notes: 0.13.1
|
|
531
|
+
|
|
532
|
+
<!-- [COPY, approved by Eric 2026-10-15] -->
|
|
533
|
+
|
|
534
|
+
0.13.1 prints a plain line when the runtime cannot reach the SecureComs Agent
|
|
535
|
+
Connector, and says so once instead of on every retry.
|
|
536
|
+
|
|
537
|
+
- **Plain connection lines.** While it waits or fails, the runtime prints one
|
|
538
|
+
line for the cause, for example `Waiting for the SecureComs Agent Connector
|
|
539
|
+
to start on this computer...` or `Another agent's Connector is using this
|
|
540
|
+
agent's port. Restart this agent's Connector.` When it connects it prints
|
|
541
|
+
`Connected to the SecureComs Agent Connector.`
|
|
542
|
+
- **Quieter log.** The service log records each cause once until it changes,
|
|
543
|
+
not on every retry. The log names the cause but never a token, a path or an
|
|
544
|
+
identity id.
|
|
545
|
+
- **Plainer install refusals.** When install cannot use this computer's Node,
|
|
546
|
+
it names the Node version it needs and the fixed places where Node must be
|
|
547
|
+
installed.
|
|
548
|
+
|
|
549
|
+
No setting changes. Use it with `@securecoms/agent-bridge` 0.30.2.
|
|
550
|
+
|
|
530
551
|
## Release notes: 0.13.0
|
|
531
552
|
|
|
532
553
|
<!-- [COPY, approved by Eric 2026-10-12] -->
|
package/dist/bin.js
CHANGED
|
@@ -77,6 +77,25 @@ function failureEpisodeReporter(warn) {
|
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
+
function reasonEpisodeReporter(onNew) {
|
|
81
|
+
let last = null;
|
|
82
|
+
return {
|
|
83
|
+
failed(reason) {
|
|
84
|
+
if (reason === last) return;
|
|
85
|
+
last = reason;
|
|
86
|
+
try {
|
|
87
|
+
onNew(reason);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
attached() {
|
|
92
|
+
last = null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function writeHumanLine(line2) {
|
|
97
|
+
console.error(line2);
|
|
98
|
+
}
|
|
80
99
|
function writeServiceLog(level, event, fields, error) {
|
|
81
100
|
const line2 = formatServiceLog(level, event, fields, error);
|
|
82
101
|
if (level === "error") console.error(line2);
|
|
@@ -1547,7 +1566,10 @@ var STABLE_LOCATIONS = [
|
|
|
1547
1566
|
// system (linux)
|
|
1548
1567
|
];
|
|
1549
1568
|
function candidates(home) {
|
|
1550
|
-
return home ? [...STABLE_LOCATIONS, `${home}/.local/bin/node`] : STABLE_LOCATIONS;
|
|
1569
|
+
return home ? [...STABLE_LOCATIONS, `${home}/.local/bin/node`] : [...STABLE_LOCATIONS];
|
|
1570
|
+
}
|
|
1571
|
+
function orList(items) {
|
|
1572
|
+
return items.length <= 1 ? items.join("") : `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
|
|
1551
1573
|
}
|
|
1552
1574
|
function usable(path, deps) {
|
|
1553
1575
|
if (!deps.fileExists(path)) return false;
|
|
@@ -1586,7 +1608,7 @@ function chooseInstallExecPath(runningExecPath, override, deps, home) {
|
|
|
1586
1608
|
const r = resolveStableNodePath(runningExecPath, deps, home);
|
|
1587
1609
|
if (!r.stable) {
|
|
1588
1610
|
throw new Error(
|
|
1589
|
-
`
|
|
1611
|
+
`The agent's background service needs Node ${REQUIRED_NODE_MAJOR} or newer at a fixed location (${orList(candidates(home))}), and none was found. Node from nvm, fnm and similar tools moves when it is upgraded, so it is not used. Install Node ${REQUIRED_NODE_MAJOR} from https://nodejs.org, then run the install command again.`
|
|
1590
1612
|
);
|
|
1591
1613
|
}
|
|
1592
1614
|
return r.path;
|
|
@@ -1602,7 +1624,7 @@ function abs(p) {
|
|
|
1602
1624
|
function deriveServiceConfig(input) {
|
|
1603
1625
|
if (isVersionEmbeddedNodePath(input.execPath)) {
|
|
1604
1626
|
throw new Error(
|
|
1605
|
-
`
|
|
1627
|
+
`The Node at ${input.execPath} moves when it is upgraded, so the background service can't use it. Install Node ${REQUIRED_NODE_MAJOR} from https://nodejs.org, then run the install command again.`
|
|
1606
1628
|
);
|
|
1607
1629
|
}
|
|
1608
1630
|
const head = input.identityId.slice(0, 8).toLowerCase();
|
|
@@ -4384,6 +4406,26 @@ function parseVoiceGuidance(frame) {
|
|
|
4384
4406
|
const g = frame.guidance.trim().slice(0, VOICE_GUIDANCE_MAX_CHARS);
|
|
4385
4407
|
return g || void 0;
|
|
4386
4408
|
}
|
|
4409
|
+
var TransportStepError = class extends Error {
|
|
4410
|
+
constructor(reason, options) {
|
|
4411
|
+
super(reason, options);
|
|
4412
|
+
this.reason = reason;
|
|
4413
|
+
this.name = "TransportStepError";
|
|
4414
|
+
}
|
|
4415
|
+
};
|
|
4416
|
+
var TRANSPORT_LINES = {
|
|
4417
|
+
no_connection_file: "Waiting for the SecureComs Agent Connector to start on this computer...",
|
|
4418
|
+
bridge_unreachable: "The SecureComs Agent Connector is not answering. Check that it is running on this computer.",
|
|
4419
|
+
wrong_agent: "Another agent's Connector is using this agent's port. Restart this agent's Connector.",
|
|
4420
|
+
attach_refused: "The Connector refused this runtime. Restart the Connector, then restart this runtime.",
|
|
4421
|
+
connection_file_unreadable: "Can't read the Connector's connection details. Restart the Connector.",
|
|
4422
|
+
token_unreadable: "Can't read the Connector's connection details. Restart the Connector.",
|
|
4423
|
+
unknown: "Can't connect to the SecureComs Agent Connector yet. Retrying.",
|
|
4424
|
+
attached: "Connected to the SecureComs Agent Connector."
|
|
4425
|
+
};
|
|
4426
|
+
function isEnoent(e) {
|
|
4427
|
+
return typeof e === "object" && e !== null && e.code === "ENOENT";
|
|
4428
|
+
}
|
|
4387
4429
|
var SEEN_MAX = 5e3;
|
|
4388
4430
|
var PENDING_REPLY_MAX = 1e3;
|
|
4389
4431
|
var TERMINAL_REPLY_ERRORS = /* @__PURE__ */ new Set(["agent_paused", "send_forbidden"]);
|
|
@@ -4394,6 +4436,11 @@ var SecureComsDataPlane = class {
|
|
|
4394
4436
|
initialDelayMs;
|
|
4395
4437
|
maxDelayMs;
|
|
4396
4438
|
log;
|
|
4439
|
+
print;
|
|
4440
|
+
/** Tier A item 6: one report per reason episode; the settle timer ends it. */
|
|
4441
|
+
episodes;
|
|
4442
|
+
/** The cause behind the failure currently being reported (its class is logged). */
|
|
4443
|
+
failureCause = void 0;
|
|
4397
4444
|
sleep;
|
|
4398
4445
|
random;
|
|
4399
4446
|
healthTimeoutMs;
|
|
@@ -4466,6 +4513,11 @@ var SecureComsDataPlane = class {
|
|
|
4466
4513
|
this.initialDelayMs = cfg.initialDelayMs ?? 250;
|
|
4467
4514
|
this.maxDelayMs = cfg.maxDelayMs ?? 3e4;
|
|
4468
4515
|
this.log = cfg.log ?? writeServiceLog;
|
|
4516
|
+
this.print = cfg.print ?? writeHumanLine;
|
|
4517
|
+
this.episodes = reasonEpisodeReporter((reason) => {
|
|
4518
|
+
this.log("error", "transport.connection_attempt_failed", { reason: logLabel(reason) }, this.failureCause);
|
|
4519
|
+
this.print(TRANSPORT_LINES[reason] ?? TRANSPORT_LINES.unknown);
|
|
4520
|
+
});
|
|
4469
4521
|
this.sleep = cfg.sleep ?? ((ms2) => new Promise((resolve4) => {
|
|
4470
4522
|
this.sleepTimer = setTimeout(() => {
|
|
4471
4523
|
this.sleepTimer = null;
|
|
@@ -4552,6 +4604,13 @@ var SecureComsDataPlane = class {
|
|
|
4552
4604
|
void this.sleep(ms2).then(finish, finish);
|
|
4553
4605
|
});
|
|
4554
4606
|
}
|
|
4607
|
+
/** A human line from a timer callback: a throwing sink must never escape it. */
|
|
4608
|
+
say(line2) {
|
|
4609
|
+
try {
|
|
4610
|
+
this.print(line2);
|
|
4611
|
+
} catch {
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4555
4614
|
clearSettle() {
|
|
4556
4615
|
if (this.settle) {
|
|
4557
4616
|
clearTimeout(this.settle);
|
|
@@ -4573,7 +4632,9 @@ var SecureComsDataPlane = class {
|
|
|
4573
4632
|
await this.attemptOnce();
|
|
4574
4633
|
delay = this.initialDelayMs;
|
|
4575
4634
|
} catch (e) {
|
|
4576
|
-
|
|
4635
|
+
const step = e instanceof TransportStepError ? e : null;
|
|
4636
|
+
this.failureCause = step && step.cause !== void 0 ? step.cause : e;
|
|
4637
|
+
this.episodes.failed(step ? step.reason : "unknown");
|
|
4577
4638
|
}
|
|
4578
4639
|
if (!live()) break;
|
|
4579
4640
|
await this.wait(this.random() * delay);
|
|
@@ -4582,9 +4643,26 @@ var SecureComsDataPlane = class {
|
|
|
4582
4643
|
if (this.generation === generation) this.running = false;
|
|
4583
4644
|
}
|
|
4584
4645
|
async attemptOnce() {
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4646
|
+
let raw;
|
|
4647
|
+
try {
|
|
4648
|
+
raw = readFileSync10(this.connectionPath, "utf8");
|
|
4649
|
+
} catch (e) {
|
|
4650
|
+
throw new TransportStepError(isEnoent(e) ? "no_connection_file" : "connection_file_unreadable", { cause: e });
|
|
4651
|
+
}
|
|
4652
|
+
let conn;
|
|
4653
|
+
let httpBase;
|
|
4654
|
+
try {
|
|
4655
|
+
conn = JSON.parse(raw);
|
|
4656
|
+
httpBase = conn.bridge_url.replace(/^ws/, "http").replace(/\/v1\/ws$/, "");
|
|
4657
|
+
} catch (e) {
|
|
4658
|
+
throw new TransportStepError("connection_file_unreadable", { cause: e });
|
|
4659
|
+
}
|
|
4660
|
+
let token;
|
|
4661
|
+
try {
|
|
4662
|
+
token = readFileSync10(conn.token_file, "utf8").trim();
|
|
4663
|
+
} catch (e) {
|
|
4664
|
+
throw new TransportStepError("token_unreadable", { cause: e });
|
|
4665
|
+
}
|
|
4588
4666
|
const healthController = new AbortController();
|
|
4589
4667
|
this.healthController = healthController;
|
|
4590
4668
|
const healthTimer = setTimeout(
|
|
@@ -4594,14 +4672,14 @@ var SecureComsDataPlane = class {
|
|
|
4594
4672
|
let health;
|
|
4595
4673
|
try {
|
|
4596
4674
|
health = await (await fetch(`${httpBase}/healthz`, { signal: healthController.signal })).json();
|
|
4675
|
+
} catch (e) {
|
|
4676
|
+
throw new TransportStepError("bridge_unreachable", { cause: e });
|
|
4597
4677
|
} finally {
|
|
4598
4678
|
clearTimeout(healthTimer);
|
|
4599
4679
|
if (this.healthController === healthController) this.healthController = null;
|
|
4600
4680
|
}
|
|
4601
4681
|
if (conn.identity_id !== health.identity_id || this.identityId !== health.identity_id) {
|
|
4602
|
-
throw new
|
|
4603
|
-
`gate refused: expected ${this.identityId}, connection file says ${conn.identity_id}, daemon on this port is ${health.identity_id}`
|
|
4604
|
-
);
|
|
4682
|
+
throw new TransportStepError("wrong_agent");
|
|
4605
4683
|
}
|
|
4606
4684
|
if (this.stopped) return;
|
|
4607
4685
|
const ws = new WebSocket(`${conn.bridge_url}?token=${encodeURIComponent(token)}`);
|
|
@@ -4611,15 +4689,18 @@ var SecureComsDataPlane = class {
|
|
|
4611
4689
|
try {
|
|
4612
4690
|
await new Promise((resolve4, reject) => {
|
|
4613
4691
|
ws.addEventListener("error", (e) => {
|
|
4614
|
-
|
|
4692
|
+
const err = Object.assign(new Error("socket_error"), { cause: e });
|
|
4693
|
+
reject(established ? err : new TransportStepError("attach_refused", { cause: err }));
|
|
4615
4694
|
});
|
|
4616
4695
|
ws.addEventListener("close", (e) => {
|
|
4617
4696
|
if (established) resolve4();
|
|
4618
4697
|
else {
|
|
4619
4698
|
reject(
|
|
4620
|
-
new
|
|
4621
|
-
|
|
4622
|
-
|
|
4699
|
+
new TransportStepError("attach_refused", {
|
|
4700
|
+
cause: new Error(
|
|
4701
|
+
`closed with code ${e.code} before the connection settled \u2014 bad token, or another connector took this port`
|
|
4702
|
+
)
|
|
4703
|
+
})
|
|
4623
4704
|
);
|
|
4624
4705
|
}
|
|
4625
4706
|
});
|
|
@@ -4632,6 +4713,8 @@ var SecureComsDataPlane = class {
|
|
|
4632
4713
|
settle = setTimeout(() => {
|
|
4633
4714
|
established = true;
|
|
4634
4715
|
this.log("info", "transport.attached");
|
|
4716
|
+
this.episodes.attached();
|
|
4717
|
+
this.say(TRANSPORT_LINES.attached);
|
|
4635
4718
|
}, this.establishedMs);
|
|
4636
4719
|
this.settle = settle;
|
|
4637
4720
|
});
|