@bridge4dev/runner 0.64.1 → 0.65.1
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/adapters/agent-tasks.d.ts +113 -0
- package/dist/adapters/agent-tasks.js +260 -0
- package/dist/adapters/claude.js +45 -236
- package/dist/adapters/codex-subagents.d.ts +169 -0
- package/dist/adapters/codex-subagents.js +569 -0
- package/dist/adapters/codex.d.ts +4 -0
- package/dist/adapters/codex.js +219 -20
- package/dist/daemon-exit.d.ts +40 -0
- package/dist/daemon-exit.js +29 -0
- package/dist/index.js +15 -17
- package/dist/session-cage.d.ts +49 -0
- package/dist/session-cage.js +238 -12
- package/dist/session-stall.d.ts +25 -0
- package/dist/session-stall.js +84 -0
- package/dist/supervisor.d.ts +86 -2
- package/dist/supervisor.js +215 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/ws-client.js +14 -0
- package/package.json +1 -1
package/dist/supervisor.js
CHANGED
|
@@ -28,9 +28,9 @@ import { cageAuthority } from './cage-authority.js';
|
|
|
28
28
|
import { invalidateAgentVersions, measureAgentVersions, } from './agent-versions.js';
|
|
29
29
|
import { rememberWorkspacePath } from './environment.js';
|
|
30
30
|
import { hostLoadChangedEnough, hostLoadHeartbeatDue, readHostLoad, HOST_LOAD_HEARTBEAT_MS, HOST_LOAD_SAMPLE_INTERVAL_MS, } from './host-load.js';
|
|
31
|
-
import { markScopeOomKillsSeen, readScopeHold, readScopeMemoryStatus, readSliceLimits, sessionAgentPid, sessionCage, sessionScopeUnitOf, setLiveLadderSource, stoppedProcesses, sweepOrphanSessionScopes, } from './session-cage.js';
|
|
31
|
+
import { markScopeOomKillsSeen, readScopeHold, readScopeMemoryStatus, readSliceLimits, restartStoppedCommands, sessionAgentPid, sessionCage, sessionScopeUnitOf, setLiveLadderSource, stopSessionScope, stoppedProcesses, sweepOrphanSessionScopes, } from './session-cage.js';
|
|
32
32
|
import { allocateSessionMemory, planLimitMove, SESSION_GUARANTEE_BYTES, } from './session-allocator.js';
|
|
33
|
-
import { freshStallState, pickKillCandidate, readScopeProcesses, readStallSample, setScopeProperties, signalSubtree, stallStep, STALL_GRACE_MS, STALL_MUTE_AFTER_LOWER_MS, STALL_SIGKILL_AFTER_MS, STALL_WINDOW_MS, } from './session-stall.js';
|
|
33
|
+
import { countRunningCommands, freshStallState, pickKillCandidate, readScopeProcesses, readStallSample, setScopeProperties, signalSubtree, stallStep, STALL_GRACE_MS, STALL_MUTE_AFTER_LOWER_MS, STALL_SIGKILL_AFTER_MS, STALL_WINDOW_MS, } from './session-stall.js';
|
|
34
34
|
import { machineReserveBytes } from './service-unit.js';
|
|
35
35
|
import { sessionLimitsChangedEnough, sessionLimitsHeartbeatDue, SESSION_LIMITS_HEARTBEAT_MS, } from './session-limits.js';
|
|
36
36
|
import { composeMessageWithAttachments, saveAttachments, } from './attachments.js';
|
|
@@ -1693,7 +1693,28 @@ export class Supervisor {
|
|
|
1693
1693
|
this.lastHostLoadSentAt = now;
|
|
1694
1694
|
}
|
|
1695
1695
|
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Set by {@link shutdown}, and read wherever this supervisor could still be
|
|
1698
|
+
* ASKED to do something after it has let go of everything (#431).
|
|
1699
|
+
*
|
|
1700
|
+
* It exists because the daemon now outlives `shutdown()` by up to three
|
|
1701
|
+
* seconds — the time its cages are given to stop. Until that change every
|
|
1702
|
+
* caller exited in the same tick and «after shutdown» was not a state
|
|
1703
|
+
* anything could be in.
|
|
1704
|
+
*/
|
|
1705
|
+
shuttingDown = false;
|
|
1696
1706
|
async onFrame(frame) {
|
|
1707
|
+
/**
|
|
1708
|
+
* Nothing from the wire is answered once the shutdown has started.
|
|
1709
|
+
*
|
|
1710
|
+
* The socket is closed right after the synchronous half of `shutdown()`,
|
|
1711
|
+
* but closing is a handshake: a frame already in flight still lands. The
|
|
1712
|
+
* client refuses to deliver it (`ws-client.ts`) and this is the second
|
|
1713
|
+
* door, on the side that would act on it — an `event_ack` here deletes the
|
|
1714
|
+
* journal of a session this supervisor has already released.
|
|
1715
|
+
*/
|
|
1716
|
+
if (this.shuttingDown)
|
|
1717
|
+
return;
|
|
1697
1718
|
switch (frame.type) {
|
|
1698
1719
|
case 'hello_ack':
|
|
1699
1720
|
this.helloAcked = true;
|
|
@@ -1866,6 +1887,21 @@ export class Supervisor {
|
|
|
1866
1887
|
* the ask any more.
|
|
1867
1888
|
*/
|
|
1868
1889
|
withdrawReason = 'not_held') {
|
|
1890
|
+
/**
|
|
1891
|
+
* Nothing is started once this runner is going down (#431).
|
|
1892
|
+
*
|
|
1893
|
+
* The wire is already shut by then, but this door has a second caller: the
|
|
1894
|
+
* tail of `pumpEvents`, which runs a microtask after the agent's stream
|
|
1895
|
+
* ends — and since the shutdown waits for its cages, the process is still
|
|
1896
|
+
* here to run it. What it would build first is a worktree and a branch, and
|
|
1897
|
+
* what it would build last is a fresh cage, moments before `process.exit`.
|
|
1898
|
+
*/
|
|
1899
|
+
if (this.shuttingDown) {
|
|
1900
|
+
log.warn('supervisor: not starting a session, this runner is going down', {
|
|
1901
|
+
sessionId: descriptor.id,
|
|
1902
|
+
});
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1869
1905
|
const existing = this.sessions.get(descriptor.id);
|
|
1870
1906
|
if (existing) {
|
|
1871
1907
|
// A resume (higher epoch) can land while the previous life is still
|
|
@@ -2192,6 +2228,22 @@ export class Supervisor {
|
|
|
2192
2228
|
});
|
|
2193
2229
|
return LAUNCH_REFUSED;
|
|
2194
2230
|
}
|
|
2231
|
+
/**
|
|
2232
|
+
* …and no agent at all once the daemon is on its way out (#431).
|
|
2233
|
+
*
|
|
2234
|
+
* The five one-shot relaunches live in the tail of `pumpEvents`, which runs
|
|
2235
|
+
* a microtask after the event stream of the dying process ends — and since
|
|
2236
|
+
* the shutdown waits for its cages, the process is still here to run it.
|
|
2237
|
+
* A launch there would put a NEW cage on the machine moments before
|
|
2238
|
+
* `process.exit`, leaving behind exactly the orphan this ticket is about.
|
|
2239
|
+
* Found by the independent review of #431.
|
|
2240
|
+
*/
|
|
2241
|
+
if (this.shuttingDown) {
|
|
2242
|
+
log.warn('supervisor: not launching an agent, this runner is going down', {
|
|
2243
|
+
sessionId: descriptor.id,
|
|
2244
|
+
});
|
|
2245
|
+
return LAUNCH_REFUSED;
|
|
2246
|
+
}
|
|
2195
2247
|
// An exhausted USD budget must not relaunch $0.01-floor processes (QA-96 F4).
|
|
2196
2248
|
// Codex reports no cost at all, so its costUsd never leaves 0 — gating on it
|
|
2197
2249
|
// would be a limit that can never fire while the UI shows $0.00. Those
|
|
@@ -3404,6 +3456,39 @@ export class Supervisor {
|
|
|
3404
3456
|
clearTimeout(running.compactionWatchdog);
|
|
3405
3457
|
delete running.compactionWatchdog;
|
|
3406
3458
|
}
|
|
3459
|
+
/**
|
|
3460
|
+
* A card that interrupted a RESTING session has been answered: go back to
|
|
3461
|
+
* rest instead of reporting a turn (#382).
|
|
3462
|
+
*
|
|
3463
|
+
* Which card it was does not matter — a Codex helper's approval, a Claude
|
|
3464
|
+
* background subagent's, an ask either of them parked while the session was
|
|
3465
|
+
* already the person's. What matters is that no turn of this session was
|
|
3466
|
+
* running when the card went out, so there is no turn to go back to and
|
|
3467
|
+
* nothing that would end one: on Codex the helper's own ending is explicitly
|
|
3468
|
+
* not the session's, so «Working» stood until somebody typed.
|
|
3469
|
+
*
|
|
3470
|
+
* Only when the burst is over — both card sets empty — because answering one
|
|
3471
|
+
* of three still leaves the session parked on the other two. The frame
|
|
3472
|
+
* carries the background count like every other status report, so the badge
|
|
3473
|
+
* and the Inbox see «resting, with helpers» rather than «your turn».
|
|
3474
|
+
*
|
|
3475
|
+
* Returns true when it handled the resolution, so the callers' «the human
|
|
3476
|
+
* answered, bill again» branches stay out of it.
|
|
3477
|
+
*/
|
|
3478
|
+
restAfterCard(running) {
|
|
3479
|
+
const back = running.restBeforeCard;
|
|
3480
|
+
if (!back)
|
|
3481
|
+
return false;
|
|
3482
|
+
if (running.openPermissions.size > 0 || running.openQuestions.size > 0)
|
|
3483
|
+
return true;
|
|
3484
|
+
// `reportStatus` clears the memory — the status it sends is the newest
|
|
3485
|
+
// truth about this session, whatever it is.
|
|
3486
|
+
this.reportStatus(running.descriptor.id, back, {
|
|
3487
|
+
costUsd: running.costUsd,
|
|
3488
|
+
activeMs: Supervisor.spentMs(running),
|
|
3489
|
+
});
|
|
3490
|
+
return true;
|
|
3491
|
+
}
|
|
3407
3492
|
/**
|
|
3408
3493
|
* Record how many subagents are alive, and say so when it matters (#236).
|
|
3409
3494
|
*
|
|
@@ -3817,6 +3902,8 @@ export class Supervisor {
|
|
|
3817
3902
|
source: event.source,
|
|
3818
3903
|
reason: event.reason,
|
|
3819
3904
|
});
|
|
3905
|
+
if (this.restAfterCard(running))
|
|
3906
|
+
return;
|
|
3820
3907
|
if (event.source === 'user' &&
|
|
3821
3908
|
event.allow &&
|
|
3822
3909
|
running.lastReported === 'WAITING_PERMISSION') {
|
|
@@ -3844,6 +3931,11 @@ export class Supervisor {
|
|
|
3844
3931
|
// is the bug session 7 removed (five of the first twelve prod sessions
|
|
3845
3932
|
// died having spent their budget waiting for a human).
|
|
3846
3933
|
running.openQuestions.add(event.askId);
|
|
3934
|
+
// #382, the same as a permission card: an ask from work that outlives
|
|
3935
|
+
// the turn finds the session at rest, and the answer must put it back.
|
|
3936
|
+
if (running.lastReported === 'WAITING_INPUT' || running.lastReported === 'REVIEW') {
|
|
3937
|
+
running.restBeforeCard = running.lastReported;
|
|
3938
|
+
}
|
|
3847
3939
|
this.sendEvent(running, 'question', {
|
|
3848
3940
|
askId: event.askId,
|
|
3849
3941
|
questions: event.questions,
|
|
@@ -3872,6 +3964,8 @@ export class Supervisor {
|
|
|
3872
3964
|
// `openQuestions.size` matters: both agents can park several asks at
|
|
3873
3965
|
// once, and reporting RUNNING while another card is still waiting would
|
|
3874
3966
|
// bill a human's thinking time all over again (QA-106 M4).
|
|
3967
|
+
if (this.restAfterCard(running))
|
|
3968
|
+
return;
|
|
3875
3969
|
if (event.source === 'user' &&
|
|
3876
3970
|
running.openQuestions.size === 0 &&
|
|
3877
3971
|
running.lastReported === 'WAITING_INPUT') {
|
|
@@ -7563,6 +7657,13 @@ export class Supervisor {
|
|
|
7563
7657
|
// The API infers WAITING_PERMISSION from the event itself, so this never
|
|
7564
7658
|
// goes through reportStatus — but the budget clock still has to stop, or
|
|
7565
7659
|
// an ask-mode session bills every second the human spends reading the card.
|
|
7660
|
+
//
|
|
7661
|
+
// #382: remember what the card interrupted. A card from work that
|
|
7662
|
+
// outlives the turn finds the session at rest, and the answer must put it
|
|
7663
|
+
// back there rather than into a turn nobody is running.
|
|
7664
|
+
if (running.lastReported === 'WAITING_INPUT' || running.lastReported === 'REVIEW') {
|
|
7665
|
+
running.restBeforeCard = running.lastReported;
|
|
7666
|
+
}
|
|
7566
7667
|
running.lastReported = 'WAITING_PERMISSION';
|
|
7567
7668
|
this.syncBudgetClock(running);
|
|
7568
7669
|
}
|
|
@@ -7572,6 +7673,8 @@ export class Supervisor {
|
|
|
7572
7673
|
const running = this.sessions.get(sessionId);
|
|
7573
7674
|
if (running) {
|
|
7574
7675
|
running.lastReported = status;
|
|
7676
|
+
// #382: any status report is newer than the rest a card interrupted.
|
|
7677
|
+
delete running.restBeforeCard;
|
|
7575
7678
|
// Coming to rest ends the busy period the once-per-turn notices were
|
|
7576
7679
|
// limited to: the next one is about a new answer and deserves saying.
|
|
7577
7680
|
if (!Supervisor.MID_TURN_STATUSES.includes(status)) {
|
|
@@ -7625,8 +7728,40 @@ export class Supervisor {
|
|
|
7625
7728
|
// changed, and the interval in the constructor is the net under both.
|
|
7626
7729
|
this.publishSlots();
|
|
7627
7730
|
}
|
|
7628
|
-
/**
|
|
7629
|
-
|
|
7731
|
+
/**
|
|
7732
|
+
* How long the whole fleet of cages is given to stop before this process goes
|
|
7733
|
+
* anyway (#431).
|
|
7734
|
+
*
|
|
7735
|
+
* The bus has been measured at 2.7 s under load and the stops run in
|
|
7736
|
+
* parallel, so three seconds is room for all of them on a bad day. Going over
|
|
7737
|
+
* it costs nothing: `stop` has been ACCEPTED by then, and systemd finishes
|
|
7738
|
+
* the job whether or not anybody is still here to watch. systemd's own
|
|
7739
|
+
* patience with this service is `TimeoutStopSec`, far above this.
|
|
7740
|
+
*/
|
|
7741
|
+
static CAGE_STOP_BUDGET_MS = 3_000;
|
|
7742
|
+
/**
|
|
7743
|
+
* Graceful daemon shutdown: kill agents, take their cages with them, keep
|
|
7744
|
+
* sessions resumable server-side.
|
|
7745
|
+
*
|
|
7746
|
+
* **Everything that has to survive this process happens BEFORE the first
|
|
7747
|
+
* `await`** — the withdrawn cards, the feed line, and the word to each agent
|
|
7748
|
+
* that it is over (a signal for Codex; for Claude, stdin closed and the SDK's
|
|
7749
|
+
* own `close()`, whose SIGTERM is a timer this process will not live to see —
|
|
7750
|
+
* on a caged machine what actually ends it is the stop below). A caller that
|
|
7751
|
+
* does not wait (the suite does not) gets exactly the shutdown it always got;
|
|
7752
|
+
* what it misses is only the stop of the cgroups. Written this way rather
|
|
7753
|
+
* than as two methods because «kill the agent» and «put its cage out» are one
|
|
7754
|
+
* action, and the shape of the code is what stops them drifting apart again
|
|
7755
|
+
* (грабля §479).
|
|
7756
|
+
*
|
|
7757
|
+
* What the caller owes in return: close the socket between the two halves.
|
|
7758
|
+
* The events above need it open, and the seconds below must not be a window
|
|
7759
|
+
* in which a `session_start` arrives at a supervisor that has already let go
|
|
7760
|
+
* of everything (`index.ts`).
|
|
7761
|
+
*/
|
|
7762
|
+
async shutdown() {
|
|
7763
|
+
// First line: every door that could put work back on this machine reads it.
|
|
7764
|
+
this.shuttingDown = true;
|
|
7630
7765
|
clearInterval(this.slotsTimer);
|
|
7631
7766
|
clearInterval(this.hostLoadTimer);
|
|
7632
7767
|
clearInterval(this.stallTimer);
|
|
@@ -7635,6 +7770,10 @@ export class Supervisor {
|
|
|
7635
7770
|
clearTimeout(this.agentCleanupFirstTimer);
|
|
7636
7771
|
clearInterval(this.agentCleanupTimer);
|
|
7637
7772
|
this.authRelay.cancel();
|
|
7773
|
+
const unitOf = this.opts.sessionScopeUnitOf ?? sessionScopeUnitOf;
|
|
7774
|
+
const readProcesses = this.opts.readScopeProcesses ?? readScopeProcesses;
|
|
7775
|
+
const agentPidOf = this.opts.sessionAgentPid ?? sessionAgentPid;
|
|
7776
|
+
const cages = [];
|
|
7638
7777
|
for (const running of this.sessions.values()) {
|
|
7639
7778
|
this.clearBudgetTimers(running);
|
|
7640
7779
|
// BEFORE stop(), and from here rather than from the adapter: the adapter
|
|
@@ -7656,15 +7795,86 @@ export class Supervisor {
|
|
|
7656
7795
|
error: String(error),
|
|
7657
7796
|
});
|
|
7658
7797
|
}
|
|
7798
|
+
/**
|
|
7799
|
+
* What this restart costs, in the person's units, before anything is
|
|
7800
|
+
* killed (#431).
|
|
7801
|
+
*
|
|
7802
|
+
* The count comes from the cage itself rather than from anything this
|
|
7803
|
+
* process believes: the runner does not track what the agent started —
|
|
7804
|
+
* that is the whole shape of the problem — and the cgroup does. Read
|
|
7805
|
+
* BEFORE `stop()`, because a moment later there is nothing left to count.
|
|
7806
|
+
*
|
|
7807
|
+
* Silent when the number is zero: an agent that was only thinking loses
|
|
7808
|
+
* nothing, and «0 commands were stopped» is a line a person has to read
|
|
7809
|
+
* and cannot use (D8).
|
|
7810
|
+
*/
|
|
7811
|
+
const unit = unitOf(running.descriptor.id);
|
|
7812
|
+
if (unit !== null) {
|
|
7813
|
+
cages.push(unit);
|
|
7814
|
+
try {
|
|
7815
|
+
const commands = countRunningCommands(readProcesses(unit), agentPidOf(running.descriptor.id));
|
|
7816
|
+
if (commands > 0) {
|
|
7817
|
+
this.sendEvent(running, 'notice', {
|
|
7818
|
+
level: 'warn',
|
|
7819
|
+
text: restartStoppedCommands(commands),
|
|
7820
|
+
});
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
7823
|
+
catch (error) {
|
|
7824
|
+
// /proc said nothing readable. The cage is still stopped below — the
|
|
7825
|
+
// person simply does not get the number.
|
|
7826
|
+
log.warn('supervisor: could not count what this session was running', {
|
|
7827
|
+
sessionId: running.descriptor.id,
|
|
7828
|
+
error: String(error),
|
|
7829
|
+
});
|
|
7830
|
+
}
|
|
7831
|
+
}
|
|
7659
7832
|
// The reason matters here: this path also runs for «Update runner», and a
|
|
7660
7833
|
// card that vanishes during an update must say why (session 12).
|
|
7661
|
-
|
|
7834
|
+
//
|
|
7835
|
+
// Guarded like its neighbours in this loop: one adapter that throws on
|
|
7836
|
+
// its way out must not cost every OTHER session the stop of its cage —
|
|
7837
|
+
// the lines below it are what puts them out (independent review of #431).
|
|
7838
|
+
try {
|
|
7839
|
+
running.session?.stop('runner_restarted');
|
|
7840
|
+
}
|
|
7841
|
+
catch (error) {
|
|
7842
|
+
log.warn('supervisor: an agent did not take its stop cleanly', {
|
|
7843
|
+
sessionId: running.descriptor.id,
|
|
7844
|
+
error: String(error),
|
|
7845
|
+
});
|
|
7846
|
+
}
|
|
7662
7847
|
}
|
|
7663
7848
|
this.sessions.clear();
|
|
7664
7849
|
// A build that was mid-flight is abandoned, not judged: its row stays
|
|
7665
7850
|
// RUNNING until the API's sweep turns it into LOST. A verdict nobody
|
|
7666
7851
|
// observed must never become PASSED.
|
|
7667
7852
|
this.verify.shutdown();
|
|
7853
|
+
await this.stopCages(cages);
|
|
7854
|
+
}
|
|
7855
|
+
/**
|
|
7856
|
+
* Put out every cage this daemon was holding, in parallel and on a clock.
|
|
7857
|
+
*
|
|
7858
|
+
* Until 0.65.0 nothing did this: `releaseSessionScope` hangs off the agent
|
|
7859
|
+
* process's `exit`, and on a restart the daemon is gone before that event
|
|
7860
|
+
* fires. What stayed behind was a whole tree — a build, its docker client,
|
|
7861
|
+
* their children — holding the name of a scope systemd will then refuse to
|
|
7862
|
+
* reuse, so the FIRST start of that session after the restart died as
|
|
7863
|
+
* «exited before the session was ready (code 1)» (#431).
|
|
7864
|
+
*/
|
|
7865
|
+
async stopCages(units) {
|
|
7866
|
+
if (units.length === 0)
|
|
7867
|
+
return;
|
|
7868
|
+
const stop = this.opts.stopSessionScope ?? stopSessionScope;
|
|
7869
|
+
log.info('supervisor: stopping the cages of this runner', { count: units.length });
|
|
7870
|
+
const all = Promise.allSettled(units.map((unit) => stop(unit))).then(() => undefined);
|
|
7871
|
+
let timer;
|
|
7872
|
+
const budget = new Promise((resolve) => {
|
|
7873
|
+
timer = setTimeout(resolve, this.opts.cageStopBudgetMs ?? Supervisor.CAGE_STOP_BUDGET_MS);
|
|
7874
|
+
});
|
|
7875
|
+
await Promise.race([all, budget]);
|
|
7876
|
+
if (timer)
|
|
7877
|
+
clearTimeout(timer);
|
|
7668
7878
|
}
|
|
7669
7879
|
}
|
|
7670
7880
|
/**
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.65.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/dist/ws-client.js
CHANGED
|
@@ -137,6 +137,20 @@ export class RunnerWsClient {
|
|
|
137
137
|
this.lastInboundAt = Date.now();
|
|
138
138
|
});
|
|
139
139
|
socket.on('message', (data) => {
|
|
140
|
+
/**
|
|
141
|
+
* A client that was stopped answers nothing, in either direction (#431).
|
|
142
|
+
*
|
|
143
|
+
* `close()` is the START of a handshake, not a cut: the socket lives on
|
|
144
|
+
* for a round trip, and this listener used to keep delivering frames off
|
|
145
|
+
* it. That was harmless while every caller of `stop()` exited in the same
|
|
146
|
+
* tick; since the daemon waits for its cages to be put out, the window is
|
|
147
|
+
* seconds wide, and the supervisor on the other side of `frame` has
|
|
148
|
+
* already let go of every session. An `event_ack` landing there DELETES
|
|
149
|
+
* the journal of a live session (found by the independent review of
|
|
150
|
+
* #431); a `session_start` starts an agent nobody will ever stop.
|
|
151
|
+
*/
|
|
152
|
+
if (this.stopped)
|
|
153
|
+
return;
|
|
140
154
|
this.lastInboundAt = Date.now();
|
|
141
155
|
let parsed;
|
|
142
156
|
try {
|
package/package.json
CHANGED