atris 3.74.18 → 3.74.20
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/bin/atris.js +131 -131
- package/commands/autoland.js +18 -2
- package/commands/fleet-report.js +3 -2
- package/commands/lesson.js +2 -1
- package/lib/campaign-cadence.js +6 -5
- package/lib/campaign-loop.js +2 -0
- package/lib/campaign-missions.js +4 -1
- package/lib/campaign-rsi.js +15 -6
- package/lib/campaign-schema.js +3 -2
- package/lib/gm-phone-page.js +23 -7
- package/lib/orb-context.js +3 -2
- package/lib/task-receipt.js +3 -1
- package/package.json +1 -1
package/commands/autoland.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const { spawnSync } = require('child_process');
|
|
7
|
+
const { localDay } = require('../lib/local-day');
|
|
7
8
|
|
|
8
9
|
const autoland = require('../lib/autoland');
|
|
9
10
|
const { gateForHuman, plainLandingReason } = require('../lib/voice-gate');
|
|
@@ -1006,6 +1007,19 @@ function mergedCloseNote(receipt) {
|
|
|
1006
1007
|
+ (receipt.merged_close_error ? ', could not check which jobs already merged' : '');
|
|
1007
1008
|
}
|
|
1008
1009
|
|
|
1010
|
+
// The digest goes out once per local day. A stamp written before the switch
|
|
1011
|
+
// to local days is the UTC day of a send at this same local hour, today or
|
|
1012
|
+
// yesterday. Across a clock change both can be the same UTC day; that stamp
|
|
1013
|
+
// cannot say which, so the digest goes out rather than go quiet.
|
|
1014
|
+
function digestSentToday(state, now = new Date()) {
|
|
1015
|
+
if (!state || !state.last_digest_date) return false;
|
|
1016
|
+
if (state.last_digest_local) return state.last_digest_date === localDay(now);
|
|
1017
|
+
const utcDay = (d) => d.toISOString().slice(0, 10);
|
|
1018
|
+
const yesterday = new Date(now);
|
|
1019
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
1020
|
+
return state.last_digest_date === utcDay(now) && state.last_digest_date !== utcDay(yesterday);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1009
1023
|
async function runTickBody(root, { json, policy, receipt, engineValidationDeps = {} }) {
|
|
1010
1024
|
|
|
1011
1025
|
try {
|
|
@@ -1298,9 +1312,9 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
1298
1312
|
}
|
|
1299
1313
|
|
|
1300
1314
|
// 4. daily digest at the configured hour
|
|
1301
|
-
const today =
|
|
1315
|
+
const today = localDay();
|
|
1302
1316
|
const digestHour = Number(policy.digest_hour ?? autoland.DEFAULT_DIGEST_HOUR);
|
|
1303
|
-
if (new Date().getHours() === digestHour && state
|
|
1317
|
+
if (new Date().getHours() === digestHour && !digestSentToday(state)) {
|
|
1304
1318
|
receipt.digest_due = true;
|
|
1305
1319
|
let waitingWishes = [];
|
|
1306
1320
|
try {
|
|
@@ -1326,6 +1340,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
|
|
|
1326
1340
|
}
|
|
1327
1341
|
receipt.digest_text = text;
|
|
1328
1342
|
state.last_digest_date = today;
|
|
1343
|
+
state.last_digest_local = true;
|
|
1329
1344
|
delete state.janitor;
|
|
1330
1345
|
|
|
1331
1346
|
// 5. once a day, keep the receipt shelf lean: compress old run receipts
|
|
@@ -1472,6 +1487,7 @@ function autolandCommand(args = []) {
|
|
|
1472
1487
|
|
|
1473
1488
|
module.exports = {
|
|
1474
1489
|
autolandCommand,
|
|
1490
|
+
digestSentToday,
|
|
1475
1491
|
digestNextMoves,
|
|
1476
1492
|
acceptSweepTimeoutMs,
|
|
1477
1493
|
describeAcceptKillCause,
|
package/commands/fleet-report.js
CHANGED
|
@@ -17,6 +17,7 @@ const { loadCredentials } = require('../utils/auth');
|
|
|
17
17
|
const { apiRequestJson } = require('../utils/api');
|
|
18
18
|
const { looksLikeBusinessSlug, requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
19
19
|
const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
20
|
+
const { localDay } = require('../lib/local-day');
|
|
20
21
|
|
|
21
22
|
function sleep(ms) {
|
|
22
23
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -58,7 +59,7 @@ async function fetchDailyReport(token, businessId) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
async function writeReportToComputer(token, businessId, workspaceId, report) {
|
|
61
|
-
const date = report.date ||
|
|
62
|
+
const date = report.date || localDay();
|
|
62
63
|
const remotePath = `/workspace/atris/reports/daily-${date}.md`;
|
|
63
64
|
const b64 = Buffer.from(report.markdown || '', 'utf8').toString('base64');
|
|
64
65
|
const command =
|
|
@@ -231,4 +232,4 @@ async function fleetReport() {
|
|
|
231
232
|
process.exitCode = delivered > 0 || dryRun || results.every((r) => r.reason === 'asleep') ? 0 : 1;
|
|
232
233
|
}
|
|
233
234
|
|
|
234
|
-
module.exports = { fleetReport, formatScoreboardPnl };
|
|
235
|
+
module.exports = { fleetReport, formatScoreboardPnl, writeReportToComputer };
|
package/commands/lesson.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
} = require('./autopilot');
|
|
9
9
|
const { detectLessonContradictions } = require('../lib/lesson-contradiction');
|
|
10
10
|
const taskDb = require('../lib/task-db');
|
|
11
|
+
const { localDay } = require('../lib/local-day');
|
|
11
12
|
const { validateDetector, appendLedgerEntry, readLedger } = require('../lib/lesson-ledger');
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -57,7 +58,7 @@ function autoResolveLessons(cwd, options = {}) {
|
|
|
57
58
|
const dryRun = !!options.dryRun;
|
|
58
59
|
const lessons = parseLessons(cwd);
|
|
59
60
|
const metadata = loadLessonMetadata(cwd);
|
|
60
|
-
const today =
|
|
61
|
+
const today = localDay();
|
|
61
62
|
const checked = [];
|
|
62
63
|
const resolved = [];
|
|
63
64
|
|
package/lib/campaign-cadence.js
CHANGED
|
@@ -67,10 +67,11 @@ function roundSummary(before, after) {
|
|
|
67
67
|
}
|
|
68
68
|
const fresh = after.missions.filter((m) => !previous.get(m.id)?.has(m.status));
|
|
69
69
|
const kept = fresh.filter((m) => m.status === 'ended' && m.outcome === 'kept').length;
|
|
70
|
-
const
|
|
70
|
+
const held = fresh.filter((m) => m.status === 'ended' && m.outcome === 'held').length;
|
|
71
|
+
const failed = fresh.filter((m) => m.status === 'ended' && m.outcome !== 'kept' && m.outcome !== 'held').length;
|
|
71
72
|
const vetoed = fresh.filter((m) => m.status === 'vetoed').length;
|
|
72
73
|
const spent = Math.round((after.budget.spent - before.budget.spent) * 100) / 100;
|
|
73
|
-
return { kept, failed, vetoed, spent, fresh };
|
|
74
|
+
return { kept, failed, held, vetoed, spent, fresh };
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// Nothing finished and the check reads the same: there is nothing to learn.
|
|
@@ -81,7 +82,7 @@ function quietRound(before, after, summary) {
|
|
|
81
82
|
|
|
82
83
|
// On a subscription the round is told without dollars, since none were paid.
|
|
83
84
|
function roundTally(view, summary) {
|
|
84
|
-
return `${summary.kept} kept, ${summary.failed} failed${paysDollars(view) ? `, $${summary.spent} spent` : ''}`;
|
|
85
|
+
return `${summary.kept} kept, ${summary.failed} failed${summary.held ? `, ${summary.held} waiting on you` : ''}${paysDollars(view) ? `, $${summary.spent} spent` : ''}`;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function reviewPrompt(view, summary, lessons) {
|
|
@@ -94,7 +95,7 @@ function reviewPrompt(view, summary, lessons) {
|
|
|
94
95
|
`Campaign: ${view.title}`,
|
|
95
96
|
`Judge: ${view.judge}`,
|
|
96
97
|
`Judge now: ${view.last_judge ? `${view.last_judge.passed ? 'passed' : 'failed'}${view.last_judge.reading !== null ? ` (reading ${view.last_judge.reading})` : ''}: ${view.answer.text || ''}` : 'never run'}`,
|
|
97
|
-
`This round: ${summary.kept} kept, ${summary.failed} failed, ${summary.vetoed} could not start${paid ? `, $${summary.spent} spent` : ''}`,
|
|
98
|
+
`This round: ${summary.kept} kept, ${summary.failed} failed, ${summary.vetoed} could not start${summary.held ? `, ${summary.held} paused for a person's review` : ''}${paid ? `, $${summary.spent} spent` : ''}`,
|
|
98
99
|
'Missions this round:',
|
|
99
100
|
missions,
|
|
100
101
|
'',
|
|
@@ -174,4 +175,4 @@ function makeCadence(root, loop, options = {}, deps = {}) {
|
|
|
174
175
|
return { run, review };
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
module.exports = { lessonsPath, makeCadence, parseDuration, readLessons, reviewPrompt };
|
|
178
|
+
module.exports = { lessonsPath, makeCadence, parseDuration, readLessons, reviewPrompt, roundSummary, roundTally };
|
package/lib/campaign-loop.js
CHANGED
|
@@ -79,6 +79,8 @@ function rankCampaignMembers(view, rsiSummary = null, events = null) {
|
|
|
79
79
|
let streak = 0;
|
|
80
80
|
for (let i = mine.length - 1; i >= 0; i -= 1) {
|
|
81
81
|
const outcome = String(mine[i].outcome || '');
|
|
82
|
+
// held waits on a person's yes: it neither adds to a streak nor ends one
|
|
83
|
+
if (outcome === 'held') continue;
|
|
82
84
|
if (outcome && outcome !== 'kept' && !mine[i].credited) streak += 1;
|
|
83
85
|
else break;
|
|
84
86
|
}
|
package/lib/campaign-missions.js
CHANGED
|
@@ -193,8 +193,11 @@ function missionCost(root, missionId, deps = {}) {
|
|
|
193
193
|
return { cost_usd: cents / 100, runs: rows.length, uncosted_runs: uncosted };
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// A mission whose last tick paused for a person's review was held, not
|
|
197
|
+
// failed: the work waits on a yes, so the campaign should say so.
|
|
196
198
|
function outcomeFor(mission) {
|
|
197
|
-
|
|
199
|
+
if (mission.status === 'complete') return 'kept';
|
|
200
|
+
return mission.last_tick_status === 'paused-for-review' ? 'held' : 'failed';
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
// Called from saveMission with the record before and after. Only the step
|
package/lib/campaign-rsi.js
CHANGED
|
@@ -40,6 +40,12 @@ function dedupeLessons(lines) {
|
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Held is a mission paused for a person's yes: neither kept nor failed, so it
|
|
44
|
+
// adds no failure, streak, or lesson.
|
|
45
|
+
function failedOutcome(outcome) {
|
|
46
|
+
return Boolean(outcome) && outcome !== 'kept' && outcome !== 'held';
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
function aggregateMembers(missions, failureOrder = missions) {
|
|
44
50
|
const byMember = new Map();
|
|
45
51
|
for (const m of missions) {
|
|
@@ -48,6 +54,7 @@ function aggregateMembers(missions, failureOrder = missions) {
|
|
|
48
54
|
byMember.set(name, {
|
|
49
55
|
member: name,
|
|
50
56
|
missions: 0,
|
|
57
|
+
held: 0,
|
|
51
58
|
kept: 0,
|
|
52
59
|
failed: 0,
|
|
53
60
|
reverted: 0,
|
|
@@ -63,12 +70,14 @@ function aggregateMembers(missions, failureOrder = missions) {
|
|
|
63
70
|
});
|
|
64
71
|
}
|
|
65
72
|
const entry = byMember.get(name);
|
|
66
|
-
|
|
73
|
+
// held is not decided yet, so it stays out of the totals a rating divides by
|
|
74
|
+
if (m.outcome === 'held') entry.held += 1;
|
|
75
|
+
else entry.missions += 1;
|
|
67
76
|
const cost = typeof m.cost_usd === 'number' && Number.isFinite(m.cost_usd) ? m.cost_usd : 0;
|
|
68
77
|
entry.cost_usd = round(entry.cost_usd + cost, 4);
|
|
69
78
|
if (m.outcome === 'kept') {
|
|
70
79
|
entry.kept += 1;
|
|
71
|
-
} else if (m.outcome) {
|
|
80
|
+
} else if (failedOutcome(m.outcome)) {
|
|
72
81
|
entry.failed += 1;
|
|
73
82
|
if (m.outcome === 'reverted') entry.reverted += 1;
|
|
74
83
|
entry.wasted_cost_usd = round(entry.wasted_cost_usd + cost, 4);
|
|
@@ -84,7 +93,7 @@ function aggregateMembers(missions, failureOrder = missions) {
|
|
|
84
93
|
for (const m of failureOrder) {
|
|
85
94
|
const entry = byMember.get(m.member || 'unknown');
|
|
86
95
|
if (m.outcome === 'kept') entry.failure_streak = 0;
|
|
87
|
-
else if (m.outcome) {
|
|
96
|
+
else if (failedOutcome(m.outcome)) {
|
|
88
97
|
entry.failure_streak += 1;
|
|
89
98
|
if (entry.failure_streak >= 2) entry.repeat_failures += 1;
|
|
90
99
|
}
|
|
@@ -223,7 +232,7 @@ function summarizeTrace(events) {
|
|
|
223
232
|
`- ${m.member} finished ${m.mission_id} on ${m.campaign_id} with ${m.reward === 0 ? 'no reading gain' : `reading change ${round(m.reward, 4)}`}; tie verify directly to a failing judge check`
|
|
224
233
|
);
|
|
225
234
|
}
|
|
226
|
-
if (m.outcome
|
|
235
|
+
if (failedOutcome(m.outcome)) {
|
|
227
236
|
rawLessons.push(
|
|
228
237
|
`- ${m.member} had ${m.mission_id} end ${m.outcome} on ${m.campaign_id}; keep scope to one checkable slice`
|
|
229
238
|
);
|
|
@@ -429,7 +438,7 @@ function writeRsiSnapshot(root, store, campaignId = null) {
|
|
|
429
438
|
);
|
|
430
439
|
const progressFile = path.join(root, 'atris', 'campaigns', cid, 'progress.md');
|
|
431
440
|
const kept = (c.missions || []).filter((m) => m.outcome === 'kept').length;
|
|
432
|
-
const failed = (c.missions || []).filter((m) => m.outcome
|
|
441
|
+
const failed = (c.missions || []).filter((m) => failedOutcome(m.outcome)).length;
|
|
433
442
|
const progressLines = [
|
|
434
443
|
`# Progress: ${c.title}`,
|
|
435
444
|
'',
|
|
@@ -559,7 +568,7 @@ function selectNextCampaigns(store, rsiSummary = null) {
|
|
|
559
568
|
: rankCampaignMembers(current, rsi, events);
|
|
560
569
|
const benched = ranked.filter((r) => r.benched).map((r) => r.member);
|
|
561
570
|
const warm = ranked.find((r) => !r.benched) || ranked[0] || { member: v.owner, overall: 75 };
|
|
562
|
-
const failedCount = (v.missions || []).filter((m) => m.status === 'ended' && m.outcome !== 'kept').length;
|
|
571
|
+
const failedCount = (v.missions || []).filter((m) => m.status === 'ended' && m.outcome !== 'kept' && m.outcome !== 'held').length;
|
|
563
572
|
const recommendation = warm.overall < 65
|
|
564
573
|
? `add seat ${topStoreMember} (top ovr) to replace cold lineup (${ranked.map((r) => `${r.member} ${r.overall} ovr`).join(', ')})`
|
|
565
574
|
: benched.length
|
package/lib/campaign-schema.js
CHANGED
|
@@ -33,8 +33,9 @@ const STATES = Object.freeze([
|
|
|
33
33
|
const STOP_REASONS = Object.freeze(['judge_passed', 'stopped', 'done_by_human', 'budget_spent']);
|
|
34
34
|
|
|
35
35
|
// A fault is a job that never ran (engine missing, crash before the first
|
|
36
|
-
// step): the setup broke, not the work.
|
|
37
|
-
|
|
36
|
+
// step): the setup broke, not the work. Held is a mission that paused for a
|
|
37
|
+
// person's review before it could finish.
|
|
38
|
+
const MISSION_OUTCOMES = Object.freeze(['kept', 'reverted', 'failed', 'fault', 'held']);
|
|
38
39
|
|
|
39
40
|
// What a person or agent writes. Everything else is folded from events.
|
|
40
41
|
const CAMPAIGN_FIELDS = Object.freeze({
|
package/lib/gm-phone-page.js
CHANGED
|
@@ -1615,12 +1615,28 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
|
|
|
1615
1615
|
if (reduced) {
|
|
1616
1616
|
document.getElementById('lava').style.opacity = '0';
|
|
1617
1617
|
planned('leave', 200);
|
|
1618
|
-
|
|
1618
|
+
afterFade(b, 200, function () { swap(); stageFade(); orb.melt(then, to, function () {}, fast); });
|
|
1619
1619
|
return;
|
|
1620
1620
|
}
|
|
1621
1621
|
orb.melt(then, to, swap, fast);
|
|
1622
1622
|
}
|
|
1623
1623
|
|
|
1624
|
+
// a fade has run all the way: the node's opacity fade ended, or ms passed
|
|
1625
|
+
// with a frame to spare when none ran. A timer alone cut the last sixth of a
|
|
1626
|
+
// fade that started a frame late on a slow phone, and it vanished at 0.17.
|
|
1627
|
+
function afterFade(node, ms, cb) {
|
|
1628
|
+
var ran = false;
|
|
1629
|
+
var finish = function (e) {
|
|
1630
|
+
// eventPhase 2: the card's own fade, not one bubbling up from inside it
|
|
1631
|
+
if (ran || (e && (e.eventPhase !== 2 || e.propertyName !== 'opacity'))) return;
|
|
1632
|
+
ran = true;
|
|
1633
|
+
node.removeEventListener('transitionend', finish);
|
|
1634
|
+
cb();
|
|
1635
|
+
};
|
|
1636
|
+
node.addEventListener('transitionend', finish);
|
|
1637
|
+
setTimeout(finish, ms + 50);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1624
1640
|
// under reduced motion the sphere never travels: it fades in where it now sits
|
|
1625
1641
|
// and its line fades in with it
|
|
1626
1642
|
function stageFade() {
|
|
@@ -1653,7 +1669,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
|
|
|
1653
1669
|
// reduced motion: no spiral; the card fades in where it goes
|
|
1654
1670
|
if (reduced) {
|
|
1655
1671
|
var show = function () { orb.settled(); showCard(card, true); bet.busy = false; if (after) after(); };
|
|
1656
|
-
if (fadeSphere())
|
|
1672
|
+
if (fadeSphere()) afterFade(document.getElementById('stage'), 200, show); else show();
|
|
1657
1673
|
return;
|
|
1658
1674
|
}
|
|
1659
1675
|
orb.play(function (phase) {
|
|
@@ -1951,14 +1967,14 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
|
|
|
1951
1967
|
b.classList.add('leave', 'fast');
|
|
1952
1968
|
// under reduced motion the sphere fades out with the words, then fades in at its own place
|
|
1953
1969
|
fadeSphere();
|
|
1954
|
-
|
|
1970
|
+
afterFade(b, 220, function () {
|
|
1955
1971
|
b.hidden = true;
|
|
1956
1972
|
b.classList.remove('leave', 'fast', 'holding', 'up');
|
|
1957
1973
|
document.body.classList.remove('has-card', 'wide', 'waiting');
|
|
1958
1974
|
stageFade();
|
|
1959
1975
|
document.getElementById('orb').setAttribute('aria-label', 'the atris orb. tap to think of one new idea for the business');
|
|
1960
1976
|
then();
|
|
1961
|
-
}
|
|
1977
|
+
});
|
|
1962
1978
|
}
|
|
1963
1979
|
|
|
1964
1980
|
// The idea lands: the calm line's words fade while the dots spiral in
|
|
@@ -1976,7 +1992,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
|
|
|
1976
1992
|
};
|
|
1977
1993
|
if (reduced) {
|
|
1978
1994
|
fadeSphere();
|
|
1979
|
-
|
|
1995
|
+
afterFade(b, 200, function () { off(); orb.settled(); showCard(card, true); bet.busy = false; if (after) after(); });
|
|
1980
1996
|
return;
|
|
1981
1997
|
}
|
|
1982
1998
|
orb.play(function (phase) {
|
|
@@ -2041,7 +2057,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
|
|
|
2041
2057
|
b.style.minHeight = '';
|
|
2042
2058
|
b.classList.remove('up');
|
|
2043
2059
|
planned('turn', 400);
|
|
2044
|
-
|
|
2060
|
+
afterFade(b, 200, function () { paint(); void b.offsetWidth; b.classList.add('up'); afterFade(b, 200, after); });
|
|
2045
2061
|
return;
|
|
2046
2062
|
}
|
|
2047
2063
|
var from = b.offsetHeight;
|
|
@@ -2183,7 +2199,7 @@ body.waiting .bet { position: absolute; left: 24px; right: 24px; width: auto; to
|
|
|
2183
2199
|
// the card fades up as the lava eases into its spot, or once it is there
|
|
2184
2200
|
var up = false;
|
|
2185
2201
|
var play = function () { orb.play(function (phase) { if (!up && (phase === 'landing' || phase === 'settled')) { up = true; showCard(r.card, true, true); } if (phase === 'settled') bet.busy = false; }, true); };
|
|
2186
|
-
if (fadeSphere())
|
|
2202
|
+
if (fadeSphere()) afterFade(document.getElementById('stage'), 200, play); else play();
|
|
2187
2203
|
}, failed);
|
|
2188
2204
|
}
|
|
2189
2205
|
|
package/lib/orb-context.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// 0ms-to-greeting is the contract (atris/features/orb-desktop/PLAN.md).
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const { localDay } = require('./local-day');
|
|
7
8
|
|
|
8
9
|
const MAX_SUGGESTIONS = 12;
|
|
9
10
|
const WARM_THREAD_WINDOW_MS = 48 * 60 * 60 * 1000;
|
|
@@ -37,7 +38,7 @@ function normalizeTime(value) {
|
|
|
37
38
|
function todayKey(now) {
|
|
38
39
|
const stamp = normalizeTime(now);
|
|
39
40
|
if (!stamp) return '';
|
|
40
|
-
return new Date(stamp)
|
|
41
|
+
return localDay(new Date(stamp));
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function normalizeStatus(value) {
|
|
@@ -467,7 +468,7 @@ async function appendOrbChoice(root, label, when = new Date()) {
|
|
|
467
468
|
const safeRoot = typeof root === 'string' && root ? path.resolve(root) : null;
|
|
468
469
|
const cleanLabel = cleanTitle(label, 120);
|
|
469
470
|
if (!safeRoot || !cleanLabel) return { ok: false, error: 'invalid root or label' };
|
|
470
|
-
const day = when
|
|
471
|
+
const day = localDay(when);
|
|
471
472
|
const line = `orb: ${cleanLabel} · ${day}\n`;
|
|
472
473
|
try {
|
|
473
474
|
const nowPath = fs.existsSync(path.join(safeRoot, 'atris'))
|
package/lib/task-receipt.js
CHANGED
|
@@ -86,7 +86,9 @@ function writeTaskReceipt({ taskId, command, root = process.cwd(), runner } = {}
|
|
|
86
86
|
const dir = runsDir(root);
|
|
87
87
|
fs.mkdirSync(dir, { recursive: true });
|
|
88
88
|
const now = new Date();
|
|
89
|
-
|
|
89
|
+
// Local calendar day for the file name; toISOString() is the UTC day,
|
|
90
|
+
// already tomorrow in the evening west of Greenwich.
|
|
91
|
+
const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
90
92
|
const safeTime = now.toISOString().replace(/[:.]/g, '-');
|
|
91
93
|
const receiptFile = path.join(dir, `${date}-task-${slugifyTaskId(taskId)}-${safeTime}.json`);
|
|
92
94
|
const receiptPath = path.relative(root, receiptFile);
|
package/package.json
CHANGED