@volter/supercode-orchestrator 0.5.181 → 0.5.182
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/board/doors.mjs +42 -3
- package/package.json +1 -1
package/board/doors.mjs
CHANGED
|
@@ -26,8 +26,11 @@ async function opened(machine) {
|
|
|
26
26
|
}
|
|
27
27
|
const opening = (async () => {
|
|
28
28
|
const channel = await openMachine({ machine: machine || null });
|
|
29
|
-
const door = { channel, node: null, harness: null };
|
|
29
|
+
const door = { channel, node: null, harness: null, heardAt: performance.now() };
|
|
30
|
+
// when the channel last carried anything (an answer, a stream's data, the keep-alive's pong): see retireSilent
|
|
31
|
+
channel.socket?.on?.('data', () => { door.heardAt = performance.now(); });
|
|
30
32
|
channel.on('close', () => { if (held.get(key) === opening) held.delete(key); });
|
|
33
|
+
door.opening = opening;
|
|
31
34
|
return door;
|
|
32
35
|
})();
|
|
33
36
|
held.set(key, opening);
|
|
@@ -43,13 +46,14 @@ async function opened(machine) {
|
|
|
43
46
|
export async function machineCall(machine, method, params = {}, { timeoutMs = 60_000 } = {}) {
|
|
44
47
|
if (!method.startsWith('harness.v1.')) throw new Error('a machine door is a harness.v1 method');
|
|
45
48
|
const started = performance.now();
|
|
49
|
+
let door = null;
|
|
46
50
|
try {
|
|
47
|
-
|
|
51
|
+
door = await opened(machine);
|
|
48
52
|
if (method.startsWith('harness.v1.sessions.')) {
|
|
49
53
|
// The harness client starts its transport again itself once it ends (a daemon restart there); one that never
|
|
50
54
|
// started is let go, so the next call starts another.
|
|
51
55
|
if (!door.harness) {
|
|
52
|
-
const starting = (async () => { const client = new SupercodeHarnessClient({ transport:
|
|
56
|
+
const starting = (async () => { const client = new SupercodeHarnessClient({ transport: channelHarness(door.channel, machine) }); await client.start?.(); return client; })();
|
|
53
57
|
starting.catch(() => { if (door.harness === starting) door.harness = null; });
|
|
54
58
|
door.harness = starting;
|
|
55
59
|
}
|
|
@@ -66,11 +70,46 @@ export async function machineCall(machine, method, params = {}, { timeoutMs = 60
|
|
|
66
70
|
door.node = opening;
|
|
67
71
|
}
|
|
68
72
|
return await (await door.node).call(method, params, { timeoutMs });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (machine && door && (error?.code === 'REQUEST_TIMEOUT' || error?.code === 504)) retireSilent(machine, door, started);
|
|
75
|
+
throw error;
|
|
69
76
|
} finally {
|
|
70
77
|
noteStep(`door ${method}${machine ? ' (another machine)' : ''}`, performance.now() - started);
|
|
71
78
|
}
|
|
72
79
|
}
|
|
73
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Another machine's channel that carried nothing at all for the whole of a request's bound is closed, so the next call
|
|
83
|
+
* opens a fresh one. Such a channel's socket to the Teams server can stay open after the server has lost the far side:
|
|
84
|
+
* every request on it waits out its bound, and nothing ends the channel until the server closes the socket. On
|
|
85
|
+
* 2026-10-14 the board owner's discoveries on other machines timed out five times running (00:05:16 to 00:06:31), the
|
|
86
|
+
* server closed their channel at 00:06:37 (two calls failed "connection closed"), and the next channel answered in
|
|
87
|
+
* 1.4 s. The keep-alive's ping is answered by the far machine's own channel, so a channel that carried anything
|
|
88
|
+
* meanwhile (another answer, a pong) reaches that machine and is kept: the request was slow there.
|
|
89
|
+
*/
|
|
90
|
+
function retireSilent(machine, door, started) {
|
|
91
|
+
if (door.heardAt >= started || door.channel.closed) return;
|
|
92
|
+
if (held.get(machine) === door.opening) held.delete(machine);
|
|
93
|
+
door.channel.close();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The harness transport over a machine channel, saying what ended when it ends: the channel's stream is no harness
|
|
98
|
+
* process, and its close (`connection closed`) read as "Volter Harness service exited" sent F81 looking for a child
|
|
99
|
+
* that never exited.
|
|
100
|
+
*/
|
|
101
|
+
function channelHarness(channel, machine) {
|
|
102
|
+
const inner = harnessTransport(channel);
|
|
103
|
+
const where = machine ? `the Teams server channel to ${machine}` : "the socket to this machine's daemon";
|
|
104
|
+
return {
|
|
105
|
+
get running() { return inner.running; },
|
|
106
|
+
start: (handlers) => inner.start({ ...handlers,
|
|
107
|
+
onExit: (code, reason) => handlers.onExit(code, `${reason ?? 'closed'}: ${where} ended, no harness process exited`) }),
|
|
108
|
+
write: (line) => inner.write(line),
|
|
109
|
+
close: () => inner.close(),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
74
113
|
export async function machineRecordStream(params = {}) {
|
|
75
114
|
const door = await opened(null);
|
|
76
115
|
return door.channel.open('machine-record-changes', params);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.182",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
|
|
6
6
|
"exports": {
|