@tea-agent/loop-agent 0.35.3 → 0.36.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 +4 -2
- package/CHANGELOG.md +59 -0
- package/dist/application/task-lifecycle/advance.js +1 -0
- package/dist/application/task-lifecycle/observe.js +15 -0
- package/dist/application/task-lifecycle/plan-transitions.js +15 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init-upgrade.js +351 -19
- package/dist/commands/init.js +14 -67
- package/dist/commands/run-dag-progress.js +14 -0
- package/dist/commands/task-advance.js +33 -3
- package/dist/executors/dag-pi-executor.js +3 -1
- package/dist/shared/operator/capabilities.js +125 -11
- package/dist/task/source-prepare/completeness.js +17 -0
- package/dist/task/source-prepare/parse-intent.js +15 -1
- package/dist/worker/console/chat/artifact-card.js +8 -1
- package/dist/worker/console/chat/chat-event-store.js +61 -0
- package/dist/worker/console/chat/human-gate-card.js +9 -1
- package/dist/worker/console/chat/operation-card.js +71 -2
- package/dist/worker/console/chat/pi-runtime.js +69 -30
- package/dist/worker/console/chat/routes.js +27 -4
- package/dist/worker/console/chat/semantic-activity.js +465 -0
- package/dist/worker/console/chat/turn-process.js +31 -12
- package/dist/worker/console/operation-run-facts.js +190 -0
- package/dist/worker/console/operation-runner.js +107 -6
- package/dist/worker/console/operation-wait.js +314 -0
- package/dist/worker/console/operator-actions.js +153 -2
- package/dist/worker/console/static/assets/index-2OeZODxk.js +57 -0
- package/dist/worker/console/static/assets/index-DVJlUL8X.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
- package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +69 -31
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +2 -1
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +16 -5
- package/dist/worker/console/static-src/operator-chat/useComposer.js +30 -7
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +7 -3
- package/dist/worker/observe/static/operator-chrome.js +1 -1
- package/dist/workflows/dag/init-hybrid.js +74 -2
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +102 -67
- package/docs/templates/init-managed-agents.md +5 -2
- package/harness.json +2 -2
- package/package.json +1 -1
- package/skills/loop-agent/SKILL.md +1 -0
- package/skills/loop-agent/references/command-reference.md +2 -0
- package/dist/worker/console/static/assets/index-DRqZiQ7J.css +0 -1
- package/dist/worker/console/static/assets/index-DuVLjCIT.js +0 -57
|
@@ -22,6 +22,7 @@ import { clearSemanticIntakeAttempt } from "../../task/source-prepare/semantic-i
|
|
|
22
22
|
import nodePath from "node:path";
|
|
23
23
|
import { resolveSiblingAgentWorkerBin } from "./sibling-controller.js";
|
|
24
24
|
import { projectOperationEventSummary, projectOperationForChat, } from "./chat/chat-event-store.js";
|
|
25
|
+
import { OperationWaitError, waitForOperationChange, } from "./operation-wait.js";
|
|
25
26
|
import { MutationGateReceiptStore } from "./mutation-gate-receipt-store.js";
|
|
26
27
|
import { assessAutonomousExecutionEligibility, deriveEligibilityFactsFromDagSpec, } from "./dag-execution-receipt.js";
|
|
27
28
|
import { issueHumanGateToken, mutationGatePayloadHash, verifyHumanGateToken, } from "./human-gate-token.js";
|
|
@@ -42,6 +43,7 @@ const MUTATION_OR_LONG = new Set([
|
|
|
42
43
|
"contractApply",
|
|
43
44
|
"contractAdopt",
|
|
44
45
|
"contractRecover",
|
|
46
|
+
"taskAdvance",
|
|
45
47
|
"dagRunTask",
|
|
46
48
|
"runDag",
|
|
47
49
|
"dagRerun",
|
|
@@ -55,6 +57,13 @@ function paramsOf(req) {
|
|
|
55
57
|
function str(v) {
|
|
56
58
|
return typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
57
59
|
}
|
|
60
|
+
const TASK_ID_PLACEHOLDER_RE = /[<>]|^(?:id|task-?id|your-task-id|todo|tbd)$/i;
|
|
61
|
+
const TASK_ADVANCE_ALIAS_ACTIONS = new Set(["promoteRun", "closeoutTask"]);
|
|
62
|
+
function taskIdInputError(taskId) {
|
|
63
|
+
if (!TASK_ID_PLACEHOLDER_RE.test(taskId))
|
|
64
|
+
return undefined;
|
|
65
|
+
return `taskId must be a concrete canonical id; placeholder values are not allowed: ${JSON.stringify(taskId)}`;
|
|
66
|
+
}
|
|
58
67
|
/** Extract write-set gate token from task advance operator JSON. */
|
|
59
68
|
function extractWriteSetGateTokenFromCliJson(json, taskId) {
|
|
60
69
|
if (!json || typeof json !== "object")
|
|
@@ -135,7 +144,21 @@ function gateError(action, code, message, details) {
|
|
|
135
144
|
*/
|
|
136
145
|
async function assertAutonomousRerunEligible(ctx, runId, taskId) {
|
|
137
146
|
const report = await runReadCli(ctx, ["dag", "report", "--run-id", runId, "--json"], "dag report");
|
|
138
|
-
|
|
147
|
+
// `dag report --json` emits { schemaVersion, runs: [...] } — the run facts
|
|
148
|
+
// (primaryFailure / primaryRecovery) live on each entry of `runs`, not on
|
|
149
|
+
// the envelope top level. Reading them from the top level made every
|
|
150
|
+
// standaloneTaskRerun call fail closed with "cannot read dagReport run
|
|
151
|
+
// facts" even though the evidence was present (2026-08-15 parse drift).
|
|
152
|
+
const reportEnvelope = (report.ok ? report.result : undefined);
|
|
153
|
+
const reportRuns = reportEnvelope?.runs ?? [];
|
|
154
|
+
const matchedRun = reportRuns.find((entry) => entry.runId === runId) ?? reportRuns[0];
|
|
155
|
+
const reportResult = matchedRun ??
|
|
156
|
+
(reportEnvelope?.primaryFailure
|
|
157
|
+
? {
|
|
158
|
+
primaryFailure: reportEnvelope.primaryFailure,
|
|
159
|
+
primaryRecovery: reportEnvelope.primaryRecovery,
|
|
160
|
+
}
|
|
161
|
+
: undefined);
|
|
139
162
|
const recovery = reportResult?.primaryRecovery;
|
|
140
163
|
if (!report.ok || !reportResult?.primaryFailure || !recovery) {
|
|
141
164
|
return gateError("standaloneTaskRerun", "INVALID_INPUT", "cannot read dagReport run facts; standaloneTaskRerun requires server-side run evidence", { runId });
|
|
@@ -519,6 +542,66 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
519
542
|
}),
|
|
520
543
|
};
|
|
521
544
|
}
|
|
545
|
+
case "operationWait": {
|
|
546
|
+
const operationId = str(p.operationId);
|
|
547
|
+
if (!operationId)
|
|
548
|
+
return invalid(action, "operationId is required");
|
|
549
|
+
const rawAfterSeq = p.afterSeq ?? 0;
|
|
550
|
+
const afterSeq = typeof rawAfterSeq === "number" ? rawAfterSeq : Number(rawAfterSeq);
|
|
551
|
+
const rawMaxWaitMs = p.maxWaitMs;
|
|
552
|
+
const maxWaitMs = typeof rawMaxWaitMs === "number"
|
|
553
|
+
? rawMaxWaitMs
|
|
554
|
+
: rawMaxWaitMs === undefined
|
|
555
|
+
? undefined
|
|
556
|
+
: Number(rawMaxWaitMs);
|
|
557
|
+
const rawWakeOn = str(p.wakeOn);
|
|
558
|
+
const wakeOn = rawWakeOn === "all" || rawWakeOn === "meaningful" ? rawWakeOn : undefined;
|
|
559
|
+
try {
|
|
560
|
+
const result = await waitForOperationChange({
|
|
561
|
+
operations: ctx.operations,
|
|
562
|
+
events: ctx.events,
|
|
563
|
+
operationId,
|
|
564
|
+
afterSeq,
|
|
565
|
+
maxWaitMs,
|
|
566
|
+
...(wakeOn ? { wakeOn } : {}),
|
|
567
|
+
});
|
|
568
|
+
return {
|
|
569
|
+
kind: "sync",
|
|
570
|
+
status: 200,
|
|
571
|
+
body: operatorSucceeded(action, result),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
if (error instanceof OperationWaitError) {
|
|
576
|
+
if (error.code === "NOT_FOUND") {
|
|
577
|
+
return {
|
|
578
|
+
kind: "error",
|
|
579
|
+
status: 404,
|
|
580
|
+
body: operatorFailed({
|
|
581
|
+
command: action,
|
|
582
|
+
outcome: "not-found",
|
|
583
|
+
code: "NOT_FOUND",
|
|
584
|
+
message: error.message,
|
|
585
|
+
}),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
if (error.code === "EVENT_CURSOR_EXPIRED") {
|
|
589
|
+
return {
|
|
590
|
+
kind: "error",
|
|
591
|
+
status: 410,
|
|
592
|
+
body: operatorFailed({
|
|
593
|
+
command: action,
|
|
594
|
+
outcome: "blocked",
|
|
595
|
+
code: "EVENT_CURSOR_EXPIRED",
|
|
596
|
+
message: error.message,
|
|
597
|
+
}),
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
return invalid(action, error.message);
|
|
601
|
+
}
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
522
605
|
case "inspect": {
|
|
523
606
|
const body = await runReadCli(ctx, ["inspect", "--json"], "inspect");
|
|
524
607
|
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
@@ -1186,8 +1269,24 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1186
1269
|
};
|
|
1187
1270
|
}
|
|
1188
1271
|
const reviewPacket = extractReviewPacketFromCliJson(gen.json) ?? {};
|
|
1272
|
+
let parsedDagSpec;
|
|
1273
|
+
try {
|
|
1274
|
+
parsedDagSpec = JSON.parse(dagText);
|
|
1275
|
+
}
|
|
1276
|
+
catch (error) {
|
|
1277
|
+
return {
|
|
1278
|
+
kind: "error",
|
|
1279
|
+
status: 409,
|
|
1280
|
+
body: operatorFailed({
|
|
1281
|
+
command: action,
|
|
1282
|
+
outcome: "blocked",
|
|
1283
|
+
code: "DAG_GENERATION_FAILED",
|
|
1284
|
+
message: `generated DAG is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
1285
|
+
}),
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1189
1288
|
const derivedFacts = deriveEligibilityFactsFromDagSpec({
|
|
1190
|
-
dagSpec:
|
|
1289
|
+
dagSpec: parsedDagSpec,
|
|
1191
1290
|
allowedPaths,
|
|
1192
1291
|
forbiddenPaths,
|
|
1193
1292
|
});
|
|
@@ -1349,6 +1448,9 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1349
1448
|
if (!taskId || !clientRequestId) {
|
|
1350
1449
|
return invalid(action, "taskId and clientRequestId are required");
|
|
1351
1450
|
}
|
|
1451
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1452
|
+
if (taskIdError)
|
|
1453
|
+
return invalid(action, taskIdError);
|
|
1352
1454
|
return acceptOperation(ctx, {
|
|
1353
1455
|
action,
|
|
1354
1456
|
actionParams: p,
|
|
@@ -1357,6 +1459,38 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1357
1459
|
cliArgs: ["task", "advance", taskId, title, "--json"],
|
|
1358
1460
|
});
|
|
1359
1461
|
}
|
|
1462
|
+
case "taskAdvance": {
|
|
1463
|
+
const taskId = str(p.taskId);
|
|
1464
|
+
const clientRequestId = str(req.clientRequestId);
|
|
1465
|
+
if (!taskId || !clientRequestId) {
|
|
1466
|
+
return invalid(action, "taskId and clientRequestId are required");
|
|
1467
|
+
}
|
|
1468
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1469
|
+
if (taskIdError)
|
|
1470
|
+
return invalid(action, taskIdError);
|
|
1471
|
+
const args = ["task", "advance", taskId];
|
|
1472
|
+
const title = str(p.title);
|
|
1473
|
+
if (title)
|
|
1474
|
+
args.push(title);
|
|
1475
|
+
const taskKind = str(p.taskKind);
|
|
1476
|
+
if (taskKind)
|
|
1477
|
+
args.push("--task-kind", taskKind);
|
|
1478
|
+
const featureId = str(p.featureId);
|
|
1479
|
+
if (featureId)
|
|
1480
|
+
args.push("--feature-id", featureId);
|
|
1481
|
+
const profile = str(p.profile);
|
|
1482
|
+
if (profile)
|
|
1483
|
+
args.push("--profile", profile);
|
|
1484
|
+
appendEngineeringCliArgs(args, parseEngineeringBoundaryFromParams(p));
|
|
1485
|
+
args.push("--json");
|
|
1486
|
+
return acceptOperation(ctx, {
|
|
1487
|
+
action,
|
|
1488
|
+
actionParams: p,
|
|
1489
|
+
clientRequestId,
|
|
1490
|
+
taskId,
|
|
1491
|
+
cliArgs: args,
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1360
1494
|
case "bootstrapFromPrd":
|
|
1361
1495
|
return dispatchBootstrapFromPrd(ctx, p);
|
|
1362
1496
|
case "importPrd": {
|
|
@@ -1367,6 +1501,9 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1367
1501
|
if (!taskId || !content) {
|
|
1368
1502
|
return invalid(action, "taskId and content are required");
|
|
1369
1503
|
}
|
|
1504
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1505
|
+
if (taskIdError)
|
|
1506
|
+
return invalid(action, taskIdError);
|
|
1370
1507
|
const taskKindRaw = str(p.taskKind)?.trim();
|
|
1371
1508
|
const taskKind = normalizeConsoleWorkflowKind(taskKindRaw, "standard");
|
|
1372
1509
|
const taskKindExplicit = Boolean(taskKindRaw);
|
|
@@ -1561,6 +1698,9 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
1561
1698
|
if (!taskId || !clientRequestId) {
|
|
1562
1699
|
return invalid(action, "taskId and clientRequestId are required");
|
|
1563
1700
|
}
|
|
1701
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1702
|
+
if (taskIdError)
|
|
1703
|
+
return invalid(action, taskIdError);
|
|
1564
1704
|
const out = await stageUtf8Text(ctx.appData, "", { extension: ".json" });
|
|
1565
1705
|
return acceptOperation(ctx, {
|
|
1566
1706
|
action,
|
|
@@ -2195,6 +2335,14 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2195
2335
|
rawArgs.some((value) => typeof value !== "string"))) {
|
|
2196
2336
|
return invalid(action, "args must be an array of strings");
|
|
2197
2337
|
}
|
|
2338
|
+
if (TASK_ADVANCE_ALIAS_ACTIONS.has(action) && Array.isArray(rawArgs)) {
|
|
2339
|
+
const aliasTaskId = str(rawArgs[0]);
|
|
2340
|
+
const taskIdError = aliasTaskId
|
|
2341
|
+
? taskIdInputError(aliasTaskId)
|
|
2342
|
+
: undefined;
|
|
2343
|
+
if (taskIdError)
|
|
2344
|
+
return invalid(action, taskIdError);
|
|
2345
|
+
}
|
|
2198
2346
|
if (capability.humanConfirmation === "required") {
|
|
2199
2347
|
const confirmationId = str(p.confirmationId);
|
|
2200
2348
|
if (!confirmationId)
|
|
@@ -2274,6 +2422,9 @@ export async function dispatchOperatorAction(ctx, req) {
|
|
|
2274
2422
|
.replace(/^(loop-agent|agent-worker)\s+/, "")
|
|
2275
2423
|
.split(/\s+/);
|
|
2276
2424
|
const args = [...prefix, ...(rawArgs ?? [])];
|
|
2425
|
+
if (TASK_ADVANCE_ALIAS_ACTIONS.has(action) && !args.includes("--json")) {
|
|
2426
|
+
args.push("--json");
|
|
2427
|
+
}
|
|
2277
2428
|
if (capability.kind === "read") {
|
|
2278
2429
|
if (!args.includes("--json"))
|
|
2279
2430
|
args.push("--json");
|