@cogcoin/client 1.0.2 → 1.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 (76) hide show
  1. package/README.md +3 -2
  2. package/dist/bitcoind/client/factory.d.ts +0 -8
  3. package/dist/bitcoind/client/factory.js +1 -59
  4. package/dist/bitcoind/client/managed-client.d.ts +1 -3
  5. package/dist/bitcoind/client/managed-client.js +3 -47
  6. package/dist/bitcoind/indexer-daemon-main.js +173 -28
  7. package/dist/bitcoind/indexer-daemon.d.ts +11 -3
  8. package/dist/bitcoind/indexer-daemon.js +123 -57
  9. package/dist/bitcoind/indexer-monitor.d.ts +12 -0
  10. package/dist/bitcoind/indexer-monitor.js +89 -0
  11. package/dist/bitcoind/progress/follow-scene.d.ts +7 -1
  12. package/dist/bitcoind/progress/follow-scene.js +87 -4
  13. package/dist/bitcoind/progress/tty-renderer.d.ts +2 -0
  14. package/dist/bitcoind/progress/tty-renderer.js +2 -0
  15. package/dist/bitcoind/testing.d.ts +0 -1
  16. package/dist/bitcoind/testing.js +0 -1
  17. package/dist/bitcoind/types.d.ts +5 -2
  18. package/dist/cli/commands/follow.js +44 -49
  19. package/dist/cli/commands/mining-admin.js +56 -2
  20. package/dist/cli/commands/mining-read.js +43 -3
  21. package/dist/cli/commands/mining-runtime.js +91 -73
  22. package/dist/cli/commands/service-runtime.js +42 -2
  23. package/dist/cli/commands/status.js +3 -1
  24. package/dist/cli/commands/sync.js +50 -90
  25. package/dist/cli/commands/wallet-admin.js +21 -3
  26. package/dist/cli/commands/wallet-read.js +2 -0
  27. package/dist/cli/context.js +5 -1
  28. package/dist/cli/managed-indexer-observer.d.ts +33 -0
  29. package/dist/cli/managed-indexer-observer.js +163 -0
  30. package/dist/cli/mining-format.d.ts +3 -1
  31. package/dist/cli/mining-format.js +35 -0
  32. package/dist/cli/mining-json.d.ts +11 -1
  33. package/dist/cli/mining-json.js +9 -0
  34. package/dist/cli/output.js +24 -0
  35. package/dist/cli/parse.d.ts +1 -1
  36. package/dist/cli/parse.js +23 -0
  37. package/dist/cli/read-json.d.ts +13 -1
  38. package/dist/cli/read-json.js +31 -0
  39. package/dist/cli/runner.js +4 -2
  40. package/dist/cli/signals.d.ts +12 -0
  41. package/dist/cli/signals.js +31 -13
  42. package/dist/cli/types.d.ts +8 -4
  43. package/dist/cli/update-service.d.ts +2 -12
  44. package/dist/cli/update-service.js +2 -68
  45. package/dist/semver.d.ts +12 -0
  46. package/dist/semver.js +68 -0
  47. package/dist/wallet/lifecycle.js +0 -6
  48. package/dist/wallet/mining/config.js +54 -3
  49. package/dist/wallet/mining/control.d.ts +5 -2
  50. package/dist/wallet/mining/control.js +153 -34
  51. package/dist/wallet/mining/domain-prompts.d.ts +17 -0
  52. package/dist/wallet/mining/domain-prompts.js +130 -0
  53. package/dist/wallet/mining/index.d.ts +2 -1
  54. package/dist/wallet/mining/index.js +1 -0
  55. package/dist/wallet/mining/runner.d.ts +58 -2
  56. package/dist/wallet/mining/runner.js +553 -331
  57. package/dist/wallet/mining/sentence-protocol.d.ts +1 -0
  58. package/dist/wallet/mining/sentences.js +7 -4
  59. package/dist/wallet/mining/types.d.ts +26 -0
  60. package/dist/wallet/mining/visualizer.d.ts +3 -0
  61. package/dist/wallet/mining/visualizer.js +106 -12
  62. package/dist/wallet/read/context.d.ts +1 -0
  63. package/dist/wallet/read/context.js +15 -10
  64. package/dist/wallet/reset.js +0 -1
  65. package/dist/wallet/state/client-password-agent.js +4 -1
  66. package/dist/wallet/state/client-password.js +15 -8
  67. package/dist/wallet/tx/anchor.js +0 -1
  68. package/dist/wallet/tx/bitcoin-transfer.js +0 -1
  69. package/dist/wallet/tx/cog.js +0 -3
  70. package/dist/wallet/tx/common.js +1 -1
  71. package/dist/wallet/tx/domain-admin.js +0 -1
  72. package/dist/wallet/tx/domain-market.js +0 -3
  73. package/dist/wallet/tx/field.js +0 -1
  74. package/dist/wallet/tx/register.js +0 -1
  75. package/dist/wallet/tx/reputation.js +0 -1
  76. package/package.json +1 -1
@@ -2,20 +2,66 @@ import { readFile } from "node:fs/promises";
2
2
  import { writeJsonFileAtomic } from "../fs/atomic.js";
