@cogcoin/client 1.0.1 → 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 (79) hide show
  1. package/README.md +4 -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 +14 -3
  8. package/dist/bitcoind/indexer-daemon.js +145 -29
  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/retryable-rpc.js +3 -0
  16. package/dist/bitcoind/service.d.ts +1 -0
  17. package/dist/bitcoind/service.js +31 -9
  18. package/dist/bitcoind/testing.d.ts +0 -1
  19. package/dist/bitcoind/testing.js +0 -1
  20. package/dist/bitcoind/types.d.ts +5 -2
  21. package/dist/cli/commands/follow.js +44 -49
  22. package/dist/cli/commands/mining-admin.js +65 -2
  23. package/dist/cli/commands/mining-read.js +43 -3
  24. package/dist/cli/commands/mining-runtime.js +91 -73
  25. package/dist/cli/commands/service-runtime.js +42 -2
  26. package/dist/cli/commands/status.js +3 -1
  27. package/dist/cli/commands/sync.js +50 -90
  28. package/dist/cli/commands/update.d.ts +2 -0
  29. package/dist/cli/commands/update.js +101 -0
  30. package/dist/cli/commands/wallet-admin.js +21 -3
  31. package/dist/cli/commands/wallet-read.js +2 -0
  32. package/dist/cli/context.js +36 -1
  33. package/dist/cli/managed-indexer-observer.d.ts +33 -0
  34. package/dist/cli/managed-indexer-observer.js +163 -0
  35. package/dist/cli/mining-format.d.ts +3 -1
  36. package/dist/cli/mining-format.js +63 -0
  37. package/dist/cli/mining-json.d.ts +11 -1
  38. package/dist/cli/mining-json.js +15 -0
  39. package/dist/cli/output.js +74 -2
  40. package/dist/cli/parse.d.ts +1 -1
  41. package/dist/cli/parse.js +28 -0
  42. package/dist/cli/prompt.js +109 -0
  43. package/dist/cli/read-json.d.ts +26 -1
  44. package/dist/cli/read-json.js +48 -0
  45. package/dist/cli/runner.js +8 -2
  46. package/dist/cli/signals.d.ts +12 -0
  47. package/dist/cli/signals.js +31 -13
  48. package/dist/cli/types.d.ts +13 -4
  49. package/dist/cli/update-notifier.js +7 -222
  50. package/dist/cli/update-service.d.ts +34 -0
  51. package/dist/cli/update-service.js +152 -0
  52. package/dist/client/initialization.js +5 -0
  53. package/dist/semver.d.ts +12 -0
  54. package/dist/semver.js +68 -0
  55. package/dist/wallet/lifecycle.d.ts +10 -0
  56. package/dist/wallet/mining/config.js +64 -3
  57. package/dist/wallet/mining/control.d.ts +5 -1
  58. package/dist/wallet/mining/control.js +269 -26
  59. package/dist/wallet/mining/domain-prompts.d.ts +17 -0
  60. package/dist/wallet/mining/domain-prompts.js +130 -0
  61. package/dist/wallet/mining/index.d.ts +2 -1
  62. package/dist/wallet/mining/index.js +1 -0
  63. package/dist/wallet/mining/provider-model.d.ts +30 -0
  64. package/dist/wallet/mining/provider-model.js +134 -0
  65. package/dist/wallet/mining/runner.d.ts +156 -5
  66. package/dist/wallet/mining/runner.js +1019 -399
  67. package/dist/wallet/mining/runtime-artifacts.js +1 -0
  68. package/dist/wallet/mining/sentence-protocol.d.ts +1 -0
  69. package/dist/wallet/mining/sentences.d.ts +2 -2
  70. package/dist/wallet/mining/sentences.js +32 -6
  71. package/dist/wallet/mining/types.d.ts +35 -1
  72. package/dist/wallet/mining/visualizer.d.ts +3 -0
  73. package/dist/wallet/mining/visualizer.js +132 -15
  74. package/dist/wallet/read/context.d.ts +1 -0
  75. package/dist/wallet/read/context.js +15 -7
  76. package/dist/wallet/state/client-password-agent.js +4 -1
  77. package/dist/wallet/state/client-password.js +15 -8
  78. package/dist/wallet/tx/common.js +1 -1
  79. package/package.json +3 -2
