@genkaku/protocol 0.0.0-stage → 0.1.1

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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bridge.d.ts +46 -0
  3. package/dist/bridge.d.ts.map +1 -0
  4. package/dist/bridge.js +609 -0
  5. package/dist/capabilities.d.ts +50 -0
  6. package/dist/capabilities.d.ts.map +1 -0
  7. package/dist/capabilities.js +66 -0
  8. package/dist/capabilities.test.d.ts +2 -0
  9. package/dist/capabilities.test.d.ts.map +1 -0
  10. package/dist/capabilities.test.js +39 -0
  11. package/dist/chain.d.ts +496 -0
  12. package/dist/chain.d.ts.map +1 -0
  13. package/dist/chain.js +763 -0
  14. package/dist/credit.d.ts +35 -0
  15. package/dist/credit.d.ts.map +1 -0
  16. package/dist/credit.js +112 -0
  17. package/dist/dial.d.ts +51 -0
  18. package/dist/dial.d.ts.map +1 -0
  19. package/dist/dial.js +287 -0
  20. package/dist/directory.d.ts +39 -0
  21. package/dist/directory.d.ts.map +1 -0
  22. package/dist/directory.js +149 -0
  23. package/dist/discovery.d.ts +20 -0
  24. package/dist/discovery.d.ts.map +1 -0
  25. package/dist/discovery.js +58 -0
  26. package/dist/hello.d.ts +14 -0
  27. package/dist/hello.d.ts.map +1 -0
  28. package/dist/hello.js +42 -0
  29. package/dist/index.d.ts +32 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +32 -0
  32. package/dist/keys.d.ts +30 -0
  33. package/dist/keys.d.ts.map +1 -0
  34. package/dist/keys.js +126 -0
  35. package/dist/llmBridge.d.ts +100 -0
  36. package/dist/llmBridge.d.ts.map +1 -0
  37. package/dist/llmBridge.js +284 -0
  38. package/dist/lp.d.ts +31 -0
  39. package/dist/lp.d.ts.map +1 -0
  40. package/dist/lp.js +116 -0
  41. package/dist/modeManager.d.ts +30 -0
  42. package/dist/modeManager.d.ts.map +1 -0
  43. package/dist/modeManager.js +81 -0
  44. package/dist/netconfig.d.ts +11 -0
  45. package/dist/netconfig.d.ts.map +1 -0
  46. package/dist/netconfig.js +39 -0
  47. package/dist/offer.d.ts +89 -0
  48. package/dist/offer.d.ts.map +1 -0
  49. package/dist/offer.js +86 -0
  50. package/dist/offer.test.d.ts +2 -0
  51. package/dist/offer.test.d.ts.map +1 -0
  52. package/dist/offer.test.js +33 -0
  53. package/dist/ollama.d.ts +53 -0
  54. package/dist/ollama.d.ts.map +1 -0
  55. package/dist/ollama.js +162 -0
  56. package/dist/p2pNode.d.ts +52 -0
  57. package/dist/p2pNode.d.ts.map +1 -0
  58. package/dist/p2pNode.js +454 -0
  59. package/dist/paywall.d.ts +30 -0
  60. package/dist/paywall.d.ts.map +1 -0
  61. package/dist/paywall.js +84 -0
  62. package/dist/publishOptIn.d.ts +15 -0
  63. package/dist/publishOptIn.d.ts.map +1 -0
  64. package/dist/publishOptIn.js +34 -0
  65. package/dist/receipts.d.ts +61 -0
  66. package/dist/receipts.d.ts.map +1 -0
  67. package/dist/receipts.js +135 -0
  68. package/dist/registry.d.ts +19 -0
  69. package/dist/registry.d.ts.map +1 -0
  70. package/dist/registry.js +50 -0
  71. package/dist/rpc.d.ts +24 -0
  72. package/dist/rpc.d.ts.map +1 -0
  73. package/dist/rpc.js +143 -0
  74. package/dist/status.d.ts +56 -0
  75. package/dist/status.d.ts.map +1 -0
  76. package/dist/status.js +354 -0
  77. package/dist/userSession.d.ts +77 -0
  78. package/dist/userSession.d.ts.map +1 -0
  79. package/dist/userSession.js +132 -0
  80. package/dist/workerSession.d.ts +87 -0
  81. package/dist/workerSession.d.ts.map +1 -0
  82. package/dist/workerSession.js +97 -0
  83. package/dist/workerSession.test.d.ts +2 -0
  84. package/dist/workerSession.test.d.ts.map +1 -0
  85. package/dist/workerSession.test.js +49 -0
  86. package/package.json +56 -3
  87. package/README.md +0 -3
