@tea-agent/loop-agent 0.35.4-beta.0 → 0.36.1-beta.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 +67 -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 +78 -4
- package/dist/workflows/dag/node-execution.js +16 -6
- package/dist/workflows/dag/retry-policy.js +11 -0
- 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
|
@@ -58,12 +58,12 @@ import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.
|
|
|
58
58
|
* (AC-01: ≤ 1900 chars and ≤ 80% of the 2498-char baseline).
|
|
59
59
|
*/
|
|
60
60
|
export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
|
|
61
|
-
"You are
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"On failure
|
|
66
|
-
"Continue until success, user stop, or no safe
|
|
61
|
+
"You are General Operator Chat.",
|
|
62
|
+
"Use operator_* tools first. Pi read/write/edit/bash/grep/find/ls are available; obey repository AGENTS.md. apply_patch, full-tools, shell and coding-chat are denied. Prefer safe-read/safe-grep for sensitive probes.",
|
|
63
|
+
"No harness and asked to init: `loop-agent init instructions --repo-root .`, then `loop-agent init --repo-root . --profile full --merge`; verify with doctor, inspect and docs audit. Updates: `loop-agent init upgrade --repo-root . --json` until stable; `loop-agent init check-update` is read-only.",
|
|
64
|
+
"Long DAGs: prepareDagExecution → runDag → operationId. Never foreground-Bash task advance, tail/head pipes or nohup/Start-Process/start; do not end on accepted/queued/running or an operationId — NOT completion. Supervise operationGet/status/dagReport/dagDoctor/operationWait with 60s → 90s → 120s → 180s backoff; reset to 60s on state change; 30-60s re-checks when stall suspected. Every operationWait continuation MUST use afterSeq=previous nextSeq (prefer recommendedNextCall); never reuse an older cursor. Heartbeats are not meaningful progress.",
|
|
65
|
+
"On failure consume terminal runSummary first. If progress exists, wait; else use a fresh dagRerunPlan; when ineligible use same-task rerun/advance, resume, or Worker retry. A run-failed task without an open gate is terminal: do not re-advance a run-failed task. Before run start, two identical lifecycleState/blocker/revision/DAG-hash observations mean no progress: stop and report after one bounded probe; never loop identical bash/read/grep. Auto-approve only bounded writeSet inside allowedPaths, outside forbiddenPaths, with structured verification.",
|
|
66
|
+
"Continue until success, user stop, or no safe recovery remains; then stop and report failure/evidence. Never create a new task for a transient failure or replace repair-pi with direct edits.",
|
|
67
67
|
].join("\n");
|
|
68
68
|
/** Compose the inspectable system prompt actually injected into Operator Chat. */
|
|
69
69
|
export function composeOperatorChatSystemPrompt(input) {
|
|
@@ -678,13 +678,12 @@ export class ConsolePiRuntime {
|
|
|
678
678
|
* closure state).
|
|
679
679
|
*/
|
|
680
680
|
async buildResourceLoaderOptions(base = {}) {
|
|
681
|
-
const runtime = this;
|
|
682
681
|
return {
|
|
683
682
|
...base,
|
|
684
683
|
skillsOverride: (current) => {
|
|
685
684
|
// Synchronous override surface: use last-known prefs via a sync
|
|
686
685
|
// fail-closed path. Prefer async cache filled by ensurePrefs.
|
|
687
|
-
const prefs =
|
|
686
|
+
const prefs = this.cachedSkillPrefs ?? {
|
|
688
687
|
schemaVersion: 1,
|
|
689
688
|
skills: {},
|
|
690
689
|
};
|
|
@@ -904,10 +903,27 @@ export class ConsolePiRuntime {
|
|
|
904
903
|
const customTools = await this.loadCustomTools();
|
|
905
904
|
if (this.disposedSessions.has(sessionId))
|
|
906
905
|
return;
|
|
906
|
+
// The SDK filters BOTH built-in tools and customTools against
|
|
907
|
+
// `tools` (allowed-tool names) inside _refreshToolRegistry:
|
|
908
|
+
// isAllowedTool = (!allowedToolNames || allowedToolNames.has(name))
|
|
909
|
+
// && !excludedToolNames.has(name)
|
|
910
|
+
// Passing only OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS here silently dropped
|
|
911
|
+
// every custom operator tool (prepareDagExecution / runDag /
|
|
912
|
+
// operationWait / safe-read / git-status / ...) from the registry, so the
|
|
913
|
+
// model could only ever see bash/read/write/edit/grep/find/ls. The
|
|
914
|
+
// allowlist must cover the full tool surface that is later activated via
|
|
915
|
+
// setActiveToolsByName(computeOperatorChatActiveToolNames(session)) —
|
|
916
|
+
// operator actions + built-in explore tools + safe explore tools.
|
|
917
|
+
// Hard write/coding channels stay blocked via excludeTools below (they
|
|
918
|
+
// are removed even when present in the allowed list).
|
|
907
919
|
const { session } = await this.bindings.createSessionFromServices({
|
|
908
920
|
services,
|
|
909
921
|
sessionManager: init.sessionManager,
|
|
910
|
-
tools: [
|
|
922
|
+
tools: [
|
|
923
|
+
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
924
|
+
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
925
|
+
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
926
|
+
],
|
|
911
927
|
excludeTools: [
|
|
912
928
|
"apply_patch",
|
|
913
929
|
"apply-patch",
|
|
@@ -1260,6 +1276,7 @@ export class ConsolePiRuntime {
|
|
|
1260
1276
|
* session is not active — callers fail closed instead of fabricating data.
|
|
1261
1277
|
*/
|
|
1262
1278
|
/**
|
|
1279
|
+
* ConsolePiRuntime.listSlashCommands
|
|
1263
1280
|
* Browser-safe slash command projection for UI-11.
|
|
1264
1281
|
* Sources: current Session extension commands, prompt templates, skills.
|
|
1265
1282
|
* Never returns prompt/skill file bodies.
|
|
@@ -1272,29 +1289,36 @@ export class ConsolePiRuntime {
|
|
|
1272
1289
|
.replace(/[A-Za-z]:\\Users\\[^\s]+/gi, "~")
|
|
1273
1290
|
.slice(0, 240);
|
|
1274
1291
|
};
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
catch {
|
|
1279
|
-
return [];
|
|
1280
|
-
}
|
|
1292
|
+
// Fail soft at the HTTP layer: bubble readiness errors so routes can attach
|
|
1293
|
+
// a compact warning while the UI keeps local Operator/Pi Web commands.
|
|
1294
|
+
await this.ensureSessionReady(sessionId);
|
|
1281
1295
|
const session = this.sessions.get(sessionId);
|
|
1282
1296
|
if (!session)
|
|
1283
|
-
return [];
|
|
1284
|
-
|
|
1285
|
-
|
|
1297
|
+
return { commands: [] };
|
|
1298
|
+
// Same-name conflict priority (AC-3): extension > skill > prompt.
|
|
1299
|
+
// Enum order must not decide the winner — prompt-before-skill still loses.
|
|
1300
|
+
const SOURCE_RANK = {
|
|
1301
|
+
extension: 0,
|
|
1302
|
+
skill: 1,
|
|
1303
|
+
prompt: 2,
|
|
1304
|
+
};
|
|
1305
|
+
const byKey = new Map();
|
|
1306
|
+
const sourceWarnings = [];
|
|
1286
1307
|
const push = (command, label, description, source) => {
|
|
1287
1308
|
const name = command.startsWith("/") ? command : `/${command}`;
|
|
1288
1309
|
const key = name.toLowerCase();
|
|
1289
|
-
if (!key || key === "/"
|
|
1310
|
+
if (!key || key === "/")
|
|
1290
1311
|
return;
|
|
1291
|
-
|
|
1292
|
-
out.push({
|
|
1312
|
+
const next = {
|
|
1293
1313
|
command: name,
|
|
1294
1314
|
label: (label || name).slice(0, 80),
|
|
1295
1315
|
description: scrub(description),
|
|
1296
1316
|
source,
|
|
1297
|
-
}
|
|
1317
|
+
};
|
|
1318
|
+
const existing = byKey.get(key);
|
|
1319
|
+
if (!existing || SOURCE_RANK[source] < SOURCE_RANK[existing.source]) {
|
|
1320
|
+
byKey.set(key, next);
|
|
1321
|
+
}
|
|
1298
1322
|
};
|
|
1299
1323
|
try {
|
|
1300
1324
|
const cmds = session.extensionRunner?.getRegisteredCommands?.() ?? [];
|
|
@@ -1305,18 +1329,20 @@ export class ConsolePiRuntime {
|
|
|
1305
1329
|
push(String(name), String(cmd.name || name), String(cmd.description || ""), "extension");
|
|
1306
1330
|
}
|
|
1307
1331
|
}
|
|
1308
|
-
catch {
|
|
1309
|
-
//
|
|
1332
|
+
catch (error) {
|
|
1333
|
+
// Source isolation: keep other sources; surface compact warning (AC-3).
|
|
1334
|
+
sourceWarnings.push(`extension: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1310
1335
|
}
|
|
1311
1336
|
try {
|
|
1337
|
+
// Enumerate prompts before skills on purpose: priority must still let skill win.
|
|
1312
1338
|
for (const tpl of session.promptTemplates ?? []) {
|
|
1313
1339
|
if (!tpl?.name)
|
|
1314
1340
|
continue;
|
|
1315
1341
|
push(String(tpl.name), String(tpl.name), String(tpl.description || ""), "prompt");
|
|
1316
1342
|
}
|
|
1317
1343
|
}
|
|
1318
|
-
catch {
|
|
1319
|
-
|
|
1344
|
+
catch (error) {
|
|
1345
|
+
sourceWarnings.push(`prompt: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1320
1346
|
}
|
|
1321
1347
|
try {
|
|
1322
1348
|
const services = this.serviceScopes.get(sessionId);
|
|
@@ -1331,10 +1357,16 @@ export class ConsolePiRuntime {
|
|
|
1331
1357
|
push(command, skill.name, String(skill.description || ""), "skill");
|
|
1332
1358
|
}
|
|
1333
1359
|
}
|
|
1334
|
-
catch {
|
|
1335
|
-
|
|
1360
|
+
catch (error) {
|
|
1361
|
+
sourceWarnings.push(`skill: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1336
1362
|
}
|
|
1337
|
-
|
|
1363
|
+
const warning = sourceWarnings.length > 0
|
|
1364
|
+
? sourceWarnings.join("; ").slice(0, 160)
|
|
1365
|
+
: undefined;
|
|
1366
|
+
return {
|
|
1367
|
+
commands: [...byKey.values()],
|
|
1368
|
+
...(warning ? { warning } : {}),
|
|
1369
|
+
};
|
|
1338
1370
|
}
|
|
1339
1371
|
async getRuntimeSnapshot(sessionId, options) {
|
|
1340
1372
|
try {
|
|
@@ -1801,10 +1833,17 @@ export class ConsolePiRuntime {
|
|
|
1801
1833
|
: undefined;
|
|
1802
1834
|
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
1803
1835
|
const customTools = await this.loadCustomTools();
|
|
1836
|
+
// Same allowlist rationale as materializeSession: the SDK filters
|
|
1837
|
+
// customTools against `tools`, so the allowed names must cover the full
|
|
1838
|
+
// operator + explore surface or every custom tool is silently dropped.
|
|
1804
1839
|
const { session } = await this.bindings.createSessionFromServices({
|
|
1805
1840
|
services,
|
|
1806
1841
|
sessionManager: init.sessionManager,
|
|
1807
|
-
tools: [
|
|
1842
|
+
tools: [
|
|
1843
|
+
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
1844
|
+
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
1845
|
+
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
1846
|
+
],
|
|
1808
1847
|
excludeTools: [
|
|
1809
1848
|
"apply_patch",
|
|
1810
1849
|
"apply-patch",
|
|
@@ -1650,14 +1650,24 @@ async function handleChatFiles(res, deps, sessionId, query) {
|
|
|
1650
1650
|
}
|
|
1651
1651
|
export async function handleChatCommands(res, deps, sessionId) {
|
|
1652
1652
|
try {
|
|
1653
|
-
const
|
|
1653
|
+
const listed = typeof deps.runtime.listSlashCommands === "function"
|
|
1654
1654
|
? await deps.runtime.listSlashCommands(sessionId)
|
|
1655
|
-
: [];
|
|
1655
|
+
: { commands: [] };
|
|
1656
|
+
// Partial single-source failures keep successful commands + compact warning (AC-3).
|
|
1657
|
+
const commands = listed.commands ?? [];
|
|
1658
|
+
const warning = typeof listed.warning === "string" && listed.warning.trim()
|
|
1659
|
+
? listed.warning.trim().slice(0, 160)
|
|
1660
|
+
: undefined;
|
|
1656
1661
|
res.statusCode = 200;
|
|
1657
1662
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1658
|
-
res.end(JSON.stringify({
|
|
1663
|
+
res.end(JSON.stringify({
|
|
1664
|
+
ok: true,
|
|
1665
|
+
commands,
|
|
1666
|
+
...(warning ? { warning } : {}),
|
|
1667
|
+
}));
|
|
1659
1668
|
}
|
|
1660
1669
|
catch (error) {
|
|
1670
|
+
// Whole-list / readiness failures stay fail-soft with empty commands + warning.
|
|
1661
1671
|
res.statusCode = 200;
|
|
1662
1672
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1663
1673
|
res.end(JSON.stringify({
|
|
@@ -2804,8 +2814,21 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
2804
2814
|
return;
|
|
2805
2815
|
}
|
|
2806
2816
|
}
|
|
2817
|
+
let body = {};
|
|
2818
|
+
try {
|
|
2819
|
+
body = await readJsonBody(req);
|
|
2820
|
+
}
|
|
2821
|
+
catch {
|
|
2822
|
+
sendJson(res, 400, {
|
|
2823
|
+
ok: false,
|
|
2824
|
+
error: { code: "INVALID_INPUT", message: "invalid json body" },
|
|
2825
|
+
});
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
2829
|
+
const customInstructions = rawInstructions?.trim() || undefined;
|
|
2807
2830
|
try {
|
|
2808
|
-
const snapshot = await deps.runtime.compact(sessionId);
|
|
2831
|
+
const snapshot = await deps.runtime.compact(sessionId, customInstructions);
|
|
2809
2832
|
const event = deps.events.append(sessionId, deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`, { kind: "compact", data: snapshot });
|
|
2810
2833
|
sendJson(res, 200, { ok: true, snapshot, eventId: event.eventId });
|
|
2811
2834
|
}
|