atris 3.31.0 → 3.33.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -4
- package/atris/CLAUDE.md +8 -0
- package/atris.md +8 -0
- package/ax +458 -15
- package/bin/atris.js +200 -76
- package/commands/autoland.js +78 -12
- package/commands/autopilot-front.js +273 -0
- package/commands/engine.js +299 -0
- package/commands/init.js +21 -0
- package/commands/integrations.js +147 -0
- package/commands/member.js +85 -0
- package/commands/mission.js +500 -43
- package/commands/run-front.js +144 -0
- package/commands/run.js +9 -6
- package/commands/sign.js +90 -0
- package/commands/task.js +380 -20
- package/commands/truth.js +72 -11
- package/lib/autoland.js +133 -25
- package/lib/fleet.js +354 -0
- package/lib/inspect-fields.js +174 -0
- package/lib/operator-next.js +7 -0
- package/lib/pulse.js +4 -3
- package/lib/runner-command.js +54 -11
- package/lib/task-db.js +57 -2
- package/package.json +3 -2
- package/scripts/agent_worktree.py +72 -0
package/commands/task.js
CHANGED
|
@@ -8,13 +8,22 @@ const http = require('http');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const os = require('os');
|
|
10
10
|
const { taskProofState, buildVerifiedProof } = require('../lib/task-proof');
|
|
11
|
-
const { evaluateAutoAccept, parseVerifyCommand } = require('../lib/auto-accept-certified');
|
|
11
|
+
const { evaluateAutoAccept, parseVerifyCommand, runVerifyCommand, DENIED_TAGS } = require('../lib/auto-accept-certified');
|
|
12
12
|
const { extractReceiptEvidence } = require('../lib/receipt-evidence');
|
|
13
13
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
14
14
|
const {
|
|
15
15
|
normalizeOwnerSlug,
|
|
16
16
|
resolveFunctionalOwner: resolveFunctionalTaskOwner,
|
|
17
17
|
} = require('../lib/functional-owner');
|
|
18
|
+
const { operatorReady, hasAgentJargon } = require('./autoland');
|
|
19
|
+
const {
|
|
20
|
+
TASK_INSPECT_FIELDS,
|
|
21
|
+
readFieldsFlag,
|
|
22
|
+
stripInspectArgs,
|
|
23
|
+
validateFields,
|
|
24
|
+
inspectTextLines,
|
|
25
|
+
buildInspectPayload,
|
|
26
|
+
} = require('../lib/inspect-fields');
|
|
18
27
|
|
|
19
28
|
const DEFAULT_OWNER = process.env.ATRIS_AGENT_ID
|
|
20
29
|
|| process.env.USER
|
|
@@ -90,13 +99,22 @@ function getTaskDb() {
|
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
|
|
102
|
+
function warnIfTaskTitleNeedsOperatorWhy(title) {
|
|
103
|
+
const text = String(title || '').trim();
|
|
104
|
+
if (!text || operatorReady(text)) return null;
|
|
105
|
+
const warning = 'Warning: add the why in plain words to this task title: what it buys or costs, who benefits, and no flags or identifiers.';
|
|
106
|
+
console.error(warning);
|
|
107
|
+
return warning;
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
function taskUsageText() {
|
|
94
111
|
return `
|
|
95
112
|
atris task - durable local task state (SQLite, gitignored)
|
|
96
113
|
|
|
97
114
|
atris task Show the task desk
|
|
98
115
|
atris task new "<title>" Create a task
|
|
99
|
-
atris task next [--create-next]
|
|
116
|
+
atris task next [--tag <tag>] [--create-next]
|
|
117
|
+
Claim/show next open task; optionally create the generated Endgame fallback
|
|
100
118
|
atris task continue-work <id> Create/reuse a certified Review follow-up task
|
|
101
119
|
atris task say <id> "<message>" Add context to a task
|
|
102
120
|
atris task chat <id> "<message>" [--goal "..."] Refine a task chat + working goal
|
|
@@ -113,6 +131,8 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
113
131
|
atris task review-chat <id> [--as <owner>] Start a task-owned /codex verification chat
|
|
114
132
|
atris task accept <id> [--proof "..."] [--public]
|
|
115
133
|
Human accepts proof, marks done; --public also publishes AgentXP
|
|
134
|
+
atris task certify-verified [--dry-run] [--limit <n>] [--as <actor>]
|
|
135
|
+
Re-run the runnable check named in each Review proof as a second actor; passing rows certify (denied lanes and check-less rows wait for a human)
|
|
116
136
|
atris task auto-accept-certified --dry-run [--strict-verify] [--limit <n>]
|
|
117
137
|
Preview certified Review rows; live accept needs --confirm-human-accept --as <human>
|
|
118
138
|
atris task revise <id> --note "..." Send reviewed work back to Do
|
|
@@ -128,6 +148,7 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
128
148
|
atris task day [--json] Show today's owner-grouped task list
|
|
129
149
|
atris task list [--all] [--status <s>] List tasks (default: this workspace)
|
|
130
150
|
atris task claim <id> [--as <owner>] Atomic claim
|
|
151
|
+
atris task release <id> [--as <owner>] Release your own mistaken claim back to open
|
|
131
152
|
atris task capabilities [--json] Read-only task CLI/API capability contract
|
|
132
153
|
atris task capabilities-check [--json] Verify task capability contract conformance
|
|
133
154
|
atris task review-lane-drain [--json] Pick next safe Review-lane agent action
|
|
@@ -143,6 +164,8 @@ atris task - durable local task state (SQLite, gitignored)
|
|
|
143
164
|
review-state lanes: needs-agent, continue-work, human-accept-waiting, certified
|
|
144
165
|
atris task note <id> "<message>" Append dialogue/context to a task
|
|
145
166
|
atris task show <id> [--json] Show a task card + dialogue
|
|
167
|
+
atris task inspect <id> --fields review,status,title [--json]
|
|
168
|
+
Field-selectable task state (review metadata, status, title, owner, tag)
|
|
146
169
|
atris task page <id> [--json] Show the one-task page contract
|
|
147
170
|
atris task step <id> [--json] Refine chat, then advance one safe Plan/Do/Review step
|
|
148
171
|
atris task done <id> --proof "..." Mark complete with proof
|
|
@@ -374,13 +397,36 @@ function textFlag(args, names) {
|
|
|
374
397
|
|
|
375
398
|
function landingFlags(args) {
|
|
376
399
|
return {
|
|
377
|
-
happened: textFlag(args, ['--happened', '--what-happened']),
|
|
400
|
+
happened: textFlag(args, ['--landing', '--happened', '--what-happened']),
|
|
378
401
|
checked: textFlag(args, ['--checked', '--how-checked']),
|
|
379
402
|
tested: textFlag(args, ['--tested', '--what-tested']),
|
|
380
403
|
decision: textFlag(args, ['--decision', '--acceptance']),
|
|
381
404
|
};
|
|
382
405
|
}
|
|
383
406
|
|
|
407
|
+
function normalizedLandingSentence(value) {
|
|
408
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function defaultLandingSentenceForTitle(title) {
|
|
412
|
+
return `Completed: ${String(title || '').trim()}.`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function landingNeedsDayOnePm(sentence, title) {
|
|
416
|
+
const text = normalizedLandingSentence(sentence);
|
|
417
|
+
if (!text) return true;
|
|
418
|
+
if (text.toLowerCase() === normalizedLandingSentence(defaultLandingSentenceForTitle(title)).toLowerCase()) return true;
|
|
419
|
+
return hasAgentJargon(text) || !operatorReady(text);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function warnIfLandingNeedsDayOnePm(landing, title) {
|
|
423
|
+
const sentence = landing && typeof landing === 'object' ? landing.happened : '';
|
|
424
|
+
if (!landingNeedsDayOnePm(sentence, title)) return null;
|
|
425
|
+
const warning = 'Advisory: add --landing with one capability sentence a day-one PM could read, with the result in plain words and no flags or identifiers.';
|
|
426
|
+
console.error(warning);
|
|
427
|
+
return warning;
|
|
428
|
+
}
|
|
429
|
+
|
|
384
430
|
function numericFlag(args, name) {
|
|
385
431
|
const value = flag(args, name);
|
|
386
432
|
if (value === null || value === true || value === undefined) return null;
|
|
@@ -597,18 +643,19 @@ function buildPendingReviewChatPredicate(taskDb, db, workspaceRoot) {
|
|
|
597
643
|
function createReviewNextTask(taskDb, db, currentTask, title) {
|
|
598
644
|
const nextTitle = String(title || '').trim();
|
|
599
645
|
if (!nextTitle) return null;
|
|
646
|
+
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(nextTitle);
|
|
600
647
|
const currentMetadata = currentTask && currentTask.metadata && typeof currentTask.metadata === 'object'
|
|
601
648
|
? currentTask.metadata
|
|
602
649
|
: {};
|
|
603
650
|
const existing = findExistingReviewNextTask(taskDb, db, currentTask, nextTitle);
|
|
604
|
-
if (existing) return { id: existing.id, inserted: false };
|
|
651
|
+
if (existing) return { id: existing.id, inserted: false, operator_title_warning: operatorTitleWarning };
|
|
605
652
|
const goalId = currentMetadata.goal_id || currentMetadata.goalId || null;
|
|
606
653
|
const parentId = currentTask && currentTask.id || null;
|
|
607
654
|
const sourceKey = parentId && typeof taskDb.sourceKey === 'function'
|
|
608
655
|
? taskDb.sourceKey(`task_review_next:${parentId}`, nextTitle)
|
|
609
656
|
: null;
|
|
610
657
|
try {
|
|
611
|
-
|
|
658
|
+
const created = taskDb.addTask(db, {
|
|
612
659
|
title: nextTitle,
|
|
613
660
|
tag: currentTask && currentTask.tag || null,
|
|
614
661
|
workspaceRoot: taskDb.workspaceRoot(),
|
|
@@ -619,10 +666,11 @@ function createReviewNextTask(taskDb, db, currentTask, title) {
|
|
|
619
666
|
source: 'task_review_next',
|
|
620
667
|
},
|
|
621
668
|
});
|
|
669
|
+
return { ...created, operator_title_warning: operatorTitleWarning };
|
|
622
670
|
} catch (error) {
|
|
623
671
|
if (sourceKey && /constraint|unique/i.test(String(error && (error.code || error.message) || error))) {
|
|
624
672
|
const racedExisting = findExistingReviewNextTask(taskDb, db, currentTask, nextTitle);
|
|
625
|
-
if (racedExisting) return { id: racedExisting.id, inserted: false };
|
|
673
|
+
if (racedExisting) return { id: racedExisting.id, inserted: false, operator_title_warning: operatorTitleWarning };
|
|
626
674
|
}
|
|
627
675
|
throw error;
|
|
628
676
|
}
|
|
@@ -1069,6 +1117,50 @@ function taskReviewSummary(task) {
|
|
|
1069
1117
|
return reviewSummaryWithVerificationChat(task, review);
|
|
1070
1118
|
}
|
|
1071
1119
|
|
|
1120
|
+
function taskReviewInspectMetadata(task) {
|
|
1121
|
+
const review = task.review || taskReviewSummary(task);
|
|
1122
|
+
if (!review) return null;
|
|
1123
|
+
return {
|
|
1124
|
+
approval_status: review.approval_status || null,
|
|
1125
|
+
agent_certified: review.agent_certified === true,
|
|
1126
|
+
agent_review_pass_count: review.agent_review_pass_count || null,
|
|
1127
|
+
agent_certification_policy: review.agent_certification_policy || null,
|
|
1128
|
+
summary: review.summary || null,
|
|
1129
|
+
proof: review.proof || null,
|
|
1130
|
+
lesson: review.lesson || null,
|
|
1131
|
+
next_task: review.next_task || null,
|
|
1132
|
+
human_revision_count: review.human_revision_count || null,
|
|
1133
|
+
human_revision_note: review.human_revision_note || null,
|
|
1134
|
+
reward: review.reward ?? null,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function taskInspectFieldValues(task, fields) {
|
|
1139
|
+
const values = {};
|
|
1140
|
+
for (const field of fields) {
|
|
1141
|
+
switch (field) {
|
|
1142
|
+
case 'status':
|
|
1143
|
+
values.status = task.status || null;
|
|
1144
|
+
break;
|
|
1145
|
+
case 'title':
|
|
1146
|
+
values.title = task.title || null;
|
|
1147
|
+
break;
|
|
1148
|
+
case 'claimed_by':
|
|
1149
|
+
values.claimed_by = task.claimed_by || null;
|
|
1150
|
+
break;
|
|
1151
|
+
case 'tag':
|
|
1152
|
+
values.tag = task.tag || null;
|
|
1153
|
+
break;
|
|
1154
|
+
case 'review':
|
|
1155
|
+
values.review = taskReviewInspectMetadata(task);
|
|
1156
|
+
break;
|
|
1157
|
+
default:
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return values;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1072
1164
|
function reviewSummaryWithVerificationChat(task, review) {
|
|
1073
1165
|
if (!review || task.status !== 'review' || review.approval_status !== 'pending') return review;
|
|
1074
1166
|
const verifierTask = taskWithReviewEvidence(task, {
|
|
@@ -3168,6 +3260,11 @@ function formatTaskQueueScope(scope = {}) {
|
|
|
3168
3260
|
.join(' ');
|
|
3169
3261
|
}
|
|
3170
3262
|
|
|
3263
|
+
function noOpenTasksMessage(scope = {}) {
|
|
3264
|
+
const scopeText = formatTaskQueueScope(scope);
|
|
3265
|
+
return scopeText ? `No open tasks for ${scopeText}.` : 'No open tasks.';
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3171
3268
|
function taskQueueContract(projection, { reviewer = 'codex-review', limit = 8, scope = {}, hasExistingReviewFollowUp = null, hasPendingReviewChat = null, excludeTaskIds = null } = {}) {
|
|
3172
3269
|
const normalizedScope = normalizeTaskQueueScope(scope);
|
|
3173
3270
|
const tasks = filterTasksByScope(sortTasksNewestFirst(projection.tasks || []), normalizedScope, { hasExistingReviewFollowUp });
|
|
@@ -4885,6 +4982,7 @@ function cmdAdd(args) {
|
|
|
4885
4982
|
const taskDb = getTaskDb();
|
|
4886
4983
|
const db = taskDb.open();
|
|
4887
4984
|
const ws = taskDb.workspaceRoot();
|
|
4985
|
+
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
|
|
4888
4986
|
// Generation throttle — the named root cause is generation > human-review rate. An AGENT cannot keep
|
|
4889
4987
|
// minting tasks while a wall of certified-but-unaccepted work waits; that treadmill is what accept-group
|
|
4890
4988
|
// only drains. Humans and --force bypass. Closes the tap instead of just enlarging the bucket.
|
|
@@ -4910,6 +5008,7 @@ function cmdAdd(args) {
|
|
|
4910
5008
|
action: 'created',
|
|
4911
5009
|
task_id: result.id,
|
|
4912
5010
|
inserted: result.inserted !== false,
|
|
5011
|
+
operator_title_warning: operatorTitleWarning,
|
|
4913
5012
|
projection_path: outPath,
|
|
4914
5013
|
task,
|
|
4915
5014
|
});
|
|
@@ -4952,6 +5051,7 @@ function cmdDelegate(args) {
|
|
|
4952
5051
|
const taskDb = getTaskDb();
|
|
4953
5052
|
const db = taskDb.open();
|
|
4954
5053
|
const ws = taskDb.workspaceRoot();
|
|
5054
|
+
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
|
|
4955
5055
|
const ownerResolution = resolveFunctionalTaskOwner({
|
|
4956
5056
|
requestedOwner: requestedOwner && requestedOwner !== true ? requestedOwner : null,
|
|
4957
5057
|
title,
|
|
@@ -5007,6 +5107,7 @@ function cmdDelegate(args) {
|
|
|
5007
5107
|
executed_by: executedBy || null,
|
|
5008
5108
|
via,
|
|
5009
5109
|
handoff,
|
|
5110
|
+
operator_title_warning: operatorTitleWarning,
|
|
5010
5111
|
projection_path: outPath,
|
|
5011
5112
|
task,
|
|
5012
5113
|
});
|
|
@@ -5135,20 +5236,24 @@ function cmdHome(args) {
|
|
|
5135
5236
|
function cmdList(args) {
|
|
5136
5237
|
const all = hasFlag(args, '--all');
|
|
5137
5238
|
const status = flag(args, '--status');
|
|
5239
|
+
const scope = taskQueueScopeFromArgs(args);
|
|
5240
|
+
const scoped = !taskQueueScopeIsEmpty(scope);
|
|
5138
5241
|
const taskDb = getTaskDb();
|
|
5139
5242
|
const db = taskDb.open();
|
|
5140
|
-
const
|
|
5243
|
+
const rawRows = taskDb.listTasks(db, {
|
|
5141
5244
|
workspaceRoot: all ? null : taskDb.workspaceRoot(),
|
|
5142
5245
|
status: typeof status === 'string' ? status : null,
|
|
5143
|
-
limit: 200,
|
|
5246
|
+
limit: scoped ? null : 200,
|
|
5144
5247
|
});
|
|
5248
|
+
const rows = filterTasksByScope(rawRows, scope);
|
|
5145
5249
|
const displayRows = taskDb.withTaskDisplayRefs(rows, workspaceRefRows(taskDb, db, all));
|
|
5146
5250
|
if (wantsJson(args)) {
|
|
5147
|
-
printJson({ ok: true, action: 'list', tasks: displayRows });
|
|
5251
|
+
printJson({ ok: true, action: 'list', scope: normalizeTaskQueueScope(scope), tasks: displayRows });
|
|
5148
5252
|
return;
|
|
5149
5253
|
}
|
|
5150
5254
|
if (rows.length === 0) {
|
|
5151
|
-
|
|
5255
|
+
const scopeText = formatTaskQueueScope(scope);
|
|
5256
|
+
console.log(scopeText ? `(no tasks for ${scopeText})` : '(no tasks)');
|
|
5152
5257
|
return;
|
|
5153
5258
|
}
|
|
5154
5259
|
for (const r of displayRows) {
|
|
@@ -5184,19 +5289,68 @@ function cmdClaim(args) {
|
|
|
5184
5289
|
}
|
|
5185
5290
|
console.log(`claimed ${taskRef(compactTaskFromProjection(projection, taskId))} as ${owner}`);
|
|
5186
5291
|
} else {
|
|
5292
|
+
const recoveryCommand = result.reason === 'already_claimed' && result.claimed_by
|
|
5293
|
+
? `atris task release ${id} --as ${result.claimed_by}`
|
|
5294
|
+
: null;
|
|
5187
5295
|
if (wantsJson(args)) {
|
|
5188
5296
|
printJson({
|
|
5189
5297
|
ok: false,
|
|
5190
5298
|
command: 'atris task claim',
|
|
5191
5299
|
reason: result.reason,
|
|
5192
5300
|
claimed_by: result.claimed_by || null,
|
|
5301
|
+
recovery_command: recoveryCommand,
|
|
5193
5302
|
detail: `claim failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`,
|
|
5194
5303
|
});
|
|
5195
5304
|
process.exit(1);
|
|
5196
5305
|
}
|
|
5197
5306
|
console.error(`claim failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`);
|
|
5307
|
+
if (recoveryCommand) console.error(`Recovery: ${recoveryCommand}`);
|
|
5308
|
+
process.exit(1);
|
|
5309
|
+
}
|
|
5310
|
+
}
|
|
5311
|
+
|
|
5312
|
+
function cmdRelease(args) {
|
|
5313
|
+
const pos = positional(args);
|
|
5314
|
+
const id = pos[0];
|
|
5315
|
+
if (!id) {
|
|
5316
|
+
failTask('atris task release', 'missing_id', 'id required');
|
|
5317
|
+
}
|
|
5318
|
+
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
5319
|
+
const taskDb = getTaskDb();
|
|
5320
|
+
const db = taskDb.open();
|
|
5321
|
+
const taskId = requireTaskId(taskDb, db, id, 'atris task release');
|
|
5322
|
+
const result = taskDb.releaseTask(db, { id: taskId, actor: String(owner) });
|
|
5323
|
+
if (result.released) {
|
|
5324
|
+
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
5325
|
+
const task = compactTaskFromProjection(projection, taskId);
|
|
5326
|
+
if (wantsJson(args)) {
|
|
5327
|
+
printJson({
|
|
5328
|
+
ok: true,
|
|
5329
|
+
action: 'released',
|
|
5330
|
+
task_id: taskId,
|
|
5331
|
+
owner: String(owner),
|
|
5332
|
+
projection_path: outPath,
|
|
5333
|
+
task,
|
|
5334
|
+
event_version: result.event && result.event.version || null,
|
|
5335
|
+
});
|
|
5336
|
+
return;
|
|
5337
|
+
}
|
|
5338
|
+
console.log(`released ${taskRef(task)} from ${owner}`);
|
|
5339
|
+
console.log(`Next: atris task next --as ${owner}`);
|
|
5340
|
+
return;
|
|
5341
|
+
}
|
|
5342
|
+
if (wantsJson(args)) {
|
|
5343
|
+
printJson({
|
|
5344
|
+
ok: false,
|
|
5345
|
+
command: 'atris task release',
|
|
5346
|
+
reason: result.reason,
|
|
5347
|
+
claimed_by: result.claimed_by || null,
|
|
5348
|
+
detail: `release failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`,
|
|
5349
|
+
});
|
|
5198
5350
|
process.exit(1);
|
|
5199
5351
|
}
|
|
5352
|
+
console.error(`release failed: ${result.reason}${result.claimed_by ? ` (held by ${result.claimed_by})` : ''}`);
|
|
5353
|
+
process.exit(1);
|
|
5200
5354
|
}
|
|
5201
5355
|
|
|
5202
5356
|
function liveTaskWithTitle(tasks, title) {
|
|
@@ -5271,6 +5425,7 @@ function buildEndgameTaskSeed({ slug, horizon, owner }) {
|
|
|
5271
5425
|
|
|
5272
5426
|
function createEndgameSeedTask(taskDb, db, seed, owner) {
|
|
5273
5427
|
const taskOwner = String(owner || DEFAULT_OWNER);
|
|
5428
|
+
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(seed.title);
|
|
5274
5429
|
const result = taskDb.addTask(db, {
|
|
5275
5430
|
title: seed.title,
|
|
5276
5431
|
tag: seed.tag,
|
|
@@ -5294,20 +5449,23 @@ function createEndgameSeedTask(taskDb, db, seed, owner) {
|
|
|
5294
5449
|
ok: true,
|
|
5295
5450
|
task_id: result.id,
|
|
5296
5451
|
inserted: result.inserted !== false,
|
|
5452
|
+
operator_title_warning: operatorTitleWarning,
|
|
5297
5453
|
note_version: note.event.version,
|
|
5298
5454
|
};
|
|
5299
5455
|
}
|
|
5300
5456
|
|
|
5301
5457
|
function cmdNext(args) {
|
|
5302
5458
|
const owner = flag(args, '--as') || DEFAULT_OWNER;
|
|
5459
|
+
const scope = taskQueueScopeFromArgs(args);
|
|
5460
|
+
const scoped = !taskQueueScopeIsEmpty(scope);
|
|
5303
5461
|
const taskDb = getTaskDb();
|
|
5304
5462
|
const db = taskDb.open();
|
|
5305
|
-
const claimed = taskDb.listTasks(db, {
|
|
5463
|
+
const claimed = filterTasksByScope(taskDb.listTasks(db, {
|
|
5306
5464
|
workspaceRoot: taskDb.workspaceRoot(),
|
|
5307
5465
|
status: 'claimed',
|
|
5308
5466
|
claimedBy: String(owner),
|
|
5309
|
-
limit: 1,
|
|
5310
|
-
});
|
|
5467
|
+
limit: scoped ? null : 1,
|
|
5468
|
+
}), scope);
|
|
5311
5469
|
if (claimed.length) {
|
|
5312
5470
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
5313
5471
|
if (wantsJson(args)) {
|
|
@@ -5316,6 +5474,7 @@ function cmdNext(args) {
|
|
|
5316
5474
|
action: 'current',
|
|
5317
5475
|
task_id: claimed[0].id,
|
|
5318
5476
|
owner: String(owner),
|
|
5477
|
+
scope: normalizeTaskQueueScope(scope),
|
|
5319
5478
|
projection_path: outPath,
|
|
5320
5479
|
task: compactTaskFromProjection(projection, claimed[0].id),
|
|
5321
5480
|
});
|
|
@@ -5326,14 +5485,14 @@ function cmdNext(args) {
|
|
|
5326
5485
|
return;
|
|
5327
5486
|
}
|
|
5328
5487
|
const reviewProjection = writeDefaultProjection(taskDb, db);
|
|
5329
|
-
const reviewTasks = (reviewProjection.projection.tasks || [])
|
|
5488
|
+
const reviewTasks = filterTasksByScope(reviewProjection.projection.tasks || [], scope)
|
|
5330
5489
|
.map(compactTaskForStatus)
|
|
5331
5490
|
.filter(task => task && task.review && task.review.handoff);
|
|
5332
|
-
const open = taskDb.listTasks(db, {
|
|
5491
|
+
const open = filterTasksByScope(taskDb.listTasks(db, {
|
|
5333
5492
|
workspaceRoot: taskDb.workspaceRoot(),
|
|
5334
5493
|
status: 'open',
|
|
5335
|
-
limit: 1,
|
|
5336
|
-
});
|
|
5494
|
+
limit: scoped ? null : 1,
|
|
5495
|
+
}), scope);
|
|
5337
5496
|
if (!open.length) {
|
|
5338
5497
|
const { projection, outPath } = reviewProjection;
|
|
5339
5498
|
const reviewTask = reviewTasks.find(task => task.review.handoff.next_action === 'agent_review_again')
|
|
@@ -5363,10 +5522,12 @@ function cmdNext(args) {
|
|
|
5363
5522
|
action: 'created_next',
|
|
5364
5523
|
task_id: created.task_id,
|
|
5365
5524
|
owner: String(owner),
|
|
5525
|
+
scope: normalizeTaskQueueScope(scope),
|
|
5366
5526
|
projection_path: createdOutPath,
|
|
5367
5527
|
handoff,
|
|
5368
5528
|
next_agent_action: nextAgentAction,
|
|
5369
5529
|
note_version: created.note_version,
|
|
5530
|
+
operator_title_warning: created.operator_title_warning || null,
|
|
5370
5531
|
task: createdTask,
|
|
5371
5532
|
review_task: reviewTask,
|
|
5372
5533
|
});
|
|
@@ -5384,6 +5545,7 @@ function cmdNext(args) {
|
|
|
5384
5545
|
action: handoff.next_action,
|
|
5385
5546
|
task_id: handoff.next_action === 'continue_work' ? null : reviewTask.id,
|
|
5386
5547
|
owner: String(owner),
|
|
5548
|
+
scope: normalizeTaskQueueScope(scope),
|
|
5387
5549
|
projection_path: outPath,
|
|
5388
5550
|
handoff,
|
|
5389
5551
|
next_agent_action: nextAgentAction,
|
|
@@ -5393,7 +5555,7 @@ function cmdNext(args) {
|
|
|
5393
5555
|
});
|
|
5394
5556
|
return;
|
|
5395
5557
|
}
|
|
5396
|
-
console.log(
|
|
5558
|
+
console.log(noOpenTasksMessage(scope));
|
|
5397
5559
|
console.log(handoff.next_action === 'agent_review_again'
|
|
5398
5560
|
? `${taskRef(reviewTask)} needs one more agent check before approval.`
|
|
5399
5561
|
: `${taskRef(reviewTask)} is ready for approval.`);
|
|
@@ -5418,11 +5580,12 @@ function cmdNext(args) {
|
|
|
5418
5580
|
action: 'none',
|
|
5419
5581
|
task_id: null,
|
|
5420
5582
|
owner: String(owner),
|
|
5583
|
+
scope: normalizeTaskQueueScope(scope),
|
|
5421
5584
|
projection_path: outPath,
|
|
5422
5585
|
});
|
|
5423
5586
|
return;
|
|
5424
5587
|
}
|
|
5425
|
-
console.log(
|
|
5588
|
+
console.log(noOpenTasksMessage(scope));
|
|
5426
5589
|
console.log('Start with: atris task new "..."');
|
|
5427
5590
|
return;
|
|
5428
5591
|
}
|
|
@@ -5438,6 +5601,7 @@ function cmdNext(args) {
|
|
|
5438
5601
|
action: 'next',
|
|
5439
5602
|
task_id: open[0].id,
|
|
5440
5603
|
owner: String(owner),
|
|
5604
|
+
scope: normalizeTaskQueueScope(scope),
|
|
5441
5605
|
projection_path: outPath,
|
|
5442
5606
|
task: compactTaskFromProjection(projection, open[0].id),
|
|
5443
5607
|
});
|
|
@@ -5445,6 +5609,9 @@ function cmdNext(args) {
|
|
|
5445
5609
|
}
|
|
5446
5610
|
console.log(`next ${taskRef(compactTaskFromProjection(projection, open[0].id))} @${owner}`);
|
|
5447
5611
|
console.log(open[0].title);
|
|
5612
|
+
const ref = taskRef(compactTaskFromProjection(projection, open[0].id));
|
|
5613
|
+
const { printOperatorNext } = require('../lib/operator-next');
|
|
5614
|
+
printOperatorNext(`atris task step ${ref}`);
|
|
5448
5615
|
}
|
|
5449
5616
|
|
|
5450
5617
|
function continueWorkForReviewTask(taskDb, db, taskId, { owner = DEFAULT_OWNER } = {}) {
|
|
@@ -5488,6 +5655,7 @@ function continueWorkForReviewTask(taskDb, db, taskId, { owner = DEFAULT_OWNER }
|
|
|
5488
5655
|
parent_task_id: taskId,
|
|
5489
5656
|
next_task_id: nextCreated ? nextCreated.id : null,
|
|
5490
5657
|
created: Boolean(nextCreated && nextCreated.inserted !== false),
|
|
5658
|
+
operator_title_warning: nextCreated ? nextCreated.operator_title_warning || null : null,
|
|
5491
5659
|
owner: String(owner || DEFAULT_OWNER),
|
|
5492
5660
|
projection_path: outPath,
|
|
5493
5661
|
parent,
|
|
@@ -5790,6 +5958,40 @@ function cmdClearPlan(args) {
|
|
|
5790
5958
|
console.log(`clear-plan moved ${result.cleared.length} task${result.cleared.length === 1 ? '' : 's'} to Backlog`);
|
|
5791
5959
|
}
|
|
5792
5960
|
|
|
5961
|
+
function cmdInspect(args) {
|
|
5962
|
+
const parsed = readFieldsFlag(args, '--fields');
|
|
5963
|
+
if (!parsed || parsed.error) {
|
|
5964
|
+
failTask('atris task inspect', 'missing_fields', parsed?.error || 'Usage: atris task inspect <id> --fields review,status,title [--json]');
|
|
5965
|
+
}
|
|
5966
|
+
const fieldError = validateFields(parsed.fields, TASK_INSPECT_FIELDS, 'task');
|
|
5967
|
+
if (fieldError) failTask('atris task inspect', 'unknown_fields', fieldError);
|
|
5968
|
+
const ref = stripInspectArgs(args)[0] || '';
|
|
5969
|
+
if (!ref) {
|
|
5970
|
+
failTask('atris task inspect', 'missing_id', 'Usage: atris task inspect <id> --fields review,status,title [--json]');
|
|
5971
|
+
}
|
|
5972
|
+
const taskDb = getTaskDb();
|
|
5973
|
+
const db = taskDb.open();
|
|
5974
|
+
const taskId = requireTaskId(taskDb, db, ref, 'atris task inspect');
|
|
5975
|
+
const projection = enrichTaskProjection(taskDb.taskProjection(db, { taskId }));
|
|
5976
|
+
const task = projection.tasks[0];
|
|
5977
|
+
if (!task) {
|
|
5978
|
+
failTask('atris task inspect', 'not_found', `task not found: ${ref}`, 1);
|
|
5979
|
+
}
|
|
5980
|
+
const values = taskInspectFieldValues(task, parsed.fields);
|
|
5981
|
+
const payload = buildInspectPayload({
|
|
5982
|
+
action: 'task_inspect',
|
|
5983
|
+
idKey: 'task_id',
|
|
5984
|
+
idValue: task.id,
|
|
5985
|
+
fields: parsed.fields,
|
|
5986
|
+
values,
|
|
5987
|
+
});
|
|
5988
|
+
if (wantsJson(args)) {
|
|
5989
|
+
printJson(payload);
|
|
5990
|
+
return;
|
|
5991
|
+
}
|
|
5992
|
+
for (const line of inspectTextLines(parsed.fields, values)) console.log(line);
|
|
5993
|
+
}
|
|
5994
|
+
|
|
5793
5995
|
function cmdShow(args) {
|
|
5794
5996
|
const pos = positional(args);
|
|
5795
5997
|
const id = pos[0];
|
|
@@ -7549,6 +7751,7 @@ function cmdFinish(args) {
|
|
|
7549
7751
|
const currentTask = taskDb.getTask(db, taskId);
|
|
7550
7752
|
const actor = String(flag(args, '--as') || DEFAULT_OWNER);
|
|
7551
7753
|
const proof = proofFlagValue(args);
|
|
7754
|
+
const landing = landingFlags(args);
|
|
7552
7755
|
const failed = hasFlag(args, '--failed');
|
|
7553
7756
|
const hasReview = hasFlag(args, '--review') || flag(args, '--lesson') || flag(args, '--next') || flag(args, '--proof') || flag(args, '--reward');
|
|
7554
7757
|
if (agentProofOnlyMode() && !failed) {
|
|
@@ -7585,6 +7788,7 @@ function cmdFinish(args) {
|
|
|
7585
7788
|
process.exit(1);
|
|
7586
7789
|
}
|
|
7587
7790
|
if (hasReview) {
|
|
7791
|
+
const landingAdvisory = !failed ? warnIfLandingNeedsDayOnePm(landing, currentTask && currentTask.title) : null;
|
|
7588
7792
|
const result = taskDb.reviewTask(db, {
|
|
7589
7793
|
id: taskId,
|
|
7590
7794
|
actor,
|
|
@@ -7593,6 +7797,7 @@ function cmdFinish(args) {
|
|
|
7593
7797
|
nextTask: typeof flag(args, '--next') === 'string' ? flag(args, '--next') : '',
|
|
7594
7798
|
proof,
|
|
7595
7799
|
careerXpEligible: false,
|
|
7800
|
+
landing,
|
|
7596
7801
|
});
|
|
7597
7802
|
const nextCreated = createNextTaskIfRequested(taskDb, db, args, currentTask, result.episode.next_task_suggestion);
|
|
7598
7803
|
const xpProjection = refreshCareerXpAfterReview(result);
|
|
@@ -7606,6 +7811,7 @@ function cmdFinish(args) {
|
|
|
7606
7811
|
reward: result.episode.reward.value,
|
|
7607
7812
|
episode: result.episode,
|
|
7608
7813
|
xp_projection: xpProjection,
|
|
7814
|
+
landing_advisory: landingAdvisory,
|
|
7609
7815
|
next_task_id: nextCreated ? nextCreated.id : null,
|
|
7610
7816
|
projection_path: outPath,
|
|
7611
7817
|
task: compactTaskFromProjection(projection, taskId),
|
|
@@ -7700,6 +7906,7 @@ function cmdReady(args) {
|
|
|
7700
7906
|
console.error(`ready failed: ${result.reason}`);
|
|
7701
7907
|
process.exit(1);
|
|
7702
7908
|
}
|
|
7909
|
+
const landingAdvisory = warnIfLandingNeedsDayOnePm(landing, result.row && result.row.title);
|
|
7703
7910
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
7704
7911
|
const agentCertified = result.event.payload.agent_certified === true;
|
|
7705
7912
|
const projectionTask = taskFromProjection(projection, taskId)
|
|
@@ -7741,6 +7948,7 @@ function cmdReady(args) {
|
|
|
7741
7948
|
agent_certified: agentCertified,
|
|
7742
7949
|
handoff,
|
|
7743
7950
|
result_trace: resultTrace,
|
|
7951
|
+
landing_advisory: landingAdvisory,
|
|
7744
7952
|
...(nextTaskInput.ignored ? { review_next_task_ignored: nextTaskInput.ignored } : {}),
|
|
7745
7953
|
projection_path: outPath,
|
|
7746
7954
|
task: compactTaskFromProjection(projection, taskId),
|
|
@@ -7909,6 +8117,151 @@ function acceptReviewTask(taskDb, db, taskId, { actor, proof, reward, lesson = '
|
|
|
7909
8117
|
return { ok: true, reviewed };
|
|
7910
8118
|
}
|
|
7911
8119
|
|
|
8120
|
+
// Second-actor certification for proof-backed Review rows whose proof names a
|
|
8121
|
+
// runnable, allowlisted check. Re-running that check as a distinct actor IS
|
|
8122
|
+
// the independent verification the certification gate asks for — the row
|
|
8123
|
+
// becomes certified and autoland can land it on the same heartbeat. Rows in
|
|
8124
|
+
// denied lanes, without a runnable check, or already certified keep their
|
|
8125
|
+
// existing paths (review chats, human accept).
|
|
8126
|
+
function certifyVerifyCandidate(task) {
|
|
8127
|
+
const metadata = task.metadata || {};
|
|
8128
|
+
const review = task.review || {};
|
|
8129
|
+
const recorded = typeof metadata.verify === 'string' && metadata.verify.trim() ? [metadata.verify.trim()] : [];
|
|
8130
|
+
const proof = String(review.proof || metadata.latest_agent_proof || '');
|
|
8131
|
+
for (const raw of [...recorded, ...taskReviewEvidenceCommands(proof)]) {
|
|
8132
|
+
// The evidence extractor can keep trailing prose ("git diff --check. Evidence
|
|
8133
|
+
// inspected: ..."); try the full clause, then the first sentence of it.
|
|
8134
|
+
for (const candidate of [raw, raw.split(/\.(?:\s|$)/)[0]].map((c) => String(c || '').trim())) {
|
|
8135
|
+
if (candidate && parseVerifyCommand(candidate).ok) return candidate;
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
return null;
|
|
8139
|
+
}
|
|
8140
|
+
|
|
8141
|
+
function stampCertifyVerifyMetadata(taskDb, db, taskId, actor, verify) {
|
|
8142
|
+
const row = taskDb.getTask(db, taskId);
|
|
8143
|
+
if (!row) return;
|
|
8144
|
+
const metadata = row.metadata && typeof row.metadata === 'object' ? { ...row.metadata } : {};
|
|
8145
|
+
metadata.verify = metadata.verify || verify;
|
|
8146
|
+
metadata.certified_verified_at = new Date().toISOString();
|
|
8147
|
+
metadata.certified_verified_by = actor;
|
|
8148
|
+
db.prepare(`
|
|
8149
|
+
UPDATE tasks
|
|
8150
|
+
SET metadata = ?,
|
|
8151
|
+
updated_at = ?
|
|
8152
|
+
WHERE id = ?
|
|
8153
|
+
`).run(JSON.stringify(metadata), Date.now(), taskId);
|
|
8154
|
+
}
|
|
8155
|
+
|
|
8156
|
+
function cmdCertifyVerified(args) {
|
|
8157
|
+
const dryRun = hasFlag(args, '--dry-run');
|
|
8158
|
+
const asJson = wantsJson(args);
|
|
8159
|
+
const actor = String(flag(args, '--as') || 'autoland-verifier');
|
|
8160
|
+
const limitRaw = flag(args, '--limit');
|
|
8161
|
+
const max = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 6) : 6;
|
|
8162
|
+
|
|
8163
|
+
const taskDb = getTaskDb();
|
|
8164
|
+
const db = taskDb.open();
|
|
8165
|
+
const { projection } = writeDefaultProjection(taskDb, db);
|
|
8166
|
+
const candidates = (projection.tasks || [])
|
|
8167
|
+
.filter((t) => t && t.status === 'review')
|
|
8168
|
+
.sort((a, b) => Number(b.updated_at || 0) - Number(a.updated_at || 0))
|
|
8169
|
+
.slice(0, max);
|
|
8170
|
+
const results = [];
|
|
8171
|
+
for (const item of candidates) {
|
|
8172
|
+
const fullProjection = enrichTaskProjection(taskDb.taskProjection(db, { taskId: item.id }));
|
|
8173
|
+
const task = fullProjection.tasks[0] || null;
|
|
8174
|
+
if (!task) {
|
|
8175
|
+
results.push({ ref: item.display_id || item.id, action: 'skipped', reason: 'task_not_found' });
|
|
8176
|
+
continue;
|
|
8177
|
+
}
|
|
8178
|
+
const ref = task.display_id || task.legacy_ref || task.id;
|
|
8179
|
+
const metadata = task.metadata || {};
|
|
8180
|
+
const review = task.review || {};
|
|
8181
|
+
if (task.status !== 'review') {
|
|
8182
|
+
results.push({ ref, action: 'skipped', reason: 'not_in_review' });
|
|
8183
|
+
continue;
|
|
8184
|
+
}
|
|
8185
|
+
const approval = String(review.approval_status || metadata.approval_status || 'pending').toLowerCase();
|
|
8186
|
+
if (approval !== 'pending') {
|
|
8187
|
+
results.push({ ref, action: 'skipped', reason: `approval_${approval}` });
|
|
8188
|
+
continue;
|
|
8189
|
+
}
|
|
8190
|
+
const tag = String(task.tag || '').toLowerCase();
|
|
8191
|
+
if (DENIED_TAGS.has(tag)) {
|
|
8192
|
+
results.push({ ref, action: 'skipped', reason: `denied_tag_${tag}` });
|
|
8193
|
+
continue;
|
|
8194
|
+
}
|
|
8195
|
+
// Skip only rows the accept lane can already land, or rows blocked by
|
|
8196
|
+
// something an executed second-actor check cannot cure. A row with two
|
|
8197
|
+
// passes from ONE actor is exactly what this command exists to cure —
|
|
8198
|
+
// "certified" alone is not landable.
|
|
8199
|
+
const evaluation = evaluateAutoAccept(task, { strictVerify: false });
|
|
8200
|
+
if (evaluation.eligible) {
|
|
8201
|
+
results.push({ ref, action: 'skipped', reason: 'already_landable' });
|
|
8202
|
+
continue;
|
|
8203
|
+
}
|
|
8204
|
+
const curable = ['not_agent_certified', 'needs_second_reviewer_or_third_pass', 'insufficient_review_passes'];
|
|
8205
|
+
if (!curable.includes(evaluation.reason)) {
|
|
8206
|
+
results.push({ ref, action: 'skipped', reason: evaluation.reason });
|
|
8207
|
+
continue;
|
|
8208
|
+
}
|
|
8209
|
+
const verify = certifyVerifyCandidate(task);
|
|
8210
|
+
if (!verify) {
|
|
8211
|
+
results.push({ ref, action: 'skipped', reason: 'no_runnable_check_in_proof' });
|
|
8212
|
+
continue;
|
|
8213
|
+
}
|
|
8214
|
+
if (dryRun) {
|
|
8215
|
+
results.push({ ref, action: 'would_certify', verify });
|
|
8216
|
+
continue;
|
|
8217
|
+
}
|
|
8218
|
+
const run = runVerifyCommand(verify, task.workspace_root || process.cwd());
|
|
8219
|
+
if (!run.ok) {
|
|
8220
|
+
results.push({ ref, action: 'verify_failed', reason: run.reason, verify });
|
|
8221
|
+
continue;
|
|
8222
|
+
}
|
|
8223
|
+
const builderProof = String(review.proof || metadata.latest_agent_proof || '').slice(0, 200);
|
|
8224
|
+
const readied = taskDb.readyTask(db, {
|
|
8225
|
+
id: task.id,
|
|
8226
|
+
actor,
|
|
8227
|
+
proof: `Second-actor check: \`${verify}\` re-run by ${actor}, exited 0. Builder proof inspected: ${builderProof}`,
|
|
8228
|
+
lesson: '',
|
|
8229
|
+
nextTask: '',
|
|
8230
|
+
});
|
|
8231
|
+
if (!readied.ready) {
|
|
8232
|
+
results.push({ ref, action: 'certify_failed', reason: readied.reason, verify });
|
|
8233
|
+
continue;
|
|
8234
|
+
}
|
|
8235
|
+
stampCertifyVerifyMetadata(taskDb, db, task.id, actor, verify);
|
|
8236
|
+
const certifiedNow = readied.event?.payload?.agent_certified === true;
|
|
8237
|
+
results.push({ ref, action: certifiedNow ? 'certified' : 'pass_recorded', verify });
|
|
8238
|
+
}
|
|
8239
|
+
const { outPath } = writeDefaultProjection(taskDb, db);
|
|
8240
|
+
const certified = results.filter((r) => r.action === 'certified').length;
|
|
8241
|
+
const payload = {
|
|
8242
|
+
ok: true,
|
|
8243
|
+
action: dryRun ? 'certify_verified_dry_run' : 'certify_verified',
|
|
8244
|
+
actor,
|
|
8245
|
+
certified,
|
|
8246
|
+
would_certify: results.filter((r) => r.action === 'would_certify').length,
|
|
8247
|
+
skipped: results.filter((r) => r.action === 'skipped').length,
|
|
8248
|
+
failed: results.filter((r) => r.action === 'verify_failed' || r.action === 'certify_failed').length,
|
|
8249
|
+
results,
|
|
8250
|
+
projection_path: outPath,
|
|
8251
|
+
};
|
|
8252
|
+
if (asJson) {
|
|
8253
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
8254
|
+
} else if (results.length === 0) {
|
|
8255
|
+
console.log('certify-verified: no review rows to consider.');
|
|
8256
|
+
} else {
|
|
8257
|
+
for (const r of results) {
|
|
8258
|
+
console.log(`${r.action.padEnd(14)} ${r.ref}${r.verify ? ` \`${r.verify}\`` : ''}${r.reason ? ` (${r.reason})` : ''}`);
|
|
8259
|
+
}
|
|
8260
|
+
console.log(`certified ${certified}; humans keep denied lanes and rows without a runnable check.`);
|
|
8261
|
+
}
|
|
8262
|
+
return payload;
|
|
8263
|
+
}
|
|
8264
|
+
|
|
7912
8265
|
function cmdAutoAcceptCertified(args) {
|
|
7913
8266
|
const dryRun = hasFlag(args, '--dry-run');
|
|
7914
8267
|
const strictVerify = hasFlag(args, '--strict-verify');
|
|
@@ -9256,13 +9609,14 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
9256
9609
|
const body = await readJsonBody(req);
|
|
9257
9610
|
const title = String(body.title || '').trim();
|
|
9258
9611
|
if (!title) return sendJson(res, 400, { ok: false, reason: 'missing_title', detail: 'title required' });
|
|
9612
|
+
const operatorTitleWarning = warnIfTaskTitleNeedsOperatorWhy(title);
|
|
9259
9613
|
const result = taskDb.addTask(db, {
|
|
9260
9614
|
title,
|
|
9261
9615
|
tag: body.tag ? String(body.tag) : 'tasks',
|
|
9262
9616
|
workspaceRoot: taskDb.workspaceRoot(),
|
|
9263
9617
|
});
|
|
9264
9618
|
const { projection, outPath } = writeDefaultProjection(taskDb, db);
|
|
9265
|
-
return sendJson(res, 200, { ok: true, action: 'created', task_id: result.id, projection_path: outPath, task: taskFromProjection(projection, result.id) });
|
|
9619
|
+
return sendJson(res, 200, { ok: true, action: 'created', task_id: result.id, operator_title_warning: operatorTitleWarning, projection_path: outPath, task: taskFromProjection(projection, result.id) });
|
|
9266
9620
|
}
|
|
9267
9621
|
if (req.method === 'POST' && url.pathname === '/api/tasks/clear-plan') {
|
|
9268
9622
|
const body = await readJsonBody(req);
|
|
@@ -9713,6 +10067,9 @@ async function run(args) {
|
|
|
9713
10067
|
return cmdClearPlan(rest);
|
|
9714
10068
|
case 'claim': return cmdClaim(rest);
|
|
9715
10069
|
case 'start': return cmdClaim(rest);
|
|
10070
|
+
case 'release':
|
|
10071
|
+
case 'unclaim':
|
|
10072
|
+
return cmdRelease(rest);
|
|
9716
10073
|
case 'current':
|
|
9717
10074
|
case 'select':
|
|
9718
10075
|
return cmdCurrent(rest);
|
|
@@ -9758,6 +10115,7 @@ async function run(args) {
|
|
|
9758
10115
|
case 'note': return cmdNote(rest);
|
|
9759
10116
|
case 'say': return cmdNote(rest);
|
|
9760
10117
|
case 'show': return cmdShow(rest);
|
|
10118
|
+
case 'inspect': return cmdInspect(rest);
|
|
9761
10119
|
case 'page': return cmdPage(rest);
|
|
9762
10120
|
case 'step': return cmdStep(rest);
|
|
9763
10121
|
case 'review-chat':
|
|
@@ -9769,6 +10127,8 @@ async function run(args) {
|
|
|
9769
10127
|
case 'auto-accept-certified':
|
|
9770
10128
|
case 'auto-accept':
|
|
9771
10129
|
return cmdAutoAcceptCertified(rest);
|
|
10130
|
+
case 'certify-verified':
|
|
10131
|
+
return cmdCertifyVerified(rest);
|
|
9772
10132
|
case 'accept-group':
|
|
9773
10133
|
return cmdAcceptGroup(rest);
|
|
9774
10134
|
case 'revise': return cmdRevise(rest);
|