atris 3.74.8 → 3.74.10
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/commands/auth.js +4 -3
- package/commands/computer.js +9 -7
- package/commands/context-sync.js +2 -1
- package/commands/gm.js +41 -14
- package/commands/integrations.js +2 -1
- package/commands/member.js +44 -10
- package/commands/ninety-nine.js +90 -30
- package/commands/orb.js +1 -1
- package/commands/pull.js +3 -3
- package/commands/push.js +2 -2
- package/commands/skill.js +5 -0
- package/commands/soul.js +2 -1
- package/commands/stream.js +3 -2
- package/commands/strings.js +1 -1
- package/commands/unknowns.js +1 -1
- package/commands/workflow.js +1 -1
- package/commands/zen.js +4 -3
- package/lib/auto-accept-certified.js +2 -3
- package/lib/autoland.js +4 -3
- package/lib/campaign-loop.js +12 -4
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/first-minute.js +1 -0
- package/lib/gm-bet.js +113 -75
- package/lib/gm-card.js +46 -19
- package/lib/gm-phone-icon.js +80 -0
- package/lib/gm-phone-page.js +866 -116
- package/lib/gm-phone.js +15 -4
- package/lib/journal.js +1 -1
- package/lib/noninteractive.js +5 -1
- package/lib/roster-swap.js +8 -3
- package/lib/task-decision.js +1 -1
- package/lib/text-table.js +138 -12
- package/lib/zen-apply.js +15 -2
- package/lib/zen.js +37 -3
- package/package.json +1 -1
- package/scripts/det/ytnotes +9 -3
- package/utils/auth.js +69 -7
package/commands/zen.js
CHANGED
|
@@ -58,14 +58,15 @@ function makeOut(calm) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// Name the three biggest places so a large count reads as "mostly agent
|
|
61
|
-
// notes in atris/" rather than an unexplained total.
|
|
61
|
+
// notes in atris/" rather than an unexplained total. A lone fourth place is
|
|
62
|
+
// named too, since "and 1 more place" takes as much room and says less.
|
|
62
63
|
function teamLine(n, places = []) {
|
|
63
64
|
if (!n) return 'no unsaved team notes.';
|
|
64
65
|
const head = n === 1 ? '1 unsaved team note left in place' : `${n} unsaved team notes left in place`;
|
|
65
66
|
if (!places.length) return `${head}.`;
|
|
66
|
-
const named = places.slice(0, 3).map((p) => `${p.place} ${p.count}`);
|
|
67
|
+
const named = places.slice(0, places.length === 4 ? 4 : 3).map((p) => `${p.place} ${p.count}`);
|
|
67
68
|
const rest = places.length - named.length;
|
|
68
|
-
if (rest) named.push(`and ${rest} more
|
|
69
|
+
if (rest) named.push(`and ${rest} more places`);
|
|
69
70
|
return `${head}: ${named.join(', ')}.`;
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -1099,7 +1099,7 @@ function sniffedProtectedLane(task) {
|
|
|
1099
1099
|
function deniedTagOf(task) {
|
|
1100
1100
|
const tag = String((task && task.tag) || '').trim().toLowerCase();
|
|
1101
1101
|
return [...DENIED_TAGS].find((d) =>
|
|
1102
|
-
tag === d || tag.split(/[^a-z0-9]+/).some((w) => w === d || w.replace(/s$/, '') === d)) || null;
|
|
1102
|
+
tag === d || tag.split(/[^a-z0-9]+/).some((w) => w === d || w.replace(/s$/, '') === d || w === d.replace(/s$/, ''))) || null;
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
1105
|
function protectedLaneOf(task) {
|
|
@@ -1197,8 +1197,7 @@ function evaluateAutoAccept(task, options = {}) {
|
|
|
1197
1197
|
// exact-match cousins: match denied lanes on whole words with a plural
|
|
1198
1198
|
// strip, so a tag variant never slips money/deploy work past the human.
|
|
1199
1199
|
const tag = String(task.tag || '').trim().toLowerCase();
|
|
1200
|
-
const deniedTag =
|
|
1201
|
-
tag === d || tag.split(/[^a-z0-9]+/).some((w) => w === d || w.replace(/s$/, '') === d));
|
|
1200
|
+
const deniedTag = deniedTagOf(task);
|
|
1202
1201
|
if (deniedTag) return { eligible: false, ref, reason: `denied_tag_${deniedTag}` };
|
|
1203
1202
|
|
|
1204
1203
|
// A protected lane declared in prose is not a protection, it is a note.
|
package/lib/autoland.js
CHANGED
|
@@ -187,13 +187,14 @@ function cronMarker(root) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function buildCronLine(root) {
|
|
190
|
+
const quote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
190
191
|
const node = process.execPath;
|
|
191
192
|
const bin = path.join(root, 'bin', 'atris.js');
|
|
192
193
|
const cliBin = fs.existsSync(bin) ? bin : 'atris';
|
|
193
194
|
const logDir = path.join(stateHome(), '.atris', 'overnight');
|
|
194
195
|
const logFile = path.join(logDir, `autoland-${path.basename(root)}.log`);
|
|
195
|
-
const invoke = cliBin.endsWith('.js') ? `${node} ${cliBin}` : cliBin;
|
|
196
|
-
return `14 * * * * cd ${root} && ${invoke} autoland tick >> ${logFile} 2>&1 # ${cronMarker(root)}`;
|
|
196
|
+
const invoke = cliBin.endsWith('.js') ? `${quote(node)} ${quote(cliBin)}` : cliBin;
|
|
197
|
+
return `14 * * * * cd ${quote(root)} && ${invoke} autoland tick >> ${quote(logFile)} 2>&1 # ${cronMarker(root)}`;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
function readCrontab() {
|
|
@@ -499,7 +500,7 @@ function dejargon(line, options = {}) {
|
|
|
499
500
|
.replace(/--([a-z][a-z-]*)/g, (_match, flag) => flag.replace(/-/g, ' '))
|
|
500
501
|
.replace(/\b[a-z0-9]+(?:_[a-z0-9]+)+\b/gi, match => match.replace(/_/g, ' '))
|
|
501
502
|
.replace(/\b[a-z]+(?:[A-Z][A-Za-z0-9]*)+\b/g, match => match.replace(/([a-z0-9])([A-Z])/g, '$1 $2'));
|
|
502
|
-
if (options.preserveTickets !== true) value = value.replace(/\bCLI-\d+\b/g, '');
|
|
503
|
+
if (options.preserveTickets !== true) value = value.replace(/\(\s*CLI-\d+\s*\)|\bCLI-\d+\b/g, '');
|
|
503
504
|
return value.replace(/\s+/g, ' ').trim();
|
|
504
505
|
}
|
|
505
506
|
|
package/lib/campaign-loop.js
CHANGED
|
@@ -50,6 +50,14 @@ function readBrief(root, campaignId) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function objectiveWithBrief(objective, brief) {
|
|
54
|
+
if (!brief) return objective;
|
|
55
|
+
// Mission objectives also become titles and log lines, so keep them on one line.
|
|
56
|
+
const line = objective.replace(/\s+/g, ' ').trim();
|
|
57
|
+
const briefLine = brief.replace(/\s+/g, ' ').trim();
|
|
58
|
+
return line.includes(briefLine) ? line : `${line} ${briefLine}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
function rankCampaignMembers(view, rsiSummary = null, events = null) {
|
|
54
62
|
const seats = Array.isArray(view && view.seats) ? view.seats.map((s) => s.member).filter(Boolean) : [];
|
|
55
63
|
const candidates = [...new Set([view && view.owner, ...seats].filter(Boolean))];
|
|
@@ -180,7 +188,7 @@ async function askOwner(root, view, prompt) {
|
|
|
180
188
|
|
|
181
189
|
// Run one atris command and resolve with its exit code and output.
|
|
182
190
|
// A live step an engine prints while a mission runs: "<member> <hh:mm> <what it is doing>".
|
|
183
|
-
const ACTIVITY_LINE = /^\s*[a-z0-9][a-z0-9-]*\s+\d{1,2}:\d{2}\s{2,}\S/i;
|
|
191
|
+
const ACTIVITY_LINE = /^\s*[a-z0-9][a-z0-9._-]*\s+\d{1,2}:\d{2}\s{2,}\S/i;
|
|
184
192
|
|
|
185
193
|
function runCli(args, { cwd, env = process.env, onLine = null } = {}) {
|
|
186
194
|
return new Promise((resolve) => {
|
|
@@ -258,7 +266,7 @@ function makeLoop(root, options = {}, deps = {}) {
|
|
|
258
266
|
proposals.forEach((proposal, index) => {
|
|
259
267
|
const missionId = proposalId(proposal.objective);
|
|
260
268
|
const key = claimKey(proposal.objective);
|
|
261
|
-
|
|
269
|
+
proposal.objective = objectiveWithBrief(proposal.objective, brief);
|
|
262
270
|
append(id, 'mission_proposed', { mission_id: missionId, member: proposal.member, objective: proposal.objective, verify: proposal.verify, estimate_usd: proposal.estimate_usd || undefined }, proposal.member);
|
|
263
271
|
append(id, 'mission_claimed', { mission_id: missionId, claim_key: key }, proposal.member);
|
|
264
272
|
if (index === 0) append(id, 'step', { mission_id: missionId, ...plan }, current.owner);
|
|
@@ -282,7 +290,7 @@ function makeLoop(root, options = {}, deps = {}) {
|
|
|
282
290
|
const current = view(id);
|
|
283
291
|
const reserve = work.estimate_usd || defaultReserve(current);
|
|
284
292
|
const brief = (deps.brief ? deps.brief(id) : readBrief(root, id)).trim();
|
|
285
|
-
const objective =
|
|
293
|
+
const objective = objectiveWithBrief(work.objective, brief);
|
|
286
294
|
const started = await cli([
|
|
287
295
|
'mission', 'start', objective, '--mission-id', work.mission_id, '--owner', work.member,
|
|
288
296
|
'--runner', runner, '--verify', work.verify || current.judge, '--campaign', id, '--reserve', String(reserve),
|
|
@@ -299,7 +307,7 @@ function makeLoop(root, options = {}, deps = {}) {
|
|
|
299
307
|
append(id, 'mission_vetoed', { mission_id: work.mission_id, reason: why.slice(0, 200) }, current.owner);
|
|
300
308
|
return { mission_id: work.mission_id, started: false };
|
|
301
309
|
}
|
|
302
|
-
log(` ${work.mission_id} started for ${work.member}: ${
|
|
310
|
+
log(` ${work.mission_id} started for ${work.member}: ${objective}`);
|
|
303
311
|
// The engine's live steps ("researcher 06:47 editing stable.mjs") pass through to
|
|
304
312
|
// the campaign's own output, so a screen watching the run can show the work.
|
|
305
313
|
const stepsBefore = (view(id).missions.find((m) => m.id === work.mission_id) || {}).steps || 0;
|
package/lib/card.js
CHANGED
|
@@ -114,7 +114,7 @@ function buildCard(spec = {}, opts = {}) {
|
|
|
114
114
|
</style></head>
|
|
115
115
|
<body><div class="card">${main}${foot}</div></body></html>`;
|
|
116
116
|
|
|
117
|
-
return { html, width: size.w, height: size.h, kind, theme: spec.theme
|
|
117
|
+
return { html, width: size.w, height: size.h, kind, theme: themes[spec.theme] ? spec.theme : 'atris', size: spec.size || 'og' };
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
module.exports = { buildCard, SIZES, KINDS };
|
package/lib/chat-log-scan.js
CHANGED
|
@@ -310,7 +310,7 @@ function scanChatLogs(options = {}) {
|
|
|
310
310
|
scope: options.global === true ? 'global' : 'workspace',
|
|
311
311
|
window_hours: maxAgeHours,
|
|
312
312
|
sources: {
|
|
313
|
-
ax_dirs: axLogDirs(cwd),
|
|
313
|
+
ax_dirs: axLogDirs(cwd, { global: options.global === true }),
|
|
314
314
|
cursor_roots: cursorTranscriptRoots(cwd),
|
|
315
315
|
},
|
|
316
316
|
summary: {
|
package/lib/first-minute.js
CHANGED
|
@@ -1058,6 +1058,7 @@ function speakFirstMinute({
|
|
|
1058
1058
|
log(JSON.stringify(freshMinuteJson(folderName(root), visible, { root }), null, 2));
|
|
1059
1059
|
return 2;
|
|
1060
1060
|
}
|
|
1061
|
+
if (asJson) return speakKeepWorkingMinute({ root, asJson, log });
|
|
1061
1062
|
const screen = buildFirstMinute({
|
|
1062
1063
|
root,
|
|
1063
1064
|
fresh: isFresh,
|
package/lib/gm-bet.js
CHANGED
|
@@ -849,6 +849,65 @@ function statusSentence(row) {
|
|
|
849
849
|
return `${row.status}: ${on}, ${row.time}`;
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
+
// The ovr cell from one member's entry in gm-season memberRatings: the
|
|
853
|
+
// rating, "new" with 1 or 2 finished, two dots with none finished.
|
|
854
|
+
function ovrCell(rated) {
|
|
855
|
+
if (!rated || !rated.finished) return NO_RATING;
|
|
856
|
+
return rated.ovr == null ? 'new' : String(rated.ovr);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// The engine pick the runner makes for a member, with the roster read once.
|
|
860
|
+
function teamEngineResolver(workspaceRoot) {
|
|
861
|
+
const reg = require('./engine-registry');
|
|
862
|
+
const { resolveEngineForMember } = require('./member-engine');
|
|
863
|
+
let rosterState = null;
|
|
864
|
+
try { rosterState = reg.readRosterState(workspaceRoot); } catch { rosterState = null; }
|
|
865
|
+
return name => resolveEngineForMember(name, workspaceRoot, { quiet: true, ...(rosterState ? { rosterState } : {}) });
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// role, ovr, engine, and parked for each name: the team page's cells, and
|
|
869
|
+
// the same cells the board and the standup draw in a terminal, so every
|
|
870
|
+
// team view says the same thing. ratings is gm-season memberRatings over one
|
|
871
|
+
// loaded season; resolve gives a member's engine pick. A name with no card
|
|
872
|
+
// and no roster line is not on the team, so no engine shows for it.
|
|
873
|
+
function teamCells(workspaceRoot, names = [], { ratings = new Map(), resolve = null } = {}) {
|
|
874
|
+
const { memberTypeOf } = require('./member-type');
|
|
875
|
+
const pick = resolve || teamEngineResolver(workspaceRoot);
|
|
876
|
+
const out = new Map();
|
|
877
|
+
for (const name of names) {
|
|
878
|
+
const key = String(name || '');
|
|
879
|
+
if (!key || /[\\/]/.test(key) || key === '.' || key === '..') {
|
|
880
|
+
out.set(key, { role: NO_RATING, ovr: ovrCell(ratings.get(key)), engine: NO_RATING, parked: false });
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
const card = memberCardFacts(workspaceRoot, key);
|
|
884
|
+
const kind = memberTypeOf({ type: card.type });
|
|
885
|
+
let engine = kind.type === 'human' ? 'person' : NO_RATING;
|
|
886
|
+
if (kind.runnable) {
|
|
887
|
+
let picked = null;
|
|
888
|
+
try { picked = pick(key); } catch { picked = null; }
|
|
889
|
+
engine = picked && picked.found === false ? NO_RATING : engineWords(picked);
|
|
890
|
+
}
|
|
891
|
+
out.set(key, { role: memberRoleWords(key, card), ovr: ovrCell(ratings.get(key)), engine, parked: card.parked });
|
|
892
|
+
}
|
|
893
|
+
return out;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// The team table as a divided box for a terminal, in the team page's
|
|
897
|
+
// columns: one line per { member, status } with that member's cells from
|
|
898
|
+
// teamCells. A long role or status wraps to fit the width (the exact model
|
|
899
|
+
// never splits); null when even that does not fit.
|
|
900
|
+
function teamBox(rows, cells, { width } = {}) {
|
|
901
|
+
const { renderBorderedTable } = require('./text-table');
|
|
902
|
+
const wraps = new Set(['role', 'status']);
|
|
903
|
+
const columns = require('./gm-card').TEAM_COLUMNS.map(header => ({ header, wrap: wraps.has(header) }));
|
|
904
|
+
const blank = { role: NO_RATING, ovr: NO_RATING, engine: NO_RATING };
|
|
905
|
+
return renderBorderedTable(columns, (rows || []).map((row) => {
|
|
906
|
+
const c = (cells && cells.get(row.member)) || blank;
|
|
907
|
+
return [row.member, c.role, c.ovr, c.engine, row.status];
|
|
908
|
+
}), { width });
|
|
909
|
+
}
|
|
910
|
+
|
|
852
911
|
// A verify command that always passes proves nothing. Binary checks only:
|
|
853
912
|
// every piece of it is one of these, or it swallows failure with || true.
|
|
854
913
|
const TRIVIAL_VERIFY = [
|
|
@@ -2049,7 +2108,7 @@ const WRITER_ANGLES = [
|
|
|
2049
2108
|
const SWARM_SAY = {
|
|
2050
2109
|
drafting: n => (n === 1 ? 'drafting an idea' : `drafting ${COUNT_WORDS[n] || n} ideas at once`),
|
|
2051
2110
|
checking: k => `the ${ORDINALS[k - 1] || 'next'} draft is in. checking it`,
|
|
2052
|
-
redraft: 'one draft
|
|
2111
|
+
redraft: 'one draft did not fit. redrafting it',
|
|
2053
2112
|
shorten: 'one draft ran long. shortening it',
|
|
2054
2113
|
heldUp: 'found one that holds up',
|
|
2055
2114
|
notHeld: 'that one did not hold up. still working',
|
|
@@ -3230,10 +3289,10 @@ function createBetDesk(deps) {
|
|
|
3230
3289
|
// the number for the card on screen, saved beside the ledger
|
|
3231
3290
|
function report({ value } = {}) {
|
|
3232
3291
|
const item = onJob('report');
|
|
3233
|
-
if (!item) return { ok: false, reason: 'gone', message: 'there is no number asked for on screen' };
|
|
3292
|
+
if (!item) return { ok: false, reason: 'gone', message: 'there is no number asked for on screen.' };
|
|
3234
3293
|
const raw = String(value == null ? '' : value).replace(/,/g, '').trim();
|
|
3235
3294
|
const v = Number(raw);
|
|
3236
|
-
if (!raw || !Number.isFinite(v)) return { ok: false, reason: 'bad_number', message: 'that is not a number' };
|
|
3295
|
+
if (!raw || !Number.isFinite(v)) return { ok: false, reason: 'bad_number', message: 'that is not a number.' };
|
|
3237
3296
|
appendLedger(scoreboardFile, { workspace: deps.workspaceRoot, kind: 'report', value: v, at: iso() });
|
|
3238
3297
|
log({ card: 'bet', choice: 'report', command: null, ran: 'in process', exit_code: 0 });
|
|
3239
3298
|
state.status = 'cleared';
|
|
@@ -3328,7 +3387,7 @@ function createBetDesk(deps) {
|
|
|
3328
3387
|
function pick({ id } = {}) {
|
|
3329
3388
|
const cur = onJob('choice');
|
|
3330
3389
|
const item = cur && cur.items.find(i => i.id === id);
|
|
3331
|
-
if (!item) return { ok: false, reason: 'gone', message: 'there is nothing to pick on screen' };
|
|
3390
|
+
if (!item) return { ok: false, reason: 'gone', message: 'there is nothing to pick on screen.' };
|
|
3332
3391
|
for (const other of cur.items) if (other !== item) record(other.id, { status: 'passed', note: 'not picked' });
|
|
3333
3392
|
log({ card: 'bet', choice: 'pick', command: null, ran: 'in process', exit_code: 0, bet_id: item.id });
|
|
3334
3393
|
state.current = item;
|
|
@@ -3673,15 +3732,16 @@ function createBetDesk(deps) {
|
|
|
3673
3732
|
const isBet = () => state.status === 'ready' && state.current && (!state.current.kind || state.current.kind === 'bet');
|
|
3674
3733
|
|
|
3675
3734
|
function skip() {
|
|
3676
|
-
if (!isBet()) return { ok: false, reason: 'gone', message: 'there is no idea on screen to skip' };
|
|
3735
|
+
if (!isBet()) return { ok: false, reason: 'gone', message: 'there is no idea on screen to skip.' };
|
|
3677
3736
|
record(state.current.id, { status: 'skipped' });
|
|
3678
3737
|
log({ card: 'bet', choice: 'skip', command: null, ran: 'in process', exit_code: 0, bet_id: state.current.id });
|
|
3679
3738
|
state.status = 'skipped';
|
|
3680
|
-
|
|
3739
|
+
// the page says what to do next under the orb, so this only says what happened
|
|
3740
|
+
return { ok: true, ack: 'set aside.' };
|
|
3681
3741
|
}
|
|
3682
3742
|
|
|
3683
3743
|
async function doIt() {
|
|
3684
|
-
if (!isBet()) return { ok: false, reason: 'gone', message: 'there is no idea on screen to start' };
|
|
3744
|
+
if (!isBet()) return { ok: false, reason: 'gone', message: 'there is no idea on screen to start.' };
|
|
3685
3745
|
const { id, bet } = state.current;
|
|
3686
3746
|
const pick = pickOwner(bet, deps.workspaceRoot);
|
|
3687
3747
|
const startArgs = missionStartArgs(bet, pick, id, deps.workspaceRoot);
|
|
@@ -3707,7 +3767,7 @@ function createBetDesk(deps) {
|
|
|
3707
3767
|
let line;
|
|
3708
3768
|
// what the owner reads; the command and exit code stay in the log
|
|
3709
3769
|
if (res.code === 0 && ran.action === 'mission_run_detached') line = `${owner} has it and is starting now.`;
|
|
3710
|
-
else if (ran.reason === 'human_asks_open') line = `${owner} has it, and is waiting
|
|
3770
|
+
else if (ran.reason === 'human_asks_open') line = `${owner} has it, and is waiting for your yes before starting.`;
|
|
3711
3771
|
else line = `${owner} has it, but has not started yet. it can be started from the mac.`;
|
|
3712
3772
|
state.status = 'started';
|
|
3713
3773
|
state.mission = { id: missionId, owner, line, commands: [display(startArgs), display(runArgs)] };
|
|
@@ -3721,7 +3781,7 @@ function createBetDesk(deps) {
|
|
|
3721
3781
|
// nice: a finished job was seen; nothing else happens
|
|
3722
3782
|
function nice() {
|
|
3723
3783
|
const item = onJob('done');
|
|
3724
|
-
if (!item) return { ok: false, reason: 'gone', message: 'there is no finished job on screen' };
|
|
3784
|
+
if (!item) return { ok: false, reason: 'gone', message: 'there is no finished job on screen.' };
|
|
3725
3785
|
record(item.job.bet_id, { done_seen_at: iso() });
|
|
3726
3786
|
log({ card: 'bet', choice: 'nice', command: null, ran: 'in process', exit_code: 0, bet_id: item.job.bet_id });
|
|
3727
3787
|
state.status = 'cleared';
|
|
@@ -3734,18 +3794,18 @@ function createBetDesk(deps) {
|
|
|
3734
3794
|
// and its answer is saved against nothing (answer ids repeat across questions)
|
|
3735
3795
|
function answer({ choice, text, card } = {}) {
|
|
3736
3796
|
const item = onJob('question');
|
|
3737
|
-
if (!item) return { ok: false, reason: 'gone', message: 'there is no question on screen' };
|
|
3738
|
-
if (card !== undefined && card !== item.id) return { ok: false, reason: 'gone', message: 'that card changed, so nothing was saved.
|
|
3797
|
+
if (!item) return { ok: false, reason: 'gone', message: 'there is no question on screen.' };
|
|
3798
|
+
if (card !== undefined && card !== item.id) return { ok: false, reason: 'gone', message: 'that card changed, so nothing was saved. here is the one on screen now.' };
|
|
3739
3799
|
const picked = item.question.choices.find(c => c.id === choice);
|
|
3740
|
-
if (!picked) return { ok: false, reason: 'bad_choice', message: 'that is not one of the answers' };
|
|
3800
|
+
if (!picked) return { ok: false, reason: 'bad_choice', message: 'that is not one of the answers.' };
|
|
3741
3801
|
const q = item.question;
|
|
3742
3802
|
if (q.key === 'scoreboard') {
|
|
3743
3803
|
let name;
|
|
3744
3804
|
let up;
|
|
3745
3805
|
if (picked.other) {
|
|
3746
3806
|
name = String(text || '').trim();
|
|
3747
|
-
if (!name) return { ok: false, reason: 'empty', message: '
|
|
3748
|
-
if (name.length > 60) return { ok: false, reason: 'too_long', message: 'keep it under 60 characters' };
|
|
3807
|
+
if (!name) return { ok: false, reason: 'empty', message: 'name the number in a few words.' };
|
|
3808
|
+
if (name.length > 60) return { ok: false, reason: 'too_long', message: 'keep it under 60 characters.' };
|
|
3749
3809
|
up = !LOWER_IS_BETTER.test(name);
|
|
3750
3810
|
} else {
|
|
3751
3811
|
const pick = q.numbers[Number(picked.id.slice(1)) - 1];
|
|
@@ -3840,7 +3900,7 @@ function createBetDesk(deps) {
|
|
|
3840
3900
|
if (job.bet_id) record(job.bet_id, { yes_answered: { mission_id: job.mission_id, step: job.lock_step, at: iso() } });
|
|
3841
3901
|
clear();
|
|
3842
3902
|
pollJobs();
|
|
3843
|
-
return { ok: true, ack:
|
|
3903
|
+
return { ok: true, ack: `told ${job.owner} yes.` };
|
|
3844
3904
|
}
|
|
3845
3905
|
const listed = await deps.runCli(['decide', '--json']);
|
|
3846
3906
|
const open = (parseJsonOut(listed.stdout) || {}).decisions || [];
|
|
@@ -3854,13 +3914,13 @@ function createBetDesk(deps) {
|
|
|
3854
3914
|
const args = ['decide', String(match.number), 'yes', '--json'];
|
|
3855
3915
|
const res = await deps.runCli(args);
|
|
3856
3916
|
log({ card: 'bet', choice: 'yes', command: require('./gm-phone').displayCommand(args), exit_code: res.code, bet_id: job.bet_id, mission_id: job.mission_id });
|
|
3857
|
-
if (res.code !== 0) return { ok: false, reason: 'answer_failed', message: 'your yes did not go through
|
|
3917
|
+
if (res.code !== 0) return { ok: false, reason: 'answer_failed', message: 'your yes did not go through. try again.' };
|
|
3858
3918
|
state.seenYes.add(job.key);
|
|
3859
3919
|
state.answeredYes.add(job.key);
|
|
3860
3920
|
if (job.bet_id) record(job.bet_id, { yes_answered: { mission_id: job.mission_id, ask_index: job.ask_index, at: iso() } });
|
|
3861
3921
|
clear();
|
|
3862
3922
|
pollJobs();
|
|
3863
|
-
return { ok: true, ack:
|
|
3923
|
+
return { ok: true, ack: `told ${job.owner} yes.` };
|
|
3864
3924
|
}
|
|
3865
3925
|
|
|
3866
3926
|
// no: the one request on the card is told no, and the job goes on without
|
|
@@ -3896,7 +3956,7 @@ function createBetDesk(deps) {
|
|
|
3896
3956
|
const args = ['decide', String(match.number), 'no', '--json'];
|
|
3897
3957
|
const res = await deps.runCli(args);
|
|
3898
3958
|
log({ card: 'bet', choice: 'no', command: display(args), exit_code: res.code, bet_id: job.bet_id, mission_id: job.mission_id });
|
|
3899
|
-
if (res.code !== 0) return { ok: false, reason: 'answer_failed', message: 'your no did not go through
|
|
3959
|
+
if (res.code !== 0) return { ok: false, reason: 'answer_failed', message: 'your no did not go through. try again.' };
|
|
3900
3960
|
if (job.bet_id) record(job.bet_id, { no_answered: { mission_id: job.mission_id, ask_index: job.ask_index, at: iso() } });
|
|
3901
3961
|
return done(told);
|
|
3902
3962
|
}
|
|
@@ -3907,7 +3967,7 @@ function createBetDesk(deps) {
|
|
|
3907
3967
|
const j = item.job;
|
|
3908
3968
|
const expect = item.kind === 'yes' ? j.expect : j.bet && j.bet.expect;
|
|
3909
3969
|
const why = expect ? ` ${sentence(expect)}` : '';
|
|
3910
|
-
if (item.kind === 'yes') return { what: plainAsk(j.ask), why: `${j.owner} is waiting
|
|
3970
|
+
if (item.kind === 'yes') return { what: plainAsk(j.ask), why: `${j.owner} is waiting for your yes.${why}` };
|
|
3911
3971
|
return { what: j.title, why: `this is done.${why}` };
|
|
3912
3972
|
}
|
|
3913
3973
|
|
|
@@ -4015,7 +4075,7 @@ function createBetDesk(deps) {
|
|
|
4015
4075
|
function cardForReader(v) {
|
|
4016
4076
|
if (v.kind === 'question') return { kind: 'question', title: v.question.ask, detail: v.question.line, buttons: v.question.choices.map(c => c.label) };
|
|
4017
4077
|
if (v.kind === 'bet' && v.bet.concern) return { kind: 'idea', title: v.bet.title, detail: [v.bet.why_now, v.bet.expect, v.bet.concern].join(' '), buttons: ['go with it', 'sharpen it', 'something else'] };
|
|
4018
|
-
if (v.kind === 'bet') return { kind: 'idea', title: v.bet.title, detail: [v.bet.why_now, v.bet.expect].join(' '), buttons: ['do it', '
|
|
4078
|
+
if (v.kind === 'bet') return { kind: 'idea', title: v.bet.title, detail: [v.bet.why_now, v.bet.expect].join(' '), buttons: ['do it', 'sharpen it', 'not now'] };
|
|
4019
4079
|
if (v.kind === 'choice') return { kind: 'choice', title: 'which one next?', detail: v.choice.options.map(o => o.title).join(' / '), buttons: [] };
|
|
4020
4080
|
if (v.kind === 'report') return { kind: 'number', title: v.report.what, detail: v.report.why, buttons: ['save', 'not now'] };
|
|
4021
4081
|
return { kind: v.kind, title: v.job ? v.job.what : '', detail: v.job ? v.job.why : '', buttons: [] };
|
|
@@ -4049,12 +4109,12 @@ function createBetDesk(deps) {
|
|
|
4049
4109
|
// next idea fits it; on a yes or a finished job, a word to that member.
|
|
4050
4110
|
async function say({ text, confirm = false, note = false, card } = {}) {
|
|
4051
4111
|
const said = String(text || '').trim();
|
|
4052
|
-
if (!said) return { ok: false, reason: 'empty', message: 'say something first' };
|
|
4053
|
-
if (said.length > SAY_MAX) return { ok: false, reason: 'too_long', message: `keep it under ${SAY_MAX} characters
|
|
4112
|
+
if (!said) return { ok: false, reason: 'empty', message: 'say something first.' };
|
|
4113
|
+
if (said.length > SAY_MAX) return { ok: false, reason: 'too_long', message: `keep it under ${SAY_MAX} characters.` };
|
|
4054
4114
|
let cur = state.status === 'ready' ? state.current : null;
|
|
4055
|
-
if (!cur) return { ok: false, reason: 'gone', message: 'there is no card on screen' };
|
|
4115
|
+
if (!cur) return { ok: false, reason: 'gone', message: 'there is no card on screen.' };
|
|
4056
4116
|
// words said on a card the page no longer shows act on nothing
|
|
4057
|
-
if (card !== undefined && card !== cur.id) return { ok: false, reason: 'gone', message: 'that card changed, so nothing was done.
|
|
4117
|
+
if (card !== undefined && card !== cur.id) return { ok: false, reason: 'gone', message: 'that card changed, so nothing was done. here is the one on screen now.' };
|
|
4058
4118
|
// on the calm line there is nothing to answer: the words are a note for the idea being written, read by no model
|
|
4059
4119
|
if (cur.kind === 'wait') return noteOnWait(cur, said);
|
|
4060
4120
|
let kind = cur.kind || 'bet';
|
|
@@ -4069,7 +4129,7 @@ function createBetDesk(deps) {
|
|
|
4069
4129
|
// said on the try-again card that took its place, never dropped
|
|
4070
4130
|
const replaced = kind === 'wait' && state.status === 'ready' && state.current && state.current.kind === 'missed' && state.current.after === cur.id;
|
|
4071
4131
|
if (replaced) { cur = state.current; kind = 'missed'; }
|
|
4072
|
-
if (state.current !== cur || state.status !== 'ready') return { ok: false, reason: 'gone', message: 'that card changed, so nothing was done' };
|
|
4132
|
+
if (state.current !== cur || state.status !== 'ready') return { ok: false, reason: 'gone', message: 'that card changed, so nothing was done. here is the one on screen now.' };
|
|
4073
4133
|
if (turn.stay) return turn.stay;
|
|
4074
4134
|
read = turn.act;
|
|
4075
4135
|
}
|
|
@@ -4149,10 +4209,10 @@ function createBetDesk(deps) {
|
|
|
4149
4209
|
// answered from what the table knows; an instruction steers them.
|
|
4150
4210
|
async function talk({ member, text, confirm = false, note = false } = {}) {
|
|
4151
4211
|
const name = String(member || '');
|
|
4152
|
-
if (!(team || []).some(m => m.name === name)) return { ok: false, reason: 'gone', message: 'there is no such member on the team' };
|
|
4212
|
+
if (!(team || []).some(m => m.name === name)) return { ok: false, reason: 'gone', message: 'there is no such member on the team.' };
|
|
4153
4213
|
const said = String(text || '').trim();
|
|
4154
|
-
if (!said) return { ok: false, reason: 'empty', message: 'say something first' };
|
|
4155
|
-
if (said.length > SAY_MAX) return { ok: false, reason: 'too_long', message: `keep it under ${SAY_MAX} characters
|
|
4214
|
+
if (!said) return { ok: false, reason: 'empty', message: 'say something first.' };
|
|
4215
|
+
if (said.length > SAY_MAX) return { ok: false, reason: 'too_long', message: `keep it under ${SAY_MAX} characters.` };
|
|
4156
4216
|
const t = threadFor(`member:${name}`);
|
|
4157
4217
|
if (!confirm && !note) {
|
|
4158
4218
|
const row = statusTable().rows.find(r => r.member === name);
|
|
@@ -4188,13 +4248,13 @@ function createBetDesk(deps) {
|
|
|
4188
4248
|
if (!OPEN_MISSION.has(status)) return null;
|
|
4189
4249
|
const asks = (Array.isArray(m.human_asks) ? m.human_asks : []).some(a => a && !(typeof a === 'object' && a.answered_at))
|
|
4190
4250
|
|| require('./yes-lock').askedYesLocks(lockDir, m.id).length > 0;
|
|
4191
|
-
if (asks) return { moved, row: { on, status: 'waiting for your yes', time: `
|
|
4251
|
+
if (asks) return { moved, row: { on, status: 'waiting for your yes', time: `asked ${spanWords(age)} ago` } };
|
|
4192
4252
|
if (driverAlive(m) || age <= MOVING_MS) {
|
|
4193
4253
|
const wall = Number(m.max_wall_seconds) || Number(m.budget_contract && m.budget_contract.requested_seconds) || 0;
|
|
4194
4254
|
const left = wall ? created + wall * 1000 - now() : 0;
|
|
4195
4255
|
return { moved, row: { on, status: 'working', time: left > 0 ? leftWords(left) : `at it for ${spanWords(now() - created)}` } };
|
|
4196
4256
|
}
|
|
4197
|
-
if (!m.last_tick_at) return { moved, row: { on, status: 'waiting to start', time: `
|
|
4257
|
+
if (!m.last_tick_at) return { moved, row: { on, status: 'waiting to start', time: `added ${spanWords(now() - created)} ago` } };
|
|
4198
4258
|
return { moved, row: { on, status: 'stopped', time: `stopped ${spanWords(age)} ago` } };
|
|
4199
4259
|
}
|
|
4200
4260
|
|
|
@@ -4222,7 +4282,7 @@ function createBetDesk(deps) {
|
|
|
4222
4282
|
const at = Date.parse((hnd && (hnd.closed_at || hnd.created_at)) || '') || 0;
|
|
4223
4283
|
if (!hnd || !hnd.to || !at || now() - at > RECENT_MS) continue;
|
|
4224
4284
|
const on = clip(String(hnd.summary || '').replace(/^\[[^\]]*\]\s*/, ''), 100);
|
|
4225
|
-
if (hnd.status === 'open') put(hnd.to, { on, status: 'waiting to start', time: `
|
|
4285
|
+
if (hnd.status === 'open') put(hnd.to, { on, status: 'waiting to start', time: `added ${spanWords(now() - at)} ago` }, at);
|
|
4226
4286
|
else put(hnd.to, { on, status: 'idle', time: `free for ${spanWords(now() - at)}` }, at);
|
|
4227
4287
|
}
|
|
4228
4288
|
const rows = [...best.values()]
|
|
@@ -4252,33 +4312,7 @@ function createBetDesk(deps) {
|
|
|
4252
4312
|
const loaded = (deps.loadSeason || (root => season.loadSeason(root, { now: now(), lastSeen: null })))(deps.workspaceRoot);
|
|
4253
4313
|
if (loaded && !loaded.error) ratings = season.memberRatings(loaded.outcomes || []);
|
|
4254
4314
|
} catch { ratings = new Map(); }
|
|
4255
|
-
|
|
4256
|
-
if (!resolve) {
|
|
4257
|
-
const reg = require('./engine-registry');
|
|
4258
|
-
const { resolveEngineForMember } = require('./member-engine');
|
|
4259
|
-
let rosterState = null;
|
|
4260
|
-
try { rosterState = reg.readRosterState(deps.workspaceRoot); } catch { rosterState = null; }
|
|
4261
|
-
resolve = name => resolveEngineForMember(name, deps.workspaceRoot, { quiet: true, ...(rosterState ? { rosterState } : {}) });
|
|
4262
|
-
}
|
|
4263
|
-
const facts = new Map();
|
|
4264
|
-
const { memberTypeOf } = require('./member-type');
|
|
4265
|
-
for (const m of team || []) {
|
|
4266
|
-
const card = memberCardFacts(deps.workspaceRoot, m.name);
|
|
4267
|
-
const kind = memberTypeOf({ type: card.type });
|
|
4268
|
-
let engine = kind.type === 'human' ? 'person' : NO_RATING;
|
|
4269
|
-
if (kind.runnable) {
|
|
4270
|
-
let picked = null;
|
|
4271
|
-
try { picked = resolve(m.name); } catch { picked = null; }
|
|
4272
|
-
engine = engineWords(picked);
|
|
4273
|
-
}
|
|
4274
|
-
const rated = ratings.get(m.name);
|
|
4275
|
-
facts.set(m.name, {
|
|
4276
|
-
role: memberRoleWords(m.name, card),
|
|
4277
|
-
ovr: !rated || !rated.finished ? NO_RATING : rated.ovr == null ? 'new' : String(rated.ovr),
|
|
4278
|
-
engine,
|
|
4279
|
-
parked: card.parked,
|
|
4280
|
-
});
|
|
4281
|
-
}
|
|
4315
|
+
const facts = teamCells(deps.workspaceRoot, (team || []).map(m => m.name), { ratings, resolve: deps.resolveEngine || null });
|
|
4282
4316
|
rosterCache = { at: now(), facts };
|
|
4283
4317
|
return facts;
|
|
4284
4318
|
}
|
|
@@ -4303,7 +4337,7 @@ function createBetDesk(deps) {
|
|
|
4303
4337
|
// the owner's tap is the approval, so there is no yes card. scope member
|
|
4304
4338
|
// gives only this member the model, scope job swaps the lead of its job.
|
|
4305
4339
|
function onTeam(name) { return (team || []).some(m => m.name === name); }
|
|
4306
|
-
const NOT_ON_TEAM = { ok: false, reason: 'gone', message: 'there is no such member on the team' };
|
|
4340
|
+
const NOT_ON_TEAM = { ok: false, reason: 'gone', message: 'there is no such member on the team.' };
|
|
4307
4341
|
function engineCard({ member } = {}) {
|
|
4308
4342
|
const name = String(member || '');
|
|
4309
4343
|
if (!onTeam(name)) return NOT_ON_TEAM;
|
|
@@ -4381,7 +4415,7 @@ function createBetDesk(deps) {
|
|
|
4381
4415
|
const at = Date.parse((hnd && hnd.created_at) || '') || 0;
|
|
4382
4416
|
if (!hnd || hnd.status !== 'open' || !names.has(hnd.to) || !at || now() - at > RECENT_MS) continue;
|
|
4383
4417
|
const on = clip(String(hnd.summary || '').replace(/^\[[^\]]*\]\s*/, ''), 100);
|
|
4384
|
-
jobs.push({ id: `handoff:${hnd.id || at}`, mission: null, member: hnd.to, row: { on, status: 'waiting to start', time: `
|
|
4418
|
+
jobs.push({ id: `handoff:${hnd.id || at}`, mission: null, member: hnd.to, row: { on, status: 'waiting to start', time: `added ${spanWords(now() - at)} ago` }, moved: at });
|
|
4385
4419
|
}
|
|
4386
4420
|
jobs.sort((x, y) => STATUS_ORDER.indexOf(x.row.status) - STATUS_ORDER.indexOf(y.row.status) || y.moved - x.moved || x.member.localeCompare(y.member));
|
|
4387
4421
|
// a yes skipped with not now waits at the back of the ones that need the owner
|
|
@@ -4426,10 +4460,10 @@ function createBetDesk(deps) {
|
|
|
4426
4460
|
|
|
4427
4461
|
async function steer({ member, text, confirm = false, note = false } = {}) {
|
|
4428
4462
|
const name = String(member || '');
|
|
4429
|
-
if (!(team || []).some(m => m.name === name)) return { ok: false, reason: 'gone', message: 'there is no such member on the team' };
|
|
4463
|
+
if (!(team || []).some(m => m.name === name)) return { ok: false, reason: 'gone', message: 'there is no such member on the team.' };
|
|
4430
4464
|
const said = String(text || '').trim();
|
|
4431
|
-
if (!said) return { ok: false, reason: 'empty', message: 'say something first' };
|
|
4432
|
-
if (said.length > SAY_MAX) return { ok: false, reason: 'too_long', message: `keep it under ${SAY_MAX} characters
|
|
4465
|
+
if (!said) return { ok: false, reason: 'empty', message: 'say something first.' };
|
|
4466
|
+
if (said.length > SAY_MAX) return { ok: false, reason: 'too_long', message: `keep it under ${SAY_MAX} characters.` };
|
|
4433
4467
|
if (note) {
|
|
4434
4468
|
steeringNote(name, said);
|
|
4435
4469
|
log({ card: 'bet', choice: 'steer_note', command: null, ran: 'in process', exit_code: 0, member: name, chars: said.length });
|
|
@@ -4448,7 +4482,7 @@ function createBetDesk(deps) {
|
|
|
4448
4482
|
if (out.action === 'member_run_not_started' && out.reason) return { ok: false, reason: 'not_started', message: `${name} did not start: ${String(out.reason).replace(/[.\s]+$/, '')}.` };
|
|
4449
4483
|
return { ok: false, reason: 'not_started', message: `${name} did not start. try again in a minute.` };
|
|
4450
4484
|
}
|
|
4451
|
-
if (out.action === 'member_not_running' || res.code === 3) return { ok: false, reason: 'needs_confirm', message: `tap again to start ${name} on this
|
|
4485
|
+
if (out.action === 'member_not_running' || res.code === 3) return { ok: false, reason: 'needs_confirm', message: `tap again to start ${name} on this.` };
|
|
4452
4486
|
return { ok: false, reason: 'steer_failed', message: 'it did not reach them. try again in a minute.' };
|
|
4453
4487
|
}
|
|
4454
4488
|
|
|
@@ -4533,8 +4567,9 @@ function createBetDesk(deps) {
|
|
|
4533
4567
|
const at = Math.min(l.at || 0, cards.length - 1);
|
|
4534
4568
|
const c = { ...cards[at], said: (l.said && l.said[cards[at].job_id]) || null };
|
|
4535
4569
|
const r = c.rows[0];
|
|
4536
|
-
// the deck card is about the job: its row without the rating and the model, which live on the team table
|
|
4537
|
-
|
|
4570
|
+
// the deck card is about the job: its row without the rating and the model, which live on the team table,
|
|
4571
|
+
// and without done, which says "not yet" on every card because the deck holds only open jobs
|
|
4572
|
+
const columns = DECK_COLUMNS.filter(k => k !== 'ovr' && k !== 'engine' && k !== 'done');
|
|
4538
4573
|
return gmCard.deck({ id: l.id, at, total: cards.length, card: c, columns, job: { member: c.member, role: r.role, ovr: r.ovr, engine: r.engine } });
|
|
4539
4574
|
}
|
|
4540
4575
|
if (l.kind === 'swap') {
|
|
@@ -4622,7 +4657,7 @@ function createBetDesk(deps) {
|
|
|
4622
4657
|
const base = stays && under && under.kind === 'wait' && state.current && state.current.kind === 'missed' ? (underCard() || under) : under;
|
|
4623
4658
|
const back = replied(out, base.id, text);
|
|
4624
4659
|
if (back) return back;
|
|
4625
|
-
if (!out.ok) return refuse(out.reason || 'gone', out.message || 'that did not send. try again');
|
|
4660
|
+
if (!out.ok) return refuse(out.reason || 'gone', out.message || 'that did not send. try again.');
|
|
4626
4661
|
state.reply = null;
|
|
4627
4662
|
next();
|
|
4628
4663
|
return done200({ ack: out.ack });
|
|
@@ -4636,7 +4671,7 @@ function createBetDesk(deps) {
|
|
|
4636
4671
|
const out = await talk({ member: name, text, ...more });
|
|
4637
4672
|
const back = replied(out, under.id, text);
|
|
4638
4673
|
if (back) return back;
|
|
4639
|
-
if (!out.ok) return refuse(out.reason || 'gone', out.message || 'that did not send. try again');
|
|
4674
|
+
if (!out.ok) return refuse(out.reason || 'gone', out.message || 'that did not send. try again.');
|
|
4640
4675
|
state.reply = null;
|
|
4641
4676
|
if (l.kind === 'deck') { l.said = { ...(l.said || {}), [under.deck_job]: out.ack }; l.id = freshId('deck'); } else { l.line = out.ack; l.id = freshId('member'); }
|
|
4642
4677
|
return done200();
|
|
@@ -4678,18 +4713,18 @@ function createBetDesk(deps) {
|
|
|
4678
4713
|
return done200(out.busy ? { busy: true } : {});
|
|
4679
4714
|
}
|
|
4680
4715
|
if (body.open === 'team' || body.open === 'deck') {
|
|
4681
|
-
if (stale) return refuse('gone', 'that card changed. here is the one on screen now');
|
|
4716
|
+
if (stale) return refuse('gone', 'that card changed. here is the one on screen now.');
|
|
4682
4717
|
open(body.open);
|
|
4683
4718
|
return done200();
|
|
4684
4719
|
}
|
|
4685
4720
|
// an option or words always name the card they were for
|
|
4686
|
-
if (!shown || !cardId || stale) return refuse('gone', 'that card changed, so nothing was done. here is the one on screen now');
|
|
4721
|
+
if (!shown || !cardId || stale) return refuse('gone', 'that card changed, so nothing was done. here is the one on screen now.');
|
|
4687
4722
|
const optionId = body.option_id == null ? null : String(body.option_id);
|
|
4688
4723
|
const opt = optionId ? shown.options.find(o => o.id === optionId) : null;
|
|
4689
|
-
if (optionId && !opt) return refuse('bad_option', 'that is not on this card');
|
|
4690
|
-
if (!opt && !shown.input) return refuse('bad_option', 'this card has no box to type in');
|
|
4724
|
+
if (optionId && !opt) return refuse('bad_option', 'that is not on this card.');
|
|
4725
|
+
if (!opt && !shown.input) return refuse('bad_option', 'this card has no box to type in.');
|
|
4691
4726
|
const typed = String(body.text == null ? '' : body.text).trim();
|
|
4692
|
-
if ((!opt || opt.type) && !typed) return refuse('empty', 'say something first');
|
|
4727
|
+
if ((!opt || opt.type) && !typed) return refuse('empty', 'say something first.');
|
|
4693
4728
|
if (opt && opt.confirm && body.confirm !== true) return { status: 409, body: { ...envelope(), ok: false, reason: 'needs_confirm', message: opt.confirm } };
|
|
4694
4729
|
const action = opt ? opt.action : shown.input.action;
|
|
4695
4730
|
const b = (opt && opt.binds) || {};
|
|
@@ -4794,10 +4829,11 @@ function createBetDesk(deps) {
|
|
|
4794
4829
|
case 'swap_to': {
|
|
4795
4830
|
const out = swapEngine({ member: state.layer.name, engine: b.engine, model: b.model, scope: b.scope });
|
|
4796
4831
|
if (!out.ok) return refuse(out.reason, out.message);
|
|
4832
|
+
// the team comes back with the line on it, so nothing says it twice
|
|
4797
4833
|
open('team', { line: out.ack });
|
|
4798
|
-
return done200(
|
|
4834
|
+
return done200();
|
|
4799
4835
|
}
|
|
4800
|
-
default: return refuse('bad_option', 'that does nothing here');
|
|
4836
|
+
default: return refuse('bad_option', 'that does nothing here.');
|
|
4801
4837
|
}
|
|
4802
4838
|
}
|
|
4803
4839
|
|
|
@@ -4857,4 +4893,6 @@ module.exports = {
|
|
|
4857
4893
|
writerCommand,
|
|
4858
4894
|
WRITER_ANGLES,
|
|
4859
4895
|
validateBet,
|
|
4896
|
+
teamBox,
|
|
4897
|
+
teamCells,
|
|
4860
4898
|
};
|