@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.
- package/README.md +4 -2
- package/dist/bitcoind/client/factory.d.ts +0 -8
- package/dist/bitcoind/client/factory.js +1 -59
- package/dist/bitcoind/client/managed-client.d.ts +1 -3
- package/dist/bitcoind/client/managed-client.js +3 -47
- package/dist/bitcoind/indexer-daemon-main.js +173 -28
- package/dist/bitcoind/indexer-daemon.d.ts +14 -3
- package/dist/bitcoind/indexer-daemon.js +145 -29
- package/dist/bitcoind/indexer-monitor.d.ts +12 -0
- package/dist/bitcoind/indexer-monitor.js +89 -0
- package/dist/bitcoind/progress/follow-scene.d.ts +7 -1
- package/dist/bitcoind/progress/follow-scene.js +87 -4
- package/dist/bitcoind/progress/tty-renderer.d.ts +2 -0
- package/dist/bitcoind/progress/tty-renderer.js +2 -0
- package/dist/bitcoind/retryable-rpc.js +3 -0
- package/dist/bitcoind/service.d.ts +1 -0
- package/dist/bitcoind/service.js +31 -9
- package/dist/bitcoind/testing.d.ts +0 -1
- package/dist/bitcoind/testing.js +0 -1
- package/dist/bitcoind/types.d.ts +5 -2
- package/dist/cli/commands/follow.js +44 -49
- package/dist/cli/commands/mining-admin.js +65 -2
- package/dist/cli/commands/mining-read.js +43 -3
- package/dist/cli/commands/mining-runtime.js +91 -73
- package/dist/cli/commands/service-runtime.js +42 -2
- package/dist/cli/commands/status.js +3 -1
- package/dist/cli/commands/sync.js +50 -90
- package/dist/cli/commands/update.d.ts +2 -0
- package/dist/cli/commands/update.js +101 -0
- package/dist/cli/commands/wallet-admin.js +21 -3
- package/dist/cli/commands/wallet-read.js +2 -0
- package/dist/cli/context.js +36 -1
- package/dist/cli/managed-indexer-observer.d.ts +33 -0
- package/dist/cli/managed-indexer-observer.js +163 -0
- package/dist/cli/mining-format.d.ts +3 -1
- package/dist/cli/mining-format.js +63 -0
- package/dist/cli/mining-json.d.ts +11 -1
- package/dist/cli/mining-json.js +15 -0
- package/dist/cli/output.js +74 -2
- package/dist/cli/parse.d.ts +1 -1
- package/dist/cli/parse.js +28 -0
- package/dist/cli/prompt.js +109 -0
- package/dist/cli/read-json.d.ts +26 -1
- package/dist/cli/read-json.js +48 -0
- package/dist/cli/runner.js +8 -2
- package/dist/cli/signals.d.ts +12 -0
- package/dist/cli/signals.js +31 -13
- package/dist/cli/types.d.ts +13 -4
- package/dist/cli/update-notifier.js +7 -222
- package/dist/cli/update-service.d.ts +34 -0
- package/dist/cli/update-service.js +152 -0
- package/dist/client/initialization.js +5 -0
- package/dist/semver.d.ts +12 -0
- package/dist/semver.js +68 -0
- package/dist/wallet/lifecycle.d.ts +10 -0
- package/dist/wallet/mining/config.js +64 -3
- package/dist/wallet/mining/control.d.ts +5 -1
- package/dist/wallet/mining/control.js +269 -26
- package/dist/wallet/mining/domain-prompts.d.ts +17 -0
- package/dist/wallet/mining/domain-prompts.js +130 -0
- package/dist/wallet/mining/index.d.ts +2 -1
- package/dist/wallet/mining/index.js +1 -0
- package/dist/wallet/mining/provider-model.d.ts +30 -0
- package/dist/wallet/mining/provider-model.js +134 -0
- package/dist/wallet/mining/runner.d.ts +156 -5
- package/dist/wallet/mining/runner.js +1019 -399
- package/dist/wallet/mining/runtime-artifacts.js +1 -0
- package/dist/wallet/mining/sentence-protocol.d.ts +1 -0
- package/dist/wallet/mining/sentences.d.ts +2 -2
- package/dist/wallet/mining/sentences.js +32 -6
- package/dist/wallet/mining/types.d.ts +35 -1
- package/dist/wallet/mining/visualizer.d.ts +3 -0
- package/dist/wallet/mining/visualizer.js +132 -15
- package/dist/wallet/read/context.d.ts +1 -0
- package/dist/wallet/read/context.js +15 -7
- package/dist/wallet/state/client-password-agent.js +4 -1
- package/dist/wallet/state/client-password.js +15 -8
- package/dist/wallet/tx/common.js +1 -1
- package/package.json +3 -2
|
@@ -10,8 +10,8 @@ export interface MiningSentenceSourceOptions {
|
|
|
10
10
|
fetchImpl?: typeof fetch;
|
|
11
11
|
}
|
|
12
12
|
declare class MiningProviderRequestError extends Error {
|
|
13
|
-
readonly providerState: "unavailable" | "rate-limited" | "auth-error";
|
|
14
|
-
constructor(providerState: "unavailable" | "rate-limited" | "auth-error", message: string);
|
|
13
|
+
readonly providerState: "unavailable" | "rate-limited" | "auth-error" | "not-found";
|
|
14
|
+
constructor(providerState: "unavailable" | "rate-limited" | "auth-error" | "not-found", message: string);
|
|
15
15
|
}
|
|
16
16
|
export declare function generateMiningSentences(request: MiningSentenceGenerationRequest, options: MiningSentenceSourceOptions): Promise<{
|
|
17
17
|
candidates: MiningSentenceCandidateV1[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadClientConfig } from "./config.js";
|
|
2
2
|
import { MINING_BUILTIN_TIMEOUT_MS, } from "./constants.js";
|
|
3
3
|
import { normalizeMiningSentenceResponse, parseStrictJsonValue, stripMarkdownCodeFence, } from "./sentence-protocol.js";
|
|
4
|
+
import { resolveBuiltInProviderModel } from "./provider-model.js";
|
|
4
5
|
class MiningProviderRequestError extends Error {
|
|
5
6
|
providerState;
|
|
6
7
|
constructor(providerState, message) {
|
|
@@ -9,6 +10,14 @@ class MiningProviderRequestError extends Error {
|
|
|
9
10
|
this.providerState = providerState;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
13
|
+
function createBuiltInProviderNotFoundError(options) {
|
|
14
|
+
const providerName = options.provider === "anthropic" ? "Anthropic" : "OpenAI";
|
|
15
|
+
const providerLabel = `The built-in ${providerName} mining provider`;
|
|
16
|
+
const message = options.usingDefaultModel
|
|
17
|
+
? `${providerLabel} returned HTTP 404 for default model "${options.model}". ${providerName} may no longer serve that model. Rerun \`cogcoin mine setup\` to choose a valid override.`
|
|
18
|
+
: `${providerLabel} returned HTTP 404 for model "${options.model}". The configured model override may be invalid. Rerun \`cogcoin mine setup\` to clear or correct it.`;
|
|
19
|
+
return new MiningProviderRequestError("not-found", message);
|
|
20
|
+
}
|
|
12
21
|
function buildSystemPrompt(extraPrompt) {
|
|
13
22
|
const lines = [
|
|
14
23
|
"You are helping generate candidate Cogcoin mining sentences.",
|
|
@@ -16,9 +25,13 @@ function buildSystemPrompt(extraPrompt) {
|
|
|
16
25
|
"Every sentence must be a single natural-language sentence.",
|
|
17
26
|
"Do not add commentary, markdown, or code fences.",
|
|
18
27
|
"Do not invent domain IDs or request IDs.",
|
|
28
|
+
"Each rootDomains entry may include an extraPrompt that applies only to that domain.",
|
|
29
|
+
"If rootDomains[i].extraPrompt is present, use it only for candidates for that domainId.",
|
|
30
|
+
"If rootDomains[i].extraPrompt is null, fall back to the request-level extraPrompt when it is present.",
|
|
31
|
+
"Never apply one domain's prompt to another domain's candidates.",
|
|
19
32
|
];
|
|
20
33
|
if (extraPrompt !== null && extraPrompt.trim().length > 0) {
|
|
21
|
-
lines.push(`
|
|
34
|
+
lines.push(`Request-level fallback instruction: ${extraPrompt.trim()}`);
|
|
22
35
|
}
|
|
23
36
|
return lines.join("\n");
|
|
24
37
|
}
|
|
@@ -65,7 +78,7 @@ async function requestBuiltInSentences(options) {
|
|
|
65
78
|
const providerSignal = createProviderSignal(options.signal, Math.min(MINING_BUILTIN_TIMEOUT_MS, options.request.limits.timeoutMs));
|
|
66
79
|
try {
|
|
67
80
|
if (options.provider === "openai") {
|
|
68
|
-
const model = options.
|
|
81
|
+
const { effectiveModel: model, usingDefaultModel } = resolveBuiltInProviderModel(options.provider, options.modelOverride);
|
|
69
82
|
const response = await fetchImpl("https://api.openai.com/v1/responses", {
|
|
70
83
|
method: "POST",
|
|
71
84
|
headers: {
|
|
@@ -77,7 +90,7 @@ async function requestBuiltInSentences(options) {
|
|
|
77
90
|
input: [
|
|
78
91
|
{
|
|
79
92
|
role: "system",
|
|
80
|
-
content: buildSystemPrompt(options.extraPrompt),
|
|
93
|
+
content: buildSystemPrompt(options.request.extraPrompt),
|
|
81
94
|
},
|
|
82
95
|
{
|
|
83
96
|
role: "user",
|
|
@@ -93,6 +106,13 @@ async function requestBuiltInSentences(options) {
|
|
|
93
106
|
if (response.status === 429) {
|
|
94
107
|
throw new MiningProviderRequestError("rate-limited", "The built-in OpenAI mining provider is rate limited.");
|
|
95
108
|
}
|
|
109
|
+
if (response.status === 404) {
|
|
110
|
+
throw createBuiltInProviderNotFoundError({
|
|
111
|
+
provider: options.provider,
|
|
112
|
+
model,
|
|
113
|
+
usingDefaultModel,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
96
116
|
if (!response.ok) {
|
|
97
117
|
throw new MiningProviderRequestError("unavailable", `The built-in OpenAI mining provider returned HTTP ${response.status}.`);
|
|
98
118
|
}
|
|
@@ -106,7 +126,7 @@ async function requestBuiltInSentences(options) {
|
|
|
106
126
|
model,
|
|
107
127
|
});
|
|
108
128
|
}
|
|
109
|
-
const model = options.modelOverride
|
|
129
|
+
const { effectiveModel: model, usingDefaultModel } = resolveBuiltInProviderModel(options.provider, options.modelOverride);
|
|
110
130
|
const response = await fetchImpl("https://api.anthropic.com/v1/messages", {
|
|
111
131
|
method: "POST",
|
|
112
132
|
headers: {
|
|
@@ -117,7 +137,7 @@ async function requestBuiltInSentences(options) {
|
|
|
117
137
|
body: JSON.stringify({
|
|
118
138
|
model,
|
|
119
139
|
max_tokens: 1_200,
|
|
120
|
-
system: buildSystemPrompt(options.extraPrompt),
|
|
140
|
+
system: buildSystemPrompt(options.request.extraPrompt),
|
|
121
141
|
messages: [
|
|
122
142
|
{
|
|
123
143
|
role: "user",
|
|
@@ -133,6 +153,13 @@ async function requestBuiltInSentences(options) {
|
|
|
133
153
|
if (response.status === 429) {
|
|
134
154
|
throw new MiningProviderRequestError("rate-limited", "The built-in Anthropic mining provider is rate limited.");
|
|
135
155
|
}
|
|
156
|
+
if (response.status === 404) {
|
|
157
|
+
throw createBuiltInProviderNotFoundError({
|
|
158
|
+
provider: options.provider,
|
|
159
|
+
model,
|
|
160
|
+
usingDefaultModel,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
136
163
|
if (!response.ok) {
|
|
137
164
|
throw new MiningProviderRequestError("unavailable", `The built-in Anthropic mining provider returned HTTP ${response.status}.`);
|
|
138
165
|
}
|
|
@@ -222,7 +249,6 @@ export async function generateMiningSentences(request, options) {
|
|
|
222
249
|
provider: builtIn.provider,
|
|
223
250
|
apiKey: builtIn.apiKey,
|
|
224
251
|
modelOverride: builtIn.modelOverride,
|
|
225
|
-
extraPrompt: builtIn.extraPrompt ?? request.extraPrompt,
|
|
226
252
|
request,
|
|
227
253
|
fetchImpl: options.fetchImpl,
|
|
228
254
|
signal: options.signal,
|
|
@@ -1,19 +1,47 @@
|
|
|
1
1
|
import type { ManagedBitcoindHealth, ManagedIndexerTruthSource } from "../../bitcoind/types.js";
|
|
2
2
|
export type MiningServiceHealth = "synced" | "catching-up" | "reorging" | "starting" | "stale-heartbeat" | "failed" | "schema-mismatch" | "service-version-mismatch" | "wallet-root-mismatch" | "unavailable";
|
|
3
3
|
export type MiningProviderKind = "openai" | "anthropic";
|
|
4
|
+
export type MiningModelSelectionSource = "catalog" | "custom" | "legacy-default" | "legacy-custom";
|
|
4
5
|
export interface MiningProviderConfigRecord {
|
|
5
6
|
provider: MiningProviderKind;
|
|
6
7
|
apiKey: string;
|
|
7
8
|
extraPrompt: string | null;
|
|
8
9
|
modelOverride: string | null;
|
|
10
|
+
modelSelectionSource: MiningModelSelectionSource;
|
|
9
11
|
updatedAtUnixMs: number;
|
|
10
12
|
}
|
|
13
|
+
export type MiningProviderConfigByProvider = Partial<Record<MiningProviderKind, MiningProviderConfigRecord>>;
|
|
11
14
|
export interface ClientConfigV1 {
|
|
12
15
|
schemaVersion: 1;
|
|
13
16
|
mining: {
|
|
14
17
|
builtIn: MiningProviderConfigRecord | null;
|
|
18
|
+
builtInByProvider?: MiningProviderConfigByProvider;
|
|
19
|
+
domainExtraPrompts: Record<string, string>;
|
|
15
20
|
};
|
|
16
21
|
}
|
|
22
|
+
export interface MiningDomainPromptEntry {
|
|
23
|
+
domain: {
|
|
24
|
+
name: string;
|
|
25
|
+
domainId: number | null;
|
|
26
|
+
};
|
|
27
|
+
mineable: boolean;
|
|
28
|
+
prompt: string | null;
|
|
29
|
+
effectivePromptSource: "domain" | "global-fallback" | "none";
|
|
30
|
+
}
|
|
31
|
+
export interface MiningDomainPromptListResult {
|
|
32
|
+
fallbackPromptConfigured: boolean;
|
|
33
|
+
prompts: MiningDomainPromptEntry[];
|
|
34
|
+
}
|
|
35
|
+
export interface MiningDomainPromptMutationResult {
|
|
36
|
+
domain: {
|
|
37
|
+
name: string;
|
|
38
|
+
domainId: number | null;
|
|
39
|
+
};
|
|
40
|
+
previousPrompt: string | null;
|
|
41
|
+
prompt: string | null;
|
|
42
|
+
status: "updated" | "cleared";
|
|
43
|
+
fallbackPromptConfigured: boolean;
|
|
44
|
+
}
|
|
17
45
|
export interface MiningEventRecord {
|
|
18
46
|
schemaVersion: 1;
|
|
19
47
|
timestampUnixMs: number;
|
|
@@ -56,7 +84,7 @@ export interface MiningRuntimeStatusV1 {
|
|
|
56
84
|
indexerReorgDepth: number | null;
|
|
57
85
|
indexerTipAligned: boolean | null;
|
|
58
86
|
corePublishState: "unknown" | "network-inactive" | "no-outbound-peers" | "ibd" | "mempool-loading" | "healthy" | null;
|
|
59
|
-
providerState: "ready" | "backoff" | "unavailable" | "rate-limited" | "auth-error" | null;
|
|
87
|
+
providerState: "ready" | "backoff" | "unavailable" | "rate-limited" | "auth-error" | "not-found" | null;
|
|
60
88
|
lastSuspendDetectedAtUnixMs: number | null;
|
|
61
89
|
reconnectSettledUntilUnixMs: number | null;
|
|
62
90
|
tipSettledUntilUnixMs: number | null;
|
|
@@ -103,8 +131,14 @@ export interface MiningProviderInspection {
|
|
|
103
131
|
provider: MiningProviderKind | null;
|
|
104
132
|
status: "ready" | "missing" | "error";
|
|
105
133
|
message: string | null;
|
|
134
|
+
modelId: string | null;
|
|
135
|
+
effectiveModel: string | null;
|
|
106
136
|
modelOverride: string | null;
|
|
137
|
+
modelSelectionSource: MiningModelSelectionSource | null;
|
|
138
|
+
usingDefaultModel: boolean | null;
|
|
107
139
|
extraPromptConfigured: boolean;
|
|
140
|
+
estimatedDailyCostUsd: number | null;
|
|
141
|
+
estimatedDailyCostDisplay: string | null;
|
|
108
142
|
}
|
|
109
143
|
export interface MiningControlPlaneView {
|
|
110
144
|
runtime: MiningRuntimeStatusV1;
|
|
@@ -11,6 +11,7 @@ export interface MiningSentenceBoardEntry {
|
|
|
11
11
|
rank: number;
|
|
12
12
|
domainName: string;
|
|
13
13
|
sentence: string;
|
|
14
|
+
requiredWords: readonly string[];
|
|
14
15
|
}
|
|
15
16
|
export interface MiningProvisionalSentenceEntry {
|
|
16
17
|
domainName: string | null;
|
|
@@ -44,6 +45,8 @@ export declare class MiningFollowVisualizer {
|
|
|
44
45
|
platform?: NodeJS.Platform;
|
|
45
46
|
env?: NodeJS.ProcessEnv;
|
|
46
47
|
clock?: RenderClock;
|
|
48
|
+
clientVersion?: string | null;
|
|
49
|
+
updateAvailable?: boolean;
|
|
47
50
|
rendererFactory?: (stream: TtyRenderStream) => VisualizerRendererLike;
|
|
48
51
|
});
|
|
49
52
|
update(snapshot: MiningRuntimeStatusV1, uiState?: MiningFollowVisualizerState): void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createBootstrapProgress } from "../../bitcoind/progress/formatting.js";
|
|
2
|
+
import { normalizeInlineText, truncateLine } from "../../bitcoind/progress/formatting.js";
|
|
2
3
|
import { advanceFollowSceneState, createFollowSceneState, replaceFollowBlockTimes, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
|
|
3
4
|
import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "../../bitcoind/progress/render-policy.js";
|
|
4
5
|
import { TtyProgressRenderer } from "../../bitcoind/progress/tty-renderer.js";
|
|
5
6
|
const MINING_ARTWORK_COG_WIDTH = 22;
|
|
6
7
|
const MINING_SENTENCE_BOARD_SIZE = 5;
|
|
8
|
+
const MINING_SENTENCE_BOARD_WRAP_WIDTH = 80;
|
|
7
9
|
function formatCogAmountWithDecimals(value, { maxFractionDigits, minFractionDigits, }) {
|
|
8
10
|
const sign = value < 0n ? "-" : "";
|
|
9
11
|
const absolute = value < 0n ? -value : value;
|
|
@@ -44,14 +46,86 @@ function formatCompactCogBalanceText(balanceCogtoshi) {
|
|
|
44
46
|
function formatCompactSatBalanceText(balanceSats) {
|
|
45
47
|
return balanceSats === null ? null : `${balanceSats.toString()} SAT`;
|
|
46
48
|
}
|
|
49
|
+
function formatArtworkVersionText(clientVersion) {
|
|
50
|
+
if (typeof clientVersion !== "string") {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const normalizedVersion = normalizeInlineText(clientVersion);
|
|
54
|
+
if (normalizedVersion.length === 0) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return normalizedVersion.startsWith("v")
|
|
58
|
+
? normalizedVersion
|
|
59
|
+
: `v${normalizedVersion}`;
|
|
60
|
+
}
|
|
47
61
|
function formatRewardCogAmount(value) {
|
|
48
62
|
return `${formatCogAmountWithDecimals(value, {
|
|
49
63
|
maxFractionDigits: 8,
|
|
50
64
|
minFractionDigits: 1,
|
|
51
65
|
})} COG`;
|
|
52
66
|
}
|
|
53
|
-
function
|
|
54
|
-
return
|
|
67
|
+
function escapeRegExp(value) {
|
|
68
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
69
|
+
}
|
|
70
|
+
function consumeWrappedLine(text, capacity) {
|
|
71
|
+
const remaining = text.trimStart();
|
|
72
|
+
if (remaining.length <= capacity) {
|
|
73
|
+
return {
|
|
74
|
+
line: remaining,
|
|
75
|
+
remaining: "",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const candidate = remaining.slice(0, capacity + 1);
|
|
79
|
+
const breakIndex = candidate.lastIndexOf(" ");
|
|
80
|
+
if (breakIndex > 0) {
|
|
81
|
+
return {
|
|
82
|
+
line: remaining.slice(0, breakIndex).trimEnd(),
|
|
83
|
+
remaining: remaining.slice(breakIndex + 1).trimStart(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
line: remaining.slice(0, capacity),
|
|
88
|
+
remaining: remaining.slice(capacity).trimStart(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function highlightRequiredWords(sentence, requiredWords) {
|
|
92
|
+
const uniqueWords = [...new Set(requiredWords
|
|
93
|
+
.map((word) => word.trim().toLowerCase())
|
|
94
|
+
.filter((word) => word.length > 0))].sort((left, right) => right.length - left.length);
|
|
95
|
+
if (uniqueWords.length === 0) {
|
|
96
|
+
return sentence;
|
|
97
|
+
}
|
|
98
|
+
const pattern = new RegExp(`\\b(?:${uniqueWords.map(escapeRegExp).join("|")})\\b`, "gi");
|
|
99
|
+
return sentence.replace(pattern, (match) => match.toUpperCase());
|
|
100
|
+
}
|
|
101
|
+
function formatSentenceSlot(prefix, sentence, requiredWords, lineCount) {
|
|
102
|
+
if (sentence === null) {
|
|
103
|
+
return [
|
|
104
|
+
prefix.trimEnd(),
|
|
105
|
+
...Array.from({ length: Math.max(0, lineCount - 1) }, () => ""),
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
const normalizedSentence = highlightRequiredWords(normalizeInlineText(sentence), requiredWords);
|
|
109
|
+
const continuationPrefix = " ".repeat(prefix.length);
|
|
110
|
+
const lines = [];
|
|
111
|
+
let remaining = normalizedSentence;
|
|
112
|
+
for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
|
|
113
|
+
const linePrefix = lineIndex === 0 ? prefix : continuationPrefix;
|
|
114
|
+
const capacity = Math.max(0, MINING_SENTENCE_BOARD_WRAP_WIDTH - linePrefix.length);
|
|
115
|
+
const wrapped = consumeWrappedLine(remaining, capacity);
|
|
116
|
+
const isLastLine = lineIndex === lineCount - 1;
|
|
117
|
+
const lineContent = isLastLine && wrapped.remaining.length > 0
|
|
118
|
+
? truncateLine(`${wrapped.line}\u2026`, capacity)
|
|
119
|
+
: wrapped.line;
|
|
120
|
+
lines.push(lineContent.length === 0
|
|
121
|
+
? ""
|
|
122
|
+
: `${linePrefix}${lineContent}`);
|
|
123
|
+
remaining = wrapped.remaining;
|
|
124
|
+
}
|
|
125
|
+
return lines;
|
|
126
|
+
}
|
|
127
|
+
function formatSentenceRow(entry) {
|
|
128
|
+
return formatSentenceSlot(`${entry.rank}. @${entry.domainName}: `, entry.sentence, entry.requiredWords, 2);
|
|
55
129
|
}
|
|
56
130
|
function formatRequiredWordsLine(words) {
|
|
57
131
|
if (words.length === 0) {
|
|
@@ -59,11 +133,11 @@ function formatRequiredWordsLine(words) {
|
|
|
59
133
|
}
|
|
60
134
|
return `Required words: ${words.map((word) => word.toUpperCase()).join(", ")}`;
|
|
61
135
|
}
|
|
62
|
-
function formatProvisionalSentenceRow(entry) {
|
|
136
|
+
function formatProvisionalSentenceRow(entry, requiredWords) {
|
|
63
137
|
if (entry.domainName === null || entry.sentence === null) {
|
|
64
|
-
return "";
|
|
138
|
+
return ["", "", ""];
|
|
65
139
|
}
|
|
66
|
-
return `@${entry.domainName}:
|
|
140
|
+
return formatSentenceSlot(`@${entry.domainName}: `, entry.sentence, requiredWords, 3);
|
|
67
141
|
}
|
|
68
142
|
export function createEmptyMiningFollowVisualizerState() {
|
|
69
143
|
return {
|
|
@@ -82,6 +156,34 @@ export function createEmptyMiningFollowVisualizerState() {
|
|
|
82
156
|
recentWin: null,
|
|
83
157
|
};
|
|
84
158
|
}
|
|
159
|
+
function cloneMiningRuntimeSnapshot(snapshot) {
|
|
160
|
+
return {
|
|
161
|
+
...snapshot,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function cloneMiningFollowVisualizerState(state) {
|
|
165
|
+
return {
|
|
166
|
+
...state,
|
|
167
|
+
visibleBlockTimesByHeight: { ...state.visibleBlockTimesByHeight },
|
|
168
|
+
settledBoardEntries: state.settledBoardEntries.map((entry) => ({
|
|
169
|
+
...entry,
|
|
170
|
+
requiredWords: [...entry.requiredWords],
|
|
171
|
+
})),
|
|
172
|
+
provisionalRequiredWords: [...state.provisionalRequiredWords],
|
|
173
|
+
provisionalEntry: {
|
|
174
|
+
...state.provisionalEntry,
|
|
175
|
+
},
|
|
176
|
+
recentWin: state.recentWin === null
|
|
177
|
+
? null
|
|
178
|
+
: {
|
|
179
|
+
...state.recentWin,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function miningFollowSceneShouldSettle(snapshot, nowUnixMs) {
|
|
184
|
+
return ((snapshot.tipSettledUntilUnixMs ?? 0) > nowUnixMs
|
|
185
|
+
|| (snapshot.reconnectSettledUntilUnixMs ?? 0) > nowUnixMs);
|
|
186
|
+
}
|
|
85
187
|
const VISUALIZER_PROGRESS_SNAPSHOT = {
|
|
86
188
|
url: "",
|
|
87
189
|
filename: "mining-follow-visualizer",
|
|
@@ -172,6 +274,8 @@ export function describeMiningVisualizerProgress(snapshot) {
|
|
|
172
274
|
}
|
|
173
275
|
export class MiningFollowVisualizer {
|
|
174
276
|
#renderer;
|
|
277
|
+
#artworkStatusLeftText;
|
|
278
|
+
#artworkStatusRightText;
|
|
175
279
|
#clock;
|
|
176
280
|
#renderThrottle;
|
|
177
281
|
#progress = createBootstrapProgress("follow_tip", VISUALIZER_PROGRESS_SNAPSHOT);
|
|
@@ -187,6 +291,9 @@ export class MiningFollowVisualizer {
|
|
|
187
291
|
env: options.env,
|
|
188
292
|
});
|
|
189
293
|
this.#clock = options.clock ?? DEFAULT_RENDER_CLOCK;
|
|
294
|
+
const artworkVersionText = formatArtworkVersionText(options.clientVersion);
|
|
295
|
+
this.#artworkStatusLeftText = options.updateAvailable === true ? "UPDATE" : null;
|
|
296
|
+
this.#artworkStatusRightText = artworkVersionText;
|
|
190
297
|
this.#renderer = renderPolicy.enabled
|
|
191
298
|
? options.rendererFactory?.(stream) ?? new TtyProgressRenderer(stream)
|
|
192
299
|
: null;
|
|
@@ -208,17 +315,20 @@ export class MiningFollowVisualizer {
|
|
|
208
315
|
if (this.#renderer === null) {
|
|
209
316
|
return;
|
|
210
317
|
}
|
|
211
|
-
this.#latestSnapshot = snapshot;
|
|
318
|
+
this.#latestSnapshot = cloneMiningRuntimeSnapshot(snapshot);
|
|
212
319
|
if (uiState !== undefined) {
|
|
213
|
-
this.#latestUiState = uiState;
|
|
320
|
+
this.#latestUiState = cloneMiningFollowVisualizerState(uiState);
|
|
214
321
|
}
|
|
215
322
|
replaceFollowBlockTimes(this.#scene, this.#latestUiState.visibleBlockTimesByHeight);
|
|
216
|
-
const indexedHeight =
|
|
217
|
-
const nodeHeight =
|
|
323
|
+
const indexedHeight = this.#latestSnapshot.indexerTipHeight ?? this.#latestSnapshot.coreBestHeight ?? null;
|
|
324
|
+
const nodeHeight = this.#latestSnapshot.coreBestHeight ?? indexedHeight;
|
|
325
|
+
const settleLatest = miningFollowSceneShouldSettle(this.#latestSnapshot, this.#clock.now());
|
|
218
326
|
syncFollowSceneState(this.#scene, {
|
|
219
327
|
indexedHeight,
|
|
220
328
|
nodeHeight,
|
|
221
329
|
liveActivated: true,
|
|
330
|
+
authoritativeTip: true,
|
|
331
|
+
settleLatest,
|
|
222
332
|
});
|
|
223
333
|
this.#renderThrottle.request();
|
|
224
334
|
}
|
|
@@ -252,7 +362,7 @@ export class MiningFollowVisualizer {
|
|
|
252
362
|
this.#progress.targetHeight = nodeHeight;
|
|
253
363
|
this.#progress.etaSeconds = null;
|
|
254
364
|
this.#progress.lastError = snapshot.lastError;
|
|
255
|
-
|
|
365
|
+
const renderOptions = {
|
|
256
366
|
artworkCogText: formatCompactCogBalanceText(uiState.balanceCogtoshi),
|
|
257
367
|
artworkSatText: formatCompactSatBalanceText(uiState.balanceSats),
|
|
258
368
|
extraLines: [
|
|
@@ -261,13 +371,20 @@ export class MiningFollowVisualizer {
|
|
|
261
371
|
...Array.from({ length: MINING_SENTENCE_BOARD_SIZE }, (_value, index) => {
|
|
262
372
|
const entry = uiState.settledBoardEntries[index];
|
|
263
373
|
return entry === undefined
|
|
264
|
-
? `${index + 1}
|
|
265
|
-
: formatSentenceRow(entry
|
|
266
|
-
}),
|
|
374
|
+
? [`${index + 1}.`, ""]
|
|
375
|
+
: formatSentenceRow(entry);
|
|
376
|
+
}).flat(),
|
|
267
377
|
"----------",
|
|
268
378
|
formatRequiredWordsLine(uiState.provisionalRequiredWords),
|
|
269
|
-
formatProvisionalSentenceRow(uiState.provisionalEntry),
|
|
379
|
+
...formatProvisionalSentenceRow(uiState.provisionalEntry, uiState.provisionalRequiredWords),
|
|
270
380
|
],
|
|
271
|
-
}
|
|
381
|
+
};
|
|
382
|
+
if (this.#artworkStatusLeftText !== null) {
|
|
383
|
+
renderOptions.artworkStatusLeftText = this.#artworkStatusLeftText;
|
|
384
|
+
}
|
|
385
|
+
if (this.#artworkStatusRightText !== null) {
|
|
386
|
+
renderOptions.artworkStatusRightText = this.#artworkStatusRightText;
|
|
387
|
+
}
|
|
388
|
+
this.#renderer.renderFollowScene(this.#progress, indexedHeight, nodeHeight, this.#scene, describeMiningVisualizerStatus(snapshot, uiState), renderOptions);
|
|
272
389
|
}
|
|
273
390
|
}
|
|
@@ -15,6 +15,7 @@ export declare function openWalletReadContext(options: {
|
|
|
15
15
|
secretProvider?: WalletSecretProvider;
|
|
16
16
|
walletControlLockHeld?: boolean;
|
|
17
17
|
startupTimeoutMs?: number;
|
|
18
|
+
expectedIndexerBinaryVersion?: string | null;
|
|
18
19
|
now?: number;
|
|
19
20
|
paths?: WalletRuntimePaths;
|
|
20
21
|
}): Promise<WalletReadContext>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { access, constants } from "node:fs/promises";
|
|
2
2
|
import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
|
|
3
|
-
import { attachOrStartIndexerDaemon, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
|
|
3
|
+
import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
|
|
4
4
|
import { createRpcClient } from "../../bitcoind/node.js";
|
|
5
5
|
import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
|
|
6
6
|
import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
|
|
@@ -245,6 +245,11 @@ function mapIndexerStartupError(message) {
|
|
|
245
245
|
health: "unavailable",
|
|
246
246
|
message: "The live indexer daemon socket responded with an invalid or incomplete protocol exchange.",
|
|
247
247
|
};
|
|
248
|
+
case INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED:
|
|
249
|
+
return {
|
|
250
|
+
health: "failed",
|
|
251
|
+
message: "The managed indexer daemon could not recover automatic background follow.",
|
|
252
|
+
};
|
|
248
253
|
default:
|
|
249
254
|
return {
|
|
250
255
|
health: "unavailable",
|
|
@@ -532,17 +537,15 @@ export async function openWalletReadContext(options) {
|
|
|
532
537
|
dataDir: options.dataDir,
|
|
533
538
|
walletRootId,
|
|
534
539
|
});
|
|
535
|
-
if (probe.compatibility === "compatible") {
|
|
536
|
-
|
|
537
|
-
observedDaemonStatus = probe.status;
|
|
538
|
-
indexerSource = "probe";
|
|
539
|
-
}
|
|
540
|
-
else if (probe.compatibility === "unreachable") {
|
|
540
|
+
if (probe.compatibility === "compatible" || probe.compatibility === "unreachable") {
|
|
541
|
+
await probe.client?.close().catch(() => undefined);
|
|
541
542
|
daemonClient = await attachOrStartIndexerDaemon({
|
|
542
543
|
dataDir: options.dataDir,
|
|
543
544
|
databasePath: options.databasePath,
|
|
544
545
|
walletRootId,
|
|
545
546
|
startupTimeoutMs,
|
|
547
|
+
ensureBackgroundFollow: true,
|
|
548
|
+
expectedBinaryVersion: options.expectedIndexerBinaryVersion,
|
|
546
549
|
});
|
|
547
550
|
}
|
|
548
551
|
else {
|
|
@@ -567,6 +570,11 @@ export async function openWalletReadContext(options) {
|
|
|
567
570
|
}
|
|
568
571
|
catch (error) {
|
|
569
572
|
daemonError = error instanceof Error ? error.message : String(error);
|
|
573
|
+
if (daemonError === INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED) {
|
|
574
|
+
await daemonClient?.close().catch(() => undefined);
|
|
575
|
+
await node.handle?.stop().catch(() => undefined);
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
570
578
|
if (observedDaemonStatus === null) {
|
|
571
579
|
observedDaemonStatus = await readObservedIndexerDaemonStatus({
|
|
572
580
|
dataDir: options.dataDir,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
2
2
|
import net from "node:net";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
|
+
function shouldRemoveAgentEndpointPath(endpoint) {
|
|
5
|
+
return !endpoint.startsWith("\\\\.\\pipe\\");
|
|
6
|
+
}
|
|
4
7
|
function zeroizeBuffer(buffer) {
|
|
5
8
|
if (buffer != null) {
|
|
6
9
|
buffer.fill(0);
|
|
@@ -67,7 +70,7 @@ async function main() {
|
|
|
67
70
|
}
|
|
68
71
|
zeroizeBuffer(key);
|
|
69
72
|
key = Buffer.alloc(0);
|
|
70
|
-
if (
|
|
73
|
+
if (shouldRemoveAgentEndpointPath(bootstrap.endpoint)) {
|
|
71
74
|
await rm(bootstrap.endpoint, { force: true }).catch(() => undefined);
|
|
72
75
|
}
|
|
73
76
|
process.exit(0);
|
|
@@ -33,13 +33,18 @@ function resolveClientPasswordStatePath(directoryPath) {
|
|
|
33
33
|
function resolveClientPasswordRotationJournalPath(directoryPath) {
|
|
34
34
|
return join(directoryPath, "client-password-rotation.json");
|
|
35
35
|
}
|
|
36
|
-
function resolveAgentEndpoint(
|
|
36
|
+
function resolveAgentEndpoint(stateRoot) {
|
|
37
37
|
const hash = createHash("sha256").update(stateRoot).digest("hex").slice(0, 24);
|
|
38
|
-
|
|
38
|
+
// Wallet provider tests simulate foreign platforms, but the local agent transport
|
|
39
|
+
// still has to follow the real host runtime.
|
|
40
|
+
if (process.platform === "win32") {
|
|
39
41
|
return `\\\\.\\pipe\\cogcoin-client-password-${hash}`;
|
|
40
42
|
}
|
|
41
43
|
return join(tmpdir(), `cogcoin-client-password-${hash}.sock`);
|
|
42
44
|
}
|
|
45
|
+
function shouldRemoveAgentEndpointPath(endpoint) {
|
|
46
|
+
return !endpoint.startsWith("\\\\.\\pipe\\");
|
|
47
|
+
}
|
|
43
48
|
function isMissingFileError(error) {
|
|
44
49
|
return error instanceof Error
|
|
45
50
|
&& "code" in error
|
|
@@ -352,7 +357,7 @@ async function openAgentConnection(endpoint) {
|
|
|
352
357
|
});
|
|
353
358
|
}
|
|
354
359
|
async function requestAgent(options, request) {
|
|
355
|
-
const endpoint = resolveAgentEndpoint(options.
|
|
360
|
+
const endpoint = resolveAgentEndpoint(options.stateRoot);
|
|
356
361
|
const socket = await openAgentConnection(endpoint);
|
|
357
362
|
return await new Promise((resolve, reject) => {
|
|
358
363
|
let received = "";
|
|
@@ -418,8 +423,9 @@ async function requestAgentOrNull(options, request) {
|
|
|
418
423
|
? String(error.code ?? "")
|
|
419
424
|
: "";
|
|
420
425
|
if (code === "ENOENT" || code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE") {
|
|
421
|
-
|
|
422
|
-
|
|
426
|
+
const endpoint = resolveAgentEndpoint(options.stateRoot);
|
|
427
|
+
if (shouldRemoveAgentEndpointPath(endpoint)) {
|
|
428
|
+
await rm(endpoint, { force: true }).catch(() => undefined);
|
|
423
429
|
}
|
|
424
430
|
return null;
|
|
425
431
|
}
|
|
@@ -441,8 +447,9 @@ export async function readClientPasswordSessionStatus(options) {
|
|
|
441
447
|
}
|
|
442
448
|
export async function lockClientPasswordSession(options) {
|
|
443
449
|
await requestAgentOrNull(options, { command: "lock" }).catch(() => null);
|
|
444
|
-
|
|
445
|
-
|
|
450
|
+
const endpoint = resolveAgentEndpoint(options.stateRoot);
|
|
451
|
+
if (shouldRemoveAgentEndpointPath(endpoint)) {
|
|
452
|
+
await rm(endpoint, { force: true }).catch(() => undefined);
|
|
446
453
|
}
|
|
447
454
|
return {
|
|
448
455
|
unlocked: false,
|
|
@@ -499,7 +506,7 @@ async function startClientPasswordSession(options) {
|
|
|
499
506
|
}
|
|
500
507
|
async function startClientPasswordSessionWithExpiry(options) {
|
|
501
508
|
const unlockUntilUnixMs = options.unlockUntilUnixMs;
|
|
502
|
-
const endpoint = resolveAgentEndpoint(options.
|
|
509
|
+
const endpoint = resolveAgentEndpoint(options.stateRoot);
|
|
503
510
|
await lockClientPasswordSession(options).catch(() => undefined);
|
|
504
511
|
await mkdir(options.runtimeRoot, { recursive: true }).catch(() => undefined);
|
|
505
512
|
const child = spawn(process.execPath, [fileURLToPath(new URL("./client-password-agent.js", import.meta.url)), endpoint, String(unlockUntilUnixMs)], {
|
package/dist/wallet/tx/common.js
CHANGED
|
@@ -435,7 +435,7 @@ export async function buildWalletMutationTransaction(options) {
|
|
|
435
435
|
const funded = await options.rpc.walletCreateFundedPsbt(options.walletName, options.plan.fixedInputs, options.plan.outputs, 0, {
|
|
436
436
|
add_inputs: true,
|
|
437
437
|
include_unsafe: false,
|
|
438
|
-
minconf:
|
|
438
|
+
minconf: availableFundingMinConf,
|
|
439
439
|
changeAddress: options.plan.changeAddress,
|
|
440
440
|
...(options.plan.changePosition == null ? {} : { changePosition: options.plan.changePosition }),
|
|
441
441
|
lockUnspents: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cogcoin/client",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"build": "rm -rf dist && node ./node_modules/typescript/bin/tsc -p tsconfig.json && node ./scripts/copy-static-assets.mjs build",
|
|
60
60
|
"generate:default-snapshot-chunk-manifest": "node scripts/generate-default-snapshot-chunk-manifest.mjs",
|
|
61
61
|
"verify:default-snapshot-chunk-manifest": "node scripts/generate-default-snapshot-chunk-manifest.mjs --check",
|
|
62
|
-
"test": "rm -rf .test-dist && node ./node_modules/typescript/bin/tsc -p tsconfig.test.json && node ./scripts/copy-static-assets.mjs test && node --test .test-dist/test/*.test.js"
|
|
62
|
+
"test": "rm -rf .test-dist && node ./node_modules/typescript/bin/tsc -p tsconfig.test.json && node ./scripts/copy-static-assets.mjs test && node --test .test-dist/test/*.test.js",
|
|
63
|
+
"test:mining": "rm -rf .test-dist && node ./node_modules/typescript/bin/tsc -p tsconfig.test-mining.json && node ./scripts/copy-static-assets.mjs test && node --test .test-dist/test/mining*.test.js"
|
|
63
64
|
},
|
|
64
65
|
"dependencies": {
|
|
65
66
|
"@cogcoin/bitcoin": "30.2.0",
|