Compare commits

...

14 Commits

Author SHA1 Message Date
Jacob Richman 9f76f34049 ink 6.6.3 (#24372)
Co-authored-by: matt korwel <matt.korwel@gmail.com>
2026-04-01 04:50:12 +00:00
matt korwel 7c5cd693ce Set memoryManager to false in settings.json (#24393) 2026-04-01 03:38:50 +00:00
matt korwel c9ed5e41b1 fix(cli): refactor test config loading and mock debugLogger in test-setup (#24389) 2026-03-31 20:11:02 -07:00
Daniel R. Neal 8ae5b56b5b feat(ui): add Tokyo Night theme (#24054)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
2026-04-01 01:08:28 +00:00
Sri Pasumarthi 1b265f343f fix(core): pin AuthType.GATEWAY to use Gemini 3.1 Pro/Flash Lite by default (#24375) 2026-04-01 00:25:59 +00:00
joshualitt fd5c103f99 refactor(core): Centralize context management logic into src/context (#24380) 2026-04-01 00:01:46 +00:00
Jarrod Whelan cdc602edd7 fix(ui): wrap topic and intent fields in TopicMessage (#24386) 2026-04-01 00:00:48 +00:00
Gaurav 782bb4e4bd fix(core): fix browser agent UX issues and improve E2E test reliability (#24312) 2026-03-31 23:00:16 +00:00
David Pierce 94f9480a3a fix(core): resolve Plan Mode deadlock during plan file creation due to sandbox restrictions (#24047) 2026-03-31 22:06:50 +00:00
Abhi 9364dd8a49 feat(core): infrastructure for event-driven subagent history (#23914) 2026-03-31 21:54:22 +00:00
Tirth Panchal 6d48a12efe docs: fix broken GEMINI.md link in CONTRIBUTING.md (#24182)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Sam Roberts <158088236+g-samroberts@users.noreply.github.com>
2026-03-31 21:44:00 +00:00
Abhi beab7cc053 docs(policy): remove unsupported mcpName wildcard edge case (#24133) 2026-03-31 21:11:29 +00:00
Gal Zahavi 6cade3eaec test(sandbox): add integration test for dynamic permission expansion (#24359) 2026-03-31 20:59:44 +00:00
Sandy Tao c8d18eb2ac fix(config): disable JIT context loading by default (#24364) 2026-03-31 20:31:22 +00:00
116 changed files with 2294 additions and 795 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"experimental": {
"extensionReloading": true,
"modelSteering": true,
"memoryManager": true,
"memoryManager": false,
"topicUpdateNarration": true
},
"general": {
+8 -5
View File
@@ -346,9 +346,11 @@ npm run lint
- Please adhere to the coding style, patterns, and conventions used throughout
the existing codebase.
- Consult [GEMINI.md](../GEMINI.md) (typically found in the project root) for
specific instructions related to AI-assisted development, including
conventions for React, comments, and Git usage.
- Consult
[GEMINI.md](https://github.com/google-gemini/gemini-cli/blob/main/GEMINI.md)
(typically found in the project root) for specific instructions related to
AI-assisted development, including conventions for React, comments, and Git
usage.
- **Imports:** Pay special attention to import paths. The project uses ESLint to
enforce restrictions on relative imports between packages.
@@ -505,8 +507,9 @@ code.
### Documentation structure
Our documentation is organized using [sidebar.json](/docs/sidebar.json) as the
table of contents. When adding new documentation:
Our documentation is organized using
[sidebar.json](https://github.com/google-gemini/gemini-cli/blob/main/docs/sidebar.json)
as the table of contents. When adding new documentation:
1. Create your markdown file **in the appropriate directory** under `/docs`.
2. Add an entry to `sidebar.json` in the relevant section.
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

+5
View File
@@ -19,6 +19,7 @@ using the `/theme` command within Gemini CLI:
- `Holiday`
- `Shades Of Purple`
- `Solarized Dark`
- `Tokyo Night`
- **Light themes:**
- `ANSI Light`
- `Ayu Light`
@@ -252,6 +253,10 @@ identify their source, for example: `shades-of-green (green-extension)`.
<img src="/docs/assets/theme-solarized-dark.png" alt="Solarized Dark theme" width="600">
### Tokyo Night
<img src="/docs/assets/theme-tokyonight-dark.png" alt="Tokyo Night theme" width="600">
## Light themes
### ANSI Light
+1 -1
View File
@@ -1647,7 +1647,7 @@ their corresponding top-level category object in your `settings.json` file.
- **`experimental.jitContext`** (boolean):
- **Description:** Enable Just-In-Time (JIT) context loading.
- **Default:** `true`
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.useOSC52Paste`** (boolean):
-14
View File
@@ -419,20 +419,6 @@ decision = "ask_user"
priority = 10
```
**4. Targeting a tool name across all servers**
Use `mcpName = "*"` with a specific `toolName` to target that operation
regardless of which server provides it.
```toml
# Allow the `search` tool across all connected MCP servers
[[rule]]
mcpName = "*"
toolName = "search"
decision = "allow"
priority = 50
```
## Default policies
The Gemini CLI ships with a set of default policies to provide a safe
+43
View File
@@ -283,4 +283,47 @@ describe('plan_mode', () => {
assertModelHasOutput(result);
},
});
evalTest('ALWAYS_PASSES', {
name: 'should transition from plan mode to normal execution and create a plan file from scratch',
params: {
settings,
},
prompt:
'Enter plan mode and plan to create a new module called foo. The plan should be saved as foo-plan.md. Then, exit plan mode.',
assert: async (rig, result) => {
const enterPlanCalled = await rig.waitForToolCall('enter_plan_mode');
expect(
enterPlanCalled,
'Expected enter_plan_mode tool to be called',
).toBe(true);
const exitPlanCalled = await rig.waitForToolCall('exit_plan_mode');
expect(exitPlanCalled, 'Expected exit_plan_mode tool to be called').toBe(
true,
);
await rig.waitForTelemetryReady();
const toolLogs = rig.readToolLogs();
// Check if the plan file was written successfully
const planWrite = toolLogs.find(
(log) =>
log.toolRequest.name === 'write_file' &&
log.toolRequest.args.includes('foo-plan.md'),
);
expect(
planWrite,
'Expected write_file to be called for foo-plan.md',
).toBeDefined();
expect(
planWrite?.toolRequest.success,
`Expected write_file to succeed, but got error: ${planWrite?.toolRequest.error}`,
).toBe(true);
assertModelHasOutput(result);
},
});
});
+105 -94
View File
@@ -10,8 +10,13 @@ import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
import { existsSync, writeFileSync, readFileSync, mkdirSync } from 'node:fs';
import { env } from 'node:process';
import stripAnsi from 'strip-ansi';
// Browser agent Chrome DevTools MCP connection is flaky in Docker sandbox.
// See: https://github.com/google-gemini/gemini-cli/issues/24382
const isDockerSandbox = env['GEMINI_SANDBOX'] === 'docker';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -59,122 +64,128 @@ describe.skipIf(!chromeAvailable)('browser-policy', () => {
await rig.cleanup();
});
it('should skip confirmation when "Allow all server tools for this session" is chosen', async () => {
rig.setup('browser-policy-skip-confirmation', {
fakeResponsesPath: join(__dirname, 'browser-policy.responses'),
settings: {
agents: {
overrides: {
browser_agent: {
enabled: true,
it.skipIf(isDockerSandbox)(
'should skip confirmation when "Allow all server tools for this session" is chosen',
async () => {
rig.setup('browser-policy-skip-confirmation', {
fakeResponsesPath: join(__dirname, 'browser-policy.responses'),
settings: {
agents: {
overrides: {
browser_agent: {
enabled: true,
},
},
browser: {
headless: true,
sessionMode: 'isolated',
allowedDomains: ['example.com'],
},
},
browser: {
headless: true,
sessionMode: 'isolated',
allowedDomains: ['example.com'],
},
},
},
});
});
// Manually trust the folder to avoid the dialog and enable option 3
const geminiDir = join(rig.homeDir!, '.gemini');
mkdirSync(geminiDir, { recursive: true });
// Manually trust the folder to avoid the dialog and enable option 3
const geminiDir = join(rig.homeDir!, '.gemini');
mkdirSync(geminiDir, { recursive: true });
// Write to trustedFolders.json
const trustedFoldersPath = join(geminiDir, 'trustedFolders.json');
const trustedFolders = {
[rig.testDir!]: 'TRUST_FOLDER',
};
writeFileSync(trustedFoldersPath, JSON.stringify(trustedFolders, null, 2));
// Write to trustedFolders.json
const trustedFoldersPath = join(geminiDir, 'trustedFolders.json');
const trustedFolders = {
[rig.testDir!]: 'TRUST_FOLDER',
};
writeFileSync(
trustedFoldersPath,
JSON.stringify(trustedFolders, null, 2),
);
// Force confirmation for browser agent.
// NOTE: We don't force confirm browser tools here because "Allow all server tools"
// adds a rule with ALWAYS_ALLOW_PRIORITY (3.9x) which would be overshadowed by
// a rule in the user tier (4.x) like the one from this TOML.
// By removing the explicit mcp rule, the first MCP tool will still prompt
// due to default approvalMode = 'default', and then "Allow all" will correctly
// bypass subsequent tools.
const policyFile = join(rig.testDir!, 'force-confirm.toml');
writeFileSync(
policyFile,
`
// Force confirmation for browser agent.
// NOTE: We don't force confirm browser tools here because "Allow all server tools"
// adds a rule with ALWAYS_ALLOW_PRIORITY (3.9x) which would be overshadowed by
// a rule in the user tier (4.x) like the one from this TOML.
// By removing the explicit mcp rule, the first MCP tool will still prompt
// due to default approvalMode = 'default', and then "Allow all" will correctly
// bypass subsequent tools.
const policyFile = join(rig.testDir!, 'force-confirm.toml');
writeFileSync(
policyFile,
`
[[rule]]
name = "Force confirm browser_agent"
toolName = "browser_agent"
decision = "ask_user"
priority = 200
`,
);
);
// Update settings.json in both project and home directories to point to the policy file
for (const baseDir of [rig.testDir!, rig.homeDir!]) {
const settingsPath = join(baseDir, '.gemini', 'settings.json');
if (existsSync(settingsPath)) {
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
settings.policyPaths = [policyFile];
// Ensure folder trust is enabled
settings.security = settings.security || {};
settings.security.folderTrust = settings.security.folderTrust || {};
settings.security.folderTrust.enabled = true;
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
// Update settings.json in both project and home directories to point to the policy file
for (const baseDir of [rig.testDir!, rig.homeDir!]) {
const settingsPath = join(baseDir, '.gemini', 'settings.json');
if (existsSync(settingsPath)) {
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
settings.policyPaths = [policyFile];
// Ensure folder trust is enabled
settings.security = settings.security || {};
settings.security.folderTrust = settings.security.folderTrust || {};
settings.security.folderTrust.enabled = true;
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
}
}
}
const run = await rig.runInteractive({
approvalMode: 'default',
env: {
GEMINI_CLI_INTEGRATION_TEST: 'true',
},
});
const run = await rig.runInteractive({
approvalMode: 'default',
env: {
GEMINI_CLI_INTEGRATION_TEST: 'true',
},
});
await run.sendKeys(
'Open https://example.com and check if there is a heading\r',
);
await run.sendKeys('\r');
await run.sendKeys(
'Open https://example.com and check if there is a heading\r',
);
await run.sendKeys('\r');
// Handle confirmations.
// 1. Initial browser_agent delegation (likely only 3 options, so use option 1: Allow once)
await poll(
() => stripAnsi(run.output).toLowerCase().includes('action required'),
60000,
1000,
);
await run.sendKeys('1\r');
await new Promise((r) => setTimeout(r, 2000));
// Handle confirmations.
// 1. Initial browser_agent delegation (likely only 3 options, so use option 1: Allow once)
await poll(
() => stripAnsi(run.output).toLowerCase().includes('action required'),
60000,
1000,
);
await run.sendKeys('1\r');
await new Promise((r) => setTimeout(r, 2000));
// Handle privacy notice
await poll(
() => stripAnsi(run.output).toLowerCase().includes('privacy notice'),
5000,
100,
);
await run.sendKeys('1\r');
await new Promise((r) => setTimeout(r, 5000));
// Handle privacy notice
await poll(
() => stripAnsi(run.output).toLowerCase().includes('privacy notice'),
5000,
100,
);
await run.sendKeys('1\r');
await new Promise((r) => setTimeout(r, 5000));
// new_page (MCP tool, should have 4 options, use option 3: Allow all server tools)
await poll(
() => {
const stripped = stripAnsi(run.output).toLowerCase();
return (
stripped.includes('new_page') &&
stripped.includes('allow all server tools for this session')
);
},
60000,
1000,
);
// new_page (MCP tool, should have 4 options, use option 3: Allow all server tools)
await poll(
() => {
const stripped = stripAnsi(run.output).toLowerCase();
return (
stripped.includes('new_page') &&
stripped.includes('allow all server tools for this session')
);
},
60000,
1000,
);
// Select "Allow all server tools for this session" (option 3)
await run.sendKeys('3\r');
await new Promise((r) => setTimeout(r, 30000));
// Select "Allow all server tools for this session" (option 3)
await run.sendKeys('3\r');
await new Promise((r) => setTimeout(r, 30000));
const output = stripAnsi(run.output).toLowerCase();
const output = stripAnsi(run.output).toLowerCase();
expect(output).toContain('browser_agent');
expect(output).toContain('completed successfully');
});
expect(output).toContain('browser_agent');
expect(output).toContain('completed successfully');
},
);
it('should show the visible warning when browser agent starts in existing session mode', async () => {
rig.setup('browser-session-warning', {
+1
View File
@@ -121,6 +121,7 @@ describe('file-system', () => {
const result = await rig.run({
args: `write "hello" to "${fileName}" and then stop. Do not perform any other actions.`,
timeout: 600000, // 10 min — real LLM can be slow in Docker sandbox
});
const foundToolCall = await rig.waitForToolCall('write_file');
+25 -50
View File
@@ -11,7 +11,7 @@
"packages/*"
],
"dependencies": {
"ink": "npm:@jrichman/ink@6.5.0",
"ink": "npm:@jrichman/ink@6.6.3",
"latest-version": "^9.0.0",
"node-fetch-native": "^1.6.7",
"proper-lockfile": "^4.1.2",
@@ -92,46 +92,6 @@
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/@alcalzone/ansi-tokenize": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.2.tgz",
"integrity": "sha512-mkOh+Wwawzuf5wa30bvc4nA+Qb6DIrGWgBhRR/Pw4T9nsgYait8izvXkNyU78D6Wcu3Z+KUdwCmLCxlWjEotYA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@alcalzone/ansi-tokenize/node_modules/is-fullwidth-code-point": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -10089,14 +10049,13 @@
},
"node_modules/ink": {
"name": "@jrichman/ink",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.5.0.tgz",
"integrity": "sha512-S4g/ng7fPZmFwclO82iWkOce8vDLy/FIDgHIfkCWGOehqHe6dexHsmq3kNQD21okh198pA5SAQTCqNQJb/svRQ==",
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.6.3.tgz",
"integrity": "sha512-0v4S7TbbF2tpQrfqH1btwLgTgH+K0vY2BJbokTE5Lk1KBr4TqZ+Pyo+geSD5F+zytX6G2ajGHBQyHk8yGK4C7A==",
"license": "MIT",
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.2.1",
"ansi-escapes": "^7.0.0",
"ansi-styles": "^6.2.1",
"ansi-styles": "^6.2.3",
"auto-bind": "^5.0.1",
"chalk": "^5.6.0",
"cli-boxes": "^3.0.0",
@@ -10105,6 +10064,7 @@
"code-excerpt": "^4.0.0",
"es-toolkit": "^1.39.10",
"indent-string": "^5.0.0",
"is-fullwidth-code-point": "^5.0.0",
"is-in-ci": "^2.0.0",
"mnemonist": "^0.40.3",
"patch-console": "^2.0.0",
@@ -10174,9 +10134,9 @@
}
},
"node_modules/ink/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -10197,6 +10157,21 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/ink/node_modules/is-fullwidth-code-point": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.3.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ink/node_modules/is-in-ci": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz",
@@ -17551,7 +17526,7 @@
"fzf": "^0.5.2",
"glob": "^12.0.0",
"highlight.js": "^11.11.1",
"ink": "npm:@jrichman/ink@6.5.0",
"ink": "npm:@jrichman/ink@6.6.3",
"ink-gradient": "^3.0.0",
"ink-spinner": "^5.0.0",
"latest-version": "^9.0.0",
+2 -2
View File
@@ -68,7 +68,7 @@
"pre-commit": "node scripts/pre-commit.js"
},
"overrides": {
"ink": "npm:@jrichman/ink@6.5.0",
"ink": "npm:@jrichman/ink@6.6.3",
"wrap-ansi": "9.0.2",
"cliui": {
"wrap-ansi": "7.0.0"
@@ -136,7 +136,7 @@
"yargs": "^17.7.2"
},
"dependencies": {
"ink": "npm:@jrichman/ink@6.5.0",
"ink": "npm:@jrichman/ink@6.6.3",
"latest-version": "^9.0.0",
"node-fetch-native": "^1.6.7",
"proper-lockfile": "^4.1.2",
+1 -1
View File
@@ -49,7 +49,7 @@
"fzf": "^0.5.2",
"glob": "^12.0.0",
"highlight.js": "^11.11.1",
"ink": "npm:@jrichman/ink@6.5.0",
"ink": "npm:@jrichman/ink@6.6.3",
"ink-gradient": "^3.0.0",
"ink-spinner": "^5.0.0",
"latest-version": "^9.0.0",
+1 -1
View File
@@ -2056,7 +2056,7 @@ const SETTINGS_SCHEMA = {
label: 'JIT Context Loading',
category: 'Experimental',
requiresRestart: true,
default: true,
default: false,
description: 'Enable Just-In-Time (JIT) context loading.',
showInDialog: false,
},
+6 -7
View File
@@ -42,6 +42,7 @@ import {
type OverflowState,
} from '../ui/contexts/OverflowContext.js';
import { makeFakeConfig } from '@google/gemini-cli-core';
import { type Config } from '@google/gemini-cli-core';
import { FakePersistentState } from './persistentStateFake.js';
import { AppContext, type AppState } from '../ui/contexts/AppContext.js';
@@ -51,7 +52,6 @@ import { themeManager, DEFAULT_THEME } from '../ui/themes/theme-manager.js';
import { DefaultLight } from '../ui/themes/builtin/light/default-light.js';
import { pickDefaultThemeName } from '../ui/themes/theme.js';
import { generateSvgForTerminal } from './svg.js';
import { loadCliConfig, type CliArgs } from '../config/config.js';
export const persistentStateMock = new FakePersistentState();
@@ -666,12 +666,11 @@ export const renderWithProviders = async (
const terminalWidth = width ?? baseState.terminalWidth;
if (!config) {
config = await loadCliConfig(
settings.merged,
'random-session-id',
{} as unknown as CliArgs,
{ cwd: '/' },
);
config = makeFakeConfig({
useAlternateBuffer: settings.merged.ui?.useAlternateBuffer,
showMemoryUsage: settings.merged.ui?.showMemoryUsage,
accessibility: settings.merged.ui?.accessibility,
});
}
const mainAreaWidth = providedUiState?.mainAreaWidth ?? terminalWidth;
@@ -441,6 +441,7 @@ describe('<Footer />', () => {
it('renders footer with all optional sections hidden (minimal footer)', async () => {
const { lastFrame, unmount } = await renderWithProviders(<Footer />, {
config: mockConfig,
width: 120,
uiState: { sessionStats: mockSessionStats },
settings: createMockSettings({
@@ -558,6 +559,7 @@ describe('<Footer />', () => {
describe('Footer Token Formatting', () => {
const renderWithTokens = async (tokens: number) => {
const result = await renderWithProviders(<Footer />, {
config: mockConfig,
width: 120,
uiState: {
sessionStats: {
@@ -810,6 +812,7 @@ describe('<Footer />', () => {
it('handles empty items array', async () => {
const { lastFrame, unmount } = await renderWithProviders(<Footer />, {
config: mockConfig,
width: 120,
uiState: { sessionStats: mockSessionStats },
settings: createMockSettings({
@@ -17,6 +17,7 @@ import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
import { CompressionMessage } from './messages/CompressionMessage.js';
import { WarningMessage } from './messages/WarningMessage.js';
import { SubagentHistoryMessage } from './messages/SubagentHistoryMessage.js';
import { Box } from 'ink';
import { AboutBox } from './AboutBox.js';
import { StatsDisplay } from './StatsDisplay.js';
@@ -215,6 +216,12 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
isExpandable={isExpandable}
/>
)}
{itemForDisplay.type === 'subagent' && (
<SubagentHistoryMessage
item={itemForDisplay}
terminalWidth={terminalWidth}
/>
)}
{itemForDisplay.type === 'compression' && (
<CompressionMessage compression={itemForDisplay.compression} />
)}
@@ -2222,85 +2222,67 @@ describe('InputPrompt', () => {
name: 'mid-word',
text: 'hello world',
visualCursor: [0, 3],
expected: `hel${chalk.inverse('l')}o world`,
},
{
name: 'at the beginning of the line',
text: 'hello',
visualCursor: [0, 0],
expected: `${chalk.inverse('h')}ello`,
},
{
name: 'at the end of the line',
text: 'hello',
visualCursor: [0, 5],
expected: `hello${chalk.inverse(' ')}`,
},
{
name: 'on a highlighted token',
text: 'run @path/to/file',
visualCursor: [0, 9],
expected: `@path/${chalk.inverse('t')}o/file`,
},
{
name: 'for multi-byte unicode characters',
text: 'hello 👍 world',
visualCursor: [0, 6],
expected: `hello ${chalk.inverse('👍')} world`,
},
{
name: 'after multi-byte unicode characters',
text: '👍A',
visualCursor: [0, 1],
expected: `👍${chalk.inverse('A')}`,
},
{
name: 'at the end of a line with unicode characters',
text: 'hello 👍',
visualCursor: [0, 8],
expected: `hello 👍`, // skip checking inverse ansi due to ink truncation bug
},
{
name: 'at the end of a short line with unicode characters',
text: '👍',
visualCursor: [0, 1],
expected: `👍${chalk.inverse(' ')}`,
},
{
name: 'on an empty line',
text: '',
visualCursor: [0, 0],
expected: chalk.inverse(' '),
},
{
name: 'on a space between words',
text: 'hello world',
visualCursor: [0, 5],
expected: `hello${chalk.inverse(' ')}world`,
},
])(
'should display cursor correctly $name',
async ({ name, text, visualCursor, expected }) => {
async ({ text, visualCursor }) => {
mockBuffer.text = text;
mockBuffer.lines = [text];
mockBuffer.viewportVisualLines = [text];
mockBuffer.visualCursor = visualCursor as [number, number];
props.config.getUseBackgroundColor = () => false;
const { stdout, unmount } = await renderWithProviders(
const renderResult = await renderWithProviders(
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrameRaw();
expect(stripAnsi(frame)).toContain(stripAnsi(expected));
if (
name !== 'at the end of a line with unicode characters' &&
name !== 'on a highlighted token'
) {
expect(frame).toContain('\u001b[7m');
}
});
unmount();
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
},
);
});
@@ -2316,7 +2298,6 @@ describe('InputPrompt', () => {
[1, 0],
[2, 0],
],
expected: `sec${chalk.inverse('o')}nd line`,
},
{
name: 'at the beginning of a line',
@@ -2326,7 +2307,6 @@ describe('InputPrompt', () => {
[0, 0],
[1, 0],
],
expected: `${chalk.inverse('s')}econd line`,
},
{
name: 'at the end of a line',
@@ -2336,11 +2316,10 @@ describe('InputPrompt', () => {
[0, 0],
[1, 0],
],
expected: `first line${chalk.inverse(' ')}`,
},
])(
'should display cursor correctly $name in a multiline block',
async ({ name, text, visualCursor, expected, visualToLogicalMap }) => {
async ({ text, visualCursor, visualToLogicalMap }) => {
mockBuffer.text = text;
mockBuffer.lines = text.split('\n');
mockBuffer.viewportVisualLines = text.split('\n');
@@ -2350,20 +2329,12 @@ describe('InputPrompt', () => {
>;
props.config.getUseBackgroundColor = () => false;
const { stdout, unmount } = await renderWithProviders(
const renderResult = await renderWithProviders(
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrameRaw();
expect(stripAnsi(frame)).toContain(stripAnsi(expected));
if (
name !== 'at the end of a line with unicode characters' &&
name !== 'on a highlighted token'
) {
expect(frame).toContain('\u001b[7m');
}
});
unmount();
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
},
);
@@ -2380,18 +2351,12 @@ describe('InputPrompt', () => {
];
props.config.getUseBackgroundColor = () => false;
const { stdout, unmount } = await renderWithProviders(
const renderResult = await renderWithProviders(
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrameRaw();
const lines = frame.split('\n');
// The line with the cursor should just be an inverted space inside the box border
expect(
lines.find((l) => l.includes(chalk.inverse(' '))),
).not.toBeUndefined();
});
unmount();
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
});
});
@@ -2412,22 +2377,14 @@ describe('InputPrompt', () => {
];
props.config.getUseBackgroundColor = () => false;
const { stdout, unmount } = await renderWithProviders(
const renderResult = await renderWithProviders(
<InputPrompt {...props} />,
);
await waitFor(() => {
const frame = stdout.lastFrameRaw();
// Check that all lines, including the empty one, are rendered.
// This implicitly tests that the Box wrapper provides height for the empty line.
expect(frame).toContain('hello');
expect(frame).toContain('world');
expect(frame).toContain(chalk.inverse(' '));
const outputLines = frame.trim().split('\n');
// The number of lines should be 2 for the border plus 3 for the content.
expect(outputLines.length).toBe(5);
});
unmount();
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
});
@@ -4088,14 +4045,12 @@ describe('InputPrompt', () => {
it('should not show inverted cursor when shell is focused', async () => {
props.isEmbeddedShellFocused = true;
props.focus = false;
const { stdout, unmount } = await renderWithProviders(
const renderResult = await renderWithProviders(
<InputPrompt {...props} />,
);
await waitFor(() => {
expect(stdout.lastFrame()).not.toContain(`{chalk.inverse(' ')}`);
});
expect(stdout.lastFrame()).toMatchSnapshot();
unmount();
await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
});
@@ -27,6 +27,10 @@ import {
} from '../hooks/useConfirmingTool.js';
// Mock dependencies
vi.mock('ink-spinner', () => ({
default: () => <Text></Text>,
}));
const mockUseSettings = vi.fn().mockReturnValue({
merged: {
ui: {
@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="88" viewBox="0 0 920 88">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="88" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">first line</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="34" width="9" height="17" fill="#ffffff" />
<text x="36" y="36" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">s</text>
<text x="45" y="36" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">econd line</text>
<text x="891" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="88" viewBox="0 0 920 88">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="88" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">first line</text>
<rect x="126" y="17" width="9" height="17" fill="#ffffff" />
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="36" y="36" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">second line</text>
<text x="891" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="105" viewBox="0 0 920 105">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="105" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">first line</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="36" y="36" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">sec</text>
<rect x="63" y="34" width="9" height="17" fill="#ffffff" />
<text x="63" y="36" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">o</text>
<text x="72" y="36" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">nd line</text>
<text x="891" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="36" y="53" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">third line</text>
<text x="891" y="53" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="105" viewBox="0 0 920 105">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="105" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">first line</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<rect x="36" y="34" width="9" height="17" fill="#ffffff" />
<text x="891" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="36" y="53" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">third line</text>
<text x="891" y="53" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="9" lengthAdjust="spacingAndGlyphs">👍</text>
<rect x="45" y="17" width="9" height="17" fill="#ffffff" />
<text x="45" y="19" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">A</text>
<text x="882" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<rect x="36" y="17" width="9" height="17" fill="#ffffff" />
<text x="36" y="19" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">h</text>
<text x="45" y="19" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">ello</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">hello 👍</text>
<text x="882" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="9" lengthAdjust="spacingAndGlyphs">👍</text>
<rect x="45" y="17" width="9" height="17" fill="#ffffff" />
<text x="882" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">hello</text>
<rect x="81" y="17" width="9" height="17" fill="#ffffff" />
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">hello </text>
<rect x="90" y="17" width="9" height="17" fill="#ffffff" />
<text x="90" y="19" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">👍</text>
<text x="99" y="19" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs"> world</text>
<text x="882" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">hel</text>
<rect x="63" y="17" width="9" height="17" fill="#ffffff" />
<text x="63" y="19" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">l</text>
<text x="72" y="19" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">o world</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">run </text>
<text x="72" y="19" fill="#d7afff" textLength="45" lengthAdjust="spacingAndGlyphs">@path</text>
<rect x="117" y="17" width="9" height="17" fill="#d7afff" />
<text x="117" y="19" fill="#000000" textLength="9" lengthAdjust="spacingAndGlyphs">/</text>
<text x="126" y="19" fill="#d7afff" textLength="63" lengthAdjust="spacingAndGlyphs">to/file</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">hello</text>
<rect x="81" y="17" width="9" height="17" fill="#ffffff" />
<text x="90" y="19" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">world</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<rect x="36" y="17" width="9" height="17" fill="#ffffff" />
<text x="45" y="19" fill="#afafaf" textLength="315" lengthAdjust="spacingAndGlyphs"> Type your message or @path/to/file</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="105" viewBox="0 0 920 105">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="105" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
<text x="0" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<text x="36" y="19" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">hello</text>
<text x="891" y="19" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="891" y="36" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="36" y="53" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">world</text>
<rect x="81" y="51" width="9" height="17" fill="#ffffff" />
<text x="891" y="53" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="70" fill="#00cd00" textLength="900" lengthAdjust="spacingAndGlyphs">────────────────────────────────────────────────────────────────────────────────────────────────────</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="71" viewBox="0 0 920 71">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="71" fill="#000000" />
<g transform="translate(10, 10)">
<rect x="0" y="0" width="900" height="17" fill="#1f1f1f" />
<text x="0" y="2" fill="#000000" textLength="900" lengthAdjust="spacingAndGlyphs">▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀</text>
<rect x="0" y="17" width="9" height="17" fill="#1f1f1f" />
<rect x="9" y="17" width="18" height="17" fill="#1f1f1f" />
<text x="9" y="19" fill="#d7afff" textLength="18" lengthAdjust="spacingAndGlyphs">&gt; </text>
<rect x="27" y="17" width="324" height="17" fill="#1f1f1f" />
<text x="27" y="19" fill="#afafaf" textLength="324" lengthAdjust="spacingAndGlyphs"> Type your message or @path/to/file</text>
<rect x="351" y="17" width="549" height="17" fill="#1f1f1f" />
<rect x="0" y="34" width="900" height="17" fill="#1f1f1f" />
<text x="0" y="36" fill="#000000" textLength="900" lengthAdjust="spacingAndGlyphs">▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -1,5 +1,95 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor correctly 'at the beginning of a line' in a multiline block 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > first line │
│ second line │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor correctly 'at the end of a line' in a multiline block 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > first line │
│ second line │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor correctly 'in the middle of a line' in a multiline block 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > first line │
│ second line │
│ third line │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor on a blank line in a multiline block 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > first line │
│ │
│ third line │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'after multi-byte unicode characters' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > 👍A │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the beginning of the line' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the end of a line with unicode cha…' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello 👍 │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the end of a short line with unico…' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > 👍 │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the end of the line' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'for multi-byte unicode characters' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello 👍 world │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'mid-word' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello world │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'on a highlighted token' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > run @path/to/file │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'on a space between words' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello world │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'on an empty line' 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > Type your message or @path/to/file │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > History Navigation and Completion Suppression > should not render suggestions during history navigation 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> second message
@@ -78,11 +168,18 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
"
`;
exports[`InputPrompt > multiline rendering > should correctly render multiline input including blank lines 1`] = `
"────────────────────────────────────────────────────────────────────────────────────────────────────
│ > hello │
│ │
│ world │
────────────────────────────────────────────────────────────────────────────────────────────────────"
`;
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Type your message or @path/to/file
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
`;
exports[`InputPrompt > snapshots > should render correctly in shell mode 1`] = `
@@ -4,7 +4,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'ASB mode - Focuse
"ScrollableList
AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ Line 11 │
│ Line 12 │
@@ -24,7 +24,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'ASB mode - Unfocu
"ScrollableList
AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ Line 11 │
│ Line 12 │
@@ -43,7 +43,7 @@ AppHeader(full)
exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Constrained height' 1`] = `
"AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ ... first 11 lines hidden (Ctrl+O to show) ... │
│ Line 12 │
@@ -62,7 +62,7 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Con
exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unconstrained height' 1`] = `
"AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
Shell Command Running a long command... │
Shell Command Running a long command... │
│ │
│ Line 1 │
│ Line 2 │
@@ -14,9 +14,9 @@ exports[`Initial Theme Selection > should default to a dark theme when terminal
│ 7. Holiday Dark │ 6 return a │ │
│ 8. Shades Of Purple Dark │ │ │
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
│ 11. Ayu Light │ │ │
│ 12. Default Light └─────────────────────────────────────────────────┘ │
│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │
│ 11. ANSI Light │ │ │
│ 12. Ayu Light └─────────────────────────────────────────────────┘ │
│ ▼ │
│ │
│ (Use Enter to select, Tab to configure scope, Esc to close) │
@@ -64,9 +64,9 @@ exports[`Initial Theme Selection > should use the theme from settings even if te
│ 7. Holiday Dark │ 6 return a │ │
│ 8. Shades Of Purple Dark │ │ │
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
│ 11. Ayu Light │ │ │
│ 12. Default Light └─────────────────────────────────────────────────┘ │
│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │
│ 11. ANSI Light │ │ │
│ 12. Ayu Light └─────────────────────────────────────────────────┘ │
│ ▼ │
│ │
│ (Use Enter to select, Tab to configure scope, Esc to close) │
@@ -103,9 +103,9 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode
│ 7. Holiday Dark │ 6 return a │ │
│ 8. Shades Of Purple Dark │ │ │
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
│ 11. Ayu Light │ │ │
│ 12. Default Light └─────────────────────────────────────────────────┘ │
│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │
│ 11. ANSI Light │ │ │
│ 12. Ayu Light └─────────────────────────────────────────────────┘ │
│ ▼ │
│ │
│ (Use Enter to select, Tab to configure scope, Esc to close) │
@@ -128,9 +128,9 @@ exports[`ThemeDialog Snapshots > should render correctly in theme selection mode
│ 7. Holiday Dark │ 6 return a │ │
│ 8. Shades Of Purple Dark │ │ │
│ 9. Solarized Dark │ 1 - print("Hello, " + name) │ │
│ 10. ANSI Light │ 1 + print(f"Hello, {name}!") │ │
│ 11. Ayu Light │ │ │
│ 12. Default Light └─────────────────────────────────────────────────┘ │
│ 10. Tokyo Night Dark │ 1 + print(f"Hello, {name}!") │ │
│ 11. ANSI Light │ │ │
│ 12. Ayu Light └─────────────────────────────────────────────────┘ │
│ ▼ │
│ ╭─────────────────────────────────────────────────╮ │
│ │ DEVELOPER TOOLS (Not visible to users) │ │
@@ -8,7 +8,7 @@ import { renderWithProviders } from '../../../test-utils/render.js';
import { SubagentGroupDisplay } from './SubagentGroupDisplay.js';
import { Kind, CoreToolCallStatus } from '@google/gemini-cli-core';
import type { IndividualToolCallDisplay } from '../../types.js';
import { vi } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import { Text } from 'ink';
vi.mock('../../utils/MarkdownDisplay.js', () => ({
@@ -191,8 +191,9 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
}
}
const history = toolCall.subagentHistory ?? progress.recentActivity;
const lastActivity: SubagentActivityItem | undefined =
progress.recentActivity[progress.recentActivity.length - 1];
history[history.length - 1];
// Collapsed View: Show single compact line per agent
if (!isExpanded) {
@@ -260,6 +261,7 @@ export const SubagentGroupDisplay: React.FC<SubagentGroupDisplayProps> = ({
<SubagentProgressDisplay
progress={progress}
terminalWidth={terminalWidth}
historyOverrides={toolCall.subagentHistory}
/>
</Box>
);
@@ -0,0 +1,85 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { renderWithProviders } from '../../../test-utils/render.js';
import { SubagentHistoryMessage } from './SubagentHistoryMessage.js';
import type { HistoryItemSubagent } from '../../types.js';
describe('SubagentHistoryMessage', () => {
const mockItem: HistoryItemSubagent = {
type: 'subagent',
agentName: 'research',
history: [
{
id: '1',
type: 'thought',
content: 'Thinking about the problem',
status: 'completed',
},
{
id: '2',
type: 'tool_call',
content: 'Calling search_web',
status: 'running',
},
{
id: '3',
type: 'tool_call',
content: 'Calling read_file fail',
status: 'error',
},
],
};
it('renders header with agent name and item count', async () => {
const renderResult = await renderWithProviders(
<SubagentHistoryMessage item={mockItem} terminalWidth={80} />,
);
await renderResult.waitUntilReady();
const output = renderResult.lastFrame();
expect(output).toContain('research Trace (3 items)');
expect(output).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
});
it('renders thought activities with brain icon', async () => {
const renderResult = await renderWithProviders(
<SubagentHistoryMessage item={mockItem} terminalWidth={80} />,
);
await renderResult.waitUntilReady();
const output = renderResult.lastFrame();
expect(output).toContain('🧠 Thinking about the problem');
renderResult.unmount();
});
it('renders tool call activities with tool icon', async () => {
const renderResult = await renderWithProviders(
<SubagentHistoryMessage item={mockItem} terminalWidth={80} />,
);
await renderResult.waitUntilReady();
const output = renderResult.lastFrame();
expect(output).toContain('🛠️ Calling search_web');
renderResult.unmount();
});
it('renders status indicators correctly', async () => {
const renderResult = await renderWithProviders(
<SubagentHistoryMessage item={mockItem} terminalWidth={80} />,
);
await renderResult.waitUntilReady();
const output = renderResult.lastFrame();
expect(output).toContain('Calling search_web (Running...)');
expect(output).toContain('Thinking about the problem ✅');
expect(output).toContain('Calling read_file fail ❌');
renderResult.unmount();
});
});
@@ -0,0 +1,38 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { Box, Text } from 'ink';
import type { HistoryItemSubagent } from '../../types.js';
interface SubagentHistoryMessageProps {
item: HistoryItemSubagent;
terminalWidth: number;
}
export const SubagentHistoryMessage: React.FC<SubagentHistoryMessageProps> = ({
item,
terminalWidth,
}) => (
<Box flexDirection="column" width={terminalWidth} marginBottom={1}>
<Box marginBottom={1}>
<Text bold color="cyan">
🤖 {item.agentName} Trace ({item.history.length} items)
</Text>
</Box>
{item.history.map((activity) => (
<Box key={activity.id} marginLeft={2} marginBottom={0}>
<Text color={activity.type === 'thought' ? 'gray' : 'white'}>
{activity.type === 'thought' ? '🧠' : '🛠️'} {activity.content}
{activity.status === 'running' && ' (Running...)'}
{activity.status === 'completed' && ' ✅'}
{activity.status === 'error' && ' ❌'}
</Text>
</Box>
))}
</Box>
);
@@ -20,6 +20,7 @@ import { safeJsonToMarkdown } from '@google/gemini-cli-core';
export interface SubagentProgressDisplayProps {
progress: SubagentProgress;
terminalWidth: number;
historyOverrides?: SubagentActivityItem[];
}
export const formatToolArgs = (args?: string): string => {
@@ -57,7 +58,7 @@ export const formatToolArgs = (args?: string): string => {
export const SubagentProgressDisplay: React.FC<
SubagentProgressDisplayProps
> = ({ progress, terminalWidth }) => {
> = ({ progress, terminalWidth, historyOverrides }) => {
let headerText: string | undefined;
let headerColor = theme.text.secondary;
@@ -85,72 +86,77 @@ export const SubagentProgressDisplay: React.FC<
</Box>
)}
<Box flexDirection="column" marginLeft={0} gap={0}>
{progress.recentActivity.map((item: SubagentActivityItem) => {
if (item.type === 'thought') {
const isCancellation = item.content === 'Request cancelled.';
const icon = isCancellation ? ' ' : '💭';
const color = isCancellation
? theme.status.warning
: theme.text.secondary;
{(historyOverrides ?? progress.recentActivity).map(
(item: SubagentActivityItem) => {
if (item.type === 'thought') {
const isCancellation = item.content === 'Request cancelled.';
const icon = isCancellation ? ' ' : '💭';
const color = isCancellation
? theme.status.warning
: theme.text.secondary;
return (
<Box key={item.id} flexDirection="row">
<Box minWidth={STATUS_INDICATOR_WIDTH}>
<Text color={color}>{icon}</Text>
return (
<Box key={item.id} flexDirection="row">
<Box minWidth={STATUS_INDICATOR_WIDTH}>
<Text color={color}>{icon}</Text>
</Box>
<Box flexGrow={1}>
<Text color={color}>{item.content}</Text>
</Box>
</Box>
<Box flexGrow={1}>
<Text color={color}>{item.content}</Text>
</Box>
</Box>
);
} else if (item.type === 'tool_call') {
const statusSymbol =
item.status === 'running' ? (
<Spinner type="dots" />
) : item.status === 'completed' ? (
<Text color={theme.status.success}>{TOOL_STATUS.SUCCESS}</Text>
) : item.status === 'cancelled' ? (
<Text color={theme.status.warning} bold>
{TOOL_STATUS.CANCELED}
</Text>
) : (
<Text color={theme.status.error}>{TOOL_STATUS.ERROR}</Text>
);
const formattedArgs = item.description || formatToolArgs(item.args);
const displayArgs =
formattedArgs.length > 60
? formattedArgs.slice(0, 60) + '...'
: formattedArgs;
return (
<Box key={item.id} flexDirection="row">
<Box minWidth={STATUS_INDICATOR_WIDTH}>{statusSymbol}</Box>
<Box flexDirection="row" flexGrow={1} flexWrap="wrap">
<Text
bold
color={theme.text.primary}
strikethrough={item.status === 'cancelled'}
>
{item.displayName || item.content}
} else if (item.type === 'tool_call') {
const statusSymbol =
item.status === 'running' ? (
<Spinner type="dots" />
) : item.status === 'completed' ? (
<Text color={theme.status.success}>
{TOOL_STATUS.SUCCESS}
</Text>
{displayArgs && (
<Box marginLeft={1}>
<Text
color={theme.text.secondary}
wrap="truncate"
strikethrough={item.status === 'cancelled'}
>
{displayArgs}
</Text>
</Box>
)}
) : item.status === 'cancelled' ? (
<Text color={theme.status.warning} bold>
{TOOL_STATUS.CANCELED}
</Text>
) : (
<Text color={theme.status.error}>{TOOL_STATUS.ERROR}</Text>
);
const formattedArgs =
item.description || formatToolArgs(item.args);
const displayArgs =
formattedArgs.length > 60
? formattedArgs.slice(0, 60) + '...'
: formattedArgs;
return (
<Box key={item.id} flexDirection="row">
<Box minWidth={STATUS_INDICATOR_WIDTH}>{statusSymbol}</Box>
<Box flexDirection="row" flexGrow={1} flexWrap="wrap">
<Text
bold
color={theme.text.primary}
strikethrough={item.status === 'cancelled'}
>
{item.displayName || item.content}
</Text>
{displayArgs && (
<Box marginLeft={1}>
<Text
color={theme.text.secondary}
wrap="truncate"
strikethrough={item.status === 'cancelled'}
>
{displayArgs}
</Text>
</Box>
)}
</Box>
</Box>
</Box>
);
}
return null;
})}
);
}
return null;
},
)}
</Box>
{progress.result && (
@@ -279,8 +279,8 @@ describe('<ToolGroupMessage />', () => {
);
const output = lastFrame();
expect(output).toContain('Testing Topic');
expect(output).toContain('This is the description');
expect(output).toContain('Testing Topic: ');
expect(output).toContain('This is the description');
expect(output).toMatchSnapshot('update_topic_tool');
unmount();
});
@@ -307,8 +307,8 @@ describe('<ToolGroupMessage />', () => {
);
const output = lastFrame();
expect(output).toContain('Testing Topic');
expect(output).toContain('This is the summary');
expect(output).toContain('Testing Topic: ');
expect(output).toContain('This is the summary');
unmount();
});
@@ -31,11 +31,16 @@ export const TopicMessage: React.FC<TopicMessageProps> = ({ args }) => {
const intent = typeof rawIntent === 'string' ? rawIntent : undefined;
return (
<Box flexDirection="row" marginLeft={2}>
<Text color={theme.text.primary} bold>
<Box flexDirection="row" marginLeft={2} flexWrap="wrap">
<Text color={theme.text.primary} bold wrap="truncate-end">
{title || 'Topic'}
{intent && <Text>: </Text>}
</Text>
{intent && <Text color={theme.text.secondary}> {intent}</Text>}
{intent && (
<Text color={theme.text.secondary} wrap="wrap">
{intent}
</Text>
)}
</Box>
);
};
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="920" height="105" viewBox="0 0 920 105">
<style>
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
</style>
<rect width="920" height="105" fill="#000000" />
<g transform="translate(10, 10)">
<text x="0" y="2" fill="#00cdcd" textLength="234" lengthAdjust="spacingAndGlyphs" font-weight="bold">🤖 research Trace (3 items)</text>
<text x="18" y="36" fill="#7f7f7f" textLength="270" lengthAdjust="spacingAndGlyphs">🧠 Thinking about the problem ✅</text>
<text x="18" y="53" fill="#e5e5e5" textLength="297" lengthAdjust="spacingAndGlyphs">🛠️ Calling search_web (Running...)</text>
<text x="18" y="70" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs">🛠️ Calling read_file fail ❌</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 881 B

@@ -0,0 +1,19 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`SubagentHistoryMessage > renders header with agent name and item count 1`] = `
"🤖 research Trace (3 items)
🧠 Thinking about the problem ✅
🛠️ Calling search_web (Running...)
🛠️ Calling read_file fail ❌
"
`;
exports[`SubagentHistoryMessage > renders header with agent name and item count 2`] = `
"🤖 research Trace (3 items)
🧠 Thinking about the problem ✅
🛠️ Calling search_web (Running...)
🛠️ Calling read_file fail ❌
"
`;
@@ -77,7 +77,7 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders header when scrolled
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders mixed tool calls including update_topic 1`] = `
" Testing Topic This is the description
" Testing Topic: This is the description
╭──────────────────────────────────────────────────────────────────────────╮
│ ✓ read_file Read a file │
│ │
@@ -141,7 +141,7 @@ exports[`<ToolGroupMessage /> > Golden Snapshots > renders two tool groups where
`;
exports[`<ToolGroupMessage /> > Golden Snapshots > renders update_topic tool call using TopicMessage > update_topic_tool 1`] = `
" Testing Topic This is the description
" Testing Topic: This is the description
"
`;
+10
View File
@@ -10,12 +10,19 @@ import {
type ToolResultDisplay,
debugLogger,
CoreToolCallStatus,
type SubagentActivityItem,
} from '@google/gemini-cli-core';
import {
type HistoryItemToolGroup,
type IndividualToolCallDisplay,
} from '../types.js';
function hasSubagentHistory(
call: ToolCall,
): call is ToolCall & { subagentHistory: SubagentActivityItem[] } {
return 'subagentHistory' in call && call.subagentHistory !== undefined;
}
/**
* Transforms `ToolCall` objects into `HistoryItemToolGroup` objects for UI
* display. This is a pure projection layer and does not track interaction
@@ -115,6 +122,9 @@ export function mapToDisplay(
progressTotal,
approvalMode: call.approvalMode,
originalRequestName: call.request.originalRequestName,
subagentHistory: hasSubagentHistory(call)
? call.subagentHistory
: undefined,
};
});
@@ -163,7 +163,6 @@ describe('useToolScheduler', () => {
},
};
// 1. Initial success
act(() => {
void mockMessageBus.publish({
type: MessageBusType.TOOL_CALLS_UPDATE,
@@ -172,7 +171,6 @@ describe('useToolScheduler', () => {
} as ToolCallsUpdateMessage);
});
// 2. Mark as submitted
act(() => {
const [, , markAsSubmitted] = result.current;
markAsSubmitted(['call-1']);
@@ -180,7 +178,7 @@ describe('useToolScheduler', () => {
expect(result.current[0][0].responseSubmittedToGemini).toBe(true);
// 3. Receive another update (should preserve the true flag)
// Verify flag is preserved across updates
act(() => {
void mockMessageBus.publish({
type: MessageBusType.TOOL_CALLS_UPDATE,
@@ -348,7 +346,6 @@ describe('useToolScheduler', () => {
confirmationDetails: { type: 'info', title: 'Confirm', prompt: 'Yes?' },
};
// 1. Populate state with multiple schedulers
act(() => {
void mockMessageBus.publish({
type: MessageBusType.TOOL_CALLS_UPDATE,
@@ -372,7 +369,6 @@ describe('useToolScheduler', () => {
toolCalls.find((t) => t.request.callId === 'call-sub'),
).toBeDefined();
// 2. Call setToolCallsForDisplay (e.g., simulate a manual update or clear)
act(() => {
const [, , , setToolCalls] = result.current;
setToolCalls((prev) =>
@@ -380,7 +376,6 @@ describe('useToolScheduler', () => {
);
});
// 3. Verify that tools are still present and maintain their scheduler IDs
const [toolCalls2] = result.current;
expect(toolCalls2).toHaveLength(2);
expect(toolCalls2.every((t) => t.responseSubmittedToGemini)).toBe(true);
@@ -482,7 +477,6 @@ describe('useToolScheduler', () => {
confirmationDetails: { type: 'info', title: 'Confirm', prompt: 'Yes?' },
} as WaitingToolCall;
// 1. Initial approval request
act(() => {
void mockMessageBus.publish({
type: MessageBusType.TOOL_CALLS_UPDATE,
@@ -493,7 +487,6 @@ describe('useToolScheduler', () => {
expect(result.current[0]).toHaveLength(1);
// 2. Approved and executing
const approvedCall = {
...subagentCall,
status: CoreToolCallStatus.Executing as const,
@@ -510,7 +503,7 @@ describe('useToolScheduler', () => {
expect(result.current[0]).toHaveLength(1);
expect(result.current[0][0].status).toBe(CoreToolCallStatus.Executing);
// 3. New turn with a background tool (should NOT be shown)
// Background tool should not be shown
const backgroundTool = {
status: CoreToolCallStatus.Executing as const,
request: {
@@ -595,4 +588,144 @@ describe('useToolScheduler', () => {
vi.useRealTimers();
});
it('accumulates SUBAGENT_ACTIVITY events and attaches them to toolCalls', async () => {
const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
() => undefined,
),
);
const mockToolCall = {
status: CoreToolCallStatus.Executing as const,
request: {
callId: 'call-1',
name: 'research',
args: {},
isClientInitiated: false,
prompt_id: 'p1',
},
tool: createMockTool({ name: 'research' }),
invocation: createMockInvocation(),
} as ExecutingToolCall;
act(() => {
void mockMessageBus.publish({
type: MessageBusType.TOOL_CALLS_UPDATE,
toolCalls: [mockToolCall],
schedulerId: ROOT_SCHEDULER_ID,
} as ToolCallsUpdateMessage);
});
expect(result.current[0]).toHaveLength(1);
expect(result.current[0][0].subagentHistory).toBeUndefined();
act(() => {
void mockMessageBus.publish({
type: MessageBusType.SUBAGENT_ACTIVITY,
subagentName: 'research',
activity: {
id: '1',
type: 'thought',
content: 'Thinking...',
status: 'running',
},
});
});
expect(result.current[0][0].subagentHistory).toHaveLength(1);
expect(result.current[0][0].subagentHistory![0].content).toBe(
'Thinking...',
);
act(() => {
void mockMessageBus.publish({
type: MessageBusType.SUBAGENT_ACTIVITY,
subagentName: 'research',
activity: {
id: '2',
type: 'tool_call',
content: 'Calling tool',
status: 'completed',
},
});
});
expect(result.current[0][0].subagentHistory).toHaveLength(2);
expect(result.current[0][0].subagentHistory![1].content).toBe(
'Calling tool',
);
});
it('replaces SUBAGENT_ACTIVITY events by ID instead of appending', async () => {
const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
() => undefined,
),
);
const mockToolCall = {
status: CoreToolCallStatus.Executing as const,
request: {
callId: 'call-1',
name: 'research',
args: {},
isClientInitiated: false,
prompt_id: 'p1',
},
tool: createMockTool({ name: 'research' }),
invocation: createMockInvocation(),
};
act(() => {
void mockMessageBus.publish({
type: MessageBusType.TOOL_CALLS_UPDATE,
toolCalls: [mockToolCall],
schedulerId: ROOT_SCHEDULER_ID,
});
});
act(() => {
void mockMessageBus.publish({
type: MessageBusType.SUBAGENT_ACTIVITY,
subagentName: 'research',
activity: {
id: '1',
type: 'thought',
content: 'Thinking...',
status: 'running',
},
});
});
expect(result.current[0][0].subagentHistory).toHaveLength(1);
expect(result.current[0][0].subagentHistory![0].content).toBe(
'Thinking...',
);
// Publish same ID with updated content
act(() => {
void mockMessageBus.publish({
type: MessageBusType.SUBAGENT_ACTIVITY,
subagentName: 'research',
activity: {
id: '1',
type: 'thought',
content: 'Thinking... Done!',
status: 'completed',
},
});
});
// Should still be length 1, and content should be updated
expect(result.current[0][0].subagentHistory).toHaveLength(1);
expect(result.current[0][0].subagentHistory![0].content).toBe(
'Thinking... Done!',
);
expect(result.current[0][0].subagentHistory![0].status).toBe('completed');
});
});
+65 -5
View File
@@ -15,6 +15,8 @@ import {
type EditorType,
type ToolCallsUpdateMessage,
CoreToolCallStatus,
type SubagentActivityItem,
type SubagentActivityMessage,
} from '@google/gemini-cli-core';
import { useCallback, useState, useMemo, useEffect, useRef } from 'react';
@@ -33,6 +35,7 @@ export type CancelAllFn = (signal: AbortSignal) => void;
*/
export type TrackedToolCall = ToolCall & {
responseSubmittedToGemini?: boolean;
subagentHistory?: SubagentActivityItem[];
};
// Narrowed types for specific statuses (used by useGeminiStream)
@@ -81,6 +84,9 @@ export function useToolScheduler(
Record<string, TrackedToolCall[]>
>({});
const [lastToolOutputTime, setLastToolOutputTime] = useState<number>(0);
const [subagentHistoryMap, setSubagentHistoryMap] = useState<
Record<string, SubagentActivityItem[]>
>({});
const messageBus = useMemo(() => config.getMessageBus(), [config]);
@@ -173,10 +179,37 @@ export function useToolScheduler(
};
}, [messageBus, internalAdaptToolCalls]);
useEffect(() => {
const handler = (event: SubagentActivityMessage) => {
setSubagentHistoryMap((prev) => {
const history = prev[event.subagentName] ?? [];
const index = history.findIndex(
(item) => item.id === event.activity.id,
);
const nextHistory = [...history];
if (index >= 0) {
nextHistory[index] = event.activity;
} else {
nextHistory.push(event.activity);
}
return {
...prev,
[event.subagentName]: nextHistory,
};
});
};
messageBus.subscribe(MessageBusType.SUBAGENT_ACTIVITY, handler);
return () => {
messageBus.unsubscribe(MessageBusType.SUBAGENT_ACTIVITY, handler);
};
}, [messageBus]);
const schedule: ScheduleFn = useCallback(
async (request, signal) => {
// Clear state for new run
setToolCallsMap({});
setSubagentHistoryMap({});
// 1. Await Core Scheduler directly
const results = await scheduler.schedule(request, signal);
@@ -216,10 +249,38 @@ export function useToolScheduler(
);
// Flatten the map for the UI components that expect a single list of tools.
const toolCalls = useMemo(
() => Object.values(toolCallsMap).flat(),
[toolCallsMap],
);
const toolCalls = useMemo(() => {
const flattened = Object.values(toolCallsMap).flat();
return flattened.map((tc) => {
let subagentName = tc.request.name;
if (tc.request.name === 'invoke_subagent') {
const argsObj = tc.request.args;
let parsedArgs: unknown = argsObj;
if (typeof argsObj === 'string') {
try {
parsedArgs = JSON.parse(argsObj);
} catch {
parsedArgs = null;
}
}
if (typeof parsedArgs === 'object' && parsedArgs !== null) {
for (const [key, value] of Object.entries(parsedArgs)) {
if (key === 'subagent_name' && typeof value === 'string') {
subagentName = value;
break;
}
}
}
}
return {
...tc,
subagentHistory: subagentHistoryMap[subagentName] ?? tc.subagentHistory,
};
});
}, [toolCallsMap, subagentHistoryMap]);
// Provide a setter that maintains compatibility with legacy [].
const setToolCallsForDisplay = useCallback(
@@ -272,7 +333,6 @@ function adaptToolCalls(
return coreCalls.map((coreCall): TrackedToolCall => {
const prev = prevMap.get(coreCall.request.callId);
const responseSubmittedToGemini = prev?.responseSubmittedToGemini ?? false;
let status = coreCall.status;
// If a tool call has completed but scheduled a tail call, it is in a transitional
// state. Force the UI to render it as "executing".
@@ -0,0 +1,155 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { type ColorsTheme, Theme } from '../../theme.js';
import { interpolateColor } from '../../color-utils.js';
const palette = {
bg: '#1a1b26',
bg_dark: '#16161e',
bg_dark1: '#0C0E14',
bg_highlight: '#292e42',
blue: '#7aa2f7',
blue0: '#3d59a1',
blue1: '#2ac3de',
blue2: '#0db9d7',
blue5: '#89ddff',
blue6: '#b4f9f8',
blue7: '#394b70',
comment: '#565f89',
cyan: '#7dcfff',
fg: '#c0caf5',
fg_dark: '#a9b1d6',
fg_gutter: '#3b4261',
green: '#9ece6a',
green1: '#73daca',
green2: '#41a6b5',
magenta: '#bb9af7',
magenta2: '#ff007c',
orange: '#ff9e64',
purple: '#9d7cd8',
red: '#f7768e',
red1: '#db4b4b',
teal: '#1abc9c',
yellow: '#e0af68',
diff: {
add: '#243e4a',
change: '#1f2231',
delete: '#4a272f',
},
};
const tokyoNightColors: ColorsTheme = {
type: 'dark',
Background: palette.bg,
Foreground: palette.fg,
LightBlue: palette.purple,
AccentBlue: palette.magenta,
AccentPurple: palette.blue,
AccentCyan: palette.cyan,
AccentGreen: palette.teal,
AccentYellow: palette.yellow,
AccentRed: palette.red1,
DiffAdded: palette.diff.add,
DiffRemoved: palette.diff.delete,
Comment: palette.comment,
Gray: palette.fg_dark,
DarkGray: palette.fg_gutter,
FocusColor: palette.blue,
GradientColors: [palette.blue, palette.magenta, palette.cyan],
};
export const TokyoNight: Theme = new Theme(
'Tokyo Night',
'dark',
{
hljs: {
display: 'block',
overflowX: 'auto',
padding: '0.5em',
background: palette.bg,
color: palette.fg,
},
'hljs-addition': { background: palette.diff.add },
'hljs-attr': { color: palette.green1 },
'hljs-attribute': { color: palette.green1 },
'hljs-brace': { color: palette.fg_dark },
'hljs-built_in': { color: palette.blue1 },
'hljs-builtin-symbol': { color: palette.blue1 },
'hljs-bullet': {
color: palette.orange,
fontWeight: 'bold',
},
'hljs-char': { color: palette.green },
'hljs-char-escape': { color: palette.magenta },
'hljs-character': { color: palette.green },
'hljs-class': { color: palette.blue1 },
'hljs-class-title': { color: palette.blue1 },
'hljs-code': { color: palette.green },
'hljs-comment': {
color: palette.comment,
fontStyle: 'italic',
},
'hljs-computation-expression': { color: palette.cyan },
'hljs-deletion': { background: palette.diff.delete },
'hljs-doctag': { color: palette.yellow },
'hljs-emphasis': { fontStyle: 'italic' },
'hljs-function': { color: palette.blue },
'hljs-function-dispatch': { color: palette.blue },
'hljs-keyword': {
color: palette.magenta,
fontStyle: 'italic',
},
'hljs-label': { color: palette.blue },
'hljs-link': { color: palette.teal },
'hljs-literal': { color: palette.orange },
'hljs-message-name': { color: palette.blue },
'hljs-meta': { color: palette.cyan },
'hljs-meta-prompt': { color: palette.fg_dark },
'hljs-name': { color: palette.magenta },
'hljs-named-character': { color: palette.blue1 },
'hljs-number': { color: palette.orange },
'hljs-operator': { color: palette.blue5 },
'hljs-params': { color: palette.yellow },
'hljs-property': { color: palette.green1 },
'hljs-punctuation': { color: palette.fg_dark },
'hljs-quote': {
color: palette.comment,
fontStyle: 'italic',
},
'hljs-regex': { color: palette.blue6 },
'hljs-regexp': { color: palette.blue6 },
'hljs-rest_arg': {
color: interpolateColor(palette.yellow, palette.fg, 0.8),
},
'hljs-section': {
color: palette.blue,
fontWeight: 'bold',
},
'hljs-selector-attr': { color: palette.cyan },
'hljs-selector-class': { color: palette.green1 },
'hljs-selector-id': { color: palette.green1 },
'hljs-selector-pseudo': { color: palette.cyan },
'hljs-selector-tag': { color: palette.magenta },
'hljs-string': { color: palette.green },
'hljs-strong': { fontWeight: 'bold' },
'hljs-subst': { color: palette.blue5 },
'hljs-symbol': { color: palette.magenta },
'hljs-tag': { color: palette.blue1 },
'hljs-template-tag': { color: palette.blue5 },
'hljs-template-variable': { color: palette.fg },
'hljs-title': { color: palette.blue },
'hljs-title-class': { color: palette.blue1 },
'hljs-title-class-inherited': { color: palette.blue1 },
'hljs-title-function': { color: palette.blue },
'hljs-title-function-invoke': { color: palette.blue },
'hljs-type': { color: palette.blue1 },
'hljs-variable': { color: palette.fg },
'hljs-variable-constant': { color: palette.orange },
'hljs-variable-language': { color: palette.red },
},
tokyoNightColors,
);
@@ -18,6 +18,7 @@ import { ShadesOfPurple } from './builtin/dark/shades-of-purple-dark.js';
import { SolarizedDark } from './builtin/dark/solarized-dark.js';
import { SolarizedLight } from './builtin/light/solarized-light.js';
import { XCode } from './builtin/light/xcode-light.js';
import { TokyoNight } from './builtin/dark/tokyonight-dark.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import type { Theme, ThemeType, ColorsTheme, CustomTheme } from './theme.js';
@@ -84,6 +85,7 @@ class ThemeManager {
SolarizedDark,
SolarizedLight,
XCode,
TokyoNight,
ANSI,
ANSILight,
];
+10 -1
View File
@@ -19,6 +19,7 @@ import {
type AnsiOutput,
CoreToolCallStatus,
checkExhaustive,
type SubagentActivityItem,
} from '@google/gemini-cli-core';
import type { PartListUnion } from '@google/genai';
import { type ReactNode } from 'react';
@@ -135,6 +136,7 @@ export interface IndividualToolCallDisplay {
originalRequestName?: string;
progress?: number;
progressTotal?: number;
subagentHistory?: SubagentActivityItem[];
}
export interface CompressionProps {
@@ -290,6 +292,12 @@ export type HistoryItemChatList = HistoryItemBase & {
chats: ChatDetail[];
};
export type HistoryItemSubagent = HistoryItemBase & {
type: 'subagent';
agentName: string;
history: SubagentActivityItem[];
};
export interface ToolDefinition {
name: string;
displayName: string;
@@ -395,7 +403,8 @@ export type HistoryItemWithoutId =
| HistoryItemMcpStatus
| HistoryItemChatList
| HistoryItemThinking
| HistoryItemHint;
| HistoryItemHint
| HistoryItemSubagent;
export type HistoryItem = HistoryItemWithoutId & { id: number };
+20 -20
View File
@@ -5,16 +5,16 @@
*/
import React, { useMemo } from 'react';
import { styledCharsToString } from '@alcalzone/ansi-tokenize';
import {
Text,
Box,
type StyledChar,
StyledLine,
toStyledCharacters,
styledCharsWidth,
wordBreakStyledChars,
wrapStyledChars,
widestLineFromStyledChars,
styledCharsWidth,
styledCharsToString,
} from 'ink';
import { theme } from '../semantic-colors.js';
import { parseMarkdownToANSI } from './markdownParsingUtils.js';
@@ -31,22 +31,22 @@ const COLUMN_PADDING = 2;
const TABLE_MARGIN = 2;
/**
* Parses markdown to StyledChar array by first converting to ANSI.
* Parses markdown to StyledLine by first converting to ANSI.
* This ensures character counts are accurate (markdown markers are removed
* and styles are applied to the character's internal style object).
*/
const parseMarkdownToStyledChars = (
const parseMarkdownToStyledLine = (
text: string,
defaultColor?: string,
): StyledChar[] => {
): StyledLine => {
const ansi = parseMarkdownToANSI(text, defaultColor);
return toStyledCharacters(ansi);
};
const calculateWidths = (styledChars: StyledChar[]) => {
const contentWidth = styledCharsWidth(styledChars);
const calculateWidths = (styledLine: StyledLine) => {
const contentWidth = styledCharsWidth(styledLine);
const words: StyledChar[][] = wordBreakStyledChars(styledChars);
const words: StyledLine[] = wordBreakStyledChars(styledLine);
const maxWordWidth = widestLineFromStyledChars(words);
return { contentWidth, maxWordWidth };
@@ -67,10 +67,10 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
rows,
terminalWidth,
}) => {
const styledHeaders = useMemo(
const styledHeaders = useMemo<StyledLine[]>(
() =>
headers.map((header) =>
parseMarkdownToStyledChars(
parseMarkdownToStyledLine(
stripUnsafeCharacters(header),
theme.text.link,
),
@@ -78,11 +78,11 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
[headers],
);
const styledRows = useMemo(
const styledRows = useMemo<StyledLine[][]>(
() =>
rows.map((row) =>
row.map((cell) =>
parseMarkdownToStyledChars(
parseMarkdownToStyledLine(
stripUnsafeCharacters(cell),
theme.text.primary,
),
@@ -100,14 +100,14 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
// --- Define Constraints per Column ---
const constraints = Array.from({ length: numColumns }).map(
(_, colIndex) => {
const headerStyledChars = styledHeaders[colIndex] || [];
const headerStyledLine = styledHeaders[colIndex] || StyledLine.empty(0);
let { contentWidth: maxContentWidth, maxWordWidth } =
calculateWidths(headerStyledChars);
calculateWidths(headerStyledLine);
styledRows.forEach((row) => {
const cellStyledChars = row[colIndex] || [];
const cellStyledLine = row[colIndex] || StyledLine.empty(0);
const { contentWidth: cellWidth, maxWordWidth: cellWordWidth } =
calculateWidths(cellStyledChars);
calculateWidths(cellStyledLine);
maxContentWidth = Math.max(maxContentWidth, cellWidth);
maxWordWidth = Math.max(maxWordWidth, cellWordWidth);
@@ -176,16 +176,16 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
// --- Pre-wrap and Optimize Widths ---
const actualColumnWidths = new Array(numColumns).fill(0);
const wrapAndProcessRow = (row: StyledChar[][]) => {
const wrapAndProcessRow = (row: StyledLine[]) => {
const rowResult: ProcessedLine[][] = [];
// Ensure we iterate up to numColumns, filling with empty cells if needed
for (let colIndex = 0; colIndex < numColumns; colIndex++) {
const cellStyledChars = row[colIndex] || [];
const cellStyledLine = row[colIndex] || StyledLine.empty(0);
const allocatedWidth = finalContentWidths[colIndex];
const contentWidth = Math.max(1, allocatedWidth);
const wrappedStyledLines = wrapStyledChars(
cellStyledChars,
cellStyledLine,
contentWidth,
);
@@ -14,25 +14,25 @@
<text x="243" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="252" lengthAdjust="spacingAndGlyphs">├────────┼────────┼────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">123456</text>
<text x="9" y="70" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> 123456 </text>
<text x="81" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="99" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Normal</text>
<text x="90" y="70" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Normal </text>
<text x="162" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="180" y="70" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="171" y="70" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="243" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="9" y="87" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="81" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="99" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">123456</text>
<text x="90" y="87" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> 123456 </text>
<text x="162" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="180" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Normal</text>
<text x="171" y="87" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Normal </text>
<text x="243" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Normal</text>
<text x="9" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Normal </text>
<text x="81" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="99" y="104" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="90" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="162" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="180" y="104" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">123456</text>
<text x="171" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> 123456 </text>
<text x="243" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="252" lengthAdjust="spacingAndGlyphs">└────────┴────────┴────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -14,31 +14,25 @@
<text x="918" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="927" lengthAdjust="spacingAndGlyphs">├───────────────────────────────────┼───────────────────────────────┼─────────────────────────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">Visit Google (</text>
<text x="144" y="70" fill="#87afff" textLength="162" lengthAdjust="spacingAndGlyphs">https://google.com</text>
<text x="306" y="70" fill="#ffffff" textLength="9" lengthAdjust="spacingAndGlyphs">)</text>
<text x="9" y="70" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs"> Visit Google (https://google.com) </text>
<text x="324" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="70" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">Plain Text</text>
<text x="333" y="70" fill="#ffffff" textLength="279" lengthAdjust="spacingAndGlyphs"> Plain Text </text>
<text x="612" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="630" y="70" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs">More Info</text>
<text x="621" y="70" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> More Info </text>
<text x="918" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs">Info Here</text>
<text x="9" y="87" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs"> Info Here </text>
<text x="324" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="87" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Visit Bing (</text>
<text x="450" y="87" fill="#87afff" textLength="144" lengthAdjust="spacingAndGlyphs">https://bing.com</text>
<text x="594" y="87" fill="#ffffff" textLength="9" lengthAdjust="spacingAndGlyphs">)</text>
<text x="333" y="87" fill="#ffffff" textLength="279" lengthAdjust="spacingAndGlyphs"> Visit Bing (https://bing.com) </text>
<text x="612" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="630" y="87" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Links</text>
<text x="621" y="87" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> Links </text>
<text x="918" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">Check This</text>
<text x="9" y="104" fill="#ffffff" textLength="315" lengthAdjust="spacingAndGlyphs"> Check This </text>
<text x="324" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="104" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Search</text>
<text x="333" y="104" fill="#ffffff" textLength="279" lengthAdjust="spacingAndGlyphs"> Search </text>
<text x="612" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="630" y="104" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs">Visit Yahoo (</text>
<text x="747" y="104" fill="#87afff" textLength="153" lengthAdjust="spacingAndGlyphs">https://yahoo.com</text>
<text x="900" y="104" fill="#ffffff" textLength="9" lengthAdjust="spacingAndGlyphs">)</text>
<text x="621" y="104" fill="#ffffff" textLength="297" lengthAdjust="spacingAndGlyphs"> Visit Yahoo (https://yahoo.com) </text>
<text x="918" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="927" lengthAdjust="spacingAndGlyphs">└───────────────────────────────────┴───────────────────────────────┴─────────────────────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -14,26 +14,25 @@
<text x="540" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="549" lengthAdjust="spacingAndGlyphs">├─────────────────┼──────────────────────┼──────────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#d7afff" textLength="108" lengthAdjust="spacingAndGlyphs">**not bold**</text>
<text x="9" y="70" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> **not bold** </text>
<text x="162" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="180" y="70" fill="#d7afff" textLength="108" lengthAdjust="spacingAndGlyphs">_not italic_</text>
<text x="171" y="70" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs"> _not italic_ </text>
<text x="369" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="387" y="70" fill="#d7afff" textLength="126" lengthAdjust="spacingAndGlyphs">~~not strike~~</text>
<text x="378" y="70" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> ~~not strike~~ </text>
<text x="540" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#d7afff" textLength="135" lengthAdjust="spacingAndGlyphs">[not link](url)</text>
<text x="9" y="87" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> [not link](url) </text>
<text x="162" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="180" y="87" fill="#d7afff" textLength="180" lengthAdjust="spacingAndGlyphs">&lt;u&gt;not underline&lt;/u&gt;</text>
<text x="171" y="87" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs"> &lt;u&gt;not underline&lt;/u&gt; </text>
<text x="369" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="387" y="87" fill="#d7afff" textLength="144" lengthAdjust="spacingAndGlyphs">https://not.link</text>
<text x="378" y="87" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> https://not.link </text>
<text x="540" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">Normal Text</text>
<text x="9" y="104" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> Normal Text </text>
<text x="162" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="180" y="104" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">More Code: </text>
<text x="279" y="104" fill="#d7afff" textLength="54" lengthAdjust="spacingAndGlyphs">*test*</text>
<text x="171" y="104" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs"> More Code: *test* </text>
<text x="369" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="387" y="104" fill="#d7afff" textLength="108" lengthAdjust="spacingAndGlyphs">***nested***</text>
<text x="378" y="104" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> ***nested*** </text>
<text x="540" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="549" lengthAdjust="spacingAndGlyphs">└─────────────────┴──────────────────────┴──────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -14,31 +14,25 @@
<text x="810" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="819" lengthAdjust="spacingAndGlyphs">├─────────────────────────────┼─────────────────────────────┼─────────────────────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Bold with </text>
<text x="108" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold" font-style="italic">Italic</text>
<text x="162" y="70" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs" font-weight="bold"> and Strike</text>
<text x="9" y="70" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Bold with Italic and Strike </text>
<text x="270" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Normal</text>
<text x="279" y="70" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Normal </text>
<text x="540" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="558" y="70" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="549" y="70" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="810" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="9" y="87" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="270" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="87" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Bold with </text>
<text x="378" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold" font-style="italic">Italic</text>
<text x="432" y="87" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs" font-weight="bold"> and Strike</text>
<text x="279" y="87" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Bold with Italic and Strike </text>
<text x="540" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="558" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Normal</text>
<text x="549" y="87" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Normal </text>
<text x="810" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Normal</text>
<text x="9" y="104" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Normal </text>
<text x="270" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="104" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="279" y="104" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="540" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="558" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs" font-weight="bold">Bold with </text>
<text x="648" y="104" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold" font-style="italic">Italic</text>
<text x="702" y="104" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs" font-weight="bold"> and Strike</text>
<text x="549" y="104" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Bold with Italic and Strike </text>
<text x="810" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="819" lengthAdjust="spacingAndGlyphs">└─────────────────────────────┴─────────────────────────────┴─────────────────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -14,18 +14,18 @@
<text x="378" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="405" lengthAdjust="spacingAndGlyphs">├──────────────┼────────────┼───────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">Start 🌟 End</text>
<text x="9" y="70" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> Start 🌟 End </text>
<text x="126" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="144" y="70" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">你好世界</text>
<text x="135" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> 你好世界 </text>
<text x="243" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="261" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Rocket 🚀 Man</text>
<text x="252" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Rocket 🚀 Man </text>
<text x="378" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">Thumbs 👍 Up</text>
<text x="9" y="87" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> Thumbs 👍 Up </text>
<text x="126" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="144" y="87" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">こんにちは</text>
<text x="135" y="87" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs"> こんにちは </text>
<text x="243" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="261" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Fire 🔥</text>
<text x="252" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Fire 🔥 </text>
<text x="378" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="405" lengthAdjust="spacingAndGlyphs">└──────────────┴────────────┴───────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -31,15 +31,15 @@
<text x="288" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="297" lengthAdjust="spacingAndGlyphs">├─────────────┼───────┼─────────┤</text>
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 1</text>
<text x="9" y="121" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> Data 1 </text>
<text x="126" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="144" y="121" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">Data</text>
<text x="135" y="121" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> Data </text>
<text x="198" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="216" y="121" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 3</text>
<text x="207" y="121" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs"> Data 3 </text>
<text x="288" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="126" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="144" y="138" fill="#ffffff" textLength="9" lengthAdjust="spacingAndGlyphs">2</text>
<text x="135" y="138" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> 2 </text>
<text x="198" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#333333" textLength="297" lengthAdjust="spacingAndGlyphs">└─────────────┴───────┴─────────┘</text>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

@@ -14,25 +14,25 @@
<text x="405" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="414" lengthAdjust="spacingAndGlyphs">├──────────────┼──────────────┼──────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 1, Col 1</text>
<text x="9" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 1, Col 1 </text>
<text x="135" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 1, Col 2</text>
<text x="144" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 1, Col 2 </text>
<text x="270" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 1, Col 3</text>
<text x="279" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 1, Col 3 </text>
<text x="405" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 2, Col 1</text>
<text x="9" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 2, Col 1 </text>
<text x="135" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="87" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 2, Col 2</text>
<text x="144" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 2, Col 2 </text>
<text x="270" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="87" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 2, Col 3</text>
<text x="279" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 2, Col 3 </text>
<text x="405" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 3, Col 1</text>
<text x="9" y="104" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 3, Col 1 </text>
<text x="135" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="104" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 3, Col 2</text>
<text x="144" y="104" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 3, Col 2 </text>
<text x="270" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="104" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Row 3, Col 3</text>
<text x="279" y="104" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Row 3, Col 3 </text>
<text x="405" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="414" lengthAdjust="spacingAndGlyphs">└──────────────┴──────────────┴──────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -93,105 +93,105 @@
<text x="1395" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#333333" textLength="1404" lengthAdjust="spacingAndGlyphs">├─────────────────────────────┼──────────────────────────────┼─────────────────────────────┼──────────────────────────────┼─────┼────────┼─────────┼───────┤</text>
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="172" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">The primary architecture</text>
<text x="9" y="172" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> The primary architecture </text>
<text x="270" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="172" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Each message is processed</text>
<text x="279" y="172" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Each message is processed </text>
<text x="549" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="172" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Historical data indicates a</text>
<text x="558" y="172" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Historical data indicates a </text>
<text x="819" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="172" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">A multi-layered defense</text>
<text x="828" y="172" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> A multi-layered defense </text>
<text x="1098" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1116" y="172" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">INF</text>
<text x="1107" y="172" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs"> INF </text>
<text x="1152" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1170" y="172" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Active</text>
<text x="1161" y="172" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Active </text>
<text x="1233" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1251" y="172" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">v2.4</text>
<text x="1242" y="172" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs"> v2.4 </text>
<text x="1323" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1341" y="172" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">J.</text>
<text x="1332" y="172" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> J. </text>
<text x="1395" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="189" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs">utilizes a decoupled</text>
<text x="9" y="189" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> utilizes a decoupled </text>
<text x="270" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="189" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs">through a series of</text>
<text x="279" y="189" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> through a series of </text>
<text x="549" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="189" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">significant reduction in</text>
<text x="558" y="189" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> significant reduction in </text>
<text x="819" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="189" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">strategy incorporates</text>
<text x="828" y="189" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> strategy incorporates </text>
<text x="1098" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1341" y="189" fill="#ffffff" textLength="27" lengthAdjust="spacingAndGlyphs">Doe</text>
<text x="1332" y="189" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> Doe </text>
<text x="1395" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="206" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">microservices approach,</text>
<text x="9" y="206" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> microservices approach, </text>
<text x="270" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="206" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">specialized workers that</text>
<text x="279" y="206" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> specialized workers that </text>
<text x="549" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="206" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">tail latency when utilizing</text>
<text x="558" y="206" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> tail latency when utilizing </text>
<text x="819" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="206" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">content security policies,</text>
<text x="828" y="206" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> content security policies, </text>
<text x="1098" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="223" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs">leveraging container</text>
<text x="9" y="223" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> leveraging container </text>
<text x="270" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="223" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">handle data transformation,</text>
<text x="279" y="223" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> handle data transformation, </text>
<text x="549" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="223" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">edge computing nodes closer</text>
<text x="558" y="223" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> edge computing nodes closer </text>
<text x="819" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="223" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">input sanitization</text>
<text x="828" y="223" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> input sanitization </text>
<text x="1098" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="240" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs">orchestration for</text>
<text x="9" y="240" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> orchestration for </text>
<text x="270" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="240" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">validation, and persistent</text>
<text x="279" y="240" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> validation, and persistent </text>
<text x="549" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="240" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">to the geographic location</text>
<text x="558" y="240" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> to the geographic location </text>
<text x="819" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="240" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">libraries, and regular</text>
<text x="828" y="240" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> libraries, and regular </text>
<text x="1098" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="257" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">scalability and fault</text>
<text x="9" y="257" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> scalability and fault </text>
<text x="270" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="257" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">storage using a persistent</text>
<text x="279" y="257" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> storage using a persistent </text>
<text x="549" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="257" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">of the end-user base.</text>
<text x="558" y="257" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> of the end-user base. </text>
<text x="819" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="257" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">automated penetration</text>
<text x="828" y="257" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> automated penetration </text>
<text x="1098" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="274" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">tolerance in high-load</text>
<text x="9" y="274" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> tolerance in high-load </text>
<text x="270" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">queue.</text>
<text x="279" y="274" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> queue. </text>
<text x="549" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="819" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="274" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs">testing routines.</text>
<text x="828" y="274" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> testing routines. </text>
<text x="1098" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="291" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">scenarios.</text>
<text x="9" y="291" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> scenarios. </text>
<text x="270" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="549" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="291" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">Monitoring tools have</text>
<text x="558" y="291" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Monitoring tools have </text>
<text x="819" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1098" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -200,85 +200,85 @@
<text x="1395" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="270" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="308" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">The pipeline features</text>
<text x="279" y="308" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> The pipeline features </text>
<text x="549" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="308" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">captured a steady increase</text>
<text x="558" y="308" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> captured a steady increase </text>
<text x="819" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="308" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">Developers are required to</text>
<text x="828" y="308" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Developers are required to </text>
<text x="1098" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="325" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">This layer provides the</text>
<text x="9" y="325" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> This layer provides the </text>
<text x="270" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="325" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">built-in retry mechanisms</text>
<text x="279" y="325" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> built-in retry mechanisms </text>
<text x="549" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="325" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">in throughput efficiency</text>
<text x="558" y="325" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> in throughput efficiency </text>
<text x="819" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="325" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">undergo mandatory security</text>
<text x="828" y="325" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> undergo mandatory security </text>
<text x="1098" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="342" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">fundamental building blocks</text>
<text x="9" y="342" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> fundamental building blocks </text>
<text x="270" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="342" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">with exponential backoff to</text>
<text x="279" y="342" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> with exponential backoff to </text>
<text x="549" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="342" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">since the introduction of</text>
<text x="558" y="342" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> since the introduction of </text>
<text x="819" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="342" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">training focusing on the</text>
<text x="828" y="342" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> training focusing on the </text>
<text x="1098" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="359" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">for service discovery, load</text>
<text x="9" y="359" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> for service discovery, load </text>
<text x="270" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="359" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">ensure message delivery</text>
<text x="279" y="359" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> ensure message delivery </text>
<text x="549" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="359" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">the vectorized query engine</text>
<text x="558" y="359" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> the vectorized query engine </text>
<text x="819" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="359" fill="#ffffff" textLength="252" lengthAdjust="spacingAndGlyphs">OWASP Top Ten to ensure that</text>
<text x="828" y="359" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> OWASP Top Ten to ensure that </text>
<text x="1098" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="376" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">balancing, and</text>
<text x="9" y="376" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> balancing, and </text>
<text x="270" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="376" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">integrity even during</text>
<text x="279" y="376" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> integrity even during </text>
<text x="549" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="376" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs">in the primary data</text>
<text x="558" y="376" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> in the primary data </text>
<text x="819" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="376" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">security is integrated into</text>
<text x="828" y="376" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> security is integrated into </text>
<text x="1098" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="393" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">inter-service communication</text>
<text x="9" y="393" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> inter-service communication </text>
<text x="270" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="393" fill="#ffffff" textLength="252" lengthAdjust="spacingAndGlyphs">transient network or service</text>
<text x="279" y="393" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> transient network or service </text>
<text x="549" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="393" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">warehouse.</text>
<text x="558" y="393" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> warehouse. </text>
<text x="819" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="393" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">the initial design phase.</text>
<text x="828" y="393" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> the initial design phase. </text>
<text x="1098" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="410" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs">via highly efficient</text>
<text x="9" y="410" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> via highly efficient </text>
<text x="270" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="410" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs">failures.</text>
<text x="279" y="410" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> failures. </text>
<text x="549" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="819" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1098" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -287,12 +287,12 @@
<text x="1323" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="427" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs">protocol buffers.</text>
<text x="9" y="427" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> protocol buffers. </text>
<text x="270" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="549" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="427" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs">Resource utilization</text>
<text x="558" y="427" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Resource utilization </text>
<text x="819" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="427" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">The implementation of a</text>
<text x="828" y="427" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> The implementation of a </text>
<text x="1098" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -300,85 +300,85 @@
<text x="1395" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="270" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="444" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Horizontal autoscaling is</text>
<text x="279" y="444" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Horizontal autoscaling is </text>
<text x="549" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="444" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">metrics demonstrate that</text>
<text x="558" y="444" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> metrics demonstrate that </text>
<text x="819" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="444" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">robust Identity and Access</text>
<text x="828" y="444" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> robust Identity and Access </text>
<text x="1098" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="461" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Advanced telemetry and</text>
<text x="9" y="461" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Advanced telemetry and </text>
<text x="270" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="461" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">triggered automatically</text>
<text x="279" y="461" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> triggered automatically </text>
<text x="549" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="461" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs">the transition to</text>
<text x="558" y="461" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> the transition to </text>
<text x="819" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="461" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">Management system ensures</text>
<text x="828" y="461" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> Management system ensures </text>
<text x="1098" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="478" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">logging integrations allow</text>
<text x="9" y="478" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> logging integrations allow </text>
<text x="270" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="478" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">based on the depth of the</text>
<text x="279" y="478" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> based on the depth of the </text>
<text x="549" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="478" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">serverless compute for</text>
<text x="558" y="478" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> serverless compute for </text>
<text x="819" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="478" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">that the principle of least</text>
<text x="828" y="478" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> that the principle of least </text>
<text x="1098" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="495" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">for real-time monitoring of</text>
<text x="9" y="495" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> for real-time monitoring of </text>
<text x="270" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="495" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">processing queue, ensuring</text>
<text x="279" y="495" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> processing queue, ensuring </text>
<text x="549" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="495" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">intermittent tasks has</text>
<text x="558" y="495" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> intermittent tasks has </text>
<text x="819" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="495" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">privilege is strictly</text>
<text x="828" y="495" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> privilege is strictly </text>
<text x="1098" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="512" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">system health and rapid</text>
<text x="9" y="512" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> system health and rapid </text>
<text x="270" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="512" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">consistent performance</text>
<text x="279" y="512" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> consistent performance </text>
<text x="549" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="512" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs">resulted in a thirty</text>
<text x="558" y="512" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> resulted in a thirty </text>
<text x="819" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="512" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs">enforced across all</text>
<text x="828" y="512" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> enforced across all </text>
<text x="1098" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="529" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs">identification of</text>
<text x="9" y="529" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> identification of </text>
<text x="270" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="529" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">during unexpected traffic</text>
<text x="279" y="529" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> during unexpected traffic </text>
<text x="549" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="567" y="529" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs">percent cost optimization.</text>
<text x="558" y="529" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> percent cost optimization. </text>
<text x="819" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="837" y="529" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs">environments.</text>
<text x="828" y="529" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> environments. </text>
<text x="1098" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1152" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1233" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1323" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="546" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">bottlenecks within the</text>
<text x="9" y="546" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> bottlenecks within the </text>
<text x="270" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="546" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">spikes.</text>
<text x="279" y="546" fill="#ffffff" textLength="270" lengthAdjust="spacingAndGlyphs"> spikes. </text>
<text x="549" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="819" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1098" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
@@ -387,7 +387,7 @@
<text x="1323" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="1395" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="563" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs">service mesh.</text>
<text x="9" y="563" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> service mesh. </text>
<text x="270" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="549" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="819" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

@@ -32,31 +32,31 @@
<text x="630" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="639" lengthAdjust="spacingAndGlyphs">├───────────────┼───────────────┼──────────────────┼──────────────────┤</text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 1.1</text>
<text x="9" y="104" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Data 1.1 </text>
<text x="144" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 1.2</text>
<text x="153" y="104" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Data 1.2 </text>
<text x="288" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="306" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 1.3</text>
<text x="297" y="104" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> Data 1.3 </text>
<text x="459" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="477" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 1.4</text>
<text x="468" y="104" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> Data 1.4 </text>
<text x="630" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 2.1</text>
<text x="9" y="121" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Data 2.1 </text>
<text x="144" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="121" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 2.2</text>
<text x="153" y="121" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Data 2.2 </text>
<text x="288" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="306" y="121" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 2.3</text>
<text x="297" y="121" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> Data 2.3 </text>
<text x="459" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="477" y="121" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 2.4</text>
<text x="468" y="121" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> Data 2.4 </text>
<text x="630" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="138" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 3.1</text>
<text x="9" y="138" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Data 3.1 </text>
<text x="144" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="138" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 3.2</text>
<text x="153" y="138" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Data 3.2 </text>
<text x="288" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="306" y="138" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 3.3</text>
<text x="297" y="138" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> Data 3.3 </text>
<text x="459" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="477" y="138" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Data 3.4</text>
<text x="468" y="138" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> Data 3.4 </text>
<text x="630" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#333333" textLength="639" lengthAdjust="spacingAndGlyphs">└───────────────┴───────────────┴──────────────────┴──────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -14,18 +14,18 @@
<text x="450" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="486" lengthAdjust="spacingAndGlyphs">├───────────────┼───────────────────┼────────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">你好 😃</text>
<text x="9" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> 你好 😃 </text>
<text x="135" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">こんにちは 🚀</text>
<text x="144" y="70" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> こんにちは 🚀 </text>
<text x="306" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">안녕하세요 📝</text>
<text x="315" y="70" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> 안녕하세요 📝 </text>
<text x="450" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">World 🌍</text>
<text x="9" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> World 🌍 </text>
<text x="135" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Code 💻</text>
<text x="144" y="87" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs"> Code 💻 </text>
<text x="306" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="87" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">Pizza 🍕</text>
<text x="315" y="87" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Pizza 🍕 </text>
<text x="450" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="486" lengthAdjust="spacingAndGlyphs">└───────────────┴───────────────────┴────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -14,18 +14,18 @@
<text x="441" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="450" lengthAdjust="spacingAndGlyphs">├──────────────┼─────────────────┼───────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">你好</text>
<text x="9" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> 你好 </text>
<text x="135" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="70" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">こんにちは</text>
<text x="144" y="70" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> こんにちは </text>
<text x="297" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="315" y="70" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">안녕하세요</text>
<text x="306" y="70" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> 안녕하세요 </text>
<text x="441" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">世界</text>
<text x="9" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> 世界 </text>
<text x="135" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="87" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">世界</text>
<text x="144" y="87" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> 世界 </text>
<text x="297" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="315" y="87" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">세계</text>
<text x="306" y="87" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> 세계 </text>
<text x="441" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="450" lengthAdjust="spacingAndGlyphs">└──────────────┴─────────────────┴───────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -14,18 +14,18 @@
<text x="279" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="315" lengthAdjust="spacingAndGlyphs">├──────────┼───────────┼──────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">Smile 😃</text>
<text x="9" y="70" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs"> Smile 😃 </text>
<text x="90" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="108" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Fire 🔥</text>
<text x="99" y="70" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> Fire 🔥 </text>
<text x="189" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="207" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Love 💖</text>
<text x="198" y="70" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs"> Love 💖 </text>
<text x="279" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Cool 😎</text>
<text x="9" y="87" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs"> Cool 😎 </text>
<text x="90" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="108" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Star ⭐</text>
<text x="99" y="87" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> Star ⭐ </text>
<text x="189" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="207" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Blue 💙</text>
<text x="198" y="87" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs"> Blue 💙 </text>
<text x="279" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="315" lengthAdjust="spacingAndGlyphs">└──────────┴───────────┴──────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -12,41 +12,39 @@
<text x="414" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="423" lengthAdjust="spacingAndGlyphs">├───────────────┼─────────────────────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">Bold</text>
<text x="9" y="70" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Bold </text>
<text x="144" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="70" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">Bold Text</text>
<text x="153" y="70" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Bold Text </text>
<text x="414" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Italic</text>
<text x="9" y="87" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Italic </text>
<text x="144" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="87" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs" font-style="italic">Italic Text</text>
<text x="153" y="87" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Italic Text </text>
<text x="414" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Combined</text>
<text x="9" y="104" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Combined </text>
<text x="144" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="104" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold" font-style="italic">Bold and Italic</text>
<text x="153" y="104" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Bold and Italic </text>
<text x="414" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">Link</text>
<text x="9" y="121" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Link </text>
<text x="144" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="121" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">Google (</text>
<text x="234" y="121" fill="#87afff" textLength="162" lengthAdjust="spacingAndGlyphs">https://google.com</text>
<text x="396" y="121" fill="#ffffff" textLength="9" lengthAdjust="spacingAndGlyphs">)</text>
<text x="153" y="121" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Google (https://google.com) </text>
<text x="414" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="138" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">Code</text>
<text x="9" y="138" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Code </text>
<text x="144" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="138" fill="#d7afff" textLength="99" lengthAdjust="spacingAndGlyphs">const x = 1</text>
<text x="153" y="138" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> const x = 1 </text>
<text x="414" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="155" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs">Strikethrough</text>
<text x="9" y="155" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Strikethrough </text>
<text x="144" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="155" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Strike</text>
<text x="153" y="155" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Strike </text>
<text x="414" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="172" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs">Underline</text>
<text x="9" y="172" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Underline </text>
<text x="144" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="162" y="172" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs" text-decoration="underline">Underline</text>
<text x="153" y="172" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> Underline </text>
<text x="414" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="189" fill="#333333" textLength="423" lengthAdjust="spacingAndGlyphs">└───────────────┴─────────────────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -10,9 +10,9 @@
<text x="162" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="171" lengthAdjust="spacingAndGlyphs">├────────┼────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 1</text>
<text x="9" y="70" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Data 1 </text>
<text x="81" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="99" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 2</text>
<text x="90" y="70" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Data 2 </text>
<text x="162" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="171" lengthAdjust="spacingAndGlyphs">└────────┴────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -14,9 +14,9 @@
<text x="297" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="306" lengthAdjust="spacingAndGlyphs">├──────────┼──────────┼──────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 1</text>
<text x="9" y="70" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> Data 1 </text>
<text x="99" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="117" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 2</text>
<text x="108" y="70" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs"> Data 2 </text>
<text x="198" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="297" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="306" lengthAdjust="spacingAndGlyphs">└──────────┴──────────┴──────────┘</text>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -14,11 +14,11 @@
<text x="405" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="414" lengthAdjust="spacingAndGlyphs">├─────────────┼───────────────┼──────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 1</text>
<text x="9" y="70" fill="#ffffff" textLength="117" lengthAdjust="spacingAndGlyphs"> Data 1 </text>
<text x="126" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="144" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 2</text>
<text x="135" y="70" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Data 2 </text>
<text x="270" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="288" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Data 3</text>
<text x="279" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs"> Data 3 </text>
<text x="405" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="414" lengthAdjust="spacingAndGlyphs">└─────────────┴───────────────┴──────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -14,38 +14,38 @@
<text x="468" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="477" lengthAdjust="spacingAndGlyphs">├────────────────┼────────────────┼─────────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">This is a very</text>
<text x="9" y="70" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> This is a very </text>
<text x="153" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="171" y="70" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">This is also a</text>
<text x="162" y="70" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> This is also a </text>
<text x="306" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="70" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">And this is the</text>
<text x="315" y="70" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> And this is the </text>
<text x="468" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">long text that</text>
<text x="9" y="87" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> long text that </text>
<text x="153" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="171" y="87" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">very long text</text>
<text x="162" y="87" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> very long text </text>
<text x="306" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="87" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">third long text</text>
<text x="315" y="87" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> third long text </text>
<text x="468" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">needs wrapping</text>
<text x="9" y="104" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> needs wrapping </text>
<text x="153" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="171" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">that needs</text>
<text x="162" y="104" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> that needs </text>
<text x="306" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">that needs</text>
<text x="315" y="104" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> that needs </text>
<text x="468" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">in column 1</text>
<text x="9" y="121" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> in column 1 </text>
<text x="153" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="171" y="121" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">wrapping in</text>
<text x="162" y="121" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> wrapping in </text>
<text x="306" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="121" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">wrapping in</text>
<text x="315" y="121" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> wrapping in </text>
<text x="468" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="153" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="171" y="138" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">column 2</text>
<text x="162" y="138" fill="#ffffff" textLength="144" lengthAdjust="spacingAndGlyphs"> column 2 </text>
<text x="306" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="138" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">column 3</text>
<text x="315" y="138" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> column 3 </text>
<text x="468" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#333333" textLength="477" lengthAdjust="spacingAndGlyphs">└────────────────┴────────────────┴─────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

@@ -14,37 +14,37 @@
<text x="486" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="495" lengthAdjust="spacingAndGlyphs">├───────────────────┼───────────────┼─────────────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Start. Stop.</text>
<text x="9" y="70" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Start. Stop. </text>
<text x="180" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="198" y="70" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Semi; colon:</text>
<text x="189" y="70" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Semi; colon: </text>
<text x="324" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="70" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs">At@ Hash#</text>
<text x="333" y="70" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> At@ Hash# </text>
<text x="486" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="87" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs">Comma, separated.</text>
<text x="9" y="87" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Comma, separated. </text>
<text x="180" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="198" y="87" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Pipe| Slash/</text>
<text x="189" y="87" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Pipe| Slash/ </text>
<text x="324" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="87" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs">Dollar$</text>
<text x="333" y="87" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> Dollar$ </text>
<text x="486" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="104" fill="#ffffff" textLength="108" lengthAdjust="spacingAndGlyphs">Exclamation!</text>
<text x="9" y="104" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Exclamation! </text>
<text x="180" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="198" y="104" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">Backslash\</text>
<text x="189" y="104" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs"> Backslash\ </text>
<text x="324" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="104" fill="#ffffff" textLength="135" lengthAdjust="spacingAndGlyphs">Percent% Caret^</text>
<text x="333" y="104" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> Percent% Caret^ </text>
<text x="486" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="121" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs">Question?</text>
<text x="9" y="121" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> Question? </text>
<text x="180" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="121" fill="#ffffff" textLength="90" lengthAdjust="spacingAndGlyphs">Ampersand&amp;</text>
<text x="333" y="121" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> Ampersand&amp; </text>
<text x="486" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="138" fill="#ffffff" textLength="99" lengthAdjust="spacingAndGlyphs">hyphen-ated</text>
<text x="9" y="138" fill="#ffffff" textLength="171" lengthAdjust="spacingAndGlyphs"> hyphen-ated </text>
<text x="180" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="324" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="342" y="138" fill="#ffffff" textLength="81" lengthAdjust="spacingAndGlyphs">Asterisk*</text>
<text x="333" y="138" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs"> Asterisk* </text>
<text x="486" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="155" fill="#333333" textLength="495" lengthAdjust="spacingAndGlyphs">└───────────────────┴───────────────┴─────────────────┘</text>
</g>

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -14,20 +14,20 @@
<text x="414" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="423" lengthAdjust="spacingAndGlyphs">├───────┼─────────────────────────────┼───────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="9" y="70" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="72" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="90" y="70" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">This is a very long cell</text>
<text x="81" y="70" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> This is a very long cell </text>
<text x="342" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="360" y="70" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">Short</text>
<text x="351" y="70" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> Short </text>
<text x="414" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="72" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="90" y="87" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">content that should wrap to</text>
<text x="81" y="87" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> content that should wrap to </text>
<text x="342" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="414" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="72" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="90" y="104" fill="#ffffff" textLength="126" lengthAdjust="spacingAndGlyphs">multiple lines</text>
<text x="81" y="104" fill="#ffffff" textLength="261" lengthAdjust="spacingAndGlyphs"> multiple lines </text>
<text x="342" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="414" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="423" lengthAdjust="spacingAndGlyphs">└───────┴─────────────────────────────┴───────┘</text>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -14,21 +14,21 @@
<text x="396" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="53" fill="#333333" textLength="405" lengthAdjust="spacingAndGlyphs">├───────┼──────────────────────────┼────────┤</text>
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="18" y="70" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">Tiny</text>
<text x="9" y="70" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs"> Tiny </text>
<text x="72" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="90" y="70" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">This is a very long text</text>
<text x="81" y="70" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs"> This is a very long text </text>
<text x="315" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="333" y="70" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">Not so</text>
<text x="324" y="70" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> Not so </text>
<text x="396" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="72" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="90" y="87" fill="#ffffff" textLength="216" lengthAdjust="spacingAndGlyphs">that definitely needs to</text>
<text x="81" y="87" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs"> that definitely needs to </text>
<text x="315" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="333" y="87" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs">long</text>
<text x="324" y="87" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs"> long </text>
<text x="396" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="72" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="90" y="104" fill="#ffffff" textLength="189" lengthAdjust="spacingAndGlyphs">wrap to the next line</text>
<text x="81" y="104" fill="#ffffff" textLength="234" lengthAdjust="spacingAndGlyphs"> wrap to the next line </text>
<text x="315" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="396" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs"></text>
<text x="0" y="121" fill="#333333" textLength="405" lengthAdjust="spacingAndGlyphs">└───────┴──────────────────────────┴────────┘</text>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

+21 -2
View File
@@ -6,7 +6,7 @@
import { vi, beforeEach, afterEach } from 'vitest';
import { format } from 'node:util';
import { coreEvents } from '@google/gemini-cli-core';
import { coreEvents, debugLogger } from '@google/gemini-cli-core';
import { themeManager } from './src/ui/themes/theme-manager.js';
import { mockInkSpinner } from './src/test-utils/mockSpinner.js';
@@ -42,10 +42,25 @@ import './src/test-utils/customMatchers.js';
let consoleErrorSpy: vi.SpyInstance;
let actWarnings: Array<{ message: string; stack: string }> = [];
let logSpy: vi.SpyInstance;
let warnSpy: vi.SpyInstance;
let errorSpy: vi.SpyInstance;
let debugSpy: vi.SpyInstance;
beforeEach(() => {
// Reset themeManager state to ensure test isolation
themeManager.resetForTesting();
// Mock debugLogger to avoid test output noise
logSpy = vi.spyOn(debugLogger, 'log').mockImplementation(() => {});
warnSpy = vi.spyOn(debugLogger, 'warn').mockImplementation((...args) => {
console.warn(...args);
});
errorSpy = vi.spyOn(debugLogger, 'error').mockImplementation((...args) => {
console.error(...args);
});
debugSpy = vi.spyOn(debugLogger, 'debug').mockImplementation(() => {});
actWarnings = [];
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
const firstArg = args[0];
@@ -88,8 +103,12 @@ beforeEach(() => {
afterEach(() => {
consoleErrorSpy.mockRestore();
vi.unstubAllEnvs();
logSpy?.mockRestore();
warnSpy?.mockRestore();
errorSpy?.mockRestore();
debugSpy?.mockRestore();
vi.unstubAllEnvs();
if (actWarnings.length > 0) {
const messages = actWarnings
.map(({ message, stack }) => `${message}\n${stack}`)
@@ -166,7 +166,7 @@ describe('browserAgentFactory', () => {
expect(browserManager).toBeDefined();
});
it('should call printOutput when provided', async () => {
it('should not call printOutput for internal setup messages', async () => {
const printOutput = vi.fn();
await createBrowserAgentDefinition(
@@ -175,7 +175,7 @@ describe('browserAgentFactory', () => {
printOutput,
);
expect(printOutput).toHaveBeenCalled();
expect(printOutput).not.toHaveBeenCalled();
});
it('should create definition with correct structure', async () => {
@@ -53,7 +53,7 @@ import {
export async function createBrowserAgentDefinition(
config: Config,
messageBus: MessageBus,
printOutput?: (msg: string) => void,
_printOutput?: (msg: string) => void,
): Promise<{
definition: LocalAgentDefinition<typeof BrowserTaskResultSchema>;
browserManager: BrowserManager;
@@ -66,23 +66,17 @@ export async function createBrowserAgentDefinition(
const browserManager = BrowserManager.getInstance(config);
await browserManager.ensureConnection();
if (printOutput) {
printOutput('Browser connected with isolated MCP client.');
}
debugLogger.log('Browser connected with isolated MCP client.');
// Determine if input blocker should be active (non-headless + enabled)
const shouldDisableInput = config.shouldDisableBrowserUserInput();
// Inject automation overlay and input blocker if not in headless mode
const browserConfig = config.getBrowserAgentConfig();
if (!browserConfig?.customConfig?.headless) {
if (printOutput) {
printOutput('Injecting automation overlay...');
}
debugLogger.log('Injecting automation overlay...');
await injectAutomationOverlay(browserManager);
if (shouldDisableInput) {
if (printOutput) {
printOutput('Injecting input blocker...');
}
debugLogger.log('Injecting input blocker...');
await injectInputBlocker(browserManager);
}
}
@@ -18,7 +18,6 @@ import { randomUUID } from 'node:crypto';
import type { Config } from '../../config/config.js';
import { type AgentLoopContext } from '../../config/agent-loop-context.js';
import { LocalAgentExecutor } from '../local-executor.js';
import { safeJsonToMarkdown } from '../../utils/markdownUtils.js';
import {
BaseToolInvocation,
type ToolResult,
@@ -30,6 +29,7 @@ import {
type SubagentActivityEvent,
type SubagentProgress,
type SubagentActivityItem,
AgentTerminateMode,
isToolActivityError,
} from '../types.js';
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
@@ -56,6 +56,8 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
AgentInputs,
ToolResult
> {
private readonly agentName: string;
constructor(
private readonly context: AgentLoopContext,
params: AgentInputs,
@@ -63,13 +65,15 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
_toolName?: string,
_toolDisplayName?: string,
) {
const resolvedName = _toolName ?? 'browser_agent';
// Note: BrowserAgentDefinition is a factory function, so we use hardcoded names
super(
params,
messageBus,
_toolName ?? 'browser_agent',
resolvedName,
_toolDisplayName ?? 'Browser Agent',
);
this.agentName = resolvedName;
}
private get config(): Config {
@@ -112,7 +116,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
// Send initial state
const initialProgress: SubagentProgress = {
isSubagentProgress: true,
agentName: this['_toolName'] ?? 'browser_agent',
agentName: this.agentName,
recentActivity: [],
state: 'running',
};
@@ -135,7 +139,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
}
updateOutput({
isSubagentProgress: true,
agentName: this['_toolName'] ?? 'browser_agent',
agentName: this.agentName,
recentActivity: [...recentActivity],
state: 'running',
} as SubagentProgress);
@@ -280,7 +284,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
const progress: SubagentProgress = {
isSubagentProgress: true,
agentName: this['_toolName'] ?? 'browser_agent',
agentName: this.agentName,
recentActivity: [...recentActivity],
state: 'running',
};
@@ -297,34 +301,40 @@ export class BrowserAgentInvocation extends BaseToolInvocation<
const output = await executor.run(this.params, signal);
const displayResult = safeJsonToMarkdown(output.result);
const resultContent = `Browser agent finished.
Termination Reason: ${output.terminate_reason}
Result:
${output.result}`;
const displayContent = `
Browser Agent Finished
// Map terminate_reason to the correct SubagentProgress state.
// GOAL = agent completed its task normally.
// ABORTED = user cancelled.
// Others (ERROR, MAX_TURNS, ERROR_NO_COMPLETE_TASK_CALL) = error.
let progressState: SubagentProgress['state'];
if (output.terminate_reason === AgentTerminateMode.ABORTED) {
progressState = 'cancelled';
} else if (output.terminate_reason === AgentTerminateMode.GOAL) {
progressState = 'completed';
} else {
progressState = 'error';
}
Termination Reason: ${output.terminate_reason}
Result:
${displayResult}
`;
const progress: SubagentProgress = {
isSubagentProgress: true,
agentName: this.agentName,
recentActivity: [...recentActivity],
state: progressState,
result: output.result,
terminateReason: output.terminate_reason,
};
if (updateOutput) {
updateOutput({
isSubagentProgress: true,
agentName: this['_toolName'] ?? 'browser_agent',
recentActivity: [...recentActivity],
state: 'completed',
} as SubagentProgress);
updateOutput(progress);
}
return {
llmContent: [{ text: resultContent }],
returnDisplay: displayContent,
returnDisplay: progress,
};
} catch (error) {
const rawErrorMessage =
@@ -343,7 +353,7 @@ ${displayResult}
const progress: SubagentProgress = {
isSubagentProgress: true,
agentName: this['_toolName'] ?? 'browser_agent',
agentName: this.agentName,
recentActivity: [...recentActivity],
state: isAbort ? 'cancelled' : 'error',
};
@@ -108,7 +108,7 @@ import {
} from '../scheduler/types.js';
import { CompressionStatus } from '../core/turn.js';
import { ChatCompressionService } from '../services/chatCompressionService.js';
import { ChatCompressionService } from '../context/chatCompressionService.js';
import type {
ModelConfigKey,
ResolvedModelConfig,
@@ -121,7 +121,7 @@ const mockSetHistory = vi.fn((newHistory: Content[]) => {
mockChatHistory = newHistory;
});
vi.mock('../services/chatCompressionService.js', () => ({
vi.mock('../context/chatCompressionService.js', () => ({
ChatCompressionService: vi.fn().mockImplementation(() => ({
compress: mockCompress,
})),
+1 -1
View File
@@ -30,7 +30,7 @@ import {
} from '../tools/mcp-tool.js';
import { CompressionStatus } from '../core/turn.js';
import { type ToolCallRequestInfo } from '../scheduler/types.js';
import { ChatCompressionService } from '../services/chatCompressionService.js';
import { ChatCompressionService } from '../context/chatCompressionService.js';
import { getDirectoryContextString } from '../utils/environmentContext.js';
import { renderUserMemory } from '../prompts/snippets.js';
import { promptIdContext } from '../utils/promptIdContext.js';
@@ -510,5 +510,36 @@ describe('LocalSubagentInvocation', () => {
'Operation cancelled by user',
);
});
it('should publish SUBAGENT_ACTIVITY events to the MessageBus', async () => {
const { MessageBusType } = await import('../confirmation-bus/types.js');
mockExecutorInstance.run.mockImplementation(async () => {
const onActivity = MockLocalAgentExecutor.create.mock.calls[0][2];
if (onActivity) {
onActivity({
isSubagentActivityEvent: true,
agentName: 'MockAgent',
type: 'THOUGHT_CHUNK',
data: { text: 'Thinking...' },
} as SubagentActivityEvent);
}
return { result: 'Done', terminate_reason: AgentTerminateMode.GOAL };
});
await invocation.execute(signal, updateOutput);
expect(mockMessageBus.publish).toHaveBeenCalledWith(
expect.objectContaining({
type: MessageBusType.SUBAGENT_ACTIVITY,
subagentName: 'Mock Agent',
activity: expect.objectContaining({
type: 'thought',
content: 'Thinking...',
}),
}),
);
});
});
});
+23 -10
View File
@@ -5,6 +5,7 @@
*/
import { type AgentLoopContext } from '../config/agent-loop-context.js';
import { MessageBusType } from '../confirmation-bus/types.js';
import { LocalAgentExecutor } from './local-executor.js';
import {
BaseToolInvocation,
@@ -33,7 +34,6 @@ import {
const INPUT_PREVIEW_MAX_LENGTH = 50;
const DESCRIPTION_MAX_LENGTH = 200;
const MAX_RECENT_ACTIVITY = 3;
/**
* Represents a validated, executable instance of a subagent tool.
@@ -87,6 +87,14 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
return description.slice(0, DESCRIPTION_MAX_LENGTH);
}
private publishActivity(activity: SubagentActivityItem): void {
void this.messageBus.publish({
type: MessageBusType.SUBAGENT_ACTIVITY,
subagentName: this.definition.displayName ?? this.definition.name,
activity,
});
}
/**
* Executes the subagent.
*
@@ -99,7 +107,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
signal: AbortSignal,
updateOutput?: (output: ToolLiveOutput) => void,
): Promise<ToolResult> {
let recentActivity: SubagentActivityItem[] = [];
const recentActivity: SubagentActivityItem[] = [];
try {
if (updateOutput) {
@@ -140,6 +148,11 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
});
}
updated = true;
const latestThought = recentActivity[recentActivity.length - 1];
if (latestThought) {
this.publishActivity(latestThought);
}
break;
}
case 'TOOL_CALL_START': {
@@ -163,6 +176,11 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
status: 'running',
});
updated = true;
const latestTool = recentActivity[recentActivity.length - 1];
if (latestTool) {
this.publishActivity(latestTool);
}
break;
}
case 'TOOL_CALL_END': {
@@ -178,6 +196,8 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
) {
recentActivity[i].status = isError ? 'error' : 'completed';
updated = true;
this.publishActivity(recentActivity[i]);
break;
}
}
@@ -242,11 +262,6 @@ export class LocalSubagentInvocation extends BaseToolInvocation<
}
if (updated) {
// Keep only the last N items
if (recentActivity.length > MAX_RECENT_ACTIVITY) {
recentActivity = recentActivity.slice(-MAX_RECENT_ACTIVITY);
}
const progress: SubagentProgress = {
isSubagentProgress: true,
agentName: this.definition.name,
@@ -332,9 +347,7 @@ ${output.result}`;
status: 'error',
});
// Maintain size limit
if (recentActivity.length > MAX_RECENT_ACTIVITY) {
recentActivity = recentActivity.slice(-MAX_RECENT_ACTIVITY);
}
// No limit on UI events sent via bus
}
}
+51 -2
View File
@@ -221,7 +221,7 @@ vi.mock('../utils/fetch.js', () => ({
setGlobalProxy: mockSetGlobalProxy,
}));
vi.mock('../services/contextManager.js', () => ({
vi.mock('../context/contextManager.js', () => ({
ContextManager: vi.fn().mockImplementation(() => ({
refresh: vi.fn(),
getGlobalMemory: vi.fn().mockReturnValue(''),
@@ -237,7 +237,7 @@ import { tokenLimit } from '../core/tokenLimits.js';
import { getCodeAssistServer } from '../code_assist/codeAssist.js';
import { getExperiments } from '../code_assist/experiments/experiments.js';
import type { CodeAssistServer } from '../code_assist/server.js';
import { ContextManager } from '../services/contextManager.js';
import { ContextManager } from '../context/contextManager.js';
import { UserTierId } from '../code_assist/types.js';
import type {
ModelConfigService,
@@ -591,6 +591,55 @@ describe('Server Config (config.ts)', () => {
expect(await config.getResolvedClassifierThreshold()).toBe(90);
});
});
describe('getGemini31LaunchedSync', () => {
it.each([AuthType.USE_GEMINI, AuthType.USE_VERTEX_AI, AuthType.GATEWAY])(
'should return true for %s',
async (authType) => {
const config = new Config(baseParams);
vi.mocked(createContentGeneratorConfig).mockResolvedValue({
authType,
});
await config.refreshAuth(authType);
expect(config.getGemini31LaunchedSync()).toBe(true);
},
);
it('should fallback to experiments for other auth types', async () => {
vi.mocked(getExperiments).mockResolvedValue({
experimentIds: [],
flags: {
[ExperimentFlags.GEMINI_3_1_PRO_LAUNCHED]: {
flagId: ExperimentFlags.GEMINI_3_1_PRO_LAUNCHED,
boolValue: true,
},
},
});
const config = new Config(baseParams);
vi.mocked(createContentGeneratorConfig).mockResolvedValue({
authType: AuthType.LOGIN_WITH_GOOGLE,
});
await config.refreshAuth(AuthType.LOGIN_WITH_GOOGLE);
expect(config.getGemini31LaunchedSync()).toBe(true);
});
});
describe('getGemini31FlashLiteLaunchedSync', () => {
it.each([AuthType.USE_GEMINI, AuthType.USE_VERTEX_AI, AuthType.GATEWAY])(
'should return true for %s',
async (authType) => {
const config = new Config(baseParams);
vi.mocked(createContentGeneratorConfig).mockResolvedValue({
authType,
});
await config.refreshAuth(authType);
expect(config.getGemini31FlashLiteLaunchedSync()).toBe(true);
},
);
});
});
describe('refreshAuth', () => {
+36 -44
View File
@@ -116,7 +116,7 @@ import {
type ModelConfigServiceConfig,
} from '../services/modelConfigService.js';
import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js';
import { ContextManager } from '../services/contextManager.js';
import { ContextManager } from '../context/contextManager.js';
import { TrackerService } from '../services/trackerService.js';
import type { GenerateContentParameters } from '@google/genai';
@@ -451,7 +451,7 @@ import {
DEFAULT_TOOL_PROTECTION_THRESHOLD,
DEFAULT_MIN_PRUNABLE_TOKENS_THRESHOLD,
DEFAULT_PROTECT_LATEST_TURN,
} from '../services/toolOutputMaskingService.js';
} from '../context/toolOutputMaskingService.js';
import {
type ExtensionLoader,
@@ -979,9 +979,29 @@ export class Config implements McpContext, AgentLoopContext {
networkAccess: false,
};
this._sandboxManager = createSandboxManager(this.sandbox, {
workspace: params.targetDir,
});
this.targetDir = path.resolve(params.targetDir);
this.folderTrust = params.folderTrust ?? false;
this.workspaceContext = new WorkspaceContext(this.targetDir, []);
this.pendingIncludeDirectories = params.includeDirectories ?? [];
this.debugMode = params.debugMode;
this.question = params.question;
this.worktreeSettings = params.worktreeSettings;
this._sandboxPolicyManager = new SandboxPolicyManager();
const initialApprovalMode =
params.approvalMode ??
params.policyEngineConfig?.approvalMode ??
'default';
this._sandboxManager = createSandboxManager(
this.sandbox,
{
workspace: this.targetDir,
includeDirectories: this.pendingIncludeDirectories,
policyManager: this._sandboxPolicyManager,
},
initialApprovalMode,
);
if (
!(this._sandboxManager instanceof NoopSandboxManager) &&
@@ -995,36 +1015,6 @@ export class Config implements McpContext, AgentLoopContext {
this.fileSystemService = new StandardFileSystemService();
}
this._sandboxPolicyManager = new SandboxPolicyManager();
const initialApprovalMode =
params.approvalMode ??
params.policyEngineConfig?.approvalMode ??
'default';
this._sandboxManager = createSandboxManager(
this.sandbox,
{
workspace: params.targetDir,
policyManager: this._sandboxPolicyManager,
},
initialApprovalMode,
);
if (
!(this._sandboxManager instanceof NoopSandboxManager) &&
this.sandbox?.enabled
) {
this.fileSystemService = new SandboxedFileSystemService(
this._sandboxManager,
params.targetDir,
);
} else {
this.fileSystemService = new StandardFileSystemService();
}
this.targetDir = path.resolve(params.targetDir);
this.folderTrust = params.folderTrust ?? false;
this.workspaceContext = new WorkspaceContext(this.targetDir, []);
this.pendingIncludeDirectories = params.includeDirectories ?? [];
this.debugMode = params.debugMode;
this.question = params.question;
this.worktreeSettings = params.worktreeSettings;
@@ -1151,7 +1141,7 @@ export class Config implements McpContext, AgentLoopContext {
modelConfigServiceConfig ?? DEFAULT_MODEL_CONFIGS,
);
this.experimentalJitContext = params.experimentalJitContext ?? true;
this.experimentalJitContext = params.experimentalJitContext ?? false;
this.experimentalMemoryManager = params.experimentalMemoryManager ?? false;
this.memoryBoundaryMarkers = params.memoryBoundaryMarkers ?? ['.git'];
this.contextManagement = {
@@ -3093,6 +3083,14 @@ export class Config implements McpContext, AgentLoopContext {
return useGemini3_1 && authType === AuthType.USE_GEMINI;
}
private isGemini31LaunchedForAuthType(authType?: AuthType): boolean {
return (
authType === AuthType.USE_GEMINI ||
authType === AuthType.USE_VERTEX_AI ||
authType === AuthType.GATEWAY
);
}
/**
* Returns whether Gemini 3.1 has been launched.
*
@@ -3102,10 +3100,7 @@ export class Config implements McpContext, AgentLoopContext {
*/
getGemini31LaunchedSync(): boolean {
const authType = this.contentGeneratorConfig?.authType;
if (
authType === AuthType.USE_GEMINI ||
authType === AuthType.USE_VERTEX_AI
) {
if (this.isGemini31LaunchedForAuthType(authType)) {
return true;
}
return (
@@ -3123,10 +3118,7 @@ export class Config implements McpContext, AgentLoopContext {
*/
getGemini31FlashLiteLaunchedSync(): boolean {
const authType = this.contentGeneratorConfig?.authType;
if (
authType === AuthType.USE_GEMINI ||
authType === AuthType.USE_VERTEX_AI
) {
if (this.isGemini31LaunchedForAuthType(authType)) {
return true;
}
return (
+10 -1
View File
@@ -12,6 +12,7 @@ import type {
} from '../tools/tools.js';
import type { ToolCall } from '../scheduler/types.js';
import type { SandboxPermissions } from '../services/sandboxManager.js';
import type { SubagentActivityItem } from '../agents/types.js';
export enum MessageBusType {
TOOL_CONFIRMATION_REQUEST = 'tool-confirmation-request',
@@ -23,6 +24,7 @@ export enum MessageBusType {
TOOL_CALLS_UPDATE = 'tool-calls-update',
ASK_USER_REQUEST = 'ask-user-request',
ASK_USER_RESPONSE = 'ask-user-response',
SUBAGENT_ACTIVITY = 'subagent-activity',
}
export interface ToolCallsUpdateMessage {
@@ -207,6 +209,12 @@ export interface AskUserResponse {
cancelled?: boolean;
}
export interface SubagentActivityMessage {
type: MessageBusType.SUBAGENT_ACTIVITY;
subagentName: string;
activity: SubagentActivityItem;
}
export type Message =
| ToolConfirmationRequest
| ToolConfirmationResponse
@@ -216,4 +224,5 @@ export type Message =
| UpdatePolicy
| AskUserRequest
| AskUserResponse
| ToolCallsUpdateMessage;
| ToolCallsUpdateMessage
| SubagentActivityMessage;
@@ -0,0 +1,31 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ToolOutputMaskingService > should match the expected snapshot for a masked tool output 1`] = `
"<tool_output_masked>
Line
Line
Line
Line
Line
Line
Line
Line
Line
Line
... [6 lines omitted] ...
Line
Line
Line
Line
Line
Line
Line
Line
Line
Output too large. Full output available at: /mock/temp/tool-outputs/session-mock-session/run_shell_command_deterministic.txt
</tool_output_masked>"
`;
@@ -17,12 +17,12 @@ vi.mock('../utils/tokenCalculation.js', () => ({
import type { Content, GenerateContentResponse, Part } from '@google/genai';
import type { Config, ContextManagementConfig } from '../config/config.js';
import type { BaseLlmClient } from '../core/baseLlmClient.js';
import type { AgentHistoryProviderConfig } from './types.js';
import type { AgentHistoryProviderConfig } from '../services/types.js';
import {
TEXT_TRUNCATION_PREFIX,
TOOL_TRUNCATION_PREFIX,
truncateProportionally,
} from 'src/utils/truncation.js';
} from './truncation.js';
describe('AgentHistoryProvider', () => {
let config: Config;
@@ -9,7 +9,7 @@ import { getResponseText } from '../utils/partUtils.js';
import { estimateTokenCountSync } from '../utils/tokenCalculation.js';
import { LlmRole } from '../telemetry/llmRole.js';
import { debugLogger } from '../utils/debugLogger.js';
import type { AgentHistoryProviderConfig } from './types.js';
import type { AgentHistoryProviderConfig } from '../services/types.js';
import type { Config } from '../config/config.js';
import {
MIN_TARGET_TOKENS,
@@ -18,7 +18,7 @@ import {
estimateCharsFromTokens,
truncateProportionally,
normalizeFunctionResponse,
} from '../utils/truncation.js';
} from './truncation.js';
export class AgentHistoryProvider {
// TODO(joshualitt): just pass the BaseLlmClient instead of the whole Config.
@@ -25,7 +25,7 @@ import {
MIN_TARGET_TOKENS,
estimateCharsFromTokens,
normalizeFunctionResponse,
} from '../utils/truncation.js';
} from './truncation.js';
// Skip structural map generation for outputs larger than this threshold (in characters)
// as it consumes excessive tokens and may not be representative of the full content.
@@ -9,7 +9,7 @@ import {
estimateTokenCountSync,
ASCII_TOKENS_PER_CHAR,
NON_ASCII_TOKENS_PER_CHAR,
} from './tokenCalculation.js';
} from '../utils/tokenCalculation.js';
export const MIN_TARGET_TOKENS = 10;
export const MIN_CHARS_FOR_TRUNCATION = 100;
+1 -1
View File
@@ -39,7 +39,7 @@ import { tokenLimit } from './tokenLimits.js';
import { ideContextStore } from '../ide/ideContext.js';
import type { ModelRouterService } from '../routing/modelRouterService.js';
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
import { ChatCompressionService } from '../services/chatCompressionService.js';
import { ChatCompressionService } from '../context/chatCompressionService.js';
import type { ChatRecordingService } from '../services/chatRecordingService.js';
import { createAvailabilityServiceMock } from '../availability/testUtils.js';
import type { ModelAvailabilityService } from '../availability/modelAvailabilityService.js';
+3 -3
View File
@@ -43,8 +43,8 @@ import type {
} from '../services/chatRecordingService.js';
import type { ContentGenerator } from './contentGenerator.js';
import { LoopDetectionService } from '../services/loopDetectionService.js';
import { ChatCompressionService } from '../services/chatCompressionService.js';
import { AgentHistoryProvider } from '../services/agentHistoryProvider.js';
import { ChatCompressionService } from '../context/chatCompressionService.js';
import { AgentHistoryProvider } from '../context/agentHistoryProvider.js';
import { ideContextStore } from '../ide/ideContext.js';
import {
logContentRetryFailure,
@@ -65,7 +65,7 @@ import { handleFallback } from '../fallback/handler.js';
import type { RoutingContext } from '../routing/routingStrategy.js';
import { debugLogger } from '../utils/debugLogger.js';
import type { ModelConfigKey } from '../services/modelConfigService.js';
import { ToolOutputMaskingService } from '../services/toolOutputMaskingService.js';
import { ToolOutputMaskingService } from '../context/toolOutputMaskingService.js';
import { calculateRequestTokenCount } from '../utils/tokenCalculation.js';
import {
applyModelSelection,
+1 -1
View File
@@ -134,7 +134,7 @@ export * from './services/fileSystemService.js';
export * from './services/sandboxedFileSystemService.js';
export * from './sandbox/windows/WindowsSandboxManager.js';
export * from './services/sessionSummaryUtils.js';
export * from './services/contextManager.js';
export * from './context/contextManager.js';
export * from './services/trackerService.js';
export * from './services/trackerTypes.js';
export * from './services/keychainService.js';

Some files were not shown because too many files have changed in this diff Show More