@volter/supercode-orchestrator 0.5.47 → 0.5.48
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 +6 -4
- package/bin/runtime.mjs +23 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -525,10 +525,12 @@ failed/timeout fires without replaying them; a restart advances a stale claim
|
|
|
525
525
|
once. `callOperator` addresses the live socket, with no cold-write fallback.
|
|
526
526
|
`Orchestrator({executeJob})` is the corresponding in-process API for embedders.
|
|
527
527
|
|
|
528
|
-
The daemon
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
528
|
+
The daemon serves a home only while it holds the home's claim, Hermes's
|
|
529
|
+
`gateway.lock` (an flock the kernel releases when its holder dies). A second
|
|
530
|
+
daemon on the home waits for the claim and serves the home as soon as the first
|
|
531
|
+
is gone, however it ended; nothing fails closed or waits for a person. The
|
|
532
|
+
serving daemon records itself in `orchestrator.lock` for `supercode orchestrator
|
|
533
|
+
status` and the operator verbs; a normal stop removes only its own record. Native `jobs.resume` computes the first fire
|
|
532
534
|
for a job that was created paused with no next-run timestamp.
|
|
533
535
|
|
|
534
536
|
## Hosted daemon lifecycle
|
package/bin/runtime.mjs
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
// Everything it prints is one JSON object per line on stdout.
|
|
54
54
|
|
|
55
55
|
import { SenderIdentityRecord } from '@volter/teams/sender-identity';
|
|
56
|
-
import { existsSync,
|
|
56
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
57
57
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
58
58
|
import { spawn, spawnSync } from 'node:child_process';
|
|
59
59
|
import { fileURLToPath } from 'node:url';
|
|
@@ -75,7 +75,8 @@ import { httpJobExecutor } from '../hosted.mjs';
|
|
|
75
75
|
import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
|
|
76
76
|
|
|
77
77
|
/** How long a starting daemon waits for a stopping predecessor to release the home's lease. */
|
|
78
|
-
|
|
78
|
+
/** How often a daemon waiting for the home's claim asks for it again. */
|
|
79
|
+
const CLAIM_RETRY_MS = 2_000;
|
|
79
80
|
|
|
80
81
|
/// Adapter selector -> constructor. A `platforms:` block picks its adapter by
|
|
81
82
|
/// `extra.adapter` when it has one and by the block's own name otherwise, so
|
|
@@ -388,16 +389,16 @@ function profileReport(name, profile) {
|
|
|
388
389
|
};
|
|
389
390
|
}
|
|
390
391
|
|
|
391
|
-
// ---------------------------------------------------------------- the
|
|
392
|
+
// ---------------------------------------------------------------- who serves the home
|
|
392
393
|
|
|
393
|
-
/** The
|
|
394
|
+
/** The record of the daemon serving the home, shared with `supercode orchestrator status|stop` (Rust `Lease`). */
|
|
394
395
|
const readLock = readLease;
|
|
395
396
|
|
|
396
397
|
function writeLock(root, lease) {
|
|
397
398
|
writeFileSync(join(root, LOCK_FILE), `${JSON.stringify(lease, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
398
399
|
}
|
|
399
400
|
|
|
400
|
-
/** Only ever removes
|
|
401
|
+
/** Only ever removes the record this process wrote. */
|
|
401
402
|
function clearLock(root) {
|
|
402
403
|
const held = readLock(root);
|
|
403
404
|
if (held && held.pid !== process.pid) return;
|
|
@@ -677,35 +678,26 @@ async function main() {
|
|
|
677
678
|
let crashed = false;
|
|
678
679
|
let gatewayLock = null;
|
|
679
680
|
if (!args.once) {
|
|
680
|
-
// One
|
|
681
|
-
//
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const guard = join(root, 'orchestrator.acquire');
|
|
692
|
-
mkdirSync(guard);
|
|
693
|
-
try {
|
|
694
|
-
const previous = readLock(root);
|
|
695
|
-
if (previous && leaseIsLive(previous)) {
|
|
696
|
-
say('error', { root, message: `already serving ${root} (pid ${previous.pid})` });
|
|
697
|
-
throw new Error(`already serving ${root}`);
|
|
681
|
+
// One runtime serves a home: whoever holds its claim, Hermes's own gateway lock (`<home>/gateway.lock`, an flock the
|
|
682
|
+
// kernel releases the moment its holder dies, however it died). So its cron jobs fire once, its channel adapters
|
|
683
|
+
// are held once and its bindings are driven once, and a Hermes gateway on the home refuses while this daemon
|
|
684
|
+
// serves it. A second daemon does not exit and nothing waits for a person: it waits for the claim and serves the
|
|
685
|
+
// home as soon as the first is gone (a service manager's restart, a crash, kill -9).
|
|
686
|
+
for (let said = false; ;) {
|
|
687
|
+
try { gatewayLock = await holdGatewayLock(root); break; }
|
|
688
|
+
catch (error) {
|
|
689
|
+
if (!/ is served by /.test(error.message)) { say('error', { root, message: error.message }); process.exit(1); }
|
|
690
|
+
if (!said) { say('log', { level: 'info', message: `${error.message}; this daemon serves the home when that one ends` }); said = true; }
|
|
691
|
+
await new Promise((resolve) => setTimeout(resolve, CLAIM_RETRY_MS));
|
|
698
692
|
}
|
|
699
|
-
crashed = Boolean(previous);
|
|
700
|
-
if (previous) unlinkSync(join(root, LOCK_FILE));
|
|
701
|
-
writeLock(root, { pid: process.pid, started_at: new Date().toISOString(), root, ...thisHost(), executor_url: args.executorUrl ?? null });
|
|
702
|
-
} finally {
|
|
703
|
-
rmdirSync(guard);
|
|
704
693
|
}
|
|
705
|
-
//
|
|
706
|
-
//
|
|
694
|
+
// Who serves the home now (`orchestrator.lock`), for `supercode orchestrator status` and the operator verbs' live
|
|
695
|
+
// door. A record left behind is a predecessor that never stopped cleanly: this start replays for it (§4.7).
|
|
696
|
+
const previous = readLock(root);
|
|
697
|
+
crashed = Boolean(previous && previous.pid !== process.pid);
|
|
698
|
+
if (previous) unlinkSync(join(root, LOCK_FILE));
|
|
699
|
+
writeLock(root, { pid: process.pid, started_at: new Date().toISOString(), root, ...thisHost(), executor_url: args.executorUrl ?? null });
|
|
707
700
|
try {
|
|
708
|
-
gatewayLock = await holdGatewayLock(root);
|
|
709
701
|
// the home in both forms before it is loaded: a Hermes home is adopted as it stands
|
|
710
702
|
for (const change of adoptHome(root)) say('log', { level: 'info', message: `home: ${change}` });
|
|
711
703
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.48",
|
|
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": {
|