3
3
  import { decryptJsonWithSecretProvider, encryptJsonWithSecretProvider } from "../state/crypto.js";
4
4
  import { normalizeMiningProviderConfigRecord } from "./provider-model.js";
5
+ const MINING_PROVIDER_KINDS = ["openai", "anthropic"];
6
+ function normalizeDomainExtraPrompts(raw) {
7
+ if (raw === null || typeof raw !== "object") {
8
+ return {};
9
+ }
10
+ const normalized = new Map();
11
+ for (const [rawDomainName, rawPrompt] of Object.entries(raw)) {
12
+ if (typeof rawPrompt !== "string") {
13
+ continue;
14
+ }
15
+ const domainName = rawDomainName.trim().toLowerCase();
16
+ const prompt = rawPrompt.trim();
17
+ if (domainName.length === 0 || prompt.length === 0) {
18
+ continue;
19
+ }
20
+ normalized.set(domainName, prompt);
21
+ }
22
+ return Object.fromEntries([...normalized.entries()].sort(([left], [right]) => left.localeCompare(right)));
23
+ }
5
24
  function createEmptyClientConfig() {
6
25
  return {
7
26
  schemaVersion: 1,
8
27
  mining: {
9
28
  builtIn: null,
29
+ domainExtraPrompts: {},
10
30
  },
11
31
  };
12
32
  }
33
+ function normalizeBuiltInProviderConfigMap(raw) {
34
+ if (raw === null || typeof raw !== "object") {
35
+ return {};
36
+ }
37
+ const normalized = {};
38
+ const entries = raw;
39
+ for (const provider of MINING_PROVIDER_KINDS) {
40
+ const config = entries[provider];
41
+ if (config == null) {
42
+ continue;
43
+ }
44
+ normalized[provider] = normalizeMiningProviderConfigRecord({
45
+ ...config,
46
+ provider,
47
+ });
48
+ }
49
+ return normalized;
50
+ }
13
51
  function normalizeClientConfig(config) {
52
+ const mining = config.mining ?? createEmptyClientConfig().mining;
53
+ const builtIn = mining.builtIn === null ? null : normalizeMiningProviderConfigRecord(mining.builtIn);
54
+ const builtInByProvider = normalizeBuiltInProviderConfigMap(mining.builtInByProvider);
55
+ if (builtIn !== null) {
56
+ builtInByProvider[builtIn.provider] = builtIn;
57
+ }
14
58
  return {
15
59
  ...config,
16
60
  mining: {
17
- ...config.mining,
18
- builtIn: config.mining.builtIn === null ? null : normalizeMiningProviderConfigRecord(config.mining.builtIn),
61
+ ...mining,
62
+ builtIn,
63
+ ...(Object.keys(builtInByProvider).length === 0 ? {} : { builtInByProvider }),
64
+ domainExtraPrompts: normalizeDomainExtraPrompts(mining.domainExtraPrompts),
19
65
  },
20
66
  };
21
67
  }
@@ -43,7 +89,12 @@ export async function saveBuiltInMiningProviderConfig(options) {
43
89
  provider: options.provider,
44
90
  }).catch(() => null);
45
91
  const nextConfig = existing ?? createEmptyClientConfig();
