@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.
Files changed (53) hide show
  1. package/AGENTS.md +4 -2
  2. package/CHANGELOG.md +67 -0
  3. package/dist/application/task-lifecycle/advance.js +1 -0
  4. package/dist/application/task-lifecycle/observe.js +15 -0
  5. package/dist/application/task-lifecycle/plan-transitions.js +15 -0
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/commands/init-upgrade.js +351 -19
  8. package/dist/commands/init.js +14 -67
  9. package/dist/commands/run-dag-progress.js +14 -0
  10. package/dist/commands/task-advance.js +33 -3
  11. package/dist/executors/dag-pi-executor.js +3 -1
  12. package/dist/shared/operator/capabilities.js +125 -11
  13. package/dist/task/source-prepare/completeness.js +17 -0
  14. package/dist/task/source-prepare/parse-intent.js +15 -1
  15. package/dist/worker/console/chat/artifact-card.js +8 -1
  16. package/dist/worker/console/chat/chat-event-store.js +61 -0
  17. package/dist/worker/console/chat/human-gate-card.js +9 -1
  18. package/dist/worker/console/chat/operation-card.js +71 -2
  19. package/dist/worker/console/chat/pi-runtime.js +69 -30
  20. package/dist/worker/console/chat/routes.js +27 -4
  21. package/dist/worker/console/chat/semantic-activity.js +465 -0
  22. package/dist/worker/console/chat/turn-process.js +31 -12
  23. package/dist/worker/console/operation-run-facts.js +190 -0
  24. package/dist/worker/console/operation-runner.js +107 -6
  25. package/dist/worker/console/operation-wait.js +314 -0
  26. package/dist/worker/console/operator-actions.js +153 -2
  27. package/dist/worker/console/static/assets/index-2OeZODxk.js +57 -0
  28. package/dist/worker/console/static/assets/index-DVJlUL8X.css +1 -0
  29. package/dist/worker/console/static/index.html +2 -2
  30. package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
  31. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
  32. package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
  33. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +69 -31
  34. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
  35. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
  36. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +2 -1
  37. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
  38. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +16 -5
  39. package/dist/worker/console/static-src/operator-chat/useComposer.js +30 -7
  40. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +7 -3
  41. package/dist/worker/observe/static/operator-chrome.js +1 -1
  42. package/dist/workflows/dag/init-hybrid.js +78 -4
  43. package/dist/workflows/dag/node-execution.js +16 -6
  44. package/dist/workflows/dag/retry-policy.js +11 -0
  45. package/docs/README.md +3 -3
  46. package/docs/architecture/evolution.md +102 -67
  47. package/docs/templates/init-managed-agents.md +5 -2
  48. package/harness.json +2 -2
  49. package/package.json +1 -1
  50. package/skills/loop-agent/SKILL.md +1 -0
  51. package/skills/loop-agent/references/command-reference.md +2 -0
  52. package/dist/worker/console/static/assets/index-DRqZiQ7J.css +0 -1
  53. 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 the General Operator Chat for loop-agent / agent-worker.",
62
- "Operate through operator_* tools first. Pi read/write/edit/bash/grep/find/ls are available, but obey the loaded repository AGENTS.md. apply_patch, full-tools, shell, and coding-chat are denied alternate runtimes. Prefer safe-read/safe-grep for sensitive probes.",
63
- "If a repository has no loop-agent harness and the user requests initialization, run `loop-agent init instructions --repo-root .` then `loop-agent init --repo-root . --profile full --merge`; finish the generated setup, init doctor, inspect, docs audit, and quick verification. For updates use `loop-agent init upgrade --repo-root . --json` until stable; use init check-update only for an explicitly read-only request.",
64
- "After a DAG is started or accepted, do not end on accepted/queued/running or an operationId. Supervise it to a terminal outcome. Poll operationGet, task status, dagReport, and dagDoctor after about 15 seconds on start/change, every 30 seconds during progress, and every 60 seconds after 3 minutes unchanged. Report only meaningful node/rank changes, review/verify/closeout, recovery, liveness concerns, and terminal outcomes.",
65
- "On failure read primaryFailure, primaryRecovery, and doctor evidence. If meaningful progress exists, wait. Otherwise use a fresh eligible dagRerunPlan and rerun its safe node; when ineligible follow AGENTS.md/runtime recovery for same-task rerun/advance, resume, or Worker retry. Auto-approve only a bounded writeSet inside allowedPaths, outside forbiddenPaths, without broad/destructive risk, and with structured verification.",
66
- "Continue until success, user stop, or no safe eligible recovery remains because limits, bindings, auth/quota recovery, or required external authorization are exhausted. Never create a new task for a transient failure or replace repair-pi with direct edits.",
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 = runtime.cachedSkillPrefs ?? {
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: [...OPERATOR_CHAT_BUILTIN_EXPLORE_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
- try {
1276
- await this.ensureSessionReady(sessionId);
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
- const out = [];
1285
- const seen = new Set();
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 === "/" || seen.has(key))
1310
+ if (!key || key === "/")
1290
1311
  return;
1291
- seen.add(key);
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
- // non-blocking
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
- // non-blocking
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
- // non-blocking
1360
+ catch (error) {
1361
+ sourceWarnings.push(`skill: ${scrub(error instanceof Error ? error.message : String(error))}`);
1336
1362
  }
1337
- return out;
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: [...OPERATOR_CHAT_BUILTIN_EXPLORE_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 commands = typeof deps.runtime.listSlashCommands === "function"
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({ ok: true, commands }));
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
  }