@tea-agent/loop-agent 0.36.4-beta.0 → 0.37.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 +2 -6
- package/CHANGELOG.md +72 -163
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/infrastructure/harness/atomic-write.js +1 -2
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/frontend-project-capability.js +12 -162
- package/dist/task/source-prepare/completeness.js +0 -17
- package/dist/task/source-prepare/parse-intent.js +1 -15
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -61
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -86
- package/dist/worker/console/chat/routes.js +8 -36
- package/dist/worker/console/chat/session-store.js +0 -3
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -97
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +13 -373
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -89
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +11 -30
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +1 -6
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/failure-routing.js +4 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +62 -394
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +86 -238
- package/dist/workflows/dag/lifecycle.js +0 -4
- package/dist/workflows/dag/node-execution.js +6 -105
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/report.js +0 -6
- package/dist/workflows/dag/retry-policy.js +0 -11
- package/dist/workflows/dag/runner.js +12 -314
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -132
- package/dist/workflows/dag/validate.js +2 -7
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/frontend-design-contract.md +14 -34
- package/docs/templates/frontend-task-constraints.md +13 -33
- package/docs/templates/frontend-task-requirement.md +20 -54
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -4
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -3
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -471
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
- package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
import { createHash, randomBytes } from "node:crypto";
|
|
29
29
|
import { access } from "node:fs/promises";
|
|
30
30
|
import path from "node:path";
|
|
31
|
-
import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
|
|
32
31
|
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
33
32
|
import { extractUsageSample } from "./usage.js";
|
|
34
33
|
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
@@ -58,12 +57,12 @@ import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.
|
|
|
58
57
|
* (AC-01: ≤ 1900 chars and ≤ 80% of the 2498-char baseline).
|
|
59
58
|
*/
|
|
60
59
|
export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
|
|
61
|
-
"You are General Operator Chat.",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"On failure
|
|
66
|
-
"Continue until success, user stop, or no safe recovery remains
|
|
60
|
+
"You are the General Operator Chat for loop-agent / agent-worker.",
|
|
61
|
+
"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.",
|
|
62
|
+
"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.",
|
|
63
|
+
"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.",
|
|
64
|
+
"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.",
|
|
65
|
+
"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.",
|
|
67
66
|
].join("\n");
|
|
68
67
|
/** Compose the inspectable system prompt actually injected into Operator Chat. */
|
|
69
68
|
export function composeOperatorChatSystemPrompt(input) {
|
|
@@ -564,7 +563,28 @@ export function projectRuntimeSnapshot(input) {
|
|
|
564
563
|
hasBash: input.hasBash,
|
|
565
564
|
};
|
|
566
565
|
}
|
|
567
|
-
|
|
566
|
+
/**
|
|
567
|
+
* Extract the latest assistant text fragment from an SDK message object.
|
|
568
|
+
* Tolerant of multiple message shapes the SDK emits.
|
|
569
|
+
*/
|
|
570
|
+
export function extractAssistantText(message) {
|
|
571
|
+
if (!message || typeof message !== "object")
|
|
572
|
+
return "";
|
|
573
|
+
const m = message;
|
|
574
|
+
if (typeof m.text === "string")
|
|
575
|
+
return m.text;
|
|
576
|
+
const contents = Array.isArray(m.content) ? m.content : [];
|
|
577
|
+
const parts = Array.isArray(m.parts) ? m.parts : [];
|
|
578
|
+
const segments = [];
|
|
579
|
+
for (const c of [...contents, ...parts]) {
|
|
580
|
+
if (!c || typeof c !== "object")
|
|
581
|
+
continue;
|
|
582
|
+
const seg = c;
|
|
583
|
+
if (typeof seg.text === "string")
|
|
584
|
+
segments.push(seg.text);
|
|
585
|
+
}
|
|
586
|
+
return segments.join("");
|
|
587
|
+
}
|
|
568
588
|
function readSessionManagerId(manager) {
|
|
569
589
|
if (!manager || typeof manager !== "object")
|
|
570
590
|
return undefined;
|
|
@@ -678,12 +698,13 @@ export class ConsolePiRuntime {
|
|
|
678
698
|
* closure state).
|
|
679
699
|
*/
|
|
680
700
|
async buildResourceLoaderOptions(base = {}) {
|
|
701
|
+
const runtime = this;
|
|
681
702
|
return {
|
|
682
703
|
...base,
|
|
683
704
|
skillsOverride: (current) => {
|
|
684
705
|
// Synchronous override surface: use last-known prefs via a sync
|
|
685
706
|
// fail-closed path. Prefer async cache filled by ensurePrefs.
|
|
686
|
-
const prefs =
|
|
707
|
+
const prefs = runtime.cachedSkillPrefs ?? {
|
|
687
708
|
schemaVersion: 1,
|
|
688
709
|
skills: {},
|
|
689
710
|
};
|
|
@@ -903,27 +924,10 @@ export class ConsolePiRuntime {
|
|
|
903
924
|
const customTools = await this.loadCustomTools();
|
|
904
925
|
if (this.disposedSessions.has(sessionId))
|
|
905
926
|
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).
|
|
919
927
|
const { session } = await this.bindings.createSessionFromServices({
|
|
920
928
|
services,
|
|
921
929
|
sessionManager: init.sessionManager,
|
|
922
|
-
tools: [
|
|
923
|
-
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
924
|
-
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
925
|
-
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
926
|
-
],
|
|
930
|
+
tools: [...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS],
|
|
927
931
|
excludeTools: [
|
|
928
932
|
"apply_patch",
|
|
929
933
|
"apply-patch",
|
|
@@ -1276,7 +1280,6 @@ export class ConsolePiRuntime {
|
|
|
1276
1280
|
* session is not active — callers fail closed instead of fabricating data.
|
|
1277
1281
|
*/
|
|
1278
1282
|
/**
|
|
1279
|
-
* ConsolePiRuntime.listSlashCommands
|
|
1280
1283
|
* Browser-safe slash command projection for UI-11.
|
|
1281
1284
|
* Sources: current Session extension commands, prompt templates, skills.
|
|
1282
1285
|
* Never returns prompt/skill file bodies.
|
|
@@ -1289,36 +1292,29 @@ export class ConsolePiRuntime {
|
|
|
1289
1292
|
.replace(/[A-Za-z]:\\Users\\[^\s]+/gi, "~")
|
|
1290
1293
|
.slice(0, 240);
|
|
1291
1294
|
};
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
+
try {
|
|
1296
|
+
await this.ensureSessionReady(sessionId);
|
|
1297
|
+
}
|
|
1298
|
+
catch {
|
|
1299
|
+
return [];
|
|
1300
|
+
}
|
|
1295
1301
|
const session = this.sessions.get(sessionId);
|
|
1296
1302
|
if (!session)
|
|
1297
|
-
return
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const SOURCE_RANK = {
|
|
1301
|
-
extension: 0,
|
|
1302
|
-
skill: 1,
|
|
1303
|
-
prompt: 2,
|
|
1304
|
-
};
|
|
1305
|
-
const byKey = new Map();
|
|
1306
|
-
const sourceWarnings = [];
|
|
1303
|
+
return [];
|
|
1304
|
+
const out = [];
|
|
1305
|
+
const seen = new Set();
|
|
1307
1306
|
const push = (command, label, description, source) => {
|
|
1308
1307
|
const name = command.startsWith("/") ? command : `/${command}`;
|
|
1309
1308
|
const key = name.toLowerCase();
|
|
1310
|
-
if (!key || key === "/")
|
|
1309
|
+
if (!key || key === "/" || seen.has(key))
|
|
1311
1310
|
return;
|
|
1312
|
-
|
|
1311
|
+
seen.add(key);
|
|
1312
|
+
out.push({
|
|
1313
1313
|
command: name,
|
|
1314
1314
|
label: (label || name).slice(0, 80),
|
|
1315
1315
|
description: scrub(description),
|
|
1316
1316
|
source,
|
|
1317
|
-
};
|
|
1318
|
-
const existing = byKey.get(key);
|
|
1319
|
-
if (!existing || SOURCE_RANK[source] < SOURCE_RANK[existing.source]) {
|
|
1320
|
-
byKey.set(key, next);
|
|
1321
|
-
}
|
|
1317
|
+
});
|
|
1322
1318
|
};
|
|
1323
1319
|
try {
|
|
1324
1320
|
const cmds = session.extensionRunner?.getRegisteredCommands?.() ?? [];
|
|
@@ -1329,20 +1325,18 @@ export class ConsolePiRuntime {
|
|
|
1329
1325
|
push(String(name), String(cmd.name || name), String(cmd.description || ""), "extension");
|
|
1330
1326
|
}
|
|
1331
1327
|
}
|
|
1332
|
-
catch
|
|
1333
|
-
//
|
|
1334
|
-
sourceWarnings.push(`extension: ${scrub(error instanceof Error ? error.message : String(error))}`);
|
|
1328
|
+
catch {
|
|
1329
|
+
// non-blocking
|
|
1335
1330
|
}
|
|
1336
1331
|
try {
|
|
1337
|
-
// Enumerate prompts before skills on purpose: priority must still let skill win.
|
|
1338
1332
|
for (const tpl of session.promptTemplates ?? []) {
|
|
1339
1333
|
if (!tpl?.name)
|
|
1340
1334
|
continue;
|
|
1341
1335
|
push(String(tpl.name), String(tpl.name), String(tpl.description || ""), "prompt");
|
|
1342
1336
|
}
|
|
1343
1337
|
}
|
|
1344
|
-
catch
|
|
1345
|
-
|
|
1338
|
+
catch {
|
|
1339
|
+
// non-blocking
|
|
1346
1340
|
}
|
|
1347
1341
|
try {
|
|
1348
1342
|
const services = this.serviceScopes.get(sessionId);
|
|
@@ -1357,16 +1351,10 @@ export class ConsolePiRuntime {
|
|
|
1357
1351
|
push(command, skill.name, String(skill.description || ""), "skill");
|
|
1358
1352
|
}
|
|
1359
1353
|
}
|
|
1360
|
-
catch
|
|
1361
|
-
|
|
1354
|
+
catch {
|
|
1355
|
+
// non-blocking
|
|
1362
1356
|
}
|
|
1363
|
-
|
|
1364
|
-
? sourceWarnings.join("; ").slice(0, 160)
|
|
1365
|
-
: undefined;
|
|
1366
|
-
return {
|
|
1367
|
-
commands: [...byKey.values()],
|
|
1368
|
-
...(warning ? { warning } : {}),
|
|
1369
|
-
};
|
|
1357
|
+
return out;
|
|
1370
1358
|
}
|
|
1371
1359
|
async getRuntimeSnapshot(sessionId, options) {
|
|
1372
1360
|
try {
|
|
@@ -1833,17 +1821,10 @@ export class ConsolePiRuntime {
|
|
|
1833
1821
|
: undefined;
|
|
1834
1822
|
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
1835
1823
|
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.
|
|
1839
1824
|
const { session } = await this.bindings.createSessionFromServices({
|
|
1840
1825
|
services,
|
|
1841
1826
|
sessionManager: init.sessionManager,
|
|
1842
|
-
tools: [
|
|
1843
|
-
...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS,
|
|
1844
|
-
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
1845
|
-
...OPERATOR_CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
1846
|
-
],
|
|
1827
|
+
tools: [...OPERATOR_CHAT_BUILTIN_EXPLORE_TOOLS],
|
|
1847
1828
|
excludeTools: [
|
|
1848
1829
|
"apply_patch",
|
|
1849
1830
|
"apply-patch",
|
|
@@ -2135,15 +2116,6 @@ export class ConsolePiRuntime {
|
|
|
2135
2116
|
this.dispose(id);
|
|
2136
2117
|
}
|
|
2137
2118
|
}
|
|
2138
|
-
function mapAssistantMessageEvent(type, sessionId, message) {
|
|
2139
|
-
const content = extractAssistantContentBlocks(message);
|
|
2140
|
-
return {
|
|
2141
|
-
type,
|
|
2142
|
-
sessionId,
|
|
2143
|
-
text: extractAssistantText(message),
|
|
2144
|
-
...(content.length > 0 ? { content } : {}),
|
|
2145
|
-
};
|
|
2146
|
-
}
|
|
2147
2119
|
function mapSdkEvent(sessionId, event) {
|
|
2148
2120
|
switch (event.type) {
|
|
2149
2121
|
case "agent_start":
|
|
@@ -2155,13 +2127,17 @@ function mapSdkEvent(sessionId, event) {
|
|
|
2155
2127
|
willRetry: Boolean(event.willRetry),
|
|
2156
2128
|
};
|
|
2157
2129
|
case "message_update":
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2130
|
+
return {
|
|
2131
|
+
type: "message_update",
|
|
2132
|
+
sessionId,
|
|
2133
|
+
text: extractAssistantText(event.message),
|
|
2134
|
+
};
|
|
2161
2135
|
case "message_end":
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2136
|
+
return {
|
|
2137
|
+
type: "message_end",
|
|
2138
|
+
sessionId,
|
|
2139
|
+
text: extractAssistantText(event.message),
|
|
2140
|
+
};
|
|
2165
2141
|
case "turn_end":
|
|
2166
2142
|
// Usage is emitted from message_end (see prompt subscribe). turn_end
|
|
2167
2143
|
// carries the same final assistant message and would double-count.
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* Mutation routes go through the Console mutation gate (boot token + Origin).
|
|
13
13
|
* Read routes (GET capabilities / sessions) are open to loopback.
|
|
14
14
|
*/
|
|
15
|
-
import { parseAssistantContent } from "./assistant-content.js";
|
|
16
15
|
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
|
|
17
16
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
18
17
|
import { cleanChatTitle } from "./session-store.js";
|
|
@@ -84,16 +83,12 @@ function turnEventToStorePartial(event) {
|
|
|
84
83
|
case "agent_start":
|
|
85
84
|
return { kind: "agent_start", data: {} };
|
|
86
85
|
case "message_update":
|
|
87
|
-
case "message_end": {
|
|
88
|
-
const content = parseAssistantContent(event.content);
|
|
89
86
|
return {
|
|
90
|
-
kind:
|
|
91
|
-
data: {
|
|
92
|
-
text: String(event.text ?? ""),
|
|
93
|
-
...(content ? { content } : {}),
|
|
94
|
-
},
|
|
87
|
+
kind: "message_update",
|
|
88
|
+
data: { text: String(event.text ?? "") },
|
|
95
89
|
};
|
|
96
|
-
|
|
90
|
+
case "message_end":
|
|
91
|
+
return { kind: "message_end", data: { text: String(event.text ?? "") } };
|
|
97
92
|
case "usage":
|
|
98
93
|
return { kind: "usage", data: (event.usage ?? {}) };
|
|
99
94
|
case "tool_call":
|
|
@@ -1650,24 +1645,14 @@ async function handleChatFiles(res, deps, sessionId, query) {
|
|
|
1650
1645
|
}
|
|
1651
1646
|
export async function handleChatCommands(res, deps, sessionId) {
|
|
1652
1647
|
try {
|
|
1653
|
-
const
|
|
1648
|
+
const commands = typeof deps.runtime.listSlashCommands === "function"
|
|
1654
1649
|
? await deps.runtime.listSlashCommands(sessionId)
|
|
1655
|
-
:
|
|
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;
|
|
1650
|
+
: [];
|
|
1661
1651
|
res.statusCode = 200;
|
|
1662
1652
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1663
|
-
res.end(JSON.stringify({
|
|
1664
|
-
ok: true,
|
|
1665
|
-
commands,
|
|
1666
|
-
...(warning ? { warning } : {}),
|
|
1667
|
-
}));
|
|
1653
|
+
res.end(JSON.stringify({ ok: true, commands }));
|
|
1668
1654
|
}
|
|
1669
1655
|
catch (error) {
|
|
1670
|
-
// Whole-list / readiness failures stay fail-soft with empty commands + warning.
|
|
1671
1656
|
res.statusCode = 200;
|
|
1672
1657
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
1673
1658
|
res.end(JSON.stringify({
|
|
@@ -2814,21 +2799,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
2814
2799
|
return;
|
|
2815
2800
|
}
|
|
2816
2801
|
}
|
|
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;
|
|
2830
2802
|
try {
|
|
2831
|
-
const snapshot = await deps.runtime.compact(sessionId
|
|
2803
|
+
const snapshot = await deps.runtime.compact(sessionId);
|
|
2832
2804
|
const event = deps.events.append(sessionId, deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`, { kind: "compact", data: snapshot });
|
|
2833
2805
|
sendJson(res, 200, { ok: true, snapshot, eventId: event.eventId });
|
|
2834
2806
|
}
|
|
@@ -368,9 +368,6 @@ export async function runChatTurn(options) {
|
|
|
368
368
|
pending.push(store.appendMessage(sessionId, {
|
|
369
369
|
role: "assistant",
|
|
370
370
|
text: event.text,
|
|
371
|
-
...(event.content && event.content.length > 0
|
|
372
|
-
? { content: event.content }
|
|
373
|
-
: {}),
|
|
374
371
|
}));
|
|
375
372
|
}
|
|
376
373
|
break;
|
|
@@ -41,6 +41,13 @@ export const SHORTCUTS = [
|
|
|
41
41
|
action: "reload",
|
|
42
42
|
source: "operator",
|
|
43
43
|
},
|
|
44
|
+
{
|
|
45
|
+
command: "/help",
|
|
46
|
+
label: "Shortcuts",
|
|
47
|
+
description: "显示可用 shortcut",
|
|
48
|
+
action: "show-help",
|
|
49
|
+
source: "operator",
|
|
50
|
+
},
|
|
44
51
|
];
|
|
45
52
|
/** Pi Web commands with real browser-equivalent behavior (UI-11). */
|
|
46
53
|
export const WEB_SLASH_COMMANDS = [
|
|
@@ -96,15 +103,6 @@ export function resolveShortcut(input) {
|
|
|
96
103
|
return { command: local.command, action: local.action };
|
|
97
104
|
return { command: token, action: "unknown" };
|
|
98
105
|
}
|
|
99
|
-
/** Map Operator panel shortcuts onto Console workspace routes. */
|
|
100
|
-
export function workspaceForShortcut(action) {
|
|
101
|
-
if (action === "focus-panel:task" || action === "focus-panel:contract") {
|
|
102
|
-
return "tasks";
|
|
103
|
-
}
|
|
104
|
-
if (action === "focus-panel:dag")
|
|
105
|
-
return "recovery";
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
106
|
/**
|
|
109
107
|
* Merge Operator, Web, and dynamic Session commands with conflict dedupe.
|
|
110
108
|
* Priority: operator/web > extension > skill > prompt. /reload is single-card.
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Turn grouping for Operator Chat inline process UX (pi-web ProcessDetails mind).
|
|
3
3
|
*/
|
|
4
|
-
import { getDisplayableAssistantBlocks, joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
|
|
5
|
-
import { semanticActivityForTool, } from "./semantic-activity.js";
|
|
6
|
-
function emptyGroup(key) {
|
|
7
|
-
return {
|
|
8
|
-
key,
|
|
9
|
-
assistants: [],
|
|
10
|
-
tools: [],
|
|
11
|
-
processItems: [],
|
|
12
|
-
live: false,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
4
|
/**
|
|
16
5
|
* Restore chat bubbles + tool cards from durable session messages.
|
|
17
6
|
* Tool rows often appear before the final assistant `message_end` message;
|
|
@@ -47,13 +36,11 @@ export function hydrateSessionThread(raw) {
|
|
|
47
36
|
if (item.role === "assistant") {
|
|
48
37
|
if (!turnId)
|
|
49
38
|
turnId = `restored-${item.id}`;
|
|
50
|
-
const content = parseAssistantContent(item.content);
|
|
51
39
|
messages.push({
|
|
52
40
|
id: item.id,
|
|
53
41
|
role: "assistant",
|
|
54
42
|
text: item.text,
|
|
55
43
|
createdAt,
|
|
56
|
-
...(content && content.length > 0 ? { content } : {}),
|
|
57
44
|
});
|
|
58
45
|
flushPendingToAssistant(item.id);
|
|
59
46
|
continue;
|
|
@@ -77,60 +64,6 @@ export function hydrateSessionThread(raw) {
|
|
|
77
64
|
}
|
|
78
65
|
return { messages, toolCalls };
|
|
79
66
|
}
|
|
80
|
-
function pushToolProcessItem(processItems, tool) {
|
|
81
|
-
const activity = semanticActivityForTool(tool);
|
|
82
|
-
if (activity)
|
|
83
|
-
processItems.push({ kind: "activity", activity });
|
|
84
|
-
else
|
|
85
|
-
processItems.push({ kind: "tool", tool });
|
|
86
|
-
}
|
|
87
|
-
function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
|
|
88
|
-
for (const block of blocks) {
|
|
89
|
-
if (block.type === "thinking") {
|
|
90
|
-
processItems.push({ kind: "thinking", thinking: block.thinking });
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (block.type === "text") {
|
|
94
|
-
if (block.text.trim())
|
|
95
|
-
processItems.push({ kind: "text", text: block.text });
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
const tool = group.tools.find((entry) => entry.id === block.toolCallId);
|
|
99
|
-
if (tool) {
|
|
100
|
-
pushToolProcessItem(processItems, tool);
|
|
101
|
-
usedToolIds.add(tool.id);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function fillProcessDetails(group) {
|
|
106
|
-
const usedToolIds = new Set();
|
|
107
|
-
const processItems = [];
|
|
108
|
-
const prior = group.assistants.slice(0, -1);
|
|
109
|
-
const finalAssistant = group.assistants.at(-1);
|
|
110
|
-
for (const assistant of prior) {
|
|
111
|
-
emitProcessBlocks(getDisplayableAssistantBlocks(assistant.content ?? []), group, processItems, usedToolIds);
|
|
112
|
-
}
|
|
113
|
-
const blocks = finalAssistant?.content ?? [];
|
|
114
|
-
const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
|
|
115
|
-
emitProcessBlocks(split.processBlocks, group, processItems, usedToolIds);
|
|
116
|
-
for (const tool of group.tools) {
|
|
117
|
-
if (!usedToolIds.has(tool.id))
|
|
118
|
-
pushToolProcessItem(processItems, tool);
|
|
119
|
-
}
|
|
120
|
-
group.processItems = processItems;
|
|
121
|
-
if (!finalAssistant)
|
|
122
|
-
return;
|
|
123
|
-
if (blocks.length === 0) {
|
|
124
|
-
group.answer = finalAssistant;
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const answerText = joinAssistantText(split.answerBlocks);
|
|
128
|
-
group.answer = {
|
|
129
|
-
...finalAssistant,
|
|
130
|
-
text: answerText,
|
|
131
|
-
content: split.answerBlocks,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
67
|
/**
|
|
135
68
|
* Build user → process → answer groups for the message timeline.
|
|
136
69
|
*/
|
|
@@ -139,7 +72,12 @@ export function buildTurnProcessGroups(input) {
|
|
|
139
72
|
const groups = [];
|
|
140
73
|
let current = null;
|
|
141
74
|
const startGroup = (key) => {
|
|
142
|
-
const group =
|
|
75
|
+
const group = {
|
|
76
|
+
key,
|
|
77
|
+
assistants: [],
|
|
78
|
+
tools: [],
|
|
79
|
+
live: false,
|
|
80
|
+
};
|
|
143
81
|
groups.push(group);
|
|
144
82
|
return group;
|
|
145
83
|
};
|
|
@@ -154,7 +92,12 @@ export function buildTurnProcessGroups(input) {
|
|
|
154
92
|
current.assistants.push(message);
|
|
155
93
|
}
|
|
156
94
|
if (groups.length === 0 && input.toolCalls.length > 0) {
|
|
157
|
-
groups.push(
|
|
95
|
+
groups.push({
|
|
96
|
+
key: "orphan-tools",
|
|
97
|
+
assistants: [],
|
|
98
|
+
tools: [],
|
|
99
|
+
live: false,
|
|
100
|
+
});
|
|
158
101
|
}
|
|
159
102
|
const assigned = new Set();
|
|
160
103
|
const assistantToGroup = new Map();
|
|
@@ -169,6 +112,7 @@ export function buildTurnProcessGroups(input) {
|
|
|
169
112
|
if (group) {
|
|
170
113
|
group.tools.push(tool);
|
|
171
114
|
assigned.add(tool.id);
|
|
115
|
+
continue;
|
|
172
116
|
}
|
|
173
117
|
}
|
|
174
118
|
}
|
|
@@ -190,6 +134,7 @@ export function buildTurnProcessGroups(input) {
|
|
|
190
134
|
if (restored) {
|
|
191
135
|
restored.tools.push(tool);
|
|
192
136
|
assigned.add(tool.id);
|
|
137
|
+
continue;
|
|
193
138
|
}
|
|
194
139
|
}
|
|
195
140
|
}
|
|
@@ -206,47 +151,28 @@ export function buildTurnProcessGroups(input) {
|
|
|
206
151
|
windowGroup.tools.push(tool);
|
|
207
152
|
assigned.add(tool.id);
|
|
208
153
|
}
|
|
209
|
-
else {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
lastGroup.tools.push(tool);
|
|
213
|
-
assigned.add(tool.id);
|
|
214
|
-
}
|
|
154
|
+
else if (groups.length > 0) {
|
|
155
|
+
groups[groups.length - 1].tools.push(tool);
|
|
156
|
+
assigned.add(tool.id);
|
|
215
157
|
}
|
|
216
158
|
}
|
|
217
159
|
for (const group of groups) {
|
|
218
|
-
group.tools.sort((a, b) =>
|
|
219
|
-
if (a.eventSeq !== undefined && b.eventSeq !== undefined) {
|
|
220
|
-
return a.eventSeq - b.eventSeq;
|
|
221
|
-
}
|
|
222
|
-
return a.createdAt - b.createdAt;
|
|
223
|
-
});
|
|
160
|
+
group.tools.sort((a, b) => a.createdAt - b.createdAt);
|
|
224
161
|
group.live =
|
|
225
162
|
input.streaming &&
|
|
226
163
|
input.activeAssistantId !== null &&
|
|
227
164
|
group.assistants.some((a) => a.id === input.activeAssistantId);
|
|
228
|
-
fillProcessDetails(group);
|
|
229
165
|
}
|
|
230
166
|
return groups;
|
|
231
167
|
}
|
|
232
|
-
export function processGroupSummary(tools
|
|
168
|
+
export function processGroupSummary(tools) {
|
|
233
169
|
const count = tools.length;
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
const stepCount = thinkingCount + textCount + activityCount + count;
|
|
238
|
-
const errorCount = tools.filter((t) => t.status === "error").length +
|
|
239
|
-
(options?.activityErrorCount ?? 0);
|
|
240
|
-
const runningCount = tools.filter((t) => t.status === "running").length +
|
|
241
|
-
(options?.activityRunningCount ?? 0);
|
|
242
|
-
const parts = [`处理详情 · ${stepCount} 步`];
|
|
243
|
-
if (count > 0)
|
|
244
|
-
parts.push(`${count} 工具`);
|
|
245
|
-
if (activityCount > 0)
|
|
246
|
-
parts.push(`${activityCount} 进展`);
|
|
170
|
+
const errorCount = tools.filter((t) => t.status === "error").length;
|
|
171
|
+
const runningCount = tools.filter((t) => t.status === "running").length;
|
|
172
|
+
const parts = [`执行过程 · ${count} 工具`];
|
|
247
173
|
if (errorCount > 0)
|
|
248
174
|
parts.push(`${errorCount} 出错`);
|
|
249
175
|
else if (runningCount > 0)
|
|
250
176
|
parts.push("运行中");
|
|
251
|
-
return { count, errorCount, runningCount,
|
|
177
|
+
return { count, errorCount, runningCount, label: parts.join(" · ") };
|
|
252
178
|
}
|
|
@@ -27,7 +27,6 @@ function readWorkspaceParams(query) {
|
|
|
27
27
|
...(read("dagRunId") ? { dagRunId: read("dagRunId") } : {}),
|
|
28
28
|
...(read("fromNodeId") ? { fromNodeId: read("fromNodeId") } : {}),
|
|
29
29
|
...(read("featureId") ? { featureId: read("featureId") } : {}),
|
|
30
|
-
...(read("intent") ? { intent: read("intent") } : {}),
|
|
31
30
|
};
|
|
32
31
|
}
|
|
33
32
|
/** Parse `#/tasks?taskId=…` hash routes (Observe-style virtual paths). */
|
|
@@ -79,7 +78,7 @@ export function resolveDefaultLanding(input) {
|
|
|
79
78
|
return (workspaceFromLocation(input.location) ??
|
|
80
79
|
configuredLanding(input.storage?.getItem(DEFAULT_LANDING_STORAGE_KEY)) ??
|
|
81
80
|
configuredLanding(input.env) ??
|
|
82
|
-
"
|
|
81
|
+
"tasks");
|
|
83
82
|
}
|
|
84
83
|
/** Hash path segment for Console virtual routing (no leading `#`). */
|
|
85
84
|
export function buildConsoleHashPath(workspace, params = {}) {
|