atris 3.16.0 → 3.17.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 +33 -7
- package/atris/skills/atris/SKILL.md +15 -2
- package/atris/skills/atris-feedback/SKILL.md +7 -0
- package/atris/skills/design/SKILL.md +29 -2
- package/atris/skills/engines/SKILL.md +44 -0
- package/atris/skills/flow/SKILL.md +1 -1
- package/atris/skills/wake/SKILL.md +37 -0
- package/atris/skills/youtube/SKILL.md +13 -39
- package/atris/team/validator/MEMBER.md +1 -0
- package/atris/wiki/concepts/agent-activation-contract.md +3 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
- package/atris/wiki/index.md +1 -0
- package/atris.md +43 -19
- package/bin/atris.js +446 -43
- package/commands/agent-spawn.js +480 -0
- package/commands/analytics.js +6 -3
- package/commands/apps.js +11 -0
- package/commands/autopilot.js +466 -20
- package/commands/brain.js +74 -7
- package/commands/brainstorm.js +9 -58
- package/commands/clean.js +1 -4
- package/commands/compile.js +574 -0
- package/commands/console.js +8 -3
- package/commands/deck.js +135 -0
- package/commands/init.js +22 -11
- package/commands/lesson.js +76 -0
- package/commands/member.js +252 -48
- package/commands/mission.js +405 -13
- package/commands/now.js +4 -2
- package/commands/probe.js +444 -0
- package/commands/pulse.js +504 -0
- package/commands/radar.js +1 -0
- package/commands/recap.js +233 -0
- package/commands/run.js +615 -22
- package/commands/skill.js +6 -2
- package/commands/slop.js +173 -0
- package/commands/spaceship.js +39 -0
- package/commands/sync.js +0 -2
- package/commands/task.js +458 -43
- package/commands/verify.js +7 -3
- package/lib/activity-stream.js +166 -0
- package/lib/auto-accept-certified.js +23 -1
- package/lib/context-gatherer.js +170 -0
- package/lib/escape-regexp.js +13 -0
- package/lib/file-ops.js +6 -3
- package/lib/journal.js +1 -1
- package/lib/lesson-contradiction.js +113 -0
- package/lib/policy-lessons.js +3 -2
- package/lib/pulse.js +401 -0
- package/lib/runner-command.js +156 -0
- package/lib/slides-deck.js +236 -0
- package/lib/state-detection.js +40 -3
- package/lib/task-db.js +101 -4
- package/lib/task-proof.js +1 -1
- package/lib/todo-fallback.js +2 -1
- package/lib/todo-sections.js +33 -0
- package/package.json +1 -2
- package/utils/api.js +14 -2
- package/atris/atrisDev.md +0 -717
package/commands/task.js
CHANGED
|
@@ -10,6 +10,7 @@ const os = require('os');
|
|
|
10
10
|
const { taskProofState } = require('../lib/task-proof');
|
|
11
11
|
const { evaluateAutoAccept, parseVerifyCommand } = require('../lib/auto-accept-certified');
|
|
12
12
|
const { extractReceiptEvidence } = require('../lib/receipt-evidence');
|
|
13
|
+
const escapeRegExp = require('../lib/escape-regexp');
|
|
13
14
|
|
|
14
15
|
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
15
16
|
|| process.env.USER
|
|
@@ -90,7 +91,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
90
91
|
|
|
91
92
|
atris task Show the task desk
|
|
92
93
|
atris task new "<title>" Create a task
|
|
93
|
-
atris task next
|
|
94
|
+
atris task next [--create-next] Claim/show next open task; optionally create the generated Endgame fallback
|
|
94
95
|
atris task continue-work <id> Create/reuse a certified Review follow-up task
|
|
95
96
|
atris task say <id> "<message>" Add context to a task
|
|
96
97
|
atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
|
|
@@ -143,6 +144,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
143
144
|
atris task serve [--port <n>] Open local task factory board
|
|
144
145
|
atris task sync --dry-run Plan cloud/Swarlo task sync writes
|
|
145
146
|
atris task import <file> One-shot import from TODO.md
|
|
147
|
+
atris task lineage <id> [--json] Show endgame -> tasks -> commits chain
|
|
146
148
|
atris task events [id] [--limit <n>] Print recent task events
|
|
147
149
|
atris task events --all Print the full append-only ledger
|
|
148
150
|
atris task export [--out <file>] Write web/desktop JSON projection
|
|
@@ -4122,7 +4124,14 @@ function cmdAcceptGroup(args) {
|
|
|
4122
4124
|
const isVerified = verifiedIds.has(task.id);
|
|
4123
4125
|
const proof = String(task.review?.proof || task.metadata?.latest_agent_proof || '').trim()
|
|
4124
4126
|
|| `Accepted via group spot-check (${groupLabel}); human ${actor} verified ${verifiedIds.size}/${group.length}.`;
|
|
4125
|
-
const done = taskDb.doneTask(db, {
|
|
4127
|
+
const done = taskDb.doneTask(db, {
|
|
4128
|
+
id: task.id,
|
|
4129
|
+
status: 'done',
|
|
4130
|
+
actor,
|
|
4131
|
+
allowReview: true,
|
|
4132
|
+
action: 'accepted',
|
|
4133
|
+
proof,
|
|
4134
|
+
});
|
|
4126
4135
|
if (!done.updated) { accepted.push({ id: task.id, ok: false, reason: 'not_review' }); continue; }
|
|
4127
4136
|
taskDb.reviewTask(db, {
|
|
4128
4137
|
id: task.id,
|
|
@@ -4425,15 +4434,26 @@ function cmdDelegate(args) {
|
|
|
4425
4434
|
if (handoff.swarlo) console.log(`swarlo: ${handoff.swarlo.channel}/${handoff.swarlo.action}`);
|
|
4426
4435
|
}
|
|
4427
4436
|
|
|
4428
|
-
|
|
4437
|
+
// Failed tasks older than this stop earning a daily owner-group row;
|
|
4438
|
+
// they collapse into one stale summary line instead (target state = clean day view).
|
|
4439
|
+
const DAY_STALE_FAILED_MS = 7 * 24 * 60 * 60 * 1000;
|
|
4440
|
+
|
|
4441
|
+
function taskDayGroups(tasks, { now = Date.now() } = {}) {
|
|
4429
4442
|
const active = tasks.filter(task => task.status !== 'done');
|
|
4430
|
-
const
|
|
4443
|
+
const staleFailed = [];
|
|
4444
|
+
const visible = [];
|
|
4431
4445
|
for (const task of active) {
|
|
4446
|
+
const isStaleFailed = task.status === 'failed' && (now - (task.updated_at || 0)) > DAY_STALE_FAILED_MS;
|
|
4447
|
+
if (isStaleFailed) staleFailed.push(task);
|
|
4448
|
+
else visible.push(task);
|
|
4449
|
+
}
|
|
4450
|
+
const groups = new Map();
|
|
4451
|
+
for (const task of visible) {
|
|
4432
4452
|
const owner = taskAssignee(task) || 'unassigned';
|
|
4433
4453
|
if (!groups.has(owner)) groups.set(owner, []);
|
|
4434
4454
|
groups.get(owner).push(task);
|
|
4435
4455
|
}
|
|
4436
|
-
|
|
4456
|
+
const grouped = Array.from(groups.entries())
|
|
4437
4457
|
.sort((a, b) => {
|
|
4438
4458
|
if (a[0] === 'unassigned') return 1;
|
|
4439
4459
|
if (b[0] === 'unassigned') return -1;
|
|
@@ -4446,6 +4466,7 @@ function taskDayGroups(tasks) {
|
|
|
4446
4466
|
return (statusOrder[a.status] - statusOrder[b.status]) || (b.updated_at - a.updated_at);
|
|
4447
4467
|
}),
|
|
4448
4468
|
}));
|
|
4469
|
+
return { groups: grouped, staleFailed };
|
|
4449
4470
|
}
|
|
4450
4471
|
|
|
4451
4472
|
function cmdDay(args) {
|
|
@@ -4453,13 +4474,15 @@ function cmdDay(args) {
|
|
|
4453
4474
|
const taskDb = getTaskDb();
|
|
4454
4475
|
const db = taskDb.open();
|
|
4455
4476
|
const { projection, outPath } = writeDefaultProjection(taskDb, db, { all });
|
|
4456
|
-
const groups = taskDayGroups(projection.tasks || []);
|
|
4477
|
+
const { groups, staleFailed } = taskDayGroups(projection.tasks || []);
|
|
4457
4478
|
const counts = {
|
|
4458
4479
|
active: groups.reduce((sum, group) => sum + group.tasks.length, 0),
|
|
4459
4480
|
owners: groups.length,
|
|
4460
4481
|
open: (projection.tasks || []).filter(task => task.status === 'open').length,
|
|
4461
4482
|
claimed: (projection.tasks || []).filter(task => task.status === 'claimed').length,
|
|
4462
|
-
review: (projection.tasks || []).filter(task => task.status === 'review'
|
|
4483
|
+
review: (projection.tasks || []).filter(task => task.status === 'review').length,
|
|
4484
|
+
failed: (projection.tasks || []).filter(task => task.status === 'failed').length,
|
|
4485
|
+
stale_failed: staleFailed.length,
|
|
4463
4486
|
};
|
|
4464
4487
|
const date = new Date().toISOString().slice(0, 10);
|
|
4465
4488
|
if (wantsJson(args)) {
|
|
@@ -4470,11 +4493,16 @@ function cmdDay(args) {
|
|
|
4470
4493
|
projection_path: outPath,
|
|
4471
4494
|
counts,
|
|
4472
4495
|
groups,
|
|
4496
|
+
stale_failed: {
|
|
4497
|
+
count: staleFailed.length,
|
|
4498
|
+
refs: staleFailed.map(task => taskRef(task)),
|
|
4499
|
+
},
|
|
4473
4500
|
});
|
|
4474
4501
|
return;
|
|
4475
4502
|
}
|
|
4476
4503
|
console.log('TASK DAY');
|
|
4477
|
-
|
|
4504
|
+
const failedText = counts.failed > 0 ? ` / failed ${counts.failed}` : '';
|
|
4505
|
+
console.log(`${date} active ${counts.active} / owners ${counts.owners} / review ${counts.review}${failedText}`);
|
|
4478
4506
|
console.log('');
|
|
4479
4507
|
if (!groups.length) {
|
|
4480
4508
|
console.log('clear no active tasks');
|
|
@@ -4487,6 +4515,10 @@ function cmdDay(args) {
|
|
|
4487
4515
|
console.log(` ${task.status.padEnd(7)} ${taskRef(task)}${claim}${tag} ${task.title}`);
|
|
4488
4516
|
}
|
|
4489
4517
|
}
|
|
4518
|
+
if (staleFailed.length) {
|
|
4519
|
+
console.log('');
|
|
4520
|
+
console.log(`stale ${staleFailed.length} failed >7d hidden — atris task list --status failed`);
|
|
4521
|
+
}
|
|
4490
4522
|
console.log('');
|
|
4491
4523
|
console.log('add: atris task delegate "..." --to codex --tag tasks');
|
|
4492
4524
|
}
|
|
@@ -4581,6 +4613,93 @@ function cmdClaim(args) {
|
|
|
4581
4613
|
}
|
|
4582
4614
|
}
|
|
4583
4615
|
|
|
4616
|
+
function readEndgameAgentAction(root, owner) {
|
|
4617
|
+
const todoPath = path.join(root || process.cwd(), 'atris', 'TODO.md');
|
|
4618
|
+
if (!fs.existsSync(todoPath)) return null;
|
|
4619
|
+
const content = fs.readFileSync(todoPath, 'utf8');
|
|
4620
|
+
const section = extractTodoSectionMarkdown(content, 'Endgame');
|
|
4621
|
+
if (!section) return null;
|
|
4622
|
+
const slug = (section.match(/\*\*Slug:\*\*\s*([^\n]+)/i)?.[1] || '').trim();
|
|
4623
|
+
const horizon = (section.match(/\*\*Horizon:\*\*\s*([^\n]+)/i)?.[1] || '').trim();
|
|
4624
|
+
if (!slug && !horizon) return null;
|
|
4625
|
+
const member = String(owner || DEFAULT_OWNER);
|
|
4626
|
+
const taskSeed = buildEndgameTaskSeed({ slug, horizon, owner: member });
|
|
4627
|
+
return {
|
|
4628
|
+
kind: 'create_bounded_endgame_task',
|
|
4629
|
+
endgame_slug: slug || null,
|
|
4630
|
+
horizon: horizon || null,
|
|
4631
|
+
task_seed: taskSeed,
|
|
4632
|
+
command: `atris brain activate --member ${member} --root . --verify`,
|
|
4633
|
+
message: `Create the next bounded task from Endgame${slug ? ` ${slug}` : ''}${horizon ? `: ${horizon}` : ''}. Do not accept XP.`,
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
function shellQuoteTaskArg(value) {
|
|
4638
|
+
const s = String(value || '');
|
|
4639
|
+
if (/^[A-Za-z0-9_./:-]+$/.test(s)) return s;
|
|
4640
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
function buildEndgameTaskSeed({ slug, horizon, owner }) {
|
|
4644
|
+
const combined = `${slug || ''} ${horizon || ''}`.toLowerCase();
|
|
4645
|
+
const runnerEndgame = /\b(runner|heartbeat|autopilot\/run|claude -p|model retirement|runner swap)\b/.test(combined);
|
|
4646
|
+
const title = runnerEndgame
|
|
4647
|
+
? 'Audit and close next runner-agnostic heartbeat gap'
|
|
4648
|
+
: `Advance Endgame ${slug || 'current horizon'}`;
|
|
4649
|
+
const tag = runnerEndgame ? 'runner' : 'endgame';
|
|
4650
|
+
const files = runnerEndgame
|
|
4651
|
+
? ['commands/autopilot.js', 'commands/run.js', 'lib/runner-command.js', 'test/autopilot-runner-model.test.js']
|
|
4652
|
+
: ['atris/TODO.md', 'atris/MAP.md'];
|
|
4653
|
+
const verifier = runnerEndgame
|
|
4654
|
+
? 'node --test test/autopilot-runner-model.test.js test/runner-command.test.js'
|
|
4655
|
+
: 'git diff --check';
|
|
4656
|
+
const stopRule = 'Move proof-ready work to Review; do not accept XP.';
|
|
4657
|
+
const goal = runnerEndgame
|
|
4658
|
+
? 'Find and close one remaining runner-agnostic heartbeat gap, or record proof that the next gap is documentation/state only.'
|
|
4659
|
+
: `Move the Endgame forward with one bounded, verifiable slice${horizon ? `: ${horizon}` : ''}.`;
|
|
4660
|
+
const note = `Goal: ${goal} Files: ${files.join(', ')}. Done: one scoped Endgame slice is implemented or the audited gap is closed with proof. Check: ${verifier}; git diff --check. Stop: ${stopRule}`;
|
|
4661
|
+
return {
|
|
4662
|
+
title,
|
|
4663
|
+
tag,
|
|
4664
|
+
files,
|
|
4665
|
+
verifier,
|
|
4666
|
+
stop_rule: stopRule,
|
|
4667
|
+
create_command: `atris task new ${shellQuoteTaskArg(title)} --tag ${shellQuoteTaskArg(tag)}`,
|
|
4668
|
+
claim_command: `atris task claim <id> --as ${shellQuoteTaskArg(owner || DEFAULT_OWNER)}`,
|
|
4669
|
+
note,
|
|
4670
|
+
note_command: `atris task note <id> ${shellQuoteTaskArg(note)}`,
|
|
4671
|
+
};
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
function createEndgameSeedTask(taskDb, db, seed, owner) {
|
|
4675
|
+
const taskOwner = String(owner || DEFAULT_OWNER);
|
|
4676
|
+
const result = taskDb.addTask(db, {
|
|
4677
|
+
title: seed.title,
|
|
4678
|
+
tag: seed.tag,
|
|
4679
|
+
workspaceRoot: taskDb.workspaceRoot(),
|
|
4680
|
+
metadata: {
|
|
4681
|
+
generated_from: 'task_next_endgame_seed',
|
|
4682
|
+
verifier: seed.verifier,
|
|
4683
|
+
files: seed.files,
|
|
4684
|
+
stop_rule: seed.stop_rule,
|
|
4685
|
+
},
|
|
4686
|
+
});
|
|
4687
|
+
const claim = taskDb.claimTask(db, { id: result.id, claimedBy: taskOwner });
|
|
4688
|
+
if (!claim.claimed) {
|
|
4689
|
+
return { ok: false, reason: claim.reason || 'claim_failed', task_id: result.id };
|
|
4690
|
+
}
|
|
4691
|
+
const note = taskDb.noteTask(db, { id: result.id, actor: taskOwner, content: seed.note });
|
|
4692
|
+
if (!note.noted) {
|
|
4693
|
+
return { ok: false, reason: note.reason || 'note_failed', task_id: result.id };
|
|
4694
|
+
}
|
|
4695
|
+
return {
|
|
4696
|
+
ok: true,
|
|
4697
|
+
task_id: result.id,
|
|
4698
|
+
inserted: result.inserted !== false,
|
|
4699
|
+
note_version: note.event.version,
|
|
4700
|
+
};
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4584
4703
|
function cmdNext(args) {
|
|
4585
4704
|
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
4586
4705
|
const taskDb = getTaskDb();
|
|
@@ -4627,6 +4746,40 @@ function cmdNext(args) {
|
|
|
4627
4746
|
const continueWorkCommand = handoff.next_action === 'continue_work'
|
|
4628
4747
|
? continueWorkCommandForTask(reviewTask, { owner })
|
|
4629
4748
|
: null;
|
|
4749
|
+
const nextAgentAction = handoff.next_action === 'human_accept_waiting'
|
|
4750
|
+
? readEndgameAgentAction(taskDb.workspaceRoot(), owner)
|
|
4751
|
+
: null;
|
|
4752
|
+
if (hasFlag(args, '--create-next')) {
|
|
4753
|
+
if (!nextAgentAction || !nextAgentAction.task_seed) {
|
|
4754
|
+
failTask('atris task next', 'no_create_next_seed', 'no concrete Endgame seed is available to create');
|
|
4755
|
+
}
|
|
4756
|
+
const created = createEndgameSeedTask(taskDb, db, nextAgentAction.task_seed, owner);
|
|
4757
|
+
if (!created.ok) {
|
|
4758
|
+
failTask('atris task next', created.reason || 'create_next_failed', `failed to create next task: ${created.reason || 'unknown'}`);
|
|
4759
|
+
}
|
|
4760
|
+
const { projection: createdProjection, outPath: createdOutPath } = writeDefaultProjection(taskDb, db);
|
|
4761
|
+
const createdTask = compactTaskFromProjection(createdProjection, created.task_id);
|
|
4762
|
+
if (wantsJson(args)) {
|
|
4763
|
+
printJson({
|
|
4764
|
+
ok: true,
|
|
4765
|
+
action: 'created_next',
|
|
4766
|
+
task_id: created.task_id,
|
|
4767
|
+
owner: String(owner),
|
|
4768
|
+
projection_path: createdOutPath,
|
|
4769
|
+
handoff,
|
|
4770
|
+
next_agent_action: nextAgentAction,
|
|
4771
|
+
note_version: created.note_version,
|
|
4772
|
+
task: createdTask,
|
|
4773
|
+
review_task: reviewTask,
|
|
4774
|
+
});
|
|
4775
|
+
return;
|
|
4776
|
+
}
|
|
4777
|
+
console.log(`created ${taskRef(createdTask)} @${owner}`);
|
|
4778
|
+
console.log(createdTask.title);
|
|
4779
|
+
console.log(`Noted v${created.note_version}. Human accept remains pending on ${taskRef(reviewTask)}.`);
|
|
4780
|
+
console.log(`Verify: ${nextAgentAction.task_seed.verifier}`);
|
|
4781
|
+
return;
|
|
4782
|
+
}
|
|
4630
4783
|
if (wantsJson(args)) {
|
|
4631
4784
|
printJson({
|
|
4632
4785
|
ok: true,
|
|
@@ -4635,6 +4788,7 @@ function cmdNext(args) {
|
|
|
4635
4788
|
owner: String(owner),
|
|
4636
4789
|
projection_path: outPath,
|
|
4637
4790
|
handoff,
|
|
4791
|
+
next_agent_action: nextAgentAction,
|
|
4638
4792
|
continue_work_command: continueWorkCommand,
|
|
4639
4793
|
continue_work_api: continueWorkCommand ? { method: 'POST', path: `/api/tasks/${encodeURIComponent(reviewTask.id)}/continue-work` } : null,
|
|
4640
4794
|
review_task: reviewTask,
|
|
@@ -4648,8 +4802,15 @@ function cmdNext(args) {
|
|
|
4648
4802
|
console.log(handoff.next_action === 'continue_work'
|
|
4649
4803
|
? 'Continue work elsewhere; AgentXP waits for human accept.'
|
|
4650
4804
|
: handoff.next_action === 'human_accept_waiting'
|
|
4651
|
-
? 'No concrete next agent task is attached; AgentXP waits for human accept.'
|
|
4805
|
+
? (nextAgentAction ? nextAgentAction.message : 'No concrete next agent task is attached; AgentXP waits for human accept.')
|
|
4652
4806
|
: 'Review this task again before continuing.');
|
|
4807
|
+
if (nextAgentAction) console.log(`Command: ${nextAgentAction.command}`);
|
|
4808
|
+
if (nextAgentAction && nextAgentAction.task_seed) {
|
|
4809
|
+
console.log(`Create: ${nextAgentAction.task_seed.create_command}`);
|
|
4810
|
+
console.log(`Claim: ${nextAgentAction.task_seed.claim_command}`);
|
|
4811
|
+
console.log(`Note: ${nextAgentAction.task_seed.note_command}`);
|
|
4812
|
+
console.log(`Verify: ${nextAgentAction.task_seed.verifier}`);
|
|
4813
|
+
}
|
|
4653
4814
|
if (continueWorkCommand) console.log(`Command: ${continueWorkCommand}`);
|
|
4654
4815
|
return;
|
|
4655
4816
|
}
|
|
@@ -5997,7 +6158,13 @@ function cmdDone(args) {
|
|
|
5997
6158
|
if (!failed || hasReview) requireMeaningfulTaskProof('atris task done', proof);
|
|
5998
6159
|
else if (proof) requireMeaningfulTaskProof('atris task done', proof);
|
|
5999
6160
|
}
|
|
6000
|
-
const result = taskDb.doneTask(db, {
|
|
6161
|
+
const result = taskDb.doneTask(db, {
|
|
6162
|
+
id: taskId,
|
|
6163
|
+
status: failed ? 'failed' : 'done',
|
|
6164
|
+
actor,
|
|
6165
|
+
action: failed ? 'failed' : 'done',
|
|
6166
|
+
proof,
|
|
6167
|
+
});
|
|
6001
6168
|
if (result.updated) {
|
|
6002
6169
|
const review = hasReview ? taskDb.reviewTask(db, {
|
|
6003
6170
|
id: taskId,
|
|
@@ -6072,7 +6239,13 @@ function cmdFinish(args) {
|
|
|
6072
6239
|
if (!failed || hasReview) requireMeaningfulTaskProof('atris task finish', proof);
|
|
6073
6240
|
else if (proof) requireMeaningfulTaskProof('atris task finish', proof);
|
|
6074
6241
|
}
|
|
6075
|
-
const done = taskDb.doneTask(db, {
|
|
6242
|
+
const done = taskDb.doneTask(db, {
|
|
6243
|
+
id: taskId,
|
|
6244
|
+
status: failed ? 'failed' : 'done',
|
|
6245
|
+
actor,
|
|
6246
|
+
action: failed ? 'failed' : 'finished',
|
|
6247
|
+
proof,
|
|
6248
|
+
});
|
|
6076
6249
|
if (!done.updated) {
|
|
6077
6250
|
const detail = `finish failed: ${taskId} not in open|claimed`;
|
|
6078
6251
|
if (wantsJson(args)) {
|
|
@@ -6273,7 +6446,14 @@ function cmdAccept(args) {
|
|
|
6273
6446
|
console.error('atris task accept: reward must be a positive number');
|
|
6274
6447
|
process.exit(2);
|
|
6275
6448
|
}
|
|
6276
|
-
const done = taskDb.doneTask(db, {
|
|
6449
|
+
const done = taskDb.doneTask(db, {
|
|
6450
|
+
id: taskId,
|
|
6451
|
+
status: 'done',
|
|
6452
|
+
actor,
|
|
6453
|
+
allowReview: true,
|
|
6454
|
+
action: 'accepted',
|
|
6455
|
+
proof,
|
|
6456
|
+
});
|
|
6277
6457
|
if (!done.updated) {
|
|
6278
6458
|
console.error(`accept failed: ${taskId} not open|claimed|review`);
|
|
6279
6459
|
process.exit(1);
|
|
@@ -6335,7 +6515,14 @@ function stampAutoAcceptMetadata(taskDb, db, taskId, actor, policy) {
|
|
|
6335
6515
|
}
|
|
6336
6516
|
|
|
6337
6517
|
function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '' }) {
|
|
6338
|
-
const done = taskDb.doneTask(db, {
|
|
6518
|
+
const done = taskDb.doneTask(db, {
|
|
6519
|
+
id: taskId,
|
|
6520
|
+
status: 'done',
|
|
6521
|
+
actor,
|
|
6522
|
+
allowReview: true,
|
|
6523
|
+
action: 'accepted',
|
|
6524
|
+
proof,
|
|
6525
|
+
});
|
|
6339
6526
|
if (!done.updated) {
|
|
6340
6527
|
return { ok: false, reason: 'not_open_claimed_or_review' };
|
|
6341
6528
|
}
|
|
@@ -6713,6 +6900,91 @@ function cmdEvents(args) {
|
|
|
6713
6900
|
}
|
|
6714
6901
|
}
|
|
6715
6902
|
|
|
6903
|
+
function cmdLineage(args) {
|
|
6904
|
+
const pos = positional(args);
|
|
6905
|
+
const id = pos[0];
|
|
6906
|
+
if (!id) {
|
|
6907
|
+
failTask('atris task lineage', 'missing_id', 'id required');
|
|
6908
|
+
}
|
|
6909
|
+
const taskDb = getTaskDb();
|
|
6910
|
+
const db = taskDb.open();
|
|
6911
|
+
const taskId = requireTaskId(taskDb, db, id, 'atris task lineage');
|
|
6912
|
+
const enriched = enrichTaskProjection(taskDb.taskProjection(db, { workspaceRoot: taskDb.workspaceRoot(), limit: 1000 }));
|
|
6913
|
+
const byId = new Map();
|
|
6914
|
+
for (const t of enriched.tasks) byId.set(t.id, t);
|
|
6915
|
+
const target = byId.get(taskId);
|
|
6916
|
+
if (!target) {
|
|
6917
|
+
console.error(`task not found: ${id}`);
|
|
6918
|
+
process.exit(1);
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6921
|
+
const parents = [];
|
|
6922
|
+
let cursor = target;
|
|
6923
|
+
const seen = new Set();
|
|
6924
|
+
while (cursor) {
|
|
6925
|
+
const parentId = cursor.lineage && cursor.lineage.parent_task_id;
|
|
6926
|
+
if (!parentId || seen.has(parentId)) break;
|
|
6927
|
+
seen.add(parentId);
|
|
6928
|
+
const parent = byId.get(parentId);
|
|
6929
|
+
if (!parent) break;
|
|
6930
|
+
parents.unshift(parent);
|
|
6931
|
+
cursor = parent;
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6934
|
+
const childIds = target.lineage && target.lineage.child_task_ids || [];
|
|
6935
|
+
const children = childIds.map(cid => byId.get(cid)).filter(Boolean);
|
|
6936
|
+
|
|
6937
|
+
const chain = [...parents, target, ...children];
|
|
6938
|
+
|
|
6939
|
+
let commits = [];
|
|
6940
|
+
try {
|
|
6941
|
+
const { spawnSync: sp } = require('child_process');
|
|
6942
|
+
const displayRefs = chain.map(t => taskRef(t)).filter(Boolean);
|
|
6943
|
+
const pattern = displayRefs.join('\\|');
|
|
6944
|
+
const result = sp('git', ['log', '--oneline', '--all', `--grep=${pattern}`], {
|
|
6945
|
+
encoding: 'utf8',
|
|
6946
|
+
timeout: 5000,
|
|
6947
|
+
});
|
|
6948
|
+
if (result.status === 0 && result.stdout) {
|
|
6949
|
+
commits = result.stdout.trim().split('\n').filter(Boolean);
|
|
6950
|
+
}
|
|
6951
|
+
} catch (_) {
|
|
6952
|
+
commits = [];
|
|
6953
|
+
}
|
|
6954
|
+
|
|
6955
|
+
if (wantsJson(args)) {
|
|
6956
|
+
printJson({
|
|
6957
|
+
ok: true,
|
|
6958
|
+
action: 'lineage',
|
|
6959
|
+
chain: {
|
|
6960
|
+
endgame: parents.length ? parents[0] : null,
|
|
6961
|
+
parents: parents.slice(1),
|
|
6962
|
+
target,
|
|
6963
|
+
children,
|
|
6964
|
+
commits,
|
|
6965
|
+
},
|
|
6966
|
+
});
|
|
6967
|
+
return;
|
|
6968
|
+
}
|
|
6969
|
+
|
|
6970
|
+
if (parents.length) {
|
|
6971
|
+
for (let i = 0; i < parents.length; i += 1) {
|
|
6972
|
+
const p = parents[i];
|
|
6973
|
+
console.log(`${' '.repeat(i)}${taskRef(p)} ${p.title} [${p.status}]`);
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
const indent = ' '.repeat(parents.length);
|
|
6977
|
+
console.log(`${indent}${taskRef(target)} ${target.title} [${target.status}]`);
|
|
6978
|
+
for (const child of children) {
|
|
6979
|
+
console.log(`${' '.repeat(parents.length + 1)}${taskRef(child)} ${child.title} [${child.status}]`);
|
|
6980
|
+
}
|
|
6981
|
+
if (commits.length) {
|
|
6982
|
+
console.log('');
|
|
6983
|
+
console.log('commits:');
|
|
6984
|
+
for (const c of commits) console.log(` ${c}`);
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
|
|
6716
6988
|
function cmdExport(args) {
|
|
6717
6989
|
const out = flag(args, '--out') || path.join('.atris', 'state', 'tasks.projection.json');
|
|
6718
6990
|
const all = hasFlag(args, '--all');
|
|
@@ -6774,7 +7046,7 @@ function cmdSetup(args) {
|
|
|
6774
7046
|
}
|
|
6775
7047
|
|
|
6776
7048
|
function extractTodoSectionMarkdown(content, sectionName) {
|
|
6777
|
-
const escaped =
|
|
7049
|
+
const escaped = escapeRegExp(sectionName || '');
|
|
6778
7050
|
const match = String(content || '').match(new RegExp(`(?:^|\\n)(##\\s+${escaped}[^\\n]*\\n[\\s\\S]*?)(?=\\n##(?!#)\\s+|$)`, 'i'));
|
|
6779
7051
|
return match ? match[1].trimEnd() : null;
|
|
6780
7052
|
}
|
|
@@ -6978,22 +7250,54 @@ function taskBoardHtml() {
|
|
|
6978
7250
|
<meta charset="utf-8">
|
|
6979
7251
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6980
7252
|
<title>Atris Task Factory</title>
|
|
7253
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
7254
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
7255
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
6981
7256
|
<style>
|
|
6982
|
-
:
|
|
7257
|
+
/* aesthetic: machine-room telemetry — warm-tinted dark, mono data, calm signals (no neon) */
|
|
7258
|
+
:root {
|
|
7259
|
+
color-scheme: dark;
|
|
7260
|
+
--bg: oklch(18% 0.012 160);
|
|
7261
|
+
--panel: oklch(22% 0.014 160);
|
|
7262
|
+
--panel-2: oklch(25% 0.016 160);
|
|
7263
|
+
--line: oklch(32% 0.015 160);
|
|
7264
|
+
--text: oklch(93% 0.012 150);
|
|
7265
|
+
--muted: oklch(70% 0.018 160);
|
|
7266
|
+
--accent: oklch(74% 0.115 158);
|
|
7267
|
+
--warn: oklch(81% 0.11 80);
|
|
7268
|
+
--bad: oklch(69% 0.14 25);
|
|
7269
|
+
--info: oklch(75% 0.10 240);
|
|
7270
|
+
--violet: oklch(73% 0.10 300);
|
|
7271
|
+
--sans: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
7272
|
+
--mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
7273
|
+
}
|
|
6983
7274
|
* { box-sizing: border-box; }
|
|
6984
|
-
body {
|
|
6985
|
-
|
|
6986
|
-
|
|
7275
|
+
body {
|
|
7276
|
+
margin:0; color:var(--text);
|
|
7277
|
+
font-family: var(--sans);
|
|
7278
|
+
-webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
|
|
7279
|
+
background:
|
|
7280
|
+
radial-gradient(1100px 520px at 82% -12%, oklch(30% 0.04 160 / 0.55), transparent 62%),
|
|
7281
|
+
repeating-linear-gradient(0deg, oklch(32% 0.015 160 / 0.16) 0 1px, transparent 1px 34px),
|
|
7282
|
+
var(--bg);
|
|
7283
|
+
background-attachment: fixed;
|
|
7284
|
+
}
|
|
7285
|
+
header { height:60px; display:flex; align-items:center; justify-content:space-between; padding:0 22px; border-bottom:1px solid var(--line); background:linear-gradient(180deg, var(--panel-2), var(--panel)); }
|
|
7286
|
+
h1 { font-size:17px; margin:0; font-weight:700; letter-spacing:-0.01em; }
|
|
6987
7287
|
.sub { color:var(--muted); font-size:12px; }
|
|
6988
|
-
main { display:grid; grid-template-columns: 320px 1fr; height:calc(100vh -
|
|
6989
|
-
aside { border-right:1px solid var(--line); padding:
|
|
6990
|
-
section { min-width:0; overflow:auto; padding:
|
|
6991
|
-
label { display:block; color:var(--muted); font-size:
|
|
6992
|
-
input, textarea, select { width:100%; border:1px solid var(--line); background
|
|
6993
|
-
textarea {
|
|
6994
|
-
|
|
6995
|
-
button
|
|
6996
|
-
|
|
7288
|
+
main { display:grid; grid-template-columns: 320px 1fr; height:calc(100vh - 60px); }
|
|
7289
|
+
aside { border-right:1px solid var(--line); padding:16px; overflow:auto; background:var(--panel); }
|
|
7290
|
+
section { min-width:0; overflow:auto; padding:16px; }
|
|
7291
|
+
label { display:block; color:var(--muted); font-size:12px; margin:10px 0 5px; }
|
|
7292
|
+
input, textarea, select { width:100%; border:1px solid var(--line); background:oklch(15% 0.012 160); color:var(--text); border-radius:8px; padding:9px 11px; font:inherit; font-size:13px; transition:border-color .18s cubic-bezier(0.25,1,0.5,1); }
|
|
7293
|
+
input:focus, textarea:focus, select:focus { outline:2px solid var(--accent); outline-offset:1px; border-color:transparent; }
|
|
7294
|
+
textarea { min-height:82px; resize:vertical; font-family:var(--mono); font-size:12px; }
|
|
7295
|
+
button { border:1px solid var(--line); background:var(--panel-2); color:var(--text); border-radius:8px; padding:8px 12px; font:inherit; font-size:12px; cursor:pointer; transition:background .18s cubic-bezier(0.25,1,0.5,1), border-color .18s; }
|
|
7296
|
+
button:hover { border-color:var(--muted); background:oklch(29% 0.018 160); }
|
|
7297
|
+
button:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
|
|
7298
|
+
button:active { transform:translateY(1px); }
|
|
7299
|
+
.primary { background:oklch(38% 0.07 158); border-color:oklch(48% 0.09 158); color:oklch(96% 0.02 158); }
|
|
7300
|
+
.primary:hover { background:oklch(43% 0.085 158); border-color:var(--accent); }
|
|
6997
7301
|
.grid { display:grid; grid-template-columns: repeat(var(--board-columns, 6), minmax(160px, 1fr)); gap:12px; align-items:start; }
|
|
6998
7302
|
.overview { display:grid; grid-template-columns: minmax(260px, 1.4fr) minmax(260px, 1fr); gap:12px; margin-bottom:12px; }
|
|
6999
7303
|
.goalbox, .chainbox { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:11px; min-height:88px; }
|
|
@@ -7002,35 +7306,62 @@ function taskBoardHtml() {
|
|
|
7002
7306
|
.chainitem { display:grid; grid-template-columns:72px 1fr; gap:8px; font-size:12px; line-height:1.3; margin:5px 0; color:var(--muted); }
|
|
7003
7307
|
.chainitem strong { color:var(--text); font-weight:600; }
|
|
7004
7308
|
.streams { display:grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap:12px; margin-bottom:12px; }
|
|
7005
|
-
.stream { background
|
|
7006
|
-
.stream h2 { margin:0 0 8px; font-size:12px; color:var(--text); line-height:1.25; }
|
|
7007
|
-
.streambar { display:flex; height:7px; overflow:hidden; border-radius:999px; background
|
|
7309
|
+
.stream { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:11px; min-height:126px; }
|
|
7310
|
+
.stream h2 { margin:0 0 8px; font-size:12px; color:var(--text); line-height:1.25; font-weight:500; }
|
|
7311
|
+
.streambar { display:flex; height:7px; overflow:hidden; border-radius:999px; background:oklch(15% 0.012 160); border:1px solid var(--line); margin:8px 0; }
|
|
7008
7312
|
.streambar span { display:block; min-width:2px; }
|
|
7009
|
-
.seg-open { background
|
|
7010
|
-
.seg-doing { background
|
|
7011
|
-
.seg-review { background
|
|
7012
|
-
.seg-blocked { background
|
|
7313
|
+
.seg-open { background:var(--muted); }
|
|
7314
|
+
.seg-doing { background:var(--accent); }
|
|
7315
|
+
.seg-review { background:var(--warn); }
|
|
7316
|
+
.seg-blocked { background:var(--bad); }
|
|
7013
7317
|
.streamtask { display:grid; grid-template-columns:64px 1fr; gap:8px; color:var(--muted); font-size:11px; line-height:1.25; margin-top:6px; }
|
|
7014
7318
|
.streamtask strong { color:var(--text); font-weight:550; }
|
|
7015
7319
|
.col { background:var(--panel); border:1px solid var(--line); border-radius:8px; min-height:160px; overflow:hidden; }
|
|
7016
7320
|
.col h2 { margin:0; padding:10px 11px; font-size:12px; color:var(--muted); border-bottom:1px solid var(--line); display:flex; justify-content:space-between; }
|
|
7017
7321
|
.cards { padding:8px; display:flex; flex-direction:column; gap:8px; }
|
|
7018
|
-
.card { text-align:left; width:100%; background
|
|
7019
|
-
.card
|
|
7322
|
+
.card { text-align:left; width:100%; background:var(--panel-2); border:1px solid var(--line); border-radius:8px; padding:9px; transition:transform .18s cubic-bezier(0.25,1,0.5,1), border-color .18s, background .18s; }
|
|
7323
|
+
.card:hover { transform:scale(1.02); border-color:var(--muted); background:oklch(28% 0.018 160); }
|
|
7324
|
+
.card.active { border-color:var(--accent); box-shadow:0 0 0 1px oklch(74% 0.115 158 / 0.3); }
|
|
7020
7325
|
.title { font-size:13px; line-height:1.25; }
|
|
7021
|
-
.meta { margin-top:6px; color:var(--muted); font-size:11px; display:flex; gap:6px; flex-wrap:wrap; }
|
|
7326
|
+
.meta { margin-top:6px; color:var(--muted); font-size:11px; display:flex; gap:6px; flex-wrap:wrap; font-family:var(--mono); }
|
|
7022
7327
|
.pill { border:1px solid var(--line); border-radius:999px; padding:1px 6px; }
|
|
7023
7328
|
.why { margin-top:7px; color:var(--muted); font-size:11px; line-height:1.25; }
|
|
7024
|
-
.fact { margin:10px 0; background
|
|
7329
|
+
.fact { margin:10px 0; background:oklch(15% 0.012 160); border:1px solid var(--line); border-radius:7px; padding:8px; font-size:12px; line-height:1.35; }
|
|
7025
7330
|
.fact b { color:var(--muted); font-size:11px; display:block; margin-bottom:3px; }
|
|
7026
7331
|
.room { margin-top:14px; border-top:1px solid var(--line); padding-top:12px; }
|
|
7027
7332
|
.room h3 { margin:0 0 4px; font-size:14px; }
|
|
7028
7333
|
.thread { margin:10px 0; display:flex; flex-direction:column; gap:7px; }
|
|
7029
|
-
.msg { background
|
|
7334
|
+
.msg { background:oklch(15% 0.012 160); border:1px solid var(--line); border-radius:7px; padding:8px; font-size:12px; }
|
|
7030
7335
|
.msg .who { color:var(--muted); font-size:11px; margin-bottom:3px; }
|
|
7031
7336
|
.actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:10px; }
|
|
7032
7337
|
.full { grid-column:1 / -1; }
|
|
7033
7338
|
.empty { color:var(--muted); font-size:12px; padding:10px; }
|
|
7339
|
+
/* heartbeat strip */
|
|
7340
|
+
.beat { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted); font-family:var(--mono); }
|
|
7341
|
+
.beat .dot { width:9px; height:9px; border-radius:50%; background:var(--muted); flex:none; }
|
|
7342
|
+
.beat.alive .dot { background:var(--accent); box-shadow:0 0 0 0 oklch(74% 0.115 158 / 0.6); animation:beat 2s cubic-bezier(0.25,1,0.5,1) infinite; }
|
|
7343
|
+
.beat.stale .dot { background:var(--bad); }
|
|
7344
|
+
.beat b { color:var(--accent); font-weight:600; }
|
|
7345
|
+
.beat .warn { color:var(--warn); }
|
|
7346
|
+
@keyframes beat { 0%{box-shadow:0 0 0 0 oklch(74% 0.115 158 / 0.5)} 70%{box-shadow:0 0 0 8px oklch(74% 0.115 158 / 0)} 100%{box-shadow:0 0 0 0 oklch(74% 0.115 158 / 0)} }
|
|
7347
|
+
@media (prefers-reduced-motion: reduce) { .beat.alive .dot { animation:none; } *, *::before, *::after { transition:none !important; } }
|
|
7348
|
+
/* activity feed */
|
|
7349
|
+
.activity { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:0; margin-bottom:12px; max-height:46vh; overflow:auto; }
|
|
7350
|
+
.activity h2 { margin:0; position:sticky; top:0; background:var(--panel); padding:11px; font-size:12px; color:var(--muted); font-weight:650; border-bottom:1px solid var(--line); z-index:1; }
|
|
7351
|
+
.ev { display:grid; grid-template-columns:52px 64px 1fr auto; gap:10px; align-items:baseline; padding:7px 11px; border-bottom:1px solid oklch(28% 0.013 160); font-size:12px; line-height:1.3; font-family:var(--mono); transition:background .15s ease; }
|
|
7352
|
+
.ev:last-child { border-bottom:0; }
|
|
7353
|
+
.ev:hover { background:oklch(25% 0.016 160); }
|
|
7354
|
+
.ev .t { color:var(--muted); font-variant-numeric:tabular-nums; font-size:11px; }
|
|
7355
|
+
.ev .src { font-size:11px; border:1px solid var(--line); border-radius:999px; padding:1px 7px; color:var(--muted); text-align:center; }
|
|
7356
|
+
.ev .src.pulse { color:var(--accent); border-color:oklch(74% 0.115 158 / 0.45); }
|
|
7357
|
+
.ev .src.reward { color:var(--warn); border-color:oklch(81% 0.11 80 / 0.45); }
|
|
7358
|
+
.ev .src.xp { color:var(--info); border-color:oklch(75% 0.10 240 / 0.45); }
|
|
7359
|
+
.ev .src.mission { color:var(--violet); border-color:oklch(73% 0.10 300 / 0.45); }
|
|
7360
|
+
.ev .msg { color:var(--text); min-width:0; }
|
|
7361
|
+
.ev .msg .d { color:var(--muted); font-size:11px; }
|
|
7362
|
+
.ev.bad .msg { color:var(--bad); }
|
|
7363
|
+
.ev .rw { font-variant-numeric:tabular-nums; font-size:11px; color:var(--muted); }
|
|
7364
|
+
.ev .rw.pos { color:var(--accent); } .ev .rw.neg { color:var(--bad); }
|
|
7034
7365
|
@media (max-width: 980px) { main { grid-template-columns:1fr; height:auto; } aside { border-right:0; border-bottom:1px solid var(--line); } .grid, .overview { grid-template-columns:1fr; } }
|
|
7035
7366
|
</style>
|
|
7036
7367
|
</head>
|
|
@@ -7038,7 +7369,7 @@ function taskBoardHtml() {
|
|
|
7038
7369
|
<header>
|
|
7039
7370
|
<div>
|
|
7040
7371
|
<h1>Atris Task Factory</h1>
|
|
7041
|
-
<div class="
|
|
7372
|
+
<div class="beat" id="heartbeat"><span class="dot"></span><span>heartbeat: loading…</span></div>
|
|
7042
7373
|
</div>
|
|
7043
7374
|
<button id="refresh">Refresh</button>
|
|
7044
7375
|
</header>
|
|
@@ -7057,6 +7388,7 @@ function taskBoardHtml() {
|
|
|
7057
7388
|
</aside>
|
|
7058
7389
|
<section>
|
|
7059
7390
|
<div class="overview" id="overview"></div>
|
|
7391
|
+
<div class="activity" id="activity"><h2>Live Stream</h2><div class="empty">waiting for the agent…</div></div>
|
|
7060
7392
|
<div class="streams" id="streams"></div>
|
|
7061
7393
|
<div class="grid" id="board"></div>
|
|
7062
7394
|
</section>
|
|
@@ -7102,10 +7434,61 @@ function taskBoardHtml() {
|
|
|
7102
7434
|
return 'done';
|
|
7103
7435
|
}
|
|
7104
7436
|
|
|
7437
|
+
function esc(s) {
|
|
7438
|
+
return String(s == null ? '' : s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
|
7439
|
+
}
|
|
7440
|
+
|
|
7441
|
+
function fmtTime(ms) {
|
|
7442
|
+
try { return new Date(ms).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); }
|
|
7443
|
+
catch (e) { return ''; }
|
|
7444
|
+
}
|
|
7445
|
+
|
|
7446
|
+
function renderHeartbeat(hb) {
|
|
7447
|
+
const el = $('heartbeat');
|
|
7448
|
+
el.className = 'beat ' + (hb.state || 'idle');
|
|
7449
|
+
if (hb.state === 'stale') {
|
|
7450
|
+
el.innerHTML = '<span class="dot"></span><span class="warn">stale</span><span>· ' + esc(hb.stale_reason || '') + ' · ' + (hb.total_ticks || 0) + ' ticks</span>';
|
|
7451
|
+
return;
|
|
7452
|
+
}
|
|
7453
|
+
if (!hb.total_ticks) {
|
|
7454
|
+
el.innerHTML = '<span class="dot"></span><span>idle · no ticks yet · run: atris pulse tick</span>';
|
|
7455
|
+
return;
|
|
7456
|
+
}
|
|
7457
|
+
const age = hb.last_tick_age_min == null ? '' : (hb.last_tick_age_min === 0 ? 'just now' : hb.last_tick_age_min + 'm ago');
|
|
7458
|
+
el.innerHTML = '<span class="dot"></span><b>alive</b>'
|
|
7459
|
+
+ '<span>· last tick ' + age + '</span>'
|
|
7460
|
+
+ (hb.last_what ? '<span>· ' + esc(hb.last_what) + '</span>' : '')
|
|
7461
|
+
+ '<span>· ' + hb.total_ticks + ' ticks, reward ' + (hb.reward_sum || 0) + '</span>';
|
|
7462
|
+
}
|
|
7463
|
+
|
|
7464
|
+
function renderActivity(events) {
|
|
7465
|
+
const el = $('activity');
|
|
7466
|
+
if (!events.length) { el.innerHTML = '<h2>Live Stream</h2><div class="empty">no activity yet — fire a tick: atris pulse tick</div>'; return; }
|
|
7467
|
+
let html = '<h2>Live Stream · ' + events.length + ' events</h2>';
|
|
7468
|
+
for (const e of events) {
|
|
7469
|
+
const rwCls = e.reward == null ? '' : (e.reward > 0 ? 'pos' : (e.reward < 0 ? 'neg' : ''));
|
|
7470
|
+
const rw = e.reward == null ? '' : (e.reward > 0 ? '+' + e.reward : '' + e.reward);
|
|
7471
|
+
html += '<div class="ev ' + (e.status === 'bad' ? 'bad' : '') + '">'
|
|
7472
|
+
+ '<span class="t">' + (e.ms ? fmtTime(e.ms) : '') + '</span>'
|
|
7473
|
+
+ '<span class="src ' + esc(e.source) + '">' + esc(e.source) + '</span>'
|
|
7474
|
+
+ '<span class="msg">' + esc(e.title) + (e.detail ? ' <span class="d">' + esc(e.detail) + '</span>' : '') + '</span>'
|
|
7475
|
+
+ '<span class="rw ' + rwCls + '">' + rw + '</span>'
|
|
7476
|
+
+ '</div>';
|
|
7477
|
+
}
|
|
7478
|
+
el.innerHTML = html;
|
|
7479
|
+
}
|
|
7480
|
+
|
|
7481
|
+
async function loadStream() {
|
|
7482
|
+
const data = await api('/api/stream');
|
|
7483
|
+
renderHeartbeat(data.heartbeat || {});
|
|
7484
|
+
renderActivity(data.events || []);
|
|
7485
|
+
}
|
|
7486
|
+
|
|
7105
7487
|
async function load() {
|
|
7106
7488
|
const data = await api('/api/tasks');
|
|
7107
7489
|
state = data.projection;
|
|
7108
7490
|
render();
|
|
7491
|
+
loadStream().catch(() => {});
|
|
7109
7492
|
}
|
|
7110
7493
|
|
|
7111
7494
|
function render() {
|
|
@@ -7320,6 +7703,24 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7320
7703
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
7321
7704
|
return sendJson(res, 200, { ok: true, projection_path: outPath, projection });
|
|
7322
7705
|
}
|
|
7706
|
+
if (req.method === 'GET' && url.pathname === '/api/stream') {
|
|
7707
|
+
// Time-ordered feed of what the agent actually did + heartbeat liveness.
|
|
7708
|
+
const { projection } = writeDefaultProjection(taskDb, db);
|
|
7709
|
+
const root = projection.workspace_root || process.cwd();
|
|
7710
|
+
const stateDir = path.join(root, '.atris', 'state');
|
|
7711
|
+
const activity = require('../lib/activity-stream');
|
|
7712
|
+
const { readJsonl } = require('../lib/pulse');
|
|
7713
|
+
const rd = (f) => readJsonl(path.join(stateDir, f));
|
|
7714
|
+
const pulseReceipts = rd('pulse_agi_loop_receipts.jsonl');
|
|
7715
|
+
const events = activity.buildActivityStream({
|
|
7716
|
+
pulseReceipts,
|
|
7717
|
+
scorecards: rd('scorecards.jsonl'),
|
|
7718
|
+
taskEpisodes: rd('task_episodes.jsonl').slice(-200),
|
|
7719
|
+
xpReceipts: rd('career_xp_receipts.jsonl').slice(-200),
|
|
7720
|
+
missionEvents: rd('mission_events.jsonl').slice(-200),
|
|
7721
|
+
}, { limit: 60 });
|
|
7722
|
+
return sendJson(res, 200, { ok: true, heartbeat: activity.buildHeartbeat(pulseReceipts), events });
|
|
7723
|
+
}
|
|
7323
7724
|
if (req.method === 'GET' && url.pathname === '/api/tasks/capabilities') {
|
|
7324
7725
|
return sendJson(res, 200, {
|
|
7325
7726
|
ok: true,
|
|
@@ -7636,7 +8037,13 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7636
8037
|
const shouldReview = Boolean(body.proof || body.lesson || body.next || body.reward !== undefined);
|
|
7637
8038
|
const proofIssue = meaningfulTaskProofIssue(proof, { required: !failed || shouldReview });
|
|
7638
8039
|
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
7639
|
-
const done = taskDb.doneTask(db, {
|
|
8040
|
+
const done = taskDb.doneTask(db, {
|
|
8041
|
+
id: taskId,
|
|
8042
|
+
status: failed ? 'failed' : 'done',
|
|
8043
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
8044
|
+
action: failed ? 'failed' : 'finished',
|
|
8045
|
+
proof,
|
|
8046
|
+
});
|
|
7640
8047
|
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_or_claimed' });
|
|
7641
8048
|
let episode = null;
|
|
7642
8049
|
let nextCreated = null;
|
|
@@ -7706,7 +8113,14 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7706
8113
|
if (hasExplicitNext && !nextTask.trim()) clearedFields.push('next_task');
|
|
7707
8114
|
const parsedReward = parseAcceptReward(body.reward);
|
|
7708
8115
|
if (!parsedReward.ok) return sendJson(res, 400, { ok: false, reason: 'invalid_reward', detail: 'reward must be a positive number' });
|
|
7709
|
-
const done = taskDb.doneTask(db, {
|
|
8116
|
+
const done = taskDb.doneTask(db, {
|
|
8117
|
+
id: taskId,
|
|
8118
|
+
status: 'done',
|
|
8119
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
8120
|
+
allowReview: true,
|
|
8121
|
+
action: 'accepted',
|
|
8122
|
+
proof,
|
|
8123
|
+
});
|
|
7710
8124
|
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_claimed_or_review' });
|
|
7711
8125
|
const reviewed = taskDb.reviewTask(db, {
|
|
7712
8126
|
id: taskId,
|
|
@@ -7896,6 +8310,7 @@ async function run(args) {
|
|
|
7896
8310
|
case 'setup': return cmdSetup(rest);
|
|
7897
8311
|
case 'serve': return cmdServe(rest);
|
|
7898
8312
|
case 'import': return cmdImport(rest);
|
|
8313
|
+
case 'lineage': return cmdLineage(rest);
|
|
7899
8314
|
case 'events': return cmdEvents(rest);
|
|
7900
8315
|
case 'export': return cmdExport(rest);
|
|
7901
8316
|
case 'render': return cmdRender(rest);
|
|
@@ -7920,4 +8335,4 @@ async function run(args) {
|
|
|
7920
8335
|
}
|
|
7921
8336
|
}
|
|
7922
8337
|
|
|
7923
|
-
module.exports = { run };
|
|
8338
|
+
module.exports = { run, taskDayGroups, AGENT_ENV_MARKERS };
|