@apnex/network-adapter 0.1.0

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.
Files changed (52) hide show
  1. package/dist/hub-error.d.ts +42 -0
  2. package/dist/hub-error.js +74 -0
  3. package/dist/hub-error.js.map +1 -0
  4. package/dist/index.d.ts +29 -0
  5. package/dist/index.js +31 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/kernel/agent-client.d.ts +192 -0
  8. package/dist/kernel/agent-client.js +44 -0
  9. package/dist/kernel/agent-client.js.map +1 -0
  10. package/dist/kernel/event-router.d.ts +49 -0
  11. package/dist/kernel/event-router.js +150 -0
  12. package/dist/kernel/event-router.js.map +1 -0
  13. package/dist/kernel/handshake.d.ts +161 -0
  14. package/dist/kernel/handshake.js +257 -0
  15. package/dist/kernel/handshake.js.map +1 -0
  16. package/dist/kernel/instance.d.ts +40 -0
  17. package/dist/kernel/instance.js +79 -0
  18. package/dist/kernel/instance.js.map +1 -0
  19. package/dist/kernel/mcp-agent-client.d.ts +139 -0
  20. package/dist/kernel/mcp-agent-client.js +505 -0
  21. package/dist/kernel/mcp-agent-client.js.map +1 -0
  22. package/dist/kernel/poll-backstop.d.ts +108 -0
  23. package/dist/kernel/poll-backstop.js +243 -0
  24. package/dist/kernel/poll-backstop.js.map +1 -0
  25. package/dist/kernel/session-claim.d.ts +67 -0
  26. package/dist/kernel/session-claim.js +106 -0
  27. package/dist/kernel/session-claim.js.map +1 -0
  28. package/dist/kernel/state-sync.d.ts +43 -0
  29. package/dist/kernel/state-sync.js +85 -0
  30. package/dist/kernel/state-sync.js.map +1 -0
  31. package/dist/logger.d.ts +82 -0
  32. package/dist/logger.js +114 -0
  33. package/dist/logger.js.map +1 -0
  34. package/dist/notification-log.d.ts +29 -0
  35. package/dist/notification-log.js +66 -0
  36. package/dist/notification-log.js.map +1 -0
  37. package/dist/prompt-format.d.ts +38 -0
  38. package/dist/prompt-format.js +220 -0
  39. package/dist/prompt-format.js.map +1 -0
  40. package/dist/tool-manager/dispatcher.d.ts +180 -0
  41. package/dist/tool-manager/dispatcher.js +379 -0
  42. package/dist/tool-manager/dispatcher.js.map +1 -0
  43. package/dist/tool-manager/tool-catalog-cache.d.ts +85 -0
  44. package/dist/tool-manager/tool-catalog-cache.js +137 -0
  45. package/dist/tool-manager/tool-catalog-cache.js.map +1 -0
  46. package/dist/wire/mcp-transport.d.ts +120 -0
  47. package/dist/wire/mcp-transport.js +447 -0
  48. package/dist/wire/mcp-transport.js.map +1 -0
  49. package/dist/wire/transport.d.ts +174 -0
  50. package/dist/wire/transport.js +43 -0
  51. package/dist/wire/transport.js.map +1 -0
  52. package/package.json +32 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Enriched register_role handshake.
