atris 3.16.1 → 3.22.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 +32 -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 +413 -31
- package/commands/agent-spawn.js +480 -0
- package/commands/analytics.js +6 -3
- package/commands/apps.js +11 -0
- package/commands/autopilot.js +42 -18
- package/commands/brain.js +74 -7
- package/commands/brainstorm.js +9 -58
- package/commands/clean.js +1 -4
- package/commands/compile.js +9 -4
- package/commands/console.js +8 -3
- package/commands/deck.js +184 -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 +105 -27
- package/commands/pulse.js +504 -0
- package/commands/radar.js +1 -0
- package/commands/recap.js +71 -25
- package/commands/run.js +615 -22
- package/commands/site.js +48 -0
- package/commands/slop.js +307 -0
- package/commands/spaceship.js +39 -0
- package/commands/sync.js +0 -2
- package/commands/task.js +429 -37
- package/commands/theme.js +217 -0
- 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/deck-from-md.js +110 -0
- package/lib/escape-regexp.js +13 -0
- package/lib/file-ops.js +6 -3
- package/lib/html-render.js +257 -0
- package/lib/journal.js +1 -1
- package/lib/lesson-contradiction.js +113 -0
- package/lib/memory-view.js +95 -0
- package/lib/policy-lessons.js +3 -2
- package/lib/pulse.js +401 -0
- package/lib/runner-command.js +156 -0
- package/lib/site.js +114 -0
- package/lib/slides-deck.js +237 -0
- package/lib/state-detection.js +1 -4
- package/lib/task-db.js +101 -4
- package/lib/task-proof.js +1 -1
- package/lib/theme.js +264 -0
- 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,
|
|
@@ -4604,6 +4613,93 @@ function cmdClaim(args) {
|
|
|
4604
4613
|
}
|
|
4605
4614
|
}
|
|
4606
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
|
+
|
|
4607
4703
|
function cmdNext(args) {
|
|
4608
4704
|
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
4609
4705
|
const taskDb = getTaskDb();
|
|
@@ -4650,6 +4746,40 @@ function cmdNext(args) {
|
|
|
4650
4746
|
const continueWorkCommand = handoff.next_action === 'continue_work'
|
|
4651
4747
|
? continueWorkCommandForTask(reviewTask, { owner })
|
|
4652
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
|
+
}
|
|
4653
4783
|
if (wantsJson(args)) {
|
|
4654
4784
|
printJson({
|
|
4655
4785
|
ok: true,
|
|
@@ -4658,6 +4788,7 @@ function cmdNext(args) {
|
|
|
4658
4788
|
owner: String(owner),
|
|
4659
4789
|
projection_path: outPath,
|
|
4660
4790
|
handoff,
|
|
4791
|
+
next_agent_action: nextAgentAction,
|
|
4661
4792
|
continue_work_command: continueWorkCommand,
|
|
4662
4793
|
continue_work_api: continueWorkCommand ? { method: 'POST', path: `/api/tasks/${encodeURIComponent(reviewTask.id)}/continue-work` } : null,
|
|
4663
4794
|
review_task: reviewTask,
|
|
@@ -4671,8 +4802,15 @@ function cmdNext(args) {
|
|
|
4671
4802
|
console.log(handoff.next_action === 'continue_work'
|
|
4672
4803
|
? 'Continue work elsewhere; AgentXP waits for human accept.'
|
|
4673
4804
|
: handoff.next_action === 'human_accept_waiting'
|
|
4674
|
-
? '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.')
|
|
4675
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
|
+
}
|
|
4676
4814
|
if (continueWorkCommand) console.log(`Command: ${continueWorkCommand}`);
|
|
4677
4815
|
return;
|
|
4678
4816
|
}
|
|
@@ -6020,7 +6158,13 @@ function cmdDone(args) {
|
|
|
6020
6158
|
if (!failed || hasReview) requireMeaningfulTaskProof('atris task done', proof);
|
|
6021
6159
|
else if (proof) requireMeaningfulTaskProof('atris task done', proof);
|
|
6022
6160
|
}
|
|
6023
|
-
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
|
+
});
|
|
6024
6168
|
if (result.updated) {
|
|
6025
6169
|
const review = hasReview ? taskDb.reviewTask(db, {
|
|
6026
6170
|
id: taskId,
|
|
@@ -6095,7 +6239,13 @@ function cmdFinish(args) {
|
|
|
6095
6239
|
if (!failed || hasReview) requireMeaningfulTaskProof('atris task finish', proof);
|
|
6096
6240
|
else if (proof) requireMeaningfulTaskProof('atris task finish', proof);
|
|
6097
6241
|
}
|
|
6098
|
-
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
|
+
});
|
|
6099
6249
|
if (!done.updated) {
|
|
6100
6250
|
const detail = `finish failed: ${taskId} not in open|claimed`;
|
|
6101
6251
|
if (wantsJson(args)) {
|
|
@@ -6296,7 +6446,14 @@ function cmdAccept(args) {
|
|
|
6296
6446
|
console.error('atris task accept: reward must be a positive number');
|
|
6297
6447
|
process.exit(2);
|
|
6298
6448
|
}
|
|
6299
|
-
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
|
+
});
|
|
6300
6457
|
if (!done.updated) {
|
|
6301
6458
|
console.error(`accept failed: ${taskId} not open|claimed|review`);
|
|
6302
6459
|
process.exit(1);
|
|
@@ -6358,7 +6515,14 @@ function stampAutoAcceptMetadata(taskDb, db, taskId, actor, policy) {
|
|
|
6358
6515
|
}
|
|
6359
6516
|
|
|
6360
6517
|
function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '', nextTask = '' }) {
|
|
6361
|
-
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
|
+
});
|
|
6362
6526
|
if (!done.updated) {
|
|
6363
6527
|
return { ok: false, reason: 'not_open_claimed_or_review' };
|
|
6364
6528
|
}
|
|
@@ -6736,6 +6900,91 @@ function cmdEvents(args) {
|
|
|
6736
6900
|
}
|
|
6737
6901
|
}
|
|
6738
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
|
+
|
|
6739
6988
|
function cmdExport(args) {
|
|
6740
6989
|
const out = flag(args, '--out') || path.join('.atris', 'state', 'tasks.projection.json');
|
|
6741
6990
|
const all = hasFlag(args, '--all');
|
|
@@ -6797,7 +7046,7 @@ function cmdSetup(args) {
|
|
|
6797
7046
|
}
|
|
6798
7047
|
|
|
6799
7048
|
function extractTodoSectionMarkdown(content, sectionName) {
|
|
6800
|
-
const escaped =
|
|
7049
|
+
const escaped = escapeRegExp(sectionName || '');
|
|
6801
7050
|
const match = String(content || '').match(new RegExp(`(?:^|\\n)(##\\s+${escaped}[^\\n]*\\n[\\s\\S]*?)(?=\\n##(?!#)\\s+|$)`, 'i'));
|
|
6802
7051
|
return match ? match[1].trimEnd() : null;
|
|
6803
7052
|
}
|
|
@@ -7001,22 +7250,54 @@ function taskBoardHtml() {
|
|
|
7001
7250
|
<meta charset="utf-8">
|
|
7002
7251
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7003
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">
|
|
7004
7256
|
<style>
|
|
7005
|
-
:
|
|
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
|
+
}
|
|
7006
7274
|
* { box-sizing: border-box; }
|
|
7007
|
-
body {
|
|
7008
|
-
|
|
7009
|
-
|
|
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; }
|
|
7010
7287
|
.sub { color:var(--muted); font-size:12px; }
|
|
7011
|
-
main { display:grid; grid-template-columns: 320px 1fr; height:calc(100vh -
|
|
7012
|
-
aside { border-right:1px solid var(--line); padding:
|
|
7013
|
-
section { min-width:0; overflow:auto; padding:
|
|
7014
|
-
label { display:block; color:var(--muted); font-size:
|
|
7015
|
-
input, textarea, select { width:100%; border:1px solid var(--line); background
|
|
7016
|
-
textarea {
|
|
7017
|
-
|
|
7018
|
-
button
|
|
7019
|
-
|
|
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); }
|
|
7020
7301
|
.grid { display:grid; grid-template-columns: repeat(var(--board-columns, 6), minmax(160px, 1fr)); gap:12px; align-items:start; }
|
|
7021
7302
|
.overview { display:grid; grid-template-columns: minmax(260px, 1.4fr) minmax(260px, 1fr); gap:12px; margin-bottom:12px; }
|
|
7022
7303
|
.goalbox, .chainbox { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:11px; min-height:88px; }
|
|
@@ -7025,35 +7306,62 @@ function taskBoardHtml() {
|
|
|
7025
7306
|
.chainitem { display:grid; grid-template-columns:72px 1fr; gap:8px; font-size:12px; line-height:1.3; margin:5px 0; color:var(--muted); }
|
|
7026
7307
|
.chainitem strong { color:var(--text); font-weight:600; }
|
|
7027
7308
|
.streams { display:grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap:12px; margin-bottom:12px; }
|
|
7028
|
-
.stream { background
|
|
7029
|
-
.stream h2 { margin:0 0 8px; font-size:12px; color:var(--text); line-height:1.25; }
|
|
7030
|
-
.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; }
|
|
7031
7312
|
.streambar span { display:block; min-width:2px; }
|
|
7032
|
-
.seg-open { background
|
|
7033
|
-
.seg-doing { background
|
|
7034
|
-
.seg-review { background
|
|
7035
|
-
.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); }
|
|
7036
7317
|
.streamtask { display:grid; grid-template-columns:64px 1fr; gap:8px; color:var(--muted); font-size:11px; line-height:1.25; margin-top:6px; }
|
|
7037
7318
|
.streamtask strong { color:var(--text); font-weight:550; }
|
|
7038
7319
|
.col { background:var(--panel); border:1px solid var(--line); border-radius:8px; min-height:160px; overflow:hidden; }
|
|
7039
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; }
|
|
7040
7321
|
.cards { padding:8px; display:flex; flex-direction:column; gap:8px; }
|
|
7041
|
-
.card { text-align:left; width:100%; background
|
|
7042
|
-
.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); }
|
|
7043
7325
|
.title { font-size:13px; line-height:1.25; }
|
|
7044
|
-
.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); }
|
|
7045
7327
|
.pill { border:1px solid var(--line); border-radius:999px; padding:1px 6px; }
|
|
7046
7328
|
.why { margin-top:7px; color:var(--muted); font-size:11px; line-height:1.25; }
|
|
7047
|
-
.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; }
|
|
7048
7330
|
.fact b { color:var(--muted); font-size:11px; display:block; margin-bottom:3px; }
|
|
7049
7331
|
.room { margin-top:14px; border-top:1px solid var(--line); padding-top:12px; }
|
|
7050
7332
|
.room h3 { margin:0 0 4px; font-size:14px; }
|
|
7051
7333
|
.thread { margin:10px 0; display:flex; flex-direction:column; gap:7px; }
|
|
7052
|
-
.msg { background
|
|
7334
|
+
.msg { background:oklch(15% 0.012 160); border:1px solid var(--line); border-radius:7px; padding:8px; font-size:12px; }
|
|
7053
7335
|
.msg .who { color:var(--muted); font-size:11px; margin-bottom:3px; }
|
|
7054
7336
|
.actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:10px; }
|
|
7055
7337
|
.full { grid-column:1 / -1; }
|
|
7056
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); }
|
|
7057
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; } }
|
|
7058
7366
|
</style>
|
|
7059
7367
|
</head>
|
|
@@ -7061,7 +7369,7 @@ function taskBoardHtml() {
|
|
|
7061
7369
|
<header>
|
|
7062
7370
|
<div>
|
|
7063
7371
|
<h1>Atris Task Factory</h1>
|
|
7064
|
-
<div class="
|
|
7372
|
+
<div class="beat" id="heartbeat"><span class="dot"></span><span>heartbeat: loading…</span></div>
|
|
7065
7373
|
</div>
|
|
7066
7374
|
<button id="refresh">Refresh</button>
|
|
7067
7375
|
</header>
|
|
@@ -7080,6 +7388,7 @@ function taskBoardHtml() {
|
|
|
7080
7388
|
</aside>
|
|
7081
7389
|
<section>
|
|
7082
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>
|
|
7083
7392
|
<div class="streams" id="streams"></div>
|
|
7084
7393
|
<div class="grid" id="board"></div>
|
|
7085
7394
|
</section>
|
|
@@ -7125,10 +7434,61 @@ function taskBoardHtml() {
|
|
|
7125
7434
|
return 'done';
|
|
7126
7435
|
}
|
|
7127
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
|
+
|
|
7128
7487
|
async function load() {
|
|
7129
7488
|
const data = await api('/api/tasks');
|
|
7130
7489
|
state = data.projection;
|
|
7131
7490
|
render();
|
|
7491
|
+
loadStream().catch(() => {});
|
|
7132
7492
|
}
|
|
7133
7493
|
|
|
7134
7494
|
function render() {
|
|
@@ -7343,6 +7703,24 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7343
7703
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
7344
7704
|
return sendJson(res, 200, { ok: true, projection_path: outPath, projection });
|
|
7345
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
|
+
}
|
|
7346
7724
|
if (req.method === 'GET' && url.pathname === '/api/tasks/capabilities') {
|
|
7347
7725
|
return sendJson(res, 200, {
|
|
7348
7726
|
ok: true,
|
|
@@ -7659,7 +8037,13 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7659
8037
|
const shouldReview = Boolean(body.proof || body.lesson || body.next || body.reward !== undefined);
|
|
7660
8038
|
const proofIssue = meaningfulTaskProofIssue(proof, { required: !failed || shouldReview });
|
|
7661
8039
|
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
7662
|
-
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
|
+
});
|
|
7663
8047
|
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_or_claimed' });
|
|
7664
8048
|
let episode = null;
|
|
7665
8049
|
let nextCreated = null;
|
|
@@ -7729,7 +8113,14 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7729
8113
|
if (hasExplicitNext && !nextTask.trim()) clearedFields.push('next_task');
|
|
7730
8114
|
const parsedReward = parseAcceptReward(body.reward);
|
|
7731
8115
|
if (!parsedReward.ok) return sendJson(res, 400, { ok: false, reason: 'invalid_reward', detail: 'reward must be a positive number' });
|
|
7732
|
-
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
|
+
});
|
|
7733
8124
|
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_claimed_or_review' });
|
|
7734
8125
|
const reviewed = taskDb.reviewTask(db, {
|
|
7735
8126
|
id: taskId,
|
|
@@ -7919,6 +8310,7 @@ async function run(args) {
|
|
|
7919
8310
|
case 'setup': return cmdSetup(rest);
|
|
7920
8311
|
case 'serve': return cmdServe(rest);
|
|
7921
8312
|
case 'import': return cmdImport(rest);
|
|
8313
|
+
case 'lineage': return cmdLineage(rest);
|
|
7922
8314
|
case 'events': return cmdEvents(rest);
|
|
7923
8315
|
case 'export': return cmdExport(rest);
|
|
7924
8316
|
case 'render': return cmdRender(rest);
|
|
@@ -7943,4 +8335,4 @@ async function run(args) {
|
|
|
7943
8335
|
}
|
|
7944
8336
|
}
|
|
7945
8337
|
|
|
7946
|
-
module.exports = { run, taskDayGroups };
|
|
8338
|
+
module.exports = { run, taskDayGroups, AGENT_ENV_MARKERS };
|