@tea-agent/loop-agent 0.35.2 → 0.35.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/AGENTS.md +2 -0
- package/CHANGELOG.md +31 -0
- package/README.md +1 -1
- package/bin/loop-agent.js +37 -1
- package/dist/build-stamp.json +6 -0
- package/dist/cli/program.js +2 -2
- package/dist/executors/dag-pi-executor.js +44 -0
- package/dist/shared/package-metadata.js +42 -0
- package/dist/worker/console/chat/assistant-content.js +11 -0
- package/dist/worker/console/chat/pi-runtime.js +6 -2
- package/dist/worker/console/chat/turn-process.js +17 -9
- package/dist/worker/console/chat/workspace-landing.js +1 -1
- package/dist/worker/console/static/assets/index-DuVLjCIT.js +57 -0
- package/dist/worker/console/static/index.html +1 -1
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +15 -3
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +1 -0
- package/dist/worker/loop-agent/loop-agent-client.js +17 -3
- package/dist/worker/observability/read-model.js +20 -0
- package/dist/worker/observe/spec-evidence.js +3 -8
- package/dist/worker/observe/static/views/dag-inspector.js +6 -71
- package/dist/worker/preflight.js +2 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +33 -23
- package/dist/workflows/dag/contract-output-registry.js +14 -0
- package/dist/workflows/dag/contract-validator-registrations.js +8 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +9 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +233 -39
- package/dist/workflows/dag/frontend-prewrite-gate.js +364 -61
- package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
- package/dist/workflows/dag/frontend-recovery-run.js +539 -0
- package/dist/workflows/dag/frontend-repair.js +219 -18
- package/dist/workflows/dag/frontend-verification-trace.js +47 -32
- package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
- package/dist/workflows/dag/init-hybrid.js +49 -24
- package/dist/workflows/dag/node-execution.js +89 -0
- package/dist/workflows/dag/recovery-recommendation.js +58 -0
- package/dist/workflows/dag/runner.js +245 -11
- package/dist/workflows/dag/scheduler.js +257 -3
- package/dist/workflows/dag/types.js +130 -2
- package/package.json +4 -3
- package/dist/worker/console/static/assets/index-gVHrlqI9.js +0 -56
package/AGENTS.md
CHANGED
|
@@ -94,6 +94,8 @@ npm run build
|
|
|
94
94
|
node bin/loop-agent.js --help
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
- 改 `src/**` 后必须先 `npm run build` 再用 `bin/loop-agent.js`:bin 内置 stale-dist 守卫(src 比 dist 新即硬失败,`--ignore-stale-dist` 逃生),`--version` 会显示构建 git SHA,发布/自测前用它确认跑的是目标改动。
|
|
98
|
+
|
|
97
99
|
文档站变更:`npm run docs:build`。init / architecture / skill entry / pack 定向验证见 verification-matrix。
|
|
98
100
|
|
|
99
101
|
## 交接
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.35.3] - 2026-08-15
|
|
6
|
+
|
|
7
|
+
### 新增
|
|
8
|
+
|
|
9
|
+
- chat as default landing + analyze-skills-first new-task flow
|
|
10
|
+
- guide plan verification targets to writeSet-internal files, command-level checks to static
|
|
11
|
+
- console recovery display — surface frontend recovery lineage + outcome
|
|
12
|
+
- frontend-implementation DAG phase 6 — recovery root manifest + statistics dedup
|
|
13
|
+
- frontend-implementation DAG phase 5 — writer partial-write rollback + child recovery
|
|
14
|
+
- frontend-implementation DAG phase 4 — writer transient rollback base
|
|
15
|
+
- frontend-implementation DAG phase 3c — scheduler activation gate + recovery trigger
|
|
16
|
+
- frontend-implementation DAG phase 3b — staged child-creation kernel
|
|
17
|
+
- frontend-implementation DAG phase 3a — recovery state/result types + terminal recovery intent
|
|
18
|
+
- producing-node contract self-check and run robustness hardening
|
|
19
|
+
- frontend-implementation DAG phase 2 — unified writer helper + structured repair assessment
|
|
20
|
+
- slim frontend-implementation DAG phase 1 — prewrite merge + writer admission gate
|
|
21
|
+
|
|
22
|
+
### 修复
|
|
23
|
+
|
|
24
|
+
- 规范证据面板不再单列 openspec,统一为「规范已读取/规范检索」
|
|
25
|
+
- green full test suite — python3 fallback, build-stamp version normalization, DEP0205 suppression
|
|
26
|
+
- update candidate-continuation fixtures for failureCode (rebase semantic fix)
|
|
27
|
+
|
|
5
28
|
## [0.35.2] - 2026-08-15
|
|
6
29
|
|
|
7
30
|
### 重点更新
|
|
@@ -51,6 +74,14 @@
|
|
|
51
74
|
- 修复文字录入「请参照这三个prd路径XXX」只物化一句话、引用文件从未导入的问题
|
|
52
75
|
- 修复 semantic intake attempt marker 一次性失败后永不重试的问题:新的 PRD 导入会重置 marker,失败可随新证据重试
|
|
53
76
|
|
|
77
|
+
## [0.35.1-beta.4] - 2026-08-15
|
|
78
|
+
|
|
79
|
+
> 观察面板规范证据不再单列 openspec:读取与检索统一为「规范已读取 / 规范检索」,空态文案同步中性化(beta,发布到 `beta` dist-tag)。
|
|
80
|
+
|
|
81
|
+
## [0.35.1-beta.3] - 2026-08-14
|
|
82
|
+
|
|
83
|
+
> frontend-implementation DAG 优化 v9 全六阶段 + Console 展示层(beta,发布到 `beta` dist-tag)。
|
|
84
|
+
|
|
54
85
|
## [0.35.0] - 2026-08-13
|
|
55
86
|
|
|
56
87
|
### 重点更新
|
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ agent-worker console doctor --repo .
|
|
|
110
110
|
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
访问 Console <http://127.0.0.1:8790/>;**Operator Chat Official** 是 Console
|
|
113
|
+
访问 Console <http://127.0.0.1:8790/>;**Operator Chat Official** 是 Console 内的主引导交互,默认打开「对话」工作区。构建时设置 `VITE_OPERATOR_CHAT_DEFAULT_LANDING=tasks` 可改回 Tasks;URL hash(`#/tasks`)与 localStorage 仍优先于默认值。顶部「检视」以及 `/inspect/` 提供只读 Dashboard、DAG、时间线与 artifacts;`loop-agent …` / `agent-worker …` CLI direct path 仍是受治理 fallback。当前状态为 **code ready, dogfood pending/unmet**,不宣称已替代 openCode 或其他外部主会话。
|
|
114
114
|
|
|
115
115
|
## 核心边界
|
|
116
116
|
|
package/bin/loop-agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
|
|
@@ -13,6 +13,42 @@ if (!existsSync(cliEntry)) {
|
|
|
13
13
|
process.exit(1);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Stale-dist guard: when running from a source checkout (src/ present), refuse
|
|
18
|
+
* to silently execute a dist/ that predates the latest source edit. The
|
|
19
|
+
* classic failure mode is editing src/** and running the bin without
|
|
20
|
+
* `npm run build`, which executes the previous build. Suppress with
|
|
21
|
+
* --ignore-stale-dist when intentionally running a stale build.
|
|
22
|
+
*/
|
|
23
|
+
const srcDir = join(packageRoot, "src");
|
|
24
|
+
const ignoreStale = process.argv.includes("--ignore-stale-dist");
|
|
25
|
+
if (ignoreStale) {
|
|
26
|
+
process.argv = process.argv.filter((arg) => arg !== "--ignore-stale-dist");
|
|
27
|
+
}
|
|
28
|
+
if (!ignoreStale && existsSync(srcDir)) {
|
|
29
|
+
const distCliMtime = statSync(cliEntry).mtimeMs;
|
|
30
|
+
let newestSrcMtime = 0;
|
|
31
|
+
const walk = (dir) => {
|
|
32
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
33
|
+
const full = join(dir, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
walk(full);
|
|
36
|
+
} else {
|
|
37
|
+
const mtime = statSync(full).mtimeMs;
|
|
38
|
+
if (mtime > newestSrcMtime) newestSrcMtime = mtime;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
walk(srcDir);
|
|
43
|
+
if (newestSrcMtime > distCliMtime) {
|
|
44
|
+
console.error(
|
|
45
|
+
`loop-agent: dist/ is stale relative to src/ (a source file is newer than ${cliEntry}).\n` +
|
|
46
|
+
` Run \`npm run build\` before using the bin, or pass --ignore-stale-dist to run the stale build intentionally.`,
|
|
47
|
+
);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
16
52
|
try {
|
|
17
53
|
await import(pathToFileURL(cliEntry).href);
|
|
18
54
|
} catch (error) {
|
package/dist/cli/program.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
-
import {
|
|
4
|
+
import { readCliVersionLabel } from "../shared/package-metadata.js";
|
|
5
5
|
import { resolveAdapter } from "../adapters/index.js";
|
|
6
6
|
import { runCoverageAudit } from "../commands/coverage-audit.js";
|
|
7
7
|
import { runCoverageReport } from "../commands/coverage-report.js";
|
|
@@ -547,7 +547,7 @@ export function buildLoopAgentProgram(options) {
|
|
|
547
547
|
program
|
|
548
548
|
.name("loop-agent")
|
|
549
549
|
.description("Loop Agent task and DAG automation CLI")
|
|
550
|
-
.version(
|
|
550
|
+
.version(readCliVersionLabel(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))) ?? "0.0.0", "-V, --version", "display version")
|
|
551
551
|
.showHelpAfterError()
|
|
552
552
|
.option("--repo-root <path>", "repository root")
|
|
553
553
|
.option("--adapter <name>", "harness adapter name")
|
|
@@ -520,6 +520,33 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
520
520
|
}
|
|
521
521
|
let result;
|
|
522
522
|
try {
|
|
523
|
+
// Phase 5 (P1-9): capture the writeSet baseline BEFORE the writer provider
|
|
524
|
+
// call so a transient partial write can be rolled back and recovered via a
|
|
525
|
+
// staged child. Only frontend-implementation writers; a failed capture is a
|
|
526
|
+
// hard fail (no baseline → no rollback → no recovery). Dynamic import avoids
|
|
527
|
+
// an executor ↔ scheduler static cycle.
|
|
528
|
+
if (isWriteTask &&
|
|
529
|
+
(input.task.id === "frontend-implement-pi" ||
|
|
530
|
+
input.task.id === "frontend-repair-pi") &&
|
|
531
|
+
(input.task.writeSet?.length ?? 0) > 0) {
|
|
532
|
+
try {
|
|
533
|
+
const { captureFrontendWriterAttemptIntent } = await import("../workflows/dag/frontend-writer-recovery.js");
|
|
534
|
+
await captureFrontendWriterAttemptIntent({
|
|
535
|
+
cwd: input.cwd,
|
|
536
|
+
runDir: meta.runDir,
|
|
537
|
+
writeSet: input.task.writeSet,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
return {
|
|
542
|
+
ok: false,
|
|
543
|
+
stdout: "",
|
|
544
|
+
stderr: `frontend writer attempt baseline capture failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
545
|
+
failureCategory: "write-guard",
|
|
546
|
+
durationMs: Date.now() - started,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
523
550
|
result = await piStepFn({
|
|
524
551
|
attachedFiles: [],
|
|
525
552
|
modelConfig: resolveDagPiModelConfig(input.model, input.thinking ? { thinking: input.thinking } : undefined),
|
|
@@ -691,6 +718,23 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
691
718
|
const changedFiles = pathsChangedDuringRun(snapshotGitStatusPorcelain(beforeStatus), afterSnapshot, beforePathFingerprints, afterPathFingerprints);
|
|
692
719
|
changeManifestAfterStatus = afterStatus;
|
|
693
720
|
changeManifestChangedFiles = changedFiles;
|
|
721
|
+
// Phase 5: record the attempt changed paths/hashes into the rollback
|
|
722
|
+
// intent so a transient partial write can be CAS-restored.
|
|
723
|
+
if ((input.task.id === "frontend-implement-pi" ||
|
|
724
|
+
input.task.id === "frontend-repair-pi") &&
|
|
725
|
+
(input.task.writeSet?.length ?? 0) > 0) {
|
|
726
|
+
try {
|
|
727
|
+
const { recordFrontendWriterAttempt } = await import("../workflows/dag/frontend-writer-recovery.js");
|
|
728
|
+
await recordFrontendWriterAttempt({
|
|
729
|
+
cwd: input.cwd,
|
|
730
|
+
runDir: meta.runDir,
|
|
731
|
+
changedPaths: changedFiles,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
// best-effort: a missing intent (no baseline captured) is a no-op
|
|
736
|
+
}
|
|
737
|
+
}
|
|
694
738
|
const guard = validateShellWriteGuardFromDiff({
|
|
695
739
|
changedFiles,
|
|
696
740
|
task: input.task,
|
|
@@ -45,6 +45,48 @@ export function readPackageName(packageDir) {
|
|
|
45
45
|
}
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the build stamp (git SHA + build time) written by the build pipeline
|
|
50
|
+
* into `<packageDir>/dist/build-stamp.json`. `undefined` when absent (e.g.
|
|
51
|
+
* running from source via tsx, or a hand-assembled package), which callers
|
|
52
|
+
* treat as "no stamp available".
|
|
53
|
+
*/
|
|
54
|
+
export function readBuildStamp(packageDir) {
|
|
55
|
+
try {
|
|
56
|
+
const raw = readFileSync(path.join(packageDir, "dist", "build-stamp.json"), "utf-8");
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
if (parsed.schemaVersion !== 1 ||
|
|
59
|
+
typeof parsed.version !== "string" ||
|
|
60
|
+
typeof parsed.builtAt !== "string")
|
|
61
|
+
return undefined;
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Human-readable CLI version label: plain version, enriched with the build
|
|
70
|
+
* stamp (short git SHA + build date) when available so a stale dist/ that was
|
|
71
|
+
* built from an old commit is immediately recognizable in `--version`.
|
|
72
|
+
*/
|
|
73
|
+
export function readCliVersionLabel(packageDir) {
|
|
74
|
+
const version = readPackageVersion(packageDir) ?? "0.0.0";
|
|
75
|
+
const stamp = readBuildStamp(packageDir);
|
|
76
|
+
if (!stamp?.gitSha)
|
|
77
|
+
return version;
|
|
78
|
+
return `${version} (build ${stamp.gitSha.slice(0, 8)} ${stamp.builtAt.slice(0, 10)})`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Strip the build-stamp suffix from a CLI `--version` label, returning the bare
|
|
82
|
+
* SemVer. Controller-identity/preflight comparisons must use this rather than
|
|
83
|
+
* the raw label so a build-stamped `0.35.0 (build 7f8c5b5b …)` still matches
|
|
84
|
+
* `packageVersion === "0.35.0"`.
|
|
85
|
+
*/
|
|
86
|
+
export function normalizeCliVersionLabel(label) {
|
|
87
|
+
const match = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/.exec(label.trim());
|
|
88
|
+
return match ? match[0] : label.trim();
|
|
89
|
+
}
|
|
48
90
|
/**
|
|
49
91
|
* Compute the SHA-256 hash of a file on disk.
|
|
50
92
|
*
|
|
@@ -64,6 +64,17 @@ export function extractAssistantContentBlocks(message) {
|
|
|
64
64
|
const top = readString(rec.text);
|
|
65
65
|
return top ? [{ type: "text", text: top }] : [];
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Pi emits message_end for user / toolResult / custom too. Only assistant
|
|
69
|
+
* payloads belong in the Operator Chat assistant bubble (pi-web same split).
|
|
70
|
+
*/
|
|
71
|
+
export function isAssistantSdkMessage(message) {
|
|
72
|
+
const rec = asRecord(message);
|
|
73
|
+
if (!rec)
|
|
74
|
+
return false;
|
|
75
|
+
const role = readString(rec.role);
|
|
76
|
+
return role === undefined || role === "assistant";
|
|
77
|
+
}
|
|
67
78
|
/**
|
|
68
79
|
* Latest assistant answer text (text blocks only). Thinking is excluded.
|
|
69
80
|
* Preserves the historical `{ text }` / `{ parts: [{ text }] }` fallbacks.
|
|
@@ -28,7 +28,7 @@
|
|
|
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, } from "./assistant-content.js";
|
|
31
|
+
import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
|
|
32
32
|
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
33
33
|
import { extractUsageSample } from "./usage.js";
|
|
34
34
|
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
@@ -564,7 +564,7 @@ export function projectRuntimeSnapshot(input) {
|
|
|
564
564
|
hasBash: input.hasBash,
|
|
565
565
|
};
|
|
566
566
|
}
|
|
567
|
-
export { extractAssistantContentBlocks, extractAssistantText };
|
|
567
|
+
export { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage };
|
|
568
568
|
function readSessionManagerId(manager) {
|
|
569
569
|
if (!manager || typeof manager !== "object")
|
|
570
570
|
return undefined;
|
|
@@ -2116,8 +2116,12 @@ function mapSdkEvent(sessionId, event) {
|
|
|
2116
2116
|
willRetry: Boolean(event.willRetry),
|
|
2117
2117
|
};
|
|
2118
2118
|
case "message_update":
|
|
2119
|
+
if (!isAssistantSdkMessage(event.message))
|
|
2120
|
+
return undefined;
|
|
2119
2121
|
return mapAssistantMessageEvent("message_update", sessionId, event.message);
|
|
2120
2122
|
case "message_end":
|
|
2123
|
+
if (!isAssistantSdkMessage(event.message))
|
|
2124
|
+
return undefined;
|
|
2121
2125
|
return mapAssistantMessageEvent("message_end", sessionId, event.message);
|
|
2122
2126
|
case "turn_end":
|
|
2123
2127
|
// Usage is emitted from message_end (see prompt subscribe). turn_end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Turn grouping for Operator Chat inline process UX (pi-web ProcessDetails mind).
|
|
3
3
|
*/
|
|
4
|
-
import { joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
|
|
4
|
+
import { getDisplayableAssistantBlocks, joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
|
|
5
5
|
function emptyGroup(key) {
|
|
6
6
|
return {
|
|
7
7
|
key,
|
|
@@ -76,19 +76,15 @@ export function hydrateSessionThread(raw) {
|
|
|
76
76
|
}
|
|
77
77
|
return { messages, toolCalls };
|
|
78
78
|
}
|
|
79
|
-
function
|
|
80
|
-
const
|
|
81
|
-
const blocks = finalAssistant?.content ?? [];
|
|
82
|
-
const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
|
|
83
|
-
const usedToolIds = new Set();
|
|
84
|
-
const processItems = [];
|
|
85
|
-
for (const block of split.processBlocks) {
|
|
79
|
+
function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
|
|
80
|
+
for (const block of blocks) {
|
|
86
81
|
if (block.type === "thinking") {
|
|
87
82
|
processItems.push({ kind: "thinking", thinking: block.thinking });
|
|
88
83
|
continue;
|
|
89
84
|
}
|
|
90
85
|
if (block.type === "text") {
|
|
91
|
-
|
|
86
|
+
if (block.text.trim())
|
|
87
|
+
processItems.push({ kind: "text", text: block.text });
|
|
92
88
|
continue;
|
|
93
89
|
}
|
|
94
90
|
const tool = group.tools.find((entry) => entry.id === block.toolCallId);
|
|
@@ -97,6 +93,18 @@ function fillProcessDetails(group) {
|
|
|
97
93
|
usedToolIds.add(tool.id);
|
|
98
94
|
}
|
|
99
95
|
}
|
|
96
|
+
}
|
|
97
|
+
function fillProcessDetails(group) {
|
|
98
|
+
const usedToolIds = new Set();
|
|
99
|
+
const processItems = [];
|
|
100
|
+
const prior = group.assistants.slice(0, -1);
|
|
101
|
+
const finalAssistant = group.assistants[group.assistants.length - 1];
|
|
102
|
+
for (const assistant of prior) {
|
|
103
|
+
emitProcessBlocks(getDisplayableAssistantBlocks(assistant.content ?? []), group, processItems, usedToolIds);
|
|
104
|
+
}
|
|
105
|
+
const blocks = finalAssistant?.content ?? [];
|
|
106
|
+
const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
|
|
107
|
+
emitProcessBlocks(split.processBlocks, group, processItems, usedToolIds);
|
|
100
108
|
for (const tool of group.tools) {
|
|
101
109
|
if (!usedToolIds.has(tool.id))
|
|
102
110
|
processItems.push({ kind: "tool", tool });
|
|
@@ -78,7 +78,7 @@ export function resolveDefaultLanding(input) {
|
|
|
78
78
|
return (workspaceFromLocation(input.location) ??
|
|
79
79
|
configuredLanding(input.storage?.getItem(DEFAULT_LANDING_STORAGE_KEY)) ??
|
|
80
80
|
configuredLanding(input.env) ??
|
|
81
|
-
"
|
|
81
|
+
"chat");
|
|
82
82
|
}
|
|
83
83
|
/** Hash path segment for Console virtual routing (no leading `#`). */
|
|
84
84
|
export function buildConsoleHashPath(workspace, params = {}) {
|