3
+ *
4
+ * After the bare `register_role({role})` call in `McpAgentClient.runHandshake`
5
+ * (which proves the wire is alive and the session is bound), the agent
6
+ * performs this enriched handshake on entry to the `synchronizing` state. It:
7
+ *
8
+ * 1. Re-calls `register_role` with full payload (globalInstanceId,
9
+ * clientMetadata, advisoryTags).
10
+ * 2. Parses the response for `agentId`/`sessionEpoch`/`wasCreated`.
11
+ * 3. Tracks epoch displacement across reconnects.
12
+ * 4. Halts on fatal codes (`agent_thrashing_detected`, `role_mismatch`).
13
+ *
14
+ * Fatal halt is delegated to a caller-provided `onFatalHalt` callback so
15
+ * each engineer can implement its own shutdown path (Claude: stdio drain +
16
+ * `process.exit(2)`; OpenCode: OpenCode lifecycle shutdown).
17
+ */
18
+ import type { ILogger, LegacyStringLogger } from "../logger.js";
19
+ export declare const FATAL_CODES: ReadonlySet<string>;
20
+ export interface HandshakeClientMetadata {
21
+ clientName: string;
22
+ clientVersion: string;
23
+ proxyName: string;
24
+ proxyVersion: string;
25
+ transport: string;
26
+ sdkVersion: string;
27
+ hostname?: string;
28
+ platform?: string;
29
+ pid?: number;
30
+ }
31
+ export interface HandshakeAdvisoryTags {
32
+ /** Best-effort, drift-prone. Hub MUST NOT route on this. */
33
+ llmModel?: string;
34
+ [key: string]: unknown;
35
+ }
36
+ export interface HandshakePayload {
37
+ role: string;
38
+ globalInstanceId: string;
39
+ clientMetadata: HandshakeClientMetadata;
40
+ advisoryTags: HandshakeAdvisoryTags;
41
+ labels?: Record<string, string>;
42
+ }
43
+ export interface HandshakeResponse {
44
+ agentId: string;
45
+ sessionEpoch: number;
46
+ wasCreated: boolean;
47
+ }
48
+ export interface HandshakeFatalError {
49
+ code: string;
50
+ message: string;
51
+ }
52
+ /**
53
+ * Parse an MCP CallTool result for a structured handshake error payload.
54
+ * Returns the fatal error if the result matches `{isError:true, content:[{text: <json>}]}`
55
+ * and the JSON body has a `code` in `FATAL_CODES`. Returns null otherwise.
56
+ */
57
+ export declare function parseHandshakeError(result: unknown): HandshakeFatalError | null;
58
+ /**
59
+ * Parse a successful handshake response. The adapter's executeTool returns
60
+ * parsed JSON directly, but some code paths deliver the raw `{content:[{text}]}`
61
+ * envelope — handle both.
62
+ *
63
+ * mission-63 W3: reads canonical envelope per Design v1.0 §3.1 + ADR-028 —
64
+ * `body.agent.id` + `body.session.epoch` (not `body.agentId` + `body.sessionEpoch`).
65
+ * The Hub-side flat-field shape is gone post-mission-63 W1+W2 (anti-goal §8.1
66
+ * clean cutover). Adapter parses the canonical shape only.
67
+ */
68
+ export declare function parseHandshakeResponse(result: unknown): HandshakeResponse | null;
69
+ export interface HandshakeConfig {
70
+ role: string;
71
+ globalInstanceId: string;
72
+ clientInfo: {
73
+ name: string;
74
+ version: string;
75
+ };
76
+ proxyName: string;
77
+ proxyVersion: string;
78
+ transport: string;
79
+ sdkVersion: string;
80
+ llmModel?: string;
81
+ /**
82
+ * Mission-19 routing labels. Forwarded as the `labels` arg on the
83
+ * enriched register_role call. Hub persists them on the Agent entity
84
+ * (immutable after first create — INV-AG1), and subsequent
85
+ * `task.labels` / dispatch selectors inherit from the Agent. Omit to
86
+ * keep legacy broadcast semantics (labels = {}).
87
+ */
88
+ labels?: Record<string, string>;
89
+ /**
90
+ * ADR-017: optional durable-wake HTTP endpoint. When set, the Hub
91
+ * POSTs here on queue-deadline miss to cold-start scaled-to-zero
92
+ * agents. For Cloud Run architects, this is the service URL. Absent
93
+ * for interactive CLI agents — watchdog skips Stage 1 re-dispatch and
94
+ * escalates directly to Director notification.
95
+ */
96
+ wakeEndpoint?: string;
97
+ /**
98
+ * ADR-017: optional per-agent receipt-SLA override in milliseconds.
99
+ * When omitted, Hub uses DEFAULT_AGENT_RECEIPT_SLA_MS (30000).
100
+ */
101
+ receiptSla?: number;
102
+ }
103
+ export interface HandshakeContext {
104
+ /** Tool executor — typically `transport.request.bind(transport)`. */
105
+ executeTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
106
+ config: HandshakeConfig;
107
+ /** Previous session epoch for displacement detection. 0 if unknown. */
108
+ previousEpoch: number;
109
+ /**
110
+ * Structured logger for diagnostics + the canonical `[Handshake] Registered as …`
111
+ * line. A legacy `(msg: string) => void` is auto-bridged for tests and
112
+ * shims that haven't migrated to `ILogger` yet.
113
+ */
114
+ log: ILogger | LegacyStringLogger;
115
+ /** Invoked on fatal codes. Implementations should terminate the process. */
116
+ onFatalHalt?: (err: HandshakeFatalError) => void;
117
+ }
118
+ export interface HandshakeResult {
119
+ /** Parsed response, or null if the handshake failed (non-fatally). */
120
+ response: HandshakeResponse | null;
121
+ /** New epoch to persist for the next reconnect's displacement check. */
122
+ epoch: number;
123
+ }
124
+ /**
125
+ * bug-17 fix: resolve clientName / clientVersion with proxy-fallback.
126
+ *
127
+ * The MCP `initialize` handshake is supposed to carry the host's `clientInfo`
128
+ * (name + version), but some hosts — notably the claude-plugin dev-channel
129
+ * load path (`claude --dangerously-load-development-channels`) — don't
130
+ * forward clientInfo through the stdio transport. The Hub then persists
131
+ * `clientName: "unknown"` + `clientVersion: "0.0.0"` into the Agent record,
132
+ * losing the identity signal entirely.
133
+ *
134
+ * Fallback policy: when either field is missing/empty/sentinel, substitute
135
+ * the proxy identity (`@apnex/claude-plugin` / `@apnex/vertex-cloudrun` / etc.)
136
+ * which is authoritative at the adapter layer. The Agent record then
137
+ * surfaces a meaningful identity even when MCP clientInfo is absent.
138
+ *
139
+ * Exported for unit-test access.
140
+ */
141
+ export declare function resolveClientName(raw: string | undefined, proxyName: string): string;
142
+ export declare function resolveClientVersion(raw: string | undefined, proxyVersion: string): string;
143
+ /**
144
+ * Build the enriched register_role payload.
145
+ */
146
+ export declare function buildHandshakePayload(config: HandshakeConfig): HandshakePayload;
147
+ /**
148
+ * Perform the enriched handshake. Never throws on tool-call failure — a
149
+ * transport or session error is logged and returned as
150
+ * `{response:null, epoch:previousEpoch}` so the caller can continue state
151
+ * sync. A fatal code (`agent_thrashing_detected`, `role_mismatch`) triggers
152
+ * `onFatalHalt` and then also returns null.
153
+ */
154
+ export declare function performHandshake(ctx: HandshakeContext): Promise<HandshakeResult>;
155
+ /**
156
+ * Build a fatal-halt function with a stdio drain delay. Engineers that use
157
+ * stdio transports (Claude) MUST use this to avoid losing the halt message
158
+ * to an unflushed buffer. Engineers that do not (OpenCode) can implement
159
+ * their own `onFatalHalt` directly.
160
+ */
161
+ export declare function makeStdioFatalHalt(log: (msg: string) => void, exit?: (code: number) => void, drainMs?: number): (err: HandshakeFatalError) => void;
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Enriched register_role handshake.
3
+ *
4
+ * After the bare `register_role({role})` call in `McpAgentClient.runHandshake`
5
+ * (which proves the wire is alive and the session is bound), the agent
6
+ * performs this enriched handshake on entry to the `synchronizing` state. It:
7
+ *
8
+ * 1. Re-calls `register_role` with full payload (globalInstanceId,
9
+ * clientMetadata, advisoryTags).
10
+ * 2. Parses the response for `agentId`/`sessionEpoch`/`wasCreated`.
11
+ * 3. Tracks epoch displacement across reconnects.
12
+ * 4. Halts on fatal codes (`agent_thrashing_detected`, `role_mismatch`).
13
+ *
14
+ * Fatal halt is delegated to a caller-provided `onFatalHalt` callback so
15
+ * each engineer can implement its own shutdown path (Claude: stdio drain +
16
+ * `process.exit(2)`; OpenCode: OpenCode lifecycle shutdown).
17
+ */
18
+ import { hostname, platform as osPlatform } from "node:os";
19
+ import { normalizeToILogger } from "../logger.js";
20
+ export const FATAL_CODES = new Set([
21
+ "agent_thrashing_detected",
22
+ "role_mismatch",
23
+ ]);
24
+ /**
25
+ * Parse an MCP CallTool result for a structured handshake error payload.
26
+ * Returns the fatal error if the result matches `{isError:true, content:[{text: <json>}]}`
27
+ * and the JSON body has a `code` in `FATAL_CODES`. Returns null otherwise.
28
+ */
29
+ export function parseHandshakeError(result) {
30
+ try {
31
+ const r = result;
32
+ if (!r || r.isError !== true || !Array.isArray(r.content) || !r.content[0]?.text) {
33
+ return null;
34
+ }
35
+ const body = JSON.parse(r.content[0].text);
36
+ if (body && typeof body.code === "string" && FATAL_CODES.has(body.code)) {
37
+ return { code: body.code, message: String(body.message ?? "") };
38
+ }
39
+ }
40
+ catch {
41
+ /* not a structured error — fall through */
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Parse a successful handshake response. The adapter's executeTool returns
47
+ * parsed JSON directly, but some code paths deliver the raw `{content:[{text}]}`
48
+ * envelope — handle both.
49
+ *
50
+ * mission-63 W3: reads canonical envelope per Design v1.0 §3.1 + ADR-028 —
51
+ * `body.agent.id` + `body.session.epoch` (not `body.agentId` + `body.sessionEpoch`).
52
+ * The Hub-side flat-field shape is gone post-mission-63 W1+W2 (anti-goal §8.1
53
+ * clean cutover). Adapter parses the canonical shape only.
54
+ */
55
+ export function parseHandshakeResponse(result) {
56
+ try {
57
+ const r = result;
58
+ let body;
59
+ if (Array.isArray(r.content)) {
60
+ const text = r.content[0]?.text;
61
+ body = text ? JSON.parse(text) : {};
62
+ }
63
+ else {
64
+ body = r;
65
+ }
66
+ const agent = body.agent;
67
+ const session = body.session;
68
+ if (agent &&
69
+ typeof agent.id === "string" &&
70
+ session &&
71
+ typeof session.epoch === "number") {
72
+ return {
73
+ agentId: agent.id,
74
+ sessionEpoch: session.epoch,
75
+ wasCreated: Boolean(body.wasCreated),
76
+ };
77
+ }
78
+ }
79
+ catch {
80
+ /* fall through */
81
+ }
82
+ return null;
83
+ }
84
+ /**
85
+ * bug-17 fix: resolve clientName / clientVersion with proxy-fallback.
86
+ *
87
+ * The MCP `initialize` handshake is supposed to carry the host's `clientInfo`
88
+ * (name + version), but some hosts — notably the claude-plugin dev-channel
89
+ * load path (`claude --dangerously-load-development-channels`) — don't
90
+ * forward clientInfo through the stdio transport. The Hub then persists
91
+ * `clientName: "unknown"` + `clientVersion: "0.0.0"` into the Agent record,
92
+ * losing the identity signal entirely.
93
+ *
94
+ * Fallback policy: when either field is missing/empty/sentinel, substitute
95
+ * the proxy identity (`@apnex/claude-plugin` / `@apnex/vertex-cloudrun` / etc.)
96
+ * which is authoritative at the adapter layer. The Agent record then
97
+ * surfaces a meaningful identity even when MCP clientInfo is absent.
98
+ *
99
+ * Exported for unit-test access.
100
+ */
101
+ export function resolveClientName(raw, proxyName) {
102
+ if (!raw || raw === "unknown")
103
+ return proxyName;
104
+ return raw;
105
+ }
106
+ export function resolveClientVersion(raw, proxyVersion) {
107
+ if (!raw || raw === "0.0.0")
108
+ return proxyVersion;
109
+ return raw;
110
+ }
111
+ /**
112
+ * Build the enriched register_role payload.
113
+ */
114
+ export function buildHandshakePayload(config) {
115
+ const payload = {
116
+ role: config.role,
117
+ globalInstanceId: config.globalInstanceId,
118
+ clientMetadata: {
119
+ clientName: resolveClientName(config.clientInfo.name, config.proxyName),
120
+ clientVersion: resolveClientVersion(config.clientInfo.version, config.proxyVersion),
121
+ proxyName: config.proxyName,
122
+ proxyVersion: config.proxyVersion,
123
+ transport: config.transport,
124
+ sdkVersion: config.sdkVersion,
125
+ hostname: hostname(),
126
+ platform: osPlatform(),
127
+ pid: process.pid,
128
+ },
129
+ advisoryTags: {
130
+ llmModel: config.llmModel ?? "unknown",
131
+ },
132
+ };
133
+ if (config.labels)
134
+ payload.labels = config.labels;
135
+ if (config.wakeEndpoint)
136
+ payload.wakeEndpoint = config.wakeEndpoint;
137
+ if (typeof config.receiptSla === "number")
138
+ payload.receiptSla = config.receiptSla;
139
+ return payload;
140
+ }
141
+ /**
142
+ * Perform the enriched handshake. Never throws on tool-call failure — a
143
+ * transport or session error is logged and returned as
144
+ * `{response:null, epoch:previousEpoch}` so the caller can continue state
145
+ * sync. A fatal code (`agent_thrashing_detected`, `role_mismatch`) triggers
146
+ * `onFatalHalt` and then also returns null.
147
+ */
148
+ export async function performHandshake(ctx) {
149
+ const log = normalizeToILogger(ctx.log, "Handshake");
150
+ const payload = buildHandshakePayload(ctx.config);
151
+ let result;
152
+ try {
153
+ result = await ctx.executeTool("register_role", payload);
154
+ }
155
+ catch (err) {
156
+ log.log("agent.handshake.tool_call_failed", { error: String(err) }, `[Handshake] tool-call failed (non-fatal, retry on reconnect): ${err}`);
157
+ return { response: null, epoch: ctx.previousEpoch };
158
+ }
159
+ const fatal = parseHandshakeError(result);
160
+ if (fatal) {
161
+ log.log("agent.handshake.fatal", { code: fatal.code, message: fatal.message }, `[Handshake] FATAL ${fatal.code}: ${fatal.message}`);
162
+ if (ctx.onFatalHalt)
163
+ ctx.onFatalHalt(fatal);
164
+ return { response: null, epoch: ctx.previousEpoch };
165
+ }
166
+ const response = parseHandshakeResponse(result);
167
+ if (!response) {
168
+ // mission-63 W3 diagnostic — capture envelope + body shape for parse
169
+ // failures. Post-mission-63 the canonical shape is `body.agent.id` +
170
+ // `body.session.epoch`; legacy flat fields (body.agentId + body.sessionEpoch)
171
+ // surfacing here would indicate the Hub is on a pre-mission-63 build.
172
+ let envelope = "unknown";
173
+ let bodyKeys = "none";
174
+ let nestedAgentKeys = "none";
175
+ let nestedSessionKeys = "none";
176
+ let legacyAgentIdType = "absent";
177
+ let legacySessionEpochType = "absent";
178
+ try {
179
+ const r = result;
180
+ let body = {};
181
+ if (Array.isArray(r.content)) {
182
+ envelope = "content-array";
183
+ const text = r.content[0]
184
+ ?.text;
185
+ body = text ? JSON.parse(text) : {};
186
+ }
187
+ else {
188
+ envelope = "raw-object";
189
+ body = r;
190
+ }
191
+ bodyKeys = Object.keys(body).sort().join(",");
192
+ if (body.agent && typeof body.agent === "object" && body.agent !== null) {
193
+ nestedAgentKeys = Object.keys(body.agent)
194
+ .sort()
195
+ .join(",");
196
+ }
197
+ if (body.session && typeof body.session === "object" && body.session !== null) {
198
+ nestedSessionKeys = Object.keys(body.session)
199
+ .sort()
200
+ .join(",");
201
+ }
202
+ legacyAgentIdType =
203
+ body.agentId === undefined
204
+ ? "undefined"
205
+ : body.agentId === null
206
+ ? "null"
207
+ : typeof body.agentId;
208
+ legacySessionEpochType =
209
+ body.sessionEpoch === undefined
210
+ ? "undefined"
211
+ : body.sessionEpoch === null
212
+ ? "null"
213
+ : typeof body.sessionEpoch;
214
+ }
215
+ catch (err) {
216
+ envelope = `parse-error:${err?.message ?? String(err)}`;
217
+ }
218
+ log.log("agent.handshake.parse_failed", { envelope, bodyKeys, nestedAgentKeys, nestedSessionKeys, legacyAgentIdType, legacySessionEpochType }, "[Handshake] response parse failed (non-fatal)");
219
+ return { response: null, epoch: ctx.previousEpoch };
220
+ }
221
+ // M-Session-Claim-Separation (mission-40) T3 HC #1: post-T2 register_role
222
+ // is pure identity assertion — does NOT increment sessionEpoch. The
223
+ // previousEpoch vs response.sessionEpoch comparison no longer detects
224
+ // "I just took over" (that signal lives on claim_session's response
225
+ // shape: sessionClaimed + displacedPriorSession). It still detects
226
+ // "someone else claimed our identity between our last register_role
227
+ // and this one": any positive delta means an out-of-band claim happened.
228
+ // Pre-T2 the threshold was `> 1` (because register_role itself bumped
229
+ // by 1 on every call). Post-T2 the threshold is `> 0`.
230
+ //
231
+ // Takeover detection inside the adapter (claude-plugin shim.ts T3) keys
232
+ // on the claim_session response fields, NOT on this delta — see
233
+ // mission-40 brief §3 T3 + anti-goal §7.5.
234
+ if (ctx.previousEpoch > 0 && response.sessionEpoch - ctx.previousEpoch > 0) {
235
+ log.log("agent.handshake.epoch_jump", { from: ctx.previousEpoch, to: response.sessionEpoch }, `[Handshake] sessionEpoch advanced from ${ctx.previousEpoch} to ${response.sessionEpoch} between register_role calls — an external claim_session has displaced our prior session; in-flight RPCs from prior epoch may be abandoned`);
236
+ }
237
+ log.log("agent.handshake.registered", {
238
+ agentId: response.agentId,
239
+ epoch: response.sessionEpoch,
240
+ wasCreated: response.wasCreated,
241
+ }, `[Handshake] Registered as ${response.agentId} (epoch=${response.sessionEpoch}${response.wasCreated ? ", newly created" : ""})`);
242
+ return { response, epoch: response.sessionEpoch };
243
+ }
244
+ /**
245
+ * Build a fatal-halt function with a stdio drain delay. Engineers that use
246
+ * stdio transports (Claude) MUST use this to avoid losing the halt message
247
+ * to an unflushed buffer. Engineers that do not (OpenCode) can implement
248
+ * their own `onFatalHalt` directly.
249
+ */
250
+ export function makeStdioFatalHalt(log, exit = process.exit.bind(process), drainMs = 100) {
251
+ return (err) => {
252
+ const text = `[FATAL:${err.code}] ${err.message}`;
253
+ log(text);
254
+ setTimeout(() => exit(2), drainMs);
255
+ };
256
+ }
257
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/kernel/handshake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * globalInstanceId bootstrap.
3
+ *
4
+ * Identity is decoupled from authentication: the Hub token grants *access*
5
+ * and *role*; the globalInstanceId grants *identity*. This lets tokens be
6
+ * rotated without orphaning Agent entities.
7
+ *
8
+ * Storage: ~/.ois/instance.json (owner-private, 0600). NOT workspace-local —
9
+ * workspace scoping fails on multi-terminal, clone, and accidental git commit.
10
+ *
11
+ * First call generates a new UUID v4. Subsequent calls read it. If the file
12
+ * is deleted or corrupted, a new UUID is generated → new fingerprint → Hub
13
+ * creates a new Agent. The old Agent remains append-only with its queue
14
+ * intact; use `migrate_agent_queue` to recover pending work.
15
+ */
16
+ export interface LoadInstanceOptions {
17
+ /** Override the default ~/.ois/instance.json location (for tests). */
18
+ instanceFile?: string;
19
+ /** Optional logger for diagnostics (regeneration on corruption, etc.). */
20
+ log?: (msg: string) => void;
21
+ }
22
+ /**
23
+ * Load an existing globalInstanceId from disk or create a new one.
24
+ * Returns the UUID (or the env-provided string override). Idempotent
25
+ * across calls within a process.
26
+ *
27
+ * Precedence: `OIS_INSTANCE_ID` env var wins outright — used when the
28
+ * operator wants a stable, human-meaningful identity (e.g. "greg" or
29
+ * "kate") and is responsible for uniqueness themselves. Absent that,
30
+ * falls back to the file-persisted UUID (original behaviour). The env
31
+ * path does NOT touch the file, so the two schemes coexist cleanly:
32
+ * toggling the env var off restores the file-derived UUID untouched.
33
+ *
34
+ * This is a pragmatic escape hatch that unblocks multi-agent
35
+ * co-location on a single user account (two Claude instances on one
36
+ * laptop). A richer design — separate agentName metadata plus an
37
+ * agentId derived from multiple inputs — is queued for the Entity
38
+ * Registry SSOT mission.
39
+ */
40
+ export declare function loadOrCreateGlobalInstanceId(opts?: LoadInstanceOptions): string;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * globalInstanceId bootstrap.
3
+ *
4
+ * Identity is decoupled from authentication: the Hub token grants *access*
5
+ * and *role*; the globalInstanceId grants *identity*. This lets tokens be
6
+ * rotated without orphaning Agent entities.
7
+ *
8
+ * Storage: ~/.ois/instance.json (owner-private, 0600). NOT workspace-local —
9
+ * workspace scoping fails on multi-terminal, clone, and accidental git commit.
10
+ *
11
+ * First call generates a new UUID v4. Subsequent calls read it. If the file
12
+ * is deleted or corrupted, a new UUID is generated → new fingerprint → Hub
13
+ * creates a new Agent. The old Agent remains append-only with its queue
14
+ * intact; use `migrate_agent_queue` to recover pending work.
15
+ */
16
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { homedir } from "node:os";
19
+ import { randomUUID } from "node:crypto";
20
+ const DEFAULT_INSTANCE_DIR = join(homedir(), ".ois");
21
+ const DEFAULT_INSTANCE_FILE = join(DEFAULT_INSTANCE_DIR, "instance.json");
22
+ /**
23
+ * Load an existing globalInstanceId from disk or create a new one.
24
+ * Returns the UUID (or the env-provided string override). Idempotent
25
+ * across calls within a process.
26
+ *
27
+ * Precedence: `OIS_INSTANCE_ID` env var wins outright — used when the
28
+ * operator wants a stable, human-meaningful identity (e.g. "greg" or
29
+ * "kate") and is responsible for uniqueness themselves. Absent that,
30
+ * falls back to the file-persisted UUID (original behaviour). The env
31
+ * path does NOT touch the file, so the two schemes coexist cleanly:
32
+ * toggling the env var off restores the file-derived UUID untouched.
33
+ *
34
+ * This is a pragmatic escape hatch that unblocks multi-agent
35
+ * co-location on a single user account (two Claude instances on one
36
+ * laptop). A richer design — separate agentName metadata plus an
37
+ * agentId derived from multiple inputs — is queued for the Entity
38
+ * Registry SSOT mission.
39
+ */
40
+ export function loadOrCreateGlobalInstanceId(opts = {}) {
41
+ const log = opts.log ?? (() => { });
42
+ const envOverride = process.env.OIS_INSTANCE_ID?.trim();
43
+ if (envOverride) {
44
+ log(`[instance] Using OIS_INSTANCE_ID from env: ${envOverride}`);
45
+ return envOverride;
46
+ }
47
+ const file = opts.instanceFile ?? DEFAULT_INSTANCE_FILE;
48
+ const dir = file.substring(0, file.lastIndexOf("/")) || DEFAULT_INSTANCE_DIR;
49
+ if (!existsSync(dir)) {
50
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
51
+ }
52
+ if (existsSync(file)) {
53
+ try {
54
+ const raw = JSON.parse(readFileSync(file, "utf-8"));
55
+ if (raw.globalInstanceId && typeof raw.globalInstanceId === "string") {
56
+ return raw.globalInstanceId;
57
+ }
58
+ log(`[instance] ${file} missing globalInstanceId — regenerating`);
59
+ }
60
+ catch (err) {
61
+ log(`[instance] Failed to parse ${file}: ${err} — regenerating`);
62
+ }
63
+ }
64
+ const globalInstanceId = randomUUID();
65
+ const body = {
66
+ globalInstanceId,
67
+ createdAt: new Date().toISOString(),
68
+ };
69
+ writeFileSync(file, JSON.stringify(body, null, 2), { mode: 0o600 });
70
+ try {
71
+ chmodSync(file, 0o600);
72
+ }
73
+ catch {
74
+ /* best effort — filesystem may not support chmod */
75
+ }
76
+ log(`[instance] Generated new globalInstanceId → ${file}`);
77
+ return globalInstanceId;
78
+ }
79
+ //# sourceMappingURL=instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance.js","sourceRoot":"","sources":["../../src/kernel/instance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AACrD,MAAM,qBAAqB,GAAG,IAAI,CAAC,oBAAoB,EAAE,eAAe,CAAC,CAAC;AAc1E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,4BAA4B,CAC1C,OAA4B,EAAE;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAgB,CAAC,CAAC,CAAC;IAEjD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC;IACxD,IAAI,WAAW,EAAE,CAAC;QAChB,GAAG,CAAC,8CAA8C,WAAW,EAAE,CAAC,CAAC;QACjE,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,IAAI,qBAAqB,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,oBAAoB,CAAC;IAE7E,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAiB,CAAC;YACpE,IAAI,GAAG,CAAC,gBAAgB,IAAI,OAAO,GAAG,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;gBACrE,OAAO,GAAG,CAAC,gBAAgB,CAAC;YAC9B,CAAC;YACD,GAAG,CAAC,cAAc,IAAI,0CAA0C,CAAC,CAAC;QACpE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,8BAA8B,IAAI,KAAK,GAAG,iBAAiB,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,MAAM,gBAAgB,GAAG,UAAU,EAAE,CAAC;IACtC,MAAM,IAAI,GAAiB;QACzB,gBAAgB;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;IACD,GAAG,CAAC,+CAA+C,IAAI,EAAE,CAAC,CAAC;IAC3D,OAAO,gBAAgB,CAAC;AAC1B,CAAC"}