@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,149 @@
1
+ // GENKAKU: on-chain worker discovery (P4.9b).
2
+ //
3
+ // Replaces the bootstrap seed. The directory is built from `WorkerLocated` logs
4
+ // and never from a server we run: a cold client with an empty config and no
5
+ // cache can find workers using nothing but a public Base RPC.
6
+ //
7
+ // Two cost controls make that affordable at scale:
8
+ // 1. a persisted checkpoint (`lastBlock`) so a normal boot scans only the
9
+ // blocks since the last run — usually a handful;
10
+ // 2. an on-disk entry cache, so the offer list is available even when the
11
+ // chain read fails (offline start, RPC trouble).
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { readWorkerDeactivations, readWorkerLocators, REGISTRY_DEPLOY_BLOCK, } from "./chain.js";
15
+ import { peerIdDigestOf } from "./registry.js";
16
+ export function directoryPath(keyDir) {
17
+ return join(keyDir, 'directory.json');
18
+ }
19
+ export function loadDirectory(keyDir, env) {
20
+ const empty = {
21
+ version: 1,
22
+ env,
23
+ lastBlock: (REGISTRY_DEPLOY_BLOCK[env] - 1n).toString(),
24
+ entries: [],
25
+ updatedAt: 0,
26
+ };
27
+ try {
28
+ if (!existsSync(directoryPath(keyDir)))
29
+ return empty;
30
+ const raw = JSON.parse(readFileSync(directoryPath(keyDir), 'utf8'));
31
+ if (raw?.version !== 1 || raw.env !== env || !Array.isArray(raw.entries))
32
+ return empty;
33
+ return raw;
34
+ }
35
+ catch {
36
+ return empty;
37
+ }
38
+ }
39
+ function saveDirectory(keyDir, cache) {
40
+ try {
41
+ mkdirSync(keyDir, { recursive: true });
42
+ writeFileSync(directoryPath(keyDir), JSON.stringify(cache, null, 2), { mode: 0o600 });
43
+ }
44
+ catch {
45
+ // the cache is an optimisation; never fail discovery because of it
46
+ }
47
+ }
48
+ /** Does the locator actually point at the peerId the wallet pinned?
49
+ * A worker could advertise someone else's address (or an old one); the digest
50
+ * compare is the only thing that makes the directory trustworthy. */
51
+ function locatorMatchesPeer(entry) {
52
+ const wanted = entry.peerId.toLowerCase();
53
+ const ids = entry.locator
54
+ .split(/[;\n]+/)
55
+ .map(part => part.split('/p2p/').pop()?.split('/')[0])
56
+ .filter((id) => id != null && id !== '');
57
+ if (ids.length === 0)
58
+ return false;
59
+ return ids.every((id) => {
60
+ try {
61
+ return peerIdDigestOf(id).toLowerCase() === wanted;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ });
67
+ }
68
+ /** Scan forward from the checkpoint, merge into the cache, and persist.
69
+ * Returns the merged directory (live entries only). */
70
+ export async function syncDirectory(publicC, env, keyDir, log) {
71
+ const cache = loadDirectory(keyDir, env);
72
+ const floor = REGISTRY_DEPLOY_BLOCK[env] - 1n;
73
+ const scannedFrom = BigInt(cache.lastBlock) + 1n > floor ? BigInt(cache.lastBlock) + 1n : floor;
74
+ const latest = await publicC.getBlockNumber();
75
+ const byWallet = new Map();
76
+ for (const entry of cache.entries)
77
+ byWallet.set(entry.wallet.toLowerCase(), entry);
78
+ let added = 0;
79
+ let removed = 0;
80
+ if (latest >= scannedFrom) {
81
+ const [located, dead] = await Promise.all([
82
+ readWorkerLocators(publicC, env, { fromBlock: scannedFrom, toBlock: latest }),
83
+ readWorkerDeactivations(publicC, env, { fromBlock: scannedFrom, toBlock: latest }),
84
+ ]);
85
+ for (const entry of located) {
86
+ if (!locatorMatchesPeer(entry)) {
87
+ log.warn('directory: %s advertises a locator that does not contain its pinned peerId — ignored', entry.wallet);
88
+ continue;
89
+ }
90
+ const key = entry.wallet.toLowerCase();
91
+ const prev = byWallet.get(key);
92
+ if (prev == null || BigInt(prev.blockNumber) < entry.blockNumber)
93
+ added++;
94
+ byWallet.set(key, toCacheEntry(entry));
95
+ }
96
+ for (const gone of dead) {
97
+ const key = gone.wallet.toLowerCase();
98
+ const prev = byWallet.get(key);
99
+ if (prev != null && BigInt(prev.blockNumber) < gone.blockNumber) {
100
+ byWallet.delete(key);
101
+ removed++;
102
+ }
103
+ }
104
+ log.info('directory: scanned %s..%s (%d located, %d deactivated, %d new, %d dropped)', scannedFrom.toString(), latest.toString(), located.length, dead.length, added, removed);
105
+ }
106
+ const next = {
107
+ version: 1,
108
+ env,
109
+ lastBlock: latest.toString(),
110
+ entries: [...byWallet.values()],
111
+ updatedAt: Date.now(),
112
+ };
113
+ saveDirectory(keyDir, next);
114
+ return { entries: next.entries, scannedFrom, scannedTo: latest, added, removed };
115
+ }
116
+ function toCacheEntry(entry) {
117
+ return {
118
+ wallet: entry.wallet,
119
+ peerId: entry.peerId,
120
+ locator: entry.locator,
121
+ updatedAt: entry.updatedAt,
122
+ blockNumber: entry.blockNumber.toString(),
123
+ };
124
+ }
125
+ /** Every dialable multiaddr in the directory, newest worker first. */
126
+ export function workerAddrs(cache) {
127
+ const addrs = [];
128
+ const newestFirst = [...cache.entries].sort((a, b) => {
129
+ const delta = BigInt(b.blockNumber) - BigInt(a.blockNumber);
130
+ return delta === 0n ? 0 : delta > 0n ? 1 : -1;
131
+ });
132
+ for (const entry of newestFirst) {
133
+ for (const addr of entry.locator.split(/[;\n]+/).map(s => s.trim())) {
134
+ if (addr.startsWith('/'))
135
+ addrs.push(addr);
136
+ }
137
+ }
138
+ return [...new Set(addrs)];
139
+ }
140
+ /** Drop a wallet from the cache after repeated dial failures, so a dead worker
141
+ * stops costing a connection attempt on every boot until it advertises again. */
142
+ export function dropEntry(keyDir, env, wallet) {
143
+ const cache = loadDirectory(keyDir, env);
144
+ const next = cache.entries.filter(e => e.wallet.toLowerCase() !== wallet.toLowerCase());
145
+ if (next.length === cache.entries.length)
146
+ return;
147
+ saveDirectory(keyDir, { ...cache, entries: next, updatedAt: Date.now() });
148
+ }
149
+ //# sourceMappingURL=directory.js.map
@@ -0,0 +1,20 @@
1
+ export declare const DNS_SEED_NAME = "_genkaku-v0.seeds.genkaku.app";
2
+ /** Cloudflare's JSON DoH endpoint (no key, no logging of the query body). */
3
+ export declare const DEFAULT_DOH_URL = "https://cloudflare-dns.com/dns-query";
4
+ export type SeedResolverOptions = {
5
+ /** the DNS name to query (default _genkaku-v0.seeds.genkaku.app) */
6
+ name?: string;
7
+ /** DoH endpoint override (tests inject a fake fetch instead) */
8
+ dohUrl?: string;
9
+ /** injectable fetch for tests */
10
+ fetchImpl?: typeof fetch;
11
+ /** hard cap — discovery must never stall the marketplace */
12
+ timeoutMs?: number;
13
+ };
14
+ /** Resolve TXT seed multiaddrs. Any failure (network, DNS, malformed record)
15
+ * degrades to [] — the caller falls through to peerstore/manual layers. */
16
+ export declare function resolveDnsSeeds(opts?: SeedResolverOptions): Promise<string[]>;
17
+ /** TXT payloads → multiaddrs. Quoted chunks are joined, `v=1;` style prefixes
18
+ * ignored, and only "/"-prefixed tokens survive. */
19
+ export declare function parseSeedTxt(records: string[]): string[];
20
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,aAAa,kCAAkC,CAAA;AAE5D,6EAA6E;AAC7E,eAAO,MAAM,eAAe,yCAAyC,CAAA;AAKrE,MAAM,MAAM,mBAAmB,GAAG;IAChC,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,iCAAiC;IACjC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;4EAC4E;AAC5E,wBAAsB,eAAe,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA0BvF;AAED;qDACqD;AACrD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAoBxD"}
@@ -0,0 +1,58 @@
1
+ // GENKAKU: discovery layer 2 (P2 §2) — DNS TXT seeds.
2
+ // A TXT record lists multiaddrs; editing DNS reboots discovery without a
3
+ // client release. Resolution goes through DNS-over-HTTPS so a client whose
4
+ // local resolver strips TXT (or is hijacked) still works, and so there is no
5
+ // native resolver dependency in the main process.
6
+ //
7
+ // Record shape (chunked strings are joined before parsing):
8
+ // _genkaku-v0.seeds.genkaku.app. TXT "v=1; seeds=<addr1>,<addr2>"
9
+ // The `v=1;` prefix is optional; only tokens that look like multiaddrs ("/…")
10
+ // are kept, so prose or future fields cannot be dialed by accident.
11
+ export const DNS_SEED_NAME = '_genkaku-v0.seeds.genkaku.app';
12
+ /** Cloudflare's JSON DoH endpoint (no key, no logging of the query body). */
13
+ export const DEFAULT_DOH_URL = 'https://cloudflare-dns.com/dns-query';
14
+ /** Resolve TXT seed multiaddrs. Any failure (network, DNS, malformed record)
15
+ * degrades to [] — the caller falls through to peerstore/manual layers. */
16
+ export async function resolveDnsSeeds(opts = {}) {
17
+ const name = opts.name ?? DNS_SEED_NAME;
18
+ const dohUrl = opts.dohUrl ?? DEFAULT_DOH_URL;
19
+ const doFetch = opts.fetchImpl ?? fetch;
20
+ const url = `${dohUrl}?name=${encodeURIComponent(name)}&type=TXT`;
21
+ try {
22
+ const res = await doFetch(url, {
23
+ headers: { accept: 'application/dns-json' },
24
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 4_000),
25
+ });
26
+ if (!res.ok) {
27
+ return [];
28
+ }
29
+ const body = (await res.json());
30
+ if (body.Status !== 0 || !Array.isArray(body.Answer)) {
31
+ return [];
32
+ }
33
+ return parseSeedTxt(body.Answer.map(a => a.data).filter((d) => typeof d === 'string'));
34
+ }
35
+ catch {
36
+ return [];
37
+ }
38
+ }
39
+ /** TXT payloads → multiaddrs. Quoted chunks are joined, `v=1;` style prefixes
40
+ * ignored, and only "/"-prefixed tokens survive. */
41
+ export function parseSeedTxt(records) {
42
+ const out = new Set();
43
+ for (const raw of records) {
44
+ const joined = raw
45
+ .split('"')
46
+ .join('')
47
+ .replace(/^\s*v=\d+\s*;\s*/i, '');
48
+ for (const token of joined.split(/[,\s]+/)) {
49
+ // tolerate `seeds=` / `v=` style key prefixes on the first token
50
+ const addr = token.trim().replace(/^[a-z][a-z0-9_-]*=/i, '');
51
+ if (addr.startsWith('/') && addr.includes('/p2p/')) {
52
+ out.add(addr);
53
+ }
54
+ }
55
+ }
56
+ return [...out];
57
+ }
58
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1,14 @@
1
+ export type HelloProof = {
2
+ peerId: string;
3
+ wallet: `0x${string}`;
4
+ sig: string;
5
+ };
6
+ /** Fresh challenge for one handshake (never reused, never stored). */
7
+ export declare function newNonce(): `0x${string}`;
8
+ /** The exact bytes both sides sign — one definition, no drift. */
9
+ export declare function helloDigest(peerId: string, wallet: `0x${string}`, nonce: `0x${string}`): Uint8Array;
10
+ /** Worker side: prove possession of the wallet key for this PeerId. */
11
+ export declare function signHello(key: `0x${string}`, peerId: string, nonce: `0x${string}`): Promise<HelloProof>;
12
+ /** Client side: does this proof really come from `wallet` over `nonce`? */
13
+ export declare function verifyHello(proof: HelloProof, nonce: `0x${string}`): Promise<boolean>;
14
+ //# sourceMappingURL=hello.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hello.d.ts","sourceRoot":"","sources":["../src/hello.ts"],"names":[],"mappings":"AA2BA,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,KAAK,MAAM,EAAE,CAAA;IACrB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,sEAAsE;AACtE,wBAAgB,QAAQ,IAAI,KAAK,MAAM,EAAE,CAExC;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,KAAK,MAAM,EAAE,EACrB,KAAK,EAAE,KAAK,MAAM,EAAE,GACnB,UAAU,CAEZ;AAED,uEAAuE;AACvE,wBAAsB,SAAS,CAC7B,GAAG,EAAE,KAAK,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,MAAM,EAAE,GACnB,OAAO,CAAC,UAAU,CAAC,CAKrB;AAED,2EAA2E;AAC3E,wBAAsB,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAI3F"}
package/dist/hello.js ADDED
@@ -0,0 +1,42 @@
1
+ // GENKAKU: data-plane identity proof (P4.9e).
2
+ //
3
+ // WorkerRegistry proves wallet ⇄ PeerId, but only for a worker that chose to
4
+ // register — and the record is self-declared (whoever pays gas claims the
5
+ // peerId). The hello handshake closes that hole directly: the client sends a
6
+ // fresh 32-byte nonce, the peer signs
7
+ //
8
+ // keccak("genkaku.hello.v0:<peerId>:<wallet>:<nonce>")
9
+ //
10
+ // with the EVM key it claims, and the client recovers the signer. A peer can
11
+ // only answer for a wallet whose private key it holds, so an offer that claims
12
+ // someone else's wallet is dropped before any escrow is opened. This also works
13
+ // for invite-link workers that never touched the chain.
14
+ //
15
+ // Both nonce and peerId are inside the digest: the nonce makes a recorded hello
16
+ // useless to another client, and the peerId stops a peer from re-serving a
17
+ // signature it obtained from a different node.
18
+ import { randomBytes } from 'node:crypto';
19
+ import { keccak256, toBytes } from 'viem';
20
+ import { walletAddress } from "./chain.js";
21
+ import { recoverSigner, signDigest } from "./receipts.js";
22
+ const HELLO_TAG = 'genkaku.hello.v0:';
23
+ /** Fresh challenge for one handshake (never reused, never stored). */
24
+ export function newNonce() {
25
+ return `0x${randomBytes(32).toString('hex')}`;
26
+ }
27
+ /** The exact bytes both sides sign — one definition, no drift. */
28
+ export function helloDigest(peerId, wallet, nonce) {
29
+ return toBytes(keccak256(toBytes(`${HELLO_TAG}${peerId}:${wallet.toLowerCase()}:${nonce.toLowerCase()}`)));
30
+ }
31
+ /** Worker side: prove possession of the wallet key for this PeerId. */
32
+ export async function signHello(key, peerId, nonce) {
33
+ const wallet = walletAddress(key);
34
+ const sig = await signDigest(key, helloDigest(peerId, wallet, nonce));
35
+ return { peerId, wallet, sig };
36
+ }
37
+ /** Client side: does this proof really come from `wallet` over `nonce`? */
38
+ export async function verifyHello(proof, nonce) {
39
+ const signer = await recoverSigner(helloDigest(proof.peerId, proof.wallet, nonce), proof.sig);
40
+ return signer != null && signer.toLowerCase() === proof.wallet.toLowerCase();
41
+ }
42
+ //# sourceMappingURL=hello.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * GenKaKu protocol core.
3
+ *
4
+ * Everything here is harness-independent: contracts, escrow accounting, the
5
+ * libp2p transport, the Ollama supervisor and the OpenAI-compatible bridge.
6
+ * The DeepSeek Harness adapters live in @genkaku/dsh-runtime and the two
7
+ * plugin packages above it.
8
+ */
9
+ export * from './bridge.ts';
10
+ export * from './chain.ts';
11
+ export * from './credit.ts';
12
+ export * from './dial.ts';
13
+ export * from './directory.ts';
14
+ export * from './discovery.ts';
15
+ export * from './hello.ts';
16
+ export * from './keys.ts';
17
+ export * from './llmBridge.ts';
18
+ export * from './lp.ts';
19
+ export * from './modeManager.ts';
20
+ export * from './netconfig.ts';
21
+ export * from './offer.ts';
22
+ export * from './ollama.ts';
23
+ export * from './p2pNode.ts';
24
+ export * from './paywall.ts';
25
+ export * from './publishOptIn.ts';
26
+ export * from './receipts.ts';
27
+ export * from './registry.ts';
28
+ export * from './rpc.ts';
29
+ export * from './status.ts';
30
+ export * from './userSession.ts';
31
+ export * from './workerSession.ts';
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * GenKaKu protocol core.
3
+ *
4
+ * Everything here is harness-independent: contracts, escrow accounting, the
5
+ * libp2p transport, the Ollama supervisor and the OpenAI-compatible bridge.
6
+ * The DeepSeek Harness adapters live in @genkaku/dsh-runtime and the two
7
+ * plugin packages above it.
8
+ */
9
+ export * from "./bridge.js";
10
+ export * from "./chain.js";
11
+ export * from "./credit.js";
12
+ export * from "./dial.js";
13
+ export * from "./directory.js";
14
+ export * from "./discovery.js";
15
+ export * from "./hello.js";
16
+ export * from "./keys.js";
17
+ export * from "./llmBridge.js";
18
+ export * from "./lp.js";
19
+ export * from "./modeManager.js";
20
+ export * from "./netconfig.js";
21
+ export * from "./offer.js";
22
+ export * from "./ollama.js";
23
+ export * from "./p2pNode.js";
24
+ export * from "./paywall.js";
25
+ export * from "./publishOptIn.js";
26
+ export * from "./receipts.js";
27
+ export * from "./registry.js";
28
+ export * from "./rpc.js";
29
+ export * from "./status.js";
30
+ export * from "./userSession.js";
31
+ export * from "./workerSession.js";
32
+ //# sourceMappingURL=index.js.map
package/dist/keys.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { Hex } from 'viem';
2
+ export type PrivKey = Hex;
3
+ export type EthAddress = `0x${string}`;
4
+ /** safeStorage-shaped codec: electron main wires { encryptString, ... };
5
+ * tests/dev can pass a plaintext pass-through (files still skip keys repo). */
6
+ export type KeyCodec = {
7
+ encrypt(plain: string): Buffer;
8
+ decrypt(blob: Buffer): string;
9
+ };
10
+ export type KeyVault = {
11
+ /** the app's user identity key — generated on first use */
12
+ userKey(): PrivKey;
13
+ /** the app's worker identity key — generated on first use */
14
+ workerKey(): PrivKey;
15
+ /** derived addresses only (renderer-safe mirror of the keys) */
16
+ addresses(): {
17
+ user: EthAddress;
18
+ worker: EthAddress;
19
+ };
20
+ /** true when ANY key came from env overrides (dev-only telemetry) */
21
+ readonly usingEnvOverrides: boolean;
22
+ };
23
+ export declare function createKeyVault(opts: {
24
+ /** directory for the encrypted key file (app userData in main process) */
25
+ dir: string;
26
+ codec?: KeyCodec;
27
+ }): KeyVault;
28
+ /** Accepts 0x-prefixed or bare 32-byte hex; returns lowercase Hex or throws. */
29
+ export declare function normalizeKey(input: string): PrivKey;
30
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAG/B,MAAM,MAAM,OAAO,GAAG,GAAG,CAAA;AACzB,MAAM,MAAM,UAAU,GAAG,KAAK,MAAM,EAAE,CAAA;AAEtC;gFACgF;AAChF,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAC9B,CAAA;AASD,MAAM,MAAM,QAAQ,GAAG;IACrB,2DAA2D;IAC3D,OAAO,IAAI,OAAO,CAAA;IAClB,6DAA6D;IAC7D,SAAS,IAAI,OAAO,CAAA;IACpB,gEAAgE;IAChE,SAAS,IAAI;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,CAAA;IACrD,qEAAqE;IACrE,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAA;CACpC,CAAA;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,QAAQ,CAAA;CACjB,GAAG,QAAQ,CA2GX;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQnD"}
package/dist/keys.js ADDED
@@ -0,0 +1,126 @@
1
+ // GENKAKU: wallet key vault (P4.4.al) — Electron main process ONLY.
2
+ // Key material: one EVM keypair as the app's USER identity (receipt signing,
3
+ // escrow open + settle) and one as the WORKER identity (sidecar co-sign,
4
+ // claim). Encrypted by an injectable safeStorage-compatible codec and stored
5
+ // under the userData dir; dev/test overrides come from GENKAKU_USER_KEY /
6
+ // GENKAKU_WORKER_KEY env (documented DEV-ONLY).
7
+ //
8
+ // NO mainnet paths: consumers are testnet-locked at the chain layer (chain.ts
9
+ // refuses non-base-sepolia writes). Keys NEVER leave the main process — the
10
+ // renderer receives only the derived ADDRESSES.
11
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
14
+ const PLAINTEXT_CODEC = {
15
+ encrypt: plain => Buffer.from(plain, 'utf8'),
16
+ decrypt: blob => blob.toString('utf8'),
17
+ };
18
+ export function createKeyVault(opts) {
19
+ const codec = opts.codec ?? PLAINTEXT_CODEC;
20
+ const file = join(opts.dir, 'genkaku.keys.json');
21
+ mkdirSync(opts.dir, { recursive: true });
22
+ let vault = { version: 1 };
23
+ try {
24
+ const raw = JSON.parse(readFileSync(file, 'utf8'));
25
+ if (raw?.version === 1) {
26
+ vault = raw;
27
+ }
28
+ }
29
+ catch {
30
+ // first run / unreadable → fresh vault below
31
+ }
32
+ // DEV override (P4.4.al): env keys win when present. Deliberately loud.
33
+ const envUser = envKey('GENKAKU_USER_KEY');
34
+ const envWorker = envKey('GENKAKU_WORKER_KEY');
35
+ if (envUser != null || envWorker != null) {
36
+ console.warn('[genkaku:keys] DEV-ONLY env key override active '
37
+ + `(user:${envUser != null ? 'set' : '-'} worker:${envWorker != null ? 'set' : '-'}) — never ship this path`);
38
+ }
39
+ let userKey = envUser;
40
+ let workerKey = envWorker;
41
+ const decode = (enc) => {
42
+ if (enc == null) {
43
+ return null;
44
+ }
45
+ try {
46
+ return normalizeKey(codec.decrypt(Buffer.from(enc, 'base64')));
47
+ }
48
+ catch (error) {
49
+ console.error('[genkaku:keys] decrypt failed', error instanceof Error ? error.message : String(error));
50
+ return null;
51
+ }
52
+ };
53
+ userKey ??= decode(vault.userKeyEnc);
54
+ workerKey ??= decode(vault.workerKeyEnc);
55
+ const persist = () => {
56
+ let writeable = true;
57
+ try {
58
+ writeFileSync(file, JSON.stringify(vault), { mode: 0o600 });
59
+ }
60
+ catch (error) {
61
+ writeable = false;
62
+ console.error('[genkaku:keys] vault write failed', error instanceof Error ? error.message : String(error));
63
+ }
64
+ if (!writeable) {
65
+ throw new Error('[genkaku:keys] cannot persist key vault');
66
+ }
67
+ };
68
+ const lock = (name) => {
69
+ const current = userKey != null && name === 'user' ? userKey : workerKey != null && name === 'worker' ? workerKey : null;
70
+ if (current != null) {
71
+ return current;
72
+ }
73
+ const generated = normalizeKey(generatePrivateKey());
74
+ // keys from env overrides are intentionally NOT re-persisted — they stay
75
+ // env-scoped for the process lifetime.
76
+ if (name === 'user') {
77
+ userKey = generated;
78
+ if (envUser != null) {
79
+ return generated;
80
+ }
81
+ vault.userKeyEnc = codec.encrypt(generated).toString('base64');
82
+ }
83
+ else {
84
+ workerKey = generated;
85
+ if (envWorker != null) {
86
+ return generated;
87
+ }
88
+ vault.workerKeyEnc = codec.encrypt(generated).toString('base64');
89
+ }
90
+ persist();
91
+ return generated;
92
+ };
93
+ return {
94
+ userKey: () => lock('user'),
95
+ workerKey: () => lock('worker'),
96
+ addresses: () => ({
97
+ user: privateKeyToAccount(lock('user')).address,
98
+ worker: privateKeyToAccount(lock('worker')).address,
99
+ }),
100
+ get usingEnvOverrides() {
101
+ return envUser != null || envWorker != null;
102
+ },
103
+ };
104
+ }
105
+ /** Accepts 0x-prefixed or bare 32-byte hex; returns lowercase Hex or throws. */
106
+ export function normalizeKey(input) {
107
+ const bare = input.trim().toLowerCase().replace(/^0x/, '');
108
+ if (!/^[0-9a-f]{64}$/.test(bare)) {
109
+ throw new Error('[genkaku:keys] invalid private key material (expected 32-byte hex)');
110
+ }
111
+ return `0x${bare}`;
112
+ }
113
+ function envKey(name) {
114
+ const value = process.env[name];
115
+ if (value == null || value === '') {
116
+ return null;
117
+ }
118
+ try {
119
+ return normalizeKey(value);
120
+ }
121
+ catch (error) {
122
+ console.error(`[genkaku:keys] ${name} malformed`, error instanceof Error ? error.message : String(error));
123
+ return null;
124
+ }
125
+ }
126
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1,100 @@
1
+ import { type ModelProfile, type OllamaModelFacts } from './capabilities.ts';
2
+ export type CompletionRequest = {
3
+ model: string;
4
+ prompt: string;
5
+ system?: string;
6
+ maxTokens?: number;
7
+ };
8
+ export type LocalCompletionSource = {
9
+ /** stream tokens; resolves when the source is exhausted */
10
+ stream(req: CompletionRequest, onDelta: (text: string) => void, signal?: AbortSignal): Promise<{
11
+ promptTokens: number;
12
+ completionTokens: number;
13
+ model: string;
14
+ }>;
15
+ };
16
+ export type ModelInfo = {
17
+ name: string;
18
+ sizeBytes?: number | undefined;
19
+ family?: string | undefined;
20
+ quantization?: string | undefined;
21
+ };
22
+ export type PullProgress = {
23
+ status: string;
24
+ percent?: number | undefined;
25
+ };
26
+ export declare function ollamaAdmin(host?: string): {
27
+ /** locally installed models (what the worker may choose to publish) */
28
+ list(): Promise<ModelInfo[]>;
29
+ /** download a model from the Ollama registry (registry name like "llama3:8b") */
30
+ pull(model: string, onProgress?: (p: PullProgress) => void, signal?: AbortSignal): Promise<{
31
+ status: string;
32
+ }>;
33
+ };
34
+ /**
35
+ * Ask Ollama what a model can do.
36
+ *
37
+ * `/api/show` is the authoritative answer for capabilities; the parameter size
38
+ * lives in `/api/tags` under `details.parameter_size`. Both are needed for the
39
+ * tier, so callers usually want {@link probeModels}.
40
+ *
41
+ * @param model - model name as Ollama knows it.
42
+ * @param host - Ollama base URL.
43
+ */
44
+ export declare function probeModel(model: string, host?: string): Promise<OllamaModelFacts>;
45
+ /**
46
+ * Profile every model Ollama has locally: capabilities from `/api/show`,
47
+ * size from `/api/tags`.
48
+ *
49
+ * A model whose probe fails is reported with no capabilities, which lands it in
50
+ * the `chat` tier. That is the safe direction: an unreadable model is never
51
+ * advertised as agent-capable.
52
+ *
53
+ * @param host - Ollama base URL.
54
+ */
55
+ export declare function probeModels(host?: string): Promise<ModelProfile[]>;
56
+ export declare function createOllamaSource(host?: string): LocalCompletionSource;
57
+ /** One chat turn as the network carries it. */
58
+ export type ChatMessage = {
59
+ role: 'system' | 'user' | 'assistant' | 'tool';
60
+ content: string;
61
+ /** assistant turns only: calls the model already made */
62
+ tool_calls?: unknown[];
63
+ /** tool turns only: which call this answers */
64
+ tool_call_id?: string;
65
+ };
66
+ /** Incremental output of one chat completion. */
67
+ export type ChatEvent = {
68
+ type: 'text';
69
+ text: string;
70
+ } | {
71
+ type: 'tool-call';
72
+ index: number;
73
+ id?: string;
74
+ name?: string;
75
+ argumentsDelta: string;
76
+ };
77
+ /** A worker-side source that can serve a chat with tool schemas. */
78
+ export type LocalChatSource = {
79
+ streamChat(req: {
80
+ model: string;
81
+ messages: ChatMessage[];
82
+ tools?: unknown[];
83
+ maxTokens?: number;
84
+ }, onEvent: (event: ChatEvent) => void, signal?: AbortSignal): Promise<{
85
+ completionTokens: number;
86
+ model: string;
87
+ }>;
88
+ };
89
+ /**
90
+ * Serve a chat with tools through Ollama's `/api/chat`.
91
+ *
92
+ * The legacy `/api/generate` path takes a single prompt string and therefore
93
+ * cannot carry tool schemas or tool results; this source exists so a worker can
94
+ * serve an agent, not only a chat box.
95
+ *
96
+ * Ollama reports tool calls as complete objects rather than deltas, so each one
97
+ * is emitted as a single `tool-call` event with the full arguments string.
98
+ */
99
+ export declare function createOllamaChatSource(host?: string): LocalChatSource;
100
+ //# sourceMappingURL=llmBridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llmBridge.d.ts","sourceRoot":"","sources":["../src/llmBridge.ts"],"names":[],"mappings":"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"}