cotal-ai 0.94.5 → 0.94.7
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/dist/daemon/client.js +17 -0
- package/dist/daemon/command.js +53 -9
- package/dist/daemon/connect.js +103 -0
- package/dist/daemon/protocol.js +9 -1
- package/dist/daemon/server.js +12 -2
- package/package.json +18 -18
- package/seeded-connectors/claude/dist/mcp.cjs +7 -7
- package/seeded-connectors/claude/package.json +1 -1
- package/seeded-connectors/codex/dist/host.js +7 -7
- package/seeded-connectors/codex/package.json +1 -1
- package/seeded-connectors/hermes/dist/launch.js +7 -7
- package/seeded-connectors/hermes/package.json +1 -1
- package/seeded-connectors/hermes/plugin/cotal/_sidecar/standalone.cjs +7 -7
- package/seeded-connectors/jcode/dist/host.js +7 -7
- package/seeded-connectors/jcode/dist/mcp.js +7 -7
- package/seeded-connectors/jcode/package.json +1 -1
- package/seeded-connectors/opencode/dist/plugin/index.js +7 -7
- package/seeded-connectors/opencode/package.json +1 -1
- package/seeded-connectors/pi/dist/index.js +4 -4
- package/seeded-connectors/pi/dist/standalone.js +7 -7
- package/seeded-connectors/pi/package.json +1 -1
- package/seeded-connectors/web/package.json +1 -1
package/dist/daemon/client.js
CHANGED
|
@@ -10,6 +10,23 @@ import { connect as netConnect } from "node:net";
|
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { DAEMON_PROTOCOL_VERSION, MAX_LINE_BYTES, EndpointRefusal, assertLinux, checkDir, checkSocket } from "./endpoint.js";
|
|
12
12
|
import { encode } from "./protocol.js";
|
|
13
|
+
/** Ask the daemon whether it could register with `space`. Nothing is registered either way. */
|
|
14
|
+
export async function connectCheckViaDaemon(opts) {
|
|
15
|
+
const sock = await openVerified({ ...opts, timeoutMs: 5000 });
|
|
16
|
+
try {
|
|
17
|
+
sock.write(encode({ v: DAEMON_PROTOCOL_VERSION, id: randomUUID().replaceAll("-", ""), op: "connect-check", space: opts.space }));
|
|
18
|
+
// The daemon's own network read is bounded by the stock client (30 s); allow a little more.
|
|
19
|
+
const reply = JSON.parse(await readLine(sock, 35_000));
|
|
20
|
+
if (!reply.ok)
|
|
21
|
+
return { ok: false, code: reply.error.code, message: reply.error.message };
|
|
22
|
+
if ("event" in reply || !("outcome" in reply.result) || !("space" in reply.result))
|
|
23
|
+
return { ok: false, code: "BAD_REPLY", message: "the daemon answered the connect check with something else" };
|
|
24
|
+
return { ok: true, result: reply.result };
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
sock.destroy();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
13
30
|
function connectTo(path, timeoutMs) {
|
|
14
31
|
return new Promise((resolve, reject) => {
|
|
15
32
|
const s = netConnect(path);
|
package/dist/daemon/command.js
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* in an `implementations/*` package so it can later wire `@cotal-ai/auth` and `@cotal-ai/manager`
|
|
4
4
|
* without one implementation importing another (AGENTS.md, repository map).
|
|
5
5
|
*
|
|
6
|
-
* This slice serves a local, authenticated endpoint for status
|
|
7
|
-
* Manager, no mesh or Cloud connection, no service install. Linux
|
|
6
|
+
* This slice serves a local, authenticated endpoint for status, sign-in and a connection check, and
|
|
7
|
+
* nothing else: no registration, no Manager, no mesh or Cloud connection, no service install. Linux
|
|
8
|
+
* only. Sign-in runs inside the
|
|
8
9
|
* daemon through stock `establishIdpSession`, so the session lands in the machine-home Cotal
|
|
9
|
-
* directory where `cotal login` keeps it, and no token ever crosses the local endpoint.
|
|
10
|
+
* directory where `cotal login` keeps it, and no token ever crosses the local endpoint. The
|
|
11
|
+
* connection check (`connect.ts`) reads the space's public account identity and keeps the daemon's
|
|
12
|
+
* own per-space identity, never a Manager's. It is a
|
|
10
13
|
* foreground foundation, not a resilient machine service: a daemon killed without cleanup leaves
|
|
11
14
|
* its socket behind, and the next start refuses until the operator removes it (see the
|
|
12
15
|
* ALREADY_RUNNING remedy). `state: "ready"` means the local control listener is bound and
|
|
@@ -16,17 +19,18 @@ import { readFileSync } from "node:fs";
|
|
|
16
19
|
import { dirname, join } from "node:path";
|
|
17
20
|
import { fileURLToPath } from "node:url";
|
|
18
21
|
import { registry } from "@cotal-ai/core";
|
|
19
|
-
import { CommandFailure, jsonFlag, plainFlag, presentationOf, renderReport, terminalWidth, writeResult } from "@cotal-ai/workspace";
|
|
22
|
+
import { CommandFailure, jsonFlag, plainFlag, presentationOf, renderReport, spaceFlag, terminalWidth, writeResult } from "@cotal-ai/workspace";
|
|
20
23
|
import { peerCredentials } from "@cotal-ai/seat";
|
|
21
24
|
import { normalizeIdpUrl } from "@cotal-ai/auth";
|
|
22
25
|
import { EndpointRefusal, daemonPaths } from "./endpoint.js";
|
|
23
26
|
import { serveDaemon } from "./server.js";
|
|
24
|
-
import { loginViaDaemon, queryDaemon } from "./client.js";
|
|
27
|
+
import { connectCheckViaDaemon, loginViaDaemon, queryDaemon } from "./client.js";
|
|
25
28
|
export const daemonFlags = [
|
|
26
29
|
jsonFlag,
|
|
27
30
|
plainFlag,
|
|
28
31
|
{ name: "idp", type: "string", value: "<auth base URL>", description: "login: the provider's auth base URL, e.g. <origin>/api/auth" },
|
|
29
32
|
{ name: "client-id", type: "string", value: "<id>", description: "login: the OAuth device-flow client id" },
|
|
33
|
+
spaceFlag,
|
|
30
34
|
];
|
|
31
35
|
/** This binary's own version: the nearest package.json above this module (bin/ in a source run). */
|
|
32
36
|
export function binVersion() {
|
|
@@ -146,6 +150,43 @@ async function login(args) {
|
|
|
146
150
|
{ title: "Signed in", blocks: [{ kind: "fields", rows: [["Account", r.label || r.sub], ["Subject", r.sub], ["Provider", r.idp]] }, { kind: "lines", lines: [{ text: "The daemon holds this session in the machine's Cotal home. No session token is printed. Each space's grants decide access separately.", tone: "hint" }] }] },
|
|
147
151
|
] }));
|
|
148
152
|
}
|
|
153
|
+
/** Ask the running daemon whether it could register with a space. The daemon reads the space's
|
|
154
|
+
* public account identity and nothing else; no registration, credential or authority results. */
|
|
155
|
+
async function connectCheckCommand(args) {
|
|
156
|
+
const presentation = presentationOf(args.values);
|
|
157
|
+
const space = args.values.space;
|
|
158
|
+
if (typeof space !== "string" || !space)
|
|
159
|
+
throw new CommandFailure("refused", "USAGE_INVALID", "cotal daemon connect-check needs --space <s>");
|
|
160
|
+
let reply;
|
|
161
|
+
try {
|
|
162
|
+
reply = await connectCheckViaDaemon({ paths: daemonPaths(), peerUid, space });
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
throw asFailure(e);
|
|
166
|
+
}
|
|
167
|
+
if (!reply.ok)
|
|
168
|
+
throw new CommandFailure("refused", "USAGE_INVALID", `the daemon refused the connect check: ${reply.code} ${reply.message}`, { native: { code: reply.code } });
|
|
169
|
+
const r = reply.result;
|
|
170
|
+
const native = { native: { code: r.outcome.toUpperCase().replaceAll("-", "_") } };
|
|
171
|
+
if (r.outcome === "not-registered")
|
|
172
|
+
throw new CommandFailure("refused", "TARGET_NOT_FOUND", r.message, native);
|
|
173
|
+
if (r.outcome === "not-user-auth" || r.outcome === "unsupported-by-space")
|
|
174
|
+
throw new CommandFailure("refused", "UNSUPPORTED", r.message, native);
|
|
175
|
+
if (r.outcome === "not-signed-in")
|
|
176
|
+
throw new CommandFailure("refused", "AUTH_REQUIRED", r.message, native);
|
|
177
|
+
if (r.outcome === "unreachable")
|
|
178
|
+
throw new CommandFailure("refused", "DEPENDENCY_UNAVAILABLE", r.message, native);
|
|
179
|
+
if (r.outcome === "account-mismatch")
|
|
180
|
+
throw new CommandFailure("refused", "CONFLICT", r.message, native);
|
|
181
|
+
if (r.outcome !== "ready-to-register")
|
|
182
|
+
throw new CommandFailure("refused", "DEPENDENCY_UNAVAILABLE", r.message, native);
|
|
183
|
+
writeResult(presentation, { command: "daemon connect-check", target: { space }, result: r }, () => ({ sections: [
|
|
184
|
+
{ title: "Ready to register", blocks: [
|
|
185
|
+
{ kind: "fields", rows: [["Space", r.space], ["Service", r.endpoint], ["Instance", r.instanceId], ["Account", r.accountPublicKey]] },
|
|
186
|
+
{ kind: "lines", lines: [{ text: "Nothing was registered. The space owner must approve this daemon before it can connect.", tone: "hint" }] },
|
|
187
|
+
] },
|
|
188
|
+
] }));
|
|
189
|
+
}
|
|
149
190
|
async function serve() {
|
|
150
191
|
let running;
|
|
151
192
|
try {
|
|
@@ -171,22 +212,25 @@ async function run(args) {
|
|
|
171
212
|
return serve();
|
|
172
213
|
if (mode === "login")
|
|
173
214
|
return login(args);
|
|
174
|
-
|
|
215
|
+
if (mode === "connect-check")
|
|
216
|
+
return connectCheckCommand(args);
|
|
217
|
+
throw new CommandFailure("refused", "USAGE_INVALID", mode === undefined ? "cotal daemon needs a mode: status, serve, login or connect-check" : `unknown daemon mode ${JSON.stringify(mode)}`, { remedy: "cotal daemon status" });
|
|
175
218
|
}
|
|
176
219
|
const daemonCommand = {
|
|
177
220
|
kind: "command",
|
|
178
221
|
name: "daemon",
|
|
179
222
|
group: "Manager",
|
|
180
|
-
summary: "the local machine daemon (Linux): serve its control endpoint, read its status, sign it in",
|
|
181
|
-
positionals: "<status|serve|login>",
|
|
223
|
+
summary: "the local machine daemon (Linux): serve its control endpoint, read its status, sign it in, check whether it can connect to a space",
|
|
224
|
+
positionals: "<status|serve|login|connect-check>",
|
|
182
225
|
flags: daemonFlags,
|
|
183
226
|
subcommands: [
|
|
184
227
|
{ name: "status", summary: "ask the running daemon for its status over the local endpoint", flagNames: ["json", "plain"], outputs: ["json", "plain"], reads: true, prepareMeshTarget: false },
|
|
185
228
|
{ name: "serve", summary: "run the daemon in the foreground and serve its local endpoint (refusals honor --json)", flagNames: ["json", "plain"], outputs: ["json", "plain"], prepareMeshTarget: false },
|
|
186
229
|
{ name: "login", summary: "sign the running daemon in to a provider with device approval; the daemon keeps the session", flagNames: ["idp", "client-id", "json", "plain"], outputs: ["json", "plain"], prepareMeshTarget: false },
|
|
230
|
+
{ name: "connect-check", summary: "ask the running daemon whether it could register with a space as its own service; registers nothing", flagNames: ["space", "json", "plain"], outputs: ["json", "plain"], reads: true, prepareMeshTarget: false },
|
|
187
231
|
],
|
|
188
232
|
prepareMeshTarget: false,
|
|
189
|
-
examples: ["cotal daemon serve", "cotal daemon status", "cotal daemon status --json", "cotal daemon login --idp https://example.org/api/auth"],
|
|
233
|
+
examples: ["cotal daemon serve", "cotal daemon status", "cotal daemon status --json", "cotal daemon login --idp https://example.org/api/auth", "cotal daemon connect-check --space acme"],
|
|
190
234
|
run,
|
|
191
235
|
};
|
|
192
236
|
registry.register(daemonCommand);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The daemon's connection readiness for one space (A2b.1). It answers whether this machine's daemon
|
|
3
|
+
* could register with the space as its own service, and if not, exactly why. It never registers,
|
|
4
|
+
* holds no bearer and starts nothing.
|
|
5
|
+
*
|
|
6
|
+
* Every input is stock: the mesh registry entry (`findMesh`), the machine's stock IdP session
|
|
7
|
+
* (`loadIdpSession`), and the registered auth provider's `serviceEndpointInfo`, which reads the
|
|
8
|
+
* space's stock service authority route (GET only, no redirects, closed parser). A 404 there means the
|
|
9
|
+
* space does not host service endpoints (stock `serviceEndpoints` is off by default); the daemon
|
|
10
|
+
* reports that, and never falls back to a Manager view, `manager.vitals` or static credentials.
|
|
11
|
+
*
|
|
12
|
+
* The daemon's identity is its own, separate from the stock Manager identity: one record per
|
|
13
|
+
* (machine home, space) under `<COTAL_HOME>/daemon/<space segment>/service-identity.json`, created
|
|
14
|
+
* once through stock `claimIdentityRecord` (exclusive create, one winner). It pins the space's
|
|
15
|
+
* DATA account on first sight; a later different account is a mismatch, never a silent repin. It
|
|
16
|
+
* holds no credential, owner, hostname or hardware id.
|
|
17
|
+
*/
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { assertLifecycleToken, isAccountNkey, mintLifecycleUid, resolveAuthProvider } from "@cotal-ai/core";
|
|
20
|
+
import { claimIdentityRecord, findMesh, homeCotalDir, spaceSegment, userAuthStateDir, workspaceSecretStore } from "@cotal-ai/workspace";
|
|
21
|
+
import { loadIdpSession } from "@cotal-ai/auth";
|
|
22
|
+
/** The service name the daemon proposes for itself. Single-label names are reserved for reference
|
|
23
|
+
* endpoints and a third-party name must be reverse-DNS, so this is the reverse-DNS of cotal.ai.
|
|
24
|
+
* Binding it still needs the space operator's `name-bind`; the daemon never claims it. */
|
|
25
|
+
export const DAEMON_ENDPOINT = "ai.cotal.machine";
|
|
26
|
+
const KIND = "cotal-daemon-service-identity";
|
|
27
|
+
/** Where this machine's daemon keeps its identity for `space`. */
|
|
28
|
+
export function daemonIdentityFile(space, home = homeCotalDir()) {
|
|
29
|
+
return join(home, "daemon", spaceSegment(space), "service-identity.json");
|
|
30
|
+
}
|
|
31
|
+
function parseIdentity(space) {
|
|
32
|
+
return (raw) => {
|
|
33
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
34
|
+
return undefined;
|
|
35
|
+
const o = raw;
|
|
36
|
+
const keys = Object.keys(o).sort().join(",");
|
|
37
|
+
if (keys !== "accountPublicKey,endpoint,instanceId,kind,space,v")
|
|
38
|
+
return undefined;
|
|
39
|
+
if (o.v !== 1 || o.kind !== KIND || o.space !== space || o.endpoint !== DAEMON_ENDPOINT || !isAccountNkey(o.accountPublicKey))
|
|
40
|
+
return undefined;
|
|
41
|
+
if (typeof o.instanceId !== "string")
|
|
42
|
+
return undefined;
|
|
43
|
+
try {
|
|
44
|
+
assertLifecycleToken(o.instanceId, "instanceId");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return o;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The fixed sentence per failure. Nothing from the remote side is quoted. */
|
|
53
|
+
const SENTENCE = {
|
|
54
|
+
"not-registered": "this space is not registered on this machine; add it with `cotal meshes add`",
|
|
55
|
+
"not-user-auth": "this space does not use user sign-in, so the daemon cannot connect to it as its own service",
|
|
56
|
+
"not-signed-in": "this machine is not signed in to the space's provider; run `cotal daemon login --idp <url>`",
|
|
57
|
+
"unsupported-by-space": "the space's auth service does not host service endpoints, so the daemon cannot register with it",
|
|
58
|
+
unreachable: "the space's auth service could not be reached",
|
|
59
|
+
refused: "the space's auth service gave an answer the daemon cannot use",
|
|
60
|
+
};
|
|
61
|
+
/** Check readiness for `space`. `home` is the machine home where the stock session and the daemon's
|
|
62
|
+
* own state live. */
|
|
63
|
+
export async function connectCheck(space, home = homeCotalDir()) {
|
|
64
|
+
const entry = findMesh(space);
|
|
65
|
+
if (!entry)
|
|
66
|
+
return { outcome: "not-registered", space, message: SENTENCE["not-registered"] };
|
|
67
|
+
const ua = entry.mode === "user" ? entry.userAuth : undefined;
|
|
68
|
+
if (!ua)
|
|
69
|
+
return { outcome: "not-user-auth", space, message: SENTENCE["not-user-auth"] };
|
|
70
|
+
let session;
|
|
71
|
+
try {
|
|
72
|
+
session = loadIdpSession(home, ua.idp.url);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
session = undefined;
|
|
76
|
+
}
|
|
77
|
+
if (!session)
|
|
78
|
+
return { outcome: "not-signed-in", space, message: SENTENCE["not-signed-in"] };
|
|
79
|
+
let accountPublicKey;
|
|
80
|
+
try {
|
|
81
|
+
const provider = resolveAuthProvider();
|
|
82
|
+
if (!provider.serviceEndpointInfo)
|
|
83
|
+
return { outcome: "unsupported-by-space", space, message: SENTENCE["unsupported-by-space"] };
|
|
84
|
+
const info = await provider.serviceEndpointInfo({ store: workspaceSecretStore(entry.root), dir: userAuthStateDir(entry.root, space, { readOnly: true }), space });
|
|
85
|
+
accountPublicKey = info.accountPublicKey;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
const status = e.httpStatus;
|
|
89
|
+
if (status === 404)
|
|
90
|
+
return { outcome: "unsupported-by-space", space, message: SENTENCE["unsupported-by-space"] };
|
|
91
|
+
if (status === undefined && e.outcome === "not-executed" && /did not return a response/.test(e.message))
|
|
92
|
+
return { outcome: "unreachable", space, message: SENTENCE.unreachable };
|
|
93
|
+
return { outcome: "refused", space, message: SENTENCE.refused };
|
|
94
|
+
}
|
|
95
|
+
const identity = claimIdentityRecord(daemonIdentityFile(space, home), "daemon service identity", parseIdentity(space), () => ({
|
|
96
|
+
v: 1, kind: KIND, space, endpoint: DAEMON_ENDPOINT, instanceId: mintLifecycleUid(), accountPublicKey,
|
|
97
|
+
}));
|
|
98
|
+
if (identity.accountPublicKey !== accountPublicKey)
|
|
99
|
+
return { outcome: "account-mismatch", space, pinnedAccountPublicKey: identity.accountPublicKey, observedAccountPublicKey: accountPublicKey,
|
|
100
|
+
message: "the space now answers with a different account than this daemon pinned; nothing was changed. Confirm the space was not replaced before removing the daemon's identity for it by hand" };
|
|
101
|
+
return { outcome: "ready-to-register", space, endpoint: DAEMON_ENDPOINT, instanceId: identity.instanceId, accountPublicKey,
|
|
102
|
+
message: "this daemon can ask the space to register it; the space owner must still approve it, and nothing has been registered" };
|
|
103
|
+
}
|
package/dist/daemon/protocol.js
CHANGED
|
@@ -13,18 +13,21 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { normalizeIdpUrl } from "@cotal-ai/auth";
|
|
15
15
|
import { DAEMON_PROTOCOL_VERSION, MAX_LINE_BYTES } from "./endpoint.js";
|
|
16
|
-
export const DAEMON_OPS = ["status", "login-start", "login-cancel"];
|
|
16
|
+
export const DAEMON_OPS = ["status", "login-start", "login-cancel", "connect-check"];
|
|
17
17
|
/** The fields each op takes besides `v`, `id` and `op`. Required ones must be present. */
|
|
18
18
|
const OP_FIELDS = {
|
|
19
19
|
status: { required: [], optional: [] },
|
|
20
20
|
"login-start": { required: ["idp"], optional: ["clientId"] },
|
|
21
21
|
"login-cancel": { required: ["attempt"], optional: [] },
|
|
22
|
+
"connect-check": { required: ["space"], optional: [] },
|
|
22
23
|
};
|
|
23
24
|
const ENVELOPE_KEYS = new Set(["v", "id", "op"]);
|
|
24
25
|
const ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
25
26
|
/** An attempt id is daemon-minted: 22 characters of base64url. */
|
|
26
27
|
export const ATTEMPT_PATTERN = /^[A-Za-z0-9_-]{22}$/;
|
|
27
28
|
const MAX_CLIENT_ID = 128;
|
|
29
|
+
/** A space name as it appears in a subject token. */
|
|
30
|
+
const SPACE_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
28
31
|
const bad = (id, message) => ({ ok: false, id, code: "BAD_FIELD", message });
|
|
29
32
|
/** Parse one line (without its newline). Pure: it decides, it does nothing. */
|
|
30
33
|
export function parseRequest(line) {
|
|
@@ -61,6 +64,11 @@ export function parseRequest(line) {
|
|
|
61
64
|
return bad(id, "attempt must be the 22-character attempt id from login-prompt");
|
|
62
65
|
return { ok: true, request: { v, id, op, attempt: obj.attempt } };
|
|
63
66
|
}
|
|
67
|
+
if (op === "connect-check") {
|
|
68
|
+
if (typeof obj.space !== "string" || !SPACE_PATTERN.test(obj.space))
|
|
69
|
+
return bad(id, "space must be 1-64 characters of [A-Za-z0-9_-]");
|
|
70
|
+
return { ok: true, request: { v, id, op, space: obj.space } };
|
|
71
|
+
}
|
|
64
72
|
if (typeof obj.idp !== "string")
|
|
65
73
|
return bad(id, "idp must be the provider's auth base URL");
|
|
66
74
|
let idp;
|
package/dist/daemon/server.js
CHANGED
|
@@ -15,6 +15,7 @@ import { createServer } from "node:net";
|
|
|
15
15
|
import { DAEMON_PROTOCOL_VERSION, MAX_LINE_BYTES, EndpointRefusal, assertLinux, checkSocket, ensureDir, refuseExisting } from "./endpoint.js";
|
|
16
16
|
import { encode, parseRequest } from "./protocol.js";
|
|
17
17
|
import { createLoginCoordinator } from "./login.js";
|
|
18
|
+
import { connectCheck } from "./connect.js";
|
|
18
19
|
import { homeCotalDir } from "@cotal-ai/workspace";
|
|
19
20
|
/** Requests one connection may send. A sign-in needs two (start, cancel); status polling a few. */
|
|
20
21
|
export const MAX_REQUESTS_PER_CONNECTION = 64;
|
|
@@ -40,8 +41,9 @@ export async function serveDaemon(opts) {
|
|
|
40
41
|
const { paths } = opts;
|
|
41
42
|
const dirOutcome = ensureDir(paths.dir, uid);
|
|
42
43
|
refuseExisting(paths.socket);
|
|
43
|
-
const counters = { accepted: 0, peerRejected: 0, framesDecoded: 0, requestsRefused: 0, statusAnswered: 0, loginsStarted: 0, loginsRefused: 0, cancelsAnswered: 0 };
|
|
44
|
-
const
|
|
44
|
+
const counters = { accepted: 0, peerRejected: 0, framesDecoded: 0, requestsRefused: 0, statusAnswered: 0, loginsStarted: 0, loginsRefused: 0, cancelsAnswered: 0, connectChecksAnswered: 0 };
|
|
45
|
+
const machineHome = opts.sessionDir ?? homeCotalDir();
|
|
46
|
+
const login = opts.login ?? createLoginCoordinator({ sessionDir: machineHome });
|
|
45
47
|
const startedAt = new Date();
|
|
46
48
|
const status = () => ({
|
|
47
49
|
state: "ready",
|
|
@@ -109,6 +111,14 @@ export async function serveDaemon(opts) {
|
|
|
109
111
|
void write({ v: DAEMON_PROTOCOL_VERSION, id: req.id, ok: true, result: status() });
|
|
110
112
|
return;
|
|
111
113
|
}
|
|
114
|
+
if (req.op === "connect-check") {
|
|
115
|
+
void connectCheck(req.space, machineHome).catch(() => ({ outcome: "refused", space: req.space, message: "the daemon could not complete the check" }))
|
|
116
|
+
.then((result) => {
|
|
117
|
+
counters.connectChecksAnswered++;
|
|
118
|
+
return write({ v: DAEMON_PROTOCOL_VERSION, id: req.id, ok: true, result });
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
112
122
|
if (req.op === "login-cancel") {
|
|
113
123
|
void login.cancel(req.attempt).then((result) => {
|
|
114
124
|
counters.cancelsAnswered++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cotal-ai",
|
|
3
|
-
"version": "0.94.
|
|
3
|
+
"version": "0.94.7",
|
|
4
4
|
"description": "Cotal — lateral agent coordination over NATS. Run `npx cotal-ai` for the CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -33,26 +33,26 @@
|
|
|
33
33
|
"node": ">=22"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@cotal-ai/auth": "0.94.
|
|
37
|
-
"@cotal-ai/cli": "0.94.
|
|
38
|
-
"@cotal-ai/connector-core": "0.94.
|
|
39
|
-
"@cotal-ai/core": "0.94.
|
|
40
|
-
"@cotal-ai/delivery": "0.94.
|
|
41
|
-
"@cotal-ai/manager": "0.94.
|
|
42
|
-
"@cotal-ai/runtime": "0.94.
|
|
43
|
-
"@cotal-ai/seat": "0.94.
|
|
44
|
-
"@cotal-ai/workspace": "0.94.
|
|
36
|
+
"@cotal-ai/auth": "0.94.7",
|
|
37
|
+
"@cotal-ai/cli": "0.94.7",
|
|
38
|
+
"@cotal-ai/connector-core": "0.94.7",
|
|
39
|
+
"@cotal-ai/core": "0.94.7",
|
|
40
|
+
"@cotal-ai/delivery": "0.94.7",
|
|
41
|
+
"@cotal-ai/manager": "0.94.7",
|
|
42
|
+
"@cotal-ai/runtime": "0.94.7",
|
|
43
|
+
"@cotal-ai/seat": "0.94.7",
|
|
44
|
+
"@cotal-ai/workspace": "0.94.7"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@cotal-ai/connector-claude-code": "0.94.
|
|
48
|
-
"@cotal-ai/connector-codex": "0.94.
|
|
49
|
-
"@cotal-ai/connector-hermes": "0.94.
|
|
50
|
-
"@cotal-ai/connector-jcode": "0.94.
|
|
51
|
-
"@cotal-ai/connector-opencode": "0.94.
|
|
52
|
-
"@cotal-ai/linear": "0.94.
|
|
53
|
-
"@cotal-ai/pi": "0.94.
|
|
47
|
+
"@cotal-ai/connector-claude-code": "0.94.7",
|
|
48
|
+
"@cotal-ai/connector-codex": "0.94.7",
|
|
49
|
+
"@cotal-ai/connector-hermes": "0.94.7",
|
|
50
|
+
"@cotal-ai/connector-jcode": "0.94.7",
|
|
51
|
+
"@cotal-ai/connector-opencode": "0.94.7",
|
|
52
|
+
"@cotal-ai/linear": "0.94.7",
|
|
53
|
+
"@cotal-ai/pi": "0.94.7",
|
|
54
54
|
"@cotal-ai/smoke-kit": "0.0.0",
|
|
55
|
-
"@cotal-ai/web": "0.94.
|
|
55
|
+
"@cotal-ai/web": "0.94.7",
|
|
56
56
|
"@nats-io/jetstream": "^3.4.0",
|
|
57
57
|
"@nats-io/transport-node": "^3.4.0",
|
|
58
58
|
"typescript": "^5.9.3"
|