@@ -0,0 +1,284 @@
1
+ // GENKAKU: local LLM bridge (worker mode).
2
+ // Translates an in-flight libp2p worker stream into the machine's local Ollama
3
+ // API (127.0.0.1:11434 — same source the upstream agent uses via
4
+ // agent/agent_init_ollama.py), streaming tokens out and reporting metering.
5
+ //
6
+ // Deviation from P2 §6 note: the LLM-side transport is *local HTTP*, not the
7
+ // Python relay — the sidecar stays a thin, testable layer; a future variant can
8
+ // point it at a hermes gateway's internal completion API instead (the interface
9
+ // below is the seam: LocalCompletionSource).
10
+ import { profileOf } from "./capabilities.js";
11
+ export function ollamaAdmin(host = process.env.GENKAKU_OLLAMA_HOST ?? DEFAULT_HOST) {
12
+ async function readNdjson(res, onFrame) {
13
+ if (!res.ok || !res.body) {
14
+ throw new Error(`[genkaku:ollama] ${new URL(res.url).pathname} refused (${res.status})`);
15
+ }
16
+ const reader = res.body.getReader();
17
+ const decoder = new TextDecoder();
18
+ let buf = '';
19
+ while (true) {
20
+ const { value, done } = await reader.read();
21
+ if (done) {
22
+ break;
23
+ }
24
+ buf += decoder.decode(value, { stream: true });
25
+ let nl;
26
+ while ((nl = buf.indexOf('\n')) >= 0) {
27
+ const line = buf.slice(0, nl).trim();
28
+ buf = buf.slice(nl + 1);
29
+ if (!line) {
30
+ continue;
31
+ }
32
+ try {
33
+ onFrame(JSON.parse(line));
34
+ }
35
+ catch { /* tolerate */ }
36
+ }
37
+ }
38
+ }
39
+ return {
40
+ /** locally installed models (what the worker may choose to publish) */
41
+ async list() {
42
+ const res = await fetch(new URL('/api/tags', host), { method: 'GET' });
43
+ if (!res.ok) {
44
+ throw new Error(`[genkaku:ollama] /api/tags refused (${res.status}) — is Ollama running?`);
45
+ }
46
+ const body = (await res.json());
47
+ return (body.models ?? []).map(m => ({
48
+ name: m.name,
49
+ sizeBytes: m.size,
50
+ family: m.details?.family,
51
+ quantization: m.details?.quantization_level,
52
+ }));
53
+ },
54
+ /** download a model from the Ollama registry (registry name like "llama3:8b") */
55
+ async pull(model, onProgress, signal) {
56
+ const res = await fetch(new URL('/api/pull', host), {
57
+ method: 'POST',
58
+ headers: { 'content-type': 'application/json' },
59
+ body: JSON.stringify({ model, stream: true }),
60
+ ...(signal === undefined ? {} : { signal }),
61
+ });
62
+ let status = 'unknown';
63
+ await readNdjson(res, (f) => {
64
+ const total = typeof f.total === 'number' ? f.total : undefined;
65
+ const completed = typeof f.completed === 'number' ? f.completed : undefined;
66
+ let percent;
67
+ if (total != null && completed != null && total > 0) {
68
+ percent = Math.min(100, Math.round((completed / total) * 100));
69
+ }
70
+ const frame = { status: String(f.status ?? ''), percent };
71
+ status = frame.status;
72
+ onProgress?.(frame);
73
+ });
74
+ return { status };
75
+ },
76
+ };
77
+ }
78
+ const DEFAULT_HOST = 'http://127.0.0.1:11434';
79
+ /**
80
+ * Ask Ollama what a model can do.
81
+ *
82
+ * `/api/show` is the authoritative answer for capabilities; the parameter size
83
+ * lives in `/api/tags` under `details.parameter_size`. Both are needed for the
84
+ * tier, so callers usually want {@link probeModels}.
85
+ *
86
+ * @param model - model name as Ollama knows it.
87
+ * @param host - Ollama base URL.
88
+ */
89
+ export async function probeModel(model, host = process.env.GENKAKU_OLLAMA_HOST ?? DEFAULT_HOST) {
90
+ const res = await fetch(new URL('/api/show', host), {
91
+ method: 'POST',
92
+ headers: { 'content-type': 'application/json' },
93
+ body: JSON.stringify({ model }),
94
+ });
95
+ if (!res.ok) {
96
+ throw new Error(`[genkaku:ollama] /api/show refused for ${model} (${res.status})`);
97
+ }
98
+ const body = (await res.json());
99
+ return {
100
+ ...(Array.isArray(body.capabilities) ? { capabilities: body.capabilities } : {}),
101
+ ...(body.details?.parameter_size === undefined ? {} : { parameterSize: body.details.parameter_size }),
102
+ };
103
+ }
104
+ /**
105
+ * Profile every model Ollama has locally: capabilities from `/api/show`,
106
+ * size from `/api/tags`.
107
+ *
108
+ * A model whose probe fails is reported with no capabilities, which lands it in
109
+ * the `chat` tier. That is the safe direction: an unreadable model is never
110
+ * advertised as agent-capable.
111
+ *
112
+ * @param host - Ollama base URL.
113
+ */
114
+ export async function probeModels(host = process.env.GENKAKU_OLLAMA_HOST ?? DEFAULT_HOST) {
115
+ const res = await fetch(new URL('/api/tags', host), { method: 'GET' });
116
+ if (!res.ok) {
117
+ throw new Error(`[genkaku:ollama] /api/tags refused (${res.status}) — is Ollama running?`);
118
+ }
119
+ const body = (await res.json());
120
+ const rows = body.models ?? [];
121
+ return Promise.all(rows.map(async (row) => {
122
+ let capabilities;
123
+ try {
124
+ capabilities = (await probeModel(row.name, host)).capabilities;
125
+ }
126
+ catch {
127
+ capabilities = undefined;
128
+ }
129
+ return profileOf(row.name, {
130
+ ...(capabilities === undefined ? {} : { capabilities }),
131
+ ...(row.details?.parameter_size === undefined ? {} : { parameterSize: row.details.parameter_size }),
132
+ });
133
+ }));
134
+ }
135
+ export function createOllamaSource(host = process.env.GENKAKU_OLLAMA_HOST ?? DEFAULT_HOST) {
136
+ return {
137
+ async stream(req, onDelta, signal) {
138
+ const res = await fetch(new URL('/api/generate', host), {
139
+ method: 'POST',
140
+ headers: { 'content-type': 'application/json' },
141
+ body: JSON.stringify({
142
+ model: req.model,
143
+ prompt: req.prompt,
144
+ system: req.system,
145
+ stream: true,
146
+ options: {
147
+ num_ctx: Number(process.env.GENKAKU_OLLAMA_NUM_CTX ?? 8192),
148
+ ...(req.maxTokens ? { num_predict: req.maxTokens } : {}),
149
+ },
150
+ }),
151
+ ...(signal === undefined ? {} : { signal }),
152
+ });
153
+ if (!res.ok || !res.body) {
154
+ throw new Error(`[genkaku:ollama] local source refused (${res.status}) — is Ollama running?`);
155
+ }
156
+ const reader = res.body.getReader();
157
+ const decoder = new TextDecoder();
158
+ let completionChars = 0;
159
+ let done = false;
160
+ let evalCount = 0;
161
+ let promptTokens = 0;
162
+ // LP-JSON over the ollama ndjson
163
+ let buf = '';
164
+ while (!done) {
165
+ const { value, done: rDone } = await reader.read();
166
+ if (rDone) {
167
+ break;
168
+ }
169
+ buf += decoder.decode(value, { stream: true });
170
+ let nl;
171
+ while ((nl = buf.indexOf('\n')) >= 0) {
172
+ const line = buf.slice(0, nl).trim();
173
+ buf = buf.slice(nl + 1);
174
+ if (!line) {
175
+ continue;
176
+ }
177
+ try {
178
+ const parsed = JSON.parse(line);
179
+ if (parsed.response) {
180
+ completionChars += parsed.response.length;
181
+ onDelta(parsed.response);
182
+ }
183
+ if (parsed.prompt_eval_count != null) {
184
+ promptTokens = parsed.prompt_eval_count;
185
+ }
186
+ if (parsed.eval_count != null) {
187
+ evalCount = parsed.eval_count;
188
+ }
189
+ if (parsed.done) {
190
+ done = true;
191
+ break;
192
+ }
193
+ }
194
+ catch {
195
+ // tolerate partial frames (ollama does not truncate mid-line)
196
+ }
197
+ }
198
+ }
199
+ return {
200
+ promptTokens,
201
+ // prefer ollama's own count; fall back to the ~4-chars-per-token estimate
202
+ completionTokens: evalCount || Math.ceil(completionChars / 4),
203
+ model: req.model,
204
+ };
205
+ },
206
+ };
207
+ }
208
+ /**
209
+ * Serve a chat with tools through Ollama's `/api/chat`.
210
+ *
211
+ * The legacy `/api/generate` path takes a single prompt string and therefore
212
+ * cannot carry tool schemas or tool results; this source exists so a worker can
213
+ * serve an agent, not only a chat box.
214
+ *
215
+ * Ollama reports tool calls as complete objects rather than deltas, so each one
216
+ * is emitted as a single `tool-call` event with the full arguments string.
217
+ */
218
+ export function createOllamaChatSource(host = process.env.GENKAKU_OLLAMA_HOST ?? DEFAULT_HOST) {
219
+ return {
220
+ async streamChat(req, onEvent, signal) {
221
+ const res = await fetch(new URL('/api/chat', host), {
222
+ method: 'POST',
223
+ headers: { 'content-type': 'application/json' },
224
+ body: JSON.stringify({
225
+ model: req.model,
226
+ messages: req.messages,
227
+ stream: true,
228
+ ...(req.tools === undefined || req.tools.length === 0 ? {} : { tools: req.tools }),
229
+ options: {
230
+ num_ctx: Number(process.env.GENKAKU_OLLAMA_NUM_CTX ?? 8192),
231
+ ...(req.maxTokens ? { num_predict: req.maxTokens } : {}),
232
+ },
233
+ }),
234
+ ...(signal === undefined ? {} : { signal }),
235
+ });
236
+ if (!res.ok) {
237
+ throw new Error(`[genkaku:ollama] /api/chat refused (${res.status}) — is Ollama running?`);
238
+ }
239
+ let completionTokens = 0;
240
+ let toolIndex = 0;
241
+ const handleFrame = (frame) => {
242
+ const message = frame.message;
243
+ if (typeof message?.content === 'string' && message.content !== '') {
244
+ completionTokens += 1;
245
+ onEvent({ type: 'text', text: message.content });
246
+ }
247
+ for (const call of message?.tool_calls ?? []) {
248
+ const args = call.function?.arguments;
249
+ onEvent({
250
+ type: 'tool-call',
251
+ index: toolIndex++,
252
+ name: call.function?.name ?? '',
253
+ argumentsDelta: typeof args === 'string' ? args : JSON.stringify(args ?? {}),
254
+ });
255
+ }
256
+ };
257
+ const reader = res.body?.getReader();
258
+ if (reader == null)
259
+ throw new Error('[genkaku:ollama] /api/chat returned no body');
260
+ const decoder = new TextDecoder();
261
+ let buffer = '';
262
+ for (;;) {
263
+ const { done, value } = await reader.read();
264
+ if (done)
265
+ break;
266
+ buffer += decoder.decode(value, { stream: true });
267
+ let nl = buffer.indexOf('\n');
268
+ while (nl >= 0) {
269
+ const line = buffer.slice(0, nl).trim();
270
+ buffer = buffer.slice(nl + 1);
271
+ if (line !== '') {
272
+ try {
273
+ handleFrame(JSON.parse(line));
274
+ }
275
+ catch { /* tolerate */ }
276
+ }
277
+ nl = buffer.indexOf('\n');
278
+ }
279
+ }
280
+ return { completionTokens, model: req.model };
281
+ },
282
+ };
283
+ }
284
+ //# sourceMappingURL=llmBridge.js.map
package/dist/lp.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ export declare function lpEncode(obj: unknown): Uint8Array;
2
+ export type ByteChunk = Uint8Array | {
3
+ subarray(): Uint8Array;
4
+ };
5
+ /** Parse every COMPLETE frame in a byte blob (tests / whole-stream reads). */
6
+ export declare function lpFrames(chunks: ByteChunk[]): unknown[];
7
+ /**
8
+ * Incremental frame reader — feeds whatever bytes arrive and pops complete
9
+ * frames WITHOUT waiting for stream end.
10
+ */
11
+ export declare class FrameReader {
12
+ private buf;
13
+ push(chunk: ByteChunk): void;
14
+ /** next complete frame, or undefined while the current one is still partial */
15
+ next(): unknown | undefined;
16
+ }
17
+ /**
18
+ * One durable write side per stream: `sink(queue.source())` once, then push
19
+ * frames as they happen; the write half stays open until .end().
20
+ */
21
+ export declare class WriteQueue {
22
+ private items;
23
+ private waiters;
24
+ /** end() has been called — readers will see EOF after draining the items */
25
+ ended: boolean;
26
+ push(b: Uint8Array): void;
27
+ end(): void;
28
+ private drain;
29
+ source(): AsyncGenerator<Uint8Array>;
30
+ }
31
+ //# sourceMappingURL=lp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lp.d.ts","sourceRoot":"","sources":["../src/lp.ts"],"names":[],"mappings":"AAOA,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,CAQjD;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG;IAAE,QAAQ,IAAI,UAAU,CAAA;CAAE,CAAA;AA2B/D,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,CAgBvD;AAED;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAoB;IAE/B,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAQ5B,+EAA+E;IAC/E,IAAI,IAAI,OAAO,GAAG,SAAS;CAQ5B;AAED;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,OAAO,CAAwB;IACvC,4EAA4E;IAC5E,KAAK,UAAQ;IAEb,IAAI,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IAMzB,GAAG,IAAI,IAAI;IAKX,OAAO,CAAC,KAAK;IAIN,MAAM,IAAI,cAAc,CAAC,UAAU,CAAC;CAQ5C"}
package/dist/lp.js ADDED
@@ -0,0 +1,116 @@
1
+ // GENKAKU: LP-JSON wire primitives (protocol v0).
2
+ // 4-byte big-endian length prefix + UTF-8 JSON. Frame codec is streamable:
3
+ // sessions START before the peer finishes writing (tokens keep flowing after
4
+ // the open envelope), so reads must increment — never drain-to-EOS.
5
+ // A sink may only be attached ONCE per libp2p stream; all writes to one stream
6
+ // go through a WriteQueue whose async generator never ends until .end().
7
+ export function lpEncode(obj) {
8
+ const body = new TextEncoder().encode(JSON.stringify(obj) + '\n');
9
+ const head = new Uint8Array(4);
10
+ new DataView(head.buffer).setUint32(0, body.byteLength);
11
+ const out = new Uint8Array(head.length + body.length);
12
+ out.set(head);
13
+ out.set(body, head.length);
14
+ return out;
15
+ }
16
+ function toU8(c) {
17
+ if (c instanceof Uint8Array) {
18
+ return c;
19
+ }
20
+ return c.subarray();
21
+ }
22
+ function parseFrames(buf) {
23
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
24
+ const frames = [];
25
+ let p = 0;
26
+ while (p + 4 <= buf.length) {
27
+ const len = dv.getUint32(p);
28
+ if (len === 0 || p + 4 + len > buf.length) {
29
+ break;
30
+ }
31
+ try {
32
+ frames.push(JSON.parse(new TextDecoder().decode(buf.subarray(p + 4, p + 4 + len))));
33
+ }
34
+ catch { /* tolerate */ }
35
+ p += 4 + len;
36
+ }
37
+ return { frames, consumed: p };
38
+ }
39
+ /** Parse every COMPLETE frame in a byte blob (tests / whole-stream reads). */
40
+ export function lpFrames(chunks) {
41
+ const parts = [];
42
+ let total = 0;
43
+ for (const c of chunks) {
44
+ const u = toU8(c);
45
+ parts.push(u);
46
+ total += u.length;
47
+ }
48
+ const buf = new Uint8Array(total);
49
+ let off = 0;
50
+ for (const u of parts) {
51
+ buf.set(u, off);
52
+ off += u.length;
53
+ }
54
+ return parseFrames(buf).frames;
55
+ }
56
+ /**
57
+ * Incremental frame reader — feeds whatever bytes arrive and pops complete
58
+ * frames WITHOUT waiting for stream end.
59
+ */
60
+ export class FrameReader {
61
+ buf = new Uint8Array(0);
62
+ push(chunk) {
63
+ const u = toU8(chunk);
64
+ const merged = new Uint8Array(this.buf.length + u.length);
65
+ merged.set(this.buf);
66
+ merged.set(u, this.buf.length);
67
+ this.buf = merged;
68
+ }
69
+ /** next complete frame, or undefined while the current one is still partial */
70
+ next() {
71
+ const { frames, consumed } = parseFrames(this.buf);
72
+ if (frames.length === 0) {
73
+ return undefined;
74
+ }
75
+ this.buf = this.buf.subarray(consumed);
76
+ return frames[0];
77
+ }
78
+ }
79
+ /**
80
+ * One durable write side per stream: `sink(queue.source())` once, then push
81
+ * frames as they happen; the write half stays open until .end().
82
+ */
83
+ export class WriteQueue {
84
+ items = [];
85
+ waiters = [];
86
+ /** end() has been called — readers will see EOF after draining the items */
87
+ ended = false;
88
+ push(b) {
89
+ if (this.ended) {
90
+ return;
91
+ }
92
+ this.items.push(b);
93
+ this.drain();
94
+ }
95
+ end() {
96
+ this.ended = true;
97
+ this.drain();
98
+ }
99
+ drain() {
100
+ for (const w of this.waiters.splice(0)) {
101
+ w();
102
+ }
103
+ }
104
+ async *source() {
105
+ while (true) {
106
+ while (this.items.length > 0) {
107
+ yield this.items.shift();
108
+ }
109
+ if (this.ended) {
110
+ return;
111
+ }
112
+ await new Promise(resolve => this.waiters.push(resolve));
113
+ }
114
+ }
115
+ }
116
+ //# sourceMappingURL=lp.js.map
@@ -0,0 +1,30 @@
1
+ export type RoleMode = 'user' | 'localPlusSharing' | 'workerDedicated';
2
+ export type ModeState = {
3
+ mode: RoleMode;
4
+ /** a live remote session consumes another peer's worker this instant */
5
+ remoteUserActive: boolean;
6
+ /** this machine exposes /genkaku/* worker protocols on its libp2p node */
7
+ sharingActive: boolean;
8
+ };
9
+ export declare class ModeError extends Error {
10
+ readonly code: string;
11
+ constructor(msg: string, code: string);
12
+ }
13
+ export declare class ModeManager {
14
+ private readonly onTransition?;
15
+ private state;
16
+ constructor(onTransition?: ((from: RoleMode, to: RoleMode) => void) | undefined);
17
+ current(): Readonly<ModeState>;
18
+ /** Enter a role. `remoteUserActive` hints whether a live remote session should
19
+ * carry over (only meaningful user→user). */
20
+ switch(to: RoleMode, opts?: {
21
+ remoteUserActive?: boolean;
22
+ }): ModeState;
23
+ /** Sidecar calls at transport start (user mode only; D-block is enforced here). */
24
+ beginRemoteSession(): void;
25
+ endRemoteSession(): void;
26
+ /** Sidecar guard before installing /genkaku/* protocol handlers. */
27
+ installWorkerListeners(): 'installed' | 'already';
28
+ removeWorkerListeners(): void;
29
+ }
30
+ //# sourceMappingURL=modeManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modeManager.d.ts","sourceRoot":"","sources":["../src/modeManager.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,kBAAkB,GAAG,iBAAiB,CAAA;AAEtE,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,QAAQ,CAAA;IACd,wEAAwE;IACxE,gBAAgB,EAAE,OAAO,CAAA;IACzB,0EAA0E;IAC1E,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,qBAAa,SAAU,SAAQ,KAAK;aACO,IAAI,EAAE,MAAM;gBAAzC,GAAG,EAAE,MAAM,EAAkB,IAAI,EAAE,MAAM;CAGtD;AAED,qBAAa,WAAW;IAGV,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IAF1C,OAAO,CAAC,KAAK,CAAW;gBAEK,YAAY,CAAC,GAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,KAAK,IAAI,aAAA;IAIlF,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC;IAI9B;kDAC8C;IAC9C,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,SAAS;IAkB1E,mFAAmF;IACnF,kBAAkB,IAAI,IAAI;IAY1B,gBAAgB,IAAI,IAAI;IAIxB,oEAAoE;IACpE,sBAAsB,IAAI,WAAW,GAAG,SAAS;IAgBjD,qBAAqB,IAAI,IAAI;CAO9B"}
@@ -0,0 +1,81 @@
1
+ // GENKAKU: role mode manager — the D-block enforcement lives HERE and in the
2
+ // sidecar (never both a remote-user dialer and a worker listener on one machine).
3
+ // Spec: docs/genkaku/P4-ADAPTATION.md §2; user-locked rule 2026-11-06.
4
+ //
5
+ // Semantics:
6
+ // · "remote session" = this machine CONSUMES someone else's worker stream
7
+ // · "sharing" = this machine SERVES its local LLM over libp2p
8
+ // · A user : may hold remote sessions, never shares
9
+ // · B localPlusSharing: owners use their LOCAL model + share idle capacity
10
+ // ("local use" is not a remote session — no counterparty, no metering)
11
+ // · C workerDedicated : pure serving
12
+ // · D : remote-consumption + sharing simultaneously — banned
13
+ export class ModeError extends Error {
14
+ code;
15
+ constructor(msg, code) {
16
+ super(`[genkaku:${code}] ${msg}`);
17
+ this.code = code;
18
+ }
19
+ }
20
+ export class ModeManager {
21
+ onTransition;
22
+ state;
23
+ constructor(onTransition) {
24
+ this.onTransition = onTransition;
25
+ this.state = { mode: 'localPlusSharing', remoteUserActive: false, sharingActive: false };
26
+ }
27
+ current() {
28
+ return { ...this.state };
29
+ }
30
+ /** Enter a role. `remoteUserActive` hints whether a live remote session should
31
+ * carry over (only meaningful user→user). */
32
+ switch(to, opts = {}) {
33
+ if (this.state.mode === 'user' && this.state.remoteUserActive && to !== 'user') {
34
+ throw new ModeError('open remote sessions must end before leaving user mode', 'remote-must-drain');
35
+ }
36
+ if (this.state.sharingActive && to === 'user') {
37
+ throw new ModeError('stop worker listening before becoming a remote user', 'sharing-must-drain');
38
+ }
39
+ const sharing = to !== 'user';
40
+ const remote = to === 'user' ? Boolean(opts.remoteUserActive) : false;
41
+ const before = this.state.mode;
42
+ this.state = { mode: to, remoteUserActive: remote, sharingActive: sharing };
43
+ this.onTransition?.(before, to);
44
+ return { ...this.state };
45
+ }
46
+ /** Sidecar calls at transport start (user mode only; D-block is enforced here). */
47
+ beginRemoteSession() {
48
+ if (this.state.mode !== 'user') {
49
+ throw new ModeError('remote consumption is only allowed in dedicated user mode (D-block)', 'd-block');
50
+ }
51
+ if (this.state.sharingActive) {
52
+ throw new ModeError('cannot consume a remote worker while sharing (D-block)', 'd-block');
53
+ }
54
+ this.state.remoteUserActive = true;
55
+ }
56
+ endRemoteSession() {
57
+ this.state.remoteUserActive = false;
58
+ }
59
+ /** Sidecar guard before installing /genkaku/* protocol handlers. */
60
+ installWorkerListeners() {
61
+ if (this.state.mode === 'user') {
62
+ throw new ModeError('worker listeners forbidden in user mode (D-block)', 'd-block');
63
+ }
64
+ if (this.state.remoteUserActive) {
65
+ throw new ModeError('still draining a remote session before installing listeners', 'remote-must-drain');
66
+ }
67
+ if (this.state.sharingActive) {
68
+ return 'already';
69
+ }
70
+ this.state.sharingActive = true;
71
+ this.onTransition?.('user', this.state.mode);
72
+ return 'installed';
73
+ }
74
+ removeWorkerListeners() {
75
+ if (this.state.remoteUserActive) {
76
+ throw new ModeError('still draining a remote session before removing listeners', 'remote-must-drain');
77
+ }
78
+ this.state.sharingActive = false;
79
+ }
80
+ }
81
+ //# sourceMappingURL=modeManager.js.map
@@ -0,0 +1,11 @@
1
+ export type NetEnvironment = 'base-sepolia' | 'base';
2
+ export declare const CHAIN_ID: Record<NetEnvironment, 84532 | 8453>;
3
+ export declare const CONTRACTS: Record<NetEnvironment, {
4
+ escrow: string;
5
+ registry: string;
6
+ usdc: string;
7
+ }>;
8
+ export declare const TREASURY = "0x639a2B713041e15B0c5C18710536b661d5265b48";
9
+ export declare const HARDCODED_SEEDS: string[];
10
+ export declare const DEFAULT_RECEIPT_STEP_TOKENS = 10;
11
+ //# sourceMappingURL=netconfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"netconfig.d.ts","sourceRoot":"","sources":["../src/netconfig.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,MAAM,CAAA;AAEpD,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,cAAc,EAAE,KAAK,GAAG,IAAI,CAGzD,CAAA;AAED,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,cAAc,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAWhG,CAAA;AAED,eAAO,MAAM,QAAQ,+CAA+C,CAAA;AAWpE,eAAO,MAAM,eAAe,EAAE,MAAM,EAQhC,CAAA;AAGJ,eAAO,MAAM,2BAA2B,KAAK,CAAA"}
@@ -0,0 +1,39 @@
1
+ // GENKAKU: network constants for the desktop client (P4).
2
+ // Chain/data-model follows protocol/GENKAKU-P1.md + GENKAKU-P2.md.
3
+ // Testnet values are verified on-chain (deployed 2026-11-06, contracts/README.md).
4
+ export const CHAIN_ID = {
5
+ 'base-sepolia': 84532,
6
+ base: 8453,
7
+ };
8
+ export const CONTRACTS = {
9
+ 'base-sepolia': {
10
+ escrow: '0x8175df796A659681009BeDa4609a3cFe9d281D82',
11
+ registry: '0xFbE9FF497dC24bD69d13E4201418938Df7435Ff6', // v0.2 — on-chain directory (P4.9a)
12
+ usdc: '0xEb33DFC0424810039337a38510ef522e9E7F9727', // MockUSDC — test only
13
+ },
14
+ base: {
15
+ escrow: '', // deploy at mainnet launch
16
+ registry: '',
17
+ usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // canonical USDC — verify vs Circle
18
+ },
19
+ };
20
+ export const TREASURY = '0x639a2B713041e15B0c5C18710536b661d5265b48';
21
+ // Discovery (P4.9): the worker directory lives on Base — see directory.ts and
22
+ // rpc.ts. There is NO baked bootstrap seed: genkaku.app is a static download
23
+ // page, and neither a user nor a worker may depend on infrastructure we run.
24
+ //
25
+ // `GENKAKU_BOOTSTRAP_SEEDS` (comma-separated multiaddrs) is a DEVELOPMENT
26
+ // escape hatch for a LAN bring-up or a private deployment; it is empty in
27
+ // shipped builds and is never required for discovery to work.
28
+ const BAKED_SEEDS = [];
29
+ export const HARDCODED_SEEDS = (() => {
30
+ const fromEnv = (process.env.GENKAKU_BOOTSTRAP_SEEDS ?? '')
31
+ .split(',')
32
+ .map(s => s.trim())
33
+ .filter(s => s !== '');
34
+ return (fromEnv.length > 0 ? fromEnv : BAKED_SEEDS)
35
+ .filter(s => !s.includes('PLACEHOLDER'));
36
+ })();
37
+ // Metered receipts: default k = 10 tokens per signed ledger increment (P2 §3).
38
+ export const DEFAULT_RECEIPT_STEP_TOKENS = 10;
39
+ //# sourceMappingURL=netconfig.js.map