atris 3.42.0 → 3.44.0
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/atris/skills/design/SKILL.md +7 -1
- package/atris/skills/engines/SKILL.md +44 -13
- package/atris/team/customer-lead/MEMBER.md +45 -0
- package/atris/team/customer-lead/SOUL.md +33 -0
- package/atris/team/customer-lead/START_HERE.md +7 -0
- package/atris/team/customer-lead/skills/customer-commitments/SKILL.md +45 -0
- package/atris/team/improver/MEMBER.md +33 -0
- package/bin/atris.js +37 -4
- package/commands/autoland.js +15 -1
- package/commands/caretaker.js +303 -0
- package/commands/clean.js +76 -0
- package/commands/engine-watch.js +212 -0
- package/commands/engine.js +99 -11
- package/commands/founder.js +304 -0
- package/commands/human-missions.js +844 -0
- package/commands/init.js +16 -7
- package/commands/lesson.js +178 -4
- package/commands/mission.js +124 -69
- package/commands/slop.js +34 -3
- package/commands/task.js +51 -4
- package/commands/team.js +329 -13
- package/commands/verify.js +99 -6
- package/commands/worktree.js +119 -4
- package/lib/auto-accept-certified.js +302 -0
- package/lib/cloud-mission.js +59 -2
- package/lib/conductor-artifacts.js +1 -1
- package/lib/dispatch-scout.js +383 -0
- package/lib/engine-ask.js +645 -0
- package/lib/engine-job-lifecycle.js +65 -0
- package/lib/engine-receipt-sweep.js +98 -0
- package/lib/engine-registry.js +2 -2
- package/lib/engine-validate.js +374 -0
- package/lib/fleet.js +459 -106
- package/lib/known-commands.js +2 -2
- package/lib/lesson-ledger.js +84 -0
- package/lib/member-alive.js +2 -2
- package/lib/policy-lessons.js +70 -0
- package/lib/receipt-evidence.js +56 -1
- package/lib/runner-command.js +1 -1
- package/lib/secret-gateway.js +588 -0
- package/lib/team-presence.js +13 -1
- package/lib/voice-gate.js +6 -0
- package/lib/wish-audit.js +5 -205
- package/lib/wish-delegate.js +5 -2
- package/package.json +6 -1
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { knownCommands } = require('../lib/known-commands');
|
|
7
|
+
const {
|
|
8
|
+
enqueueCloudMission,
|
|
9
|
+
fetchCloudMissionStatus,
|
|
10
|
+
fetchCurrentCloudMission,
|
|
11
|
+
updateCloudMission,
|
|
12
|
+
fetchCloudMissionChecks,
|
|
13
|
+
} = require('../lib/cloud-mission');
|
|
14
|
+
const { apiRequestJson } = require('../utils/api');
|
|
15
|
+
const { decodeJwtClaims, loadCredentials } = require('../utils/auth');
|
|
16
|
+
|
|
17
|
+
const PACKAGE_PATH = path.join(__dirname, '..', 'package.json');
|
|
18
|
+
const HUMAN_STATES = Object.freeze({
|
|
19
|
+
ready: 'Ready',
|
|
20
|
+
working: 'Working',
|
|
21
|
+
your_turn: 'Your turn',
|
|
22
|
+
checking: 'Checking',
|
|
23
|
+
done: 'Done',
|
|
24
|
+
stopped: 'Stopped',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
class HumanCommandError extends Error {
|
|
28
|
+
constructor(message, next, exitCode = 1) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'HumanCommandError';
|
|
31
|
+
this.next = next;
|
|
32
|
+
this.exitCode = exitCode;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeWant(value) {
|
|
37
|
+
return String(value || '')
|
|
38
|
+
.normalize('NFKC')
|
|
39
|
+
.trim()
|
|
40
|
+
.replace(/\s+/g, ' ')
|
|
41
|
+
.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stableMissionKey(userId, businessId, want) {
|
|
45
|
+
return crypto
|
|
46
|
+
.createHash('sha256')
|
|
47
|
+
.update(`${String(userId)}\n${String(businessId)}\n${normalizeWant(want)}`)
|
|
48
|
+
.digest('hex');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function valueFlag(args, name) {
|
|
52
|
+
const prefix = `${name}=`;
|
|
53
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
54
|
+
const value = String(args[index]);
|
|
55
|
+
if (value.startsWith(prefix)) return { present: true, value: value.slice(prefix.length), index };
|
|
56
|
+
if (value === name) {
|
|
57
|
+
const next = args[index + 1];
|
|
58
|
+
return {
|
|
59
|
+
present: true,
|
|
60
|
+
value: next && !String(next).startsWith('--') ? String(next) : '',
|
|
61
|
+
index,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { present: false, value: '', index: -1 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseAskArgs(args) {
|
|
69
|
+
const budgetFlag = valueFlag(args, '--budget');
|
|
70
|
+
if (budgetFlag.present && !budgetFlag.value) {
|
|
71
|
+
throw new HumanCommandError(
|
|
72
|
+
'Atris needs a dollar amount after --budget.',
|
|
73
|
+
'Try again with: atris ask "what you want" --budget 2',
|
|
74
|
+
2,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
let budgetUsd = null;
|
|
78
|
+
if (budgetFlag.present) {
|
|
79
|
+
budgetUsd = Number(budgetFlag.value);
|
|
80
|
+
if (!Number.isFinite(budgetUsd) || budgetUsd <= 0) {
|
|
81
|
+
throw new HumanCommandError(
|
|
82
|
+
'Atris could not use that budget because it is not a positive dollar amount.',
|
|
83
|
+
'Try again with: atris ask "what you want" --budget 2',
|
|
84
|
+
2,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const textParts = [];
|
|
90
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
91
|
+
const value = String(args[index]);
|
|
92
|
+
if (value === '--json') continue;
|
|
93
|
+
if (value === '--budget') {
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (value.startsWith('--budget=')) continue;
|
|
98
|
+
if (value.startsWith('--')) {
|
|
99
|
+
throw new HumanCommandError(
|
|
100
|
+
`Atris does not know the option ${value}.`,
|
|
101
|
+
'Run: atris ask --help',
|
|
102
|
+
2,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
textParts.push(value);
|
|
106
|
+
}
|
|
107
|
+
const text = textParts.join(' ').trim();
|
|
108
|
+
if (!text) {
|
|
109
|
+
throw new HumanCommandError(
|
|
110
|
+
'Atris needs to know what you want.',
|
|
111
|
+
'Try: atris ask "make the home page clearer"',
|
|
112
|
+
2,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return { text, budgetUsd, asJson: args.includes('--json') };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function findWorkspaceFile(root, relativePath) {
|
|
119
|
+
let current = path.resolve(root || process.cwd());
|
|
120
|
+
while (true) {
|
|
121
|
+
const candidate = path.join(current, relativePath);
|
|
122
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
123
|
+
const parent = path.dirname(current);
|
|
124
|
+
if (parent === current) return null;
|
|
125
|
+
current = parent;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readBusinessBinding(root) {
|
|
130
|
+
const file = findWorkspaceFile(root, path.join('.atris', 'business.json'));
|
|
131
|
+
if (!file) return null;
|
|
132
|
+
try {
|
|
133
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
134
|
+
const businessId = String(parsed.business_id || parsed.id || '').trim();
|
|
135
|
+
return businessId || null;
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function credentialUserId(credentials) {
|
|
142
|
+
const claims = decodeJwtClaims(credentials && credentials.token);
|
|
143
|
+
return String(
|
|
144
|
+
credentials && (credentials.user_id || credentials.email)
|
|
145
|
+
|| claims && (claims.sub || claims.user_id || claims.email)
|
|
146
|
+
|| '',
|
|
147
|
+
).trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function resolveBusinessId(credentials, options = {}) {
|
|
151
|
+
const bound = options.businessId
|
|
152
|
+
|| process.env.ATRIS_BUSINESS_ID
|
|
153
|
+
|| credentials && credentials.business_id
|
|
154
|
+
|| readBusinessBinding(options.root || process.cwd());
|
|
155
|
+
if (bound) return String(bound);
|
|
156
|
+
|
|
157
|
+
const request = options.apiRequestJson || apiRequestJson;
|
|
158
|
+
const response = await request('/business/', {
|
|
159
|
+
method: 'GET',
|
|
160
|
+
token: credentials.token,
|
|
161
|
+
});
|
|
162
|
+
if (!response || !response.ok || !Array.isArray(response.data)) {
|
|
163
|
+
throw new HumanCommandError(
|
|
164
|
+
'Atris could not find the business for this work.',
|
|
165
|
+
'Open a business workspace, then try again.',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const businesses = response.data.filter((business) => business && (business.id || business.business_id));
|
|
169
|
+
if (businesses.length === 1) return String(businesses[0].id || businesses[0].business_id);
|
|
170
|
+
if (businesses.length > 1) {
|
|
171
|
+
throw new HumanCommandError(
|
|
172
|
+
'Atris found more than one business and could not safely choose one.',
|
|
173
|
+
'Open the business workspace you want, then try again.',
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
throw new HumanCommandError(
|
|
177
|
+
'Atris could not find a business to do this work for.',
|
|
178
|
+
'Create or join a business, then try again.',
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function wireState(value, needs = null) {
|
|
183
|
+
const state = String(value || '').trim().toLowerCase().replace(/[ -]+/g, '_');
|
|
184
|
+
if (needs && (!state || ['paused', 'blocked', 'waiting'].includes(state))) return 'your_turn';
|
|
185
|
+
if (['ready', 'pending', 'queued', 'planning', 'created'].includes(state)) return 'ready';
|
|
186
|
+
if (['working', 'running', 'active', 'in_progress', 'started'].includes(state)) return 'working';
|
|
187
|
+
if (['your_turn', 'waiting_for_human', 'needs_input', 'paused', 'blocked'].includes(state)) return 'your_turn';
|
|
188
|
+
if (['checking', 'verifying', 'reviewing', 'review'].includes(state)) return 'checking';
|
|
189
|
+
if (['done', 'complete', 'completed', 'passed', 'success', 'succeeded'].includes(state)) return 'done';
|
|
190
|
+
if (['stopped', 'cancelled', 'canceled', 'failed', 'error'].includes(state)) return 'stopped';
|
|
191
|
+
return 'ready';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function numberOrNull(value) {
|
|
195
|
+
if (value === null || value === undefined || value === '') return null;
|
|
196
|
+
const number = Number(value);
|
|
197
|
+
return Number.isFinite(number) ? number : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function secondsBetween(start, end) {
|
|
201
|
+
const startMs = Date.parse(start || '');
|
|
202
|
+
const endMs = Date.parse(end || '');
|
|
203
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return null;
|
|
204
|
+
return Math.max(0, Math.round((endMs - startMs) / 1000));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function defaultProgress(state) {
|
|
208
|
+
return {
|
|
209
|
+
ready: 0,
|
|
210
|
+
working: 35,
|
|
211
|
+
your_turn: 50,
|
|
212
|
+
checking: 90,
|
|
213
|
+
done: 100,
|
|
214
|
+
stopped: 0,
|
|
215
|
+
}[state];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function defaultWorkingOn(state) {
|
|
219
|
+
return {
|
|
220
|
+
ready: 'Waiting to start',
|
|
221
|
+
working: 'Working through your request',
|
|
222
|
+
your_turn: 'Waiting for your answer',
|
|
223
|
+
checking: 'Checking the finished work',
|
|
224
|
+
done: 'Work finished',
|
|
225
|
+
stopped: 'Work stopped',
|
|
226
|
+
}[state];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function defaultNext(state) {
|
|
230
|
+
return {
|
|
231
|
+
ready: 'Start the work',
|
|
232
|
+
working: 'Finish and check the work',
|
|
233
|
+
your_turn: 'Answer or approve the request',
|
|
234
|
+
checking: 'Finish the checks',
|
|
235
|
+
done: 'Review the proof',
|
|
236
|
+
stopped: 'Start a new mission when ready',
|
|
237
|
+
}[state];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function missionCard(payload, fallback = {}, now = Date.now()) {
|
|
241
|
+
const outer = payload && typeof payload === 'object' ? payload : {};
|
|
242
|
+
const source = outer.card || outer.mission || outer;
|
|
243
|
+
const result = source.result && typeof source.result === 'object' ? source.result : {};
|
|
244
|
+
const needs = source.needs || result.needs || null;
|
|
245
|
+
const state = wireState(source.state || source.status || result.state || result.status, needs);
|
|
246
|
+
const progress = numberOrNull(source.progress_pct ?? result.progress_pct);
|
|
247
|
+
const startedAt = source.started_at || outer.started_at || source.created_at || outer.created_at;
|
|
248
|
+
const endedAt = source.completed_at || outer.completed_at || source.stopped_at || outer.stopped_at;
|
|
249
|
+
const elapsed = numberOrNull(source.elapsed_s ?? result.elapsed_s)
|
|
250
|
+
?? secondsBetween(startedAt, endedAt || new Date(now).toISOString())
|
|
251
|
+
?? 0;
|
|
252
|
+
const content = source.content && typeof source.content === 'object' ? source.content : {};
|
|
253
|
+
const rawTitle = source.title || result.title || fallback.title || content.text || source.text || 'Current mission';
|
|
254
|
+
const title = String(rawTitle).split('\n\nWhen you finish,')[0].trim() || 'Current mission';
|
|
255
|
+
return {
|
|
256
|
+
mission_id: String(source.mission_id || source.task_id || source.id || outer.mission_id || outer.task_id || outer.id || fallback.mission_id || ''),
|
|
257
|
+
title,
|
|
258
|
+
state,
|
|
259
|
+
progress_pct: Math.max(0, Math.min(100, Math.round(progress ?? defaultProgress(state)))),
|
|
260
|
+
working_on: String(source.working_on || result.working_on || result.current_step || defaultWorkingOn(state)),
|
|
261
|
+
next: String(source.next || source.next_action || result.next || result.next_action || defaultNext(state)),
|
|
262
|
+
elapsed_s: Math.max(0, Math.round(elapsed)),
|
|
263
|
+
cost_usd: numberOrNull(source.cost_usd ?? result.cost_usd) ?? 0,
|
|
264
|
+
budget_usd: numberOrNull(source.budget_usd ?? result.budget_usd ?? fallback.budget_usd),
|
|
265
|
+
needs: needs && typeof needs === 'object'
|
|
266
|
+
? {
|
|
267
|
+
question: String(needs.question || needs.text || ''),
|
|
268
|
+
options: Array.isArray(needs.options) ? needs.options : [],
|
|
269
|
+
}
|
|
270
|
+
: null,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function statusPhrase(state) {
|
|
275
|
+
return {
|
|
276
|
+
ready: 'Ready to begin your work',
|
|
277
|
+
working: 'Working on your request now',
|
|
278
|
+
your_turn: 'Waiting for your answer now',
|
|
279
|
+
checking: 'Checking the finished work now',
|
|
280
|
+
done: 'Your work is finished now',
|
|
281
|
+
stopped: 'This work has been stopped',
|
|
282
|
+
}[state];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function formatSeconds(value) {
|
|
286
|
+
const seconds = Math.max(0, Math.round(Number(value) || 0));
|
|
287
|
+
if (seconds < 60) return `${seconds}s`;
|
|
288
|
+
const minutes = Math.floor(seconds / 60);
|
|
289
|
+
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
|
290
|
+
const hours = Math.floor(minutes / 60);
|
|
291
|
+
return `${hours}h ${minutes % 60}m`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function progressBar(progressPct) {
|
|
295
|
+
const width = 20;
|
|
296
|
+
const filled = Math.round((progressPct / 100) * width);
|
|
297
|
+
return `[${'#'.repeat(filled)}${'-'.repeat(width - filled)}] ${progressPct}%`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function renderMissionCard(card) {
|
|
301
|
+
const humanState = HUMAN_STATES[card.state] || HUMAN_STATES.ready;
|
|
302
|
+
const cost = `$${card.cost_usd.toFixed(2)}`;
|
|
303
|
+
const budget = card.budget_usd === null ? cost : `${cost} of $${card.budget_usd.toFixed(2)}`;
|
|
304
|
+
const lines = [
|
|
305
|
+
card.title,
|
|
306
|
+
`${humanState}: ${statusPhrase(card.state)}`,
|
|
307
|
+
progressBar(card.progress_pct),
|
|
308
|
+
`Working on: ${card.working_on}`,
|
|
309
|
+
`Next: ${card.next}`,
|
|
310
|
+
`Time: ${formatSeconds(card.elapsed_s)}`,
|
|
311
|
+
`Cost: ${budget}`,
|
|
312
|
+
];
|
|
313
|
+
if (card.needs && card.needs.question) lines.push(`Needs you: ${card.needs.question}`);
|
|
314
|
+
return lines;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function missionProofPayload(payload) {
|
|
318
|
+
const outer = payload && typeof payload === 'object' ? payload : {};
|
|
319
|
+
const mission = outer.mission && typeof outer.mission === 'object' ? outer.mission : outer;
|
|
320
|
+
const card = outer.card && typeof outer.card === 'object' ? outer.card : {};
|
|
321
|
+
const candidates = [
|
|
322
|
+
outer.proof,
|
|
323
|
+
outer.result && outer.result.proof,
|
|
324
|
+
mission.proof,
|
|
325
|
+
mission.result && mission.result.proof,
|
|
326
|
+
card.proof,
|
|
327
|
+
card.result && card.result.proof,
|
|
328
|
+
];
|
|
329
|
+
let proof = candidates.find((candidate) => candidate && typeof candidate === 'object');
|
|
330
|
+
if (!proof && outer.goal && (outer.changed || outer.kept_same || outer.checks)) proof = outer;
|
|
331
|
+
if (!proof) return null;
|
|
332
|
+
|
|
333
|
+
const fallbackCard = missionCard(payload);
|
|
334
|
+
return {
|
|
335
|
+
goal: String(proof.goal || mission.goal || fallbackCard.title || ''),
|
|
336
|
+
changed: Array.isArray(proof.changed) ? proof.changed : [],
|
|
337
|
+
kept_same: Array.isArray(proof.kept_same) ? proof.kept_same : [],
|
|
338
|
+
checks: Array.isArray(proof.checks) ? proof.checks : [],
|
|
339
|
+
elapsed_s: numberOrNull(proof.elapsed_s) ?? fallbackCard.elapsed_s,
|
|
340
|
+
cost_usd: numberOrNull(proof.cost_usd) ?? fallbackCard.cost_usd,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function proofItemText(value) {
|
|
345
|
+
if (typeof value === 'string') return value;
|
|
346
|
+
if (!value || typeof value !== 'object') return String(value || '');
|
|
347
|
+
return String(value.label || value.name || value.title || value.summary || value.text || '');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function humanLabel(value) {
|
|
351
|
+
const words = String(value || '')
|
|
352
|
+
.trim()
|
|
353
|
+
.replace(/[_-]+/g, ' ')
|
|
354
|
+
.replace(/\s+/g, ' ')
|
|
355
|
+
.split(' ')
|
|
356
|
+
.filter(Boolean)
|
|
357
|
+
.map((word) => ({ id: 'ID', ids: 'IDs', api: 'API', url: 'URL', usd: 'USD' }[word.toLowerCase()] || word));
|
|
358
|
+
if (words.length === 0) return 'Unnamed check';
|
|
359
|
+
words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1);
|
|
360
|
+
return words.join(' ');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function proofCheckPassed(check) {
|
|
364
|
+
if (typeof check === 'string') return true;
|
|
365
|
+
if (!check || typeof check !== 'object') return false;
|
|
366
|
+
if (typeof check.passed === 'boolean') return check.passed;
|
|
367
|
+
return ['passed', 'pass', 'ok', 'done'].includes(String(check.status || check.state || '').toLowerCase());
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function renderProofCard(proof) {
|
|
371
|
+
const changed = proof.changed.map(proofItemText).filter(Boolean);
|
|
372
|
+
const keptSame = proof.kept_same.map(proofItemText).filter(Boolean);
|
|
373
|
+
const passedChecks = proof.checks.filter(proofCheckPassed);
|
|
374
|
+
const failedChecks = proof.checks.filter((check) => !proofCheckPassed(check));
|
|
375
|
+
const lines = [
|
|
376
|
+
`Goal: ${proof.goal || 'Not reported'}`,
|
|
377
|
+
'Changed:',
|
|
378
|
+
...(changed.length ? changed.map((item) => `- ${item}`) : ['- Nothing reported']),
|
|
379
|
+
'Kept the same:',
|
|
380
|
+
...(keptSame.length ? keptSame.map((item) => `- ${item}`) : ['- Nothing reported']),
|
|
381
|
+
'Checks passed:',
|
|
382
|
+
...(passedChecks.length
|
|
383
|
+
? passedChecks.map((check) => `- ${humanLabel(proofItemText(check))}`)
|
|
384
|
+
: ['- None reported']),
|
|
385
|
+
];
|
|
386
|
+
if (failedChecks.length) {
|
|
387
|
+
lines.push('Checks not passed:');
|
|
388
|
+
lines.push(...failedChecks.map((check) => `- ${humanLabel(proofItemText(check))}`));
|
|
389
|
+
}
|
|
390
|
+
lines.push(`Time: ${formatSeconds(proof.elapsed_s)}`);
|
|
391
|
+
lines.push(`Cost: $${proof.cost_usd.toFixed(2)}`);
|
|
392
|
+
return lines;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function outputCard(card, asJson, log = console.log) {
|
|
396
|
+
if (asJson) {
|
|
397
|
+
log(JSON.stringify(card, null, 2));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
for (const line of renderMissionCard(card)) log(line);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function latestCloudReceipt(root) {
|
|
404
|
+
const file = findWorkspaceFile(root || process.cwd(), path.join('.atris', 'state', 'missions.jsonl'));
|
|
405
|
+
if (!file) return null;
|
|
406
|
+
try {
|
|
407
|
+
const rows = fs.readFileSync(file, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
408
|
+
for (let index = rows.length - 1; index >= 0; index -= 1) {
|
|
409
|
+
let row = null;
|
|
410
|
+
try { row = JSON.parse(rows[index]); } catch { continue; }
|
|
411
|
+
if (row && row.cloud === true && row.task_id) return row;
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function errorCopy(caught, action) {
|
|
420
|
+
if (caught instanceof HumanCommandError) {
|
|
421
|
+
return { message: caught.message, next: caught.next, exitCode: caught.exitCode };
|
|
422
|
+
}
|
|
423
|
+
const status = Number(caught && caught.status) || 0;
|
|
424
|
+
if (status === 401 || /not logged in/i.test(String(caught && caught.message || ''))) {
|
|
425
|
+
return {
|
|
426
|
+
message: `Atris could not ${action} because you are not signed in.`,
|
|
427
|
+
next: 'Run: atris login',
|
|
428
|
+
exitCode: 1,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
if (status === 404 && action === 'show the current mission') {
|
|
432
|
+
return {
|
|
433
|
+
message: 'Atris could not find a running mission.',
|
|
434
|
+
next: 'Start one with: atris ask "what you want"',
|
|
435
|
+
exitCode: 1,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
if (status === 404 && action === 'show proof for the last mission') {
|
|
439
|
+
return {
|
|
440
|
+
message: 'Atris could not find a mission with proof yet.',
|
|
441
|
+
next: 'Finish a mission, then run: atris proof',
|
|
442
|
+
exitCode: 1,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (status === 0) {
|
|
446
|
+
return {
|
|
447
|
+
message: `Atris could not ${action} because its service could not be reached.`,
|
|
448
|
+
next: 'Check your connection, then try again.',
|
|
449
|
+
exitCode: 1,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
message: `Atris could not ${action}.`,
|
|
454
|
+
next: 'Nothing changed. Check atris mission, then try again.',
|
|
455
|
+
exitCode: caught && caught.exitCode || 1,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function reportError(caught, action, asJson, options = {}) {
|
|
460
|
+
const copy = errorCopy(caught, action);
|
|
461
|
+
const logError = options.error || console.error;
|
|
462
|
+
const log = options.log || console.log;
|
|
463
|
+
if (asJson) {
|
|
464
|
+
log(JSON.stringify({
|
|
465
|
+
ok: false,
|
|
466
|
+
error: copy.message,
|
|
467
|
+
did: 'Nothing changed.',
|
|
468
|
+
next: copy.next,
|
|
469
|
+
}, null, 2));
|
|
470
|
+
} else {
|
|
471
|
+
logError(copy.message);
|
|
472
|
+
logError('Atris left your work unchanged.');
|
|
473
|
+
logError(copy.next);
|
|
474
|
+
}
|
|
475
|
+
if (options.setProcessExitCode !== false) process.exitCode = copy.exitCode;
|
|
476
|
+
return copy.exitCode;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function credentialsOrThrow(options = {}) {
|
|
480
|
+
const load = options.loadCredentials || loadCredentials;
|
|
481
|
+
const credentials = load();
|
|
482
|
+
if (!credentials || !String(credentials.token || '').trim()) {
|
|
483
|
+
throw new HumanCommandError(
|
|
484
|
+
'Atris could not continue because you are not signed in.',
|
|
485
|
+
'Run: atris login',
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
return credentials;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function askCommand(args, options = {}) {
|
|
492
|
+
const asJson = args.includes('--json');
|
|
493
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
494
|
+
(options.log || console.log)('Usage: atris ask "what you want" [--budget <usd>] [--json]');
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
const parsed = parseAskArgs(args);
|
|
499
|
+
const credentials = credentialsOrThrow(options);
|
|
500
|
+
const userId = credentialUserId(credentials);
|
|
501
|
+
if (!userId) {
|
|
502
|
+
throw new HumanCommandError(
|
|
503
|
+
'Atris could not safely identify who is starting this work.',
|
|
504
|
+
'Sign in again with: atris login',
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
const businessId = await resolveBusinessId(credentials, options);
|
|
508
|
+
const idempotencyKey = stableMissionKey(userId, businessId, parsed.text);
|
|
509
|
+
const mission = await enqueueCloudMission({
|
|
510
|
+
text: parsed.text,
|
|
511
|
+
businessId,
|
|
512
|
+
idempotencyKey,
|
|
513
|
+
budgetUsd: parsed.budgetUsd,
|
|
514
|
+
}, options);
|
|
515
|
+
const receipt = {
|
|
516
|
+
cloud: true,
|
|
517
|
+
task_id: mission.mission_id || mission.task_id || mission.id,
|
|
518
|
+
lane: mission.lane || 'fast',
|
|
519
|
+
text: parsed.text,
|
|
520
|
+
business_id: businessId,
|
|
521
|
+
budget_usd: parsed.budgetUsd,
|
|
522
|
+
};
|
|
523
|
+
const append = options.appendCloudMissionReceipt
|
|
524
|
+
|| require('../lib/cloud-mission').appendCloudMissionReceipt;
|
|
525
|
+
append(options.root || process.cwd(), receipt);
|
|
526
|
+
const card = missionCard(mission, {
|
|
527
|
+
mission_id: receipt.task_id,
|
|
528
|
+
title: parsed.text,
|
|
529
|
+
budget_usd: parsed.budgetUsd,
|
|
530
|
+
}, options.now ? options.now() : Date.now());
|
|
531
|
+
const log = options.log || console.log;
|
|
532
|
+
if (!parsed.asJson) {
|
|
533
|
+
const separator = /[.!?]$/.test(parsed.text) ? ' ' : '. ';
|
|
534
|
+
log(`I understood: ${parsed.text}${separator}I'm starting now.`);
|
|
535
|
+
log('');
|
|
536
|
+
}
|
|
537
|
+
outputCard(card, parsed.asJson, log);
|
|
538
|
+
return 0;
|
|
539
|
+
} catch (caught) {
|
|
540
|
+
return reportError(caught, 'start that mission', asJson, options);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function loadCurrentMission(options = {}) {
|
|
545
|
+
try {
|
|
546
|
+
return await fetchCurrentCloudMission(options);
|
|
547
|
+
} catch (caught) {
|
|
548
|
+
if (Number(caught && caught.status) !== 404) throw caught;
|
|
549
|
+
const receipt = latestCloudReceipt(options.root || process.cwd());
|
|
550
|
+
if (!receipt) throw caught;
|
|
551
|
+
const mission = await fetchCloudMissionStatus(receipt.task_id, options);
|
|
552
|
+
return {
|
|
553
|
+
...mission,
|
|
554
|
+
title: mission.title || receipt.text,
|
|
555
|
+
budget_usd: mission.budget_usd ?? receipt.budget_usd,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function currentMissionCommand(args = [], options = {}) {
|
|
561
|
+
const asJson = args.includes('--json');
|
|
562
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
563
|
+
(options.log || console.log)('Usage: atris mission [--json]');
|
|
564
|
+
return 0;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
credentialsOrThrow(options);
|
|
568
|
+
const mission = await loadCurrentMission(options);
|
|
569
|
+
const card = missionCard(mission, {}, options.now ? options.now() : Date.now());
|
|
570
|
+
outputCard(card, asJson, options.log || console.log);
|
|
571
|
+
return 0;
|
|
572
|
+
} catch (caught) {
|
|
573
|
+
return reportError(caught, 'show the current mission', asJson, options);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async function proofCommand(args = [], options = {}) {
|
|
578
|
+
const asJson = args.includes('--json');
|
|
579
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
580
|
+
(options.log || console.log)('Usage: atris proof [--json]');
|
|
581
|
+
return 0;
|
|
582
|
+
}
|
|
583
|
+
const unsupported = args.find((value) => value !== '--json');
|
|
584
|
+
if (unsupported) {
|
|
585
|
+
return reportError(
|
|
586
|
+
new HumanCommandError(
|
|
587
|
+
`Atris does not know the option ${unsupported}.`,
|
|
588
|
+
'Run: atris proof --help',
|
|
589
|
+
2,
|
|
590
|
+
),
|
|
591
|
+
'show proof for the last mission',
|
|
592
|
+
asJson,
|
|
593
|
+
options,
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
credentialsOrThrow(options);
|
|
598
|
+
const mission = await loadCurrentMission(options);
|
|
599
|
+
let proof = missionProofPayload(mission);
|
|
600
|
+
if (!proof) {
|
|
601
|
+
const card = missionCard(mission);
|
|
602
|
+
if (card.mission_id) {
|
|
603
|
+
proof = missionProofPayload(await fetchCloudMissionChecks(card.mission_id, options));
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (!proof) {
|
|
607
|
+
throw new HumanCommandError(
|
|
608
|
+
'Atris could not find proof for the last mission.',
|
|
609
|
+
'Wait for the mission to finish, then run: atris proof',
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
const log = options.log || console.log;
|
|
613
|
+
if (asJson) log(JSON.stringify(proof, null, 2));
|
|
614
|
+
else renderProofCard(proof).forEach((line) => log(line));
|
|
615
|
+
return 0;
|
|
616
|
+
} catch (caught) {
|
|
617
|
+
return reportError(caught, 'show proof for the last mission', asJson, options);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function changeCurrentMission(action, body, args, options = {}) {
|
|
622
|
+
const asJson = args.includes('--json');
|
|
623
|
+
try {
|
|
624
|
+
credentialsOrThrow(options);
|
|
625
|
+
const current = await loadCurrentMission(options);
|
|
626
|
+
const currentCard = missionCard(current, {}, options.now ? options.now() : Date.now());
|
|
627
|
+
if (!currentCard.mission_id) {
|
|
628
|
+
throw new HumanCommandError(
|
|
629
|
+
'Atris could not identify the current mission.',
|
|
630
|
+
'Run: atris mission',
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
if (['approve', 'answer'].includes(action) && currentCard.state !== 'your_turn') {
|
|
634
|
+
throw new HumanCommandError(
|
|
635
|
+
action === 'approve'
|
|
636
|
+
? 'Nothing is waiting for your approval.'
|
|
637
|
+
: 'The current mission is not waiting for an answer.',
|
|
638
|
+
'Check it with: atris mission',
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
const changed = await updateCloudMission(currentCard.mission_id, action, body, options);
|
|
642
|
+
const card = missionCard(changed, currentCard, options.now ? options.now() : Date.now());
|
|
643
|
+
outputCard(card, asJson, options.log || console.log);
|
|
644
|
+
return 0;
|
|
645
|
+
} catch (caught) {
|
|
646
|
+
const verb = action === 'approve' ? 'approve that step' : action === 'answer' ? 'send that answer' : 'stop that mission';
|
|
647
|
+
return reportError(caught, verb, asJson, options);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async function approveCommand(args, options = {}) {
|
|
652
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
653
|
+
(options.log || console.log)('Usage: atris approve [--json]');
|
|
654
|
+
return 0;
|
|
655
|
+
}
|
|
656
|
+
return changeCurrentMission('approve', {}, args, options);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function stopCommand(args, options = {}) {
|
|
660
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
661
|
+
(options.log || console.log)('Usage: atris stop [--json]');
|
|
662
|
+
return 0;
|
|
663
|
+
}
|
|
664
|
+
return changeCurrentMission('stop', {}, args, options);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async function answerCommand(args, options = {}) {
|
|
668
|
+
const text = args.filter((value) => value !== '--json').join(' ').trim();
|
|
669
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
670
|
+
(options.log || console.log)('Usage: atris mission answer "your answer" [--json]');
|
|
671
|
+
return 0;
|
|
672
|
+
}
|
|
673
|
+
if (!text) {
|
|
674
|
+
return reportError(
|
|
675
|
+
new HumanCommandError('Atris needs your answer.', 'Try: atris mission answer "yes, go ahead"', 2),
|
|
676
|
+
'send that answer',
|
|
677
|
+
args.includes('--json'),
|
|
678
|
+
options,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
return changeCurrentMission('answer', { answer: text }, args, options);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function checkPayload(payload, runId) {
|
|
685
|
+
const source = payload && typeof payload === 'object' ? payload : {};
|
|
686
|
+
const checks = source.checks || source.check_results || source.result && source.result.checks || [];
|
|
687
|
+
const rows = Array.isArray(checks) ? checks : [];
|
|
688
|
+
const passed = typeof source.passed === 'boolean'
|
|
689
|
+
? source.passed
|
|
690
|
+
: typeof source.ok === 'boolean'
|
|
691
|
+
? source.ok
|
|
692
|
+
: rows.length > 0 && rows.every((check) => check && (
|
|
693
|
+
check.passed === true
|
|
694
|
+
|| ['passed', 'pass', 'ok', 'done'].includes(String(check.status || check.state || '').toLowerCase())
|
|
695
|
+
));
|
|
696
|
+
return {
|
|
697
|
+
mission_id: String(source.mission_id || source.task_id || source.run_id || runId),
|
|
698
|
+
passed,
|
|
699
|
+
checks: rows,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function checkLine(check, index) {
|
|
704
|
+
if (typeof check === 'string') return `- ${check}`;
|
|
705
|
+
const name = String(check && (check.name || check.title || check.check) || `Check ${index + 1}`);
|
|
706
|
+
if (check && typeof check.passed === 'boolean') {
|
|
707
|
+
return `- ${name}: ${check.passed ? 'passed' : 'failed'}`;
|
|
708
|
+
}
|
|
709
|
+
const state = String(check && (check.status || check.state) || 'unknown').toLowerCase();
|
|
710
|
+
const word = ['passed', 'pass', 'ok', 'done'].includes(state) ? 'passed' : state;
|
|
711
|
+
return `- ${name}: ${word}`;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async function checkCommand(args, options = {}) {
|
|
715
|
+
const asJson = args.includes('--json');
|
|
716
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
717
|
+
(options.log || console.log)('Usage: atris check <run-id> [--json]');
|
|
718
|
+
return 0;
|
|
719
|
+
}
|
|
720
|
+
const positionals = args.filter((value) => !String(value).startsWith('--'));
|
|
721
|
+
const runId = String(positionals[0] || '').trim();
|
|
722
|
+
if (!runId) {
|
|
723
|
+
return reportError(
|
|
724
|
+
new HumanCommandError('Atris needs the mission id to check.', 'Try: atris check <run-id> --json', 2),
|
|
725
|
+
'show those checks',
|
|
726
|
+
asJson,
|
|
727
|
+
options,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
credentialsOrThrow(options);
|
|
732
|
+
const result = checkPayload(await fetchCloudMissionChecks(runId, options), runId);
|
|
733
|
+
const log = options.log || console.log;
|
|
734
|
+
if (asJson) {
|
|
735
|
+
log(JSON.stringify(result, null, 2));
|
|
736
|
+
} else {
|
|
737
|
+
log(`Checks for ${result.mission_id}`);
|
|
738
|
+
log(result.passed ? 'Passed: yes' : 'Passed: no');
|
|
739
|
+
if (result.checks.length === 0) log('No check results are ready yet.');
|
|
740
|
+
result.checks.forEach((check, index) => log(checkLine(check, index)));
|
|
741
|
+
}
|
|
742
|
+
return result.passed ? 0 : 1;
|
|
743
|
+
} catch (caught) {
|
|
744
|
+
return reportError(caught, 'show those checks', asJson, options);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function cliVersion() {
|
|
749
|
+
try {
|
|
750
|
+
const parsed = JSON.parse(fs.readFileSync(PACKAGE_PATH, 'utf8'));
|
|
751
|
+
return typeof parsed.version === 'string' ? parsed.version : null;
|
|
752
|
+
} catch {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function routeWasReached(response, expectedMissingText) {
|
|
758
|
+
if (!response) return false;
|
|
759
|
+
if (response.ok) return true;
|
|
760
|
+
if (response.status !== 404) return false;
|
|
761
|
+
const detail = String(response.error || response.data && response.data.detail || '').trim();
|
|
762
|
+
return detail.toLowerCase().includes(expectedMissingText);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async function safeProbe(request, pathname, options) {
|
|
766
|
+
try {
|
|
767
|
+
return await request(pathname, options);
|
|
768
|
+
} catch {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
async function readyCommand(args, options = {}) {
|
|
774
|
+
const asJson = args.includes('--json');
|
|
775
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
776
|
+
(options.log || console.log)('Usage: atris ready --json');
|
|
777
|
+
return 0;
|
|
778
|
+
}
|
|
779
|
+
const request = options.apiRequestJson || apiRequestJson;
|
|
780
|
+
const load = options.loadCredentials || loadCredentials;
|
|
781
|
+
const credentials = load() || {};
|
|
782
|
+
const token = String(credentials.token || '').trim();
|
|
783
|
+
const [computerStatus, atrisHealth, missionProbe, checkProbe] = await Promise.all([
|
|
784
|
+
token
|
|
785
|
+
? safeProbe(request, '/ai-computer/user/status', { method: 'GET', token })
|
|
786
|
+
: Promise.resolve(null),
|
|
787
|
+
safeProbe(request, '/atris2/health', token ? { method: 'GET', token } : { method: 'GET' }),
|
|
788
|
+
token
|
|
789
|
+
? safeProbe(request, '/atris2/missions/atris-ready-probe', { method: 'GET', token })
|
|
790
|
+
: Promise.resolve(null),
|
|
791
|
+
token
|
|
792
|
+
? safeProbe(request, '/mission-control/missions/atris-ready-probe', { method: 'GET', token })
|
|
793
|
+
: Promise.resolve(null),
|
|
794
|
+
]);
|
|
795
|
+
const version = cliVersion();
|
|
796
|
+
const computer = computerStatus && computerStatus.ok && computerStatus.data && typeof computerStatus.data === 'object'
|
|
797
|
+
? computerStatus.data
|
|
798
|
+
: {};
|
|
799
|
+
const rawComputerVersion = computer.computer_version
|
|
800
|
+
|| computer.runtime_version
|
|
801
|
+
|| computer.atris_version
|
|
802
|
+
|| computer.version
|
|
803
|
+
|| computer.runtime && computer.runtime.version
|
|
804
|
+
|| null;
|
|
805
|
+
const computerVersion = rawComputerVersion ? String(rawComputerVersion) : null;
|
|
806
|
+
const atrisReady = Boolean(atrisHealth && atrisHealth.ok && atrisHealth.data && atrisHealth.data.ready !== false);
|
|
807
|
+
const canRunMissions = Boolean(token && atrisReady && routeWasReached(missionProbe, 'mission not found'));
|
|
808
|
+
const canCheckWork = Boolean(token && atrisReady && routeWasReached(checkProbe, 'mission not found'));
|
|
809
|
+
const canMakeProof = knownCommands.includes('proof');
|
|
810
|
+
const payload = {
|
|
811
|
+
ready: Boolean(version && canRunMissions && canCheckWork && canMakeProof),
|
|
812
|
+
cli_version: version,
|
|
813
|
+
computer_version: computerVersion,
|
|
814
|
+
can_run_missions: canRunMissions,
|
|
815
|
+
can_check_work: canCheckWork,
|
|
816
|
+
can_make_proof: canMakeProof,
|
|
817
|
+
};
|
|
818
|
+
const log = options.log || console.log;
|
|
819
|
+
if (asJson) log(JSON.stringify(payload, null, 2));
|
|
820
|
+
else {
|
|
821
|
+
log(payload.ready ? 'Atris is ready.' : 'Atris is not fully ready yet.');
|
|
822
|
+
log(`Run missions: ${payload.can_run_missions ? 'yes' : 'no'}`);
|
|
823
|
+
log(`Check work: ${payload.can_check_work ? 'yes' : 'no'}`);
|
|
824
|
+
log(`Make proof: ${payload.can_make_proof ? 'yes' : 'no'}`);
|
|
825
|
+
}
|
|
826
|
+
return payload.ready ? 0 : 1;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
module.exports = {
|
|
830
|
+
HUMAN_STATES,
|
|
831
|
+
stableMissionKey,
|
|
832
|
+
missionCard,
|
|
833
|
+
renderMissionCard,
|
|
834
|
+
missionProofPayload,
|
|
835
|
+
renderProofCard,
|
|
836
|
+
askCommand,
|
|
837
|
+
currentMissionCommand,
|
|
838
|
+
proofCommand,
|
|
839
|
+
approveCommand,
|
|
840
|
+
stopCommand,
|
|
841
|
+
answerCommand,
|
|
842
|
+
checkCommand,
|
|
843
|
+
readyCommand,
|
|
844
|
+
};
|