ruvnet-brain 4.7.20 → 4.7.22
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 +2 -2
- package/bin/install.mjs +618 -104
- package/console/activity.js +122 -38
- package/kb/download-retry.mjs +93 -21
- package/kb/forge-guard-injection.mjs +277 -0
- package/kb/forge-update.mjs +166 -38
- package/kb/lifecycle-evidence-retention.mjs +151 -14
- package/kb/model-requirements.mjs +4 -1
- package/kb/refresh-run.mjs +175 -27
- package/kb/verify-citation.mjs +15 -2
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/mcp/managed-cli-interface.mjs +16 -8
- package/plugin/scripts/adr-currency-gate.mjs +2 -1
- package/plugin/scripts/advisory-contract.mjs +21 -0
- package/plugin/scripts/advocacy-route.mjs +5 -4
- package/plugin/scripts/agentdb-recall-process.mjs +115 -5
- package/plugin/scripts/agentdb-recall-reader.mjs +44 -2
- package/plugin/scripts/agentdb-recall.mjs +49 -17
- package/plugin/scripts/automatic-update.mjs +9 -1
- package/plugin/scripts/brain-confirmation.mjs +2 -1
- package/plugin/scripts/brain-footprint.mjs +72 -11
- package/plugin/scripts/capability-registry.mjs +1 -1
- package/plugin/scripts/capacity-aware-parallel-work.mjs +2 -1
- package/plugin/scripts/codex-overnight-notice.mjs +33 -0
- package/plugin/scripts/completion-claim-evidence.mjs +241 -23
- package/plugin/scripts/continuation-gate.mjs +75 -11
- package/plugin/scripts/decision-gate.mjs +13 -63
- package/plugin/scripts/decision-outcomes.mjs +36 -0
- package/plugin/scripts/decision-recall-gate.mjs +40 -5
- package/plugin/scripts/decision-record.mjs +8 -3
- package/plugin/scripts/degradation-watch.mjs +2 -1
- package/plugin/scripts/developer-update-lock.mjs +172 -33
- package/plugin/scripts/developer-update-owner.mjs +8 -5
- package/plugin/scripts/developer-update-policy.mjs +4 -1
- package/plugin/scripts/developer-update.mjs +155 -53
- package/plugin/scripts/doc-currency.mjs +2 -16
- package/plugin/scripts/duplicate-gate.mjs +2 -1
- package/plugin/scripts/entrypoint.mjs +34 -0
- package/plugin/scripts/footprint-io.mjs +135 -8
- package/plugin/scripts/grok-hook-adapter.mjs +2 -1
- package/plugin/scripts/ground-ruvnet.sh +6 -3
- package/plugin/scripts/grounding-answer.mjs +2 -1
- package/plugin/scripts/grounding-code-projection.mjs +2 -1
- package/plugin/scripts/grounding-substance.mjs +2 -1
- package/plugin/scripts/grounding-turn-gate.mjs +2 -2
- package/plugin/scripts/grounding-turn-mark.mjs +2 -2
- package/plugin/scripts/hook-context-budget.mjs +2 -1
- package/plugin/scripts/hook-input.mjs +2 -2
- package/plugin/scripts/hook-shim.mjs +9 -2
- package/plugin/scripts/host-update.mjs +29 -5
- package/plugin/scripts/identifier-preflight.mjs +2 -1
- package/plugin/scripts/kb-copy-proof.mjs +57 -9
- package/plugin/scripts/learn-capture.mjs +2 -1
- package/plugin/scripts/learning-store.mjs +62 -16
- package/plugin/scripts/lesson-bridge.mjs +2 -2
- package/plugin/scripts/lesson-gate.mjs +7 -39
- package/plugin/scripts/lesson-promote.mjs +83 -22
- package/plugin/scripts/lesson-scope.mjs +51 -0
- package/plugin/scripts/lesson-store.mjs +1 -1
- package/plugin/scripts/md-stamp.mjs +2 -1
- package/plugin/scripts/native-turn-transcript.mjs +98 -6
- package/plugin/scripts/nightly-scheduler.mjs +10 -5
- package/plugin/scripts/package-recommender.mjs +2 -2
- package/plugin/scripts/project-store-resolver.mjs +19 -0
- package/plugin/scripts/routing-outcome-capture.mjs +2 -1
- package/plugin/scripts/ruflo-bin.mjs +14 -6
- package/plugin/scripts/ruflo-memory-hooks.mjs +157 -19
- package/plugin/scripts/session-snapshot-hook.mjs +25 -5
- package/plugin/scripts/session-start-core.mjs +7 -16
- package/plugin/scripts/session-start-health.mjs +98 -10
- package/plugin/scripts/session-start-issue-alert.mjs +63 -0
- package/plugin/scripts/session-start-update-plane.mjs +15 -1
- package/plugin/scripts/signal-watch.mjs +2 -1
- package/plugin/scripts/spend-guard.mjs +2 -1
- package/plugin/scripts/substitution-wall.mjs +2 -1
- package/plugin/scripts/turn-outcome-capture.mjs +7 -3
- package/plugin/scripts/unprompted-runtime.mjs +20 -3
- package/plugin/scripts/update-apply.mjs +186 -15
- package/plugin/skills/release-proof/scripts/release-proof.mjs +2 -1
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +2 -2
- package/plugin/skills/ruvnet-brain/SKILL.md +3 -2
- package/scripts/adr-072-completion.mjs +2 -1
- package/scripts/agentdb-context.mjs +2 -1
- package/scripts/agentdb-fleet-doctor.mjs +30 -8
- package/scripts/approved-runtime.mjs +2 -1
- package/scripts/architecture-review-lock.mjs +2 -1
- package/scripts/ascii-drift.mjs +2 -1
- package/scripts/brain-latency-50.mjs +2 -1
- package/scripts/brain-novice-50.mjs +2 -16
- package/scripts/brain-score.mjs +13 -2
- package/scripts/build-bundle.mjs +2 -1
- package/scripts/build-concepts.mjs +2 -1
- package/scripts/candidate-ci-receipt.mjs +2 -1
- package/scripts/candidate-host-evidence.mjs +2 -1
- package/scripts/card-from-source.mjs +2 -1
- package/scripts/ci/warm-brain-models.mjs +2 -1
- package/scripts/claims-verify.mjs +7 -3
- package/scripts/claude-controlled-terminal.mjs +2 -1
- package/scripts/claude-terminal-mod.mjs +2 -1
- package/scripts/code-release-corpus.mjs +2 -1
- package/scripts/codex-boot-surface-proof.mjs +208 -0
- package/scripts/codex-hook-trust-reconcile.mjs +67 -1
- package/scripts/codex-hook-trust.mjs +40 -1
- package/scripts/codex-hook-wrapper-history.mjs +2 -1
- package/scripts/codex-host-execution-proof.mjs +2 -1
- package/scripts/completion-claim-replay.mjs +2 -1
- package/scripts/console-engine.mjs +8 -3
- package/scripts/corpus-canary.mjs +2 -1
- package/scripts/corpus-candidate.mjs +2 -2
- package/scripts/corpus-coverage-sidecar.mjs +2 -1
- package/scripts/corpus-dispatch-decision.mjs +2 -1
- package/scripts/corpus-dispatch-receipt.mjs +2 -1
- package/scripts/corpus-next-seed.mjs +2 -1
- package/scripts/corpus-qa.mjs +2 -1
- package/scripts/corpus-reconcile.mjs +2 -2
- package/scripts/corpus-watchdog.mjs +2 -1
- package/scripts/count-chunks.mjs +2 -1
- package/scripts/customer-state-matrix.mjs +2 -1
- package/scripts/derive-passage-content-map.mjs +2 -1
- package/scripts/development-push-check.mjs +2 -1
- package/scripts/dispatch-receipt.mjs +2 -1
- package/scripts/doc-currency.mjs +2 -1
- package/scripts/dream-issue-gate.mjs +2 -1
- package/scripts/dual-host-deliberation.mjs +2 -16
- package/scripts/dual-host-suggest.mjs +2 -16
- package/scripts/eval-brain.mjs +7 -3
- package/scripts/execution-policy.mjs +2 -1
- package/scripts/execution-preflight.mjs +2 -1
- package/scripts/falsify.mjs +2 -1
- package/scripts/fix-metaharness-memretrieve.mjs +2 -1
- package/scripts/fix-workstream.mjs +2 -1
- package/scripts/full-hints.mjs +2 -1
- package/scripts/full-suite-gate.mjs +2 -1
- package/scripts/grok-host.mjs +68 -0
- package/scripts/hook-qualify-core.mjs +3 -1
- package/scripts/hook-qualify.mjs +2 -1
- package/scripts/hook-retirement-check.mjs +2 -1
- package/scripts/host-registry.mjs +2 -1
- package/scripts/independent-review-receipt.mjs +2 -1
- package/scripts/ingest-new-repos.mjs +2 -1
- package/scripts/ingest-repo.mjs +12 -2
- package/scripts/integration-evidence.mjs +2 -1
- package/scripts/issue-fix.mjs +7 -24
- package/scripts/issue-watch.mjs +116 -40
- package/scripts/job-heartbeat.sh +54 -19
- package/scripts/key-canary.mjs +9 -3
- package/scripts/latency-to-surface.mjs +2 -1
- package/scripts/learning-replay-cli.mjs +97 -19
- package/scripts/learning-replay-execution.mjs +2 -0
- package/scripts/learning-replay-fixture.mjs +3 -0
- package/scripts/learning-replay-proof.mjs +14 -13
- package/scripts/learning-replay.mjs +3 -3
- package/scripts/learnings.mjs +2 -1
- package/scripts/lesson-ratify.mjs +88 -40
- package/scripts/loop-checkpoint.mjs +2 -1
- package/scripts/metaharness-receipts.mjs +2 -1
- package/scripts/model-catalog.mjs +2 -1
- package/scripts/model-currency-evidence.mjs +6 -4
- package/scripts/model-currency.mjs +33 -3
- package/scripts/model-native-catalog.mjs +2 -1
- package/scripts/model-router-agent-hook.mjs +2 -1
- package/scripts/model-router-dispatch.mjs +2 -1
- package/scripts/model-router-engine.mjs +2 -1
- package/scripts/model-router-outcome.mjs +2 -1
- package/scripts/model-router-setup.mjs +2 -1
- package/scripts/model-routing-eval.mjs +2 -1
- package/scripts/model-routing-gateway.mjs +2 -1
- package/scripts/model-routing-launchers.mjs +4 -1
- package/scripts/model-terminal-gateway.mjs +2 -1
- package/scripts/model-terminal-launchers.mjs +2 -1
- package/scripts/model-weekly-analyst.mjs +2 -1
- package/scripts/model-weekly-cycle.mjs +2 -1
- package/scripts/model-weekly-qualification.mjs +2 -1
- package/scripts/nightly-gists.sh +26 -20
- package/scripts/nightly-two-run-proof.mjs +2 -1
- package/scripts/nightly-watchdog.mjs +158 -47
- package/scripts/no-silent-substitution.mjs +2 -1
- package/scripts/notify.sh +11 -6
- package/scripts/ntfy-inbox.mjs +41 -18
- package/scripts/onboarding-console.mjs +123 -23
- package/scripts/oracle/abstain-threshold-sweep.mjs +2 -1
- package/scripts/oracle/abstain-trace.mjs +2 -1
- package/scripts/oracle/doc2query-generate.mjs +2 -1
- package/scripts/oracle/doc2query-reach.mjs +2 -1
- package/scripts/oracle/judge-train.mjs +2 -1
- package/scripts/oracle/need-set-split.mjs +2 -1
- package/scripts/oracle/produce-questions.mjs +2 -16
- package/scripts/oracle/repo-recall.mjs +2 -2
- package/scripts/oracle/retrieval-accuracy.mjs +2 -2
- package/scripts/oracle/sona-query-adapter-eval.mjs +2 -1
- package/scripts/oracle/source-tree.mjs +2 -15
- package/scripts/oracle/source-units.mjs +2 -16
- package/scripts/oracle/spike-run.mjs +2 -16
- package/scripts/oracle/validate-labels.mjs +2 -16
- package/scripts/org-repo-count.mjs +2 -1
- package/scripts/package-cards.mjs +2 -1
- package/scripts/prepublication-evidence.mjs +2 -1
- package/scripts/private-overlay.mjs +2 -1
- package/scripts/product-integrity-contract.mjs +2 -1
- package/scripts/protected-release-invocation.mjs +2 -1
- package/scripts/public-verification-abandon.mjs +2 -1
- package/scripts/public-verification-aggregate.mjs +2 -1
- package/scripts/public-verification-finalizer.mjs +2 -1
- package/scripts/public-verification-inputs.mjs +2 -1
- package/scripts/public-verification-lane.mjs +2 -1
- package/scripts/publication-receipt.mjs +2 -1
- package/scripts/published-surface-probe.mjs +2 -1
- package/scripts/qe/card-lane-gate.mjs +2 -16
- package/scripts/qe/session-start-gate.mjs +2 -16
- package/scripts/qe/ux-suite.mjs +2 -1
- package/scripts/qualified-candidate-check.mjs +2 -1
- package/scripts/rebuild-gists-from-receipts.mjs +2 -1
- package/scripts/recall-latency-probe.mjs +48 -0
- package/scripts/recommendation-eval.mjs +2 -1
- package/scripts/recommendation-floor.mjs +2 -1
- package/scripts/recommendation-judge-score.mjs +2 -1
- package/scripts/recommendation-real-host-score.mjs +2 -1
- package/scripts/recommendation-real-host.mjs +2 -1
- package/scripts/refresh-capability-only-store.mjs +2 -1
- package/scripts/refresh-model-catalog.mjs +2 -1
- package/scripts/rehearse-corpus-pipeline.mjs +2 -1
- package/scripts/release-authority.mjs +2 -1
- package/scripts/release-evidence-aggregate.mjs +2 -1
- package/scripts/release-payload.mjs +2 -1
- package/scripts/release-projection.mjs +2 -1
- package/scripts/release-proof.mjs +2 -1
- package/scripts/release-qualification-contract.mjs +82 -28
- package/scripts/release-qualification.mjs +8 -1
- package/scripts/release-vector.mjs +2 -1
- package/scripts/restore-local-ingests.mjs +2 -1
- package/scripts/retrieval-canary.mjs +2 -1
- package/scripts/route-cheap.mjs +2 -1
- package/scripts/route-gold-rank.mjs +2 -1
- package/scripts/route-index-memory.mjs +2 -1
- package/scripts/route-latency-warm.mjs +2 -1
- package/scripts/router-utilization.mjs +2 -1
- package/scripts/routing-flywheel.mjs +2 -1
- package/scripts/run-operational-benchmark.mjs +2 -1
- package/scripts/run-operational-benchmark.v3.mjs +2 -1
- package/scripts/rvf-index-audit.mjs +2 -16
- package/scripts/seal-gist-receipt.mjs +2 -1
- package/scripts/security-guidance-codex-compat.mjs +2 -1
- package/scripts/selfcheck.mjs +2 -1
- package/scripts/signal-watch.mjs +2 -1
- package/scripts/source-coverage.mjs +2 -1
- package/scripts/stabilization-receipt.mjs +2 -1
- package/scripts/stamp-sweep.mjs +2 -1
- package/scripts/status-honesty.mjs +2 -1
- package/scripts/sync-commands.mjs +2 -2
- package/scripts/top100-benchmark.mjs +2 -1
- package/scripts/top100-corpus.mjs +2 -1
- package/scripts/user-model-hook-install.mjs +2 -1
- package/scripts/user-model-prompt-hook.mjs +2 -1
- package/scripts/verify-bundle.mjs +3 -1
- package/scripts/verify-model-catalog.mjs +2 -1
- package/scripts/version.mjs +2 -1
- package/scripts/wired-check.mjs +13 -5
- package/scripts/worktree-integrity.mjs +2 -2
package/console/activity.js
CHANGED
|
@@ -10,6 +10,81 @@
|
|
|
10
10
|
(function () {
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
+
/* #499: every figure this card shows, as display text. A value that could not be read (null /
|
|
14
|
+
undefined / non-numeric) is the word "Unknown" — never 0, which would claim the store is empty.
|
|
15
|
+
Pure and published on the global before the DOM guard below so a test can load this file without a
|
|
16
|
+
page. nMem/nLes/nMachine are numeric (unknown -> 0) for the animation maths only; never display them. */
|
|
17
|
+
function activityFigures(d) {
|
|
18
|
+
function known(v) { return typeof v === 'number' && isFinite(v); }
|
|
19
|
+
function show(v) { return known(v) ? Number(v).toLocaleString('en-US') : 'Unknown'; }
|
|
20
|
+
function num(v) { return known(v) ? v : 0; }
|
|
21
|
+
d = d || {};
|
|
22
|
+
var t = d.totals || {};
|
|
23
|
+
var ag = known(t.agentdbLessons) ? t.agentdbLessons : t.lessons;
|
|
24
|
+
var ap = d.approvedLessons && d.approvedLessons.available === true ? d.approvedLessons.count : null;
|
|
25
|
+
var mach = d.machine && d.machine.totalMemories;
|
|
26
|
+
var apx = d.approvedLessons && d.approvedLessons.available === true ? d.approvedLessons.applicable : null;
|
|
27
|
+
return {
|
|
28
|
+
memories: show(t.memories), agentdbLessons: show(ag), lessons: show(ag),
|
|
29
|
+
approvedLessons: show(ap), approvedMachine: show(ap), approvedApplicable: show(apx), machine: show(mach),
|
|
30
|
+
observations: show(t.observations), patterns: show(t.patterns), episodes: show(t.episodes),
|
|
31
|
+
approvedAvailable: known(ap), agentdbKnown: known(ag),
|
|
32
|
+
nMem: num(t.memories), nLes: num(ag), nMachine: num(mach)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityFigures = activityFigures;
|
|
36
|
+
|
|
37
|
+
function escText(v) { return String(v == null ? '' : v).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
|
|
38
|
+
|
|
39
|
+
/* #499: the approved lessons that apply to this project, with the approval / scope / enforcement the owner
|
|
40
|
+
set — rendered from the server's own bounded list. Pure; an unreadable lesson store says Unknown. */
|
|
41
|
+
function approvedLessonsHTML(d) {
|
|
42
|
+
var a = d && d.approvedLessons;
|
|
43
|
+
if (!a || a.available !== true) return '<p class="ab-fallback">Approved lessons: Unknown (the lesson store could not be read).</p>';
|
|
44
|
+
var items = Array.isArray(a.items) ? a.items : [];
|
|
45
|
+
var F = activityFigures(d);
|
|
46
|
+
var head = '<div class="ab-dl-title"><span class="ab-g">✦</span>' + F.approvedApplicable + ' approved lessons apply to this project · ' + F.approvedMachine + ' approved machine-wide</div>';
|
|
47
|
+
if (!items.length) return head + '<p class="ab-fallback">None of your approved lessons are scoped to this project.</p>';
|
|
48
|
+
return head + items.map(function (l) {
|
|
49
|
+
return '<div class="ab-lrow"><b>' + escText(l.id) + '</b> ' + escText(l.title) +
|
|
50
|
+
' <span class="ab-lmeta">scope: ' + escText(l.scope) + ' · enforcement: ' + escText(l.enforcement == null ? 'unknown' : l.enforcement) +
|
|
51
|
+
' · ' + (l.ratified ? 'ratified by you' : escText(l.status || 'not ratified')) + '</span></div>';
|
|
52
|
+
}).join('') + (a.itemsTruncated ? '<p class="ab-fallback">…showing the first ' + items.length + ' of ' + F.approvedApplicable + '.</p>' : '');
|
|
53
|
+
}
|
|
54
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityApprovedHTML = approvedLessonsHTML;
|
|
55
|
+
|
|
56
|
+
/* #500: which machine-wide row is "you". By STORE IDENTITY (the server's projectId against each fleet entry's
|
|
57
|
+
id), never by folder name — two unrelated repos can both be called "app". No identity on either side means
|
|
58
|
+
no match (the row is then synthesised from this project's own totals), not a guess from the name. */
|
|
59
|
+
function activityUniverse(d) {
|
|
60
|
+
d = d || {};
|
|
61
|
+
var machine = d.machine || {};
|
|
62
|
+
var projects = (Array.isArray(machine.projects) ? machine.projects.slice() : [])
|
|
63
|
+
.sort(function (a, b) { return (b.memories || 0) - (a.memories || 0); });
|
|
64
|
+
var isMe = function (p) { return !!d.projectId && p.id === d.projectId; };
|
|
65
|
+
var found = projects.filter(isMe)[0] || null;
|
|
66
|
+
var others = projects.filter(function (p) { return !isMe(p); });
|
|
67
|
+
return { projects: projects, me: found, others: others.slice(0, 12), more: Math.max(0, others.length - 12) };
|
|
68
|
+
}
|
|
69
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityUniverse = activityUniverse;
|
|
70
|
+
|
|
71
|
+
/* #499: the rolling narration, from each pipeline stage's OWN measurement. Distillation turns observations
|
|
72
|
+
into patterns and episodes; it does not write lessons — lessons are ratified by the owner. */
|
|
73
|
+
function activityNarration(d) {
|
|
74
|
+
var F = activityFigures(d);
|
|
75
|
+
var lines = [
|
|
76
|
+
'Your AI captures everything as it works — ' + F.memories + ' memories in this project so far.',
|
|
77
|
+
'Learning pipeline in this project: ' + F.observations + ' workflow observations, ' + F.patterns + ' structural patterns, ' + F.episodes + ' episodes.',
|
|
78
|
+
'Observations are distilled in the background (session end or compaction, when learning is on) into patterns and episodes — not into lessons.',
|
|
79
|
+
F.approvedApplicable + ' approved lessons apply here (' + F.approvedMachine + ' machine-wide). You ratify lessons; nothing is auto-approved.'
|
|
80
|
+
];
|
|
81
|
+
if (F.agentdbKnown) lines.push(F.agentdbLessons + ' AgentDB lesson records in this project.');
|
|
82
|
+
else lines.push('AgentDB lesson records: Unknown.');
|
|
83
|
+
lines.push('Click any number above to see exactly what it knows.');
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
(typeof globalThis !== 'undefined' ? globalThis : this).activityNarration = activityNarration;
|
|
87
|
+
|
|
13
88
|
var CARD = document.getElementById('card-activity');
|
|
14
89
|
var BODY = document.getElementById('body-activity');
|
|
15
90
|
if (!CARD || !BODY) return;
|
|
@@ -148,6 +223,8 @@
|
|
|
148
223
|
'#card-activity .ab-glyph.ab-pulse{animation:ab-pulse-once 1.1s ease-out 1;display:inline-block}',
|
|
149
224
|
'#card-activity .ab-caret{display:inline-block;width:7px;color:var(--amber-text)}',
|
|
150
225
|
/* graceful degrade */
|
|
226
|
+
'#card-activity .ab-lrow{font-size:13px;color:var(--ink-2);margin:6px 2px;line-height:1.4}',
|
|
227
|
+
'#card-activity .ab-lmeta{display:block;font-size:12px;color:var(--muted)}',
|
|
151
228
|
'#card-activity .ab-fallback{margin-top:16px;font-size:13.5px;color:var(--ink-2);font-style:italic}',
|
|
152
229
|
'@media (prefers-reduced-motion:reduce){',
|
|
153
230
|
' #card-activity .ab-glyph.ab-pulse{animation:none}',
|
|
@@ -194,6 +271,14 @@
|
|
|
194
271
|
setChips('<span class="chip tone-grey">unavailable</span>');
|
|
195
272
|
}
|
|
196
273
|
|
|
274
|
+
/* #499: no readable project store, but the approved-lesson count is a separate, readable fact. */
|
|
275
|
+
function degradeWithApprovals(d) {
|
|
276
|
+
var F = activityFigures(d);
|
|
277
|
+
BODY.innerHTML = '<p class="ab-fallback">This project\'s memory store is not readable right now (memories: ' + F.memories + '). ' +
|
|
278
|
+
'<b>' + F.approvedMachine + '</b> approved lessons are in force on this machine' + (F.approvedApplicable !== 'Unknown' ? ' (<b>' + F.approvedApplicable + '</b> apply to this project)' : '') + '.</p>' + approvedLessonsHTML(d);
|
|
279
|
+
setChips('<span class="chip tone-grey">memories unknown</span><span class="chip tone-green">' + F.approvedMachine + ' approved lessons (machine-wide)</span>');
|
|
280
|
+
}
|
|
281
|
+
|
|
197
282
|
/* ---------------- key → plain-English sentence mapper ---------------- */
|
|
198
283
|
var TOOL_LABEL = { memory: 'AgentDB', autocapture: 'Autocapture', hooks: 'Hooks intel', metaharness: 'MetaHarness' };
|
|
199
284
|
var LESSON_TITLES = {
|
|
@@ -267,6 +352,7 @@
|
|
|
267
352
|
fetch('/api/activity', { headers: { Accept: 'application/json' } })
|
|
268
353
|
.then(function (r) { if (!r.ok) throw new Error('http ' + r.status); return r.json(); })
|
|
269
354
|
.then(function (d) {
|
|
355
|
+
if (d && d.hasStore === false && d.approvedLessons && d.approvedLessons.available === true) { degradeWithApprovals(d); return; }
|
|
270
356
|
if (!d || d.hasStore === false || !d.totals) { degrade(); return; }
|
|
271
357
|
render(d);
|
|
272
358
|
})
|
|
@@ -282,8 +368,9 @@
|
|
|
282
368
|
var projects = (Array.isArray(machine.projects) ? machine.projects.slice() : [])
|
|
283
369
|
.sort(function (a, b) { return (b.memories || 0) - (a.memories || 0); });
|
|
284
370
|
var projectName = d.project || 'this project';
|
|
285
|
-
var
|
|
286
|
-
var
|
|
371
|
+
var F = activityFigures(d);
|
|
372
|
+
var nMem = F.nMem, nLes = F.nLes;
|
|
373
|
+
var nProj = projects.length;
|
|
287
374
|
|
|
288
375
|
var LESSONS = lessonsRaw.map(function (l) {
|
|
289
376
|
return { key: l.key, learned: l.learned, recalled: l.access_count || 0, segs: parseExcerpt(l.excerpt) };
|
|
@@ -293,8 +380,10 @@
|
|
|
293
380
|
|
|
294
381
|
/* summary chips */
|
|
295
382
|
setChips(
|
|
296
|
-
'<span class="chip tone-amber">' +
|
|
297
|
-
'<span class="chip tone-green">' +
|
|
383
|
+
'<span class="chip tone-amber">' + F.memories + ' memories</span>' +
|
|
384
|
+
'<span class="chip tone-green">' + F.approvedApplicable + ' approved lessons · this project</span>' +
|
|
385
|
+
'<span class="chip tone-grey">' + F.approvedMachine + ' approved machine-wide</span>' +
|
|
386
|
+
'<span class="chip tone-grey">' + F.agentdbLessons + ' AgentDB lesson records</span>'
|
|
298
387
|
);
|
|
299
388
|
|
|
300
389
|
/* ---------------- panel skeleton ---------------- */
|
|
@@ -302,16 +391,18 @@
|
|
|
302
391
|
'<div class="ab-stats">' +
|
|
303
392
|
'<div class="ab-stat ab-door" id="ab-stat-memories" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
304
393
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
305
|
-
'<div class="ab-n">' +
|
|
394
|
+
'<div class="ab-n">' + F.memories + '</div><div class="ab-l">memories · this project</div></div>' +
|
|
306
395
|
'<div class="ab-stat ab-door" id="ab-stat-lessons" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
307
396
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
308
|
-
'<div class="ab-n">' +
|
|
397
|
+
'<div class="ab-n">' + F.approvedApplicable + '</div><div class="ab-l">approved lessons · apply to this project</div>' +
|
|
398
|
+
'<div class="ab-l">' + F.approvedMachine + ' approved machine-wide</div>' +
|
|
399
|
+
'<div class="ab-l">' + F.agentdbLessons + ' AgentDB lesson records · this project</div></div>' +
|
|
309
400
|
'<div class="ab-stat ab-door" id="ab-stat-projects" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
310
401
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
311
402
|
'<div class="ab-n">' + fmt(nProj) + '</div><div class="ab-l">projects machine-wide</div></div>' +
|
|
312
403
|
'<div class="ab-stat ab-door" id="ab-stat-machine" role="button" tabindex="0" aria-expanded="false" aria-controls="ab-doors">' +
|
|
313
404
|
'<span class="ab-hint"></span><span class="ab-tchev">▸</span>' +
|
|
314
|
-
'<div class="ab-n">' +
|
|
405
|
+
'<div class="ab-n">' + F.machine + '</div><div class="ab-l">memories machine-wide</div></div>' +
|
|
315
406
|
'</div>' +
|
|
316
407
|
'<div class="ab-doors" id="ab-doors">' +
|
|
317
408
|
'<div class="ab-doors-clip"><div class="ab-door-box">' +
|
|
@@ -324,17 +415,17 @@
|
|
|
324
415
|
'<div class="ab-door-view" id="ab-view-universe"></div>' +
|
|
325
416
|
'</div></div>' +
|
|
326
417
|
'</div>' +
|
|
327
|
-
'<div class="ab-flow" id="ab-flow" aria-label="' +
|
|
418
|
+
'<div class="ab-flow" id="ab-flow" aria-label="' + F.memories + ' captured memories, of which ' + F.lessons + ' are kept lesson records">' +
|
|
328
419
|
'<canvas id="ab-flowCanvas"></canvas>' +
|
|
329
|
-
'<div class="ab-flow-hit ab-left" id="ab-flowHitLeft" role="button" tabindex="0" aria-label="see the ' +
|
|
330
|
-
'<div class="ab-flow-hit ab-right" id="ab-flowHitRight" role="button" tabindex="0" aria-label="open the ' +
|
|
331
|
-
'<div class="ab-flow-label ab-left"><span class="mono">' +
|
|
332
|
-
'<div class="ab-flow-label ab-gate">
|
|
333
|
-
'<div class="ab-flow-label ab-right"><span class="mono">' +
|
|
420
|
+
'<div class="ab-flow-hit ab-left" id="ab-flowHitLeft" role="button" tabindex="0" aria-label="see the ' + F.memories + ' memories by kind" title="see the ' + F.memories + ' memories by kind"></div>' +
|
|
421
|
+
'<div class="ab-flow-hit ab-right" id="ab-flowHitRight" role="button" tabindex="0" aria-label="open the ' + F.lessons + ' lesson records" title="open the ' + F.lessons + ' lesson records"></div>' +
|
|
422
|
+
'<div class="ab-flow-label ab-left"><span class="mono">' + F.memories + '</span> captured</div>' +
|
|
423
|
+
'<div class="ab-flow-label ab-gate">curated</div>' +
|
|
424
|
+
'<div class="ab-flow-label ab-right"><span class="mono">' + F.lessons + '</span> lesson records — the keepers</div>' +
|
|
334
425
|
'<div class="ab-flow-narr"><span class="ab-fn-text" id="ab-fnText"></span><span class="ab-fn-steps" id="ab-fnSteps" aria-hidden="true"></span></div>' +
|
|
335
426
|
'</div>' +
|
|
336
|
-
'<div class="ab-vocab"><b>memories</b> = everything your AI captures automatically · <b>
|
|
337
|
-
(nLes ? '' : '<div class="ab-empty-note">
|
|
427
|
+
'<div class="ab-vocab"><b>memories</b> = everything your AI captures automatically · <b>lesson records</b> = the ones kept in the AgentDB lessons namespace (task → what failed → what works) · <b>approved lessons</b> = the ones you ratified. Background distillation makes patterns and episodes, not lessons.</div>' +
|
|
428
|
+
((nLes || !F.agentdbKnown) ? '' : '<div class="ab-empty-note">No lesson records in this project\'s AgentDB yet. Lessons are ratified by you; background distillation (session end or compaction, when learning is on) produces patterns and episodes, not lesson records. <code>record-lesson</code> keeps one now. Approved lessons are counted separately above.</div>') +
|
|
338
429
|
'<div class="ab-roster" id="ab-roster">' +
|
|
339
430
|
'<span class="ab-who">written by</span>' +
|
|
340
431
|
'<span class="ab-rchip" data-tool="memory">AgentDB<span class="ab-cs">via ruflo memory</span><span class="ab-k">✦</span></span>' +
|
|
@@ -404,11 +495,13 @@
|
|
|
404
495
|
});
|
|
405
496
|
|
|
406
497
|
/* ---------------- doors ---------------- */
|
|
407
|
-
document.getElementById('ab-view-lessons').innerHTML = LESSONS.length
|
|
498
|
+
document.getElementById('ab-view-lessons').innerHTML = approvedLessonsHTML(d) + (LESSONS.length
|
|
408
499
|
? LESSONS.map(function (L) {
|
|
409
500
|
return '<div class="ab-dl-title"><span class="ab-g">✦</span>' + esc(lessonTitle(L.key)) + '</div>' + lessonCardHTML(L);
|
|
410
501
|
}).join('')
|
|
411
|
-
:
|
|
502
|
+
: (F.agentdbKnown
|
|
503
|
+
? '<p class="ab-fallback">No lesson records in this project\'s AgentDB yet. Lessons are ratified by you; background distillation produces patterns and episodes, not lesson records. <code>record-lesson</code> keeps one now.</p>'
|
|
504
|
+
: '<p class="ab-fallback">AgentDB lesson records: Unknown (the store could not be read).</p>'));
|
|
412
505
|
|
|
413
506
|
(function () {
|
|
414
507
|
var GLOSS = {
|
|
@@ -418,7 +511,7 @@
|
|
|
418
511
|
'adr-patterns': 'architecture decision patterns',
|
|
419
512
|
'adr-edges': 'links between decisions',
|
|
420
513
|
trajectories: 'reasoning paths kept for replay',
|
|
421
|
-
lessons: '
|
|
514
|
+
lessons: 'lesson records — the keepers',
|
|
422
515
|
'metaharness-audit': 'MetaHarness audit runs',
|
|
423
516
|
feedback: 'edit verifications, scored',
|
|
424
517
|
pattern: 'learned edit patterns',
|
|
@@ -436,7 +529,7 @@
|
|
|
436
529
|
'<span class="ab-kc">' + fmt(b.n) + '</span>' +
|
|
437
530
|
'<span class="ab-kg">' + esc(GLOSS[b.namespace] || '') + '</span></div>';
|
|
438
531
|
}).join('') +
|
|
439
|
-
'<div class="ab-door-foot">' + breakdown.length + ' namespaces · <span class="mono">' +
|
|
532
|
+
'<div class="ab-door-foot">' + breakdown.length + ' namespaces · <span class="mono">' + F.memories + '</span> rows — live from <span class="mono">.swarm/memory.db</span> · bars log-scaled</div>';
|
|
440
533
|
})();
|
|
441
534
|
|
|
442
535
|
(function () {
|
|
@@ -444,9 +537,12 @@
|
|
|
444
537
|
document.getElementById('ab-view-universe').innerHTML = '<p class="ab-fallback">no machine-wide data available</p>';
|
|
445
538
|
return;
|
|
446
539
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
var
|
|
540
|
+
/* #500: "you are here" is the project whose STORE IDENTITY matches, never whose folder name does —
|
|
541
|
+
two unrelated repos can share a name. No identity (older payload) means no match, not a name guess. */
|
|
542
|
+
var U = activityUniverse(d);
|
|
543
|
+
var others = U.others;
|
|
544
|
+
var meRow = U.me || { name: projectName, memories: nMem };
|
|
545
|
+
var more = U.more;
|
|
450
546
|
var top = Math.log10(Math.max(projects[0].memories || 1, 10));
|
|
451
547
|
var base = Math.min(2.7, top - 0.5);
|
|
452
548
|
var wU = function (n) { return Math.round(100 * Math.max(0.10, (Math.log10(Math.max(n, 1)) - base) / (top - base))); };
|
|
@@ -459,12 +555,12 @@
|
|
|
459
555
|
others.map(function (p) { return rowHTML(p.name, p.memories, false); }).join('') +
|
|
460
556
|
rowHTML(meRow.name, meRow.memories, true) +
|
|
461
557
|
(more ? '<div class="ab-umore">…and ' + more + ' more projects</div>' : '') +
|
|
462
|
-
'<div class="ab-door-foot"><span class="mono">' +
|
|
558
|
+
'<div class="ab-door-foot"><span class="mono">' + F.machine + '</span> memories machine-wide — every one persists across sessions and reboots</div>';
|
|
463
559
|
})();
|
|
464
560
|
|
|
465
561
|
var DOOR_META = {
|
|
466
|
-
lessons: [
|
|
467
|
-
memories: [
|
|
562
|
+
lessons: [F.lessons + ' lesson records', 'kept in this project\'s AgentDB lessons namespace — every card is a real memory'],
|
|
563
|
+
memories: [F.memories + ' memories in this project', 'by namespace — what your AI writes down as you work'],
|
|
468
564
|
universe: [fmt(nProj) + ' projects machine-wide', "everywhere this machine's AI remembers"]
|
|
469
565
|
};
|
|
470
566
|
var VIEW_OF = { 'ab-stat-memories': 'memories', 'ab-stat-lessons': 'lessons', 'ab-stat-projects': 'universe', 'ab-stat-machine': 'universe' };
|
|
@@ -721,19 +817,7 @@
|
|
|
721
817
|
|
|
722
818
|
/* ---------------- rolling narration — the takeaway in plain English ---------------- */
|
|
723
819
|
(function () {
|
|
724
|
-
var LINES =
|
|
725
|
-
'Your AI captures everything as it works — ' + fmt(nMem) + ' memories in this project so far.',
|
|
726
|
-
"Most of that stays raw. Only what's proven survives distillation.",
|
|
727
|
-
fmt(nLes) + ' lessons have earned permanence — the task, what failed, what actually works.',
|
|
728
|
-
"These survive compaction, restarts, and reboots. Your AI doesn't start over tomorrow.",
|
|
729
|
-
'Click any number above to see exactly what it knows.'
|
|
730
|
-
] : [
|
|
731
|
-
'Your AI captures everything as it works — ' + fmt(nMem) + ' memories in this project so far.',
|
|
732
|
-
"Most of that stays raw. Only what's proven survives distillation.",
|
|
733
|
-
'0 lessons distilled yet. Distillation is manual for now; there is no auto-distill.',
|
|
734
|
-
'Capture one any time: run record-lesson in this project.',
|
|
735
|
-
'Click any number above to see exactly what it knows.'
|
|
736
|
-
];
|
|
820
|
+
var LINES = activityNarration(d);
|
|
737
821
|
var txt = document.getElementById('ab-fnText');
|
|
738
822
|
var steps = document.getElementById('ab-fnSteps');
|
|
739
823
|
var idx = 0;
|
package/kb/download-retry.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Retry transport failures only. Parsing, signature and corpus validation remain caller-owned.
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import http from 'node:http';
|
|
3
4
|
import https from 'node:https';
|
|
4
5
|
import { pipeline } from 'node:stream/promises';
|
|
5
6
|
import { randomUUID } from 'node:crypto';
|
|
@@ -16,23 +17,43 @@ function httpError(status) {
|
|
|
16
17
|
return Object.assign(new Error(`server returned HTTP ${status}`), { status });
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// `attemptMs` is a NO-PROGRESS window, not a cap on the whole attempt: an operation that reports
|
|
21
|
+
// progress (ctx.progress) keeps its attempt alive for as long as bytes keep arriving. A whole-attempt
|
|
22
|
+
// cap of 5 min against a 745 MB asset meant links under ~2.5 MB/s could never finish (4.7.21 RC2.2).
|
|
23
|
+
// Operations that never report progress (the small JSON reads) keep the old meaning: a hard cap.
|
|
24
|
+
// `deadlineMs` bounds all attempts together; ctx.expectBytes(n) raises it to n / minBytesPerSec once
|
|
25
|
+
// the body size is known, so a large body on a slow-but-steady link is not cut off by the floor.
|
|
26
|
+
export async function retryDownload(operation, { deadlineMs = 90 * 60_000,
|
|
27
|
+
attemptMs = 60_000, minBytesPerSec = 256 * 1024, delays = [250, 1000], onRetry = () => {} } = {}) {
|
|
28
|
+
const started = Date.now();
|
|
29
|
+
let deadline = started + deadlineMs;
|
|
22
30
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
23
31
|
const remaining = deadline - Date.now();
|
|
24
32
|
if (remaining <= 0) throw new Error('download deadline exceeded');
|
|
25
33
|
const controller = new AbortController();
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
const abort = (message) => () => controller.abort(Object.assign(new Error(message), { code: 'ETIMEDOUT' }));
|
|
35
|
+
let idleTimer = setTimeout(abort('download stalled: no bytes received'), Math.min(attemptMs, remaining));
|
|
36
|
+
let deadlineTimer = setTimeout(abort('download deadline exceeded'), remaining);
|
|
37
|
+
const progress = () => {
|
|
38
|
+
if (controller.signal.aborted) return;
|
|
39
|
+
clearTimeout(idleTimer);
|
|
40
|
+
idleTimer = setTimeout(abort('download stalled: no bytes received'), attemptMs);
|
|
41
|
+
};
|
|
42
|
+
const expectBytes = (bytes) => {
|
|
43
|
+
const needed = started + Math.ceil(Number(bytes) / minBytesPerSec * 1000);
|
|
44
|
+
if (!Number.isFinite(needed) || needed <= deadline || controller.signal.aborted) return;
|
|
45
|
+
deadline = needed;
|
|
46
|
+
clearTimeout(deadlineTimer);
|
|
47
|
+
deadlineTimer = setTimeout(abort('download deadline exceeded'), Math.max(0, deadline - Date.now()));
|
|
48
|
+
};
|
|
49
|
+
try { return await operation({ signal: controller.signal, attempt, progress, expectBytes }); }
|
|
29
50
|
catch (caught) {
|
|
30
51
|
const error = controller.signal.aborted ? controller.signal.reason : caught;
|
|
31
52
|
const delay = delays[attempt - 1] ?? 0;
|
|
32
53
|
if (attempt === 3 || !retryable(error) || Date.now() + delay >= deadline) throw error;
|
|
33
54
|
onRetry({ attempt, error });
|
|
34
55
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
35
|
-
} finally { clearTimeout(
|
|
56
|
+
} finally { clearTimeout(idleTimer); clearTimeout(deadlineTimer); }
|
|
36
57
|
}
|
|
37
58
|
}
|
|
38
59
|
|
|
@@ -56,11 +77,14 @@ export async function fetchJsonWithRetry(url, options = {}) {
|
|
|
56
77
|
function get(url, { signal, headers }, redirects = 0) {
|
|
57
78
|
return new Promise((resolve, reject) => {
|
|
58
79
|
if (redirects > 10) { reject(new Error('too many redirects')); return; }
|
|
59
|
-
|
|
80
|
+
// The release host is https; a plain-http URL (a local mirror, the test servers) is fetched as such.
|
|
81
|
+
const transport = new URL(url).protocol === 'http:' ? http : https;
|
|
82
|
+
const request = transport.get(url, { signal, headers }, response => {
|
|
60
83
|
if ([301, 302, 303, 307, 308].includes(response.statusCode) && response.headers.location) {
|
|
61
84
|
response.destroy();
|
|
62
85
|
resolve(get(new URL(response.headers.location, url).toString(), { signal, headers }, redirects + 1));
|
|
63
|
-
} else if (response.statusCode !== 200) {
|
|
86
|
+
} else if (response.statusCode !== 200 && !([206, 416].includes(response.statusCode) && headers.Range)) {
|
|
87
|
+
// With Range: 206 continues the stage, 416 (unsatisfiable) restarts it; the caller decides.
|
|
64
88
|
response.destroy(); reject(httpError(response.statusCode));
|
|
65
89
|
} else resolve(response);
|
|
66
90
|
});
|
|
@@ -68,21 +92,69 @@ function get(url, { signal, headers }, redirects = 0) {
|
|
|
68
92
|
});
|
|
69
93
|
}
|
|
70
94
|
|
|
95
|
+
function stagedBytes(file) {
|
|
96
|
+
try { return fs.statSync(file).size; } catch (error) { if (error.code === 'ENOENT') return 0; throw error; }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// A resumed body is appended ONLY when the server proves it is the continuation of the bytes already
|
|
100
|
+
// staged: HTTP 206 with a Content-Range that starts exactly at the staged size. Anything else (a 200 for
|
|
101
|
+
// the whole body, a mirror that ignores Range, a different start) restarts the stage from zero.
|
|
102
|
+
// Returns the declared total, 0 for an unknown total ('*'), or null when the response is not a continuation.
|
|
103
|
+
function resumedTotal(response, offset) {
|
|
104
|
+
if (response.statusCode !== 206) return null;
|
|
105
|
+
const match = /^bytes (\d+)-(\d+)\/(\d+|\*)$/.exec(String(response.headers['content-range'] || '').trim());
|
|
106
|
+
if (!match || Number(match[1]) !== offset) return null;
|
|
107
|
+
return match[3] === '*' ? 0 : Number(match[3]);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// What the staged bytes are a prefix of, taken from the response that started the stage: its validator
|
|
111
|
+
// (a strong ETag, else Last-Modified) for If-Range, and its full size. An asset replaced at the same URL
|
|
112
|
+
// between attempts must restart from zero, never be stitched onto the old prefix (which only the signature
|
|
113
|
+
// check would catch, as exit 4 and a full reinstall).
|
|
114
|
+
function bodyIdentity(response, total) {
|
|
115
|
+
const etag = response.headers.etag || null;
|
|
116
|
+
const strong = etag && !/^W\//.test(etag) ? etag : null;
|
|
117
|
+
return { etag, validator: strong || response.headers['last-modified'] || null, total: total || 0 };
|
|
118
|
+
}
|
|
119
|
+
|
|
71
120
|
export async function downloadFileWithRetry(url, destination, { headers = {}, onProgress = () => {}, ...options } = {}) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
121
|
+
// One stage for the whole call: a retry continues it (Range) instead of discarding every byte received.
|
|
122
|
+
const staged = `${destination}.part-${process.pid}-${randomUUID()}`;
|
|
123
|
+
let identity = null;
|
|
124
|
+
try {
|
|
125
|
+
await retryDownload(async ({ signal, progress, expectBytes }) => {
|
|
126
|
+
let offset = stagedBytes(staged);
|
|
127
|
+
const resume = { ...headers, Range: `bytes=${offset}-`, ...(identity?.validator ? { 'If-Range': identity.validator } : {}) };
|
|
128
|
+
let response = await get(url, { signal, headers: offset ? resume : headers });
|
|
129
|
+
let total = offset ? resumedTotal(response, offset) : null;
|
|
130
|
+
if (total !== null) {
|
|
131
|
+
// '*' (unknown total) keeps the size the stage was started with, so the final size check still runs.
|
|
132
|
+
if (total === 0) total = identity?.total || 0;
|
|
133
|
+
const etag = response.headers.etag || null;
|
|
134
|
+
if ((identity?.etag && etag && etag !== identity.etag) || (identity?.total && total !== identity.total)) {
|
|
135
|
+
total = null; // a continuation of a DIFFERENT body: the asset changed under the stage
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (total === null) {
|
|
139
|
+
if (offset) { // the server did not continue the stage: restart from byte zero
|
|
140
|
+
offset = 0;
|
|
141
|
+
// A 200 already carries the whole body; a 206 for other bytes (or another body) or a 416 needs a fresh request.
|
|
142
|
+
if (response.statusCode !== 200) { response.destroy(); response = await get(url, { signal, headers }); }
|
|
143
|
+
}
|
|
144
|
+
total = Number(response.headers['content-length'] || 0);
|
|
145
|
+
identity = bodyIdentity(response, total);
|
|
146
|
+
}
|
|
147
|
+
if (total) expectBytes(total);
|
|
148
|
+
let received = offset;
|
|
149
|
+
response.on('data', bytes => { received += bytes.length; progress(); onProgress({ received, total }); });
|
|
150
|
+
const flags = offset ? 'a' : fs.existsSync(staged) ? 'w' : 'wx';
|
|
151
|
+
await pipeline(response, fs.createWriteStream(staged, { flags, mode: 0o600 }), { signal });
|
|
152
|
+
if (!response.complete || (total && stagedBytes(staged) !== total)) {
|
|
81
153
|
throw Object.assign(new Error('download body ended prematurely'), { code: 'ECONNRESET' });
|
|
82
154
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
},
|
|
155
|
+
}, options);
|
|
156
|
+
fs.renameSync(staged, destination);
|
|
157
|
+
} finally { fs.rmSync(staged, { force: true }); }
|
|
86
158
|
}
|
|
87
159
|
|
|
88
160
|
export async function httpsJsonWithRetry(url, { headers = {}, ...options } = {}) {
|