@@ -2,11 +2,14 @@ import { acquireFileLock } from "../fs/lock.js";
2
2
  import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
3
3
  import { createDefaultWalletSecretProvider, createWalletSecretReference, } from "../state/provider.js";
4
4
  import { loadWalletState } from "../state/storage.js";
5
+ import { isRootDomainName } from "../read/filter.js";
5
6
  import { appendMiningEvent, getLastMiningEventTimestamp, loadMiningRuntimeStatus, readMiningEvents, saveMiningRuntimeStatus, followMiningEvents, } from "./runtime-artifacts.js";
6
7
  import { requestMiningGenerationPreemption } from "./coordination.js";
7
8
  import { normalizeMiningPublishState, normalizeMiningStateRecord } from "./state.js";
8
9
  import { loadClientConfig, saveBuiltInMiningProviderConfig } from "./config.js";
9
10
  import { MINING_WORKER_API_VERSION, MINING_WORKER_HEARTBEAT_STALE_MS, } from "./constants.js";
11
+ import { estimateBuiltInModelDailyCost, getBuiltInProviderModelCatalog, getRecommendedBuiltInProviderModel, MINING_MODEL_DAILY_COST_ESTIMATE_ASSUMPTION, resolveBuiltInProviderSelection, } from "./provider-model.js";
12
+ const KEEP_CURRENT_MODEL_SELECTION = "__keep_current__";
10
13
  function createMiningEvent(kind, message, options = {}) {
11
14
  return {
12
15
  schemaVersion: 1,
@@ -23,8 +26,14 @@ function buildProviderInspection(options) {
23
26
  provider: null,
24
27
  status: "error",
25
28
  message: options.error,
29
+ modelId: null,
30
+ effectiveModel: null,
26
31
  modelOverride: null,
32
+ modelSelectionSource: null,
33
+ usingDefaultModel: null,
27
34
  extraPromptConfigured: false,
35
+ estimatedDailyCostUsd: null,
36
+ estimatedDailyCostDisplay: null,
28
37
  };
29
38
  }
30
39
  if (options.config === null) {
@@ -33,19 +42,50 @@ function buildProviderInspection(options) {
33
42
  provider: null,
34
43
  status: "missing",
35
44
  message: "Built-in mining provider is not configured yet.",
45
+ modelId: null,
46
+ effectiveModel: null,
36
47
  modelOverride: null,
48
+ modelSelectionSource: null,
49
+ usingDefaultModel: null,
37
50
  extraPromptConfigured: false,
51
+ estimatedDailyCostUsd: null,
52
+ estimatedDailyCostDisplay: null,
38
53
  };
39
54
  }
55
+ const selection = resolveBuiltInProviderSelection(options.config);
56
+ const estimate = options.eligibleRootCount === null
57
+ ? null
58
+ : estimateBuiltInModelDailyCost(options.config.provider, selection.modelId, options.eligibleRootCount);
40
59
  return {
41
60
  configured: true,
42
61
  provider: options.config.provider,
43
62
  status: "ready",
44
63
  message: null,
64
+ modelId: selection.modelId,
65
+ effectiveModel: selection.effectiveModel,
45
66
  modelOverride: options.config.modelOverride,
67
+ modelSelectionSource: selection.modelSelectionSource,
68
+ usingDefaultModel: selection.usingDefaultModel,
46
69
  extraPromptConfigured: options.config.extraPrompt !== null && options.config.extraPrompt.length > 0,
70
+ estimatedDailyCostUsd: estimate?.estimatedDailyCostUsd ?? null,
71
+ estimatedDailyCostDisplay: estimate?.estimatedDailyCostDisplay ?? null,
47
72
  };
48
73
  }
