chore: v2.8.16 — Oh My Pi login sharing: a pane's key is listed before it starts
omp's gateway re-reads its clients file only every 10 s, and an omp that starts while its key is refused fails model discovery once and hangs for good. omp-share.js now keeps four keys queued in the clients file (hashes only; the keys in <data dir>/omp-share/tokens.json, 0600): a new pane takes one already listed (AGENTHUB_OMP_KEY), a fresh one joins the queue, and a gone pane's key leaves at the next sync.
This commit is contained in:
@@ -7239,7 +7239,7 @@ exports[`screen snapshots > Settings — about 1`] = `
|
||||
<span
|
||||
class="text-status-warning/90"
|
||||
>
|
||||
UI build v2.8.15 (rebuild client to match)
|
||||
UI build v2.8.16 (rebuild client to match)
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
+10
-6
@@ -553,14 +553,18 @@ omp login # e.g. anthropic
|
||||
- A member's omp pane gets one provider `agenthub` in its
|
||||
`~/.omp/agent/models.yml` (written as the member; the rest of the file is
|
||||
kept, a file that is not a YAML mapping is left alone), pointing at the
|
||||
gateway with `apiKey: AGENTHUB_PANE_TOKEN` — the NAME of the env var holding
|
||||
its pane token — and `OMP_SKIP_SETUP=1`. It runs on `--model agenthub/<model>`:
|
||||
gateway with `apiKey: AGENTHUB_OMP_KEY` — the NAME of the env var holding
|
||||
the pane's key — and `OMP_SKIP_SETUP=1`. It runs on `--model agenthub/<model>`:
|
||||
the model picked in AgentHub, else the host's own `modelRoles.default`
|
||||
(`~/.omp/agent/config.yml`) — left to itself omp would take the provider's
|
||||
first listed model, a retired one the API answers 404 for. The clients file lists the SHA-256
|
||||
of each LIVE omp pane's token (rewritten every 15 s; the gateway re-reads it
|
||||
every 10 s), so a token stops working shortly after its pane ends, and
|
||||
turning sharing off empties it at once.
|
||||
first listed model, a retired one the API answers 404 for.
|
||||
- The gateway re-reads its clients file only every 10 s, and an omp that
|
||||
starts while its key is still refused hangs for good. So AgentHub keeps four
|
||||
keys QUEUED in the clients file (hashes only; the keys themselves in
|
||||
`<data dir>/omp-share/tokens.json`, 0600): a new pane takes one that is
|
||||
already listed, and a fresh key joins the queue. A pane's key leaves the
|
||||
file at the first sync (every 15 s) after its pane is gone, and turning
|
||||
sharing off empties both files at once.
|
||||
- A scoped client token calls models only: it cannot read a credential, the
|
||||
gateway's usage or credential health, and refresh tokens never leave the
|
||||
broker. Host panes use the host's login directly and get nothing new.
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "agenthub",
|
||||
"version": "2.8.15",
|
||||
"version": "2.8.16",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "agenthub",
|
||||
"version": "2.8.15",
|
||||
"version": "2.8.16",
|
||||
"hasInstallScript": true,
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "agenthub",
|
||||
"version": "2.8.15",
|
||||
"version": "2.8.16",
|
||||
"bin": {
|
||||
"agenthub": "bin/agenthub.js"
|
||||
},
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
* @file omp-share.test.js
|
||||
* @description Oh My Pi login sharing (server/lib/omp-share.js): a member's
|
||||
* `models.yml` gets one `agenthub` provider pointing at the loopback gateway
|
||||
* with the pane token's env var NAME as its key (the rest of the file kept, a
|
||||
* non-mapping refused); the gateway's clients file holds only LIVE omp panes'
|
||||
* token hashes; a spawn is untouched while sharing is off, for a host pane, or
|
||||
* with the key's env var NAME as its key (the rest of the file kept, a
|
||||
* non-mapping refused); a pane takes a key the clients file listed BEFORE it
|
||||
* started (the gateway re-reads it only every 10 s), and a gone pane's key
|
||||
* leaves it; a spawn is untouched while sharing is off, for a host pane, or
|
||||
* without S3; and the route refuses outside S3. No omp daemon is started.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
@@ -23,13 +24,15 @@ process.env.CLAUDE_HOME = path.join(TMP, ".claude");
|
||||
process.env.DASHBOARD_DATA_DIR = path.join(TMP, "data");
|
||||
process.env.AGENTHUB_OMP_GATEWAY_PORT = "4999";
|
||||
|
||||
const crypto = require("node:crypto");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const sha = (t) => crypto.createHash("sha256").update(t).digest("hex");
|
||||
const { createApp, startServer } = require("../index");
|
||||
const share = require("../lib/omp-share");
|
||||
const linux = require("../lib/linux-accounts");
|
||||
const runAs = require("../lib/run-as");
|
||||
const tmux = require("../lib/tmux");
|
||||
const { db } = require("../db");
|
||||
|
||||
const realS3 = linux.s3Enabled;
|
||||
const realLive = tmux.liveSessions;
|
||||
@@ -57,7 +60,7 @@ describe("mergedModels", () => {
|
||||
providers: {
|
||||
agenthub: {
|
||||
baseUrl: "http://127.0.0.1:4999",
|
||||
apiKey: "AGENTHUB_PANE_TOKEN",
|
||||
apiKey: "AGENTHUB_OMP_KEY",
|
||||
api: "anthropic-messages",
|
||||
authHeader: true,
|
||||
discovery: { type: "proxy" },
|
||||
@@ -72,7 +75,7 @@ describe("mergedModels", () => {
|
||||
const doc = yaml.load(share.mergedModels(text));
|
||||
assert.equal(doc.providers.mine.baseUrl, "http://h/v1");
|
||||
assert.deepEqual(doc.equivalence, { x: "y" });
|
||||
assert.equal(doc.providers.agenthub.apiKey, "AGENTHUB_PANE_TOKEN");
|
||||
assert.equal(doc.providers.agenthub.apiKey, "AGENTHUB_OMP_KEY");
|
||||
});
|
||||
|
||||
it("writes nothing when the provider is already there as is", () => {
|
||||
@@ -113,17 +116,18 @@ describe("forSpawn", () => {
|
||||
const member = { login: "ah-mia", home: path.join(TMP, "home-mia") };
|
||||
|
||||
it("is null while sharing is off, for a host pane, or without S3", () => {
|
||||
assert.equal(share.forSpawn(member, "agenthub-lane-1", "h"), null); // off
|
||||
assert.equal(share.forSpawn(member, "agenthub-lane-1"), null); // off
|
||||
fs.mkdirSync(path.dirname(flag), { recursive: true });
|
||||
fs.writeFileSync(flag, "");
|
||||
assert.equal(share.forSpawn(member, "agenthub-lane-1", "h"), null); // no S3
|
||||
assert.equal(share.forSpawn(member, "agenthub-lane-1"), null); // no S3
|
||||
linux.s3Enabled = () => true;
|
||||
assert.equal(share.forSpawn(null, "agenthub-lane-1", "h"), null); // the host's
|
||||
assert.equal(share.forSpawn(member, "agenthub-lane-1", null), null); // no token
|
||||
assert.equal(share.forSpawn(null, "agenthub-lane-1"), null); // the host's
|
||||
});
|
||||
|
||||
it("writes the member's models.yml AS them and lists the pane's token", () => {
|
||||
it("hands out a key the gateway already listed, and writes models.yml AS the member", () => {
|
||||
turnOn();
|
||||
share.syncClients(); // the queue, listed before any pane asks
|
||||
const listedBefore = JSON.parse(fs.readFileSync(clientsFile, "utf8"));
|
||||
const writes = [];
|
||||
const real = { read: runAs.readFileSafe, mkdir: runAs.mkdirAs, write: runAs.writeAs };
|
||||
runAs.readFileSafe = () => {
|
||||
@@ -135,17 +139,27 @@ describe("forSpawn", () => {
|
||||
runAs.writeAs = (acct, file, mode, content) => writes.push([mode, acct.login, file, content]);
|
||||
tmux.liveSessions = () => new Set();
|
||||
try {
|
||||
const s = share.forSpawn(member, "agenthub-lane-7", "a".repeat(64));
|
||||
assert.deepEqual(s.env, { OMP_SKIP_SETUP: "1" });
|
||||
const s = share.forSpawn(member, "agenthub-lane-7");
|
||||
const key = s.env.AGENTHUB_OMP_KEY;
|
||||
assert.match(key, /^ahomp_/);
|
||||
assert.equal(s.env.OMP_SKIP_SETUP, "1");
|
||||
const hash = sha(key);
|
||||
assert.ok(
|
||||
Object.values(listedBefore).some((c) => c.tokenSha256 === hash),
|
||||
"the key was listed before the pane started"
|
||||
);
|
||||
const after = JSON.parse(fs.readFileSync(clientsFile, "utf8"));
|
||||
assert.deepEqual(after["agenthub-lane-7"], { tokenSha256: hash });
|
||||
// its place in the queue is filled again, and no plaintext reaches the clients file
|
||||
assert.equal(Object.keys(after).filter((k) => k.startsWith("standby-")).length, 4);
|
||||
assert.ok(!fs.readFileSync(clientsFile, "utf8").includes("ahomp_"));
|
||||
assert.equal((fs.statSync(clientsFile).mode & 0o777).toString(8), "600");
|
||||
s.place();
|
||||
const agentDir = path.join(member.home, ".omp", "agent");
|
||||
assert.deepEqual(writes[0], ["mkdir", "ah-mia", agentDir]);
|
||||
assert.equal(writes[1][0], "replace");
|
||||
assert.equal(writes[1][2], path.join(agentDir, "models.yml"));
|
||||
assert.equal(yaml.load(writes[1][3]).providers.agenthub.apiKey, "AGENTHUB_PANE_TOKEN");
|
||||
const clients = JSON.parse(fs.readFileSync(clientsFile, "utf8"));
|
||||
assert.deepEqual(clients["agenthub-lane-7"], { tokenSha256: "a".repeat(64) });
|
||||
assert.equal((fs.statSync(clientsFile).mode & 0o777).toString(8), "600");
|
||||
assert.equal(yaml.load(writes[1][3]).providers.agenthub.apiKey, "AGENTHUB_OMP_KEY");
|
||||
} finally {
|
||||
runAs.readFileSafe = real.read;
|
||||
runAs.mkdirAs = real.mkdir;
|
||||
@@ -154,29 +168,26 @@ describe("forSpawn", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("liveClients", () => {
|
||||
before(() => {
|
||||
const ins = db.prepare(
|
||||
`INSERT INTO dashboard_runs (id, cwd, status, tool, pane_token_hash) VALUES (?, '/x', ?, ?, ?)`
|
||||
);
|
||||
ins.run("agenthub-lane-21", "running", "omp", "b".repeat(64));
|
||||
ins.run("agenthub-lane-22", "running", "omp", "c".repeat(64)); // pane gone
|
||||
ins.run("agenthub-lane-23", "ended", "omp", "d".repeat(64));
|
||||
ins.run("agenthub-lane-24", "running", "claude", "e".repeat(64));
|
||||
describe("pruneTokens", () => {
|
||||
const now = 1_000_000;
|
||||
const tokens = () => ({
|
||||
standby: [],
|
||||
used: {
|
||||
"agenthub-lane-21": { hash: "b", at: now - 120_000 }, // live
|
||||
"agenthub-lane-22": { hash: "c", at: now - 120_000 }, // pane gone
|
||||
"agenthub-lane-23": { hash: "d", at: now - 5_000 }, // just started
|
||||
},
|
||||
});
|
||||
|
||||
it("lists only live omp panes", () => {
|
||||
tmux.liveSessions = () => new Set(["agenthub-lane-21", "agenthub-lane-23", "agenthub-lane-24"]);
|
||||
const c = share.liveClients();
|
||||
assert.deepEqual(c["agenthub-lane-21"], { tokenSha256: "b".repeat(64) });
|
||||
for (const id of ["agenthub-lane-22", "agenthub-lane-23", "agenthub-lane-24"]) {
|
||||
assert.equal(c[id], undefined, id);
|
||||
}
|
||||
it("drops only a gone pane's key past the grace, and keeps the queue full", () => {
|
||||
const t = share.pruneTokens(tokens(), new Set(["agenthub-lane-21"]), now);
|
||||
assert.deepEqual(Object.keys(t.used).sort(), ["agenthub-lane-21", "agenthub-lane-23"]);
|
||||
assert.equal(t.standby.length, 4);
|
||||
});
|
||||
|
||||
it("says nothing when tmux could not be asked", () => {
|
||||
tmux.liveSessions = () => null;
|
||||
assert.equal(share.liveClients(), null);
|
||||
it("drops nothing when tmux could not be asked", () => {
|
||||
const t = share.pruneTokens(tokens(), null, now);
|
||||
assert.equal(Object.keys(t.used).length, 3);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+110
-49
@@ -14,13 +14,14 @@
|
||||
* `/v1/messages`, `/v1/chat/completions`, `/v1/responses`, `/v1/models`
|
||||
* with the broker's credentials put in upstream. Its own unscoped token
|
||||
* stays in the host's home too.
|
||||
* A member's omp pane reaches the gateway as a SCOPED client: the clients file
|
||||
* lists the SHA-256 of each live omp pane's own pane token
|
||||
* (`dashboard_runs.pane_token_hash`, the same hash `auth.js` keeps), and its
|
||||
* `~/.omp/agent/models.yml` gets one `agenthub` provider whose `apiKey` is the
|
||||
* env var NAME `AGENTHUB_PANE_TOKEN`. A scoped client can call models but
|
||||
* never read a credential or the gateway's usage, and its token stops working
|
||||
* within the gateway's 10 s re-read once its pane is gone (`syncClients`) — so
|
||||
* A member's omp pane reaches the gateway as a SCOPED client: it is handed one
|
||||
* key (`AGENTHUB_OMP_KEY`, through tmux -e / its launch file) that the
|
||||
* clients file had already listed — by SHA-256 only — before the pane started
|
||||
* (the gateway re-reads that file only every 10 s; see `takeToken`), and its
|
||||
* `~/.omp/agent/models.yml` gets one `agenthub` provider whose `apiKey` is
|
||||
* that env var's NAME. A scoped client can call models but never read a
|
||||
* credential or the gateway's usage, and its key stops working within the
|
||||
* next sync + the gateway's re-read once its pane is gone (`syncClients`) — so
|
||||
* an agent can spend through the login while its pane lives, never carry it
|
||||
* away. omp's gateway serves only its built-in providers' models (a custom
|
||||
* `models.yml` provider of the host, such as a 9Router, is not shared).
|
||||
@@ -30,6 +31,7 @@
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const crypto = require("node:crypto");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
@@ -37,10 +39,8 @@ const net = require("node:net");
|
||||
const { spawn } = require("node:child_process");
|
||||
|
||||
const PROVIDER = "agenthub";
|
||||
const TOKEN_ENV = "AGENTHUB_PANE_TOKEN";
|
||||
const TOKEN_ENV = "AGENTHUB_OMP_KEY";
|
||||
const SYNC_MS = 15_000;
|
||||
/** A spawn's token counts before its run row exists (recordRun runs after). */
|
||||
const PENDING_MS = 60_000;
|
||||
|
||||
const port = (name, fallback) => {
|
||||
const n = parseInt(process.env[name] || "", 10);
|
||||
@@ -53,6 +53,7 @@ const dir = () => path.join(require("./claude-home").getDataDir(), "omp-share");
|
||||
const onFlag = () => path.join(dir(), "enabled");
|
||||
const clientsFile = () => path.join(dir(), "clients.json");
|
||||
const pidsFile = () => path.join(dir(), "pids.json");
|
||||
const tokensFile = () => path.join(dir(), "tokens.json");
|
||||
const logFile = (name) => path.join(dir(), `${name}.log`);
|
||||
/** The host's own omp config dir, where the broker keeps its token. */
|
||||
const ompDir = () => process.env.AGENTHUB_OMP_DIR || path.join(os.homedir(), ".omp");
|
||||
@@ -67,52 +68,110 @@ function ensureDir() {
|
||||
fs.mkdirSync(dir(), { recursive: true, mode: 0o700 });
|
||||
}
|
||||
|
||||
// ── the clients file ──────────────────────────────────────────────────────
|
||||
// ── tokens and the clients file ───────────────────────────────────────────
|
||||
//
|
||||
// The gateway re-reads its clients file only every 10 s, and an omp that
|
||||
// starts while its key is still refused hangs for good (its model discovery
|
||||
// fails once and is never retried). So a pane never gets a key minted for
|
||||
// it: it takes one of STANDBY keys that have been listed for a while, and a
|
||||
// fresh one takes that key's place in the queue. `tokens.json` (0600, the
|
||||
// host's data dir) keeps the queue and which run holds which key, so both
|
||||
// survive a restart; a key whose pane is gone leaves the file at the next sync.
|
||||
|
||||
const pending = new Map(); // run id → { hash, at }
|
||||
let lastClients = null;
|
||||
const STANDBY = 4;
|
||||
/** A run's key stays listed this long before its pane must show up in tmux. */
|
||||
const GRACE_MS = 60_000;
|
||||
|
||||
/** `{ <run id>: { tokenSha256 } }` for every live omp pane, or null when tmux could not say. */
|
||||
function liveClients() {
|
||||
const live = require("./tmux").liveSessions();
|
||||
if (!live) return null;
|
||||
const rows = require("../db")
|
||||
.db.prepare(
|
||||
`SELECT id, pane_token_hash FROM dashboard_runs
|
||||
WHERE tool = 'omp' AND status IN ('running', 'spawning') AND pane_token_hash IS NOT NULL`
|
||||
)
|
||||
.all();
|
||||
const sha = (t) => crypto.createHash("sha256").update(t).digest("hex");
|
||||
const newToken = () => `ahomp_${crypto.randomBytes(24).toString("base64url")}`;
|
||||
|
||||
function readTokens() {
|
||||
try {
|
||||
const j = JSON.parse(fs.readFileSync(tokensFile(), "utf8"));
|
||||
return {
|
||||
standby: Array.isArray(j.standby) ? j.standby.filter((t) => typeof t === "string") : [],
|
||||
used: j.used && typeof j.used === "object" ? j.used : {},
|
||||
};
|
||||
} catch {
|
||||
return { standby: [], used: {} };
|
||||
}
|
||||
}
|
||||
|
||||
function writeAtomic(file, text) {
|
||||
ensureDir();
|
||||
const tmp = `${file}.${process.pid}.tmp`;
|
||||
fs.writeFileSync(tmp, text, { mode: 0o600 });
|
||||
fs.renameSync(tmp, file);
|
||||
}
|
||||
|
||||
function refill(t) {
|
||||
while (t.standby.length < STANDBY) t.standby.push(newToken());
|
||||
}
|
||||
|
||||
/** The gateway's view: every queued key and every key a run holds — hashes only. */
|
||||
function clientsFor(t) {
|
||||
const out = {};
|
||||
for (const r of rows) if (live.has(r.id)) out[r.id] = { tokenSha256: r.pane_token_hash };
|
||||
const now = Date.now();
|
||||
for (const [id, p] of pending) {
|
||||
if (now - p.at > PENDING_MS) pending.delete(id);
|
||||
else out[id] = { tokenSha256: p.hash };
|
||||
for (const s of t.standby) out[`standby-${sha(s).slice(0, 16)}`] = { tokenSha256: sha(s) };
|
||||
for (const [id, u] of Object.entries(t.used)) {
|
||||
if (u && typeof u.hash === "string") out[id] = { tokenSha256: u.hash };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function writeClients(clients) {
|
||||
const text = `${JSON.stringify(clients, null, 2)}\n`;
|
||||
if (text === lastClients) return;
|
||||
ensureDir();
|
||||
const tmp = `${clientsFile()}.${process.pid}.tmp`;
|
||||
fs.writeFileSync(tmp, text, { mode: 0o600 });
|
||||
fs.renameSync(tmp, clientsFile());
|
||||
let lastClients = null;
|
||||
|
||||
function save(t) {
|
||||
writeAtomic(tokensFile(), `${JSON.stringify(t)}\n`);
|
||||
const text = `${JSON.stringify(clientsFor(t), null, 2)}\n`;
|
||||
if (text === lastClients && fs.existsSync(clientsFile())) return;
|
||||
writeAtomic(clientsFile(), text);
|
||||
lastClients = text;
|
||||
}
|
||||
|
||||
/** Rewrite the clients file from the live omp panes (a gone pane's token drops out). */
|
||||
/** A key for run `runId`, already listed with the gateway; its place in the queue is refilled. */
|
||||
function takeToken(runId) {
|
||||
const t = readTokens();
|
||||
refill(t);
|
||||
const token = t.standby.shift();
|
||||
t.used[runId] = { hash: sha(token), at: Date.now() };
|
||||
refill(t);
|
||||
save(t);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the keys of runs whose pane is gone (after GRACE_MS), keep the queue
|
||||
* full, and rewrite both files. `live`: the tmux session names, or null when
|
||||
* tmux could not say — then nothing is dropped.
|
||||
*/
|
||||
function pruneTokens(t, live, now = Date.now()) {
|
||||
if (live) {
|
||||
for (const [id, u] of Object.entries(t.used)) {
|
||||
if (!live.has(id) && now - (Number(u?.at) || 0) > GRACE_MS) delete t.used[id];
|
||||
}
|
||||
}
|
||||
refill(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
/** Rewrite the clients file from the live omp panes (a gone pane's key drops out). */
|
||||
function syncClients() {
|
||||
if (!enabled() || !available()) return;
|
||||
try {
|
||||
const clients = liveClients();
|
||||
if (clients) writeClients(clients);
|
||||
save(pruneTokens(readTokens(), require("./tmux").liveSessions()));
|
||||
} catch (err) {
|
||||
console.warn("omp share: clients sync failed:", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sharing turned off: no key stays listed, queued or held. */
|
||||
function revokeAll() {
|
||||
lastClients = null;
|
||||
if (fs.existsSync(tokensFile()) || fs.existsSync(clientsFile())) {
|
||||
save({ standby: [], used: {} });
|
||||
}
|
||||
}
|
||||
|
||||
// ── a member's models.yml ─────────────────────────────────────────────────
|
||||
|
||||
function providerEntry() {
|
||||
@@ -200,10 +259,12 @@ function shareModel(requested, hostDefault = hostDefaultModel()) {
|
||||
* null when sharing is off (the pane then runs on the member's own login).
|
||||
* Only a MEMBER's pane — the host's omp already holds the login itself.
|
||||
*/
|
||||
function forSpawn(member, runId, tokenHash) {
|
||||
if (!member || !tokenHash || !enabled() || !available()) return null;
|
||||
function forSpawn(member, runId) {
|
||||
if (!member || !enabled() || !available()) return null;
|
||||
const token = takeToken(runId);
|
||||
return {
|
||||
env: { OMP_SKIP_SETUP: "1" },
|
||||
// The key itself, never in argv: tmux -e, or a member's launch file.
|
||||
env: { OMP_SKIP_SETUP: "1", [TOKEN_ENV]: token },
|
||||
model: (requested) => shareModel(requested),
|
||||
place() {
|
||||
try {
|
||||
@@ -211,8 +272,6 @@ function forSpawn(member, runId, tokenHash) {
|
||||
} catch (err) {
|
||||
console.warn(`omp share: ${member.login}'s models.yml left as is:`, err.message);
|
||||
}
|
||||
pending.set(runId, { hash: tokenHash, at: Date.now() });
|
||||
syncClients();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -328,8 +387,9 @@ async function startDaemons() {
|
||||
return;
|
||||
}
|
||||
killStale();
|
||||
// The gateway refuses to start without the clients file it names.
|
||||
if (!fs.existsSync(clientsFile())) writeClients({});
|
||||
// The gateway refuses to start without the clients file it names, and
|
||||
// must list the queued keys from its first read.
|
||||
save(pruneTokens(readTokens(), null));
|
||||
const bport = brokerPort();
|
||||
const gport = gatewayPort();
|
||||
for (const p of [bport, gport]) {
|
||||
@@ -422,9 +482,7 @@ async function setEnabled(on) {
|
||||
fs.rmSync(onFlag(), { force: true });
|
||||
stop();
|
||||
// No pane keeps a way in once sharing is off.
|
||||
lastClients = null;
|
||||
pending.clear();
|
||||
if (fs.existsSync(clientsFile())) writeClients({});
|
||||
revokeAll();
|
||||
}
|
||||
return state();
|
||||
}
|
||||
@@ -458,7 +516,10 @@ module.exports = {
|
||||
mergedModels,
|
||||
providerEntry,
|
||||
shareModel,
|
||||
liveClients,
|
||||
takeToken,
|
||||
pruneTokens,
|
||||
clientsFor,
|
||||
readTokens,
|
||||
PROVIDER,
|
||||
TOKEN_ENV,
|
||||
};
|
||||
|
||||
@@ -490,13 +490,12 @@ function spawnRun(args) {
|
||||
: null;
|
||||
const paneCred = require("./auth").newPaneToken();
|
||||
// A member's omp on the host's shared omp login (omp-share.js): its
|
||||
// models.yml points at the loopback gateway, keyed by this pane's token.
|
||||
// models.yml points at the loopback gateway, with a key already listed there.
|
||||
const ompShare =
|
||||
tool === "omp"
|
||||
? require("./omp-share").forSpawn(
|
||||
require("./run-as").accountForLane(laneId),
|
||||
runIdForLane(laneId, slot),
|
||||
paneCred?.hash
|
||||
runIdForLane(laneId, slot)
|
||||
)
|
||||
: null;
|
||||
if (forkSession === true) {
|
||||
|
||||
Reference in New Issue
Block a user