@bubblebrain-ai/bubble 0.0.58 → 0.0.60
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/README.md +56 -6
- package/dist/agent/child-runner.js +5 -1
- package/dist/agent/internal-reminder-sanitizer.d.ts +1 -1
- package/dist/agent/internal-reminder-sanitizer.js +7 -1
- package/dist/agent/profiles.js +4 -4
- package/dist/agent/routing-catalog.d.ts +19 -1
- package/dist/agent/routing-catalog.js +32 -13
- package/dist/agent/subagent/runtime.d.ts +28 -0
- package/dist/agent/subagent/runtime.js +34 -30
- package/dist/agent/subagent-control.d.ts +7 -0
- package/dist/agent/subagent-control.js +2 -0
- package/dist/agent/subagent-lifecycle-reminder.js +2 -1
- package/dist/agent/subagent-wait-guidance.d.ts +2 -0
- package/dist/agent/subagent-wait-guidance.js +8 -0
- package/dist/agent/subtask-policy.js +4 -4
- package/dist/agent.d.ts +20 -11
- package/dist/agent.js +200 -117
- package/dist/anthropic-thinking.d.ts +4 -0
- package/dist/anthropic-thinking.js +11 -0
- package/dist/approval/controller.d.ts +20 -6
- package/dist/approval/controller.js +67 -8
- package/dist/approval/protected-paths.d.ts +1 -0
- package/dist/approval/protected-paths.js +35 -0
- package/dist/approval/session-cache.d.ts +12 -26
- package/dist/approval/session-cache.js +20 -50
- package/dist/approval/types.d.ts +27 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +25 -2
- package/dist/config.d.ts +3 -0
- package/dist/config.js +7 -0
- package/dist/context/checkpoint.d.ts +18 -0
- package/dist/context/checkpoint.js +117 -0
- package/dist/context/compact-llm.js +7 -0
- package/dist/context/compact.d.ts +8 -0
- package/dist/context/compact.js +40 -7
- package/dist/context/llm-compactor.d.ts +36 -2
- package/dist/context/llm-compactor.js +227 -44
- package/dist/context/session-write-lock.d.ts +20 -0
- package/dist/context/session-write-lock.js +162 -0
- package/dist/context/tool-images.d.ts +3 -0
- package/dist/context/tool-images.js +35 -0
- package/dist/context/usage.js +22 -5
- package/dist/feishu/agent-host/run-driver.js +56 -9
- package/dist/lsp/service.d.ts +13 -1
- package/dist/lsp/service.js +163 -43
- package/dist/main.js +82 -16
- package/dist/mcp/config.d.ts +4 -0
- package/dist/mcp/config.js +17 -0
- package/dist/mcp/manager.d.ts +13 -1
- package/dist/mcp/manager.js +32 -1
- package/dist/mcp/transports.js +11 -0
- package/dist/memory/db.d.ts +1 -0
- package/dist/memory/db.js +20 -4
- package/dist/memory/phase1.js +122 -16
- package/dist/memory/prompts.js +3 -0
- package/dist/model-catalog.js +19 -3
- package/dist/model-config.d.ts +1 -0
- package/dist/model-config.js +5 -5
- package/dist/model-pricing.js +26 -9
- package/dist/network/provider-transport-log.d.ts +12 -0
- package/dist/network/provider-transport-log.js +32 -0
- package/dist/network/provider-transport.js +6 -1
- package/dist/network/retry.d.ts +1 -1
- package/dist/network/retry.js +1 -1
- package/dist/oauth/grok.d.ts +1 -0
- package/dist/oauth/grok.js +20 -16
- package/dist/oauth/openai-codex.d.ts +1 -0
- package/dist/oauth/openai-codex.js +29 -25
- package/dist/oauth/refresh-control.d.ts +12 -0
- package/dist/oauth/refresh-control.js +42 -0
- package/dist/oauth/storage.d.ts +55 -1
- package/dist/oauth/storage.js +281 -16
- package/dist/permissions/rule.d.ts +6 -3
- package/dist/permissions/rule.js +71 -12
- package/dist/permissions/settings.d.ts +28 -1
- package/dist/permissions/settings.js +93 -3
- package/dist/permissions/shell-command.d.ts +36 -0
- package/dist/permissions/shell-command.js +226 -0
- package/dist/permissions/trust-prompt.d.ts +10 -0
- package/dist/permissions/trust-prompt.js +34 -0
- package/dist/permissions/trust.d.ts +26 -0
- package/dist/permissions/trust.js +113 -0
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +24 -7
- package/dist/prompt/environment.js +2 -2
- package/dist/prompt/reminders.js +1 -1
- package/dist/prompt/runtime.js +1 -1
- package/dist/provider-anthropic.d.ts +6 -2
- package/dist/provider-anthropic.js +98 -18
- package/dist/provider-error-record.d.ts +9 -0
- package/dist/provider-error-record.js +42 -14
- package/dist/provider-grok.js +5 -1
- package/dist/provider-openai-codex.d.ts +8 -0
- package/dist/provider-openai-codex.js +107 -24
- package/dist/provider-registry.d.ts +45 -2
- package/dist/provider-registry.js +300 -88
- package/dist/provider-transform.js +8 -0
- package/dist/provider-zen-errors.d.ts +9 -0
- package/dist/provider-zen-errors.js +36 -0
- package/dist/provider.js +36 -5
- package/dist/sdk/index.d.ts +69 -8
- package/dist/sdk/index.js +252 -39
- package/dist/sdk/replay-event-log.d.ts +7 -0
- package/dist/sdk/replay-event-log.js +66 -16
- package/dist/session-context-fence.d.ts +16 -0
- package/dist/session-context-fence.js +26 -0
- package/dist/session-log.d.ts +11 -2
- package/dist/session-log.js +187 -39
- package/dist/session-types.d.ts +5 -1
- package/dist/session.d.ts +39 -7
- package/dist/session.js +341 -93
- package/dist/skills/format.d.ts +31 -1
- package/dist/skills/format.js +45 -24
- package/dist/slash-commands/commands.d.ts +3 -0
- package/dist/slash-commands/commands.js +29 -14
- package/dist/system-prompt.d.ts +3 -1
- package/dist/tasks/manager.d.ts +7 -0
- package/dist/tasks/manager.js +97 -18
- package/dist/tools/agent-lifecycle.js +11 -7
- package/dist/tools/child-tools.d.ts +1 -1
- package/dist/tools/child-tools.js +4 -4
- package/dist/tools/edit-apply.d.ts +0 -3
- package/dist/tools/edit-apply.js +45 -249
- package/dist/tools/edit.js +3 -3
- package/dist/tools/exit-plan-mode.d.ts +5 -0
- package/dist/tools/exit-plan-mode.js +3 -2
- package/dist/tools/file-state.d.ts +4 -0
- package/dist/tools/file-state.js +12 -3
- package/dist/tools/grep.js +20 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/ls.d.ts +3 -0
- package/dist/tools/ls.js +85 -0
- package/dist/tools/path-utils.d.ts +6 -0
- package/dist/tools/path-utils.js +38 -1
- package/dist/tools/sensitive-paths.d.ts +8 -0
- package/dist/tools/sensitive-paths.js +85 -3
- package/dist/tools/skill.d.ts +2 -0
- package/dist/tools/skill.js +34 -2
- package/dist/tools/task-tools.js +11 -7
- package/dist/tui/app.js +22 -12
- package/dist/tui/components/approval-dialog.d.ts +5 -5
- package/dist/tui/components/approval-dialog.js +16 -51
- package/dist/tui/components/skills-panel.d.ts +4 -0
- package/dist/tui/components/skills-panel.js +15 -1
- package/dist/tui/components/streaming-message.js +7 -2
- package/dist/tui/components/tasks-pane.d.ts +38 -0
- package/dist/tui/components/tasks-pane.js +182 -32
- package/dist/tui/components/transcript.d.ts +11 -4
- package/dist/tui/components/transcript.js +158 -71
- package/dist/tui/composer-autocomplete.d.ts +17 -2
- package/dist/tui/composer-autocomplete.js +79 -2
- package/dist/tui/controller/controller.d.ts +9 -4
- package/dist/tui/controller/controller.js +87 -95
- package/dist/tui/controller/goal-runtime-controller.js +1 -1
- package/dist/tui/controller/session-transition.js +1 -1
- package/dist/tui/model/subagent-view.d.ts +11 -19
- package/dist/tui/model/subagent-view.js +56 -85
- package/dist/tui/model/task-lifecycle.d.ts +57 -0
- package/dist/tui/model/task-lifecycle.js +124 -0
- package/dist/tui/model/trace-groups.js +10 -9
- package/dist/tui/trace-groups.js +10 -9
- package/dist/types.d.ts +20 -0
- package/dist/variant/variant-resolver.js +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +6 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +8 -4
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -1
- package/package.json +11 -3
- package/dist/prompt/skills.d.ts +0 -2
- package/dist/prompt/skills.js +0 -4
- package/dist/tools/glob.d.ts +0 -5
- package/dist/tools/glob.js +0 -199
package/dist/tools/path-utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { lstatSync, readlinkSync, realpathSync } from "node:fs";
|
|
1
2
|
import { homedir } from "node:os";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
4
|
export function expandHomePath(value) {
|
|
4
5
|
const text = String(value ?? "");
|
|
5
6
|
if (text === "~")
|
|
@@ -14,3 +15,39 @@ export function resolveToolPath(cwd, value, fallback = ".") {
|
|
|
14
15
|
const path = text === "" ? fallback : text;
|
|
15
16
|
return resolve(cwd, expandHomePath(path));
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Where a path really leads once symlinks are followed — including links in
|
|
20
|
+
* its parent directories and dangling links (writing through a dangling link
|
|
21
|
+
* creates its target). Parts that do not exist yet are kept as written.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveThroughLinks(path) {
|
|
24
|
+
return followLinks(path, 0);
|
|
25
|
+
}
|
|
26
|
+
function followLinks(path, depth) {
|
|
27
|
+
let current = resolve(path);
|
|
28
|
+
const rest = [];
|
|
29
|
+
for (;;) {
|
|
30
|
+
try {
|
|
31
|
+
return join(realpathSync.native(current), ...rest);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Missing (or dangling): follow a dangling link, else step up.
|
|
35
|
+
}
|
|
36
|
+
if (depth < 16) {
|
|
37
|
+
try {
|
|
38
|
+
if (lstatSync(current).isSymbolicLink()) {
|
|
39
|
+
const target = resolve(dirname(current), readlinkSync(current));
|
|
40
|
+
return followLinks(join(target, ...rest), depth + 1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Not there at all.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const parent = dirname(current);
|
|
48
|
+
if (parent === current)
|
|
49
|
+
return resolve(path);
|
|
50
|
+
rest.unshift(basename(current));
|
|
51
|
+
current = parent;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage the no-prompt tools (read, grep, ls, edit excerpts) must
|
|
3
|
+
* never open. Reading never asks for approval, and neither do web fetches, so
|
|
4
|
+
* without this a prompt-injected model could read a private key and send it
|
|
5
|
+
* out without a single prompt. Bash is not covered here beyond Bubble's own
|
|
6
|
+
* files: bash commands are shown to the user before they run.
|
|
7
|
+
*/
|
|
1
8
|
export declare function getSensitivePaths(): string[];
|
|
2
9
|
export declare function isSensitivePath(filePath: string): boolean;
|
|
10
|
+
/** Bash commands may not name Bubble's own credential files. */
|
|
3
11
|
export declare function referencesSensitivePath(command: string): boolean;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
1
2
|
import { homedir } from "node:os";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, resolve, sep } from "node:path";
|
|
3
4
|
import { getBubbleHome } from "../bubble-home.js";
|
|
5
|
+
/**
|
|
6
|
+
* Credential storage the no-prompt tools (read, grep, ls, edit excerpts) must
|
|
7
|
+
* never open. Reading never asks for approval, and neither do web fetches, so
|
|
8
|
+
* without this a prompt-injected model could read a private key and send it
|
|
9
|
+
* out without a single prompt. Bash is not covered here beyond Bubble's own
|
|
10
|
+
* files: bash commands are shown to the user before they run.
|
|
11
|
+
*/
|
|
4
12
|
export function getSensitivePaths() {
|
|
5
13
|
const bubbleHome = getBubbleHome();
|
|
6
14
|
return [
|
|
@@ -8,10 +16,84 @@ export function getSensitivePaths() {
|
|
|
8
16
|
resolve(bubbleHome, "auth.json"),
|
|
9
17
|
];
|
|
10
18
|
}
|
|
19
|
+
/** Home-relative credential files. */
|
|
20
|
+
const HOME_CREDENTIAL_FILES = [
|
|
21
|
+
".aws/credentials",
|
|
22
|
+
".netrc",
|
|
23
|
+
".git-credentials",
|
|
24
|
+
".npmrc",
|
|
25
|
+
".pypirc",
|
|
26
|
+
".docker/config.json",
|
|
27
|
+
".kube/config",
|
|
28
|
+
".config/gh/hosts.yml",
|
|
29
|
+
".cargo/credentials",
|
|
30
|
+
".cargo/credentials.toml",
|
|
31
|
+
".gem/credentials",
|
|
32
|
+
".terraform.d/credentials.tfrc.json",
|
|
33
|
+
".codex/auth.json",
|
|
34
|
+
".claude/.credentials.json",
|
|
35
|
+
];
|
|
36
|
+
/** Home-relative directories whose whole content is credential material. */
|
|
37
|
+
const HOME_CREDENTIAL_DIRS = [
|
|
38
|
+
".gnupg",
|
|
39
|
+
".password-store",
|
|
40
|
+
".config/gcloud",
|
|
41
|
+
".azure",
|
|
42
|
+
".config/op",
|
|
43
|
+
".aws/sso/cache",
|
|
44
|
+
".aws/cli/cache",
|
|
45
|
+
"Library/Keychains",
|
|
46
|
+
];
|
|
47
|
+
/** Non-secret files in ~/.ssh; everything else there (private keys) is blocked. */
|
|
48
|
+
const SSH_PUBLIC_FILES = new Set(["config", "known_hosts", "known_hosts.old", "authorized_keys"]);
|
|
11
49
|
export function isSensitivePath(filePath) {
|
|
12
|
-
const
|
|
13
|
-
|
|
50
|
+
const candidates = new Set([resolve(filePath)]);
|
|
51
|
+
// A symlink inside the workspace must not smuggle a credential file out.
|
|
52
|
+
try {
|
|
53
|
+
candidates.add(realpathSync(filePath));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Missing files have no link target to check.
|
|
57
|
+
}
|
|
58
|
+
return [...candidates].some(isSensitiveResolved);
|
|
59
|
+
}
|
|
60
|
+
function isSensitiveResolved(resolvedPath) {
|
|
61
|
+
// Case-folded: on the default macOS (and Windows) filesystem
|
|
62
|
+
// `~/.AWS/Credentials` opens `~/.aws/credentials`. On case-sensitive
|
|
63
|
+
// systems this only blocks a few more look-alike paths.
|
|
64
|
+
const path = resolvedPath.toLowerCase();
|
|
65
|
+
if (getSensitivePaths().some((sensitive) => sensitive.toLowerCase() === path))
|
|
66
|
+
return true;
|
|
67
|
+
// Compare against the home directory as written and as resolved, so a
|
|
68
|
+
// resolved link target still lines up when home itself sits behind a link.
|
|
69
|
+
return homeForms().some((home) => isSensitiveUnderHome(path, home.toLowerCase()));
|
|
70
|
+
}
|
|
71
|
+
function homeForms() {
|
|
72
|
+
const home = homedir();
|
|
73
|
+
try {
|
|
74
|
+
const real = realpathSync(home);
|
|
75
|
+
return real === home ? [home] : [home, real];
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return [home];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function isSensitiveUnderHome(path, home) {
|
|
82
|
+
if (HOME_CREDENTIAL_FILES.some((file) => path === resolve(home, file.toLowerCase())))
|
|
83
|
+
return true;
|
|
84
|
+
if (HOME_CREDENTIAL_DIRS.some((dir) => isInside(path, resolve(home, dir.toLowerCase()))))
|
|
85
|
+
return true;
|
|
86
|
+
const ssh = resolve(home, ".ssh");
|
|
87
|
+
if (dirname(path) === ssh) {
|
|
88
|
+
const name = basename(path);
|
|
89
|
+
return !SSH_PUBLIC_FILES.has(name) && !name.endsWith(".pub");
|
|
90
|
+
}
|
|
91
|
+
return isInside(path, ssh) && path !== ssh;
|
|
92
|
+
}
|
|
93
|
+
function isInside(path, dir) {
|
|
94
|
+
return path === dir || path.startsWith(dir + sep);
|
|
14
95
|
}
|
|
96
|
+
/** Bash commands may not name Bubble's own credential files. */
|
|
15
97
|
export function referencesSensitivePath(command) {
|
|
16
98
|
const lower = command.toLowerCase();
|
|
17
99
|
if (lower.includes("~/.bubble/config.json") || lower.includes("~/.bubble/auth.json")) {
|
package/dist/tools/skill.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ import type { SkillRegistry } from "../skills/registry.js";
|
|
|
3
3
|
import type { ToolRegistryEntry } from "../types.js";
|
|
4
4
|
export declare function formatLoadedSkill(skill: SkillRecord): string;
|
|
5
5
|
export declare function createSkillTool(registry: SkillRegistry): ToolRegistryEntry;
|
|
6
|
+
/** Up to three known names closest to a mistyped one (substring first, then edit distance). */
|
|
7
|
+
export declare function closestSkillNames(name: string, names: string[]): string[];
|
package/dist/tools/skill.js
CHANGED
|
@@ -22,7 +22,7 @@ export function createSkillTool(registry) {
|
|
|
22
22
|
name: "skill",
|
|
23
23
|
readOnly: true,
|
|
24
24
|
effect: "read",
|
|
25
|
-
description: "Load a
|
|
25
|
+
description: "Load a skill by its exact name, as listed in the Skills section of the system prompt. Returns the skill's instructions; follow them for the task.",
|
|
26
26
|
parameters: {
|
|
27
27
|
type: "object",
|
|
28
28
|
properties: {
|
|
@@ -38,8 +38,11 @@ export function createSkillTool(registry) {
|
|
|
38
38
|
}
|
|
39
39
|
const skill = registry.get(name);
|
|
40
40
|
if (!skill) {
|
|
41
|
+
const close = closestSkillNames(name, registry.summaries().map((s) => s.name));
|
|
41
42
|
return {
|
|
42
|
-
content: `Error: Unknown skill "${name}"
|
|
43
|
+
content: `Error: Unknown skill "${name}".` +
|
|
44
|
+
(close.length ? ` Did you mean: ${close.join(", ")}?` : "") +
|
|
45
|
+
" Use the exact name from the Skills section.",
|
|
43
46
|
isError: true,
|
|
44
47
|
};
|
|
45
48
|
}
|
|
@@ -47,3 +50,32 @@ export function createSkillTool(registry) {
|
|
|
47
50
|
},
|
|
48
51
|
};
|
|
49
52
|
}
|
|
53
|
+
/** Up to three known names closest to a mistyped one (substring first, then edit distance). */
|
|
54
|
+
export function closestSkillNames(name, names) {
|
|
55
|
+
const target = name.toLowerCase();
|
|
56
|
+
const scored = names.map((candidate) => {
|
|
57
|
+
const lower = candidate.toLowerCase();
|
|
58
|
+
// Substring matches only for real fragments; a 1–2 letter typo would match everything.
|
|
59
|
+
const contains = target.length >= 3 && (lower.includes(target) || target.includes(lower));
|
|
60
|
+
return { candidate, score: contains ? 0 : editDistance(target, lower) };
|
|
61
|
+
});
|
|
62
|
+
const limit = Math.max(2, Math.floor(target.length / 3));
|
|
63
|
+
return scored
|
|
64
|
+
.filter((s) => s.score <= limit)
|
|
65
|
+
.sort((a, b) => a.score - b.score || a.candidate.localeCompare(b.candidate))
|
|
66
|
+
.slice(0, 3)
|
|
67
|
+
.map((s) => s.candidate);
|
|
68
|
+
}
|
|
69
|
+
function editDistance(a, b) {
|
|
70
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
71
|
+
for (let i = 1; i <= a.length; i++) {
|
|
72
|
+
let prev = row[0];
|
|
73
|
+
row[0] = i;
|
|
74
|
+
for (let j = 1; j <= b.length; j++) {
|
|
75
|
+
const cur = row[j];
|
|
76
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
77
|
+
prev = cur;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return row[b.length];
|
|
81
|
+
}
|
package/dist/tools/task-tools.js
CHANGED
|
@@ -87,14 +87,18 @@ export function createBackgroundTaskTools(manager) {
|
|
|
87
87
|
if (!existing) {
|
|
88
88
|
return { content: `Error: unknown task id: ${id || "(empty)"}.`, isError: true };
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
const alreadyFinished = (task) => ({
|
|
91
|
+
content: `Task ${id} already finished (${task.status}${task.exitCode != null ? `, exit ${task.exitCode}` : ""}). Nothing to kill.`,
|
|
92
|
+
status: "success",
|
|
93
|
+
metadata: { kind: "shell", background: true },
|
|
94
|
+
});
|
|
95
|
+
if (existing.status !== "running")
|
|
96
|
+
return alreadyFinished(existing);
|
|
97
97
|
const killed = await manager.killTask(id);
|
|
98
|
+
// The task can exit on its own just before the kill lands; the manager
|
|
99
|
+
// then keeps its real outcome, so do not claim we killed it.
|
|
100
|
+
if (killed && killed.status !== "killed")
|
|
101
|
+
return alreadyFinished(killed);
|
|
98
102
|
return {
|
|
99
103
|
content: `Killed background task ${id}${killed?.description ? ` (${killed.description})` : ""}.`,
|
|
100
104
|
status: "success",
|
package/dist/tui/app.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* All runtime logic flows through BubbleTuiController + OverlayRequestController;
|
|
11
11
|
* this file owns rendering and input routing only.
|
|
12
12
|
*/
|
|
13
|
+
import { classifyExternalRuntimeBinding } from "../external-runtime/session-policy.js";
|
|
13
14
|
import process from "node:process";
|
|
14
15
|
import { ProcessTerminal, TuiAltScreen, TuiMainScreen, VStack, Editor, Input, ScrollView, Markdown, matchesKey, isKeyRelease, isViewportTUI, } from "@bubblebrain-ai/pi-tui";
|
|
15
16
|
import { StreamingMessageComponent } from "./components/streaming-message.js";
|
|
@@ -32,6 +33,7 @@ import { SkillsPanelComponent } from "./components/skills-panel.js";
|
|
|
32
33
|
import { SessionPickerComponent } from "./components/session-picker.js";
|
|
33
34
|
import { ComposerImagePreviewComponent, ImageViewerComponent } from "./components/image-preview.js";
|
|
34
35
|
import { registry as slashRegistry } from "../slash-commands/index.js";
|
|
36
|
+
import { syncSystemPrompt } from "../slash-commands/commands.js";
|
|
35
37
|
import { collectUsageStatsBundle } from "../stats/usage.js";
|
|
36
38
|
import { ComposerController, } from "./controller/composer-controller.js";
|
|
37
39
|
import { OverlayRequestController } from "./controller/overlay-controller.js";
|
|
@@ -153,6 +155,7 @@ export class PiTuiApp {
|
|
|
153
155
|
providerId: () => this.options.agent.providerId,
|
|
154
156
|
themeMode: () => this.themeMode,
|
|
155
157
|
detectedTheme: () => this.detectedTheme,
|
|
158
|
+
grokRuntimeActive: () => classifyExternalRuntimeBinding(this.activeSessionManager().getMetadata?.().externalRuntime) === "grok",
|
|
156
159
|
onModelSuggestionsChanged: () => {
|
|
157
160
|
if (!this.disposed)
|
|
158
161
|
this.editor.refreshAutocomplete();
|
|
@@ -206,6 +209,7 @@ export class PiTuiApp {
|
|
|
206
209
|
groups: this.options.controller.getSubagentGroups?.() ?? [],
|
|
207
210
|
workflows: this.options.controller.getWorkflows?.() ?? [],
|
|
208
211
|
tasks: this.options.controller.getBackgroundTasks?.() ?? [],
|
|
212
|
+
turnStartedAt: this.options.controller.getTurnStartedAt?.(),
|
|
209
213
|
}), () => this.tui.terminal.rows, {
|
|
210
214
|
// Pane navigation only changes local presentation state. Reprojecting
|
|
211
215
|
// the live trace/Markdown here is both unnecessary and expensive.
|
|
@@ -271,17 +275,10 @@ export class PiTuiApp {
|
|
|
271
275
|
if (approvalHandlerRef) {
|
|
272
276
|
approvalHandlerRef.current = async (request) => {
|
|
273
277
|
const choice = await this.approvalDialog(request);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
feedback: "This host cannot remember Bash approvals for the current session.",
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
// Session-scoped and command-scoped: PermissionAwareApprovalController
|
|
282
|
-
// consults this same instance before opening the next dialog.
|
|
283
|
-
this.options.bashAllowlist.add(choice.prefix);
|
|
284
|
-
return { action: "approve" };
|
|
278
|
+
// "This session": the approval controller records the request's
|
|
279
|
+
// sessionGrant (exact bash command / MCP tool) before the next dialog.
|
|
280
|
+
if (choice.kind === "approve_session") {
|
|
281
|
+
return { action: "approve", remember: "session" };
|
|
285
282
|
}
|
|
286
283
|
return choice.kind === "approve_once"
|
|
287
284
|
? { action: "approve" }
|
|
@@ -619,6 +616,12 @@ export class PiTuiApp {
|
|
|
619
616
|
this.composer.replaceDraft(mode === "model" ? "/model " : mode === "provider" ? "/provider " : "/theme ");
|
|
620
617
|
this.editor.refreshAutocomplete();
|
|
621
618
|
}
|
|
619
|
+
else if (mode === "login" || mode === "logout") {
|
|
620
|
+
// Same inline surface as /model: the account list appears as the
|
|
621
|
+
// command's argument menu, so nothing is chosen for the user.
|
|
622
|
+
this.composer.replaceDraft(mode === "login" ? "/login " : "/logout ");
|
|
623
|
+
this.editor.refreshAutocomplete();
|
|
624
|
+
}
|
|
622
625
|
else if (mode === "key" && providerId) {
|
|
623
626
|
this.openProviderKeyPhase(providerId);
|
|
624
627
|
}
|
|
@@ -771,6 +774,10 @@ export class PiTuiApp {
|
|
|
771
774
|
this.animationClock.setActive(false);
|
|
772
775
|
this.tui.stop();
|
|
773
776
|
const { FullscreenApp } = await import("./fullscreen.js");
|
|
777
|
+
// The import yields; a dispose (or a second /fullscreen) racing it must not
|
|
778
|
+
// mount an alternate screen nobody will tear down.
|
|
779
|
+
if (this.disposed || this.fullscreen)
|
|
780
|
+
return;
|
|
774
781
|
this.fullscreen = new FullscreenApp({
|
|
775
782
|
controller: this.options.controller,
|
|
776
783
|
agent: this.options.agent,
|
|
@@ -848,8 +855,12 @@ export class PiTuiApp {
|
|
|
848
855
|
getTerminalRows: () => this.tui.terminal.rows,
|
|
849
856
|
onClose: () => handle?.hide(),
|
|
850
857
|
onRender: () => this.renderSnapshot(),
|
|
858
|
+
getCatalogChars: () => this.options.agent.getSkillCatalogChars?.(),
|
|
851
859
|
onSkillsChanged: () => {
|
|
852
860
|
this.options.agent.setSkillSummaries(skillRegistry.summaries());
|
|
861
|
+
// The Skills catalog lives in the system prompt; rebuild it so the next turn sees the change.
|
|
862
|
+
const ctx = this.buildSlashContext();
|
|
863
|
+
syncSystemPrompt(ctx, ctx.agent.model ?? "");
|
|
853
864
|
this.editor.refreshAutocomplete();
|
|
854
865
|
this.renderSnapshot();
|
|
855
866
|
},
|
|
@@ -1509,7 +1520,6 @@ export class PiTuiApp {
|
|
|
1509
1520
|
approvalDialog(request) {
|
|
1510
1521
|
return new Promise((resolve) => {
|
|
1511
1522
|
const dialog = new ApprovalDialogComponent(request, () => this.tui.terminal.rows, {
|
|
1512
|
-
allowBashPrefix: this.options.bashAllowlist !== undefined,
|
|
1513
1523
|
getTheme: () => this.theme,
|
|
1514
1524
|
});
|
|
1515
1525
|
const handle = this.tui.showOverlay(dialog, {
|
|
@@ -4,8 +4,7 @@ import { type Theme } from "../model/theme.js";
|
|
|
4
4
|
export type ApprovalDialogChoice = {
|
|
5
5
|
kind: "approve_once";
|
|
6
6
|
} | {
|
|
7
|
-
kind: "
|
|
8
|
-
prefix: string;
|
|
7
|
+
kind: "approve_session";
|
|
9
8
|
} | {
|
|
10
9
|
kind: "reject";
|
|
11
10
|
};
|
|
@@ -14,8 +13,6 @@ interface ApprovalPresentation {
|
|
|
14
13
|
details: string[];
|
|
15
14
|
}
|
|
16
15
|
export interface ApprovalDialogOptions {
|
|
17
|
-
/** Only advertise session remembering when the host can persist the prefix. */
|
|
18
|
-
allowBashPrefix?: boolean;
|
|
19
16
|
getTheme?: () => Theme;
|
|
20
17
|
}
|
|
21
18
|
/** Convert every approval variant into a compact, request-specific heading and preview. */
|
|
@@ -31,10 +28,13 @@ export declare class ApprovalDialogComponent implements Component, Focusable {
|
|
|
31
28
|
private readonly options;
|
|
32
29
|
focused: boolean;
|
|
33
30
|
private selectedIndex;
|
|
34
|
-
private bashPrefix;
|
|
35
31
|
onSelect?: (choice: ApprovalDialogChoice) => void;
|
|
36
32
|
onCancel?: () => void;
|
|
37
33
|
constructor(request: ApprovalRequest, getTerminalRows: () => number, options?: ApprovalDialogOptions);
|
|
34
|
+
/**
|
|
35
|
+
* "This session" appears only when the approval controller attached a
|
|
36
|
+
* sessionGrant: the exact bash command, or an MCP tool name.
|
|
37
|
+
*/
|
|
38
38
|
private choices;
|
|
39
39
|
render(width: number): string[];
|
|
40
40
|
handleInput(data: string): void;
|
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import {
|
|
4
|
-
import { inferBashPrefix } from "../../approval/session-cache.js";
|
|
3
|
+
import { matchesKey, truncateToWidth, wrapTextWithAnsi, } from "@bubblebrain-ai/pi-tui";
|
|
5
4
|
import { paintSheetLine, padSheetLine, safeSheetText } from "./bottom-sheet.js";
|
|
6
5
|
import { darkTheme } from "../model/theme.js";
|
|
7
6
|
import { themeDim, themeForeground } from "../model/theme-style.js";
|
|
8
|
-
function printableInput(data) {
|
|
9
|
-
const decoded = decodeKittyPrintable(data);
|
|
10
|
-
if (decoded !== undefined)
|
|
11
|
-
return decoded;
|
|
12
|
-
// ProcessTerminal has already unwrapped bracketed paste. Reject raw control
|
|
13
|
-
// input here so an escape sequence can never become part of an allow rule.
|
|
14
|
-
// eslint-disable-next-line no-control-regex
|
|
15
|
-
if (!data || /[\u0000-\u001f\u007f-\u009f]/.test(data))
|
|
16
|
-
return undefined;
|
|
17
|
-
return data;
|
|
18
|
-
}
|
|
19
7
|
function bashCommandLabel(command) {
|
|
20
8
|
const firstToken = safeSheetText(command).trim().match(/^([^\s;&|]+)/)?.[1] ?? "command";
|
|
21
9
|
const unquoted = firstToken.replace(/^["'`]+|["'`]+$/g, "");
|
|
@@ -97,23 +85,29 @@ export class ApprovalDialogComponent {
|
|
|
97
85
|
options;
|
|
98
86
|
focused = false;
|
|
99
87
|
selectedIndex = 0;
|
|
100
|
-
bashPrefix;
|
|
101
88
|
onSelect;
|
|
102
89
|
onCancel;
|
|
103
90
|
constructor(request, getTerminalRows, options = {}) {
|
|
104
91
|
this.request = request;
|
|
105
92
|
this.getTerminalRows = getTerminalRows;
|
|
106
93
|
this.options = options;
|
|
107
|
-
this.bashPrefix = request.type === "bash" ? inferBashPrefix(request.command) : "";
|
|
108
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* "This session" appears only when the approval controller attached a
|
|
97
|
+
* sessionGrant: the exact bash command, or an MCP tool name.
|
|
98
|
+
*/
|
|
109
99
|
choices() {
|
|
100
|
+
const grant = this.request.type === "bash" || this.request.type === "external_tool"
|
|
101
|
+
? this.request.sessionGrant
|
|
102
|
+
: undefined;
|
|
110
103
|
return [
|
|
111
104
|
{ kind: "approve_once", label: "Yes, proceed" },
|
|
112
|
-
...(
|
|
105
|
+
...(grant
|
|
113
106
|
? [{
|
|
114
|
-
kind: "
|
|
115
|
-
label:
|
|
116
|
-
|
|
107
|
+
kind: "approve_session",
|
|
108
|
+
label: this.request.type === "bash"
|
|
109
|
+
? "Yes, and don't ask again for this exact command this session"
|
|
110
|
+
: `Yes, don't ask again this session for ${safeSheetText(grant)}`,
|
|
117
111
|
}]
|
|
118
112
|
: []),
|
|
119
113
|
{ kind: "reject", label: "No, reject" },
|
|
@@ -173,10 +167,7 @@ export class ApprovalDialogComponent {
|
|
|
173
167
|
for (const { choice, index } of visibleChoices) {
|
|
174
168
|
const selected = index === this.selectedIndex;
|
|
175
169
|
const radio = selected ? "●" : "○";
|
|
176
|
-
const
|
|
177
|
-
? ` [${safeSheetText(this.bashPrefix) || "command prefix"}${selected ? "▏" : ""}]`
|
|
178
|
-
: "";
|
|
179
|
-
const label = `${index + 1} (${radio}) ${choice.label}${prefix}`;
|
|
170
|
+
const label = `${index + 1} (${radio}) ${choice.label}`;
|
|
180
171
|
const line = `${indent}${selected
|
|
181
172
|
? chalk.bold(themeForeground(theme.inputText, label))
|
|
182
173
|
: themeForeground(theme.muted, label)}`;
|
|
@@ -187,10 +178,7 @@ export class ApprovalDialogComponent {
|
|
|
187
178
|
const paintedPanel = panelLines.slice(0, panelBudget).map(({ line, selected }) => paintSheetLine(line, safeWidth, selected, theme));
|
|
188
179
|
if (!showHelp)
|
|
189
180
|
return paintedPanel;
|
|
190
|
-
const
|
|
191
|
-
const help = editing
|
|
192
|
-
? `${this.selectedIndex + 1}/${choices.length} select │ type/backspace edit prefix │ Ctrl+U clear │ Enter confirm │ Esc deny`
|
|
193
|
-
: `${this.selectedIndex + 1}/${choices.length} select │ Tab next │ Enter confirm │ Esc deny`;
|
|
181
|
+
const help = `${this.selectedIndex + 1}/${choices.length} select │ Tab next │ Enter confirm │ Esc deny`;
|
|
194
182
|
return [...paintedPanel, themeDim(theme.dim, padSheetLine(help, safeWidth))];
|
|
195
183
|
}
|
|
196
184
|
handleInput(data) {
|
|
@@ -210,35 +198,12 @@ export class ApprovalDialogComponent {
|
|
|
210
198
|
const selected = choices[this.selectedIndex];
|
|
211
199
|
if (!selected)
|
|
212
200
|
return;
|
|
213
|
-
if (selected.editablePrefix && (matchesKey(data, "backspace") || matchesKey(data, "delete"))) {
|
|
214
|
-
this.bashPrefix = Array.from(this.bashPrefix).slice(0, -1).join("");
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (selected.editablePrefix && matchesKey(data, "ctrl+u")) {
|
|
218
|
-
this.bashPrefix = "";
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
201
|
if (matchesKey(data, "enter")) {
|
|
222
|
-
|
|
223
|
-
const prefix = this.bashPrefix.trim();
|
|
224
|
-
if (!prefix)
|
|
225
|
-
return;
|
|
226
|
-
this.onSelect?.({ kind: selected.kind, prefix });
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
this.onSelect?.({ kind: selected.kind });
|
|
230
|
-
}
|
|
202
|
+
this.onSelect?.({ kind: selected.kind });
|
|
231
203
|
return;
|
|
232
204
|
}
|
|
233
205
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
234
206
|
this.onCancel?.();
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (selected.editablePrefix) {
|
|
238
|
-
const printable = printableInput(data);
|
|
239
|
-
if (printable !== undefined) {
|
|
240
|
-
this.bashPrefix += safeSheetText(printable).replace(/[\r\n\t]+/g, " ");
|
|
241
|
-
}
|
|
242
207
|
}
|
|
243
208
|
}
|
|
244
209
|
invalidate() {
|
|
@@ -6,6 +6,8 @@ export interface SkillsPanelComponentOptions {
|
|
|
6
6
|
onClose(): void;
|
|
7
7
|
onRender(): void;
|
|
8
8
|
onSkillsChanged(): void;
|
|
9
|
+
/** Catalog budget in characters (the agent's setting); undefined uses the default. */
|
|
10
|
+
getCatalogChars?(): number | undefined;
|
|
9
11
|
theme?: Theme;
|
|
10
12
|
}
|
|
11
13
|
/** Grok-style centered Skill browser/manager opened by /skills. */
|
|
@@ -37,6 +39,8 @@ export declare class SkillsPanelComponent implements Component, Focusable {
|
|
|
37
39
|
handleMouse(event: TuiMouseEvent): boolean;
|
|
38
40
|
invalidate(): void;
|
|
39
41
|
private frameLine;
|
|
42
|
+
/** How much of the always-present catalog the enabled skills use, so users know when to prune. */
|
|
43
|
+
private catalogSummary;
|
|
40
44
|
private renderSearch;
|
|
41
45
|
private renderFooter;
|
|
42
46
|
private buildRows;
|
|
@@ -2,6 +2,7 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { Input, decodeKittyPrintable, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@bubblebrain-ai/pi-tui";
|
|
5
|
+
import { buildSkillCatalog } from "../../skills/format.js";
|
|
5
6
|
import { parseTerminalMouseWheel } from "../model/terminal-mouse.js";
|
|
6
7
|
import { darkTheme } from "../model/theme.js";
|
|
7
8
|
import { themeBackground, themeDim, themeForeground } from "../model/theme-style.js";
|
|
@@ -116,7 +117,7 @@ export class SkillsPanelComponent {
|
|
|
116
117
|
const top = themeForeground(theme.border, `┌${"─".repeat(topDashCount)}`) + ` ${close} ` + themeForeground(theme.border, "┐");
|
|
117
118
|
const bottom = themeForeground(theme.border, `└${horizontal}┘`);
|
|
118
119
|
const separator = themeForeground(theme.border, `├${horizontal}┤`);
|
|
119
|
-
const tab = this.frameLine(` ${chalk.bold(themeForeground(theme.inputText, themeBackground(theme.traceHoverBg, " Skills ")))}`, innerWidth);
|
|
120
|
+
const tab = this.frameLine(` ${chalk.bold(themeForeground(theme.inputText, themeBackground(theme.traceHoverBg, " Skills ")))} ${themeForeground(theme.muted, this.catalogSummary())}`, innerWidth);
|
|
120
121
|
const search = this.frameLine(this.renderSearch(innerWidth), innerWidth);
|
|
121
122
|
const footerRows = this.searchActive ? 0 : 2;
|
|
122
123
|
this.bodyRows = Math.max(1, frameHeight - 5 - footerRows);
|
|
@@ -284,6 +285,19 @@ export class SkillsPanelComponent {
|
|
|
284
285
|
const theme = this.options.theme ?? darkTheme;
|
|
285
286
|
return `${themeForeground(theme.border, "│")}${themeBackground(theme.backgroundPanel, fit(content, width))}${themeForeground(theme.border, "│")}`;
|
|
286
287
|
}
|
|
288
|
+
/** How much of the always-present catalog the enabled skills use, so users know when to prune. */
|
|
289
|
+
catalogSummary() {
|
|
290
|
+
const catalog = buildSkillCatalog(this.registry.summaries(), { budgetChars: this.options.getCatalogChars?.() });
|
|
291
|
+
if (catalog.budgetChars === 0)
|
|
292
|
+
return "catalog off · skill_search only";
|
|
293
|
+
const duplicates = this.registry.getDiagnostics().filter((d) => d.message.startsWith("Duplicate skill name")).length;
|
|
294
|
+
return [
|
|
295
|
+
`catalog ${catalog.chars.toLocaleString("en-US")}/${catalog.budgetChars.toLocaleString("en-US")} chars`,
|
|
296
|
+
`${catalog.listed} listed`,
|
|
297
|
+
catalog.unlisted ? `${catalog.unlisted} not listed` : "",
|
|
298
|
+
duplicates ? `${duplicates} duplicate${duplicates === 1 ? "" : "s"} ignored` : "",
|
|
299
|
+
].filter(Boolean).join(" · ");
|
|
300
|
+
}
|
|
287
301
|
renderSearch(innerWidth) {
|
|
288
302
|
const theme = this.options.theme ?? darkTheme;
|
|
289
303
|
const filter = filterLabel(this.filter);
|
|
@@ -268,10 +268,15 @@ export class StreamingMessageComponent extends VStack {
|
|
|
268
268
|
timelineBlocks.push({ rows, traceTargets: rows.map(() => undefined) });
|
|
269
269
|
}
|
|
270
270
|
const timeline = joinTranscriptProjections(timelineBlocks);
|
|
271
|
-
|
|
271
|
+
const start = Math.max(0, timeline.rows.length - this.timelineRows.length);
|
|
272
|
+
// The settled transcript joins reasoning and the timeline with exactly one
|
|
273
|
+
// spacer. A tool group already carries its own top padding, so adding the
|
|
274
|
+
// gap row on top of it made the live surface one row taller than the
|
|
275
|
+
// committed one and the trace jumped up at every turn boundary.
|
|
276
|
+
const timelineBringsSpacer = start === 0 && timeline.leadingSpacer === true;
|
|
277
|
+
this.reasoningGapRow.setLines(tail.reasoning && timeline.rows.length > 0 && !timelineBringsSpacer ? 1 : 0);
|
|
272
278
|
if (timeline.rows.length > 0) {
|
|
273
279
|
hasLiveRows = true;
|
|
274
|
-
const start = Math.max(0, timeline.rows.length - this.timelineRows.length);
|
|
275
280
|
const visible = timeline.rows.slice(start);
|
|
276
281
|
const visibleTargets = timeline.traceTargets.slice(start);
|
|
277
282
|
this.timelineEllipsisRow.setText(timeline.rows.length > visible.length
|
|
@@ -7,6 +7,8 @@ export interface TasksPaneSnapshot {
|
|
|
7
7
|
groups: SubagentGroup[];
|
|
8
8
|
workflows: WorkflowRunSnapshot[];
|
|
9
9
|
tasks: BackgroundTaskInfo[];
|
|
10
|
+
/** Start of the current agent run; undefined keeps finished work hidden. */
|
|
11
|
+
turnStartedAt?: number;
|
|
10
12
|
}
|
|
11
13
|
export interface WorkflowPaneItem {
|
|
12
14
|
kind: "workflow";
|
|
@@ -51,10 +53,25 @@ export declare class TasksPaneComponent implements Component {
|
|
|
51
53
|
focused: boolean;
|
|
52
54
|
private open;
|
|
53
55
|
private manuallyClosed;
|
|
56
|
+
/**
|
|
57
|
+
* History is a view the user opts into for one look, never a mode: it is
|
|
58
|
+
* dropped whenever the pane closes and when a new round of activity starts.
|
|
59
|
+
* Otherwise one Ctrl+G after a finished turn left every earlier subagent
|
|
60
|
+
* listed above the composer for the rest of the session, next to a header
|
|
61
|
+
* that counts only the running ones.
|
|
62
|
+
*/
|
|
54
63
|
private showHistory;
|
|
55
64
|
private selectedId?;
|
|
65
|
+
/**
|
|
66
|
+
* Whether the user picked the selection (keys or click). The pane always
|
|
67
|
+
* needs one, so it defaults to the first row — but a default nobody chose
|
|
68
|
+
* must not steer the window: a task that was once the only row stayed
|
|
69
|
+
* "selected" and scrolled later subagents out of an untouched pane.
|
|
70
|
+
*/
|
|
71
|
+
private selectionIsUsers;
|
|
56
72
|
private hoveredId?;
|
|
57
73
|
private lastActiveCount;
|
|
74
|
+
private lastTurnStartedAt;
|
|
58
75
|
private lastRows;
|
|
59
76
|
private allRows;
|
|
60
77
|
private frame;
|
|
@@ -67,11 +84,32 @@ export declare class TasksPaneComponent implements Component {
|
|
|
67
84
|
isOpen(): boolean;
|
|
68
85
|
isAvailable(): boolean;
|
|
69
86
|
activeCount(): number;
|
|
87
|
+
/** Rows the default view lists: running work plus what this turn launched. */
|
|
88
|
+
defaultViewCount(): number;
|
|
89
|
+
visibleCount(): number;
|
|
90
|
+
/**
|
|
91
|
+
* Finished rows the pane lists next to the running ones, by outcome. Counted
|
|
92
|
+
* in the pane's current view, history included when it is showing, so the
|
|
93
|
+
* header never reports fewer rows than the list holds.
|
|
94
|
+
*/
|
|
95
|
+
settledCounts(): {
|
|
96
|
+
done: number;
|
|
97
|
+
failed: number;
|
|
98
|
+
stopped: number;
|
|
99
|
+
};
|
|
70
100
|
totalCount(): number;
|
|
71
101
|
activityGlyph(): string;
|
|
72
102
|
theme(): Theme;
|
|
73
103
|
toggle(forceFocus?: boolean): void;
|
|
74
104
|
close(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Advance open/history state from the latest snapshot. Both the status bar
|
|
107
|
+
* and the pane call this before rendering: the bar is laid out first, so a
|
|
108
|
+
* transition made only inside the pane's render would leave the bar one
|
|
109
|
+
* frame behind — forever, when nothing is animating to trigger a redraw.
|
|
110
|
+
* Idempotent per snapshot (it compares against the last values it saw).
|
|
111
|
+
*/
|
|
112
|
+
syncState(): void;
|
|
75
113
|
render(width: number): string[];
|
|
76
114
|
private renderItem;
|
|
77
115
|
handleInput(data: string): void;
|