74
+ function countEligibleAnchoredRoots(localState) {
75
+ const state = localState.state;
76
+ if (state === null || state === undefined) {
77
+ return null;
78
+ }
79
+ let count = 0;
80
+ for (const domain of state.domains) {
81
+ if (isRootDomainName(domain.name)
82
+ && domain.canonicalChainStatus === "anchored"
83
+ && domain.currentOwnerScriptPubKeyHex === state.funding.scriptPubKeyHex) {
84
+ count += 1;
85
+ }
86
+ }
87
+ return count;
88
+ }
49
89
  async function isProcessAlive(pid) {
50
90
  if (pid === null) {
51
91
  return false;
@@ -61,10 +101,13 @@ async function isProcessAlive(pid) {
61
101
  return true;
62
102
  }
63
103
  }
64
- function mapProviderState(provider, localState) {
104
+ function mapProviderState(provider, localState, existingRuntime) {
65
105
  const miningState = localState.state?.miningState === undefined
66
106
  ? null
67
107
  : normalizeMiningStateRecord(localState.state.miningState);
108
+ if (existingRuntime?.currentPhase === "waiting-provider" && existingRuntime.providerState !== null) {
109
+ return existingRuntime.providerState;
110
+ }
68
111
  if (miningState?.state === "paused" && miningState.pauseReason?.includes("rate-limit")) {
69
112
  return "rate-limited";
70
113
  }
@@ -167,7 +210,7 @@ async function buildMiningRuntimeSnapshot(options) {
167
210
  localState: options.localState,
168
211
  nowUnixMs: options.nowUnixMs,
169
212
  });
170
- const providerState = mapProviderState(options.provider, options.localState);
213
+ const providerState = mapProviderState(options.provider, options.localState, options.existingRuntime);
171
214
  const indexerDaemonState = mapIndexerDaemonState(options.indexer);
172
215
  const corePublishState = mapCorePublishState(options.nodeHealth, options.nodeStatus);
173
216
  const existing = options.existingRuntime;
@@ -289,7 +332,10 @@ export async function inspectMiningControlPlane(options) {
289
332
  paths,
290
333
  provider,
291
334
  });
292
- const providerInspection = buildProviderInspection(providerConfig);
335
+ const providerInspection = buildProviderInspection({
336
+ ...providerConfig,
337
+ eligibleRootCount: countEligibleAnchoredRoots(options.localState),
338
+ });
293
339
  const existingRuntime = await loadMiningRuntimeStatus(paths.miningStatusPath).catch(() => null);
294
340
  const lastEventAtUnixMs = await getLastMiningEventTimestamp(paths.miningEventsPath).catch(() => null);
295
341
  const nodeBestHeight = options.nodeStatus?.nodeBestHeight ?? null;
@@ -323,6 +369,20 @@ function normalizeProviderChoice(raw) {
323
369
  const value = raw.trim().toLowerCase();
324
370
  return value === "openai" || value === "anthropic" ? value : null;
325
371
  }
372
+ function describeModelSelectionSource(source) {
373
+ switch (source) {
374
+ case "catalog":
375
+ return "catalog";
376
+ case "custom":
377
+ return "custom";
378
+ case "legacy-default":
379
+ return "legacy-default";
380
+ case "legacy-custom":
381
+ return "legacy-custom";
382
+ default:
383
+ throw new Error(`unsupported_model_selection_source:${String(source)}`);
384
+ }
385
+ }
326
386
  function writeBuiltInMiningProviderDisclosure(prompter) {
327
387
  prompter.writeLine("Built-in mining provider disclosure:");
328
388
  prompter.writeLine("The built-in mining provider will send the following to the selected provider:");
@@ -332,27 +392,194 @@ function writeBuiltInMiningProviderDisclosure(prompter) {
332
392
  prompter.writeLine("- referenced previous-block hash");
333
393
  prompter.writeLine("- optional extra prompt when configured");
334
394
  }
335
- async function promptForMiningProviderConfig(prompter) {
395
+ async function promptForMiningProviderModelSelectionFallback(prompter, options) {
396
+ prompter.writeLine(options.message);
397
+ for (const [index, option] of options.options.entries()) {
398
+ const description = option.description == null || option.description.length === 0
399
+ ? ""
400
+ : ` - ${option.description}`;
401
+ prompter.writeLine(`${index + 1}. ${option.label}${description}`);
402
+ }
403
+ if (options.footer != null && options.footer.length > 0) {
404
+ prompter.writeLine(options.footer);
405
+ }
406
+ while (true) {
407
+ const answer = (await prompter.prompt(`Choice [1-${options.options.length}]: `)).trim();
408
+ if (/^(q|quit|esc|escape)$/i.test(answer)) {
409
+ throw new Error("mining_setup_canceled");
410
+ }
411
+ const choice = Number.parseInt(answer, 10);
412
+ if (Number.isInteger(choice) && choice >= 1 && choice <= options.options.length) {
413
+ return options.options[choice - 1].value;
414
+ }
415
+ prompter.writeLine(`Enter a number from 1 to ${options.options.length}, or q to cancel.`);
416
+ }
417
+ }
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 = {}) {
336
479
  writeBuiltInMiningProviderDisclosure(prompter);
337
- const providerInput = await prompter.prompt("Provider (openai/anthropic): ");
338
- const provider = normalizeProviderChoice(providerInput);
339
- if (provider === null) {
340
- 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
+ }
515
+ }
516
+ const selectorModelOptions = buildMiningProviderModelSelectorOptions(provider, eligibleRootCount, rememberedConfig);
517
+ const selectorOptions = {
518
+ message: "Choose the mining model:",
519
+ options: selectorModelOptions.options,
520
+ initialValue: selectorModelOptions.initialValue,
521
+ footer: MINING_MODEL_DAILY_COST_ESTIMATE_ASSUMPTION,
522
+ };
523
+ const selectedModelId = prompter.selectOption == null
524
+ ? await promptForMiningProviderModelSelectionFallback(prompter, selectorOptions)
525
+ : await prompter.selectOption(selectorOptions);
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
+ }
341
554
  }
