atris 3.29.0 → 3.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/security-review.js +64 -0
- package/commands/task.js +730 -23
- package/commands/workflow.js +6 -6
- package/commands/xp.js +10 -1
- package/lib/file-ops.js +1 -1
- package/lib/journal.js +7 -73
- package/lib/security-scan.js +73 -1
- package/lib/task-db.js +20 -10
- package/package.json +1 -1
package/commands/task.js
CHANGED
|
@@ -17,6 +17,7 @@ const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
|
17
17
|
|| os.userInfo().username
|
|
18
18
|
|| 'unknown';
|
|
19
19
|
const AGENT_CERTIFICATION_REVIEW_PASSES = 2;
|
|
20
|
+
const RESULT_SAVED_TEXT_LIMIT = 200;
|
|
20
21
|
const REVIEW_LANE_LOOP_DEFAULT_MAX_STEPS = 3;
|
|
21
22
|
const REVIEW_LANE_LOOP_MAX_STEPS = 10;
|
|
22
23
|
const REVIEW_LANE_RUN_DEFAULT_MAX_RUNS = 3;
|
|
@@ -97,8 +98,15 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
97
98
|
atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
|
|
98
99
|
atris task ready <id> --proof "..." Agent proof ready; native goal can complete
|
|
99
100
|
atris task ready <id> --verify "<cmd>" Run <cmd>; only ready if it exits 0 (executed proof)
|
|
101
|
+
atris task plan-preview "<purpose>" [--tag <tag>] [--owner <member>] [--task <id>]
|
|
102
|
+
Show the plain Plan before work starts
|
|
103
|
+
atris task ready <id> --proof "..." [--changed "..." --checked "..." --saved "..." --try "..."]
|
|
104
|
+
Agent proof ready; records Result if needed
|
|
105
|
+
atris task result <id> --changed "..." --checked "..." [--saved "..."] [--try "..."]
|
|
106
|
+
Show the plain Result and store trace on the task
|
|
100
107
|
atris task review-chat <id> [--as <owner>] Start a task-owned /codex verification chat
|
|
101
|
-
atris task accept <id> [--proof "..."]
|
|
108
|
+
atris task accept <id> [--proof "..."] [--public]
|
|
109
|
+
Human accepts proof, marks done; --public also publishes AgentXP
|
|
102
110
|
atris task auto-accept-certified --dry-run [--strict-verify] [--limit <n>]
|
|
103
111
|
Preview certified Review rows; live accept needs --confirm-human-accept --as <human>
|
|
104
112
|
atris task revise <id> --note "..." Send reviewed work back to Do
|
|
@@ -5021,7 +5029,8 @@ function cmdPlan(args) {
|
|
|
5021
5029
|
const pos = positional(args);
|
|
5022
5030
|
const id = pos[0];
|
|
5023
5031
|
if (!id) failTask('atris task plan', 'missing_id', 'id required');
|
|
5024
|
-
const
|
|
5032
|
+
const actorFlag = flag(args, '--as');
|
|
5033
|
+
const actor = String(actorFlag || DEFAULT_OWNER);
|
|
5025
5034
|
const goal = textFlag(args, ['--goal', '--objective']);
|
|
5026
5035
|
const exit = textFlag(args, ['--exit', '--exit-condition']);
|
|
5027
5036
|
const proofNeeded = textFlag(args, ['--proof-needed', '--proof', '--verify']);
|
|
@@ -5033,18 +5042,29 @@ function cmdPlan(args) {
|
|
|
5033
5042
|
const taskDb = getTaskDb();
|
|
5034
5043
|
const db = taskDb.open();
|
|
5035
5044
|
const taskId = requireTaskId(taskDb, db, id, 'atris task plan');
|
|
5045
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
5046
|
+
const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
|
|
5047
|
+
actor,
|
|
5048
|
+
actorExplicit: typeof actorFlag === 'string' && Boolean(actorFlag.trim()),
|
|
5049
|
+
owner,
|
|
5050
|
+
goal,
|
|
5051
|
+
summary,
|
|
5052
|
+
firstMove,
|
|
5053
|
+
exit,
|
|
5054
|
+
});
|
|
5036
5055
|
const result = taskDb.stageTask(db, {
|
|
5037
5056
|
id: taskId,
|
|
5038
5057
|
actor,
|
|
5039
5058
|
stage: 'plan',
|
|
5040
5059
|
goal,
|
|
5041
5060
|
summary,
|
|
5042
|
-
owner,
|
|
5061
|
+
owner: automaticPlan.ownerForStage || owner,
|
|
5043
5062
|
exit,
|
|
5044
5063
|
proofNeeded,
|
|
5045
5064
|
firstMove,
|
|
5046
5065
|
nextButton,
|
|
5047
5066
|
confidence,
|
|
5067
|
+
planTrace: automaticPlan.trace,
|
|
5048
5068
|
});
|
|
5049
5069
|
if (!result.staged) {
|
|
5050
5070
|
failTask('atris task plan', result.reason || 'stage_failed', stageErrorDetail('atris task plan', result.reason, result), 1);
|
|
@@ -5056,6 +5076,11 @@ function cmdPlan(args) {
|
|
|
5056
5076
|
action: 'planned',
|
|
5057
5077
|
task_id: taskId,
|
|
5058
5078
|
version: result.event.version,
|
|
5079
|
+
plan_trace: automaticPlan.trace ? {
|
|
5080
|
+
plan: automaticPlan.plan,
|
|
5081
|
+
owner_choice: automaticPlan.ownerChoice,
|
|
5082
|
+
trace: automaticPlan.trace,
|
|
5083
|
+
} : null,
|
|
5059
5084
|
stage_packet: result.stage_packet,
|
|
5060
5085
|
projection_path: outPath,
|
|
5061
5086
|
task: compactTaskFromProjection(projection, taskId),
|
|
@@ -5320,6 +5345,611 @@ function taskCommandQuote(value) {
|
|
|
5320
5345
|
return `"${text || '...'}"`;
|
|
5321
5346
|
}
|
|
5322
5347
|
|
|
5348
|
+
function cleanPublicText(value, max = 500) {
|
|
5349
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
5350
|
+
if (!text) return '';
|
|
5351
|
+
return text.length > max ? `${text.slice(0, Math.max(0, max - 3)).trim()}...` : text;
|
|
5352
|
+
}
|
|
5353
|
+
|
|
5354
|
+
function publicWords(value) {
|
|
5355
|
+
return (String(value || '').toLowerCase().match(/[a-z0-9]{3,}/g) || [])
|
|
5356
|
+
.map(word => word.endsWith('s') && word.length > 4 ? word.slice(0, -1) : word)
|
|
5357
|
+
.filter(word => !new Set([
|
|
5358
|
+
'and',
|
|
5359
|
+
'for',
|
|
5360
|
+
'from',
|
|
5361
|
+
'into',
|
|
5362
|
+
'the',
|
|
5363
|
+
'this',
|
|
5364
|
+
'that',
|
|
5365
|
+
'task',
|
|
5366
|
+
'work',
|
|
5367
|
+
'with',
|
|
5368
|
+
]).has(word));
|
|
5369
|
+
}
|
|
5370
|
+
|
|
5371
|
+
function parseMemberFrontmatter(text) {
|
|
5372
|
+
const source = String(text || '');
|
|
5373
|
+
if (!source.startsWith('---')) return {};
|
|
5374
|
+
const end = source.indexOf('\n---', 3);
|
|
5375
|
+
if (end === -1) return {};
|
|
5376
|
+
const block = source.slice(3, end).split(/\r?\n/);
|
|
5377
|
+
const data = {};
|
|
5378
|
+
for (const raw of block) {
|
|
5379
|
+
const match = raw.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
5380
|
+
if (!match) continue;
|
|
5381
|
+
data[match[1]] = match[2].replace(/^["']|["']$/g, '').trim();
|
|
5382
|
+
}
|
|
5383
|
+
return data;
|
|
5384
|
+
}
|
|
5385
|
+
|
|
5386
|
+
function readTeamMembers(root = process.cwd()) {
|
|
5387
|
+
const teamDir = path.join(root, 'atris', 'team');
|
|
5388
|
+
if (!fs.existsSync(teamDir)) return [];
|
|
5389
|
+
return fs.readdirSync(teamDir, { withFileTypes: true })
|
|
5390
|
+
.filter(entry => entry.isDirectory())
|
|
5391
|
+
.map(entry => {
|
|
5392
|
+
const slug = entry.name;
|
|
5393
|
+
const memberPath = path.join(teamDir, slug, 'MEMBER.md');
|
|
5394
|
+
if (!fs.existsSync(memberPath)) return null;
|
|
5395
|
+
const text = fs.readFileSync(memberPath, 'utf8');
|
|
5396
|
+
const frontmatter = parseMemberFrontmatter(text);
|
|
5397
|
+
return {
|
|
5398
|
+
slug,
|
|
5399
|
+
role: cleanPublicText(frontmatter.role || slug.replace(/[-_]/g, ' '), 120),
|
|
5400
|
+
description: cleanPublicText(frontmatter.description || '', 240),
|
|
5401
|
+
path: memberPath,
|
|
5402
|
+
};
|
|
5403
|
+
})
|
|
5404
|
+
.filter(Boolean);
|
|
5405
|
+
}
|
|
5406
|
+
|
|
5407
|
+
const GENERIC_MEMBER_SLUGS = new Set([
|
|
5408
|
+
'_template',
|
|
5409
|
+
'coordinator',
|
|
5410
|
+
'executor',
|
|
5411
|
+
'generalist',
|
|
5412
|
+
'navigator',
|
|
5413
|
+
'supervisor',
|
|
5414
|
+
]);
|
|
5415
|
+
|
|
5416
|
+
function scoreTeamMember(member, words, tag) {
|
|
5417
|
+
const slug = String(member.slug || '').toLowerCase();
|
|
5418
|
+
const role = String(member.role || '').toLowerCase();
|
|
5419
|
+
const description = String(member.description || '').toLowerCase();
|
|
5420
|
+
const haystack = `${slug} ${role} ${description}`.replace(/[-_]/g, ' ');
|
|
5421
|
+
let score = 0;
|
|
5422
|
+
const cleanTag = String(tag || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
5423
|
+
if (cleanTag) {
|
|
5424
|
+
if (slug === cleanTag || slug.replace(/[-_]/g, ' ') === cleanTag) score += 12;
|
|
5425
|
+
else if (haystack.includes(cleanTag)) score += 5;
|
|
5426
|
+
}
|
|
5427
|
+
for (const word of words) {
|
|
5428
|
+
if (slug.includes(word)) score += 5;
|
|
5429
|
+
else if (role.includes(word)) score += 3;
|
|
5430
|
+
else if (description.includes(word)) score += 2;
|
|
5431
|
+
}
|
|
5432
|
+
if (GENERIC_MEMBER_SLUGS.has(slug)) score -= 3;
|
|
5433
|
+
return score;
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5436
|
+
function plainMemberDescription(member) {
|
|
5437
|
+
const description = cleanPublicText(member && member.description, 220);
|
|
5438
|
+
if (!description) return '';
|
|
5439
|
+
const lowered = `${description.charAt(0).toLowerCase()}${description.slice(1)}`;
|
|
5440
|
+
return /[.!?]$/.test(lowered) ? lowered : `${lowered}.`;
|
|
5441
|
+
}
|
|
5442
|
+
|
|
5443
|
+
function chooseTaskOwner({ purpose, tag, requestedOwner, root = process.cwd() } = {}) {
|
|
5444
|
+
const members = readTeamMembers(root);
|
|
5445
|
+
const requested = cleanPublicText(requestedOwner, 80);
|
|
5446
|
+
if (requested) {
|
|
5447
|
+
const match = members.find(member => member.slug === requested);
|
|
5448
|
+
if (match) {
|
|
5449
|
+
const description = plainMemberDescription(match);
|
|
5450
|
+
return {
|
|
5451
|
+
owner: match.slug,
|
|
5452
|
+
member: match,
|
|
5453
|
+
source: 'requested',
|
|
5454
|
+
reason: `${match.slug} matches this work${description ? ` because ${description}` : '.'}`,
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
return {
|
|
5458
|
+
owner: requested,
|
|
5459
|
+
member: null,
|
|
5460
|
+
source: 'requested',
|
|
5461
|
+
reason: `${requested} was requested, but no matching atris/team member was found.`,
|
|
5462
|
+
};
|
|
5463
|
+
}
|
|
5464
|
+
const words = publicWords(`${purpose || ''} ${tag || ''}`);
|
|
5465
|
+
let best = null;
|
|
5466
|
+
for (const member of members) {
|
|
5467
|
+
const score = scoreTeamMember(member, words, tag);
|
|
5468
|
+
if (!best || score > best.score) best = { member, score };
|
|
5469
|
+
}
|
|
5470
|
+
if (best && best.score > 0) {
|
|
5471
|
+
const member = best.member;
|
|
5472
|
+
const description = plainMemberDescription(member);
|
|
5473
|
+
return {
|
|
5474
|
+
owner: member.slug,
|
|
5475
|
+
member,
|
|
5476
|
+
source: 'team',
|
|
5477
|
+
score: best.score,
|
|
5478
|
+
reason: `${member.slug} fits this work${description ? ` because ${description}` : '.'}`,
|
|
5479
|
+
};
|
|
5480
|
+
}
|
|
5481
|
+
return {
|
|
5482
|
+
owner: DEFAULT_OWNER,
|
|
5483
|
+
member: null,
|
|
5484
|
+
source: 'fallback',
|
|
5485
|
+
reason: `${DEFAULT_OWNER} is handling it because no specific atris/team owner matched this work.`,
|
|
5486
|
+
};
|
|
5487
|
+
}
|
|
5488
|
+
|
|
5489
|
+
function isGenericPlanActor(value) {
|
|
5490
|
+
const actor = cleanPublicText(value, 80).toLowerCase();
|
|
5491
|
+
if (!actor) return true;
|
|
5492
|
+
if (actor === String(DEFAULT_OWNER || '').toLowerCase()) return true;
|
|
5493
|
+
return GENERIC_MEMBER_SLUGS.has(actor) || new Set([
|
|
5494
|
+
'codex',
|
|
5495
|
+
'codex-executor',
|
|
5496
|
+
'claude',
|
|
5497
|
+
'claude-code',
|
|
5498
|
+
'cursor',
|
|
5499
|
+
'devin',
|
|
5500
|
+
]).has(actor);
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
function taskTextMentionsActor(actor, text) {
|
|
5504
|
+
const actorWords = publicWords(actor);
|
|
5505
|
+
if (!actorWords.length) return false;
|
|
5506
|
+
const words = new Set(publicWords(text));
|
|
5507
|
+
return actorWords.some(word => words.has(word));
|
|
5508
|
+
}
|
|
5509
|
+
|
|
5510
|
+
function buildPublicPlan({ purpose, owner, ownerReason, plan, expected }) {
|
|
5511
|
+
const cleanPurpose = cleanPublicText(purpose, 240);
|
|
5512
|
+
const cleanOwner = cleanPublicText(owner, 80);
|
|
5513
|
+
const cleanReason = cleanPublicText(ownerReason, 240);
|
|
5514
|
+
const cleanPlan = cleanPublicText(plan, 320) || `${cleanOwner || 'The owner'} will make the smallest needed change, then check the result.`;
|
|
5515
|
+
const cleanExpected = cleanPublicText(expected, 240) || 'the check passes and the result is ready to review.';
|
|
5516
|
+
return {
|
|
5517
|
+
purpose: cleanPurpose,
|
|
5518
|
+
owner: cleanOwner,
|
|
5519
|
+
owner_reason: cleanReason,
|
|
5520
|
+
plan: cleanPlan,
|
|
5521
|
+
expected_result: cleanExpected,
|
|
5522
|
+
};
|
|
5523
|
+
}
|
|
5524
|
+
|
|
5525
|
+
function renderPublicPlan(plan) {
|
|
5526
|
+
const lines = [];
|
|
5527
|
+
if (plan.purpose) lines.push(`Purpose: ${plan.purpose}`);
|
|
5528
|
+
if (plan.owner) lines.push(`Owner: ${plan.owner} is handling it.`);
|
|
5529
|
+
if (plan.owner_reason) lines.push(`Why: ${plan.owner_reason}`);
|
|
5530
|
+
if (plan.plan) lines.push(`Plan: ${plan.plan}`);
|
|
5531
|
+
if (plan.expected_result) lines.push(`Expected result: ${plan.expected_result}`);
|
|
5532
|
+
return lines.join('\n');
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5535
|
+
function planTraceData(plan, ownerChoice) {
|
|
5536
|
+
return {
|
|
5537
|
+
schema: 'atris.task_plan_trace.v1',
|
|
5538
|
+
purpose: plan.purpose,
|
|
5539
|
+
owner: plan.owner,
|
|
5540
|
+
owner_reason: plan.owner_reason,
|
|
5541
|
+
plan: plan.plan,
|
|
5542
|
+
expected_result: plan.expected_result,
|
|
5543
|
+
owner_source: ownerChoice && ownerChoice.source || null,
|
|
5544
|
+
owner_score: ownerChoice && ownerChoice.score || null,
|
|
5545
|
+
recorded_at: new Date().toISOString(),
|
|
5546
|
+
};
|
|
5547
|
+
}
|
|
5548
|
+
|
|
5549
|
+
function planTraceNote(plan, ownerChoice) {
|
|
5550
|
+
return `TASK_PLAN_TRACE ${JSON.stringify(planTraceData(plan, ownerChoice))}`;
|
|
5551
|
+
}
|
|
5552
|
+
|
|
5553
|
+
function traceLineFromContent(content, prefix) {
|
|
5554
|
+
const lines = String(content || '').split(/\r?\n/);
|
|
5555
|
+
return lines.find(line => line.startsWith(prefix)) || '';
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5558
|
+
function latestTraceValue(task, prefix, key) {
|
|
5559
|
+
const messages = Array.isArray(task && task.messages) ? task.messages : [];
|
|
5560
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5561
|
+
const content = String(messages[i] && messages[i].content || '');
|
|
5562
|
+
const line = traceLineFromContent(content, prefix);
|
|
5563
|
+
if (!line) continue;
|
|
5564
|
+
try {
|
|
5565
|
+
const parsed = JSON.parse(line.slice(prefix.length).trim());
|
|
5566
|
+
const value = parsed && parsed[key];
|
|
5567
|
+
if (value !== undefined && value !== null && String(value).trim()) return String(value);
|
|
5568
|
+
} catch (_) {
|
|
5569
|
+
continue;
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
return '';
|
|
5573
|
+
}
|
|
5574
|
+
|
|
5575
|
+
function taskHasTrace(task, prefix) {
|
|
5576
|
+
const messages = Array.isArray(task && task.messages) ? task.messages : [];
|
|
5577
|
+
if (messages.some(message => traceLineFromContent(message && message.content, prefix))) return true;
|
|
5578
|
+
const events = Array.isArray(task && task.events) ? task.events : [];
|
|
5579
|
+
return events.some(event => {
|
|
5580
|
+
const payload = event && event.payload && typeof event.payload === 'object' ? event.payload : {};
|
|
5581
|
+
if (traceLineFromContent(payload.stage_packet, prefix)) return true;
|
|
5582
|
+
if (traceLineFromContent(payload.result_packet, prefix)) return true;
|
|
5583
|
+
if (prefix === 'TASK_RESULT_TRACE ' && payload.result_trace && typeof payload.result_trace === 'object') return true;
|
|
5584
|
+
if (prefix === 'TASK_PLAN_TRACE ' && payload.plan_trace && typeof payload.plan_trace === 'object') return true;
|
|
5585
|
+
return false;
|
|
5586
|
+
});
|
|
5587
|
+
}
|
|
5588
|
+
|
|
5589
|
+
function taskPurpose(task) {
|
|
5590
|
+
const metadata = task && task.metadata || {};
|
|
5591
|
+
return cleanPublicText(
|
|
5592
|
+
metadata.task_goal
|
|
5593
|
+
|| metadata.goal_objective
|
|
5594
|
+
|| metadata.objective
|
|
5595
|
+
|| metadata.stage_goal
|
|
5596
|
+
|| latestTraceValue(task, 'TASK_PLAN_TRACE ', 'purpose')
|
|
5597
|
+
|| task && task.title
|
|
5598
|
+
|| '',
|
|
5599
|
+
240,
|
|
5600
|
+
);
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
function buildPublicResult(task, fields) {
|
|
5604
|
+
const owner = cleanPublicText(
|
|
5605
|
+
fields.owner
|
|
5606
|
+
|| latestTraceValue(task, 'TASK_PLAN_TRACE ', 'owner')
|
|
5607
|
+
|| taskAssignee(task)
|
|
5608
|
+
|| task && task.claimed_by
|
|
5609
|
+
|| fields.actor,
|
|
5610
|
+
80,
|
|
5611
|
+
);
|
|
5612
|
+
const result = {
|
|
5613
|
+
purpose: cleanPublicText(fields.purpose || taskPurpose(task), 240),
|
|
5614
|
+
owner,
|
|
5615
|
+
changed: cleanPublicText(fields.changed, 320),
|
|
5616
|
+
checked: cleanPublicText(fields.checked, 320),
|
|
5617
|
+
passed: cleanPublicText(fields.passed, 240),
|
|
5618
|
+
failed: cleanPublicText(fields.failed, 240),
|
|
5619
|
+
cost: cleanPublicText(fields.cost, 80),
|
|
5620
|
+
saved: cleanPublicText(fields.saved, RESULT_SAVED_TEXT_LIMIT),
|
|
5621
|
+
try_next: cleanPublicText(fields.tryNext, 240),
|
|
5622
|
+
status: cleanPublicText(fields.status, 160) || 'ready for review',
|
|
5623
|
+
};
|
|
5624
|
+
return result;
|
|
5625
|
+
}
|
|
5626
|
+
|
|
5627
|
+
function renderPublicResult(result) {
|
|
5628
|
+
return [
|
|
5629
|
+
`Changed: ${cleanPublicText(result && result.changed, 320) || 'changed the requested work'}`,
|
|
5630
|
+
`Checked: ${cleanPublicText(result && result.checked, 320) || 'checked the result'}`,
|
|
5631
|
+
`Try: ${cleanPublicText(result && result.try_next, 240) || 'try the changed work'}`,
|
|
5632
|
+
].join('\n');
|
|
5633
|
+
}
|
|
5634
|
+
|
|
5635
|
+
function latestResultTrace(task) {
|
|
5636
|
+
const messages = Array.isArray(task && task.messages) ? task.messages : [];
|
|
5637
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5638
|
+
const content = String(messages[i] && messages[i].content || '');
|
|
5639
|
+
const line = traceLineFromContent(content, 'TASK_RESULT_TRACE ');
|
|
5640
|
+
if (!line) continue;
|
|
5641
|
+
try {
|
|
5642
|
+
const parsed = JSON.parse(line.slice('TASK_RESULT_TRACE '.length).trim());
|
|
5643
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
5644
|
+
} catch (_) {}
|
|
5645
|
+
}
|
|
5646
|
+
|
|
5647
|
+
const events = Array.isArray(task && task.events) ? task.events : [];
|
|
5648
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
5649
|
+
const payload = events[i] && events[i].payload && typeof events[i].payload === 'object'
|
|
5650
|
+
? events[i].payload
|
|
5651
|
+
: {};
|
|
5652
|
+
if (payload.result_trace && typeof payload.result_trace === 'object') return payload.result_trace;
|
|
5653
|
+
const packetLine = traceLineFromContent(payload.result_packet, 'TASK_RESULT_TRACE ');
|
|
5654
|
+
if (!packetLine) continue;
|
|
5655
|
+
try {
|
|
5656
|
+
const parsed = JSON.parse(packetLine.slice('TASK_RESULT_TRACE '.length).trim());
|
|
5657
|
+
if (parsed && typeof parsed === 'object') return parsed;
|
|
5658
|
+
} catch (_) {}
|
|
5659
|
+
}
|
|
5660
|
+
return null;
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
function buildAcceptHumanResult({ task, proof, nextTask, publicSync }) {
|
|
5664
|
+
const trace = latestResultTrace(task) || {};
|
|
5665
|
+
const traceChanged = cleanPublicText(trace.changed, 320);
|
|
5666
|
+
const changed = traceChanged === 'prepared the work for review'
|
|
5667
|
+
? 'accepted the completed work'
|
|
5668
|
+
: traceChanged
|
|
5669
|
+
|| cleanPublicText(task && task.title, 260)
|
|
5670
|
+
|| 'accepted the completed work';
|
|
5671
|
+
let checked = cleanPublicText(trace.checked, 320)
|
|
5672
|
+
|| cleanPublicText(proof, 320)
|
|
5673
|
+
|| 'checked the proof';
|
|
5674
|
+
if (publicSync && publicSync.enabled === true && !publicSync.ok) {
|
|
5675
|
+
const error = cleanPublicText(publicSync.error || 'publish failed', 140);
|
|
5676
|
+
checked = `${checked}; AgentXP publish failed${error ? ` (${error})` : ''}`;
|
|
5677
|
+
}
|
|
5678
|
+
const tryNext = cleanPublicText(nextTask, 240)
|
|
5679
|
+
|| cleanPublicText(trace.try_next, 240)
|
|
5680
|
+
|| 'try the changed work';
|
|
5681
|
+
return { changed, checked, try_next: tryNext };
|
|
5682
|
+
}
|
|
5683
|
+
|
|
5684
|
+
function renderAcceptLanding({ task, proof, nextTask, publicSync }) {
|
|
5685
|
+
return renderPublicResult(buildAcceptHumanResult({
|
|
5686
|
+
task,
|
|
5687
|
+
proof,
|
|
5688
|
+
nextTask,
|
|
5689
|
+
publicSync,
|
|
5690
|
+
}));
|
|
5691
|
+
}
|
|
5692
|
+
|
|
5693
|
+
async function publishAcceptAgentXp(args, actor) {
|
|
5694
|
+
const token = flag(args, '--token');
|
|
5695
|
+
const syncArgs = ['--all', '--root', process.cwd(), '--public', '--as', actor];
|
|
5696
|
+
if (typeof token === 'string' && token.trim()) syncArgs.push('--token', token.trim());
|
|
5697
|
+
try {
|
|
5698
|
+
const { syncAgentXp } = require('../commands/xp');
|
|
5699
|
+
const result = await syncAgentXp(syncArgs);
|
|
5700
|
+
const server = result && result.server ? result.server : {};
|
|
5701
|
+
const publicCount = Number(server.public_accepted_count);
|
|
5702
|
+
const acceptedCount = Number(server.accepted_count);
|
|
5703
|
+
const published = (
|
|
5704
|
+
(Number.isFinite(publicCount) && publicCount > 0)
|
|
5705
|
+
|| (Number.isFinite(acceptedCount) && acceptedCount > 0 && server.private_agentxp !== true)
|
|
5706
|
+
);
|
|
5707
|
+
return {
|
|
5708
|
+
enabled: true,
|
|
5709
|
+
ok: published,
|
|
5710
|
+
result,
|
|
5711
|
+
error: published ? null : 'server accepted no public AgentXP rows',
|
|
5712
|
+
};
|
|
5713
|
+
} catch (error) {
|
|
5714
|
+
return {
|
|
5715
|
+
enabled: true,
|
|
5716
|
+
ok: false,
|
|
5717
|
+
error: error && error.message ? error.message : String(error),
|
|
5718
|
+
};
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
|
|
5722
|
+
function todayResultLogName() {
|
|
5723
|
+
const now = new Date();
|
|
5724
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}.md`;
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5727
|
+
function appendResultOwnerLog(root, task, result) {
|
|
5728
|
+
const owner = cleanPublicText(result && result.owner, 80);
|
|
5729
|
+
if (!owner || !/^[A-Za-z0-9._-]+$/.test(owner)) return null;
|
|
5730
|
+
const memberFile = path.join(root, 'atris', 'team', owner, 'MEMBER.md');
|
|
5731
|
+
if (!fs.existsSync(memberFile)) return null;
|
|
5732
|
+
const logName = todayResultLogName();
|
|
5733
|
+
const logDir = path.join(root, 'atris', 'team', owner, 'logs');
|
|
5734
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
5735
|
+
const logPath = path.join(logDir, logName);
|
|
5736
|
+
const stamp = new Date().toTimeString().slice(0, 5);
|
|
5737
|
+
const lines = [
|
|
5738
|
+
`## ${stamp} - Result`,
|
|
5739
|
+
`- task: ${taskRef(task)}`,
|
|
5740
|
+
`- purpose: ${result.purpose || ''}`,
|
|
5741
|
+
`- result: ${result.changed || ''}`,
|
|
5742
|
+
`- checked: ${result.checked || ''}`,
|
|
5743
|
+
`- saved: ${result.saved || ''}`,
|
|
5744
|
+
`- try: ${result.try_next || ''}`,
|
|
5745
|
+
`- status: ${result.status || ''}`,
|
|
5746
|
+
'',
|
|
5747
|
+
];
|
|
5748
|
+
fs.appendFileSync(logPath, lines.join('\n'), 'utf8');
|
|
5749
|
+
return {
|
|
5750
|
+
member_log_path: path.relative(root, logPath),
|
|
5751
|
+
};
|
|
5752
|
+
}
|
|
5753
|
+
|
|
5754
|
+
function resultTraceData(result, fields) {
|
|
5755
|
+
return {
|
|
5756
|
+
schema: 'atris.task_result_trace.v1',
|
|
5757
|
+
purpose: result.purpose,
|
|
5758
|
+
owner: result.owner || null,
|
|
5759
|
+
changed: result.changed,
|
|
5760
|
+
checked: result.checked,
|
|
5761
|
+
passed: result.passed || null,
|
|
5762
|
+
failed: result.failed || null,
|
|
5763
|
+
cost: result.cost || null,
|
|
5764
|
+
saved: result.saved || null,
|
|
5765
|
+
try_next: result.try_next || null,
|
|
5766
|
+
status: result.status,
|
|
5767
|
+
files: cleanPublicText(fields.files, 500) || null,
|
|
5768
|
+
commands: cleanPublicText(fields.commands, 500) || null,
|
|
5769
|
+
member_log_path: fields.savedPaths && fields.savedPaths.member_log_path || null,
|
|
5770
|
+
recorded_at: new Date().toISOString(),
|
|
5771
|
+
};
|
|
5772
|
+
}
|
|
5773
|
+
|
|
5774
|
+
function resultTraceNote(result, fields) {
|
|
5775
|
+
return `TASK_RESULT_TRACE ${JSON.stringify(resultTraceData(result, fields))}`;
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
function cmdPlanPreview(args) {
|
|
5779
|
+
const pos = positional(args);
|
|
5780
|
+
const purpose = cleanPublicText(textFlag(args, ['--purpose', '--goal', '--objective']) || pos.join(' '), 240);
|
|
5781
|
+
if (!purpose) failTask('atris task plan-preview', 'missing_purpose', 'purpose required');
|
|
5782
|
+
const tag = textFlag(args, ['--tag']);
|
|
5783
|
+
const requestedOwner = textFlag(args, ['--owner', '--as', '--member']);
|
|
5784
|
+
const planText = textFlag(args, ['--plan', '--action', '--first-move']);
|
|
5785
|
+
const expected = textFlag(args, ['--expected', '--expected-result', '--exit']);
|
|
5786
|
+
const recordRef = textFlag(args, ['--task', '--record']);
|
|
5787
|
+
const taskDb = getTaskDb();
|
|
5788
|
+
const db = taskDb.open();
|
|
5789
|
+
const ownerChoice = chooseTaskOwner({
|
|
5790
|
+
purpose,
|
|
5791
|
+
tag,
|
|
5792
|
+
requestedOwner,
|
|
5793
|
+
root: taskDb.workspaceRoot(),
|
|
5794
|
+
});
|
|
5795
|
+
const publicPlan = buildPublicPlan({
|
|
5796
|
+
purpose,
|
|
5797
|
+
owner: ownerChoice.owner,
|
|
5798
|
+
ownerReason: ownerChoice.reason,
|
|
5799
|
+
plan: planText,
|
|
5800
|
+
expected,
|
|
5801
|
+
});
|
|
5802
|
+
let recorded = null;
|
|
5803
|
+
if (recordRef) {
|
|
5804
|
+
const taskId = requireTaskId(taskDb, db, recordRef, 'atris task plan-preview');
|
|
5805
|
+
const note = taskDb.noteTask(db, {
|
|
5806
|
+
id: taskId,
|
|
5807
|
+
actor: publicPlan.owner || DEFAULT_OWNER,
|
|
5808
|
+
content: planTraceNote(publicPlan, ownerChoice),
|
|
5809
|
+
});
|
|
5810
|
+
if (!note.noted) failTask('atris task plan-preview', note.reason || 'note_failed', `plan-preview failed: ${note.reason || 'note_failed'}`, 1);
|
|
5811
|
+
const { outPath } = writeDefaultProjection(taskDb, db);
|
|
5812
|
+
recorded = {
|
|
5813
|
+
task_id: taskId,
|
|
5814
|
+
version: note.event.version,
|
|
5815
|
+
projection_path: outPath,
|
|
5816
|
+
};
|
|
5817
|
+
}
|
|
5818
|
+
if (wantsJson(args)) {
|
|
5819
|
+
printJson({
|
|
5820
|
+
ok: true,
|
|
5821
|
+
action: 'plan_preview',
|
|
5822
|
+
plan: publicPlan,
|
|
5823
|
+
owner_choice: {
|
|
5824
|
+
owner: ownerChoice.owner,
|
|
5825
|
+
source: ownerChoice.source,
|
|
5826
|
+
score: ownerChoice.score || null,
|
|
5827
|
+
member: ownerChoice.member ? {
|
|
5828
|
+
slug: ownerChoice.member.slug,
|
|
5829
|
+
role: ownerChoice.member.role,
|
|
5830
|
+
description: ownerChoice.member.description,
|
|
5831
|
+
} : null,
|
|
5832
|
+
},
|
|
5833
|
+
recorded,
|
|
5834
|
+
text: renderPublicPlan(publicPlan),
|
|
5835
|
+
});
|
|
5836
|
+
return;
|
|
5837
|
+
}
|
|
5838
|
+
console.log(renderPublicPlan(publicPlan));
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5841
|
+
function buildAutomaticPlanTrace(taskDb, task, { actor, actorExplicit = false, owner, goal, summary, firstMove, exit } = {}) {
|
|
5842
|
+
if (!task) return { trace: null, plan: null, ownerChoice: null, ownerForStage: owner || null };
|
|
5843
|
+
const metadata = task.metadata || {};
|
|
5844
|
+
const purpose = cleanPublicText(goal, 240) || taskPurpose(task);
|
|
5845
|
+
const claimedOwner = cleanPublicText(task.claimed_by, 80);
|
|
5846
|
+
const actorNamed = taskTextMentionsActor(actor, `${purpose} ${task.title || ''} ${task.tag || ''}`);
|
|
5847
|
+
const requestedActor = actorExplicit && (!isGenericPlanActor(actor) || actorNamed) ? actor : null;
|
|
5848
|
+
const requestedOwner = owner || claimedOwner || requestedActor || null;
|
|
5849
|
+
const ownerChoice = chooseTaskOwner({
|
|
5850
|
+
purpose,
|
|
5851
|
+
tag: task.tag,
|
|
5852
|
+
requestedOwner,
|
|
5853
|
+
root: taskDb.workspaceRoot(),
|
|
5854
|
+
});
|
|
5855
|
+
const publicPlan = buildPublicPlan({
|
|
5856
|
+
purpose,
|
|
5857
|
+
owner: ownerChoice.owner,
|
|
5858
|
+
ownerReason: ownerChoice.reason,
|
|
5859
|
+
plan: firstMove || summary || metadata.first_move || metadata.stage_summary || '',
|
|
5860
|
+
expected: exit || metadata.exit_condition || '',
|
|
5861
|
+
});
|
|
5862
|
+
return {
|
|
5863
|
+
trace: planTraceData(publicPlan, ownerChoice),
|
|
5864
|
+
plan: publicPlan,
|
|
5865
|
+
ownerChoice: {
|
|
5866
|
+
owner: ownerChoice.owner,
|
|
5867
|
+
source: ownerChoice.source,
|
|
5868
|
+
score: ownerChoice.score || null,
|
|
5869
|
+
},
|
|
5870
|
+
ownerForStage: publicPlan.owner || actor || DEFAULT_OWNER,
|
|
5871
|
+
};
|
|
5872
|
+
}
|
|
5873
|
+
|
|
5874
|
+
function buildAutomaticResultTrace(taskDb, db, taskId, { actor, proof, changed, checked, passed, failed, cost, saved, tryNext, status, files, commands } = {}) {
|
|
5875
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
5876
|
+
if (!task || taskHasTrace(task, 'TASK_RESULT_TRACE ')) return null;
|
|
5877
|
+
const fields = {
|
|
5878
|
+
actor,
|
|
5879
|
+
changed: cleanPublicText(changed, 320) || 'prepared the work for review',
|
|
5880
|
+
checked: cleanPublicText(checked, 320) || cleanPublicText(proof, 320),
|
|
5881
|
+
passed: cleanPublicText(passed, 240),
|
|
5882
|
+
failed: cleanPublicText(failed, 240),
|
|
5883
|
+
cost: cleanPublicText(cost, 80),
|
|
5884
|
+
saved: cleanPublicText(saved, RESULT_SAVED_TEXT_LIMIT) || 'task trace was updated',
|
|
5885
|
+
tryNext: cleanPublicText(tryNext, 240) || 'review the proof and try the changed work',
|
|
5886
|
+
status: cleanPublicText(status, 160) || 'ready for review',
|
|
5887
|
+
files,
|
|
5888
|
+
commands,
|
|
5889
|
+
};
|
|
5890
|
+
const result = buildPublicResult(task, fields);
|
|
5891
|
+
const savedPaths = appendResultOwnerLog(taskDb.workspaceRoot(), task, result);
|
|
5892
|
+
const trace = resultTraceData(result, { ...fields, savedPaths });
|
|
5893
|
+
return {
|
|
5894
|
+
result,
|
|
5895
|
+
trace,
|
|
5896
|
+
saved_paths: savedPaths,
|
|
5897
|
+
};
|
|
5898
|
+
}
|
|
5899
|
+
|
|
5900
|
+
function cmdResult(args) {
|
|
5901
|
+
const pos = positional(args);
|
|
5902
|
+
const id = pos[0];
|
|
5903
|
+
if (!id) failTask('atris task result', 'missing_id', 'id required');
|
|
5904
|
+
const fields = {
|
|
5905
|
+
purpose: textFlag(args, ['--purpose', '--goal', '--objective']),
|
|
5906
|
+
changed: textFlag(args, ['--changed', '--result', '--done']),
|
|
5907
|
+
checked: textFlag(args, ['--checked', '--check', '--verified']),
|
|
5908
|
+
passed: textFlag(args, ['--passed', '--pass']),
|
|
5909
|
+
failed: textFlag(args, ['--failed', '--fail']),
|
|
5910
|
+
cost: textFlag(args, ['--cost']),
|
|
5911
|
+
saved: textFlag(args, ['--saved', '--savings']),
|
|
5912
|
+
tryNext: textFlag(args, ['--try', '--try-next', '--handoff']),
|
|
5913
|
+
status: textFlag(args, ['--status']),
|
|
5914
|
+
files: textFlag(args, ['--files']),
|
|
5915
|
+
commands: textFlag(args, ['--commands', '--command']),
|
|
5916
|
+
};
|
|
5917
|
+
if (!fields.changed) failTask('atris task result', 'changed_required', '--changed required');
|
|
5918
|
+
if (!fields.checked) failTask('atris task result', 'checked_required', '--checked required');
|
|
5919
|
+
if (!fields.tryNext) failTask('atris task result', 'try_required', '--try required');
|
|
5920
|
+
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
5921
|
+
const taskDb = getTaskDb();
|
|
5922
|
+
const db = taskDb.open();
|
|
5923
|
+
const taskId = requireTaskId(taskDb, db, id, 'atris task result');
|
|
5924
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
5925
|
+
if (!task) failTask('atris task result', 'not_found', `task not found: ${id}`, 1);
|
|
5926
|
+
const result = buildPublicResult(task, { ...fields, actor });
|
|
5927
|
+
const savedPaths = appendResultOwnerLog(taskDb.workspaceRoot(), task, result);
|
|
5928
|
+
const note = taskDb.noteTask(db, {
|
|
5929
|
+
id: taskId,
|
|
5930
|
+
actor,
|
|
5931
|
+
content: resultTraceNote(result, { ...fields, savedPaths }),
|
|
5932
|
+
});
|
|
5933
|
+
if (!note.noted) failTask('atris task result', note.reason || 'note_failed', `result failed: ${note.reason || 'note_failed'}`, 1);
|
|
5934
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
5935
|
+
const text = renderPublicResult(result);
|
|
5936
|
+
if (wantsJson(args)) {
|
|
5937
|
+
printJson({
|
|
5938
|
+
ok: true,
|
|
5939
|
+
action: 'result',
|
|
5940
|
+
task_id: taskId,
|
|
5941
|
+
version: note.event.version,
|
|
5942
|
+
projection_path: outPath,
|
|
5943
|
+
result,
|
|
5944
|
+
saved_paths: savedPaths,
|
|
5945
|
+
text,
|
|
5946
|
+
task: compactTaskFromProjection(projection, taskId),
|
|
5947
|
+
});
|
|
5948
|
+
return;
|
|
5949
|
+
}
|
|
5950
|
+
console.log(text);
|
|
5951
|
+
}
|
|
5952
|
+
|
|
5323
5953
|
function taskPageGoal(task) {
|
|
5324
5954
|
const metadata = task && task.metadata || {};
|
|
5325
5955
|
const candidates = [
|
|
@@ -5764,16 +6394,25 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
5764
6394
|
let episode = null;
|
|
5765
6395
|
let xpProjection = null;
|
|
5766
6396
|
if (current === 'backlog') {
|
|
6397
|
+
const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
|
|
6398
|
+
actor,
|
|
6399
|
+
owner: actor,
|
|
6400
|
+
goal,
|
|
6401
|
+
summary,
|
|
6402
|
+
firstMove: String(options.firstMove || ''),
|
|
6403
|
+
exit: String(options.exit || ''),
|
|
6404
|
+
});
|
|
5767
6405
|
const planned = taskDb.stageTask(db, {
|
|
5768
6406
|
id: taskId,
|
|
5769
6407
|
actor,
|
|
5770
6408
|
stage: 'plan',
|
|
5771
6409
|
goal,
|
|
5772
6410
|
summary,
|
|
5773
|
-
owner: actor,
|
|
6411
|
+
owner: automaticPlan.ownerForStage || actor,
|
|
5774
6412
|
exit: String(options.exit || ''),
|
|
5775
6413
|
proofNeeded: String(options.proofNeeded || ''),
|
|
5776
6414
|
firstMove: String(options.firstMove || ''),
|
|
6415
|
+
planTrace: automaticPlan.trace,
|
|
5777
6416
|
});
|
|
5778
6417
|
if (!planned.staged) taskStepFailure('atris task step', planned, actionPage);
|
|
5779
6418
|
stepAction = 'planned';
|
|
@@ -5807,7 +6446,15 @@ function runTaskStep(taskDb, db, taskId, options = {}) {
|
|
|
5807
6446
|
}
|
|
5808
6447
|
const lesson = String(options.lesson || '');
|
|
5809
6448
|
const nextTask = String(options.nextTask || '');
|
|
5810
|
-
const
|
|
6449
|
+
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, { actor, proof });
|
|
6450
|
+
const ready = taskDb.readyTask(db, {
|
|
6451
|
+
id: taskId,
|
|
6452
|
+
actor,
|
|
6453
|
+
proof,
|
|
6454
|
+
lesson,
|
|
6455
|
+
nextTask,
|
|
6456
|
+
resultTrace: resultTrace && resultTrace.trace,
|
|
6457
|
+
});
|
|
5811
6458
|
if (!ready.ready) taskStepFailure('atris task step', ready, actionPage);
|
|
5812
6459
|
task = taskDetail(taskDb, db, taskId) || task;
|
|
5813
6460
|
stepAction = 'ready';
|
|
@@ -6343,15 +6990,33 @@ function cmdReady(args) {
|
|
|
6343
6990
|
const lesson = flag(args, '--lesson') || '';
|
|
6344
6991
|
const nextTaskInput = normalizeReviewNextTaskInput(typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '');
|
|
6345
6992
|
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
6993
|
+
const resultFields = {
|
|
6994
|
+
changed: textFlag(args, ['--changed', '--result', '--done']),
|
|
6995
|
+
checked: textFlag(args, ['--checked', '--check', '--verified']),
|
|
6996
|
+
passed: textFlag(args, ['--passed', '--pass']),
|
|
6997
|
+
failed: textFlag(args, ['--failed', '--fail']),
|
|
6998
|
+
cost: textFlag(args, ['--cost']),
|
|
6999
|
+
saved: textFlag(args, ['--saved', '--savings']),
|
|
7000
|
+
tryNext: textFlag(args, ['--try', '--try-next', '--handoff']),
|
|
7001
|
+
status: textFlag(args, ['--status']),
|
|
7002
|
+
files: textFlag(args, ['--files']),
|
|
7003
|
+
commands: textFlag(args, ['--commands', '--command']),
|
|
7004
|
+
};
|
|
6346
7005
|
const taskDb = getTaskDb();
|
|
6347
7006
|
const db = taskDb.open();
|
|
6348
7007
|
const taskId = requireTaskId(taskDb, db, id, 'atris task ready');
|
|
7008
|
+
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
7009
|
+
actor,
|
|
7010
|
+
proof: String(proof),
|
|
7011
|
+
...resultFields,
|
|
7012
|
+
});
|
|
6349
7013
|
const result = taskDb.readyTask(db, {
|
|
6350
7014
|
id: taskId,
|
|
6351
7015
|
actor,
|
|
6352
7016
|
proof: String(proof),
|
|
6353
7017
|
lesson: typeof lesson === 'string' ? lesson : '',
|
|
6354
7018
|
nextTask: nextTaskInput.nextTask,
|
|
7019
|
+
resultTrace: resultTrace && resultTrace.trace,
|
|
6355
7020
|
});
|
|
6356
7021
|
if (!result.ready) {
|
|
6357
7022
|
console.error(`ready failed: ${result.reason}`);
|
|
@@ -6397,6 +7062,7 @@ function cmdReady(args) {
|
|
|
6397
7062
|
review_pass_count: result.event.payload.review_pass_count,
|
|
6398
7063
|
agent_certified: agentCertified,
|
|
6399
7064
|
handoff,
|
|
7065
|
+
result_trace: resultTrace,
|
|
6400
7066
|
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
6401
7067
|
projection_path: outPath,
|
|
6402
7068
|
task: compactTaskFromProjection(projection, taskId),
|
|
@@ -6404,13 +7070,14 @@ function cmdReady(args) {
|
|
|
6404
7070
|
return;
|
|
6405
7071
|
}
|
|
6406
7072
|
console.log(`ready ${taskRef(compactTaskFromProjection(projection, taskId))} v${result.event.version} pending approval`);
|
|
7073
|
+
if (resultTrace) console.log('Result trace recorded.');
|
|
6407
7074
|
console.log(handoff.rule);
|
|
6408
7075
|
for (const hint of policyHints) {
|
|
6409
7076
|
console.log(`policy (${hint.id}): ${hint.hint}`);
|
|
6410
7077
|
}
|
|
6411
7078
|
}
|
|
6412
7079
|
|
|
6413
|
-
function cmdAccept(args) {
|
|
7080
|
+
async function cmdAccept(args) {
|
|
6414
7081
|
const pos = positional(args);
|
|
6415
7082
|
const id = pos[0];
|
|
6416
7083
|
if (!id) {
|
|
@@ -6490,6 +7157,7 @@ function cmdAccept(args) {
|
|
|
6490
7157
|
// Inform the gate, never block it: show what the receipts named in the proof
|
|
6491
7158
|
// actually say so the accepting human isn't trusting prose.
|
|
6492
7159
|
const evidence = extractReceiptEvidence(proof, projection.workspace_root || process.cwd());
|
|
7160
|
+
const publicSync = hasFlag(args, '--public') ? await publishAcceptAgentXp(args, actor) : null;
|
|
6493
7161
|
if (wantsJson(args)) {
|
|
6494
7162
|
printJson({
|
|
6495
7163
|
ok: true,
|
|
@@ -6499,21 +7167,21 @@ function cmdAccept(args) {
|
|
|
6499
7167
|
reward: reviewed.episode.reward.value,
|
|
6500
7168
|
episode: reviewed.episode,
|
|
6501
7169
|
evidence,
|
|
7170
|
+
public_sync: publicSync,
|
|
6502
7171
|
xp_projection: xpProjection,
|
|
6503
7172
|
projection_path: outPath,
|
|
6504
7173
|
task: compactTaskFromProjection(projection, taskId),
|
|
6505
7174
|
});
|
|
7175
|
+
if (publicSync && !publicSync.ok) process.exitCode = 1;
|
|
6506
7176
|
return;
|
|
6507
7177
|
}
|
|
6508
|
-
console.log(
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
evidence.missing.forEach((missingPath) => console.log(` receipt: ${missingPath} MISSING`));
|
|
6516
|
-
}
|
|
7178
|
+
console.log(renderAcceptLanding({
|
|
7179
|
+
task: taskDetail(taskDb, db, taskId) || compactTaskFromProjection(projection, taskId),
|
|
7180
|
+
proof,
|
|
7181
|
+
nextTask,
|
|
7182
|
+
publicSync,
|
|
7183
|
+
}));
|
|
7184
|
+
if (publicSync && !publicSync.ok) process.exitCode = 1;
|
|
6517
7185
|
}
|
|
6518
7186
|
|
|
6519
7187
|
function stampAutoAcceptMetadata(taskDb, db, taskId, actor, policy) {
|
|
@@ -7975,22 +8643,39 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
7975
8643
|
});
|
|
7976
8644
|
}
|
|
7977
8645
|
if (op === 'plan') {
|
|
8646
|
+
const actor = String(body.actor || DEFAULT_OWNER);
|
|
8647
|
+
const goal = String(body.goal || body.objective || '');
|
|
8648
|
+
const summary = String(body.summary || body.plan || '');
|
|
8649
|
+
const owner = String(body.owner || body.assignee || '');
|
|
8650
|
+
const exit = String(body.exit || body.exit_condition || '');
|
|
8651
|
+
const firstMove = String(body.first_move || body.firstMove || body.first || '');
|
|
8652
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
8653
|
+
const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
|
|
8654
|
+
actor,
|
|
8655
|
+
actorExplicit: Boolean(body.actor),
|
|
8656
|
+
owner,
|
|
8657
|
+
goal,
|
|
8658
|
+
summary,
|
|
8659
|
+
firstMove,
|
|
8660
|
+
exit,
|
|
8661
|
+
});
|
|
7978
8662
|
const result = taskDb.stageTask(db, {
|
|
7979
8663
|
id: taskId,
|
|
7980
|
-
actor
|
|
8664
|
+
actor,
|
|
7981
8665
|
stage: 'plan',
|
|
7982
|
-
goal
|
|
7983
|
-
summary
|
|
7984
|
-
owner:
|
|
7985
|
-
exit
|
|
8666
|
+
goal,
|
|
8667
|
+
summary,
|
|
8668
|
+
owner: automaticPlan.ownerForStage || owner,
|
|
8669
|
+
exit,
|
|
7986
8670
|
proofNeeded: String(body.proof_needed || body.proofNeeded || body.proof || body.verify || ''),
|
|
7987
|
-
firstMove
|
|
8671
|
+
firstMove,
|
|
7988
8672
|
nextButton: String(body.next_button || body.nextButton || ''),
|
|
7989
8673
|
confidence: body.confidence,
|
|
8674
|
+
planTrace: automaticPlan.trace,
|
|
7990
8675
|
});
|
|
7991
8676
|
if (!result.staged) return sendJson(res, 409, { ok: false, reason: result.reason, detail: stageErrorDetail('task plan', result.reason, result) });
|
|
7992
8677
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
7993
|
-
return sendJson(res, 200, { ok: true, action: 'planned', task_id: taskId, version: result.event.version, stage_packet: result.stage_packet, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
8678
|
+
return sendJson(res, 200, { ok: true, action: 'planned', task_id: taskId, version: result.event.version, plan_trace: automaticPlan.trace, stage_packet: result.stage_packet, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
7994
8679
|
}
|
|
7995
8680
|
if (op === 'do') {
|
|
7996
8681
|
const firstMove = String(body.first_move || body.firstMove || body.first || '').trim();
|
|
@@ -8097,12 +8782,28 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
8097
8782
|
const proofIssue = meaningfulTaskProofIssue(proof);
|
|
8098
8783
|
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
8099
8784
|
const nextTaskInput = normalizeReviewNextTaskInput(body.next);
|
|
8785
|
+
const actor = String(body.actor || DEFAULT_OWNER);
|
|
8786
|
+
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
8787
|
+
actor,
|
|
8788
|
+
proof,
|
|
8789
|
+
changed: body.changed || body.result || body.done,
|
|
8790
|
+
checked: body.checked || body.check || body.verified,
|
|
8791
|
+
passed: body.passed || body.pass,
|
|
8792
|
+
failed: body.failed || body.fail,
|
|
8793
|
+
cost: body.cost,
|
|
8794
|
+
saved: body.saved || body.savings,
|
|
8795
|
+
tryNext: body.try_next || body.tryNext || body.try || body.handoff,
|
|
8796
|
+
status: body.status,
|
|
8797
|
+
files: body.files,
|
|
8798
|
+
commands: body.commands || body.command,
|
|
8799
|
+
});
|
|
8100
8800
|
const result = taskDb.readyTask(db, {
|
|
8101
8801
|
id: taskId,
|
|
8102
|
-
actor
|
|
8802
|
+
actor,
|
|
8103
8803
|
proof,
|
|
8104
8804
|
lesson: String(body.lesson || ''),
|
|
8105
8805
|
nextTask: nextTaskInput.nextTask,
|
|
8806
|
+
resultTrace: resultTrace && resultTrace.trace,
|
|
8106
8807
|
});
|
|
8107
8808
|
if (!result.ready) return sendJson(res, 409, { ok: false, reason: result.reason });
|
|
8108
8809
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
@@ -8110,6 +8811,7 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
8110
8811
|
ok: true,
|
|
8111
8812
|
action: 'ready',
|
|
8112
8813
|
task_id: taskId,
|
|
8814
|
+
result_trace: resultTrace,
|
|
8113
8815
|
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
8114
8816
|
projection_path: outPath,
|
|
8115
8817
|
task: taskFromProjection(projection, taskId),
|
|
@@ -8300,6 +9002,10 @@ async function run(args) {
|
|
|
8300
9002
|
case 'continue':
|
|
8301
9003
|
return cmdContinueWork(rest);
|
|
8302
9004
|
case 'chat': return cmdChat(rest);
|
|
9005
|
+
case 'plan-preview':
|
|
9006
|
+
case 'preview-plan':
|
|
9007
|
+
case 'plan-card':
|
|
9008
|
+
return cmdPlanPreview(rest);
|
|
8303
9009
|
case 'note': return cmdNote(rest);
|
|
8304
9010
|
case 'say': return cmdNote(rest);
|
|
8305
9011
|
case 'show': return cmdShow(rest);
|
|
@@ -8309,6 +9015,7 @@ async function run(args) {
|
|
|
8309
9015
|
case 'chat-review':
|
|
8310
9016
|
return cmdReviewChat(rest);
|
|
8311
9017
|
case 'ready': return cmdReady(rest);
|
|
9018
|
+
case 'result': return cmdResult(rest);
|
|
8312
9019
|
case 'accept': return cmdAccept(rest);
|
|
8313
9020
|
case 'auto-accept-certified':
|
|
8314
9021
|
case 'auto-accept':
|