local-operator-ui 0.30.3 → 0.30.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/main/index.js +58 -3
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -5773,6 +5773,16 @@ class DaemonStateMachine {
|
|
|
5773
5773
|
this.state = "attached";
|
|
5774
5774
|
this.detail = this.identity ? `Connected to the daemon on ${this.identity.url} (pid ${this.identity.pid}, v${this.identity.version}).` : "Connected to the Local Operator daemon.";
|
|
5775
5775
|
break;
|
|
5776
|
+
case "reanchored":
|
|
5777
|
+
this.identity = observation.identity;
|
|
5778
|
+
this.failures = 0;
|
|
5779
|
+
this.unanswered = 0;
|
|
5780
|
+
this.capabilityStatus = null;
|
|
5781
|
+
this.detachedSince = null;
|
|
5782
|
+
this.backoffMs = REATTACH_BACKOFF_MS;
|
|
5783
|
+
this.state = "attached";
|
|
5784
|
+
this.detail = observation.detail;
|
|
5785
|
+
break;
|
|
5776
5786
|
case "capability":
|
|
5777
5787
|
this.capabilityStatus = observation.status;
|
|
5778
5788
|
this.detail = `${observation.detail} The daemon is running.`;
|
|
@@ -8876,8 +8886,16 @@ class BackendServiceManager {
|
|
|
8876
8886
|
const probe = await probeIdentity(this.backendUrl, expected, {
|
|
8877
8887
|
timeoutMs: PROBE_TIMEOUT_MS
|
|
8878
8888
|
});
|
|
8879
|
-
const
|
|
8880
|
-
const
|
|
8889
|
+
const attachedPid = this.daemonState.snapshot().pid;
|
|
8890
|
+
const reanchored = probe.outcome === "identity-mismatch" && attachedPid !== null && probe.identity.pid === attachedPid && this.holdsLiveChildProcess(attachedPid) ? (
|
|
8891
|
+
// Spread rather than field by field: this value IS `probe.identity` plus the
|
|
8892
|
+
// address. A field added to `DaemonIdentity` later must not have to be
|
|
8893
|
+
// remembered in this one literal, where it would silently take the widened
|
|
8894
|
+
// type's `undefined` instead of the answering value.
|
|
8895
|
+
{ ...probe.identity, url: this.backendUrl }
|
|
8896
|
+
) : null;
|
|
8897
|
+
const answersThisApp = reanchored !== null || probe.outcome === "identified" && probe.identity.pid === this.daemonState.snapshot().pid;
|
|
8898
|
+
const answeredAnotherProcess = reanchored === null && (probe.outcome === "identity-mismatch" || probe.outcome === "not-a-daemon" || probe.outcome === "identified");
|
|
8881
8899
|
if (answersThisApp) {
|
|
8882
8900
|
this.daemonState.setPairing(DAEMON_PAIRED);
|
|
8883
8901
|
} else if (answeredAnotherProcess) {
|
|
@@ -8893,6 +8911,13 @@ class BackendServiceManager {
|
|
|
8893
8911
|
detail: "The answering daemon's PID no longer matches the attachment."
|
|
8894
8912
|
};
|
|
8895
8913
|
case "identity-mismatch":
|
|
8914
|
+
if (reanchored) {
|
|
8915
|
+
return {
|
|
8916
|
+
kind: "reanchored",
|
|
8917
|
+
identity: reanchored,
|
|
8918
|
+
detail: `Connected to the daemon on ${this.backendUrl} (pid ${reanchored.pid}, v${reanchored.version}). It reloaded in place and kept the same process.`
|
|
8919
|
+
};
|
|
8920
|
+
}
|
|
8896
8921
|
return {
|
|
8897
8922
|
kind: "contradicted",
|
|
8898
8923
|
detail: `Another process is answering at ${this.backendUrl} (${probe.detail})`
|
|
@@ -9019,7 +9044,7 @@ class BackendServiceManager {
|
|
|
9019
9044
|
try {
|
|
9020
9045
|
if (this.daemonState.expectedInstanceId()) {
|
|
9021
9046
|
const observation = await this.probeAttachedDaemon();
|
|
9022
|
-
if (observation.kind === "identified") {
|
|
9047
|
+
if (observation.kind === "identified" || observation.kind === "reanchored") {
|
|
9023
9048
|
this.daemonState.observe(observation);
|
|
9024
9049
|
this.nextRecoveryAt = 0;
|
|
9025
9050
|
this.notifyStatus();
|
|
@@ -9068,6 +9093,36 @@ class BackendServiceManager {
|
|
|
9068
9093
|
getOwnedPid() {
|
|
9069
9094
|
return this.process?.pid ?? null;
|
|
9070
9095
|
}
|
|
9096
|
+
/**
|
|
9097
|
+
* Whether `pid` is the process this app spawned and that process is still live.
|
|
9098
|
+
*
|
|
9099
|
+
* The three clauses are one statement - "this is my child, and it has not
|
|
9100
|
+
* exited" - and each is load-bearing:
|
|
9101
|
+
*
|
|
9102
|
+
* - the handle is the proof of identity. A `ChildProcess` is only ever built by
|
|
9103
|
+
* this manager's own `spawn()`, so its `pid` cannot be a number read off a
|
|
9104
|
+
* record that something else wrote, and it cannot be recycled: the OS does not
|
|
9105
|
+
* reuse a pid while the process it names is alive, and a reused one means THIS
|
|
9106
|
+
* child exited - which the `exitCode` clause reads. That reading is Node's
|
|
9107
|
+
* RECORDED exit, set when the process event arrives rather than when the process
|
|
9108
|
+
* dies, so a pid recycled inside the window before it is recorded would pass
|
|
9109
|
+
* here. It is the same window the recovery guard accepts, and the caller that
|
|
9110
|
+
* matters (the reload re-anchor) needs the answering pid to be bound to this
|
|
9111
|
+
* app's own port as well, which is why this is stated rather than hedged.
|
|
9112
|
+
* - `exitCode === null && signalCode == null` is the same liveness spelling
|
|
9113
|
+
* `stop()` and the recovery guard act on (see `ownedPid`'s note), so a process
|
|
9114
|
+
* this app has already reaped is never treated as live by one caller and dead
|
|
9115
|
+
* by another;
|
|
9116
|
+
* - a `null` pid is not a match. A manager that has never spawned anything owns
|
|
9117
|
+
* nothing, and `undefined === undefined` must not read as ownership.
|
|
9118
|
+
*
|
|
9119
|
+
* Used by the reload re-anchor in `probeAttachedDaemon`, which is the one place
|
|
9120
|
+
* this app decides that a changed `instance_id` belongs to the process it
|
|
9121
|
+
* already holds rather than to a successor.
|
|
9122
|
+
*/
|
|
9123
|
+
holdsLiveChildProcess(pid) {
|
|
9124
|
+
return pid !== null && this.process !== null && this.process.pid === pid && this.process.exitCode === null && this.process.signalCode == null;
|
|
9125
|
+
}
|
|
9071
9126
|
/**
|
|
9072
9127
|
* The install SERVING this app, read from the serving process's own record.
|
|
9073
9128
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "local-operator-ui",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.4",
|
|
4
4
|
"productName": "Local Operator",
|
|
5
5
|
"description": "User interface for AI agent assistants that run Python code safely on your device through an intuitive chat interface. Supports local and cloud LLM models with built-in security features.",
|
|
6
6
|
"main": "./out/main/index.js",
|