342
- const apiKey = (await prompter.prompt("API key: ")).trim();
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();
343
562
  if (apiKey.length === 0) {
344
563
  throw new Error("mining_setup_missing_api_key");
345
564
  }
346
- const extraPrompt = (await prompter.prompt("Extra prompt (optional, blank for none): ")).trim();
347
- const modelOverride = (await prompter.prompt("Model override (optional, blank for default): ")).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;
348
571
  return {
349
572
  provider,
350
573
  apiKey,
351
- extraPrompt: extraPrompt.length === 0 ? null : extraPrompt,
352
- modelOverride: modelOverride.length === 0 ? null : modelOverride,
574
+ extraPrompt: extraPrompt === null || extraPrompt.length === 0 ? null : extraPrompt,
575
+ modelOverride,
576
+ modelSelectionSource,
353
577
  updatedAtUnixMs: Date.now(),
354
578
  };
355
579
  }
580
+ export async function promptForMiningProviderConfigForTesting(prompter, eligibleRootCount, options = {}) {
581
+ return await promptForMiningProviderConfig(prompter, eligibleRootCount, options);
582
+ }
356
583
  export async function setupBuiltInMining(options) {
357
584
  if (!options.prompter.isInteractive) {
358
585
  throw new Error("mine_setup_requires_tty");
@@ -375,9 +602,28 @@ export async function setupBuiltInMining(options) {
375
602
  }, {
376
603
  provider,
377
604
  });
605
+ const localState = {
606
+ availability: "ready",
607
+ clientPasswordReadiness: "ready",
608
+ unlockRequired: false,
609
+ walletRootId: loaded.state.walletRootId,
610
+ state: loaded.state,
611
+ source: loaded.source,
612
+ hasPrimaryStateFile: true,
613
+ hasBackupStateFile: true,
614
+ message: null,
615
+ };
616
+ const eligibleRootCount = countEligibleAnchoredRoots(localState) ?? 0;
617
+ const clientConfig = await loadClientConfig({
618
+ path: paths.clientConfigPath,
619
+ provider,
620
+ }).catch(() => null);
378
621
  await appendMiningEvent(paths.miningEventsPath, createMiningEvent("mine-setup-started", "Started built-in mining provider setup.", { timestampUnixMs: nowUnixMs }));
379
622
  try {
380
- const config = await promptForMiningProviderConfig(options.prompter);
623
+ const config = await promptForMiningProviderConfig(options.prompter, eligibleRootCount, {
624
+ currentConfig: clientConfig?.mining.builtIn ?? null,
625
+ rememberedConfigs: clientConfig?.mining.builtInByProvider ?? {},
626
+ });
381
627
  config.updatedAtUnixMs = nowUnixMs;
382
628
  await saveBuiltInMiningProviderConfig({
383
629
  path: paths.clientConfigPath,
@@ -385,20 +631,10 @@ export async function setupBuiltInMining(options) {
385
631
  secretReference: createWalletSecretReference(loaded.state.walletRootId),
386
632
  config,
387
633
  });
388
- await appendMiningEvent(paths.miningEventsPath, createMiningEvent("mine-setup-completed", `Configured the built-in ${config.provider} mining provider.`, { timestampUnixMs: nowUnixMs }));
634
+ await appendMiningEvent(paths.miningEventsPath, createMiningEvent("mine-setup-completed", `Configured the built-in ${config.provider} mining provider with model ${config.modelOverride} (${describeModelSelectionSource(config.modelSelectionSource)}).`, { timestampUnixMs: nowUnixMs }));
389
635
  return refreshMiningRuntimeStatus({
390
636
  provider,
391
- localState: {
392
- availability: "ready",
393
- clientPasswordReadiness: "ready",
394
- unlockRequired: false,
395
- walletRootId: loaded.state.walletRootId,
396
- state: loaded.state,
397
- source: loaded.source,
398
- hasPrimaryStateFile: true,
399
- hasBackupStateFile: true,
400
- message: null,
401
- },
637
+ localState,
402
638
  bitcoind: {
403
639
  health: "unavailable",
404
640
  status: null,
@@ -421,6 +657,13 @@ export async function setupBuiltInMining(options) {
421
657
  });
422
658
  }
423
659
  catch (error) {
660
+ if (error instanceof Error && error.message === "mining_setup_canceled") {
661
+ await appendMiningEvent(paths.miningEventsPath, createMiningEvent("mine-setup-canceled", "Canceled built-in mining provider setup.", {
662
+ level: "warn",
663
+ timestampUnixMs: nowUnixMs,
664
+ }));
665
+ throw error;
666
+ }
424
667
  await appendMiningEvent(paths.miningEventsPath, createMiningEvent("mine-setup-failed", error instanceof Error ? error.message : String(error), {
425
668
  level: "error",
426
669
  timestampUnixMs: nowUnixMs,
@@ -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, 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";
@@ -0,0 +1,30 @@
1
+ import type { MiningModelSelectionSource, MiningProviderConfigRecord, MiningProviderKind } from "./types.js";
2
+ export interface BuiltInProviderModelCatalogEntry {
3
+ label: string;
4
+ modelId: string;
5
+ inputUsdPerMillionTokens: number;
6
+ outputUsdPerMillionTokens: number;
7
+ }
8
+ export interface BuiltInProviderSelection {
9
+ modelId: string;
10
+ effectiveModel: string;
11
+ modelSelectionSource: MiningModelSelectionSource;
12
+ usingDefaultModel: boolean;
13
+ }
14
+ export interface BuiltInModelDailyCostEstimate {
15
+ estimatedDailyCostUsd: number;
16
+ estimatedDailyCostDisplay: string;
17
+ }
18
+ export declare const MINING_MODEL_DAILY_COST_ESTIMATE_ASSUMPTION = "Approximate daily cost assumes 144 sentence-generation calls/day using your current anchored root count, standard token pricing, no caching, and no extra prompt.";
19
+ export declare function getLegacyBuiltInProviderDefaultModel(provider: MiningProviderKind): string;
20
+ export declare function getRecommendedBuiltInProviderModel(provider: MiningProviderKind): string;
21
+ export declare function getBuiltInProviderModelCatalog(provider: MiningProviderKind): readonly BuiltInProviderModelCatalogEntry[];
22
+ export declare function findBuiltInProviderModelCatalogEntry(provider: MiningProviderKind, modelId: string): BuiltInProviderModelCatalogEntry | null;
23
+ export declare function normalizeMiningModelSelectionSource(raw: unknown, modelOverride: string | null): MiningModelSelectionSource;
24
+ export declare function normalizeMiningProviderConfigRecord(config: MiningProviderConfigRecord): MiningProviderConfigRecord;
25
+ export declare function resolveBuiltInProviderModel(provider: MiningProviderKind, modelOverride: string | null): {
26
+ effectiveModel: string;
27
+ usingDefaultModel: boolean;
28
+ };
29
+ export declare function resolveBuiltInProviderSelection(config: Pick<MiningProviderConfigRecord, "provider" | "modelOverride" | "modelSelectionSource">): BuiltInProviderSelection;
30
+ export declare function estimateBuiltInModelDailyCost(provider: MiningProviderKind, modelId: string, eligibleRootCount: number): BuiltInModelDailyCostEstimate | null;