@vybestack/llxprt-code 0.10.0 → 0.11.0-nightly.260728.ffaa8d5d7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/commands/mcp/add.ts +8 -4
- package/src/config/cliArgParser.ts +2 -0
- package/src/config/config.ts +2 -0
- package/src/config/configBuilder.ts +3 -0
- package/src/config/configError.ts +23 -0
- package/src/config/extensions/extensionEnablement.ts +62 -1
- package/src/config/interactiveContext.ts +2 -1
- package/src/config/intermediateConfig.ts +4 -0
- package/src/config/keyBindings.ts +29 -3
- package/src/config/policy.ts +7 -1
- package/src/config/postConfigRuntime.ts +20 -14
- package/src/config/settings-schema/schema-ui.ts +9 -0
- package/src/config/settingsLoader.ts +21 -17
- package/src/config/settingsSchema.ts +2 -2
- package/src/config/trustedFolders.ts +275 -58
- package/src/config/yargsOptions.ts +7 -0
- package/src/generated/git-commit.json +1 -1
- package/src/nonInteractiveCli.ts +1 -0
- package/src/nonInteractiveCliSupport.ts +171 -51
- package/src/runtime/interactiveToolScheduler.ts +6 -6
- package/src/services/BuiltinCommandLoader.ts +33 -25
- package/src/services/FileCommandLoader.ts +34 -7
- package/src/test-utils/async.ts +20 -0
- package/src/test-utils/render.tsx +10 -1
- package/src/ui/App.tsx +17 -10
- package/src/ui/AppContainerRuntime.tsx +7 -9
- package/src/ui/cliUiRuntime.ts +28 -0
- package/src/ui/commands/authCommand.ts +240 -3
- package/src/ui/commands/compressCommand.ts +9 -1
- package/src/ui/commands/directoryCommand.tsx +1 -1
- package/src/ui/commands/logoutCommand.ts +2 -2
- package/src/ui/commands/permissionsCommand.ts +77 -20
- package/src/ui/commands/policiesCommand.ts +6 -1
- package/src/ui/commands/quotaCommand.ts +501 -0
- package/src/ui/commands/statsCommand.ts +2 -2
- package/src/ui/commands/statsQuota.ts +1 -1
- package/src/ui/commands/subagentCommand.ts +59 -27
- package/src/ui/components/AuthDialog.tsx +3 -3
- package/src/ui/components/Composer.tsx +1 -0
- package/src/ui/components/DialogManager.tsx +9 -2
- package/src/ui/components/FolderTrustDialog.tsx +71 -60
- package/src/ui/components/PermissionsModifyTrustDialog.tsx +211 -146
- package/src/ui/components/ProfileCreateWizard/constants.ts +13 -1
- package/src/ui/components/QueuedMessagesPanel.tsx +388 -0
- package/src/ui/components/inputPromptHooks.ts +2 -0
- package/src/ui/components/inputPromptKeyHandlers.ts +14 -0
- package/src/ui/components/inputPromptTypes.ts +6 -0
- package/src/ui/components/messages/AiMessage.tsx +24 -12
- package/src/ui/components/messages/ToolMessage.tsx +26 -40
- package/src/ui/containers/AppContainer/builders/buildUIActions.ts +3 -1
- package/src/ui/containers/AppContainer/builders/buildUIState.ts +5 -4
- package/src/ui/containers/AppContainer/hooks/useAppBootstrap.ts +3 -33
- package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +2 -14
- package/src/ui/containers/AppContainer/hooks/useAppInput.ts +31 -25
- package/src/ui/containers/AppContainer/hooks/useAppLayout.ts +90 -60
- package/src/ui/containers/AppContainer/hooks/useDisplayPreferences.ts +10 -0
- package/src/ui/containers/AppContainer/hooks/useInputHandling.ts +3 -17
- package/src/ui/containers/AppContainer/hooks/useKeybindings.ts +51 -38
- package/src/ui/containers/AppContainer/hooks/useSteer.ts +51 -0
- package/src/ui/contexts/KeypressContext.tsx +13 -2
- package/src/ui/contexts/ShellCommandDisplayContext.tsx +103 -0
- package/src/ui/contexts/TodoContext.tsx +0 -13
- package/src/ui/contexts/TodoProvider.tsx +1 -34
- package/src/ui/contexts/UIActionsContext.tsx +2 -1
- package/src/ui/contexts/UIStateContext.tsx +3 -2
- package/src/ui/hooks/agentStream/streamUtils.ts +0 -24
- package/src/ui/hooks/agentStream/thoughtState.ts +110 -30
- package/src/ui/hooks/agentStream/types.ts +1 -0
- package/src/ui/hooks/agentStream/useAgentEventStream.ts +8 -17
- package/src/ui/hooks/agentStream/useAgentStream.ts +2 -2
- package/src/ui/hooks/agentStream/useAgentStreamLifecycle.ts +4 -5
- package/src/ui/hooks/agentStream/useAgentStreamOrchestration.ts +12 -5
- package/src/ui/hooks/agentStream/useQueuedSubmissions.ts +99 -0
- package/src/ui/hooks/agentStream/useStreamEventHandlers.ts +4 -13
- package/src/ui/hooks/agentStream/useStreamState.ts +60 -41
- package/src/ui/hooks/agentStream/useSubmitQuery.ts +284 -59
- package/src/ui/hooks/slashCommandProcessorSupport.ts +4 -0
- package/src/ui/hooks/useAgentStream-test-helpers.ts +1 -0
- package/src/ui/hooks/useAutoAcceptIndicator.ts +13 -0
- package/src/ui/hooks/useFolderTrust.ts +118 -81
- package/src/ui/hooks/useGitBranchName.ts +79 -33
- package/src/ui/hooks/useIdeTrustListener.ts +5 -19
- package/src/ui/hooks/usePermissionsModifyTrust.ts +223 -85
- package/src/ui/hooks/useReactToolScheduler.ts +13 -17
- package/src/ui/hooks/useTodoPausePreserver.ts +0 -55
- package/src/ui/layouts/DefaultAppLayout.tsx +2 -0
- package/src/ui/layouts/DefaultAppLayoutHelpers.tsx +37 -162
- package/src/ui/layouts/InlineContent.tsx +198 -0
- package/src/ui/trustDialogHelpers.ts +190 -0
- package/src/utils/cleanup-state.ts +115 -0
- package/src/utils/cleanup.ts +32 -34
- package/src/utils/sandbox-containers.ts +183 -53
- package/src/utils/sandbox-exec.ts +9 -3
- package/src/zed-integration/acp-terminal-shell-host.ts +112 -0
- package/src/zed-integration/acp-types.ts +35 -0
- package/src/zed-integration/fileSystemService.ts +1 -1
- package/src/zed-integration/runZedIntegration.ts +142 -0
- package/src/zed-integration/zed-agent-event-handler.ts +179 -0
- package/src/zed-integration/zed-agent-setup.ts +43 -0
- package/src/zed-integration/zed-command-registry.ts +167 -0
- package/src/zed-integration/zed-config-options.ts +280 -0
- package/src/zed-integration/zed-content-utils.ts +2 -2
- package/src/zed-integration/zed-helpers.ts +132 -0
- package/src/zed-integration/zed-initialize.ts +64 -0
- package/src/zed-integration/zed-plan-update.ts +20 -0
- package/src/zed-integration/zed-prompt-command.ts +77 -0
- package/src/zed-integration/zed-session-config.ts +119 -0
- package/src/zed-integration/zed-session-errors.ts +337 -0
- package/src/zed-integration/zed-session-events.ts +216 -0
- package/src/zed-integration/zed-session-info.ts +255 -0
- package/src/zed-integration/zed-session-lifecycle.ts +186 -0
- package/src/zed-integration/zed-session-listing.ts +153 -0
- package/src/zed-integration/zed-session-loader.ts +193 -0
- package/src/zed-integration/zed-session-pagination.ts +240 -0
- package/src/zed-integration/zed-session-replay.ts +564 -0
- package/src/zed-integration/zed-stream-batcher.ts +236 -0
- package/src/zed-integration/zed-terminal-manager.ts +416 -0
- package/src/zed-integration/zed-terminal-setup.ts +72 -0
- package/src/zed-integration/zed-test-helpers.ts +349 -4
- package/src/zed-integration/zed-tool-handler.ts +47 -5
- package/src/zed-integration/zedIntegration.ts +538 -587
- package/src/services/todo-continuation/todoContinuationService.ts +0 -610
- package/src/ui/containers/AppContainer/hooks/useIdeRestartHotkey.ts +0 -33
- package/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts +0 -160
- package/src/ui/hooks/useTodoContinuation.ts +0 -299
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type * as acp from '@agentclientprotocol/sdk';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The pinned ACP SDK (0.14.1) does not yet export `CloseSessionRequest`,
|
|
11
|
+
* `DeleteSessionRequest`, or their responses, nor does its
|
|
12
|
+
* `ClientCapabilities` type include the `session` field used for
|
|
13
|
+
* config-option gating. These local type aliases keep the integration
|
|
14
|
+
* strongly-typed without resorting to `any`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface CloseSessionRequest {
|
|
18
|
+
readonly sessionId: acp.SessionId;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CloseSessionResponse = Record<string, never>;
|
|
22
|
+
|
|
23
|
+
export interface DeleteSessionRequest {
|
|
24
|
+
readonly sessionId: acp.SessionId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type DeleteSessionResponse = Record<string, never>;
|
|
28
|
+
|
|
29
|
+
export type ClientCapabilitiesWithSession = NonNullable<
|
|
30
|
+
acp.InitializeRequest['clientCapabilities']
|
|
31
|
+
> & {
|
|
32
|
+
readonly session?: {
|
|
33
|
+
readonly configOptions?: boolean;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -14,7 +14,7 @@ export class AcpFileSystemService implements FileSystemService {
|
|
|
14
14
|
constructor(
|
|
15
15
|
private readonly connection: acp.AgentSideConnection,
|
|
16
16
|
private readonly sessionId: string,
|
|
17
|
-
private readonly capabilities: acp.
|
|
17
|
+
private readonly capabilities: acp.FileSystemCapabilities,
|
|
18
18
|
private readonly fallback: FileSystemService,
|
|
19
19
|
) {}
|
|
20
20
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { type Config, createInkStdio } from '@vybestack/llxprt-code-core';
|
|
8
|
+
import { DebugLogger } from '@vybestack/llxprt-code-telemetry';
|
|
9
|
+
import * as acp from '@agentclientprotocol/sdk';
|
|
10
|
+
import { Readable, Writable } from 'node:stream';
|
|
11
|
+
import * as process from 'node:process';
|
|
12
|
+
import { setCliRuntimeContext } from '@vybestack/llxprt-code-providers/runtime.js';
|
|
13
|
+
import type { LoadedSettings } from '../config/settings.js';
|
|
14
|
+
import { runExitCleanup } from '../utils/cleanup.js';
|
|
15
|
+
import { ZedAgent } from './zedIntegration.js';
|
|
16
|
+
|
|
17
|
+
const DISPOSAL_SIGNALS: readonly NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Listener target for process-signal-driven disposal. Injectable so tests can
|
|
21
|
+
* drive the disposal path without sending real OS signals.
|
|
22
|
+
*/
|
|
23
|
+
export type SignalListenerTarget = {
|
|
24
|
+
on(signal: NodeJS.Signals, listener: () => void): unknown;
|
|
25
|
+
off(signal: NodeJS.Signals, listener: () => void): unknown;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Builds the disposal action performed when a disposal signal fires.
|
|
30
|
+
*
|
|
31
|
+
* The transport-ownership rule: {@link ndJsonStream} calls
|
|
32
|
+
* `input.getReader()` on the web stream, which **locks** it. Calling
|
|
33
|
+
* `.cancel()` on a locked `ReadableStream` rejects with `TypeError: Cannot
|
|
34
|
+
* cancel a readable stream that is locked`, so disposal silently no-ops and
|
|
35
|
+
* `connection.closed` never resolves — the process hangs.
|
|
36
|
+
*
|
|
37
|
+
* Instead, we destroy the **owned** Node.js `Readable` source that
|
|
38
|
+
* `Readable.toWeb` wraps. Destroying the source propagates an abort/EOF into
|
|
39
|
+
* the web stream's active reader, which the SDK's `ndJsonStream` read loop
|
|
40
|
+
* detects (either a stream-end `{ done: true }` chunk or an underlying error).
|
|
41
|
+
* That causes the SDK to call `controller.close()` / `controller.error()`,
|
|
42
|
+
* resolving `connection.closed` naturally so the `finally` cleanup runs.
|
|
43
|
+
*/
|
|
44
|
+
export function buildSignalDisposalHandler(
|
|
45
|
+
source: Readable,
|
|
46
|
+
logger: Pick<DebugLogger, 'debug'>,
|
|
47
|
+
): () => void {
|
|
48
|
+
return () => {
|
|
49
|
+
try {
|
|
50
|
+
// destroy() is idempotent and safe to call on an already-ended stream.
|
|
51
|
+
// It propagates to the web stream via Readable.toWeb's adapter, causing
|
|
52
|
+
// the SDK's locked reader to observe the end/error.
|
|
53
|
+
source.destroy();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.debug(
|
|
56
|
+
() => `Signal-driven transport destroy failed: ${String(error)}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Installs process-signal listeners that trigger graceful disposal. Returns a
|
|
64
|
+
* disposer that removes the listeners.
|
|
65
|
+
*
|
|
66
|
+
* Registers listeners so Node's default SIGINT/SIGTERM behavior (immediate
|
|
67
|
+
* process termination) is suppressed long enough for the `finally` cleanup in
|
|
68
|
+
* {@link runZedIntegration} to run.
|
|
69
|
+
*/
|
|
70
|
+
export function installDisposalSignalHandlers(
|
|
71
|
+
onSignal: () => void,
|
|
72
|
+
signals: readonly NodeJS.Signals[] = DISPOSAL_SIGNALS,
|
|
73
|
+
target: SignalListenerTarget = process,
|
|
74
|
+
): () => void {
|
|
75
|
+
for (const signal of signals) {
|
|
76
|
+
target.on(signal, onSignal);
|
|
77
|
+
}
|
|
78
|
+
return () => {
|
|
79
|
+
for (const signal of signals) {
|
|
80
|
+
target.off(signal, onSignal);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function cleanupAgents(
|
|
86
|
+
agents: readonly ZedAgent[],
|
|
87
|
+
logger: DebugLogger,
|
|
88
|
+
): Promise<void> {
|
|
89
|
+
const disposalResults = await Promise.allSettled(
|
|
90
|
+
agents.map((agent) => agent.disposeAll()),
|
|
91
|
+
);
|
|
92
|
+
const rejected = disposalResults.filter(
|
|
93
|
+
(result): result is PromiseRejectedResult => result.status === 'rejected',
|
|
94
|
+
);
|
|
95
|
+
for (const result of rejected) {
|
|
96
|
+
logger.warn(() => `Zed agent cleanup failed: ${String(result.reason)}`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await runExitCleanup();
|
|
100
|
+
} catch (cleanupError) {
|
|
101
|
+
logger.debug(() => `Exit cleanup failed: ${String(cleanupError)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function runZedIntegration(
|
|
106
|
+
config: Config,
|
|
107
|
+
settings: LoadedSettings,
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
const logger = new DebugLogger('llxprt:zed-integration');
|
|
110
|
+
logger.debug(() => 'Starting Zed integration');
|
|
111
|
+
const { stdout: workingStdout } = createInkStdio();
|
|
112
|
+
const stdout = Writable.toWeb(workingStdout) as WritableStream;
|
|
113
|
+
// Keep the owned Node.js Readable reference. Destroying this source (not the
|
|
114
|
+
// locked web stream) is the only way to make the ACP ndJsonStream reader
|
|
115
|
+
// observe EOF/abort so connection.closed settles.
|
|
116
|
+
const stdinSource = process.stdin;
|
|
117
|
+
const stdin = Readable.toWeb(stdinSource) as ReadableStream<Uint8Array>;
|
|
118
|
+
setCliRuntimeContext(config.getSettingsService(), config, {
|
|
119
|
+
runtimeId: 'cli.runtime.zed',
|
|
120
|
+
metadata: { source: 'zed-integration', stage: 'bootstrap' },
|
|
121
|
+
allowDefaultHandoff: true,
|
|
122
|
+
});
|
|
123
|
+
const agents: ZedAgent[] = [];
|
|
124
|
+
const removeSignalHandlers = installDisposalSignalHandlers(
|
|
125
|
+
buildSignalDisposalHandler(stdinSource, logger),
|
|
126
|
+
);
|
|
127
|
+
try {
|
|
128
|
+
const stream = acp.ndJsonStream(stdout, stdin);
|
|
129
|
+
const connection = new acp.AgentSideConnection((conn) => {
|
|
130
|
+
const agent = new ZedAgent(config, settings, conn);
|
|
131
|
+
agents.push(agent);
|
|
132
|
+
return agent;
|
|
133
|
+
}, stream);
|
|
134
|
+
await connection.closed;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.warn(() => `Zed agent connection error: ${error}`);
|
|
137
|
+
throw error;
|
|
138
|
+
} finally {
|
|
139
|
+
removeSignalHandlers();
|
|
140
|
+
await cleanupAgents(agents, logger);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type * as acp from '@agentclientprotocol/sdk';
|
|
8
|
+
import type { AgentEvent } from '@vybestack/llxprt-code-agents';
|
|
9
|
+
import { DebugLogger } from '@vybestack/llxprt-code-core';
|
|
10
|
+
import {
|
|
11
|
+
extractThoughtText,
|
|
12
|
+
mapDoneReasonToStopReason,
|
|
13
|
+
translateErrorEvent,
|
|
14
|
+
translateIdleTimeout,
|
|
15
|
+
} from './zed-helpers.js';
|
|
16
|
+
import type { StreamBatcher } from './zed-stream-batcher.js';
|
|
17
|
+
import {
|
|
18
|
+
emitToolCallStart,
|
|
19
|
+
emitToolResult,
|
|
20
|
+
emitToolStatus,
|
|
21
|
+
} from './zed-tool-handler.js';
|
|
22
|
+
|
|
23
|
+
const logger = new DebugLogger('llxprt:zed-integration:agent-events');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Flushes pending batched chunks without letting a flush failure replace the
|
|
27
|
+
* original event context, so an 'error' event is not swallowed and a 'done'
|
|
28
|
+
* stop reason is not dropped by a flush exception.
|
|
29
|
+
*/
|
|
30
|
+
async function safeFlush(batcher: StreamBatcher): Promise<void> {
|
|
31
|
+
try {
|
|
32
|
+
await batcher.flush();
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.debug(() => `Stream flush failed: ${String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface AgentEventHandlers {
|
|
39
|
+
sendUpdate(update: acp.SessionUpdate): Promise<void>;
|
|
40
|
+
sendUsage(
|
|
41
|
+
usage: Extract<AgentEvent, { type: 'usage' }>['usage'],
|
|
42
|
+
): Promise<void>;
|
|
43
|
+
handleConfirmation(
|
|
44
|
+
event: Extract<AgentEvent, { type: 'tool-confirmation' }>,
|
|
45
|
+
): Promise<void>;
|
|
46
|
+
resolveToolKind(toolName: string): string | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function handleZedAgentEvent(
|
|
50
|
+
event: AgentEvent,
|
|
51
|
+
batcher: StreamBatcher,
|
|
52
|
+
handlers: AgentEventHandlers,
|
|
53
|
+
): Promise<acp.StopReason | null> {
|
|
54
|
+
switch (event.type) {
|
|
55
|
+
case 'text':
|
|
56
|
+
batcher.append(event.text, false);
|
|
57
|
+
return null;
|
|
58
|
+
case 'thinking':
|
|
59
|
+
return handleThinking(event, batcher);
|
|
60
|
+
case 'tool-call':
|
|
61
|
+
return handleToolEvent(event, batcher, handlers);
|
|
62
|
+
case 'tool-status':
|
|
63
|
+
return handleToolUpdate(event, batcher, handlers);
|
|
64
|
+
case 'tool-result':
|
|
65
|
+
return handleToolResultEvent(event, batcher, handlers);
|
|
66
|
+
case 'tool-confirmation':
|
|
67
|
+
await safeFlush(batcher);
|
|
68
|
+
await handlers.handleConfirmation(event);
|
|
69
|
+
return null;
|
|
70
|
+
case 'done':
|
|
71
|
+
await safeFlush(batcher);
|
|
72
|
+
return mapDoneReasonToStopReason(event.reason);
|
|
73
|
+
case 'error':
|
|
74
|
+
await safeFlush(batcher);
|
|
75
|
+
throw translateErrorEvent(event);
|
|
76
|
+
case 'idle-timeout':
|
|
77
|
+
await safeFlush(batcher);
|
|
78
|
+
throw translateIdleTimeout(event);
|
|
79
|
+
case 'invalid-stream':
|
|
80
|
+
await safeFlush(batcher);
|
|
81
|
+
throw new Error(
|
|
82
|
+
'Agent produced an invalid stream that could not be recovered.',
|
|
83
|
+
);
|
|
84
|
+
case 'hook-blocked':
|
|
85
|
+
await safeFlush(batcher);
|
|
86
|
+
if (event.info.systemMessage !== undefined) {
|
|
87
|
+
await handlers.sendUpdate({
|
|
88
|
+
sessionUpdate: 'agent_message_chunk',
|
|
89
|
+
content: { type: 'text', text: event.info.systemMessage },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
case 'loop-detected':
|
|
94
|
+
await safeFlush(batcher);
|
|
95
|
+
return 'end_turn';
|
|
96
|
+
case 'notice':
|
|
97
|
+
return handleNotice(event, batcher, handlers);
|
|
98
|
+
case 'usage':
|
|
99
|
+
await safeFlush(batcher);
|
|
100
|
+
await handlers.sendUsage(event.usage);
|
|
101
|
+
return null;
|
|
102
|
+
case 'context-warning':
|
|
103
|
+
case 'compression':
|
|
104
|
+
case 'model-info':
|
|
105
|
+
case 'retry':
|
|
106
|
+
case 'citation':
|
|
107
|
+
return null;
|
|
108
|
+
default:
|
|
109
|
+
return assertNever(event);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleThinking(
|
|
114
|
+
event: Extract<AgentEvent, { type: 'thinking' }>,
|
|
115
|
+
batcher: StreamBatcher,
|
|
116
|
+
): null {
|
|
117
|
+
const thoughtText = extractThoughtText(event.thought);
|
|
118
|
+
if (thoughtText.length > 0) batcher.append(thoughtText, true);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function handleToolEvent(
|
|
123
|
+
event: Extract<AgentEvent, { type: 'tool-call' }>,
|
|
124
|
+
batcher: StreamBatcher,
|
|
125
|
+
handlers: AgentEventHandlers,
|
|
126
|
+
): Promise<null> {
|
|
127
|
+
await safeFlush(batcher);
|
|
128
|
+
await emitToolCallStart(
|
|
129
|
+
event.call,
|
|
130
|
+
handlers.sendUpdate,
|
|
131
|
+
handlers.resolveToolKind(event.call.name),
|
|
132
|
+
);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function handleToolUpdate(
|
|
137
|
+
event: Extract<AgentEvent, { type: 'tool-status' }>,
|
|
138
|
+
batcher: StreamBatcher,
|
|
139
|
+
handlers: AgentEventHandlers,
|
|
140
|
+
): Promise<null> {
|
|
141
|
+
await safeFlush(batcher);
|
|
142
|
+
await emitToolStatus(
|
|
143
|
+
event.update,
|
|
144
|
+
handlers.sendUpdate,
|
|
145
|
+
handlers.resolveToolKind(event.update.name),
|
|
146
|
+
);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function handleToolResultEvent(
|
|
151
|
+
event: Extract<AgentEvent, { type: 'tool-result' }>,
|
|
152
|
+
batcher: StreamBatcher,
|
|
153
|
+
handlers: AgentEventHandlers,
|
|
154
|
+
): Promise<null> {
|
|
155
|
+
await safeFlush(batcher);
|
|
156
|
+
await emitToolResult(
|
|
157
|
+
event.result,
|
|
158
|
+
handlers.sendUpdate,
|
|
159
|
+
handlers.resolveToolKind(event.result.name),
|
|
160
|
+
);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function handleNotice(
|
|
165
|
+
event: Extract<AgentEvent, { type: 'notice' }>,
|
|
166
|
+
batcher: StreamBatcher,
|
|
167
|
+
handlers: AgentEventHandlers,
|
|
168
|
+
): Promise<null> {
|
|
169
|
+
await safeFlush(batcher);
|
|
170
|
+
await handlers.sendUpdate({
|
|
171
|
+
sessionUpdate: 'agent_message_chunk',
|
|
172
|
+
content: { type: 'text', text: event.message },
|
|
173
|
+
});
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function assertNever(event: never): never {
|
|
178
|
+
throw new Error(`Unhandled agent event: ${JSON.stringify(event)}`);
|
|
179
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Agent } from '@vybestack/llxprt-code-agents';
|
|
8
|
+
|
|
9
|
+
export async function enableZedSessionRecording(
|
|
10
|
+
agent: Agent,
|
|
11
|
+
onFailure: (error: unknown) => void,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
try {
|
|
14
|
+
await agent.session.setRecording({ enabled: true });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
try {
|
|
17
|
+
onFailure(error);
|
|
18
|
+
} catch {
|
|
19
|
+
// Recording remains best-effort even when failure notification fails.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function buildZedSession<T>(
|
|
25
|
+
agent: Agent,
|
|
26
|
+
build: () => T | Promise<T>,
|
|
27
|
+
onDisposeFailure: (error: unknown) => void,
|
|
28
|
+
): Promise<T> {
|
|
29
|
+
try {
|
|
30
|
+
return await build();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
try {
|
|
33
|
+
await agent.dispose();
|
|
34
|
+
} catch (disposeError) {
|
|
35
|
+
try {
|
|
36
|
+
onDisposeFailure(disposeError);
|
|
37
|
+
} catch {
|
|
38
|
+
// Preserve the original session build failure.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type * as acp from '@agentclientprotocol/sdk';
|
|
8
|
+
import type { Agent } from '@vybestack/llxprt-code-agents';
|
|
9
|
+
import { DebugLogger } from '@vybestack/llxprt-code-core';
|
|
10
|
+
|
|
11
|
+
const logger = new DebugLogger('llxprt:zed-integration:commands');
|
|
12
|
+
|
|
13
|
+
export interface ZedCommandContext {
|
|
14
|
+
readonly agent: Agent;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ZedCommandResult {
|
|
18
|
+
readonly text: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface ZedCommandDefinition {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly description: string;
|
|
24
|
+
readonly inputHint?: string;
|
|
25
|
+
readonly handler: (
|
|
26
|
+
context: ZedCommandContext,
|
|
27
|
+
args: string,
|
|
28
|
+
) => Promise<ZedCommandResult>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parseZedCommandPrompt(
|
|
32
|
+
prompt: string,
|
|
33
|
+
): { readonly name: string; readonly args: string } | null {
|
|
34
|
+
if (!prompt.startsWith('/')) return null;
|
|
35
|
+
const value = prompt.slice(1).trimStart();
|
|
36
|
+
if (value.length === 0) return null;
|
|
37
|
+
const boundary = value.search(/\s/);
|
|
38
|
+
return boundary === -1
|
|
39
|
+
? { name: value.toLowerCase(), args: '' }
|
|
40
|
+
: {
|
|
41
|
+
name: value.slice(0, boundary).toLowerCase(),
|
|
42
|
+
args: value.slice(boundary + 1).trim(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function formatCompression(
|
|
47
|
+
result: Awaited<ReturnType<Agent['compress']>>,
|
|
48
|
+
): string {
|
|
49
|
+
const counts =
|
|
50
|
+
result.originalTokenCount === undefined ||
|
|
51
|
+
result.newTokenCount === undefined
|
|
52
|
+
? ''
|
|
53
|
+
: ` (${result.originalTokenCount} → ${result.newTokenCount} tokens)`;
|
|
54
|
+
return `Context compression ${result.status}${counts}.`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const COMMANDS: readonly ZedCommandDefinition[] = [
|
|
58
|
+
{
|
|
59
|
+
name: 'compact',
|
|
60
|
+
description: 'Compress conversation history to free context space',
|
|
61
|
+
handler: async ({ agent }) => ({
|
|
62
|
+
text: formatCompression(await agent.compress()),
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'tools',
|
|
67
|
+
description: 'List available tools and their enabled status',
|
|
68
|
+
handler: async ({ agent }) => {
|
|
69
|
+
const tools = agent.tools.list();
|
|
70
|
+
return {
|
|
71
|
+
text:
|
|
72
|
+
tools.length === 0
|
|
73
|
+
? 'No tools available.'
|
|
74
|
+
: `Available tools:\n${tools
|
|
75
|
+
.map((tool) => ` ${tool.enabled ? '[x]' : '[ ]'} ${tool.name}`)
|
|
76
|
+
.join('\n')}`,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'memory',
|
|
82
|
+
description: 'Show memory files currently in use',
|
|
83
|
+
handler: async ({ agent }) => {
|
|
84
|
+
const paths = agent.memory.getFilePaths();
|
|
85
|
+
return {
|
|
86
|
+
text:
|
|
87
|
+
paths.length === 0
|
|
88
|
+
? 'No memory files in use.'
|
|
89
|
+
: `Memory files:\n${paths.map((path) => ` ${path}`).join('\n')}`,
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'profile',
|
|
95
|
+
description: 'List saved profiles and identify the startup default',
|
|
96
|
+
handler: async ({ agent }) => {
|
|
97
|
+
const profiles = agent.profiles.list();
|
|
98
|
+
return {
|
|
99
|
+
text:
|
|
100
|
+
profiles.length === 0
|
|
101
|
+
? 'No profiles available.'
|
|
102
|
+
: `Profiles:\n${profiles
|
|
103
|
+
.map(
|
|
104
|
+
(profile) =>
|
|
105
|
+
` ${profile.name}${profile.isDefault ? ' (default)' : ''}`,
|
|
106
|
+
)
|
|
107
|
+
.join('\n')}`,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'model',
|
|
113
|
+
description: 'Show the current model',
|
|
114
|
+
handler: async ({ agent }) => ({
|
|
115
|
+
text: `Current model: ${agent.getModel()}`,
|
|
116
|
+
}),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'task',
|
|
120
|
+
description: 'Show active subagent tasks',
|
|
121
|
+
handler: async ({ agent }) => {
|
|
122
|
+
const tasks = agent.tasks.list();
|
|
123
|
+
return {
|
|
124
|
+
text:
|
|
125
|
+
tasks.length === 0
|
|
126
|
+
? 'No active subagent tasks.'
|
|
127
|
+
: `Subagent tasks:\n${tasks
|
|
128
|
+
.map((task) => ` [${task.status}] ${task.goalPrompt}`)
|
|
129
|
+
.join('\n')}`,
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const COMMAND_MAP = new Map(COMMANDS.map((command) => [command.name, command]));
|
|
136
|
+
|
|
137
|
+
export function getZedAvailableCommands(): acp.AvailableCommand[] {
|
|
138
|
+
return COMMANDS.map(({ name, description, inputHint }) => ({
|
|
139
|
+
name,
|
|
140
|
+
description,
|
|
141
|
+
...(inputHint === undefined ? {} : { input: { hint: inputHint } }),
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function buildAvailableCommandsUpdate(): acp.SessionUpdate {
|
|
146
|
+
return {
|
|
147
|
+
sessionUpdate: 'available_commands_update',
|
|
148
|
+
availableCommands: getZedAvailableCommands(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function executeZedCommand(
|
|
153
|
+
prompt: string,
|
|
154
|
+
context: ZedCommandContext,
|
|
155
|
+
): Promise<ZedCommandResult | null> {
|
|
156
|
+
const parsed = parseZedCommandPrompt(prompt);
|
|
157
|
+
if (parsed === null) return null;
|
|
158
|
+
const command = COMMAND_MAP.get(parsed.name);
|
|
159
|
+
if (command === undefined) return null;
|
|
160
|
+
try {
|
|
161
|
+
return await command.handler(context, parsed.args);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
164
|
+
logger.error(() => `Command /${command.name} failed: ${detail}`);
|
|
165
|
+
return { text: `Command /${command.name} failed. See logs for details.` };
|
|
166
|
+
}
|
|
167
|
+
}
|