@north-light/crouter 0.3.270 → 0.3.272
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/dist/clients/attach/viewer.js +406 -406
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +36 -0
- package/dist/commands/memory/read.js +6 -2
- package/dist/commands/memory/write.js +7 -3
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +2 -2
- package/dist/core/__tests__/helpers/harness.js +1 -0
- package/dist/core/__tests__/seam/broker-provider-retry.test.js +2 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +3 -3
- package/dist/core/nested-stores.d.ts +1 -1
- package/dist/core/nested-stores.js +4 -2
- package/dist/core/runtime/fleet.d.ts +5 -6
- package/dist/core/runtime/nodes.js +6 -1
- package/dist/daemon/fleet.js +9 -21
- package/dist/shared/env.d.ts +2 -0
- package/dist/shared/env.js +4 -0
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -283,6 +283,42 @@ test('a failing lint returns its declared rows and fails by exit code', async ()
|
|
|
283
283
|
process.exitCode = 0;
|
|
284
284
|
}
|
|
285
285
|
});
|
|
286
|
+
test('initializing a repository-root store does not lint its valid namespace as misplaced', async () => {
|
|
287
|
+
const fresh = join(base, 'fresh-root');
|
|
288
|
+
mkdirSync(fresh, { recursive: true });
|
|
289
|
+
const result = (await writeLeaf.run({
|
|
290
|
+
name: 'fresh-root',
|
|
291
|
+
kind: 'knowledge',
|
|
292
|
+
dir: fresh,
|
|
293
|
+
body: 'FRESH ROOT BODY',
|
|
294
|
+
whenAndWhyToRead: 'When opening this repository, read this because it explains the workspace.',
|
|
295
|
+
surface: ['{on: workspace-open, at: content}', '{on: read, match: "./**", at: content}'],
|
|
296
|
+
}));
|
|
297
|
+
assert.equal(result.findings, undefined, JSON.stringify(result.findings));
|
|
298
|
+
});
|
|
299
|
+
test('a targeted read suppresses malformed descendant-store warnings', async () => {
|
|
300
|
+
const noisy = join(base, 'noisy');
|
|
301
|
+
mkdirSync(storeDir(noisy), { recursive: true });
|
|
302
|
+
writeDoc(join(home, '.crouter', 'memory', 'quiet-target.md'), 'kind: knowledge', 'QUIET TARGET BODY');
|
|
303
|
+
writeDoc(join(noisy, 'unrelated', '.crouter', 'memory', 'doc.md'), 'kind: knowledge', 'UNRELATED BODY');
|
|
304
|
+
process.chdir(noisy);
|
|
305
|
+
resetScopeCache();
|
|
306
|
+
const stderr = process.stderr;
|
|
307
|
+
const originalWrite = stderr.write;
|
|
308
|
+
const writes = [];
|
|
309
|
+
stderr.write = ((chunk) => {
|
|
310
|
+
writes.push(String(chunk));
|
|
311
|
+
return true;
|
|
312
|
+
});
|
|
313
|
+
try {
|
|
314
|
+
const read = (await readLeaf.run({ name: 'quiet-target', kind: undefined, frontmatter: false, scope: undefined, dir: undefined }));
|
|
315
|
+
assert.match(read.content, /QUIET TARGET BODY/);
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
stderr.write = originalWrite;
|
|
319
|
+
}
|
|
320
|
+
assert.equal(writes.some((line) => line.includes('memory store not mounted')), false, writes.join(''));
|
|
321
|
+
});
|
|
286
322
|
test('deleted history points at the revision holding the retained text, never a read that cannot answer', async () => {
|
|
287
323
|
await deleteLeaf.run({ name: 'acme/disposable', dir: root });
|
|
288
324
|
const result = (await historyLeaf.run({ name: 'acme/disposable', dir: root }));
|
|
@@ -4,7 +4,7 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
4
4
|
import { CrtrError } from '../../core/errors.js';
|
|
5
5
|
import { memoryExtensionEffectiveCatalog, projectEffectiveMemoryExtensions } from '../../core/memory/extensions.js';
|
|
6
6
|
import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
|
|
7
|
-
import { createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
|
|
7
|
+
import { ambientMemoryTarget, createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
|
|
8
8
|
import { expandShellBlocks, hasShellBlocks, makeNodeShellRunner } from '../../core/runtime/shell-expansion.js';
|
|
9
9
|
import { emptyContextExposureState, exposureTarget, loadContextExposureState, registerDocumentExposure, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
|
|
10
10
|
import { dirDedupKey, docsByName, isDirName, renderDirListing } from '../../core/substrate/listings.js';
|
|
@@ -121,7 +121,10 @@ export const readLeaf = defineLeaf({
|
|
|
121
121
|
...(selector.scope === undefined ? {} : { scope: selector.scope }),
|
|
122
122
|
includeDescendants: true,
|
|
123
123
|
};
|
|
124
|
-
|
|
124
|
+
// A named read must not turn malformed stores elsewhere in the corpus
|
|
125
|
+
// into warnings about the requested document. `memory lint` owns that
|
|
126
|
+
// corpus health report; resolution still includes every candidate.
|
|
127
|
+
listingByName = linkByName = docsByName(listAllMemoryDocs(selector.scope, true, true));
|
|
125
128
|
try {
|
|
126
129
|
set = resolveMemoryCandidates(nameRaw, resolveOpts);
|
|
127
130
|
}
|
|
@@ -190,6 +193,7 @@ export const readLeaf = defineLeaf({
|
|
|
190
193
|
subject,
|
|
191
194
|
target,
|
|
192
195
|
byName: listingByName,
|
|
196
|
+
memoryTarget: ambientMemoryTarget(),
|
|
193
197
|
listings: 'blocks',
|
|
194
198
|
explicit: true,
|
|
195
199
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { envNodeId, envProfileId } from '../../shared/env.js';
|
|
2
|
-
import { join } from 'node:path';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
3
|
import { defineLeaf } from '../../core/command.js';
|
|
4
4
|
import { usage } from '../../core/errors.js';
|
|
5
5
|
import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
|
|
6
6
|
import { lintMemoryDocument, resolvableCanonicalNames } from '../../core/memory/lint.js';
|
|
7
7
|
import { descendantStoreRoots } from '../../core/nested-stores.js';
|
|
8
8
|
import { projectScopeRoots } from '../../core/scope.js';
|
|
9
|
-
import { loadMemoryTargetView } from '../../core/memory-resolver.js';
|
|
9
|
+
import { loadMemoryTargetView, openProjectMemoryStore } from '../../core/memory-resolver.js';
|
|
10
10
|
import { parsedSubstrateSurfaces } from '../../core/substrate/frontmatter-validation.js';
|
|
11
11
|
import { ensureDir, realpathOrSelf, walkFiles, writeText } from '../../core/fs-utils.js';
|
|
12
12
|
import { associateRepository } from '../../core/memory/repository-association.js';
|
|
@@ -187,7 +187,11 @@ export const writeLeaf = defineLeaf({
|
|
|
187
187
|
const surfaceEntries = parsedSubstrateSurfaces(frontmatter);
|
|
188
188
|
const carriesReference = docLinkNames(body).length > 0 || surfaceEntries.some((entry) => entry.on === 'memory-read');
|
|
189
189
|
const carriesBootEntry = surfaceEntries.some((entry) => entry.on === 'boot');
|
|
190
|
-
|
|
190
|
+
// Initializing created the root declaration after selector resolution, so
|
|
191
|
+
// validate against a freshly described store: the stale descriptor has no
|
|
192
|
+
// repository-root association yet and would reject its valid namespace.
|
|
193
|
+
const lintStore = initializing ? openProjectMemoryStore(store.ownerDir ?? dirname(dirname(memoryDir))) : store;
|
|
194
|
+
const lint = lintMemoryDocument(lintStore, path, {
|
|
191
195
|
...(carriesReference
|
|
192
196
|
? {
|
|
193
197
|
resolvable: resolvableCanonicalNames(loadMemoryTargetView({
|
|
@@ -28,7 +28,7 @@ import { ApiError } from '../../api/errors.js';
|
|
|
28
28
|
// debounce window before asserting, exactly as against the real 800/1000 cadence.
|
|
29
29
|
const TICK_MS = 20;
|
|
30
30
|
const DEBOUNCE_MS = 25;
|
|
31
|
-
const SETTLE_MS = TICK_MS * 2 + DEBOUNCE_MS +
|
|
31
|
+
const SETTLE_MS = TICK_MS * 2 + DEBOUNCE_MS + 200;
|
|
32
32
|
let origHome;
|
|
33
33
|
let origNode;
|
|
34
34
|
const homes = [];
|
|
@@ -302,7 +302,7 @@ describe('canvas inbox watcher — hold + idle delivery', () => {
|
|
|
302
302
|
},
|
|
303
303
|
};
|
|
304
304
|
const drive = createEngineDriver({
|
|
305
|
-
registry: { sendFrame: () => undefined },
|
|
305
|
+
registry: { sendFrame: () => undefined, broadcast: () => undefined },
|
|
306
306
|
replies: {},
|
|
307
307
|
session: () => session,
|
|
308
308
|
dispatches: { track: (promise) => promise },
|
|
@@ -109,6 +109,8 @@ test('a managed-cooling terminal authors a rate-limit auto fault whose deadline
|
|
|
109
109
|
seedPool('openai-codex', false);
|
|
110
110
|
const child = await h.spawnHeadlessChild(parent, 'cooling terminal', { model: OPENAI_STRONG.spec });
|
|
111
111
|
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial cooling fixture turn started' });
|
|
112
|
+
const kickoffObserver = await attachUntil(child, 'observer', 'cooling-terminal-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'cooling terminal kickoff settled');
|
|
113
|
+
kickoffObserver.close();
|
|
112
114
|
const baselineStarts = agentStartCount(child);
|
|
113
115
|
// A terminal managed-cooling error at agent_end: its explanatory prose avoids
|
|
114
116
|
// rate-limit words entirely; the typed diagnostic alone must classify the
|
|
@@ -195,7 +195,7 @@ export declare function openProjectMemoryStore(ownerDir: string): MemoryStoreDes
|
|
|
195
195
|
/** Every store a target sees, in precedence order: node, each project store
|
|
196
196
|
* nearest-first (its enabled plugins after it), then profile, user, builtin.
|
|
197
197
|
* One real store reached twice contributes once, at its first position. */
|
|
198
|
-
export declare function memoryStoresInPrecedence(target: MemoryTarget, scope?: MemoryScope, includeDescendants?: boolean): MemoryStoreDescriptor[];
|
|
198
|
+
export declare function memoryStoresInPrecedence(target: MemoryTarget, scope?: MemoryScope, includeDescendants?: boolean, quiet?: boolean): MemoryStoreDescriptor[];
|
|
199
199
|
/** The native (non-plugin) MOUNTED store dirs in resolution precedence.
|
|
200
200
|
* Addresses a store directly when the document itself may be absent, which is
|
|
201
201
|
* how a DELETED doc's revision log is still found: history outlives the doc,
|
|
@@ -193,7 +193,7 @@ export function openProjectMemoryStore(ownerDir) {
|
|
|
193
193
|
/** Every store a target sees, in precedence order: node, each project store
|
|
194
194
|
* nearest-first (its enabled plugins after it), then profile, user, builtin.
|
|
195
195
|
* One real store reached twice contributes once, at its first position. */
|
|
196
|
-
export function memoryStoresInPrecedence(target, scope, includeDescendants = false) {
|
|
196
|
+
export function memoryStoresInPrecedence(target, scope, includeDescendants = false, quiet = false) {
|
|
197
197
|
const out = [];
|
|
198
198
|
const projectStores = (crtrRoot, projectMemory) => {
|
|
199
199
|
out.push(projectStoreDescriptor(dirname(crtrRoot), join(crtrRoot, 'memory'), projectMemory));
|
|
@@ -211,7 +211,7 @@ export function memoryStoresInPrecedence(target, scope, includeDescendants = fal
|
|
|
211
211
|
// nested one. They carry the neutral ceiling — no profile entry names
|
|
212
212
|
// them, and descendant discovery is off for every automatic delivery path.
|
|
213
213
|
if (includeDescendants) {
|
|
214
|
-
for (const root of descendantStoreRoots(annotated.map(({ root: r }) => r))) {
|
|
214
|
+
for (const root of descendantStoreRoots(annotated.map(({ root: r }) => r), quiet)) {
|
|
215
215
|
projectStores(root, NEUTRAL_PROJECT_MEMORY);
|
|
216
216
|
}
|
|
217
217
|
}
|
|
@@ -438,7 +438,7 @@ function makeView(stores, docs, scope) {
|
|
|
438
438
|
* with its migration remedy and contributes nothing. */
|
|
439
439
|
export function loadMemoryTargetView(target, opts = {}) {
|
|
440
440
|
const quiet = opts.quiet ?? false;
|
|
441
|
-
const stores = memoryStoresInPrecedence(target, opts.scope, opts.includeDescendants ?? false);
|
|
441
|
+
const stores = memoryStoresInPrecedence(target, opts.scope, opts.includeDescendants ?? false, quiet);
|
|
442
442
|
const docs = [];
|
|
443
443
|
for (const store of stores) {
|
|
444
444
|
if (store.mountStatus === 'absent')
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* excluding centrally gives the resolver and lint the identical call with no
|
|
10
10
|
* duplicated dedupe logic.
|
|
11
11
|
*/
|
|
12
|
-
export declare function descendantStoreRoots(ancestorRoots: string[]): string[];
|
|
12
|
+
export declare function descendantStoreRoots(ancestorRoots: string[], quiet?: boolean): string[];
|
|
13
13
|
/** Complete descendant-store discovery for state migrations. Unlike the
|
|
14
14
|
* interactive addressing lane, this filesystem walk has no time or depth
|
|
15
15
|
* budget, crosses embedded repositories, admits plugin-only `.crouter` roots,
|
|
@@ -39,7 +39,7 @@ const cache = new Map();
|
|
|
39
39
|
* excluding centrally gives the resolver and lint the identical call with no
|
|
40
40
|
* duplicated dedupe logic.
|
|
41
41
|
*/
|
|
42
|
-
export function descendantStoreRoots(ancestorRoots) {
|
|
42
|
+
export function descendantStoreRoots(ancestorRoots, quiet = false) {
|
|
43
43
|
const key = [...ancestorRoots].sort().join('\n');
|
|
44
44
|
const cached = cache.get(key);
|
|
45
45
|
if (cached !== undefined)
|
|
@@ -56,7 +56,9 @@ export function descendantStoreRoots(ancestorRoots) {
|
|
|
56
56
|
owners.push(owner);
|
|
57
57
|
}
|
|
58
58
|
const tripBudget = (owner) => {
|
|
59
|
-
|
|
59
|
+
if (!quiet) {
|
|
60
|
+
warn(`memory: nested-store discovery under ${owner} exceeded its ${TIME_BUDGET_MS}ms budget — nested stores may be missing from this listing; register deep stores explicitly (add their dirs to the selected profile's projects)`);
|
|
61
|
+
}
|
|
60
62
|
};
|
|
61
63
|
const found = [];
|
|
62
64
|
const foundSeen = new Set();
|
|
@@ -47,9 +47,8 @@ export interface FleetRegistry {
|
|
|
47
47
|
get(nodeId: string): FleetEntry | undefined;
|
|
48
48
|
/** The capacity count: registered brokers only, excluding reservations. */
|
|
49
49
|
size(): number;
|
|
50
|
-
/** Every slot the cap accounts for — registered brokers plus
|
|
51
|
-
* reservations
|
|
52
|
-
* an exited broker whose exit-policy job has not yet run). */
|
|
50
|
+
/** Every slot the cap accounts for — registered brokers plus launch
|
|
51
|
+
* reservations that have not registered yet. */
|
|
53
52
|
occupancy(): number;
|
|
54
53
|
/** Claim a slot for `nodeId`. True means the caller may launch; false means
|
|
55
54
|
* the cap is reached and the caller must freeze or refuse. Idempotent — a
|
|
@@ -62,9 +61,9 @@ export interface FleetRegistry {
|
|
|
62
61
|
/** Drop an entry without policy (terminal exits + close paths). */
|
|
63
62
|
forget(nodeId: string): void;
|
|
64
63
|
/** Wired by `headlessBrokerHost.launch` to the handle's `exited` promise.
|
|
65
|
-
* Removes the entry (freeing the capacity slot), then enqueues
|
|
66
|
-
*
|
|
67
|
-
*
|
|
64
|
+
* Removes the entry (freeing the capacity slot), then enqueues its
|
|
65
|
+
* observation on the daemon's serial work lane — it never runs inline on
|
|
66
|
+
* the Node event callback (design D-3). */
|
|
68
67
|
onChildExit(nodeId: string, status: FleetExitStatus): void;
|
|
69
68
|
/** Test seam — delivers a synthetic exit through the SAME path as
|
|
70
69
|
* onChildExit, so fabricated-row tests drive policy without a process. */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// learns when the work finishes — this seeds the subscription
|
|
13
13
|
// graph to mirror the spawn structure. A `spawned_by` audit edge
|
|
14
14
|
// is also recorded.
|
|
15
|
-
import { envHomeOverride } from '../../shared/env.js';
|
|
15
|
+
import { envHomeOverride, envRuntimeHomeOverride } from '../../shared/env.js';
|
|
16
16
|
import { randomBytes } from 'node:crypto';
|
|
17
17
|
import { mkdirSync } from 'node:fs';
|
|
18
18
|
import { createNode, getNode, getRow, subscribe, recordSpawn, subscribersOf, contextDir, } from '../canvas/index.js';
|
|
@@ -206,6 +206,11 @@ export function nodeEnv(meta) {
|
|
|
206
206
|
const home = envHomeOverride();
|
|
207
207
|
if (home !== undefined && home !== '')
|
|
208
208
|
env['CRTR_HOME'] = home;
|
|
209
|
+
// Propagate an explicit runtime home so broker-side `crtr` invocations use
|
|
210
|
+
// the same installed generation despite an isolated HOME.
|
|
211
|
+
const runtimeHome = envRuntimeHomeOverride();
|
|
212
|
+
if (runtimeHome !== undefined && runtimeHome !== '')
|
|
213
|
+
env['CRTR_RUNTIME_HOME'] = runtimeHome;
|
|
209
214
|
// Propagate the subtree-root node id. The broker uses it to group a subtree;
|
|
210
215
|
// each launch site also emits the authoritative rootOfSpine(nodeId) over this
|
|
211
216
|
// passthrough.
|
package/dist/daemon/fleet.js
CHANGED
|
@@ -122,10 +122,7 @@ export class DaemonFleet {
|
|
|
122
122
|
#enqueue;
|
|
123
123
|
#now;
|
|
124
124
|
#map = new Map();
|
|
125
|
-
/** Slots claimed
|
|
126
|
-
* or a broker that exited and whose exit-policy job has not yet decided
|
|
127
|
-
* whether to relaunch it. Counting both against the cap is what keeps a
|
|
128
|
-
* 1:1 respawn from losing its own slot to a concurrent launcher. */
|
|
125
|
+
/** Slots claimed by launches that have not registered a broker yet. */
|
|
129
126
|
#reservations = new Set();
|
|
130
127
|
#lastCapacityLogAt = Number.NEGATIVE_INFINITY;
|
|
131
128
|
constructor(opts) {
|
|
@@ -191,13 +188,15 @@ export class DaemonFleet {
|
|
|
191
188
|
this.#reservations.delete(nodeId);
|
|
192
189
|
}
|
|
193
190
|
onChildExit(nodeId, status) {
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
// and
|
|
197
|
-
|
|
191
|
+
// The real child exit is the liveness boundary. Its queued observation only
|
|
192
|
+
// records diagnostics, so it cannot retain capacity after the process is
|
|
193
|
+
// gone and make an explicit replacement look already live.
|
|
194
|
+
const meta = getNode(nodeId);
|
|
195
|
+
const launchedMs = meta?.launched_at == null ? Number.NaN : Date.parse(meta.launched_at);
|
|
196
|
+
const uptimeMs = Number.isFinite(launchedMs) ? Math.max(0, this.#now() - launchedMs) : null;
|
|
197
|
+
const consecutiveFailures = meta?.respawn_failures ?? 0;
|
|
198
198
|
this.#map.delete(nodeId);
|
|
199
|
-
this.#
|
|
200
|
-
this.#enqueue(() => this.#runExitPolicy(nodeId, status));
|
|
199
|
+
this.#enqueue(() => this.#emitExitObserved(nodeId, status, uptimeMs, consecutiveFailures));
|
|
201
200
|
}
|
|
202
201
|
deliverExit(nodeId, status) {
|
|
203
202
|
this.onChildExit(nodeId, status);
|
|
@@ -223,17 +222,6 @@ export class DaemonFleet {
|
|
|
223
222
|
return true;
|
|
224
223
|
}
|
|
225
224
|
dispose() { }
|
|
226
|
-
async #runExitPolicy(nodeId, status) {
|
|
227
|
-
try {
|
|
228
|
-
const launchedAt = getNode(nodeId)?.launched_at;
|
|
229
|
-
const launchedMs = launchedAt == null ? Number.NaN : Date.parse(launchedAt);
|
|
230
|
-
const uptimeMs = Number.isFinite(launchedMs) ? Math.max(0, this.#now() - launchedMs) : null;
|
|
231
|
-
this.#emitExitObserved(nodeId, status, uptimeMs, getNode(nodeId)?.respawn_failures ?? 0);
|
|
232
|
-
}
|
|
233
|
-
finally {
|
|
234
|
-
this.releaseReservation(nodeId);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
225
|
#emitExitObserved(nodeId, status, uptimeMs, consecutiveFailures) {
|
|
238
226
|
emitEvent({
|
|
239
227
|
level: 'info',
|
package/dist/shared/env.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export declare function envModelExact(): boolean;
|
|
|
15
15
|
* only when a caller needs the override itself — to check whether one is
|
|
16
16
|
* set, or to propagate it verbatim into a child process's env. */
|
|
17
17
|
export declare function envHomeOverride(): string | undefined;
|
|
18
|
+
/** The raw installed-runtime directory override, unresolved. */
|
|
19
|
+
export declare function envRuntimeHomeOverride(): string | undefined;
|
|
18
20
|
/** Dead-provider watchdog timeout (`CRTR_STREAM_WATCHDOG_MS`), 5 minutes by
|
|
19
21
|
* default. See `runtime/stream-watchdog.ts` for what it guards. */
|
|
20
22
|
export declare function envStreamWatchdogMs(): number;
|
package/dist/shared/env.js
CHANGED
|
@@ -51,6 +51,10 @@ export function envModelExact() {
|
|
|
51
51
|
export function envHomeOverride() {
|
|
52
52
|
return process.env['CRTR_HOME'];
|
|
53
53
|
}
|
|
54
|
+
/** The raw installed-runtime directory override, unresolved. */
|
|
55
|
+
export function envRuntimeHomeOverride() {
|
|
56
|
+
return process.env['CRTR_RUNTIME_HOME'];
|
|
57
|
+
}
|
|
54
58
|
// Tuning
|
|
55
59
|
/** Generic `Number(raw)` parse with a positive-finite guard, shared by every
|
|
56
60
|
* millisecond tuning var below (each previously reimplemented this locally,
|
package/package.json
CHANGED
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.272",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/crouter",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.272",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|