46
- nextConfig.mining.builtIn = normalizeMiningProviderConfigRecord(options.config);
92
+ const normalizedConfig = normalizeMiningProviderConfigRecord(options.config);
93
+ nextConfig.mining.builtIn = normalizedConfig;
94
+ nextConfig.mining.builtInByProvider = {
95
+ ...(nextConfig.mining.builtInByProvider ?? {}),
96
+ [normalizedConfig.provider]: normalizedConfig,
97
+ };
47
98
  await saveClientConfig({
48
99
  path: options.path,
49
100
  provider: options.provider,
@@ -2,7 +2,7 @@ import type { WalletPrompter } from "../lifecycle.js";
2
2
  import { type WalletRuntimePaths } from "../runtime.js";
3
3
  import { type WalletSecretProvider } from "../state/provider.js";
4
4
  import type { WalletBitcoindStatus, WalletIndexerStatus, WalletLocalStateStatus, WalletNodeStatus } from "../read/types.js";
5
- import type { MiningControlPlaneView, MiningEventRecord, MiningProviderConfigRecord, MiningRuntimeStatusV1 } from "./types.js";
5
+ import type { MiningControlPlaneView, MiningEventRecord, MiningProviderConfigByProvider, MiningProviderConfigRecord, MiningRuntimeStatusV1 } from "./types.js";
6
6
  export declare function inspectMiningControlPlane(options: {
7
7
  provider?: WalletSecretProvider;
8
8
  localState: WalletLocalStateStatus;
@@ -23,7 +23,10 @@ export declare function refreshMiningRuntimeStatus(options: {
23
23
  nowUnixMs?: number;
24
24
  paths?: WalletRuntimePaths;
25
25
  }): Promise<MiningControlPlaneView>;
26
- export declare function promptForMiningProviderConfigForTesting(prompter: WalletPrompter, eligibleRootCount: number): Promise<MiningProviderConfigRecord>;
26
+ export declare function promptForMiningProviderConfigForTesting(prompter: WalletPrompter, eligibleRootCount: number, options?: {
27
+ currentConfig?: MiningProviderConfigRecord | null;
28
+ rememberedConfigs?: MiningProviderConfigByProvider;
29
+ }): Promise<MiningProviderConfigRecord>;
27
30
  export declare function setupBuiltInMining(options: {
28
31
  provider?: WalletSecretProvider;
29
32
  prompter: WalletPrompter;
@@ -9,6 +9,7 @@ import { normalizeMiningPublishState, normalizeMiningStateRecord } from "./state
9
9
  import { loadClientConfig, saveBuiltInMiningProviderConfig } from "./config.js";
10
10
  import { MINING_WORKER_API_VERSION, MINING_WORKER_HEARTBEAT_STALE_MS, } from "./constants.js";
11
11
  import { estimateBuiltInModelDailyCost, getBuiltInProviderModelCatalog, getRecommendedBuiltInProviderModel, MINING_MODEL_DAILY_COST_ESTIMATE_ASSUMPTION, resolveBuiltInProviderSelection, } from "./provider-model.js";
12
+ const KEEP_CURRENT_MODEL_SELECTION = "__keep_current__";
12
13
  function createMiningEvent(kind, message, options = {}) {
13
14
  return {
14
15
  schemaVersion: 1,
@@ -414,59 +415,170 @@ async function promptForMiningProviderModelSelectionFallback(prompter, options)
414
415
  prompter.writeLine(`Enter a number from 1 to ${options.options.length}, or q to cancel.`);
415
416
  }
416
417
  }
417
- async function promptForMiningProviderConfig(prompter, eligibleRootCount) {
418
+ function formatMiningProviderDisplayName(provider) {
419
+ return provider === "openai" ? "OpenAI" : "Anthropic";
420
+ }
421
+ async function promptMiningSetupYesNo(prompter, message, defaultAnswer) {
422
+ const prompt = `${message}${defaultAnswer ? " [Y/n]: " : " [y/N]: "}`;
423
+ while (true) {
424
+ const answer = (await prompter.prompt(prompt)).trim().toLowerCase();
425
+ if (answer.length === 0) {
426
+ return defaultAnswer;
427
+ }
428
+ if (answer === "y" || answer === "yes") {
429
+ return true;
430
+ }
431
+ if (answer === "n" || answer === "no") {
432
+ return false;
433
+ }
434
+ if (answer === "q" || answer === "quit" || answer === "esc" || answer === "escape") {
435
+ throw new Error("mining_setup_canceled");
436
+ }
437
+ prompter.writeLine("Enter y or n, or q to cancel.");
438
+ }
439
+ }
440
+ function buildMiningProviderModelSelectorOptions(provider, eligibleRootCount, currentConfig) {
441
+ const catalogOptions = getBuiltInProviderModelCatalog(provider).map((entry) => {
442
+ const estimate = estimateBuiltInModelDailyCost(provider, entry.modelId, eligibleRootCount);
443
+ return {
444
+ label: entry.label,
445
+ description: `${entry.modelId} - ${estimate?.estimatedDailyCostDisplay ?? "n/a"}`,
446
+ value: entry.modelId,
447
+ };
448
+ });
449
+ const selection = currentConfig === null ? null : resolveBuiltInProviderSelection(currentConfig);
450
+ const options = [...catalogOptions];
451
+ let initialValue = getRecommendedBuiltInProviderModel(provider);
452
+ if (selection !== null) {
453
+ if (selection.modelSelectionSource === "custom" || selection.modelSelectionSource === "legacy-custom") {
454
+ initialValue = "custom";
455
+ }
456
+ else if (catalogOptions.some((entry) => entry.value === selection.modelId)) {
457
+ initialValue = selection.modelId;
458
+ }
459
+ else {
460
+ options.push({
461
+ label: "Current configured model",
462
+ description: `${selection.modelId} - current saved setting`,
463
+ value: KEEP_CURRENT_MODEL_SELECTION,
464
+ });
465
+ initialValue = KEEP_CURRENT_MODEL_SELECTION;
466
+ }
467
+ }
468
+ options.push({
469
+ label: "Custom model ID...",
470
+ description: "",
471
+ value: "custom",
472
+ });
473
+ return {
474
+ initialValue,
475
+ options,
476
+ };
477
+ }
478
+ async function promptForMiningProviderConfig(prompter, eligibleRootCount, options = {}) {
418
479
  writeBuiltInMiningProviderDisclosure(prompter);
419
- const providerInput = await prompter.prompt("Provider (openai/anthropic): ");
420
- const provider = normalizeProviderChoice(providerInput);
421
- if (provider === null) {
422
- throw new Error("mining_setup_invalid_provider");
480
+ const currentConfig = options.currentConfig ?? null;
481
+ const rememberedConfigs = options.rememberedConfigs ?? {};
482
+ let provider;
483
+ let rememberedConfig = currentConfig;
484
+ let reuseSavedApiKey = false;
485
+ if (currentConfig !== null) {
486
+ const useDifferentProviderOrApiKey = await promptMiningSetupYesNo(prompter, "Use a different provider or API key?", false);
487
+ if (!useDifferentProviderOrApiKey) {
488
+ provider = currentConfig.provider;
489
+ reuseSavedApiKey = true;
490
+ }
491
+ else {
492
+ const providerInput = await prompter.prompt("Provider (openai/anthropic): ");
493
+ const selectedProvider = normalizeProviderChoice(providerInput);
494
+ if (selectedProvider === null) {
495
+ throw new Error("mining_setup_invalid_provider");
496
+ }
497
+ provider = selectedProvider;
498
+ rememberedConfig = rememberedConfigs[provider] ?? null;
499
+ if (rememberedConfig !== null) {
500
+ reuseSavedApiKey = await promptMiningSetupYesNo(prompter, `Use saved ${formatMiningProviderDisplayName(provider)} API key?`, true);
501
+ }
502
+ }
503
+ }
504
+ else {
505
+ const providerInput = await prompter.prompt("Provider (openai/anthropic): ");
506
+ const selectedProvider = normalizeProviderChoice(providerInput);
507
+ if (selectedProvider === null) {
508
+ throw new Error("mining_setup_invalid_provider");
509
+ }
510
+ provider = selectedProvider;
511
+ rememberedConfig = rememberedConfigs[provider] ?? null;
512
+ if (rememberedConfig !== null) {
513
+ reuseSavedApiKey = await promptMiningSetupYesNo(prompter, `Use saved ${formatMiningProviderDisplayName(provider)} API key?`, true);
514
+ }
423
515
  }
516
+ const selectorModelOptions = buildMiningProviderModelSelectorOptions(provider, eligibleRootCount, rememberedConfig);
424
517
  const selectorOptions = {
425
518
  message: "Choose the mining model:",
426
- options: [
427
- ...getBuiltInProviderModelCatalog(provider).map((entry) => {
428
- const estimate = estimateBuiltInModelDailyCost(provider, entry.modelId, eligibleRootCount);
429
- return {
430
- label: entry.label,
431
- description: `${entry.modelId} - ${estimate?.estimatedDailyCostDisplay ?? "n/a"}`,
432
- value: entry.modelId,
433
- };
434
- }),
435
- {
436
- label: "Custom model ID...",
437
- description: null,
438
- value: "custom",
439
- },
440
- ],
441
- initialValue: getRecommendedBuiltInProviderModel(provider),
519
+ options: selectorModelOptions.options,
520
+ initialValue: selectorModelOptions.initialValue,
442
521
  footer: MINING_MODEL_DAILY_COST_ESTIMATE_ASSUMPTION,
443
522
  };
444
523
  const selectedModelId = prompter.selectOption == null
445
524
  ? await promptForMiningProviderModelSelectionFallback(prompter, selectorOptions)
446
525
  : await prompter.selectOption(selectorOptions);
447
- const modelSelectionSource = selectedModelId === "custom" ? "custom" : "catalog";
448
- const modelOverride = selectedModelId === "custom"
449
- ? (await prompter.prompt("Custom model ID: ")).trim()
450
- : selectedModelId;
451
- if (modelOverride.length === 0) {
452
- throw new Error("mining_setup_missing_model_id");
453
- }
454
- const apiKey = (await prompter.prompt("API key: ")).trim();
526
+ let modelSelectionSource;
527
+ let modelOverride;
528
+ if (selectedModelId === KEEP_CURRENT_MODEL_SELECTION) {
529
+ if (rememberedConfig === null) {
530
+ throw new Error("mining_setup_missing_model_id");
531
+ }
532
+ modelSelectionSource = rememberedConfig.modelSelectionSource;
533
+ modelOverride = rememberedConfig.modelOverride;
534
+ }
535
+ else if (selectedModelId === "custom") {
536
+ const currentCustomModel = rememberedConfig !== null
537
+ && (rememberedConfig.modelSelectionSource === "custom" || rememberedConfig.modelSelectionSource === "legacy-custom")
538
+ ? rememberedConfig.modelOverride
539
+ : null;
540
+ const customModelId = (await prompter.prompt(currentCustomModel === null
541
+ ? "Custom model ID: "
542
+ : `Custom model ID (blank to keep current: ${currentCustomModel}): `)).trim();
543
+ if (customModelId.length === 0) {
544
+ if (currentCustomModel === null) {
545
+ throw new Error("mining_setup_missing_model_id");
546
+ }
547
+ modelSelectionSource = rememberedConfig?.modelSelectionSource ?? "custom";
548
+ modelOverride = currentCustomModel;
549
+ }
550
+ else {
551
+ modelSelectionSource = "custom";
552
+ modelOverride = customModelId;
553
+ }
554
+ }
555
+ else {
556
+ modelSelectionSource = "catalog";
557
+ modelOverride = selectedModelId;
558
+ }
559
+ const apiKey = reuseSavedApiKey && rememberedConfig !== null
560
+ ? rememberedConfig.apiKey
561
+ : (await prompter.prompt("API key: ")).trim();
455
562
  if (apiKey.length === 0) {
456
563
  throw new Error("mining_setup_missing_api_key");
457
564
  }
458
- const extraPrompt = (await prompter.prompt("Extra prompt (optional, blank for none): ")).trim();
565
+ const extraPromptInput = (await prompter.prompt(rememberedConfig === null
566
+ ? "Extra prompt (optional, blank for none): "
567
+ : `Extra prompt (optional, blank to keep current: ${rememberedConfig.extraPrompt ?? "none"}): `)).trim();
568
+ const extraPrompt = extraPromptInput.length === 0
569
+ ? rememberedConfig?.extraPrompt ?? null
570
+ : extraPromptInput;
459
571
  return {
460
572
  provider,
461
573
  apiKey,
462
- extraPrompt: extraPrompt.length === 0 ? null : extraPrompt,
574
+ extraPrompt: extraPrompt === null || extraPrompt.length === 0 ? null : extraPrompt,
463
575
  modelOverride,
464
576
  modelSelectionSource,
465
577
  updatedAtUnixMs: Date.now(),
466
578
  };
467
579
  }
468
- export async function promptForMiningProviderConfigForTesting(prompter, eligibleRootCount) {
469
- return await promptForMiningProviderConfig(prompter, eligibleRootCount);
580
+ export async function promptForMiningProviderConfigForTesting(prompter, eligibleRootCount, options = {}) {
581
+ return await promptForMiningProviderConfig(prompter, eligibleRootCount, options);
470
582
  }
471
583
  export async function setupBuiltInMining(options) {
472
584
  if (!options.prompter.isInteractive) {
@@ -502,9 +614,16 @@ export async function setupBuiltInMining(options) {
502
614
  message: null,
503
615
  };
504
616
  const eligibleRootCount = countEligibleAnchoredRoots(localState) ?? 0;
617
+ const clientConfig = await loadClientConfig({
618
+ path: paths.clientConfigPath,
619
+ provider,
620
+ }).catch(() => null);
505
621
  await appendMiningEvent(paths.miningEventsPath, createMiningEvent("mine-setup-started", "Started built-in mining provider setup.", { timestampUnixMs: nowUnixMs }));
506
622
  try {
507
- const config = await promptForMiningProviderConfig(options.prompter, eligibleRootCount);
623
+ const config = await promptForMiningProviderConfig(options.prompter, eligibleRootCount, {
624
+ currentConfig: clientConfig?.mining.builtIn ?? null,
625
+ rememberedConfigs: clientConfig?.mining.builtInByProvider ?? {},
626
+ });
508
627
  config.updatedAtUnixMs = nowUnixMs;
509
628
  await saveBuiltInMiningProviderConfig({
510
629
  path: paths.clientConfigPath,
@@ -0,0 +1,17 @@
1
+ import type { WalletReadContext } from "../read/index.js";
2
+ import { type WalletSecretProvider } from "../state/provider.js";
3
+ import type { WalletRuntimePaths } from "../runtime.js";
4
+ import type { MiningDomainPromptListResult, MiningDomainPromptMutationResult } from "./types.js";
5
+ export declare function canonicalizeMiningDomainPromptName(domainName: string): string;
6
+ export declare function inspectMiningDomainPromptState(options: {
7
+ paths: WalletRuntimePaths;
8
+ provider: WalletSecretProvider;
9
+ readContext: WalletReadContext;
10
+ }): Promise<MiningDomainPromptListResult>;
11
+ export declare function updateMiningDomainPrompt(options: {
12
+ paths: WalletRuntimePaths;
13
+ provider: WalletSecretProvider;
14
+ readContext: WalletReadContext;
15
+ domainName: string;
16
+ prompt: string | null;
17
+ }): Promise<MiningDomainPromptMutationResult>;
@@ -0,0 +1,130 @@
1
+ import { findWalletDomain, isMineableWalletDomain } from "../read/index.js";
2
+ import { createWalletSecretReference } from "../state/provider.js";
3
+ import { loadClientConfig, saveClientConfig } from "./config.js";
4
+ function createEmptyClientConfig() {
5
+ return {
6
+ schemaVersion: 1,
7
+ mining: {
8
+ builtIn: null,
9
+ domainExtraPrompts: {},
10
+ },
11
+ };
12
+ }
13
+ export function canonicalizeMiningDomainPromptName(domainName) {
14
+ return domainName.trim().toLowerCase();
15
+ }
16
+ function fallbackPromptConfigured(config) {
17
+ const extraPrompt = config?.mining.builtIn?.extraPrompt ?? null;
18
+ return extraPrompt !== null && extraPrompt.length > 0;
19
+ }
20
+ function listMineableDomains(readContext) {
21
+ const domains = new Map();
22
+ for (const domain of readContext.model?.domains ?? []) {
23
+ if (!isMineableWalletDomain(readContext, domain)) {
24
+ continue;
25
+ }
26
+ domains.set(canonicalizeMiningDomainPromptName(domain.name), {
27
+ name: domain.name,
28
+ domainId: domain.domainId,
29
+ });
30
+ }
31
+ return domains;
32
+ }
33
+ function buildPromptEntries(options) {
34
+ const entries = new Map();
35
+ const mineableDomains = listMineableDomains(options.readContext);
36
+ for (const domain of mineableDomains.values()) {
37
+ const canonicalDomainName = canonicalizeMiningDomainPromptName(domain.name);
38
+ const prompt = options.domainExtraPrompts[canonicalDomainName] ?? null;
39
+ entries.set(canonicalDomainName, {
40
+ domain,
41
+ mineable: true,
42
+ prompt,
43
+ effectivePromptSource: prompt !== null
44
+ ? "domain"
45
+ : options.fallbackPromptConfigured
46
+ ? "global-fallback"
47
+ : "none",
48
+ });
49
+ }
50
+ for (const [domainName, prompt] of Object.entries(options.domainExtraPrompts)) {
51
+ if (entries.has(domainName)) {
52
+ continue;
53
+ }
54
+ const found = findWalletDomain(options.readContext, domainName);
55
+ entries.set(domainName, {
56
+ domain: {
57
+ name: domainName,
58
+ domainId: found?.domain.domainId ?? null,
59
+ },
60
+ mineable: false,
61
+ prompt,
62
+ effectivePromptSource: "domain",
63
+ });
64
+ }
65
+ return [...entries.values()].sort((left, right) => left.domain.name.localeCompare(right.domain.name));
66
+ }
67
+ function isMineableTarget(readContext, domainName) {
68
+ const domain = readContext.model?.domains.find((entry) => canonicalizeMiningDomainPromptName(entry.name) === domainName);
69
+ return domain === undefined ? false : isMineableWalletDomain(readContext, domain);
70
+ }
71
+ export async function inspectMiningDomainPromptState(options) {
72
+ const config = await loadClientConfig({
73
+ path: options.paths.clientConfigPath,
74
+ provider: options.provider,
75
+ });
76
+ return {
77
+ fallbackPromptConfigured: fallbackPromptConfigured(config),
78
+ prompts: buildPromptEntries({
79
+ readContext: options.readContext,
80
+ domainExtraPrompts: config?.mining.domainExtraPrompts ?? {},
81
+ fallbackPromptConfigured: fallbackPromptConfigured(config),
82
+ }),
83
+ };
84
+ }
85
+ export async function updateMiningDomainPrompt(options) {
86
+ const canonicalDomainName = canonicalizeMiningDomainPromptName(options.domainName);
87
+ const config = await loadClientConfig({
88
+ path: options.paths.clientConfigPath,
89
+ provider: options.provider,
90
+ });
91
+ const currentPrompts = {
92
+ ...(config?.mining.domainExtraPrompts ?? {}),
93
+ };
94
+ const existingPrompt = currentPrompts[canonicalDomainName] ?? null;
95
+ const mineable = isMineableTarget(options.readContext, canonicalDomainName);
96
+ const found = findWalletDomain(options.readContext, canonicalDomainName);
97
+ const nextPrompt = options.prompt === null || options.prompt.trim().length === 0
98
+ ? null
99
+ : options.prompt.trim();
100
+ if (!mineable && existingPrompt === null) {
101
+ throw new Error("mine_prompt_domain_not_mineable");
102
+ }
103
+ if (options.readContext.localState.walletRootId === null) {
104
+ throw new Error("wallet_uninitialized");
105
+ }
106
+ if (nextPrompt === null) {
107
+ delete currentPrompts[canonicalDomainName];
108
+ }
109
+ else {
110
+ currentPrompts[canonicalDomainName] = nextPrompt;
111
+ }
112
+ const nextConfig = config ?? createEmptyClientConfig();
113
+ nextConfig.mining.domainExtraPrompts = currentPrompts;
114
+ await saveClientConfig({
115
+ path: options.paths.clientConfigPath,
116
+ provider: options.provider,
117
+ secretReference: createWalletSecretReference(options.readContext.localState.walletRootId),
118
+ config: nextConfig,
119
+ });
120
+ return {
121
+ domain: {
122
+ name: canonicalDomainName,
123
+ domainId: found?.domain.domainId ?? null,
124
+ },
125
+ previousPrompt: existingPrompt,
126
+ prompt: nextPrompt,
127
+ status: nextPrompt === null ? "cleared" : "updated",
128
+ fallbackPromptConfigured: fallbackPromptConfigured(config),
129
+ };
130
+ }
@@ -1,7 +1,8 @@
1
1
  export { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, readMiningGenerationActivity, readMiningPreemptionRequest, requestMiningGenerationPreemption, } from "./coordination.js";
2
+ export { canonicalizeMiningDomainPromptName, inspectMiningDomainPromptState, updateMiningDomainPrompt, } from "./domain-prompts.js";
2
3
  export { followMiningLog, inspectMiningControlPlane, readMiningLog, refreshMiningRuntimeStatus, setupBuiltInMining, } from "./control.js";
3
4
  export { ensureBuiltInMiningSetupIfNeeded, runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, type MiningStartResult, } from "./runner.js";
4
5
  export { appendMiningEvent, loadMiningRuntimeStatus, readMiningEvents, resolveRotatedMiningEventsPath, saveMiningRuntimeStatus, } from "./runtime-artifacts.js";
5
6
  export type { MiningSentenceCandidateV1, MiningSentenceGenerationRequestV1, MiningSentenceGenerationResponseV1, } from "./sentence-protocol.js";
6
7
  export { loadClientConfig, saveBuiltInMiningProviderConfig, saveClientConfig, } from "./config.js";
7
- export type { ClientConfigV1, MiningControlPlaneView, MiningEventRecord, MiningModelSelectionSource, MiningProviderConfigRecord, MiningProviderInspection, MiningRuntimeStatusV1, MiningServiceHealth, } from "./types.js";
8
+ export type { ClientConfigV1, MiningControlPlaneView, MiningDomainPromptEntry, MiningDomainPromptListResult, MiningDomainPromptMutationResult, MiningEventRecord, MiningModelSelectionSource, MiningProviderConfigRecord, MiningProviderInspection, MiningRuntimeStatusV1, MiningServiceHealth, } from "./types.js";
@@ -1,4 +1,5 @@
1
1
  export { isMiningGenerationAbortRequested, markMiningGenerationActive, markMiningGenerationInactive, readMiningGenerationActivity, readMiningPreemptionRequest, requestMiningGenerationPreemption, } from "./coordination.js";
2
+ export { canonicalizeMiningDomainPromptName, inspectMiningDomainPromptState, updateMiningDomainPrompt, } from "./domain-prompts.js";
2
3
  export { followMiningLog, inspectMiningControlPlane, readMiningLog, refreshMiningRuntimeStatus, setupBuiltInMining, } from "./control.js";
3
4
  export { ensureBuiltInMiningSetupIfNeeded, runBackgroundMiningWorker, runForegroundMining, startBackgroundMining, stopBackgroundMining, } from "./runner.js";
4
5
  export { appendMiningEvent, loadMiningRuntimeStatus, readMiningEvents, resolveRotatedMiningEventsPath, saveMiningRuntimeStatus, } from "./runtime-artifacts.js";
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
2
+ import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopManagedBitcoindService } from "../../bitcoind/service.js";
3
3
  import { createRpcClient } from "../../bitcoind/node.js";
4
4
  import type { ProgressOutputMode } from "../../bitcoind/types.js";
5
5
  import { type FixedWalletInput, type MutationSender, type WalletMutationRpcClient } from "../tx/common.js";
@@ -10,6 +10,7 @@ import { type WalletRuntimePaths } from "../runtime.js";
10
10
  import { type WalletSecretProvider } from "../state/provider.js";
11
11
  import type { MiningStateRecord, OutpointRecord, WalletStateV1 } from "../types.js";
12
12
  import { requestMiningGenerationPreemption } from "./coordination.js";
13
+ import { type MiningSentenceGenerationRequest } from "./sentences.js";
13
14
  import type { MiningControlPlaneView, MiningEventRecord, MiningRuntimeStatusV1 } from "./types.js";
14
15
  import { type MiningFollowVisualizerState, type MiningSentenceBoardEntry, MiningFollowVisualizer } from "./visualizer.js";
15
16
  type MiningRpcClient = WalletMutationRpcClient & {
@@ -94,6 +95,8 @@ interface MiningRunnerStatusOverrides {
94
95
  currentAbsoluteFeeSats?: number | null;
95
96
  currentBlockFeeSpentSats?: string;
96
97
  lastSuspendDetectedAtUnixMs?: number | null;
98
+ reconnectSettledUntilUnixMs?: number | null;
99
+ tipSettledUntilUnixMs?: number | null;
97
100
  providerState?: MiningRuntimeStatusV1["providerState"];
98
101
  corePublishState?: MiningRuntimeStatusV1["corePublishState"];
99
102
  currentPublishDecision?: string | null;
@@ -175,10 +178,19 @@ interface MiningLoopState {
175
178
  selectedCandidate: MiningCandidate | null;
176
179
  ui: MiningFollowVisualizerState;
177
180
  waitingNote: string | null;
181
+ bitcoinRecoveryFirstFailureAtUnixMs: number | null;
182
+ bitcoinRecoveryFirstUnreachableAtUnixMs: number | null;
183
+ bitcoinRecoveryLastRestartAttemptAtUnixMs: number | null;
184
+ bitcoinRecoveryServiceInstanceId: string | null;
185
+ bitcoinRecoveryProcessId: number | null;
186
+ reconnectSettledUntilUnixMs: number | null;
187
+ tipSettledUntilUnixMs: number | null;
178
188
  }
179
189
  export interface RunForegroundMiningOptions extends RunnerDependencies {
180
190
  dataDir: string;
181
191
  databasePath: string;
192
+ clientVersion?: string | null;
193
+ updateAvailable?: boolean;
182
194
  provider?: WalletSecretProvider;
183
195
  prompter: WalletPrompter;
184
196
  builtInSetupEnsured?: boolean;
@@ -224,6 +236,7 @@ export declare function resetMiningUiForTipForTesting(loopState: MiningLoopState
224
236
  export declare function resolveSettledBoardForTesting(options: {
225
237
  snapshotState: NonNullable<WalletReadContext["snapshot"]>["state"] | null | undefined;
226
238
  snapshotTipHeight: number | null;
239
+ snapshotTipPreviousHashHex?: string | null;
227
240
  nodeBestHeight: number | null;
228
241
  }): {
229
242
  settledBlockHeight: number | null;
@@ -273,7 +286,7 @@ export declare function createMiningPlanForTesting(options: {
273
286
  referencedBlockHashInternal: Uint8Array;
274
287
  targetBlockHeight: number;
275
288
  };
276
- conflictOutpoint: OutpointRecord;
289
+ conflictOutpoint: OutpointRecord | null;
277
290
  allUtxos: Awaited<ReturnType<MiningRpcClient["listUnspent"]>>;
278
291
  feeRateSatVb: number;
279
292
  }): {
@@ -294,6 +307,19 @@ export declare function resolveMiningConflictOutpointForTesting(options: {
294
307
  state: WalletStateV1;
295
308
  allUtxos: Awaited<ReturnType<MiningRpcClient["listUnspent"]>>;
296
309
  }): OutpointRecord | null;
310
+ export declare function buildMiningGenerationRequestForTesting(options: {
311
+ targetBlockHeight: number;
312
+ referencedBlockHashDisplay: string;
313
+ generatedAtUnixMs?: number;
314
+ requestId?: string;
315
+ domains: Array<{
316
+ domainId: number;
317
+ domainName: string;
318
+ requiredWords: [string, string, string, string, string];
319
+ }>;
320
+ domainExtraPrompts?: Record<string, string>;
321
+ extraPrompt?: string | null;
322
+ }): MiningSentenceGenerationRequest;
297
323
  declare function publishCandidateOnce(options: {
298
324
  readContext: WalletReadContext & {
299
325
  localState: {
@@ -356,11 +382,15 @@ declare function runMiningLoop(options: {
356
382
  fetchImpl?: typeof fetch;
357
383
  openReadContext: typeof openWalletReadContext;
358
384
  attachService: typeof attachOrStartManagedBitcoindService;
385
+ probeService?: typeof probeManagedBitcoindService;
386
+ stopService?: typeof stopManagedBitcoindService;
359
387
  rpcFactory: (config: Parameters<typeof createRpcClient>[0]) => MiningRpcClient;
360
388
  stdout?: {
361
389
  write(chunk: string): void;
362
390
  };
363
391
  visualizer?: MiningFollowVisualizer;
392
+ nowImpl?: () => number;
393
+ sleepImpl?: typeof sleep;
364
394
  }): Promise<void>;
365
395
  declare function waitForBackgroundHealthy(paths: WalletRuntimePaths): Promise<MiningRuntimeStatusV1 | null>;
366
396
  export declare function runForegroundMining(options: RunForegroundMiningOptions): Promise<void>;
@@ -384,6 +414,7 @@ export declare function handleDetectedMiningRuntimeResumeForTesting(options: {
384
414
  detectedAtUnixMs: number;
385
415
  openReadContext: typeof openWalletReadContext;
386
416
  visualizer?: MiningFollowVisualizer;
417
+ loopState?: MiningLoopState;
387
418
  }): Promise<void>;
388
419
  export declare function takeOverMiningRuntimeForTesting(options: {
389
420
  paths: WalletRuntimePaths;
@@ -406,11 +437,36 @@ export declare function performMiningCycleForTesting(options: {
406
437
  fetchImpl?: typeof fetch;
407
438
  openReadContext: typeof openWalletReadContext;
408
439
  attachService: typeof attachOrStartManagedBitcoindService;
440
+ probeService?: typeof probeManagedBitcoindService;
441
+ stopService?: typeof stopManagedBitcoindService;
409
442
  rpcFactory: (config: Parameters<typeof createRpcClient>[0]) => MiningRpcClient;
410
443
  stdout?: {
411
444
  write(chunk: string): void;
412
445
  };
413
446
  loopState?: MiningLoopState;
447
+ nowImpl?: () => number;
448
+ }): Promise<void>;
449
+ export declare function runMiningLoopForTesting(options: {
450
+ dataDir: string;
451
+ databasePath: string;
452
+ provider: WalletSecretProvider;
453
+ paths: WalletRuntimePaths;
454
+ runMode: "foreground" | "background";
455
+ backgroundWorkerPid: number | null;
456
+ backgroundWorkerRunId: string | null;
457
+ signal?: AbortSignal;
458
+ fetchImpl?: typeof fetch;
459
+ openReadContext: typeof openWalletReadContext;
460
+ attachService: typeof attachOrStartManagedBitcoindService;
461
+ probeService?: typeof probeManagedBitcoindService;
462
+ stopService?: typeof stopManagedBitcoindService;
463
+ rpcFactory: (config: Parameters<typeof createRpcClient>[0]) => MiningRpcClient;
464
+ stdout?: {
465
+ write(chunk: string): void;
466
+ };
467
+ visualizer?: MiningFollowVisualizer;
468
+ nowImpl?: () => number;
469
+ sleepImpl?: typeof sleep;
414
470
  }): Promise<void>;
415
471
  export declare function buildPrePublishStatusOverridesForTesting(options: {
416
472
  state: WalletStateV1;