atris 3.70.0 → 3.71.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/README.md +18 -0
- package/atris/skills/README.md +1 -0
- package/atris/skills/campaigns/SKILL.md +81 -0
- package/atris/skills/meta/SKILL.md +52 -8
- package/ax +226 -42
- package/bin/atris.js +26 -6
- package/commands/autoland.js +176 -7
- package/commands/autopilot.js +71 -11
- package/commands/campaign.js +759 -0
- package/commands/compile.js +19 -8
- package/commands/doctor.js +58 -0
- package/commands/dream.js +18 -3
- package/commands/engine.js +147 -5
- package/commands/improve.js +119 -9
- package/commands/init.js +18 -1
- package/commands/interview.js +15 -3
- package/commands/land.js +22 -6
- package/commands/link.js +1 -0
- package/commands/member.js +359 -94
- package/commands/mission.js +249 -36
- package/commands/report.js +42 -8
- package/commands/run.js +9 -2
- package/commands/scout.js +9 -1
- package/commands/task.js +71 -17
- package/commands/team.js +212 -4
- package/commands/unknowns.js +15 -1
- package/commands/verify.js +46 -0
- package/commands/worktree.js +108 -15
- package/commands/write.js +12 -1
- package/commands/youtube.js +17 -2
- package/lib/accept-verify-gate.js +1 -0
- package/lib/auto-accept-certified.js +1 -0
- package/lib/autoland.js +13 -4
- package/lib/campaign-cadence.js +131 -0
- package/lib/campaign-core.js +330 -0
- package/lib/campaign-holdout.js +65 -0
- package/lib/campaign-loop.js +242 -0
- package/lib/campaign-missions.js +237 -0
- package/lib/campaign-new.js +251 -0
- package/lib/campaign-protect.js +75 -0
- package/lib/campaign-reading.js +147 -0
- package/lib/campaign-schema.js +196 -0
- package/lib/campaign-store.js +380 -0
- package/lib/campaign-sync.js +302 -0
- package/lib/campaign-trace.js +300 -0
- package/lib/conductor-artifacts.js +15 -1
- package/lib/daily-log.js +11 -1
- package/lib/dispatch-scout.js +18 -0
- package/lib/engine-ask.js +33 -3
- package/lib/engine-registry.js +2 -0
- package/lib/engine-resource.js +518 -0
- package/lib/fleet.js +125 -16
- package/lib/init-commit.js +80 -0
- package/lib/intents.js +8 -0
- package/lib/known-commands.js +1 -1
- package/lib/land-green.js +58 -7
- package/lib/landing-proof.js +352 -0
- package/lib/member-autoland.js +395 -18
- package/lib/member-board.js +7 -1
- package/lib/member-map-fold.js +126 -0
- package/lib/member-map-inbox.js +80 -0
- package/lib/member-progress.js +48 -1
- package/lib/member-report.js +127 -8
- package/lib/member-self-next-job.js +97 -0
- package/lib/member-task-pick.js +41 -14
- package/lib/member-up.js +249 -16
- package/lib/roster-prep.js +25 -2
- package/lib/runner-command.js +5 -0
- package/lib/task-db.js +47 -11
- package/lib/task-explanation.js +7 -0
- package/lib/task-list-keeper.js +8 -1
- package/lib/task-receipt.js +6 -1
- package/mcp/atris-mcp/index.mjs +212 -10
- package/package.json +2 -1
- package/scripts/det/engine-reading.js +37 -0
- package/scripts/det/ytnotes +29 -5
package/lib/task-receipt.js
CHANGED
|
@@ -51,7 +51,12 @@ function writeTaskReceipt({ taskId, command, root = process.cwd(), runner } = {}
|
|
|
51
51
|
if (!taskId) return { ok: false, passed: false, reason: 'task_id_required' };
|
|
52
52
|
if (!cmd) return { ok: false, passed: false, reason: 'verify_command_required' };
|
|
53
53
|
const run = runner || spawnSync;
|
|
54
|
-
|
|
54
|
+
// A login shell can put an older node first on PATH (macOS path_helper puts
|
|
55
|
+
// /usr/local/bin ahead), so the check would run on a node atris does not
|
|
56
|
+
// support. Put the node running atris first, after the profile has loaded.
|
|
57
|
+
const nodeDir = path.dirname(process.execPath).replace(/'/g, `'\\''`);
|
|
58
|
+
const script = `export PATH='${nodeDir}':"$PATH"\n${cmd}`;
|
|
59
|
+
const result = run('bash', ['-lc', script], {
|
|
55
60
|
cwd: root,
|
|
56
61
|
encoding: 'utf8',
|
|
57
62
|
timeout: 120000,
|
package/mcp/atris-mcp/index.mjs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// atris mcp, a stdio Model Context Protocol server
|
|
3
|
-
//
|
|
4
|
-
// Auth resolves the same way as the CLI: ATRIS_API_KEY, then
|
|
5
|
-
//
|
|
2
|
+
// atris mcp, a stdio Model Context Protocol server.
|
|
3
|
+
// Design tools (design_extract, design_check, design_search) call the design
|
|
4
|
+
// API. Auth resolves the same way as the CLI: ATRIS_API_KEY, then the
|
|
5
|
+
// logged-in atris token, then ~/.atris/design-api-key.
|
|
6
|
+
// Campaign tools (campaign_list, campaign_show, campaign_new, campaign_start,
|
|
7
|
+
// campaign_run, campaign_ask, campaign_answer) run `atris campaign ... --json`
|
|
8
|
+
// in the workspace and need no key.
|
|
6
9
|
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
7
11
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
8
12
|
import { createInterface } from 'node:readline';
|
|
9
13
|
import { fileURLToPath } from 'node:url';
|
|
@@ -11,7 +15,7 @@ import designApi from '../../lib/design-api.js';
|
|
|
11
15
|
|
|
12
16
|
const { resolveDesignKey, designRequest, pollDesignJob, billingOf } = designApi;
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
const DESIGN_TOOLS = [
|
|
15
19
|
{
|
|
16
20
|
name: 'design_extract',
|
|
17
21
|
description: 'extract a site\'s design system (colors, typography, layout, voice) as json. costs 10 credits, 2 on a cache hit.',
|
|
@@ -49,6 +53,104 @@ export const TOOLS = [
|
|
|
49
53
|
},
|
|
50
54
|
];
|
|
51
55
|
|
|
56
|
+
const CWD = { type: 'string', description: 'workspace folder to run in; defaults to the folder this server started in' };
|
|
57
|
+
const ID = { type: 'string', description: 'campaign id, e.g. cmp-ship-billing-1a2b' };
|
|
58
|
+
const AS = { type: 'string', description: 'the member you are working as, e.g. builder; writes are never recorded as a person' };
|
|
59
|
+
|
|
60
|
+
export const CAMPAIGN_TOOLS = [
|
|
61
|
+
{
|
|
62
|
+
name: 'campaign_list',
|
|
63
|
+
description: 'list every atris campaign with its state, money spent, and the next move. a campaign is a long goal with a judge (a command that exits 0 when the goal is met), a budget, and members.',
|
|
64
|
+
inputSchema: { type: 'object', properties: { cwd: CWD } },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'campaign_show',
|
|
68
|
+
description: 'show one campaign: judge and its last reading, budget, seats, missions, open asks, and the next move.',
|
|
69
|
+
inputSchema: { type: 'object', properties: { id: ID, cwd: CWD }, required: ['id'] },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'campaign_new',
|
|
73
|
+
description: 'the easiest start: give a goal in plain words. the owner\'s engine drafts a judge script, atris runs it to prove it fails today, and returns the draft. start: true also starts the campaign. budget defaults to $20.',
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
goal: { type: 'string', description: 'the goal in plain words' },
|
|
78
|
+
budget: { type: 'number', description: 'dollars the campaign may spend (default 20)' },
|
|
79
|
+
owner: { type: 'string', description: 'member who owns it (default mission-lead)' },
|
|
80
|
+
start: { type: 'boolean', description: 'start the campaign once the drafted judge fails today' },
|
|
81
|
+
cwd: CWD,
|
|
82
|
+
},
|
|
83
|
+
required: ['goal'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'campaign_start',
|
|
88
|
+
description: 'start a campaign with a judge you wrote. the judge runs first and a judge that already passes is refused: it must fail today. print graded output like "ℹ pass 3" and "ℹ fail 2" so progress shows.',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
title: { type: 'string', description: 'short name for the goal' },
|
|
93
|
+
judge: { type: 'string', description: 'shell command that exits 0 only when the goal is met' },
|
|
94
|
+
budget: { type: 'number', description: 'dollars the campaign may spend' },
|
|
95
|
+
owner: { type: 'string', description: 'member who owns it (default mission-lead)' },
|
|
96
|
+
seats: { type: 'array', items: { type: 'string' }, description: 'members who work on it, as member or member:role' },
|
|
97
|
+
bet: { type: 'string', description: 'what you think will work' },
|
|
98
|
+
id: { type: 'string', description: 'optional id, cmp-<lowercase-words>' },
|
|
99
|
+
as: AS,
|
|
100
|
+
cwd: CWD,
|
|
101
|
+
},
|
|
102
|
+
required: ['title', 'judge', 'budget'],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'campaign_run',
|
|
107
|
+
description: 'let the campaign run itself for at most max_steps steps: it judges, proposes missions, and runs them. it stops early when the judge passes, the money runs out, or a person must answer an ask. returns the stop reason.',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
id: ID,
|
|
112
|
+
max_steps: { type: 'number', description: 'steps before it stops, 1 to 20 (default 3)' },
|
|
113
|
+
parallel: { type: 'number', description: 'missions at once, 1 to 5 (default 3)' },
|
|
114
|
+
as: AS,
|
|
115
|
+
cwd: CWD,
|
|
116
|
+
},
|
|
117
|
+
required: ['id'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'campaign_ask',
|
|
122
|
+
description: 'stop for a person before anything that cannot be undone: sending, paying, deleting, deploying, publishing. the campaign waits until a person answers.',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
id: ID,
|
|
127
|
+
question: { type: 'string', description: 'the question, with what happens on yes' },
|
|
128
|
+
mission: { type: 'string', description: 'mission id the ask belongs to' },
|
|
129
|
+
as: AS,
|
|
130
|
+
cwd: CWD,
|
|
131
|
+
},
|
|
132
|
+
required: ['id', 'question'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'campaign_answer',
|
|
137
|
+
description: 'pass along a person\'s answer to an open ask. an agent cannot close an ask: the answer is recorded and the ask stays open until the person runs the returned person_command in their own terminal.',
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
id: ID,
|
|
142
|
+
ask_id: { type: 'string', description: 'ask id from campaign_show, e.g. a-1b2c3d' },
|
|
143
|
+
answer: { type: 'string', description: 'the person\'s answer in their words' },
|
|
144
|
+
as: AS,
|
|
145
|
+
cwd: CWD,
|
|
146
|
+
},
|
|
147
|
+
required: ['id', 'ask_id', 'answer'],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
export const TOOLS = [...DESIGN_TOOLS, ...CAMPAIGN_TOOLS];
|
|
153
|
+
|
|
52
154
|
function terminal(job) {
|
|
53
155
|
const s = String((job && job.status) || '').toLowerCase();
|
|
54
156
|
return s === 'completed' || s === 'failed' || s === 'error' || s === 'succeeded';
|
|
@@ -112,6 +214,100 @@ async function runJob(first, pollPath, key) {
|
|
|
112
214
|
return job;
|
|
113
215
|
}
|
|
114
216
|
|
|
217
|
+
const CLI = fileURLToPath(new URL('../../bin/atris.js', import.meta.url));
|
|
218
|
+
|
|
219
|
+
function text(value) {
|
|
220
|
+
return String(value ?? '').trim();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function bounded(value, min, max, fallback) {
|
|
224
|
+
const n = Number(value);
|
|
225
|
+
return Number.isFinite(n) ? Math.min(max, Math.max(min, Math.round(n))) : fallback;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// The argv for `atris campaign` for one tool call, or { error }.
|
|
229
|
+
export function campaignArgs(name, args = {}) {
|
|
230
|
+
const as = text(args.as) ? ['--as', text(args.as)] : [];
|
|
231
|
+
const need = (...keys) => keys.filter((key) => !text(args[key]));
|
|
232
|
+
if (name === 'campaign_list') return ['campaign', 'list', '--json'];
|
|
233
|
+
if (name === 'campaign_show') {
|
|
234
|
+
if (need('id').length) return { error: 'campaign_show needs an id' };
|
|
235
|
+
return ['campaign', 'show', text(args.id), '--json'];
|
|
236
|
+
}
|
|
237
|
+
if (name === 'campaign_new') {
|
|
238
|
+
if (need('goal').length) return { error: 'campaign_new needs a goal' };
|
|
239
|
+
const out = ['campaign', 'new', text(args.goal)];
|
|
240
|
+
if (args.budget !== undefined) out.push('--budget', String(args.budget));
|
|
241
|
+
if (text(args.owner)) out.push('--owner', text(args.owner));
|
|
242
|
+
if (args.start === true) out.push('--yes');
|
|
243
|
+
return [...out, '--json'];
|
|
244
|
+
}
|
|
245
|
+
if (name === 'campaign_start') {
|
|
246
|
+
const missing = need('title', 'judge');
|
|
247
|
+
if (missing.length || args.budget === undefined) return { error: 'campaign_start needs a title, a judge, and a budget' };
|
|
248
|
+
const out = ['campaign', 'start', text(args.title), '--judge', text(args.judge), '--budget', String(args.budget)];
|
|
249
|
+
if (text(args.owner)) out.push('--owner', text(args.owner));
|
|
250
|
+
for (const seat of Array.isArray(args.seats) ? args.seats : []) if (text(seat)) out.push('--seat', text(seat));
|
|
251
|
+
if (text(args.bet)) out.push('--bet', text(args.bet));
|
|
252
|
+
if (text(args.id)) out.push('--id', text(args.id));
|
|
253
|
+
return [...out, ...as, '--json'];
|
|
254
|
+
}
|
|
255
|
+
if (name === 'campaign_run') {
|
|
256
|
+
if (need('id').length) return { error: 'campaign_run needs an id' };
|
|
257
|
+
return ['campaign', 'run', text(args.id), '--max-steps', String(bounded(args.max_steps, 1, 20, 3)),
|
|
258
|
+
'--parallel', String(bounded(args.parallel, 1, 5, 3)), ...as, '--json'];
|
|
259
|
+
}
|
|
260
|
+
if (name === 'campaign_ask') {
|
|
261
|
+
if (need('id', 'question').length) return { error: 'campaign_ask needs an id and a question' };
|
|
262
|
+
const out = ['campaign', 'ask', text(args.id), text(args.question)];
|
|
263
|
+
if (text(args.mission)) out.push('--mission', text(args.mission));
|
|
264
|
+
return [...out, ...as, '--json'];
|
|
265
|
+
}
|
|
266
|
+
if (name === 'campaign_answer') {
|
|
267
|
+
if (need('id', 'ask_id', 'answer').length) return { error: 'campaign_answer needs an id, an ask_id, and an answer' };
|
|
268
|
+
return ['campaign', 'answer', text(args.id), text(args.ask_id), text(args.answer), ...as, '--json'];
|
|
269
|
+
}
|
|
270
|
+
return { error: `unknown tool: ${name}` };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Every write from here is an agent's: the CLI never records it as a person.
|
|
274
|
+
function runCli(argv, cwd) {
|
|
275
|
+
const env = { ...process.env, ATRIS_SKIP_UPDATE_CHECK: '1', ATRIS_NONINTERACTIVE: '1', ATRIS_AGENT_ID: process.env.ATRIS_AGENT_ID || 'mcp-agent' };
|
|
276
|
+
delete env.ATRIS_HUMAN;
|
|
277
|
+
return new Promise((resolve) => {
|
|
278
|
+
const child = spawn(process.execPath, [CLI, ...argv], { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
279
|
+
let stdout = '';
|
|
280
|
+
let stderr = '';
|
|
281
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
282
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
283
|
+
child.on('error', (error) => resolve({ code: null, stdout, stderr: error.message }));
|
|
284
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function lastJson(stdout) {
|
|
289
|
+
const lines = String(stdout).split('\n').map((line) => line.trim()).filter(Boolean).reverse();
|
|
290
|
+
for (const line of lines) {
|
|
291
|
+
try { return JSON.parse(line); } catch {}
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function handleCampaignTool(name, args = {}) {
|
|
297
|
+
const argv = campaignArgs(name, args);
|
|
298
|
+
if (!Array.isArray(argv)) return fail(argv.error);
|
|
299
|
+
const ran = await runCli(argv, text(args.cwd) || process.cwd());
|
|
300
|
+
const data = lastJson(ran.stdout);
|
|
301
|
+
if (!data) return fail(`atris campaign gave no json (exit ${ran.code}): ${text(ran.stderr || ran.stdout).slice(-400)}`);
|
|
302
|
+
if (name === 'campaign_answer' && data.campaign && data.campaign.asks.some((ask) => ask.id === text(args.ask_id))) {
|
|
303
|
+
data.still_open = true;
|
|
304
|
+
data.person_command = `atris campaign answer ${text(args.id)} ${text(args.ask_id)} ${JSON.stringify(text(args.answer))}`;
|
|
305
|
+
}
|
|
306
|
+
const result = { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
307
|
+
if (data.ok === false) result.isError = true;
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
115
311
|
export async function handleTool(name, args = {}, key) {
|
|
116
312
|
if (name === 'design_extract') {
|
|
117
313
|
const url = String(args.url || '').trim();
|
|
@@ -184,13 +380,19 @@ async function serveStdio() {
|
|
|
184
380
|
} else if (request.method === 'tools/call') {
|
|
185
381
|
const pending = { cancelled: false };
|
|
186
382
|
const work = (async () => {
|
|
383
|
+
const toolName = String(request.params?.name || '');
|
|
384
|
+
const isCampaign = toolName.startsWith('campaign_');
|
|
187
385
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
386
|
+
if (isCampaign) {
|
|
387
|
+
result = await handleCampaignTool(toolName, request.params?.arguments || {});
|
|
388
|
+
} else {
|
|
389
|
+
const key = resolveDesignKey();
|
|
390
|
+
result = key
|
|
391
|
+
? await handleTool(request.params?.name, request.params?.arguments || {}, key)
|
|
392
|
+
: fail('no api key found. set ATRIS_API_KEY or run: atris login (or save a key in ~/.atris/design-api-key)');
|
|
393
|
+
}
|
|
192
394
|
} catch (error) {
|
|
193
|
-
result = fail(
|
|
395
|
+
result = fail(`${isCampaign ? 'campaign' : 'design'} call failed: ${(error && error.message) || error}`);
|
|
194
396
|
}
|
|
195
397
|
if (!pending.cancelled) {
|
|
196
398
|
await new Promise((resolve, reject) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.71.0",
|
|
4
4
|
"description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
|
|
5
5
|
"main": "bin/atris.js",
|
|
6
6
|
"bin": {
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"test:fast": "xargs node --test < test/fast-tests.txt",
|
|
74
74
|
"lint:decks": "node scripts/lint-all-decks.js",
|
|
75
75
|
"audit:markdown-whitespace": "node scripts/audit-markdown-whitespace.js",
|
|
76
|
+
"validate:campaign": "node scripts/campaign-conformance.js --merged",
|
|
76
77
|
"publish:release": "node scripts/publish-atris-release.js",
|
|
77
78
|
"prepare": "cp scripts/pre-commit .git/hooks/pre-commit 2>/dev/null && cp scripts/commit-msg .git/hooks/commit-msg 2>/dev/null && cp scripts/prepare-commit-msg .git/hooks/prepare-commit-msg 2>/dev/null && chmod +x .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/prepare-commit-msg || true"
|
|
78
79
|
},
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Machine readings for an engine a shell script launches, written to the
|
|
5
|
+
// caller's Atris workspace like every other engine run.
|
|
6
|
+
// engine-reading.js start <engine> <task> <pid> prints a token
|
|
7
|
+
// engine-reading.js end <engine> <task> <token> <status> prints the kill cause, if any
|
|
8
|
+
// The token is the run id and the start time, so the end can say how long it ran.
|
|
9
|
+
// The pid is the calling script's, which outlives this helper, so a sweep
|
|
10
|
+
// closes the run only once the script itself is gone.
|
|
11
|
+
// A shell sees a SIGKILL as exit 137 and an alarm as 142. Outside an Atris
|
|
12
|
+
// workspace (no atris/ folder) it writes nothing and prints nothing.
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
const { recordEngineResource } = require('../../lib/engine-resource');
|
|
18
|
+
|
|
19
|
+
const [phase, engine, task, tokenOrPid, status] = process.argv.slice(2);
|
|
20
|
+
const root = process.cwd();
|
|
21
|
+
|
|
22
|
+
if (fs.existsSync(path.join(root, 'atris'))) {
|
|
23
|
+
if (phase === 'start') {
|
|
24
|
+
const id = crypto.randomUUID();
|
|
25
|
+
recordEngineResource(root, { phase: 'start', engine, task, run_id: id }, { pid: Number(tokenOrPid) });
|
|
26
|
+
process.stdout.write(`${id}@${Date.now()}`);
|
|
27
|
+
} else if (phase === 'end' && tokenOrPid) {
|
|
28
|
+
const [runId, startedAt] = tokenOrPid.split('@');
|
|
29
|
+
const code = Number(status);
|
|
30
|
+
const signal = code === 137 ? 'SIGKILL' : null;
|
|
31
|
+
const timedOut = code === 142;
|
|
32
|
+
const error = code && !signal && !timedOut ? `exited ${code}` : '';
|
|
33
|
+
const durationMs = Math.max(0, Date.now() - Number(startedAt));
|
|
34
|
+
const entry = recordEngineResource(root, { phase: 'end', engine, task, run_id: runId, signal, timed_out: timedOut, error, duration_ms: durationMs });
|
|
35
|
+
if (entry && entry.cause) process.stdout.write(entry.cause);
|
|
36
|
+
}
|
|
37
|
+
}
|
package/scripts/det/ytnotes
CHANGED
|
@@ -315,6 +315,28 @@ run_with_timeout() {
|
|
|
315
315
|
perl -e '$seconds = shift @ARGV; alarm $seconds; exec @ARGV or do { print STDERR "$ARGV[0]: not installed\n"; exit 127 }' "$seconds" "$@"
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
# Machine memory and load at both ends of one writer launch, in the caller's
|
|
319
|
+
# Atris workspace, so a writer killed with SIGKILL says whether the machine
|
|
320
|
+
# was short on memory. A reading that fails never stops the notes.
|
|
321
|
+
engine_reading() {
|
|
322
|
+
(cd "$CALLER_CWD" && node "$SCRIPT_DIR/engine-reading.js" "$@") 2>/dev/null || true
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
with_readings() {
|
|
326
|
+
local engine="$1" token cause status=0
|
|
327
|
+
shift
|
|
328
|
+
token=$(engine_reading start "$engine" "youtube notes" "$$")
|
|
329
|
+
"$@" || status=$?
|
|
330
|
+
[ -n "$token" ] || return "$status"
|
|
331
|
+
cause=$(engine_reading end "$engine" "youtube notes" "$token" "$status")
|
|
332
|
+
if [ -n "$cause" ]; then
|
|
333
|
+
echo "$engine stopped by SIGKILL; $cause" >&2
|
|
334
|
+
# auto sends writer errors to a file; it shows this line on screen.
|
|
335
|
+
echo "$engine stopped by SIGKILL; $cause" > "$WORK/yt_${ID}.kill"
|
|
336
|
+
fi
|
|
337
|
+
return "$status"
|
|
338
|
+
}
|
|
339
|
+
|
|
318
340
|
run_atris_fast() {
|
|
319
341
|
if [ ! -d "$CALLER_CWD/atris" ]; then
|
|
320
342
|
echo "atris-fast requires ytnotes to run from an initialized Atris workspace" >&2
|
|
@@ -392,12 +414,12 @@ process.stdin.on("end", () => {
|
|
|
392
414
|
}
|
|
393
415
|
|
|
394
416
|
run_grok() {
|
|
395
|
-
run_with_timeout 240 grok --verbatim --no-memory --no-plan --no-subagents \
|
|
417
|
+
with_readings grok run_with_timeout 240 grok --verbatim --no-memory --no-plan --no-subagents \
|
|
396
418
|
--disable-web-search --max-turns 3 -p "$PROMPT"
|
|
397
419
|
}
|
|
398
420
|
|
|
399
421
|
run_haiku() {
|
|
400
|
-
run_with_timeout 240 claude -p "$PROMPT" --model "${1:-claude-haiku-5-5}"
|
|
422
|
+
with_readings claude run_with_timeout 240 claude -p "$PROMPT" --model "${1:-claude-haiku-5-5}"
|
|
401
423
|
}
|
|
402
424
|
|
|
403
425
|
# Gemini Flash through the Antigravity CLI (agy). On an 85-minute transcript it
|
|
@@ -426,7 +448,7 @@ run_gemini() {
|
|
|
426
448
|
command -v agy >/dev/null 2>&1 || { echo "agy is not installed" >&2; return 127; }
|
|
427
449
|
agy_is_cli || { echo "agy on PATH is the Antigravity editor launcher, which has no -p mode. Install the Antigravity CLI for Gemini notes." >&2; return 127; }
|
|
428
450
|
agy_signed_in || { echo "agy is installed but not signed in. Run: agy (sign in once) for Gemini notes." >&2; return 127; }
|
|
429
|
-
run_with_timeout 120 agy -p "$PROMPT" --model "${ATRIS_YTNOTES_GEMINI_MODEL:-${1:-gemini-3.8-flash}}" \
|
|
451
|
+
with_readings agy run_with_timeout 120 agy -p "$PROMPT" --model "${ATRIS_YTNOTES_GEMINI_MODEL:-${1:-gemini-3.8-flash}}" \
|
|
430
452
|
--effort "${2:-low}" --print-timeout 110s </dev/null
|
|
431
453
|
}
|
|
432
454
|
|
|
@@ -483,8 +505,8 @@ run_writer() {
|
|
|
483
505
|
case "$1" in
|
|
484
506
|
agy) run_gemini "$model" "${3:-}" ;;
|
|
485
507
|
claude) run_haiku "$model" ;;
|
|
486
|
-
codex) run_with_timeout 240 codex exec ${model:+-m "$model"} --sandbox read-only --skip-git-repo-check "$PROMPT" </dev/null ;;
|
|
487
|
-
cursor-agent) run_with_timeout 240 cursor-agent --trust ${model:+--model "$model"} -p "$PROMPT" ;;
|
|
508
|
+
codex) with_readings codex run_with_timeout 240 codex exec ${model:+-m "$model"} --sandbox read-only --skip-git-repo-check "$PROMPT" </dev/null ;;
|
|
509
|
+
cursor-agent) with_readings cursor-agent run_with_timeout 240 cursor-agent --trust ${model:+--model "$model"} -p "$PROMPT" ;;
|
|
488
510
|
esac
|
|
489
511
|
}
|
|
490
512
|
|
|
@@ -501,11 +523,13 @@ run_auto() {
|
|
|
501
523
|
if [ -n "$tried" ]; then
|
|
502
524
|
echo "$tried did not answer; writing the notes with $label instead." >&2
|
|
503
525
|
fi
|
|
526
|
+
rm -f "$WORK/yt_${ID}.kill"
|
|
504
527
|
if out=$(run_writer "$cmd" "$model" "$effort" 2>>"$WORK/yt_${ID}.engine.err") && printf '%s' "$out" | grep -q '[[:alnum:]]'; then
|
|
505
528
|
printf '%s\n' "$out"
|
|
506
529
|
echo "notes by $label" >&2
|
|
507
530
|
return 0
|
|
508
531
|
fi
|
|
532
|
+
[ -f "$WORK/yt_${ID}.kill" ] && cat "$WORK/yt_${ID}.kill" >&2
|
|
509
533
|
tried="$label"
|
|
510
534
|
done
|
|
511
535
|
if [ -n "$tried" ]; then
|