atris 3.38.0 → 3.41.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/AGENTS.md +25 -6
- package/atris/PERSONA.md +8 -4
- package/atris.md +7 -0
- package/ax +2 -1
- package/bin/atris.js +31 -6
- package/commands/agent-spawn.js +13 -11
- package/commands/autoland.js +28 -77
- package/commands/bench.js +10 -12
- package/commands/business.js +345 -0
- package/commands/chat-scan.js +5 -7
- package/commands/codex-goal.js +8 -10
- package/commands/computer.js +20 -0
- package/commands/console.js +19 -3
- package/commands/decide.js +166 -0
- package/commands/deck.js +1 -4
- package/commands/drill.js +14 -24
- package/commands/engine.js +196 -8
- package/commands/gm.js +8 -6
- package/commands/harvest.js +1 -4
- package/commands/init.js +23 -3
- package/commands/land.js +8 -14
- package/commands/launchpad.js +1 -14
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/member.js +558 -574
- package/commands/mission.js +456 -237
- package/commands/pack.js +2746 -164
- package/commands/play.js +6 -4
- package/commands/probe.js +2 -2
- package/commands/pulse.js +15 -16
- package/commands/release.js +10 -9
- package/commands/router.js +5 -4
- package/commands/site-deploy.js +885 -0
- package/commands/site.js +11 -2
- package/commands/slop.js +14 -2
- package/commands/stream.js +4 -18
- package/commands/task.js +880 -558
- package/commands/taste.js +101 -0
- package/commands/team.js +176 -3
- package/commands/vercel.js +4 -2
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -22
- package/commands/wiki.js +1 -4
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +1 -14
- package/commands/xp.js +27 -24
- package/lib/accept-verify-gate.js +5 -1
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +116 -1
- package/lib/autoland.js +66 -0
- package/lib/bench/runner.js +19 -1
- package/lib/context-gatherer.js +7 -1
- package/lib/engine-registry.js +141 -20
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +65 -15
- package/lib/git-spawn.js +15 -0
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +0 -2
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +4 -1
- package/lib/official-cli-integration.js +47 -2
- package/lib/orb-context.js +8 -1
- package/lib/pack-capabilities.js +685 -0
- package/lib/router-brain.js +51 -1
- package/lib/runner-command.js +0 -6
- package/lib/self-drive.js +44 -13
- package/lib/task-db.js +137 -3
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +66 -0
- package/lib/wish-audit.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/zip.js +95 -7
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/task.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const http = require('http');
|
|
8
|
+
const { execSync } = require('node:child_process');
|
|
8
9
|
const path = require('path');
|
|
9
10
|
const os = require('os');
|
|
11
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
10
12
|
const { taskProofState } = require('../lib/task-proof');
|
|
11
13
|
const {
|
|
12
14
|
evaluateAutoAccept,
|
|
@@ -21,7 +23,7 @@ const { evaluateAcceptVerify } = require('../lib/accept-verify-gate');
|
|
|
21
23
|
const { extractReceiptEvidence, RECEIPT_PATH_PATTERN } = require('../lib/receipt-evidence');
|
|
22
24
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
23
25
|
const reviewIntegrity = require('../lib/review-integrity');
|
|
24
|
-
const { gateForHuman, isRetiredFillerReason, landingWhyClause, numberWord } = require('../lib/voice-gate');
|
|
26
|
+
const { gateForHuman, isRetiredFillerReason, landingWhyClause, numberWord, plainLandingReason } = require('../lib/voice-gate');
|
|
25
27
|
const {
|
|
26
28
|
normalizeOwnerSlug,
|
|
27
29
|
resolveFunctionalOwner: resolveFunctionalTaskOwner,
|
|
@@ -35,6 +37,11 @@ const {
|
|
|
35
37
|
inspectTextLines,
|
|
36
38
|
buildInspectPayload,
|
|
37
39
|
} = require('../lib/inspect-fields');
|
|
40
|
+
const {
|
|
41
|
+
isDecisionTask,
|
|
42
|
+
decisionMarkerFor,
|
|
43
|
+
DECISION_REFUSE_REASON,
|
|
44
|
+
} = require('../lib/task-decision');
|
|
38
45
|
|
|
39
46
|
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
40
47
|
|| process.env.USER
|
|
@@ -129,7 +136,7 @@ function taskUsageText() {
|
|
|
129
136
|
return `
|
|
130
137
|
atris task - durable local task state (SQLite, gitignored)
|
|
131
138
|
|
|
132
|
-
golden path (
|
|
139
|
+
golden path (one tick, by cron or by hand):
|
|
133
140
|
atris task delegate "fix the login bug" --to <member>
|
|
134
141
|
atris task claim <id> --as <member>
|
|
135
142
|
... build ...
|
|
@@ -137,7 +144,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
137
144
|
atris autoland tick # second check runs, task lands
|
|
138
145
|
|
|
139
146
|
atris task Show the task desk
|
|
140
|
-
atris task new "<title>"
|
|
147
|
+
atris task new "<title>" [--verify <cmd>] Create a task; weak or missing checks are marked degraded
|
|
141
148
|
atris task next [--tag <tag>] [--create-next]
|
|
142
149
|
Claim/show next open task; optionally create the generated Endgame fallback
|
|
143
150
|
atris task continue-work <id> Create/reuse a certified Review follow-up task
|
|
@@ -169,8 +176,8 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
169
176
|
atris task audit [--limit <n>] [--revise] re-run stored verifies for newest accepted tasks; report-only unless --revise
|
|
170
177
|
atris task revise <id> --note "..." Send reviewed work back to Do
|
|
171
178
|
|
|
172
|
-
atris task add "<title>" [--tag <tag>] [--goal-id <id>] Create a task
|
|
173
|
-
atris task delegate "<title>" [--to <member>] [--executed-by <engine>] [--goal-id <id>] [--tag <tag>] Create assigned work
|
|
179
|
+
atris task add "<title>" [--tag <tag>] [--goal-id <id>] [--verify <cmd>] Create a task
|
|
180
|
+
atris task delegate "<title>" [--to <member>] [--executed-by <engine>] [--goal-id <id>] [--tag <tag>] [--verify <cmd>] Create assigned work
|
|
174
181
|
atris task plan <id> --goal "..." --exit "..." --proof-needed "..."
|
|
175
182
|
Record a task-owned Plan stage
|
|
176
183
|
atris task do <id> --as <owner> --first-move "..."
|
|
@@ -213,6 +220,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
213
220
|
Sweep off-roadmap/duplicate work as archived (not failed);
|
|
214
221
|
--from-failed opts in to relabel a fail-closed row (never done)
|
|
215
222
|
atris task clear-done [--before <days>] [--dry-run] [--json] Archive completed rows, oldest first
|
|
223
|
+
atris task reap-mission-blockers [--json] Close blocker rows whose missions are complete or stopped
|
|
216
224
|
atris task relabel-archived [--dry-run|--apply]
|
|
217
225
|
One-time OBL-1622 migration: relabel June-10 backlog-reset rows failed -> archived
|
|
218
226
|
atris task finish <id> --proof "..." Legacy alias for done with proof
|
|
@@ -270,10 +278,6 @@ function flag(args, name) {
|
|
|
270
278
|
return args[i + 1] || true;
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
function hasFlag(args, name) {
|
|
274
|
-
return args.indexOf(name) !== -1;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
281
|
function taskScopeEverywhere(args = [], options = {}) {
|
|
278
282
|
if (options.everywhere !== undefined) return Boolean(options.everywhere);
|
|
279
283
|
return hasFlag(args, '--everywhere');
|
|
@@ -5176,13 +5180,49 @@ function evaluateReviewAutoAccept(task, root) {
|
|
|
5176
5180
|
if (!protectedGate.ok) return { eligible: false, ref, reason: protectedGate.reason };
|
|
5177
5181
|
const sizeGate = reviewAutoAcceptSizeGate(task, root);
|
|
5178
5182
|
if (!sizeGate.ok) return { eligible: false, ref, reason: sizeGate.reason, size: sizeGate.stats };
|
|
5179
|
-
|
|
5183
|
+
// executeVerify: false — this is a READ path. The verdict comes from
|
|
5184
|
+
// metadata.verify_cache, stamped by the two lanes allowed to spawn
|
|
5185
|
+
// checks (certify-verified, autoland landing re-check). Spawning here is
|
|
5186
|
+
// what turned `atris task reviews` into a fork-bomb multiplier.
|
|
5187
|
+
const evaluation = evaluateAutoAccept(task, { strictVerify: true, executeVerify: false });
|
|
5180
5188
|
if (!evaluation.eligible) {
|
|
5181
5189
|
return {
|
|
5182
5190
|
...evaluation,
|
|
5183
5191
|
size: sizeGate.stats,
|
|
5184
5192
|
};
|
|
5185
5193
|
}
|
|
5194
|
+
if (evaluation.verification_pending) {
|
|
5195
|
+
const verdict = cachedVerifyVerdict(task, root);
|
|
5196
|
+
if (!verdict.fresh) {
|
|
5197
|
+
return {
|
|
5198
|
+
eligible: false,
|
|
5199
|
+
ref,
|
|
5200
|
+
reason: 'verification_pending',
|
|
5201
|
+
verify: verdict.command || null,
|
|
5202
|
+
next_action: 'no fresh executed verify on record; `atris task certify-verified` or the autoland tick runs the stored check and stamps metadata.verify_cache',
|
|
5203
|
+
size: sizeGate.stats,
|
|
5204
|
+
};
|
|
5205
|
+
}
|
|
5206
|
+
if (!verdict.ok) {
|
|
5207
|
+
return {
|
|
5208
|
+
eligible: false,
|
|
5209
|
+
ref,
|
|
5210
|
+
reason: verdict.reason || 'verify_failed',
|
|
5211
|
+
verify: verdict.command,
|
|
5212
|
+
verify_cache: verdict,
|
|
5213
|
+
size: sizeGate.stats,
|
|
5214
|
+
};
|
|
5215
|
+
}
|
|
5216
|
+
return {
|
|
5217
|
+
...evaluation,
|
|
5218
|
+
verification_pending: false,
|
|
5219
|
+
verify_cache: verdict,
|
|
5220
|
+
eligible: true,
|
|
5221
|
+
reason: 'certified_small',
|
|
5222
|
+
policy: REVIEW_AUTO_ACCEPT_POLICY,
|
|
5223
|
+
size: sizeGate.stats,
|
|
5224
|
+
};
|
|
5225
|
+
}
|
|
5186
5226
|
return {
|
|
5187
5227
|
...evaluation,
|
|
5188
5228
|
eligible: true,
|
|
@@ -5605,7 +5645,7 @@ function cmdReviews(args) {
|
|
|
5605
5645
|
if (item.accept_command) {
|
|
5606
5646
|
console.log(` say yes: atris task accept ${item.display_id || taskRef(item.id)}`);
|
|
5607
5647
|
} else if (item.blocked_accept_reason) {
|
|
5608
|
-
console.log(` approve: blocked
|
|
5648
|
+
console.log(` approve: blocked; ${plainLandingReason(item.blocked_accept_reason)}`);
|
|
5609
5649
|
console.log(` rework: ${item.revise_command}`);
|
|
5610
5650
|
}
|
|
5611
5651
|
});
|
|
@@ -5814,7 +5854,8 @@ function formatTaskLine(task) {
|
|
|
5814
5854
|
const owner = task.claimed_by ? ` @${task.claimed_by}` : '';
|
|
5815
5855
|
const assigned = !task.claimed_by && taskAssignee(task) ? ` -> ${taskAssignee(task)}` : '';
|
|
5816
5856
|
const tag = task.tag ? ` #${task.tag}` : '';
|
|
5817
|
-
|
|
5857
|
+
const decision = decisionMarkerFor(task) ? ` ${decisionMarkerFor(task)}` : '';
|
|
5858
|
+
return `${taskRef(task)}${owner}${assigned}${tag}${decision} ${task.title}`;
|
|
5818
5859
|
}
|
|
5819
5860
|
|
|
5820
5861
|
function cmdStatus(args) {
|
|
@@ -5909,7 +5950,8 @@ function renderTaskDesk(rows, refRows = rows) {
|
|
|
5909
5950
|
const owner = r.claimed_by ? ` @${r.claimed_by}` : '';
|
|
5910
5951
|
const assigned = !r.claimed_by && taskAssignee(r) ? ` -> ${taskAssignee(r)}` : '';
|
|
5911
5952
|
const tag = r.tag ? ` #${r.tag}` : '';
|
|
5912
|
-
|
|
5953
|
+
const decision = decisionMarkerFor(r) ? ` ${decisionMarkerFor(r)}` : '';
|
|
5954
|
+
console.log(`${r.status.padEnd(7)} ${taskRef(r)}${owner}${assigned}${tag}${decision}`);
|
|
5913
5955
|
console.log(` ${r.title}`);
|
|
5914
5956
|
}
|
|
5915
5957
|
if (active.length === 0) console.log('clear no active tasks');
|
|
@@ -5927,12 +5969,14 @@ function cmdAdd(args) {
|
|
|
5927
5969
|
const tag = flag(args, '--tag');
|
|
5928
5970
|
const goalId = flag(args, '--goal-id');
|
|
5929
5971
|
const goalObjective = flag(args, '--goal-objective') || flag(args, '--goal');
|
|
5972
|
+
const verify = flag(args, '--verify');
|
|
5930
5973
|
const metadata = {};
|
|
5931
5974
|
if (goalId && goalId !== true) metadata.goal_id = String(goalId);
|
|
5932
5975
|
if (goalObjective && goalObjective !== true) {
|
|
5933
5976
|
metadata.task_goal = String(goalObjective);
|
|
5934
5977
|
metadata.goal_objective = String(goalObjective);
|
|
5935
5978
|
}
|
|
5979
|
+
if (typeof verify === 'string' && verify.trim()) metadata.verify = verify.trim();
|
|
5936
5980
|
const taskDb = getTaskDb();
|
|
5937
5981
|
const db = taskDb.open();
|
|
5938
5982
|
const ws = taskDb.workspaceRoot();
|
|
@@ -5999,6 +6043,7 @@ function delegateTask(args, options = {}) {
|
|
|
5999
6043
|
const note = flag(args, '--note');
|
|
6000
6044
|
const goalId = flag(args, '--goal-id');
|
|
6001
6045
|
const goalObjective = flag(args, '--goal-objective') || flag(args, '--goal');
|
|
6046
|
+
const verify = flag(args, '--verify');
|
|
6002
6047
|
const claimNow = hasFlag(args, '--claim');
|
|
6003
6048
|
const requestedOwner = flag(args, '--to') || flag(args, '--as');
|
|
6004
6049
|
const explicitExecutedBy = flag(args, '--executed-by');
|
|
@@ -6036,6 +6081,7 @@ function delegateTask(args, options = {}) {
|
|
|
6036
6081
|
metadata.task_goal = String(goalObjective);
|
|
6037
6082
|
metadata.goal_objective = String(goalObjective);
|
|
6038
6083
|
}
|
|
6084
|
+
if (typeof verify === 'string' && verify.trim()) metadata.verify = verify.trim();
|
|
6039
6085
|
const result = taskDb.addTask(db, {
|
|
6040
6086
|
title,
|
|
6041
6087
|
tag: typeof tag === 'string' ? tag : null,
|
|
@@ -6191,7 +6237,8 @@ function cmdDay(args) {
|
|
|
6191
6237
|
for (const task of group.tasks) {
|
|
6192
6238
|
const tag = task.tag ? ` #${task.tag}` : '';
|
|
6193
6239
|
const claim = task.claimed_by ? ` @${task.claimed_by}` : '';
|
|
6194
|
-
|
|
6240
|
+
const decision = decisionMarkerFor(task) ? ` ${decisionMarkerFor(task)}` : '';
|
|
6241
|
+
console.log(` ${task.status.padEnd(7)} ${taskRef(task)}${claim}${tag}${decision} ${taskDayTitle(task.title)}`);
|
|
6195
6242
|
}
|
|
6196
6243
|
}
|
|
6197
6244
|
if (textView.hiddenTasks > 0) {
|
|
@@ -6260,7 +6307,8 @@ function cmdList(args) {
|
|
|
6260
6307
|
for (const r of displayRows) {
|
|
6261
6308
|
const claim = r.claimed_by ? ` [${r.claimed_by}]` : '';
|
|
6262
6309
|
const tag = r.tag ? ` #${r.tag}` : '';
|
|
6263
|
-
|
|
6310
|
+
const decision = decisionMarkerFor(r) ? ` ${decisionMarkerFor(r)}` : '';
|
|
6311
|
+
console.log(`${r.status.padEnd(8)} ${taskRef(r)}${claim}${tag}${decision}\t${r.title}`);
|
|
6264
6312
|
}
|
|
6265
6313
|
}
|
|
6266
6314
|
|
|
@@ -6290,7 +6338,11 @@ function guardExplicitActor(command, value) {
|
|
|
6290
6338
|
// instead of just reporting the failure, straight from the live projection
|
|
6291
6339
|
// (never the rendered file), preferring the same tag when one is open.
|
|
6292
6340
|
function suggestNextClaimableTask(projection, { excludeId = null, tag = '' } = {}) {
|
|
6293
|
-
|
|
6341
|
+
// Decision rows (needs-human / decision) belong to a human. Autonomous
|
|
6342
|
+
// recovery after already_done must not hand an agent a judgment call.
|
|
6343
|
+
const open = (projection && projection.tasks || []).filter((t) => (
|
|
6344
|
+
t && t.status === 'open' && t.id !== excludeId && !isDecisionTask(t)
|
|
6345
|
+
));
|
|
6294
6346
|
if (!open.length) return null;
|
|
6295
6347
|
const normalizedTag = String(tag || '').trim().toLowerCase();
|
|
6296
6348
|
if (normalizedTag) {
|
|
@@ -6540,13 +6592,31 @@ function cmdNext(args) {
|
|
|
6540
6592
|
const reviewTasks = filterTasksByScope(reviewProjection.projection.tasks || [], scope)
|
|
6541
6593
|
.map(compactTaskForStatus)
|
|
6542
6594
|
.filter(task => task && task.review && task.review.handoff);
|
|
6543
|
-
const
|
|
6595
|
+
const openAll = filterTasksByScope(taskDb.listTasks(db, {
|
|
6544
6596
|
workspaceRoot: taskDb.workspaceRoot(),
|
|
6545
6597
|
status: 'open',
|
|
6546
|
-
limit: scoped ? null :
|
|
6598
|
+
limit: scoped ? null : 50,
|
|
6547
6599
|
}), scope);
|
|
6600
|
+
const open = openAll.filter((task) => !isDecisionTask(task));
|
|
6601
|
+
const skippedDecisions = openAll.length - open.length;
|
|
6548
6602
|
if (!open.length) {
|
|
6549
6603
|
const { projection, outPath } = reviewProjection;
|
|
6604
|
+
if (skippedDecisions > 0 && !reviewTasks.length) {
|
|
6605
|
+
if (wantsJson(args)) {
|
|
6606
|
+
printJson({
|
|
6607
|
+
ok: false,
|
|
6608
|
+
action: 'refused',
|
|
6609
|
+
reason: DECISION_REFUSE_REASON,
|
|
6610
|
+
skipped_decision_count: skippedDecisions,
|
|
6611
|
+
owner: String(owner),
|
|
6612
|
+
scope: normalizeTaskQueueScope(scope),
|
|
6613
|
+
projection_path: outPath,
|
|
6614
|
+
});
|
|
6615
|
+
process.exit(1);
|
|
6616
|
+
}
|
|
6617
|
+
console.error(DECISION_REFUSE_REASON);
|
|
6618
|
+
process.exit(1);
|
|
6619
|
+
}
|
|
6550
6620
|
const reviewTask = reviewTasks.find(task => task.review.handoff.next_action === 'agent_review_again')
|
|
6551
6621
|
|| reviewTasks.find(task => task.review.handoff.next_action === 'continue_work')
|
|
6552
6622
|
|| reviewTasks.find(task => task.review.handoff.next_action === 'human_accept_waiting');
|
|
@@ -7197,6 +7267,12 @@ function cmdShow(args) {
|
|
|
7197
7267
|
: task.status.toUpperCase();
|
|
7198
7268
|
console.log(`${statusLabel} ${taskRef(task)} v${task.current_version}${owner}${tag}`);
|
|
7199
7269
|
console.log(task.title);
|
|
7270
|
+
if (task.metadata?.verification_status === 'degraded') {
|
|
7271
|
+
const reason = task.metadata.verification_degraded_reason === 'diff_only_verify'
|
|
7272
|
+
? 'diff-only verify command'
|
|
7273
|
+
: 'missing verify command';
|
|
7274
|
+
console.log(`verification: degraded (${reason})`);
|
|
7275
|
+
}
|
|
7200
7276
|
if (task.review) {
|
|
7201
7277
|
console.log('');
|
|
7202
7278
|
printReviewLanding(task.review);
|
|
@@ -9195,6 +9271,36 @@ function cmdClearDone(args) {
|
|
|
9195
9271
|
console.log(`cleared ${candidates.length} completed task(s).`);
|
|
9196
9272
|
}
|
|
9197
9273
|
|
|
9274
|
+
function cmdReapMissionBlockers(args) {
|
|
9275
|
+
const taskDb = getTaskDb();
|
|
9276
|
+
const db = taskDb.open();
|
|
9277
|
+
const workspaceRoot = taskDb.workspaceRoot();
|
|
9278
|
+
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
9279
|
+
const missions = require('./mission').listMissions(workspaceRoot);
|
|
9280
|
+
const result = taskDb.reapMissionBlockerTasks(db, { workspaceRoot, missions, actor });
|
|
9281
|
+
const { outPath } = writeDefaultProjection(taskDb, db);
|
|
9282
|
+
const taskById = new Map(taskDb.withTaskDisplayRefs(taskDb.listTasks(db, { workspaceRoot, limit: null }))
|
|
9283
|
+
.map(task => [task.id, task]));
|
|
9284
|
+
const closed = result.closed.map(row => ({
|
|
9285
|
+
...row,
|
|
9286
|
+
task_ref: taskRef(taskById.get(row.task_id)),
|
|
9287
|
+
}));
|
|
9288
|
+
if (wantsJson(args)) {
|
|
9289
|
+
printJson({
|
|
9290
|
+
ok: true,
|
|
9291
|
+
action: 'reaped_mission_blockers',
|
|
9292
|
+
closed_count: closed.length,
|
|
9293
|
+
closed,
|
|
9294
|
+
projection_path: outPath,
|
|
9295
|
+
});
|
|
9296
|
+
return;
|
|
9297
|
+
}
|
|
9298
|
+
console.log(`closed ${closed.length} mission blocker task(s).`);
|
|
9299
|
+
for (const row of closed) {
|
|
9300
|
+
console.log(`closed ${row.task_ref} because its mission is ${row.mission_status}.`);
|
|
9301
|
+
}
|
|
9302
|
+
}
|
|
9303
|
+
|
|
9198
9304
|
// One-time migration for OBL-1622: the 2026-06-10 "first-principles backlog
|
|
9199
9305
|
// reset" archived ~125 certified, proof-backed tasks by writing status
|
|
9200
9306
|
// 'failed' (no distinct archived status existed yet). This relabels exactly
|
|
@@ -9281,6 +9387,20 @@ function cmdReady(args) {
|
|
|
9281
9387
|
if (receipt.receiptPath) console.error(`receipt: ${receipt.receiptPath}`);
|
|
9282
9388
|
process.exit(1);
|
|
9283
9389
|
}
|
|
9390
|
+
// Exit 0 says the check passed. It does not say the check could have
|
|
9391
|
+
// failed. Run it again with none of the work present: a check anchored to
|
|
9392
|
+
// this codebase fails there, and one that passes anywhere passes there too.
|
|
9393
|
+
if (!hasFlag(args, '--no-falsify-check')) {
|
|
9394
|
+
const { probeVerifierCanFail } = require('../lib/falsifier-probe');
|
|
9395
|
+
const probe = probeVerifierCanFail({ command: verifyFlag });
|
|
9396
|
+
if (probe.probed && probe.canFail === false) {
|
|
9397
|
+
console.error(`atris task ready: this check cannot fail — ${probe.reason}`);
|
|
9398
|
+
console.error(` command: ${verifyFlag}`);
|
|
9399
|
+
console.error(' give a check that fails when the work is missing, or pass --no-falsify-check to accept anyway.');
|
|
9400
|
+
process.exit(1);
|
|
9401
|
+
}
|
|
9402
|
+
if (!probe.probed && !wantsJson(args)) console.log(` ${probe.reason}`);
|
|
9403
|
+
}
|
|
9284
9404
|
const base = proof.trim();
|
|
9285
9405
|
proof = `[verified] \`${verifyFlag}\` passed (exit 0)${base ? ` — ${base}` : ''}${receipt.output ? `\n${receipt.output}` : ''}\nReceipt: ${receipt.receiptPath}`;
|
|
9286
9406
|
if (!wantsJson(args)) console.log(`✓ verified: \`${verifyFlag}\` exited 0 (receipt ${receipt.receiptPath})`);
|
|
@@ -9353,7 +9473,10 @@ function cmdReady(args) {
|
|
|
9353
9473
|
nextTask: nextTaskInput.nextTask,
|
|
9354
9474
|
});
|
|
9355
9475
|
const reviewChat = taskReviewChatHandoff(verifierTask, { reviewer: 'codex-review' });
|
|
9356
|
-
const
|
|
9476
|
+
const autolandLib = require('../lib/autoland');
|
|
9477
|
+
const workspaceRoot = taskDb.workspaceRoot();
|
|
9478
|
+
const autolandOn = autolandLib.liveAcceptAuthorization(workspaceRoot).ok;
|
|
9479
|
+
const whenLands = autolandLib.whenAutolandLands(workspaceRoot);
|
|
9357
9480
|
const needsExternalVerifier = Boolean(usedVerify && !verifyAutoCertifyAllowed);
|
|
9358
9481
|
const handoff = {
|
|
9359
9482
|
native_goal_status: agentCertified ? 'agent_certified' : 'needs_second_agent_review',
|
|
@@ -9361,10 +9484,10 @@ function cmdReady(args) {
|
|
|
9361
9484
|
next_action: agentCertified ? certifiedReviewNextAction(nextTaskInput.nextTask) : 'agent_review_again',
|
|
9362
9485
|
rule: autolandOn
|
|
9363
9486
|
? (agentCertified
|
|
9364
|
-
?
|
|
9487
|
+
? `double-check complete; autoland will accept this ${whenLands}.`
|
|
9365
9488
|
: needsExternalVerifier
|
|
9366
9489
|
? 'proof is ready; this verifier needs a second agent review because autoland cannot rerun it.'
|
|
9367
|
-
:
|
|
9490
|
+
: `proof is ready; autoland runs the second check and lands it ${whenLands}.`)
|
|
9368
9491
|
: (agentCertified
|
|
9369
9492
|
? 'double-check complete; ready to keep moving. XP is awarded only after the human approves the task.'
|
|
9370
9493
|
: 'proof is ready; one more agent check before human approval. XP waits for the human.'),
|
|
@@ -9681,6 +9804,52 @@ function stampCertifyVerifyMetadata(taskDb, db, taskId, actor, verify) {
|
|
|
9681
9804
|
`).run(JSON.stringify(metadata), Date.now(), taskId);
|
|
9682
9805
|
}
|
|
9683
9806
|
|
|
9807
|
+
function currentHeadSha(workspaceRoot) {
|
|
9808
|
+
try {
|
|
9809
|
+
return execSync('git rev-parse HEAD', {
|
|
9810
|
+
cwd: workspaceRoot || process.cwd(),
|
|
9811
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
9812
|
+
encoding: 'utf8',
|
|
9813
|
+
}).trim();
|
|
9814
|
+
} catch {
|
|
9815
|
+
return null;
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
|
|
9819
|
+
// metadata.verify_cache is the read path's only verify verdict: `reviews`
|
|
9820
|
+
// must never spawn a check (that read-side execution fork-bombed the fleet
|
|
9821
|
+
// on 2026-07-29), so the two lanes that legitimately execute verifies —
|
|
9822
|
+
// certify-verified and the autoland landing re-check — persist the outcome
|
|
9823
|
+
// here for the read path to consult.
|
|
9824
|
+
function stampVerifyCacheMetadata(taskDb, db, taskId, { verify, result, actor, workspaceRoot }) {
|
|
9825
|
+
const row = taskDb.getTask(db, taskId);
|
|
9826
|
+
if (!row) return;
|
|
9827
|
+
const metadata = row.metadata && typeof row.metadata === 'object' ? { ...row.metadata } : {};
|
|
9828
|
+
metadata.verify_cache = {
|
|
9829
|
+
command: verify,
|
|
9830
|
+
ok: result && result.ok === true,
|
|
9831
|
+
reason: (result && result.reason) || null,
|
|
9832
|
+
ran_at: new Date().toISOString(),
|
|
9833
|
+
ran_by: actor,
|
|
9834
|
+
code_sha: currentHeadSha(workspaceRoot),
|
|
9835
|
+
};
|
|
9836
|
+
db.prepare(`
|
|
9837
|
+
UPDATE tasks
|
|
9838
|
+
SET metadata = ?,
|
|
9839
|
+
updated_at = ?
|
|
9840
|
+
WHERE id = ?
|
|
9841
|
+
`).run(JSON.stringify(metadata), Date.now(), taskId);
|
|
9842
|
+
}
|
|
9843
|
+
|
|
9844
|
+
function cachedVerifyVerdict(task, root) {
|
|
9845
|
+
const metadata = (task && task.metadata) || {};
|
|
9846
|
+
const cache = metadata.verify_cache;
|
|
9847
|
+
if (!cache || typeof cache !== 'object') return { fresh: false, command: metadata.verify || null };
|
|
9848
|
+
const fresh = cache.command === metadata.verify
|
|
9849
|
+
&& (!cache.code_sha || cache.code_sha === currentHeadSha(task.workspace_root || root));
|
|
9850
|
+
return { ...cache, fresh: Boolean(fresh) };
|
|
9851
|
+
}
|
|
9852
|
+
|
|
9684
9853
|
// `atris task ready --verify "<cmd>"` already runs the command live and
|
|
9685
9854
|
// gates on exit 0, but that alone leaves no machine re-runnable trace once
|
|
9686
9855
|
// the proof text scrolls out of easy reach: storing the exact command on
|
|
@@ -9722,6 +9891,7 @@ function reverifyBeforeLanding(taskDb, db, task, { actor = 'autoland-verifier',
|
|
|
9722
9891
|
};
|
|
9723
9892
|
}
|
|
9724
9893
|
const result = runVerifyCommandCached(verify, task.workspace_root || process.cwd(), verifyCache);
|
|
9894
|
+
stampVerifyCacheMetadata(taskDb, db, task.id, { verify, result, actor, workspaceRoot: task.workspace_root });
|
|
9725
9895
|
if (result.ok) return { ok: true, verify, result };
|
|
9726
9896
|
const note = landingVerifyFailureNote(verify, result);
|
|
9727
9897
|
const revised = taskDb.reviseTask(db, {
|
|
@@ -9791,8 +9961,10 @@ function cmdCertifyVerified(args, options = {}) {
|
|
|
9791
9961
|
// "certified" alone is not landable.
|
|
9792
9962
|
// strictVerify stays off in this eligibility probe: certify-verified runs
|
|
9793
9963
|
// the check itself right below, and strict mode here would execute it a
|
|
9794
|
-
// second time per row before the real run.
|
|
9795
|
-
|
|
9964
|
+
// second time per row before the real run. executeVerify: false as well —
|
|
9965
|
+
// an untrusted trust tier re-enables the strict block internally even
|
|
9966
|
+
// with strictVerify off, which is how this probe spawned a verify per row.
|
|
9967
|
+
const evaluation = evaluateAutoAccept(task, { strictVerify: false, executeVerify: false });
|
|
9796
9968
|
if (evaluation.eligible) {
|
|
9797
9969
|
results.push({ ref, action: 'skipped', reason: 'already_landable' });
|
|
9798
9970
|
continue;
|
|
@@ -9821,6 +9993,7 @@ function cmdCertifyVerified(args, options = {}) {
|
|
|
9821
9993
|
continue;
|
|
9822
9994
|
}
|
|
9823
9995
|
const run = runVerifyCommandCached(verify, task.workspace_root || process.cwd(), verifyCache);
|
|
9996
|
+
stampVerifyCacheMetadata(taskDb, db, task.id, { verify, result: run, actor, workspaceRoot: task.workspace_root });
|
|
9824
9997
|
if (!run.ok) {
|
|
9825
9998
|
results.push({ ref, action: 'verify_failed', reason: run.reason, verify });
|
|
9826
9999
|
continue;
|
|
@@ -10109,7 +10282,7 @@ function autoAcceptSweepDeniedReason(task) {
|
|
|
10109
10282
|
for (const value of autoAcceptSweepLabelValues(task)) {
|
|
10110
10283
|
const tokens = autoAcceptSweepNormalizedTokens(value);
|
|
10111
10284
|
for (const token of tokens) {
|
|
10112
|
-
if (token === 'needs-human' || token === 'needshuman') {
|
|
10285
|
+
if (token === 'needs-human' || token === 'needshuman' || token === 'decision') {
|
|
10113
10286
|
return 'needs_human';
|
|
10114
10287
|
}
|
|
10115
10288
|
const protectedLane = [...SWEEP_AUTO_ACCEPT_PROTECTED].find((denied) =>
|
|
@@ -11137,7 +11310,45 @@ function taskHasReview(task) {
|
|
|
11137
11310
|
return review.reward != null || Boolean(review.proof || review.lesson || review.next_task);
|
|
11138
11311
|
}
|
|
11139
11312
|
|
|
11140
|
-
|
|
11313
|
+
const TASK_BOARD_COLUMNS = [
|
|
11314
|
+
['backlog', 'Backlog'],
|
|
11315
|
+
['open', 'Open'],
|
|
11316
|
+
['doing', 'Doing'],
|
|
11317
|
+
['review', 'Review'],
|
|
11318
|
+
['blocked', 'Blocked'],
|
|
11319
|
+
['done', 'Done'],
|
|
11320
|
+
];
|
|
11321
|
+
|
|
11322
|
+
// Pure state shaping: projection in, board view model out. No markup here.
|
|
11323
|
+
function taskBoardViewModel(projection = {}) {
|
|
11324
|
+
const tasks = Array.isArray(projection.tasks) ? projection.tasks : [];
|
|
11325
|
+
const rows = tasks.map((task) => ({
|
|
11326
|
+
id: task.id,
|
|
11327
|
+
task,
|
|
11328
|
+
column: taskColumn(task),
|
|
11329
|
+
decision: taskHasReview(task),
|
|
11330
|
+
}));
|
|
11331
|
+
const columns = TASK_BOARD_COLUMNS.map(([key, label]) => ({
|
|
11332
|
+
key,
|
|
11333
|
+
label,
|
|
11334
|
+
rows: rows.filter((row) => row.column === key),
|
|
11335
|
+
}));
|
|
11336
|
+
const counts = {};
|
|
11337
|
+
for (const column of columns) counts[column.key] = column.rows.length;
|
|
11338
|
+
return {
|
|
11339
|
+
columns,
|
|
11340
|
+
counts,
|
|
11341
|
+
rows,
|
|
11342
|
+
total: rows.length,
|
|
11343
|
+
planTags: Array.from(STATUS_PLAN_TAGS),
|
|
11344
|
+
};
|
|
11345
|
+
}
|
|
11346
|
+
|
|
11347
|
+
// Markup only: view model in, board page out. The page hydrates itself from
|
|
11348
|
+
// /api/tasks in the browser, so the template bakes in just the shared
|
|
11349
|
+
// plan-tag list; the column keys and labels must stay in step with
|
|
11350
|
+
// TASK_BOARD_COLUMNS above.
|
|
11351
|
+
function taskBoardTemplate(model) {
|
|
11141
11352
|
return `<!doctype html>
|
|
11142
11353
|
<html lang="en">
|
|
11143
11354
|
<head>
|
|
@@ -11296,7 +11507,7 @@ function taskBoardHtml() {
|
|
|
11296
11507
|
['blocked', 'Blocked'],
|
|
11297
11508
|
['done', 'Done']
|
|
11298
11509
|
];
|
|
11299
|
-
const planTags = new Set(${JSON.stringify(
|
|
11510
|
+
const planTags = new Set(${JSON.stringify(model.planTags)});
|
|
11300
11511
|
let state = { tasks: [] };
|
|
11301
11512
|
let selected = null;
|
|
11302
11513
|
const $ = (id) => document.getElementById(id);
|
|
@@ -11569,6 +11780,10 @@ function taskBoardHtml() {
|
|
|
11569
11780
|
</html>`;
|
|
11570
11781
|
}
|
|
11571
11782
|
|
|
11783
|
+
function taskBoardHtml() {
|
|
11784
|
+
return taskBoardTemplate(taskBoardViewModel());
|
|
11785
|
+
}
|
|
11786
|
+
|
|
11572
11787
|
function readJsonBody(req) {
|
|
11573
11788
|
return new Promise((resolve, reject) => {
|
|
11574
11789
|
let body = '';
|
|
@@ -11587,10 +11802,11 @@ function readJsonBody(req) {
|
|
|
11587
11802
|
});
|
|
11588
11803
|
}
|
|
11589
11804
|
|
|
11590
|
-
function sendJson(res, status, value) {
|
|
11805
|
+
function sendJson(res, status, value, headers = {}) {
|
|
11591
11806
|
res.writeHead(status, {
|
|
11592
11807
|
'content-type': 'application/json; charset=utf-8',
|
|
11593
11808
|
'access-control-allow-origin': 'http://localhost',
|
|
11809
|
+
...headers,
|
|
11594
11810
|
});
|
|
11595
11811
|
res.end(JSON.stringify(value, null, 2));
|
|
11596
11812
|
}
|
|
@@ -11600,551 +11816,655 @@ function sendHtml(res, value) {
|
|
|
11600
11816
|
res.end(value);
|
|
11601
11817
|
}
|
|
11602
11818
|
|
|
11603
|
-
|
|
11604
|
-
const
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11667
|
-
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
11678
|
-
|
|
11679
|
-
|
|
11680
|
-
|
|
11681
|
-
|
|
11682
|
-
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
|
|
11686
|
-
|
|
11687
|
-
|
|
11688
|
-
|
|
11689
|
-
|
|
11690
|
-
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11819
|
+
function taskApiQueueOptions(url) {
|
|
11820
|
+
const limitParam = url.searchParams.get('limit');
|
|
11821
|
+
const limit = limitParam ? Number(limitParam) : 8;
|
|
11822
|
+
return {
|
|
11823
|
+
owner: url.searchParams.get('owner') || url.searchParams.get('as') || DEFAULT_OWNER,
|
|
11824
|
+
reviewer: url.searchParams.get('reviewer') || url.searchParams.get('as_reviewer') || 'codex-review',
|
|
11825
|
+
all: url.searchParams.get('all') === '1' || url.searchParams.get('all') === 'true',
|
|
11826
|
+
everywhere: url.searchParams.get('everywhere') === '1' || url.searchParams.get('everywhere') === 'true',
|
|
11827
|
+
limit: Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 8,
|
|
11828
|
+
scope: taskQueueScopeFromSearchParams(url.searchParams),
|
|
11829
|
+
};
|
|
11830
|
+
}
|
|
11831
|
+
|
|
11832
|
+
function serveTaskApiOptions({ res }) {
|
|
11833
|
+
return sendJson(res, 200, { ok: true });
|
|
11834
|
+
}
|
|
11835
|
+
|
|
11836
|
+
function serveTaskBoardPage({ res }) {
|
|
11837
|
+
return sendHtml(res, taskBoardHtml());
|
|
11838
|
+
}
|
|
11839
|
+
|
|
11840
|
+
function serveTaskList({ res, taskDb, db }) {
|
|
11841
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11842
|
+
return sendJson(res, 200, { ok: true, projection_path: outPath, projection });
|
|
11843
|
+
}
|
|
11844
|
+
|
|
11845
|
+
function serveTaskActivityStream({ res, taskDb, db }) {
|
|
11846
|
+
// Time-ordered feed of what the agent actually did + heartbeat liveness.
|
|
11847
|
+
const { projection } = writeDefaultProjection(taskDb, db);
|
|
11848
|
+
const root = projection.workspace_root || process.cwd();
|
|
11849
|
+
const stateDir = path.join(root, '.atris', 'state');
|
|
11850
|
+
const activity = require('../lib/activity-stream');
|
|
11851
|
+
const { readJsonl } = require('../lib/pulse');
|
|
11852
|
+
const readStateRows = (file) => readJsonl(path.join(stateDir, file));
|
|
11853
|
+
const pulseReceipts = readStateRows('pulse_agi_loop_receipts.jsonl');
|
|
11854
|
+
const events = activity.buildActivityStream({
|
|
11855
|
+
pulseReceipts,
|
|
11856
|
+
scorecards: readStateRows('scorecards.jsonl'),
|
|
11857
|
+
taskEpisodes: readStateRows('task_episodes.jsonl').slice(-200),
|
|
11858
|
+
xpReceipts: readStateRows('career_xp_receipts.jsonl').slice(-200),
|
|
11859
|
+
missionEvents: readStateRows('mission_events.jsonl').slice(-200),
|
|
11860
|
+
}, { limit: 60 });
|
|
11861
|
+
return sendJson(res, 200, {
|
|
11862
|
+
ok: true,
|
|
11863
|
+
heartbeat: activity.buildHeartbeat(pulseReceipts),
|
|
11864
|
+
events,
|
|
11865
|
+
}, {
|
|
11866
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
11867
|
+
'cache-control': 'no-cache',
|
|
11868
|
+
connection: 'keep-alive',
|
|
11869
|
+
});
|
|
11870
|
+
}
|
|
11871
|
+
|
|
11872
|
+
function serveTaskCapabilities({ res }) {
|
|
11873
|
+
return sendJson(res, 200, {
|
|
11874
|
+
ok: true,
|
|
11875
|
+
action: 'capabilities',
|
|
11876
|
+
capabilities: taskCapabilitiesContract(),
|
|
11877
|
+
safety: {
|
|
11878
|
+
read_only: true,
|
|
11879
|
+
claims_work: false,
|
|
11880
|
+
human_accept: false,
|
|
11881
|
+
xp_after_human_accept: true,
|
|
11882
|
+
},
|
|
11883
|
+
});
|
|
11884
|
+
}
|
|
11885
|
+
|
|
11886
|
+
function serveTaskCapabilitiesCheck({ res, taskDb, db, url }) {
|
|
11887
|
+
const report = taskCapabilitiesCheckReport(taskDb, db, [], taskApiQueueOptions(url));
|
|
11888
|
+
return sendJson(res, report.ok ? 200 : 409, report);
|
|
11889
|
+
}
|
|
11890
|
+
|
|
11891
|
+
function serveTaskReviewLaneDrain({ res, taskDb, db, url }) {
|
|
11892
|
+
const report = taskReviewLaneDrainReport(taskDb, db, [], taskApiQueueOptions(url));
|
|
11893
|
+
return sendJson(res, report.ok ? 200 : 409, report);
|
|
11894
|
+
}
|
|
11895
|
+
|
|
11896
|
+
async function postTaskReviewLaneAct({ req, res, taskDb, db, url }) {
|
|
11897
|
+
const body = await readJsonBody(req);
|
|
11898
|
+
const result = taskReviewLaneAct(taskDb, db, taskReviewLaneActOptionsFromBody(body, url.searchParams));
|
|
11899
|
+
return sendJson(res, result.ok ? 200 : result.status || 409, result);
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11902
|
+
async function postTaskReviewLaneLoop({ req, res, taskDb, db, url }) {
|
|
11903
|
+
const body = await readJsonBody(req);
|
|
11904
|
+
const result = taskReviewLaneLoop(taskDb, db, taskReviewLaneLoopOptionsFromBody(body, url.searchParams));
|
|
11905
|
+
return sendJson(res, result.ok ? 200 : result.status || 409, result);
|
|
11906
|
+
}
|
|
11907
|
+
|
|
11908
|
+
async function postTaskReviewLaneRun({ req, res, taskDb, db, url }) {
|
|
11909
|
+
const body = await readJsonBody(req);
|
|
11910
|
+
const result = taskReviewLaneRun(taskDb, db, taskReviewLaneRunOptionsFromBody(body, url.searchParams));
|
|
11911
|
+
return sendJson(res, result.ok ? 200 : result.status || 409, result);
|
|
11912
|
+
}
|
|
11913
|
+
|
|
11914
|
+
function serveTaskCurrent({ res, taskDb, db, url }) {
|
|
11915
|
+
const { outPath, current } = buildTaskCurrent(taskDb, db, [], taskApiQueueOptions(url));
|
|
11916
|
+
const action = url.pathname.endsWith('/queue') ? 'queue' : 'current';
|
|
11917
|
+
return sendJson(res, 200, {
|
|
11918
|
+
ok: true,
|
|
11919
|
+
action,
|
|
11920
|
+
projection_path: outPath,
|
|
11921
|
+
current,
|
|
11922
|
+
selected: current.selected,
|
|
11923
|
+
page: current.page,
|
|
11924
|
+
queue: current.queue,
|
|
11925
|
+
});
|
|
11926
|
+
}
|
|
11927
|
+
|
|
11928
|
+
async function postCurrentTaskStep({ req, res, taskDb, db, url }) {
|
|
11929
|
+
const body = await readJsonBody(req);
|
|
11930
|
+
const options = taskCurrentStepOptionsFromBody(body, url.searchParams);
|
|
11931
|
+
try {
|
|
11932
|
+
const result = runCurrentTaskStep(taskDb, db, options);
|
|
11933
|
+
return sendJson(res, 200, result);
|
|
11934
|
+
} catch (error) {
|
|
11935
|
+
const errorCurrent = error.current || null;
|
|
11936
|
+
return sendJson(res, error.status || 409, {
|
|
11937
|
+
ok: false,
|
|
11938
|
+
action: 'current_step',
|
|
11939
|
+
reason: error.reason || 'step_failed',
|
|
11940
|
+
detail: error.message,
|
|
11941
|
+
selected_task_id: errorCurrent ? errorCurrent.selected_task_id : null,
|
|
11942
|
+
selected_ref: errorCurrent ? errorCurrent.selected_ref : null,
|
|
11943
|
+
selected_next_key: selectedNextKeyFromCurrent(errorCurrent),
|
|
11944
|
+
current: errorCurrent,
|
|
11945
|
+
page: error.page || null,
|
|
11712
11946
|
});
|
|
11713
11947
|
}
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11948
|
+
}
|
|
11949
|
+
|
|
11950
|
+
async function postTaskCreate({ req, res, taskDb, db }) {
|
|
11951
|
+
const body = await readJsonBody(req);
|
|
11952
|
+
const title = String(body.title || '').trim();
|
|
11953
|
+
if (!title) return sendJson(res, 400, { ok: false, reason: 'missing_title', detail: 'title required' });
|
|
11954
|
+
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
|
|
11955
|
+
const result = taskDb.addTask(db, {
|
|
11956
|
+
title,
|
|
11957
|
+
tag: body.tag ? String(body.tag) : 'tasks',
|
|
11958
|
+
workspaceRoot: taskDb.workspaceRoot(),
|
|
11959
|
+
metadata: body.verify ? { verify: String(body.verify).trim() } : null,
|
|
11960
|
+
});
|
|
11961
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11962
|
+
return sendJson(res, 200, {
|
|
11963
|
+
ok: true,
|
|
11964
|
+
action: 'created',
|
|
11965
|
+
task_id: result.id,
|
|
11966
|
+
operator_title_warning: operatorTitleWarning,
|
|
11967
|
+
projection_path: outPath,
|
|
11968
|
+
task: taskFromProjection(projection, result.id),
|
|
11969
|
+
});
|
|
11970
|
+
}
|
|
11971
|
+
|
|
11972
|
+
async function postTaskClearPlan({ req, res, taskDb, db }) {
|
|
11973
|
+
const body = await readJsonBody(req);
|
|
11974
|
+
if (!body.confirm && !body.yes) {
|
|
11975
|
+
return sendJson(res, 400, { ok: false, reason: 'confirm_required', detail: stageErrorDetail('task clear-plan', 'confirm_required') });
|
|
11734
11976
|
}
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11977
|
+
const result = taskDb.clearPlanTasks(db, {
|
|
11978
|
+
workspaceRoot: taskDb.workspaceRoot(),
|
|
11979
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
11980
|
+
reason: String(body.reason || body.note || 'clear_plan'),
|
|
11981
|
+
tag: String(body.tag || 'capture'),
|
|
11982
|
+
});
|
|
11983
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11984
|
+
const taskById = new Map((projection.tasks || []).map(task => [task.id, task]));
|
|
11985
|
+
return sendJson(res, 200, {
|
|
11986
|
+
ok: true,
|
|
11987
|
+
action: 'clear_plan',
|
|
11988
|
+
cleared_count: result.cleared.length,
|
|
11989
|
+
skipped_count: result.skipped.length,
|
|
11990
|
+
skipped: result.skipped,
|
|
11991
|
+
projection_path: outPath,
|
|
11992
|
+
tasks: result.cleared.map(task => taskFromProjection(projection, task.id) || taskById.get(task.id)).filter(Boolean),
|
|
11993
|
+
});
|
|
11994
|
+
}
|
|
11995
|
+
|
|
11996
|
+
const TASK_API_ROUTES = [
|
|
11997
|
+
{ method: 'OPTIONS', pathname: null, serve: serveTaskApiOptions },
|
|
11998
|
+
{ method: 'GET', pathname: '/', serve: serveTaskBoardPage },
|
|
11999
|
+
{ method: 'GET', pathname: '/api/tasks', serve: serveTaskList },
|
|
12000
|
+
{ method: 'GET', pathname: '/api/stream', serve: serveTaskActivityStream },
|
|
12001
|
+
{ method: 'GET', pathname: '/api/tasks/capabilities', serve: serveTaskCapabilities },
|
|
12002
|
+
{ method: 'GET', pathname: '/api/tasks/capabilities/check', serve: serveTaskCapabilitiesCheck },
|
|
12003
|
+
{ method: 'GET', pathname: '/api/tasks/review-lane-drain', serve: serveTaskReviewLaneDrain },
|
|
12004
|
+
{ method: 'POST', pathname: '/api/tasks/review-lane-act', serve: postTaskReviewLaneAct },
|
|
12005
|
+
{ method: 'POST', pathname: '/api/tasks/review-lane-loop', serve: postTaskReviewLaneLoop },
|
|
12006
|
+
{ method: 'POST', pathname: '/api/tasks/review-lane-run', serve: postTaskReviewLaneRun },
|
|
12007
|
+
{ method: 'GET', pathname: '/api/tasks/current', serve: serveTaskCurrent },
|
|
12008
|
+
{ method: 'GET', pathname: '/api/tasks/queue', serve: serveTaskCurrent },
|
|
12009
|
+
{ method: 'POST', pathname: '/api/tasks/current/step', serve: postCurrentTaskStep },
|
|
12010
|
+
{ method: 'POST', pathname: '/api/tasks', serve: postTaskCreate },
|
|
12011
|
+
{ method: 'POST', pathname: '/api/tasks/clear-plan', serve: postTaskClearPlan },
|
|
12012
|
+
{ pattern: /^\/api\/tasks\/([^/]+)$/, serve: serveTaskDetail },
|
|
12013
|
+
{ pattern: /^\/api\/tasks\/([^/]+)\/page$/, serve: serveTaskPage },
|
|
12014
|
+
{ pattern: /^\/api\/tasks\/([^/]+)\/(claim|message|chat|step|plan|do|backlog|ready|accept|revise|finish|review|review-chat|continue-work|events)$/, serve: serveTaskOperation },
|
|
12015
|
+
];
|
|
12016
|
+
|
|
12017
|
+
function serveTaskDetail({ req, res, taskDb, db, match }) {
|
|
12018
|
+
if (req.method !== 'GET') return sendJson(res, 405, { ok: false, reason: 'method_not_allowed' });
|
|
12019
|
+
const resolved = resolveTaskRef(taskDb, db, match[1]);
|
|
12020
|
+
if (!resolved.ok) return sendJson(res, resolved.reason === 'ambiguous' ? 409 : 404, { ok: false, reason: resolved.reason });
|
|
12021
|
+
const task = taskDetail(taskDb, db, resolved.id);
|
|
12022
|
+
if (!task) return sendJson(res, 404, { ok: false, reason: 'not_found' });
|
|
12023
|
+
const { outPath } = writeDefaultProjection(taskDb, db);
|
|
12024
|
+
return sendJson(res, 200, { ok: true, action: 'detail', task_id: resolved.id, projection_path: outPath, task, page: taskPageContract(task) });
|
|
12025
|
+
}
|
|
12026
|
+
|
|
12027
|
+
function serveTaskPage({ req, res, taskDb, db, match }) {
|
|
12028
|
+
if (req.method !== 'GET') return sendJson(res, 405, { ok: false, reason: 'method_not_allowed' });
|
|
12029
|
+
const resolved = resolveTaskRef(taskDb, db, match[1]);
|
|
12030
|
+
if (!resolved.ok) return sendJson(res, resolved.reason === 'ambiguous' ? 409 : 404, { ok: false, reason: resolved.reason });
|
|
12031
|
+
const task = taskDetail(taskDb, db, resolved.id);
|
|
12032
|
+
if (!task) return sendJson(res, 404, { ok: false, reason: 'not_found' });
|
|
12033
|
+
const { outPath } = writeDefaultProjection(taskDb, db);
|
|
12034
|
+
return sendJson(res, 200, { ok: true, action: 'page', task_id: resolved.id, projection_path: outPath, page: taskPageContract(task) });
|
|
12035
|
+
}
|
|
12036
|
+
|
|
12037
|
+
function postTaskApiStep({ res, taskDb, db, taskId, body }) {
|
|
12038
|
+
try {
|
|
12039
|
+
const result = runTaskStep(taskDb, db, taskId, taskStepOptionsFromBody(body));
|
|
12040
|
+
return sendJson(res, 200, result);
|
|
12041
|
+
} catch (error) {
|
|
12042
|
+
return sendJson(res, error.status || 409, {
|
|
12043
|
+
ok: false,
|
|
12044
|
+
action: 'step',
|
|
12045
|
+
task_id: taskId,
|
|
12046
|
+
reason: error.reason || 'step_failed',
|
|
12047
|
+
detail: error.message,
|
|
12048
|
+
page: error.page || null,
|
|
11744
12049
|
});
|
|
11745
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11746
|
-
return sendJson(res, 200, { ok: true, action: 'created', task_id: result.id, operator_title_warning: operatorTitleWarning, projection_path: outPath, task: taskFromProjection(projection, result.id) });
|
|
11747
12050
|
}
|
|
11748
|
-
|
|
11749
|
-
|
|
11750
|
-
|
|
11751
|
-
|
|
11752
|
-
}
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11757
|
-
|
|
11758
|
-
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
return sendJson(res, 200, {
|
|
11762
|
-
ok: true,
|
|
11763
|
-
action: 'clear_plan',
|
|
11764
|
-
cleared_count: result.cleared.length,
|
|
11765
|
-
skipped_count: result.skipped.length,
|
|
11766
|
-
skipped: result.skipped,
|
|
11767
|
-
projection_path: outPath,
|
|
11768
|
-
tasks: result.cleared.map(task => taskFromProjection(projection, task.id) || taskById.get(task.id)).filter(Boolean),
|
|
12051
|
+
}
|
|
12052
|
+
|
|
12053
|
+
function postTaskApiContinueWork({ res, taskDb, db, taskId, body }) {
|
|
12054
|
+
try {
|
|
12055
|
+
const result = continueWorkForReviewTask(taskDb, db, taskId, { owner: body.owner || body.actor || DEFAULT_OWNER });
|
|
12056
|
+
return sendJson(res, 200, result);
|
|
12057
|
+
} catch (error) {
|
|
12058
|
+
return sendJson(res, error.status || 409, {
|
|
12059
|
+
ok: false,
|
|
12060
|
+
action: 'continue_work',
|
|
12061
|
+
task_id: taskId,
|
|
12062
|
+
reason: error.reason || 'continue_work_failed',
|
|
12063
|
+
detail: error.message,
|
|
11769
12064
|
});
|
|
11770
12065
|
}
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
return sendJson(res, 200, { ok: true, events });
|
|
11800
|
-
}
|
|
11801
|
-
if (req.method !== 'POST') return sendJson(res, 405, { ok: false, reason: 'method_not_allowed' });
|
|
11802
|
-
const body = await readJsonBody(req);
|
|
11803
|
-
if (op === 'step') {
|
|
11804
|
-
try {
|
|
11805
|
-
const result = runTaskStep(taskDb, db, taskId, taskStepOptionsFromBody(body));
|
|
11806
|
-
return sendJson(res, 200, result);
|
|
11807
|
-
} catch (error) {
|
|
11808
|
-
return sendJson(res, error.status || 409, {
|
|
11809
|
-
ok: false,
|
|
11810
|
-
action: 'step',
|
|
11811
|
-
task_id: taskId,
|
|
11812
|
-
reason: error.reason || 'step_failed',
|
|
11813
|
-
detail: error.message,
|
|
11814
|
-
page: error.page || null,
|
|
11815
|
-
});
|
|
11816
|
-
}
|
|
11817
|
-
}
|
|
11818
|
-
if (op === 'continue-work') {
|
|
11819
|
-
try {
|
|
11820
|
-
const result = continueWorkForReviewTask(taskDb, db, taskId, { owner: body.owner || body.actor || DEFAULT_OWNER });
|
|
11821
|
-
return sendJson(res, 200, result);
|
|
11822
|
-
} catch (error) {
|
|
11823
|
-
return sendJson(res, error.status || 409, {
|
|
11824
|
-
ok: false,
|
|
11825
|
-
action: 'continue_work',
|
|
11826
|
-
task_id: taskId,
|
|
11827
|
-
reason: error.reason || 'continue_work_failed',
|
|
11828
|
-
detail: error.message,
|
|
11829
|
-
});
|
|
11830
|
-
}
|
|
11831
|
-
}
|
|
11832
|
-
if (op === 'claim') {
|
|
11833
|
-
const owner = String(body.owner || body.actor || DEFAULT_OWNER);
|
|
11834
|
-
const result = taskDb.claimTask(db, { id: taskId, claimedBy: owner });
|
|
11835
|
-
if (!result.claimed) return sendJson(res, 409, { ok: false, reason: result.reason, claimed_by: result.claimed_by || null });
|
|
11836
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11837
|
-
return sendJson(res, 200, { ok: true, action: 'claimed', task_id: taskId, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
11838
|
-
}
|
|
11839
|
-
if (op === 'message') {
|
|
11840
|
-
const result = taskDb.noteTask(db, { id: taskId, actor: String(body.actor || DEFAULT_OWNER), content: String(body.content || '') });
|
|
11841
|
-
if (!result.noted) return sendJson(res, 404, { ok: false, reason: result.reason });
|
|
11842
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11843
|
-
return sendJson(res, 200, { ok: true, action: 'noted', task_id: taskId, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
11844
|
-
}
|
|
11845
|
-
if (op === 'chat') {
|
|
11846
|
-
const result = taskDb.chatTask(db, {
|
|
11847
|
-
id: taskId,
|
|
11848
|
-
actor: String(body.actor || DEFAULT_OWNER),
|
|
11849
|
-
content: String(body.content || body.message || body.text || ''),
|
|
11850
|
-
goal: String(body.goal || body.objective || ''),
|
|
11851
|
-
summary: String(body.summary || ''),
|
|
11852
|
-
});
|
|
11853
|
-
if (!result.chatted) {
|
|
11854
|
-
const status = result.reason === 'content_required' ? 400 : result.reason === 'not_found' ? 404 : 409;
|
|
11855
|
-
return sendJson(res, status, { ok: false, reason: result.reason, detail: stageErrorDetail('task chat', result.reason, result) });
|
|
11856
|
-
}
|
|
11857
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11858
|
-
return sendJson(res, 200, {
|
|
11859
|
-
ok: true,
|
|
11860
|
-
action: 'chatted',
|
|
11861
|
-
task_id: taskId,
|
|
11862
|
-
version: result.event.version,
|
|
11863
|
-
goal_changed: result.goal_changed,
|
|
11864
|
-
chat_packet: result.chat_packet,
|
|
11865
|
-
projection_path: outPath,
|
|
11866
|
-
task: taskFromProjection(projection, taskId),
|
|
11867
|
-
});
|
|
12066
|
+
}
|
|
12067
|
+
|
|
12068
|
+
function postTaskApiClaim({ res, taskDb, db, taskId, body }) {
|
|
12069
|
+
const owner = String(body.owner || body.actor || DEFAULT_OWNER);
|
|
12070
|
+
const result = taskDb.claimTask(db, { id: taskId, claimedBy: owner });
|
|
12071
|
+
if (!result.claimed) return sendJson(res, 409, { ok: false, reason: result.reason, claimed_by: result.claimed_by || null });
|
|
12072
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12073
|
+
return sendJson(res, 200, { ok: true, action: 'claimed', task_id: taskId, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
12074
|
+
}
|
|
12075
|
+
|
|
12076
|
+
function postTaskApiMessage({ res, taskDb, db, taskId, body }) {
|
|
12077
|
+
const result = taskDb.noteTask(db, { id: taskId, actor: String(body.actor || DEFAULT_OWNER), content: String(body.content || '') });
|
|
12078
|
+
if (!result.noted) return sendJson(res, 404, { ok: false, reason: result.reason });
|
|
12079
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12080
|
+
return sendJson(res, 200, { ok: true, action: 'noted', task_id: taskId, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
12081
|
+
}
|
|
12082
|
+
|
|
12083
|
+
function postTaskApiChat({ res, taskDb, db, taskId, body }) {
|
|
12084
|
+
const result = taskDb.chatTask(db, {
|
|
12085
|
+
id: taskId,
|
|
12086
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
12087
|
+
content: String(body.content || body.message || body.text || ''),
|
|
12088
|
+
goal: String(body.goal || body.objective || ''),
|
|
12089
|
+
summary: String(body.summary || ''),
|
|
12090
|
+
});
|
|
12091
|
+
if (!result.chatted) {
|
|
12092
|
+
const status = result.reason === 'content_required' ? 400 : result.reason === 'not_found' ? 404 : 409;
|
|
12093
|
+
return sendJson(res, status, { ok: false, reason: result.reason, detail: stageErrorDetail('task chat', result.reason, result) });
|
|
11868
12094
|
}
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
12095
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12096
|
+
return sendJson(res, 200, {
|
|
12097
|
+
ok: true,
|
|
12098
|
+
action: 'chatted',
|
|
12099
|
+
task_id: taskId,
|
|
12100
|
+
version: result.event.version,
|
|
12101
|
+
goal_changed: result.goal_changed,
|
|
12102
|
+
chat_packet: result.chat_packet,
|
|
12103
|
+
projection_path: outPath,
|
|
12104
|
+
task: taskFromProjection(projection, taskId),
|
|
12105
|
+
});
|
|
12106
|
+
}
|
|
12107
|
+
|
|
12108
|
+
function postTaskApiPlan({ res, taskDb, db, taskId, body }) {
|
|
12109
|
+
const actor = String(body.actor || DEFAULT_OWNER);
|
|
12110
|
+
const goal = String(body.goal || body.objective || '');
|
|
12111
|
+
const summary = String(body.summary || body.plan || '');
|
|
12112
|
+
const owner = String(body.owner || body.assignee || '');
|
|
12113
|
+
const exit = String(body.exit || body.exit_condition || '');
|
|
12114
|
+
const firstMove = String(body.first_move || body.firstMove || body.first || '');
|
|
12115
|
+
const task = taskDetail(taskDb, db, taskId);
|
|
12116
|
+
const automaticPlan = buildAutomaticPlanTrace(taskDb, task, {
|
|
12117
|
+
actor,
|
|
12118
|
+
actorExplicit: Boolean(body.actor),
|
|
12119
|
+
owner,
|
|
12120
|
+
goal,
|
|
12121
|
+
summary,
|
|
12122
|
+
firstMove,
|
|
12123
|
+
exit,
|
|
12124
|
+
});
|
|
12125
|
+
const result = taskDb.stageTask(db, {
|
|
12126
|
+
id: taskId,
|
|
12127
|
+
actor,
|
|
12128
|
+
stage: 'plan',
|
|
12129
|
+
goal,
|
|
12130
|
+
summary,
|
|
12131
|
+
owner: automaticPlan.ownerForStage || owner,
|
|
12132
|
+
exit,
|
|
12133
|
+
proofNeeded: String(body.proof_needed || body.proofNeeded || body.proof || body.verify || ''),
|
|
12134
|
+
firstMove,
|
|
12135
|
+
nextButton: String(body.next_button || body.nextButton || ''),
|
|
12136
|
+
confidence: body.confidence,
|
|
12137
|
+
planTrace: automaticPlan.trace,
|
|
12138
|
+
});
|
|
12139
|
+
if (!result.staged) return sendJson(res, 409, { ok: false, reason: result.reason, detail: stageErrorDetail('task plan', result.reason, result) });
|
|
12140
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12141
|
+
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) });
|
|
12142
|
+
}
|
|
12143
|
+
|
|
12144
|
+
function postTaskApiDo({ res, taskDb, db, taskId, body }) {
|
|
12145
|
+
const firstMove = String(body.first_move || body.firstMove || body.first || '').trim();
|
|
12146
|
+
if (!firstMove) return sendJson(res, 400, { ok: false, reason: 'first_move_required', detail: 'task do: first_move required' });
|
|
12147
|
+
const result = taskDb.stageTask(db, {
|
|
12148
|
+
id: taskId,
|
|
12149
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
12150
|
+
stage: 'do',
|
|
12151
|
+
goal: String(body.goal || body.objective || ''),
|
|
12152
|
+
summary: String(body.summary || ''),
|
|
12153
|
+
owner: String(body.actor || DEFAULT_OWNER),
|
|
12154
|
+
exit: String(body.exit || body.exit_condition || body.exitCondition || ''),
|
|
12155
|
+
proofNeeded: String(body.proof_needed || body.proofNeeded || body.proof || body.verify || ''),
|
|
12156
|
+
firstMove,
|
|
12157
|
+
nextButton: String(body.next_button || body.nextButton || ''),
|
|
12158
|
+
confidence: body.confidence,
|
|
12159
|
+
});
|
|
12160
|
+
if (!result.staged) return sendJson(res, 409, { ok: false, reason: result.reason, detail: stageErrorDetail('task do', result.reason, result) });
|
|
12161
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12162
|
+
return sendJson(res, 200, { ok: true, action: 'doing', task_id: taskId, version: result.event.version, stage_packet: result.stage_packet, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
12163
|
+
}
|
|
12164
|
+
|
|
12165
|
+
function postTaskApiBacklog({ res, taskDb, db, taskId, body }) {
|
|
12166
|
+
const result = taskDb.backlogTask(db, {
|
|
12167
|
+
id: taskId,
|
|
12168
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
12169
|
+
reason: String(body.reason || body.note || 'clear_plan'),
|
|
12170
|
+
tag: String(body.tag || 'capture'),
|
|
12171
|
+
});
|
|
12172
|
+
if (!result.backlogged) return sendJson(res, 409, { ok: false, reason: result.reason, detail: stageErrorDetail('task backlog', result.reason, result) });
|
|
12173
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12174
|
+
return sendJson(res, 200, {
|
|
12175
|
+
ok: true,
|
|
12176
|
+
action: 'backlogged',
|
|
12177
|
+
task_id: taskId,
|
|
12178
|
+
version: result.event.version,
|
|
12179
|
+
cleared_keys: result.cleared_keys,
|
|
12180
|
+
projection_path: outPath,
|
|
12181
|
+
task: taskFromProjection(projection, taskId),
|
|
12182
|
+
});
|
|
12183
|
+
}
|
|
12184
|
+
|
|
12185
|
+
function postTaskApiReviewChat({ res, taskDb, db, taskId, body }) {
|
|
12186
|
+
try {
|
|
12187
|
+
const result = appendTaskReviewChat(taskDb, db, taskId, {
|
|
12188
|
+
reviewer: body.reviewer || body.actor || 'codex-review',
|
|
12189
|
+
dryRun: Boolean(body.dryRun || body.noNote),
|
|
11885
12190
|
});
|
|
11886
|
-
const
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
exit,
|
|
11894
|
-
proofNeeded: String(body.proof_needed || body.proofNeeded || body.proof || body.verify || ''),
|
|
11895
|
-
firstMove,
|
|
11896
|
-
nextButton: String(body.next_button || body.nextButton || ''),
|
|
11897
|
-
confidence: body.confidence,
|
|
11898
|
-
planTrace: automaticPlan.trace,
|
|
12191
|
+
const { event, compactProjection, outPath, ...payload } = result;
|
|
12192
|
+
return sendJson(res, 200, payload);
|
|
12193
|
+
} catch (error) {
|
|
12194
|
+
return sendJson(res, error.status || 409, {
|
|
12195
|
+
ok: false,
|
|
12196
|
+
reason: error.reason || 'review_chat_failed',
|
|
12197
|
+
detail: error.message,
|
|
11899
12198
|
});
|
|
11900
|
-
if (!result.staged) return sendJson(res, 409, { ok: false, reason: result.reason, detail: stageErrorDetail('task plan', result.reason, result) });
|
|
11901
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
11902
|
-
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) });
|
|
11903
12199
|
}
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
12200
|
+
}
|
|
12201
|
+
|
|
12202
|
+
function postTaskApiFinish({ res, taskDb, db, taskId, body }) {
|
|
12203
|
+
const currentTask = taskDb.getTask(db, taskId);
|
|
12204
|
+
const failed = Boolean(body.failed);
|
|
12205
|
+
const proof = String(body.proof || '').trim();
|
|
12206
|
+
const shouldReview = Boolean(body.proof || body.lesson || body.next || body.reward !== undefined);
|
|
12207
|
+
const proofIssue = meaningfulTaskProofIssue(proof, { required: !failed || shouldReview });
|
|
12208
|
+
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
12209
|
+
const done = taskDb.doneTask(db, {
|
|
12210
|
+
id: taskId,
|
|
12211
|
+
status: failed ? 'failed' : 'done',
|
|
12212
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
12213
|
+
action: failed ? 'failed' : 'finished',
|
|
12214
|
+
proof,
|
|
12215
|
+
});
|
|
12216
|
+
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_or_claimed' });
|
|
12217
|
+
let episode = null;
|
|
12218
|
+
let nextCreated = null;
|
|
12219
|
+
let xpProjection = null;
|
|
12220
|
+
if (shouldReview) {
|
|
12221
|
+
const reviewed = taskDb.reviewTask(db, {
|
|
11908
12222
|
id: taskId,
|
|
11909
12223
|
actor: String(body.actor || DEFAULT_OWNER),
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
|
|
11913
|
-
|
|
11914
|
-
|
|
11915
|
-
proofNeeded: String(body.proof_needed || body.proofNeeded || body.proof || body.verify || ''),
|
|
11916
|
-
firstMove,
|
|
11917
|
-
nextButton: String(body.next_button || body.nextButton || ''),
|
|
11918
|
-
confidence: body.confidence,
|
|
12224
|
+
reward: body.reward === undefined ? 1 : body.reward,
|
|
12225
|
+
lesson: String(body.lesson || ''),
|
|
12226
|
+
nextTask: String(body.next || ''),
|
|
12227
|
+
proof: String(body.proof || ''),
|
|
12228
|
+
careerXpEligible: false,
|
|
11919
12229
|
});
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
12230
|
+
episode = reviewed.episode;
|
|
12231
|
+
nextCreated = body.createNext ? createNextTaskIfRequested(taskDb, db, ['--create-next'], currentTask, episode.next_task_suggestion) : null;
|
|
12232
|
+
xpProjection = refreshCareerXpAfterReview(reviewed);
|
|
11923
12233
|
}
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
|
|
11927
|
-
|
|
11928
|
-
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11932
|
-
|
|
11933
|
-
|
|
11934
|
-
|
|
11935
|
-
|
|
11936
|
-
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
12234
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12235
|
+
return sendJson(res, 200, {
|
|
12236
|
+
ok: true,
|
|
12237
|
+
action: 'finished',
|
|
12238
|
+
task_id: taskId,
|
|
12239
|
+
reviewed: Boolean(episode),
|
|
12240
|
+
episode,
|
|
12241
|
+
xp_projection: xpProjection,
|
|
12242
|
+
next_task_id: nextCreated ? nextCreated.id : null,
|
|
12243
|
+
projection_path: outPath,
|
|
12244
|
+
task: taskFromProjection(projection, taskId),
|
|
12245
|
+
});
|
|
12246
|
+
}
|
|
12247
|
+
|
|
12248
|
+
function postTaskApiReady({ res, taskDb, db, taskId, body }) {
|
|
12249
|
+
const proof = String(body.proof || '').trim();
|
|
12250
|
+
const proofIssue = meaningfulTaskProofIssue(proof);
|
|
12251
|
+
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
12252
|
+
const nextTaskInput = normalizeReviewNextTaskInput(body.next);
|
|
12253
|
+
const actor = String(body.actor || DEFAULT_OWNER);
|
|
12254
|
+
const resultText = String(body.result || '').replace(/\s+/g, ' ').trim();
|
|
12255
|
+
if (resultText) {
|
|
12256
|
+
const resultIssue = resultSentenceIssue(resultText);
|
|
12257
|
+
if (resultIssue) return sendJson(res, 400, { ok: false, reason: 'weak_result', detail: resultIssue });
|
|
11942
12258
|
}
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
12259
|
+
const resultTrace = buildAutomaticResultTrace(taskDb, db, taskId, {
|
|
12260
|
+
actor,
|
|
12261
|
+
proof,
|
|
12262
|
+
changed: body.changed || resultText || body.done,
|
|
12263
|
+
checked: body.checked || body.check || body.verified,
|
|
12264
|
+
passed: body.passed || body.pass,
|
|
12265
|
+
failed: body.failed || body.fail,
|
|
12266
|
+
cost: body.cost,
|
|
12267
|
+
saved: body.saved || body.savings,
|
|
12268
|
+
tryNext: body.try_next || body.tryNext || body.try || body.handoff,
|
|
12269
|
+
status: body.status,
|
|
12270
|
+
files: body.files,
|
|
12271
|
+
commands: body.commands || body.command,
|
|
12272
|
+
});
|
|
12273
|
+
const result = taskDb.readyTask(db, {
|
|
12274
|
+
id: taskId,
|
|
12275
|
+
actor,
|
|
12276
|
+
proof,
|
|
12277
|
+
lesson: String(body.lesson || ''),
|
|
12278
|
+
nextTask: nextTaskInput.nextTask,
|
|
12279
|
+
resultTrace: resultTrace && resultTrace.trace,
|
|
12280
|
+
result: resultText,
|
|
12281
|
+
landing: body.landing || {
|
|
12282
|
+
happened: body.happened,
|
|
12283
|
+
checked: body.checked,
|
|
12284
|
+
tested: body.tested,
|
|
12285
|
+
decision: body.decision,
|
|
12286
|
+
},
|
|
12287
|
+
});
|
|
12288
|
+
if (!result.ready) return sendJson(res, 409, { ok: false, reason: result.reason });
|
|
12289
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12290
|
+
return sendJson(res, 200, {
|
|
12291
|
+
ok: true,
|
|
12292
|
+
action: 'ready',
|
|
12293
|
+
task_id: taskId,
|
|
12294
|
+
result_trace: resultTrace,
|
|
12295
|
+
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
12296
|
+
projection_path: outPath,
|
|
12297
|
+
task: taskFromProjection(projection, taskId),
|
|
12298
|
+
});
|
|
12299
|
+
}
|
|
12300
|
+
|
|
12301
|
+
function postTaskApiAccept({ res, taskDb, db, taskId, body }) {
|
|
12302
|
+
const currentTask = enrichTaskProjection(taskDb.taskProjection(db, { taskId })).tasks[0] || null;
|
|
12303
|
+
const hasExplicitProof = Object.prototype.hasOwnProperty.call(body, 'proof');
|
|
12304
|
+
const proof = String(hasExplicitProof ? body.proof : currentTask?.metadata?.latest_agent_proof || '').trim();
|
|
12305
|
+
const proofIssue = meaningfulTaskProofIssue(proof);
|
|
12306
|
+
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
12307
|
+
const hasExplicitLesson = Object.prototype.hasOwnProperty.call(body, 'lesson');
|
|
12308
|
+
const hasExplicitNext = Object.prototype.hasOwnProperty.call(body, 'next');
|
|
12309
|
+
const lesson = hasExplicitLesson ? String(body.lesson || '') : String(currentTask?.review?.lesson || currentTask?.metadata?.latest_agent_lesson || '');
|
|
12310
|
+
const nextTask = hasExplicitNext ? String(body.next || '') : String(currentTask?.review?.next_task || currentTask?.metadata?.latest_agent_next_task || '');
|
|
12311
|
+
const clearedFields = [];
|
|
12312
|
+
if (hasExplicitLesson && !lesson.trim()) clearedFields.push('lesson');
|
|
12313
|
+
if (hasExplicitNext && !nextTask.trim()) clearedFields.push('next_task');
|
|
12314
|
+
const parsedReward = parseAcceptReward(body.reward);
|
|
12315
|
+
if (!parsedReward.ok) return sendJson(res, 400, { ok: false, reason: 'invalid_reward', detail: 'reward must be a positive number' });
|
|
12316
|
+
const done = taskDb.doneTask(db, {
|
|
12317
|
+
id: taskId,
|
|
12318
|
+
status: 'done',
|
|
12319
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
12320
|
+
allowReview: true,
|
|
12321
|
+
action: 'accepted',
|
|
12322
|
+
proof,
|
|
12323
|
+
});
|
|
12324
|
+
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_claimed_or_review' });
|
|
12325
|
+
const reviewed = taskDb.reviewTask(db, {
|
|
12326
|
+
id: taskId,
|
|
12327
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
12328
|
+
reward: parsedReward.value,
|
|
12329
|
+
lesson,
|
|
12330
|
+
nextTask,
|
|
12331
|
+
proof,
|
|
12332
|
+
careerXpEligible: true,
|
|
12333
|
+
clearedFields,
|
|
12334
|
+
});
|
|
12335
|
+
const nextCreated = body.createNext ? createNextTaskIfRequested(taskDb, db, ['--create-next'], currentTask, reviewed.episode.next_task_suggestion) : null;
|
|
12336
|
+
const xpProjection = refreshCareerXpAfterReview(reviewed);
|
|
12337
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12338
|
+
return sendJson(res, 200, { ok: true, action: 'accepted', task_id: taskId, episode: reviewed.episode, xp_projection: xpProjection, next_task_id: nextCreated ? nextCreated.id : null, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
12339
|
+
}
|
|
12340
|
+
|
|
12341
|
+
function postTaskApiRevise({ res, taskDb, db, taskId, body }) {
|
|
12342
|
+
const result = taskDb.reviseTask(db, { id: taskId, actor: String(body.actor || DEFAULT_OWNER), note: String(body.note || body.reason || '') });
|
|
12343
|
+
if (!result.revised) return sendJson(res, 409, { ok: false, reason: result.reason });
|
|
12344
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12345
|
+
return sendJson(res, 200, { ok: true, action: 'revise', task_id: taskId, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
12346
|
+
}
|
|
12347
|
+
|
|
12348
|
+
function postTaskApiReview({ res, taskDb, db, taskId, body }) {
|
|
12349
|
+
const currentTask = taskDb.getTask(db, taskId);
|
|
12350
|
+
const rewardValue = body.reward === undefined ? 0 : body.reward;
|
|
12351
|
+
const proof = String(body.proof || '').trim();
|
|
12352
|
+
const hasExplicitLesson = Object.prototype.hasOwnProperty.call(body, 'lesson');
|
|
12353
|
+
const hasExplicitNext = Object.prototype.hasOwnProperty.call(body, 'next')
|
|
12354
|
+
|| Object.prototype.hasOwnProperty.call(body, 'next_task')
|
|
12355
|
+
|| Object.prototype.hasOwnProperty.call(body, 'nextTask');
|
|
12356
|
+
const lessonText = hasExplicitLesson ? String(body.lesson || '') : '';
|
|
12357
|
+
const rawNext = Object.prototype.hasOwnProperty.call(body, 'next')
|
|
12358
|
+
? body.next
|
|
12359
|
+
: Object.prototype.hasOwnProperty.call(body, 'next_task')
|
|
12360
|
+
? body.next_task
|
|
12361
|
+
: body.nextTask;
|
|
12362
|
+
const nextTaskInput = normalizeReviewNextTaskInput(hasExplicitNext ? rawNext : '');
|
|
12363
|
+
const clearedFields = [];
|
|
12364
|
+
if (hasExplicitLesson && !lessonText.trim()) clearedFields.push('lesson');
|
|
12365
|
+
if (hasExplicitNext && !String(rawNext || '').trim()) clearedFields.push('next_task');
|
|
12366
|
+
const proofIssue = Number(rewardValue) > 0 || proof
|
|
12367
|
+
? meaningfulTaskProofIssue(proof)
|
|
12368
|
+
: null;
|
|
12369
|
+
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
12370
|
+
const reviewed = taskDb.reviewTask(db, {
|
|
12371
|
+
id: taskId,
|
|
12372
|
+
actor: String(body.actor || DEFAULT_OWNER),
|
|
12373
|
+
reward: rewardValue,
|
|
12374
|
+
lesson: lessonText,
|
|
12375
|
+
nextTask: nextTaskInput.nextTask,
|
|
12376
|
+
proof,
|
|
12377
|
+
careerXpEligible: false,
|
|
12378
|
+
clearedFields,
|
|
12379
|
+
});
|
|
12380
|
+
const nextCreated = body.createNext ? createNextTaskIfRequested(taskDb, db, ['--create-next'], currentTask, reviewed.episode.next_task_suggestion) : null;
|
|
12381
|
+
const xpProjection = refreshCareerXpAfterReview(reviewed);
|
|
12382
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12383
|
+
return sendJson(res, 200, {
|
|
12384
|
+
ok: true,
|
|
12385
|
+
action: 'reviewed',
|
|
12386
|
+
task_id: taskId,
|
|
12387
|
+
episode: reviewed.episode,
|
|
12388
|
+
xp_projection: xpProjection,
|
|
12389
|
+
next_task_id: nextCreated ? nextCreated.id : null,
|
|
12390
|
+
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
12391
|
+
projection_path: outPath,
|
|
12392
|
+
task: taskFromProjection(projection, taskId),
|
|
12393
|
+
});
|
|
12394
|
+
}
|
|
12395
|
+
|
|
12396
|
+
const TASK_API_POST_OPERATIONS = {
|
|
12397
|
+
step: postTaskApiStep,
|
|
12398
|
+
'continue-work': postTaskApiContinueWork,
|
|
12399
|
+
claim: postTaskApiClaim,
|
|
12400
|
+
message: postTaskApiMessage,
|
|
12401
|
+
chat: postTaskApiChat,
|
|
12402
|
+
plan: postTaskApiPlan,
|
|
12403
|
+
do: postTaskApiDo,
|
|
12404
|
+
backlog: postTaskApiBacklog,
|
|
12405
|
+
'review-chat': postTaskApiReviewChat,
|
|
12406
|
+
finish: postTaskApiFinish,
|
|
12407
|
+
ready: postTaskApiReady,
|
|
12408
|
+
accept: postTaskApiAccept,
|
|
12409
|
+
revise: postTaskApiRevise,
|
|
12410
|
+
review: postTaskApiReview,
|
|
12411
|
+
};
|
|
12412
|
+
|
|
12413
|
+
function serveTaskEvents({ res, taskDb, db, taskId }) {
|
|
12414
|
+
const events = taskDb.listTaskEvents(db, { taskId, limit: 500 });
|
|
12415
|
+
return sendJson(res, 200, { ok: true, events });
|
|
12416
|
+
}
|
|
12417
|
+
|
|
12418
|
+
async function serveTaskOperation({ req, res, taskDb, db, match }) {
|
|
12419
|
+
const resolved = resolveTaskRef(taskDb, db, match[1]);
|
|
12420
|
+
if (!resolved.ok) return sendJson(res, resolved.reason === 'ambiguous' ? 409 : 404, { ok: false, reason: resolved.reason });
|
|
12421
|
+
const taskId = resolved.id;
|
|
12422
|
+
const operation = match[2];
|
|
12423
|
+
if (req.method === 'GET' && operation === 'events') {
|
|
12424
|
+
return serveTaskEvents({ res, taskDb, db, taskId });
|
|
11958
12425
|
}
|
|
11959
|
-
if (
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
});
|
|
11973
|
-
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_or_claimed' });
|
|
11974
|
-
let episode = null;
|
|
11975
|
-
let nextCreated = null;
|
|
11976
|
-
let xpProjection = null;
|
|
11977
|
-
if (shouldReview) {
|
|
11978
|
-
const reviewed = taskDb.reviewTask(db, {
|
|
11979
|
-
id: taskId,
|
|
11980
|
-
actor: String(body.actor || DEFAULT_OWNER),
|
|
11981
|
-
reward: body.reward === undefined ? 1 : body.reward,
|
|
11982
|
-
lesson: String(body.lesson || ''),
|
|
11983
|
-
nextTask: String(body.next || ''),
|
|
11984
|
-
proof: String(body.proof || ''),
|
|
11985
|
-
careerXpEligible: false,
|
|
11986
|
-
});
|
|
11987
|
-
episode = reviewed.episode;
|
|
11988
|
-
nextCreated = body.createNext ? createNextTaskIfRequested(taskDb, db, ['--create-next'], currentTask, episode.next_task_suggestion) : null;
|
|
11989
|
-
xpProjection = refreshCareerXpAfterReview(reviewed);
|
|
12426
|
+
if (req.method !== 'POST') return sendJson(res, 405, { ok: false, reason: 'method_not_allowed' });
|
|
12427
|
+
const body = await readJsonBody(req);
|
|
12428
|
+
const postTaskOperation = TASK_API_POST_OPERATIONS[operation];
|
|
12429
|
+
if (!postTaskOperation) return;
|
|
12430
|
+
return postTaskOperation({ res, taskDb, db, taskId, body });
|
|
12431
|
+
}
|
|
12432
|
+
|
|
12433
|
+
function matchTaskApiRoute(req, url) {
|
|
12434
|
+
for (const route of TASK_API_ROUTES) {
|
|
12435
|
+
if (route.method && route.method !== req.method) continue;
|
|
12436
|
+
if (Object.prototype.hasOwnProperty.call(route, 'pathname')) {
|
|
12437
|
+
if (route.pathname === null || route.pathname === url.pathname) return { route, match: null };
|
|
12438
|
+
continue;
|
|
11990
12439
|
}
|
|
11991
|
-
const
|
|
11992
|
-
|
|
11993
|
-
ok: true,
|
|
11994
|
-
action: 'finished',
|
|
11995
|
-
task_id: taskId,
|
|
11996
|
-
reviewed: Boolean(episode),
|
|
11997
|
-
episode,
|
|
11998
|
-
xp_projection: xpProjection,
|
|
11999
|
-
next_task_id: nextCreated ? nextCreated.id : null,
|
|
12000
|
-
projection_path: outPath,
|
|
12001
|
-
task: taskFromProjection(projection, taskId),
|
|
12002
|
-
});
|
|
12440
|
+
const match = url.pathname.match(route.pattern);
|
|
12441
|
+
if (match) return { route, match };
|
|
12003
12442
|
}
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
commands: body.commands || body.command,
|
|
12028
|
-
});
|
|
12029
|
-
const result = taskDb.readyTask(db, {
|
|
12030
|
-
id: taskId,
|
|
12031
|
-
actor,
|
|
12032
|
-
proof,
|
|
12033
|
-
lesson: String(body.lesson || ''),
|
|
12034
|
-
nextTask: nextTaskInput.nextTask,
|
|
12035
|
-
resultTrace: resultTrace && resultTrace.trace,
|
|
12036
|
-
result: resultText,
|
|
12037
|
-
landing: body.landing || {
|
|
12038
|
-
happened: body.happened,
|
|
12039
|
-
checked: body.checked,
|
|
12040
|
-
tested: body.tested,
|
|
12041
|
-
decision: body.decision,
|
|
12042
|
-
},
|
|
12043
|
-
});
|
|
12044
|
-
if (!result.ready) return sendJson(res, 409, { ok: false, reason: result.reason });
|
|
12045
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12046
|
-
return sendJson(res, 200, {
|
|
12047
|
-
ok: true,
|
|
12048
|
-
action: 'ready',
|
|
12049
|
-
task_id: taskId,
|
|
12050
|
-
result_trace: resultTrace,
|
|
12051
|
-
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
12052
|
-
projection_path: outPath,
|
|
12053
|
-
task: taskFromProjection(projection, taskId),
|
|
12054
|
-
});
|
|
12055
|
-
}
|
|
12056
|
-
if (op === 'accept') {
|
|
12057
|
-
const currentTask = enrichTaskProjection(taskDb.taskProjection(db, { taskId })).tasks[0] || null;
|
|
12058
|
-
const hasExplicitProof = Object.prototype.hasOwnProperty.call(body, 'proof');
|
|
12059
|
-
const proof = String(hasExplicitProof ? body.proof : currentTask?.metadata?.latest_agent_proof || '').trim();
|
|
12060
|
-
const proofIssue = meaningfulTaskProofIssue(proof);
|
|
12061
|
-
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
12062
|
-
const hasExplicitLesson = Object.prototype.hasOwnProperty.call(body, 'lesson');
|
|
12063
|
-
const hasExplicitNext = Object.prototype.hasOwnProperty.call(body, 'next');
|
|
12064
|
-
const lesson = hasExplicitLesson ? String(body.lesson || '') : String(currentTask?.review?.lesson || currentTask?.metadata?.latest_agent_lesson || '');
|
|
12065
|
-
const nextTask = hasExplicitNext ? String(body.next || '') : String(currentTask?.review?.next_task || currentTask?.metadata?.latest_agent_next_task || '');
|
|
12066
|
-
const clearedFields = [];
|
|
12067
|
-
if (hasExplicitLesson && !lesson.trim()) clearedFields.push('lesson');
|
|
12068
|
-
if (hasExplicitNext && !nextTask.trim()) clearedFields.push('next_task');
|
|
12069
|
-
const parsedReward = parseAcceptReward(body.reward);
|
|
12070
|
-
if (!parsedReward.ok) return sendJson(res, 400, { ok: false, reason: 'invalid_reward', detail: 'reward must be a positive number' });
|
|
12071
|
-
const done = taskDb.doneTask(db, {
|
|
12072
|
-
id: taskId,
|
|
12073
|
-
status: 'done',
|
|
12074
|
-
actor: String(body.actor || DEFAULT_OWNER),
|
|
12075
|
-
allowReview: true,
|
|
12076
|
-
action: 'accepted',
|
|
12077
|
-
proof,
|
|
12078
|
-
});
|
|
12079
|
-
if (!done.updated) return sendJson(res, 409, { ok: false, reason: 'not_open_claimed_or_review' });
|
|
12080
|
-
const reviewed = taskDb.reviewTask(db, {
|
|
12081
|
-
id: taskId,
|
|
12082
|
-
actor: String(body.actor || DEFAULT_OWNER),
|
|
12083
|
-
reward: parsedReward.value,
|
|
12084
|
-
lesson,
|
|
12085
|
-
nextTask,
|
|
12086
|
-
proof,
|
|
12087
|
-
careerXpEligible: true,
|
|
12088
|
-
clearedFields,
|
|
12089
|
-
});
|
|
12090
|
-
const nextCreated = body.createNext ? createNextTaskIfRequested(taskDb, db, ['--create-next'], currentTask, reviewed.episode.next_task_suggestion) : null;
|
|
12091
|
-
const xpProjection = refreshCareerXpAfterReview(reviewed);
|
|
12092
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12093
|
-
return sendJson(res, 200, { ok: true, action: 'accepted', task_id: taskId, episode: reviewed.episode, xp_projection: xpProjection, next_task_id: nextCreated ? nextCreated.id : null, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
12094
|
-
}
|
|
12095
|
-
if (op === 'revise') {
|
|
12096
|
-
const result = taskDb.reviseTask(db, { id: taskId, actor: String(body.actor || DEFAULT_OWNER), note: String(body.note || body.reason || '') });
|
|
12097
|
-
if (!result.revised) return sendJson(res, 409, { ok: false, reason: result.reason });
|
|
12098
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12099
|
-
return sendJson(res, 200, { ok: true, action: 'revise', task_id: taskId, projection_path: outPath, task: taskFromProjection(projection, taskId) });
|
|
12100
|
-
}
|
|
12101
|
-
if (op === 'review') {
|
|
12102
|
-
const currentTask = taskDb.getTask(db, taskId);
|
|
12103
|
-
const rewardValue = body.reward === undefined ? 0 : body.reward;
|
|
12104
|
-
const proof = String(body.proof || '').trim();
|
|
12105
|
-
const hasExplicitLesson = Object.prototype.hasOwnProperty.call(body, 'lesson');
|
|
12106
|
-
const hasExplicitNext = Object.prototype.hasOwnProperty.call(body, 'next')
|
|
12107
|
-
|| Object.prototype.hasOwnProperty.call(body, 'next_task')
|
|
12108
|
-
|| Object.prototype.hasOwnProperty.call(body, 'nextTask');
|
|
12109
|
-
const lessonText = hasExplicitLesson ? String(body.lesson || '') : '';
|
|
12110
|
-
const rawNext = Object.prototype.hasOwnProperty.call(body, 'next')
|
|
12111
|
-
? body.next
|
|
12112
|
-
: Object.prototype.hasOwnProperty.call(body, 'next_task')
|
|
12113
|
-
? body.next_task
|
|
12114
|
-
: body.nextTask;
|
|
12115
|
-
const nextTaskInput = normalizeReviewNextTaskInput(hasExplicitNext ? rawNext : '');
|
|
12116
|
-
const clearedFields = [];
|
|
12117
|
-
if (hasExplicitLesson && !lessonText.trim()) clearedFields.push('lesson');
|
|
12118
|
-
if (hasExplicitNext && !String(rawNext || '').trim()) clearedFields.push('next_task');
|
|
12119
|
-
const proofIssue = Number(rewardValue) > 0 || proof
|
|
12120
|
-
? meaningfulTaskProofIssue(proof)
|
|
12121
|
-
: null;
|
|
12122
|
-
if (proofIssue) return sendProofIssue(res, proof, proofIssue);
|
|
12123
|
-
const reviewed = taskDb.reviewTask(db, {
|
|
12124
|
-
id: taskId,
|
|
12125
|
-
actor: String(body.actor || DEFAULT_OWNER),
|
|
12126
|
-
reward: rewardValue,
|
|
12127
|
-
lesson: lessonText,
|
|
12128
|
-
nextTask: nextTaskInput.nextTask,
|
|
12129
|
-
proof,
|
|
12130
|
-
careerXpEligible: false,
|
|
12131
|
-
clearedFields,
|
|
12132
|
-
});
|
|
12133
|
-
const nextCreated = body.createNext ? createNextTaskIfRequested(taskDb, db, ['--create-next'], currentTask, reviewed.episode.next_task_suggestion) : null;
|
|
12134
|
-
const xpProjection = refreshCareerXpAfterReview(reviewed);
|
|
12135
|
-
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
12136
|
-
return sendJson(res, 200, {
|
|
12137
|
-
ok: true,
|
|
12138
|
-
action: 'reviewed',
|
|
12139
|
-
task_id: taskId,
|
|
12140
|
-
episode: reviewed.episode,
|
|
12141
|
-
xp_projection: xpProjection,
|
|
12142
|
-
next_task_id: nextCreated ? nextCreated.id : null,
|
|
12143
|
-
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
12144
|
-
projection_path: outPath,
|
|
12145
|
-
task: taskFromProjection(projection, taskId),
|
|
12443
|
+
return null;
|
|
12444
|
+
}
|
|
12445
|
+
|
|
12446
|
+
async function handleTaskApi(req, res, taskDb, db) {
|
|
12447
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
12448
|
+
const matchedRoute = matchTaskApiRoute(req, url);
|
|
12449
|
+
if (!matchedRoute) return sendJson(res, 404, { ok: false, reason: 'not_found' });
|
|
12450
|
+
return matchedRoute.route.serve({
|
|
12451
|
+
req,
|
|
12452
|
+
res,
|
|
12453
|
+
taskDb,
|
|
12454
|
+
db,
|
|
12455
|
+
url,
|
|
12456
|
+
match: matchedRoute.match,
|
|
12457
|
+
});
|
|
12458
|
+
}
|
|
12459
|
+
|
|
12460
|
+
function createTaskApiServer(taskApi = {}) {
|
|
12461
|
+
const taskDb = taskApi.taskDb || getTaskDb();
|
|
12462
|
+
const db = taskApi.db || taskDb.open();
|
|
12463
|
+
return http.createServer((req, res) => {
|
|
12464
|
+
handleTaskApi(req, res, taskDb, db).catch((error) => {
|
|
12465
|
+
sendJson(res, 500, { ok: false, reason: 'server_error', detail: String(error && error.message || error) });
|
|
12146
12466
|
});
|
|
12147
|
-
}
|
|
12467
|
+
});
|
|
12148
12468
|
}
|
|
12149
12469
|
|
|
12150
12470
|
function cmdServe(args) {
|
|
@@ -12152,11 +12472,7 @@ function cmdServe(args) {
|
|
|
12152
12472
|
const port = Number(flag(args, '--port') || process.env.PORT || 8787);
|
|
12153
12473
|
const taskDb = getTaskDb();
|
|
12154
12474
|
const db = taskDb.open();
|
|
12155
|
-
const server =
|
|
12156
|
-
handleTaskApi(req, res, taskDb, db).catch((e) => {
|
|
12157
|
-
sendJson(res, 500, { ok: false, reason: 'server_error', detail: String(e && e.message || e) });
|
|
12158
|
-
});
|
|
12159
|
-
});
|
|
12475
|
+
const server = createTaskApiServer({ taskDb, db });
|
|
12160
12476
|
return new Promise((resolve, reject) => {
|
|
12161
12477
|
server.on('error', reject);
|
|
12162
12478
|
server.listen(port, host, () => {
|
|
@@ -12281,6 +12597,9 @@ async function runTaskCommand(args) {
|
|
|
12281
12597
|
case 'fail': return cmdDone([...rest, '--failed']);
|
|
12282
12598
|
case 'archive': return cmdArchive(rest);
|
|
12283
12599
|
case 'clear-done': return cmdClearDone(rest);
|
|
12600
|
+
case 'reap-mission-blockers':
|
|
12601
|
+
case 'reap-blockers':
|
|
12602
|
+
return cmdReapMissionBlockers(rest);
|
|
12284
12603
|
case 'relabel-archived': return cmdRelabelArchived(rest);
|
|
12285
12604
|
case 'review': return cmdReview(rest);
|
|
12286
12605
|
case 'reviews':
|
|
@@ -12321,7 +12640,7 @@ const MUTATING_TASK_COMMANDS = new Set([
|
|
|
12321
12640
|
'continue-work', 'continue', 'chat', 'note', 'say', 'retitle', 'tag', 'tags', 'step',
|
|
12322
12641
|
'ready', 'result', 'accept', 'landing', 'land-review', 'auto-accept-certified',
|
|
12323
12642
|
'auto-accept', 'sweep', 'audit', 'certify-verified', 'accept-group', 'revise',
|
|
12324
|
-
'done', 'finish', 'fail', 'archive', 'clear-done', 'relabel-archived', 'review', 'import',
|
|
12643
|
+
'done', 'finish', 'fail', 'archive', 'clear-done', 'reap-mission-blockers', 'reap-blockers', 'relabel-archived', 'review', 'import',
|
|
12325
12644
|
'setup', 'review-lane-act', 'review-act', 'act-review', 'review-lane-loop',
|
|
12326
12645
|
'review-loop', 'loop-review', 'review-lane-run', 'review-run', 'run-review',
|
|
12327
12646
|
]);
|
|
@@ -12337,6 +12656,7 @@ async function run(args) {
|
|
|
12337
12656
|
|
|
12338
12657
|
module.exports = {
|
|
12339
12658
|
run,
|
|
12659
|
+
createTaskApiServer,
|
|
12340
12660
|
taskDayGroups,
|
|
12341
12661
|
taskDayTextGroups,
|
|
12342
12662
|
taskDayTitle,
|
|
@@ -12347,4 +12667,6 @@ module.exports = {
|
|
|
12347
12667
|
autoRenderTodoFromDb,
|
|
12348
12668
|
projectionMissions,
|
|
12349
12669
|
projectionWishes,
|
|
12670
|
+
taskBoardViewModel,
|
|
12671
|
+
taskBoardTemplate,
|
|
12350
12672
|
};
|