@vybestack/llxprt-code-core 0.1.14 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -0
- package/dist/src/code_assist/oauth2.test.d.ts +1 -1
- package/dist/src/code_assist/oauth2.test.js +15 -15
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/config/config.d.ts +15 -1
- package/dist/src/config/config.js +27 -5
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +5 -0
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/flashFallback.test.js +12 -46
- package/dist/src/config/flashFallback.test.js.map +1 -1
- package/dist/src/config/models.d.ts +1 -0
- package/dist/src/config/models.js +1 -0
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/core/client.d.ts +4 -0
- package/dist/src/core/client.js +75 -15
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.d.ts +1 -1
- package/dist/src/core/client.test.js +204 -22
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.js +2 -7
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +6 -2
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +1 -3
- package/dist/src/core/coreToolScheduler.js +9 -4
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +76 -1
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.js +1 -0
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/googleGenAIWrapper.d.ts +21 -0
- package/dist/src/core/googleGenAIWrapper.js +36 -0
- package/dist/src/core/googleGenAIWrapper.js.map +1 -0
- package/dist/src/core/googleGenAIWrapper.test.d.ts +6 -0
- package/dist/src/core/googleGenAIWrapper.test.js +104 -0
- package/dist/src/core/googleGenAIWrapper.test.js.map +1 -0
- package/dist/src/core/logger.d.ts +1 -0
- package/dist/src/core/logger.js +22 -8
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/logger.test.js +60 -9
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.js +8 -1
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +3 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.js +1 -1
- package/dist/src/core/tokenLimits.js +1 -0
- package/dist/src/core/tokenLimits.js.map +1 -1
- package/dist/src/core/turn.d.ts +2 -1
- package/dist/src/core/turn.js +4 -2
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +2 -2
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +8 -6
- package/dist/src/ide/ide-client.js +73 -36
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +212 -107
- package/dist/src/ide/ideContext.js +45 -44
- package/dist/src/ide/ideContext.js.map +1 -1
- package/dist/src/ide/ideContext.test.js +254 -100
- package/dist/src/ide/ideContext.test.js.map +1 -1
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/integration-tests/todo-system.test.d.ts +6 -0
- package/dist/src/integration-tests/todo-system.test.js +610 -0
- package/dist/src/integration-tests/todo-system.test.js.map +1 -0
- package/dist/src/mcp/oauth-provider.d.ts +5 -1
- package/dist/src/mcp/oauth-provider.js +36 -11
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +2 -2
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +3 -1
- package/dist/src/mcp/oauth-token-storage.js +3 -1
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-utils.js +2 -2
- package/dist/src/mcp/oauth-utils.js.map +1 -1
- package/dist/src/mcp/oauth-utils.test.js +1 -1
- package/dist/src/mcp/oauth-utils.test.js.map +1 -1
- package/dist/src/prompts/mcp-prompts.d.ts +8 -0
- package/dist/src/prompts/mcp-prompts.js +13 -0
- package/dist/src/prompts/mcp-prompts.js.map +1 -0
- package/dist/src/prompts/prompt-registry.d.ts +26 -0
- package/dist/src/prompts/prompt-registry.js +47 -0
- package/dist/src/prompts/prompt-registry.js.map +1 -0
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.js +24 -9
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.js.map +1 -1
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js +1 -1
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js.map +1 -1
- package/dist/src/providers/gemini/GeminiProvider.js +8 -16
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
- package/dist/src/providers/openai/OpenAIProvider.js +54 -6
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/services/complexity-analyzer.d.ts +92 -0
- package/dist/src/services/complexity-analyzer.js +287 -0
- package/dist/src/services/complexity-analyzer.js.map +1 -0
- package/dist/src/services/fileDiscoveryService.test.js +101 -60
- package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
- package/dist/src/services/gitService.test.d.ts +1 -1
- package/dist/src/services/gitService.test.js +65 -102
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/index.d.ts +9 -0
- package/dist/src/services/index.js +8 -0
- package/dist/src/services/index.js.map +1 -0
- package/dist/src/services/loopDetectionService.d.ts +3 -0
- package/dist/src/services/loopDetectionService.js +18 -0
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +90 -11
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +70 -0
- package/dist/src/services/shellExecutionService.js +152 -0
- package/dist/src/services/shellExecutionService.js.map +1 -0
- package/dist/src/services/shellExecutionService.test.d.ts +6 -0
- package/dist/src/services/shellExecutionService.test.js +258 -0
- package/dist/src/services/shellExecutionService.test.js.map +1 -0
- package/dist/src/services/todo-reminder-service.d.ts +42 -0
- package/dist/src/services/todo-reminder-service.js +77 -0
- package/dist/src/services/todo-reminder-service.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +2 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +59 -31
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +3 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +7 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +1 -0
- package/dist/src/telemetry/constants.js +1 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +2 -2
- package/dist/src/telemetry/index.js +2 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +2 -1
- package/dist/src/telemetry/loggers.js +18 -1
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +5 -0
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +8 -1
- package/dist/src/telemetry/types.js +12 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/tools/edit.js +2 -1
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/glob.test.js +7 -4
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/grep.test.js +4 -4
- package/dist/src/tools/grep.test.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +26 -2
- package/dist/src/tools/mcp-client.js +64 -21
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +53 -1
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/modifiable-tool.test.js +51 -62
- package/dist/src/tools/modifiable-tool.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +8 -3
- package/dist/src/tools/shell.js +169 -114
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.d.ts +1 -1
- package/dist/src/tools/shell.test.js +298 -145
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/todo-read.d.ts +4 -1
- package/dist/src/tools/todo-read.js +136 -20
- package/dist/src/tools/todo-read.js.map +1 -1
- package/dist/src/tools/todo-read.test.js +10 -6
- package/dist/src/tools/todo-read.test.js.map +1 -1
- package/dist/src/tools/todo-schemas.d.ts +4 -4
- package/dist/src/tools/todo-write.d.ts +3 -0
- package/dist/src/tools/todo-write.js +96 -9
- package/dist/src/tools/todo-write.js.map +1 -1
- package/dist/src/tools/todo-write.test.js +17 -13
- package/dist/src/tools/todo-write.test.js.map +1 -1
- package/dist/src/tools/tool-context.d.ts +14 -0
- package/dist/src/tools/tool-context.js +7 -0
- package/dist/src/tools/tool-context.js.map +1 -0
- package/dist/src/tools/tool-registry.d.ts +4 -1
- package/dist/src/tools/tool-registry.js +14 -5
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +2 -2
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +8 -1
- package/dist/src/tools/tools.js +2 -0
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.js +18 -4
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.js +51 -27
- package/dist/src/utils/bfsFileSearch.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.test.js +57 -0
- package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
- package/dist/src/utils/formatters.d.ts +6 -0
- package/dist/src/utils/formatters.js +16 -0
- package/dist/src/utils/formatters.js.map +1 -0
- package/dist/src/utils/nextSpeakerChecker.js +2 -2
- package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.test.js +2 -2
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/retry.d.ts +3 -0
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +37 -3
- package/dist/src/utils/shell-utils.js +110 -47
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +146 -396
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/textUtils.d.ts +13 -0
- package/dist/src/utils/textUtils.js +28 -0
- package/dist/src/utils/textUtils.js.map +1 -0
- package/package.json +2 -2
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/** A structured result from a shell command execution. */
|
|
7
|
+
export interface ShellExecutionResult {
|
|
8
|
+
/** The raw, unprocessed output buffer. */
|
|
9
|
+
rawOutput: Buffer;
|
|
10
|
+
/** The combined, decoded stdout and stderr as a string. */
|
|
11
|
+
output: string;
|
|
12
|
+
/** The decoded stdout as a string. */
|
|
13
|
+
stdout: string;
|
|
14
|
+
/** The decoded stderr as a string. */
|
|
15
|
+
stderr: string;
|
|
16
|
+
/** The process exit code, or null if terminated by a signal. */
|
|
17
|
+
exitCode: number | null;
|
|
18
|
+
/** The signal that terminated the process, if any. */
|
|
19
|
+
signal: NodeJS.Signals | null;
|
|
20
|
+
/** An error object if the process failed to spawn. */
|
|
21
|
+
error: Error | null;
|
|
22
|
+
/** A boolean indicating if the command was aborted by the user. */
|
|
23
|
+
aborted: boolean;
|
|
24
|
+
/** The process ID of the spawned shell. */
|
|
25
|
+
pid: number | undefined;
|
|
26
|
+
}
|
|
27
|
+
/** A handle for an ongoing shell execution. */
|
|
28
|
+
export interface ShellExecutionHandle {
|
|
29
|
+
/** The process ID of the spawned shell. */
|
|
30
|
+
pid: number | undefined;
|
|
31
|
+
/** A promise that resolves with the complete execution result. */
|
|
32
|
+
result: Promise<ShellExecutionResult>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Describes a structured event emitted during shell command execution.
|
|
36
|
+
*/
|
|
37
|
+
export type ShellOutputEvent = {
|
|
38
|
+
/** The event contains a chunk of output data. */
|
|
39
|
+
type: 'data';
|
|
40
|
+
/** The stream from which the data originated. */
|
|
41
|
+
stream: 'stdout' | 'stderr';
|
|
42
|
+
/** The decoded string chunk. */
|
|
43
|
+
chunk: string;
|
|
44
|
+
} | {
|
|
45
|
+
/** Signals that the output stream has been identified as binary. */
|
|
46
|
+
type: 'binary_detected';
|
|
47
|
+
} | {
|
|
48
|
+
/** Provides progress updates for a binary stream. */
|
|
49
|
+
type: 'binary_progress';
|
|
50
|
+
/** The total number of bytes received so far. */
|
|
51
|
+
bytesReceived: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* A centralized service for executing shell commands with robust process
|
|
55
|
+
* management, cross-platform compatibility, and streaming output capabilities.
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
export declare class ShellExecutionService {
|
|
59
|
+
/**
|
|
60
|
+
* Executes a shell command using `spawn`, capturing all output and lifecycle events.
|
|
61
|
+
*
|
|
62
|
+
* @param commandToExecute The exact command string to run.
|
|
63
|
+
* @param cwd The working directory to execute the command in.
|
|
64
|
+
* @param onOutputEvent A callback for streaming structured events about the execution, including data chunks and status updates.
|
|
65
|
+
* @param abortSignal An AbortSignal to terminate the process and its children.
|
|
66
|
+
* @returns An object containing the process ID (pid) and a promise that
|
|
67
|
+
* resolves with the complete execution result.
|
|
68
|
+
*/
|
|
69
|
+
static execute(commandToExecute: string, cwd: string, onOutputEvent: (event: ShellOutputEvent) => void, abortSignal: AbortSignal): ShellExecutionHandle;
|
|
70
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import { TextDecoder } from 'util';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import stripAnsi from 'strip-ansi';
|
|
10
|
+
import { getCachedEncodingForBuffer } from '../utils/systemEncoding.js';
|
|
11
|
+
import { isBinary } from '../utils/textUtils.js';
|
|
12
|
+
const SIGKILL_TIMEOUT_MS = 200;
|
|
13
|
+
/**
|
|
14
|
+
* A centralized service for executing shell commands with robust process
|
|
15
|
+
* management, cross-platform compatibility, and streaming output capabilities.
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
export class ShellExecutionService {
|
|
19
|
+
/**
|
|
20
|
+
* Executes a shell command using `spawn`, capturing all output and lifecycle events.
|
|
21
|
+
*
|
|
22
|
+
* @param commandToExecute The exact command string to run.
|
|
23
|
+
* @param cwd The working directory to execute the command in.
|
|
24
|
+
* @param onOutputEvent A callback for streaming structured events about the execution, including data chunks and status updates.
|
|
25
|
+
* @param abortSignal An AbortSignal to terminate the process and its children.
|
|
26
|
+
* @returns An object containing the process ID (pid) and a promise that
|
|
27
|
+
* resolves with the complete execution result.
|
|
28
|
+
*/
|
|
29
|
+
static execute(commandToExecute, cwd, onOutputEvent, abortSignal) {
|
|
30
|
+
const isWindows = os.platform() === 'win32';
|
|
31
|
+
const shell = isWindows ? 'cmd.exe' : 'bash';
|
|
32
|
+
const shellArgs = [isWindows ? '/c' : '-c', commandToExecute];
|
|
33
|
+
const child = spawn(shell, shellArgs, {
|
|
34
|
+
cwd,
|
|
35
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
36
|
+
detached: !isWindows, // Use process groups on non-Windows for robust killing
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
LLXPRT_CLI: '1',
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const result = new Promise((resolve) => {
|
|
43
|
+
// Use decoders to handle multi-byte characters safely (for streaming output).
|
|
44
|
+
let stdoutDecoder = null;
|
|
45
|
+
let stderrDecoder = null;
|
|
46
|
+
let stdout = '';
|
|
47
|
+
let stderr = '';
|
|
48
|
+
const outputChunks = [];
|
|
49
|
+
let error = null;
|
|
50
|
+
let exited = false;
|
|
51
|
+
let isStreamingRawContent = true;
|
|
52
|
+
const MAX_SNIFF_SIZE = 4096;
|
|
53
|
+
let sniffedBytes = 0;
|
|
54
|
+
const handleOutput = (data, stream) => {
|
|
55
|
+
if (!stdoutDecoder || !stderrDecoder) {
|
|
56
|
+
const encoding = getCachedEncodingForBuffer(data);
|
|
57
|
+
try {
|
|
58
|
+
stdoutDecoder = new TextDecoder(encoding);
|
|
59
|
+
stderrDecoder = new TextDecoder(encoding);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// If the encoding is not supported, fall back to utf-8.
|
|
63
|
+
// This can happen on some platforms for certain encodings like 'utf-32le'.
|
|
64
|
+
stdoutDecoder = new TextDecoder('utf-8');
|
|
65
|
+
stderrDecoder = new TextDecoder('utf-8');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
outputChunks.push(data);
|
|
69
|
+
// Binary detection logic. This only runs until we've made a determination.
|
|
70
|
+
if (isStreamingRawContent && sniffedBytes < MAX_SNIFF_SIZE) {
|
|
71
|
+
const sniffBuffer = Buffer.concat(outputChunks.slice(0, 20));
|
|
72
|
+
sniffedBytes = sniffBuffer.length;
|
|
73
|
+
if (isBinary(sniffBuffer)) {
|
|
74
|
+
// Change state to stop streaming raw content.
|
|
75
|
+
isStreamingRawContent = false;
|
|
76
|
+
onOutputEvent({ type: 'binary_detected' });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const decodedChunk = stream === 'stdout'
|
|
80
|
+
? stdoutDecoder.decode(data, { stream: true })
|
|
81
|
+
: stderrDecoder.decode(data, { stream: true });
|
|
82
|
+
const strippedChunk = stripAnsi(decodedChunk);
|
|
83
|
+
if (stream === 'stdout') {
|
|
84
|
+
stdout += strippedChunk;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
stderr += strippedChunk;
|
|
88
|
+
}
|
|
89
|
+
if (isStreamingRawContent) {
|
|
90
|
+
onOutputEvent({ type: 'data', stream, chunk: strippedChunk });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const totalBytes = outputChunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
94
|
+
onOutputEvent({ type: 'binary_progress', bytesReceived: totalBytes });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
child.stdout.on('data', (data) => handleOutput(data, 'stdout'));
|
|
98
|
+
child.stderr.on('data', (data) => handleOutput(data, 'stderr'));
|
|
99
|
+
child.on('error', (err) => {
|
|
100
|
+
error = err;
|
|
101
|
+
});
|
|
102
|
+
const abortHandler = async () => {
|
|
103
|
+
if (child.pid && !exited) {
|
|
104
|
+
if (isWindows) {
|
|
105
|
+
spawn('taskkill', ['/pid', child.pid.toString(), '/f', '/t']);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
try {
|
|
109
|
+
// Kill the entire process group (negative PID).
|
|
110
|
+
// SIGTERM first, then SIGKILL if it doesn't die.
|
|
111
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
112
|
+
await new Promise((res) => setTimeout(res, SIGKILL_TIMEOUT_MS));
|
|
113
|
+
if (!exited) {
|
|
114
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (_e) {
|
|
118
|
+
// Fall back to killing just the main process if group kill fails.
|
|
119
|
+
if (!exited)
|
|
120
|
+
child.kill('SIGKILL');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
abortSignal.addEventListener('abort', abortHandler, { once: true });
|
|
126
|
+
child.on('exit', (code, signal) => {
|
|
127
|
+
exited = true;
|
|
128
|
+
abortSignal.removeEventListener('abort', abortHandler);
|
|
129
|
+
if (stdoutDecoder) {
|
|
130
|
+
stdout += stripAnsi(stdoutDecoder.decode());
|
|
131
|
+
}
|
|
132
|
+
if (stderrDecoder) {
|
|
133
|
+
stderr += stripAnsi(stderrDecoder.decode());
|
|
134
|
+
}
|
|
135
|
+
const finalBuffer = Buffer.concat(outputChunks);
|
|
136
|
+
resolve({
|
|
137
|
+
rawOutput: finalBuffer,
|
|
138
|
+
output: stdout + (stderr ? `\n${stderr}` : ''),
|
|
139
|
+
stdout,
|
|
140
|
+
stderr,
|
|
141
|
+
exitCode: code,
|
|
142
|
+
signal,
|
|
143
|
+
error,
|
|
144
|
+
aborted: abortSignal.aborted,
|
|
145
|
+
pid: child.pid,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
return { pid: child.pid, result };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=shellExecutionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellExecutionService.js","sourceRoot":"","sources":["../../../src/services/shellExecutionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
7
|
+
const mockSpawn = vi.hoisted(() => vi.fn());
|
|
8
|
+
vi.mock('child_process', () => ({
|
|
9
|
+
spawn: mockSpawn,
|
|
10
|
+
}));
|
|
11
|
+
import EventEmitter from 'events';
|
|
12
|
+
import { ShellExecutionService, } from './shellExecutionService.js';
|
|
13
|
+
const mockIsBinary = vi.hoisted(() => vi.fn());
|
|
14
|
+
vi.mock('../utils/textUtils.js', () => ({
|
|
15
|
+
isBinary: mockIsBinary,
|
|
16
|
+
}));
|
|
17
|
+
const mockPlatform = vi.hoisted(() => vi.fn());
|
|
18
|
+
vi.mock('os', () => ({
|
|
19
|
+
default: {
|
|
20
|
+
platform: mockPlatform,
|
|
21
|
+
},
|
|
22
|
+
platform: mockPlatform,
|
|
23
|
+
}));
|
|
24
|
+
const mockProcessKill = vi
|
|
25
|
+
.spyOn(process, 'kill')
|
|
26
|
+
.mockImplementation(() => true);
|
|
27
|
+
describe('ShellExecutionService', () => {
|
|
28
|
+
let mockChildProcess;
|
|
29
|
+
let onOutputEventMock;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
mockIsBinary.mockReturnValue(false);
|
|
33
|
+
mockPlatform.mockReturnValue('linux');
|
|
34
|
+
onOutputEventMock = vi.fn();
|
|
35
|
+
mockChildProcess = new EventEmitter();
|
|
36
|
+
// FIX: Cast simple EventEmitters to the expected stream type.
|
|
37
|
+
mockChildProcess.stdout = new EventEmitter();
|
|
38
|
+
mockChildProcess.stderr = new EventEmitter();
|
|
39
|
+
mockChildProcess.kill = vi.fn();
|
|
40
|
+
// FIX: Use Object.defineProperty to set the readonly 'pid' property.
|
|
41
|
+
Object.defineProperty(mockChildProcess, 'pid', {
|
|
42
|
+
value: 12345,
|
|
43
|
+
configurable: true,
|
|
44
|
+
});
|
|
45
|
+
mockSpawn.mockReturnValue(mockChildProcess);
|
|
46
|
+
});
|
|
47
|
+
// Helper function to run a standard execution simulation
|
|
48
|
+
const simulateExecution = async (command, simulation) => {
|
|
49
|
+
const abortController = new AbortController();
|
|
50
|
+
const handle = ShellExecutionService.execute(command, '/test/dir', onOutputEventMock, abortController.signal);
|
|
51
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
52
|
+
simulation(mockChildProcess, abortController);
|
|
53
|
+
const result = await handle.result;
|
|
54
|
+
return { result, handle, abortController };
|
|
55
|
+
};
|
|
56
|
+
describe('Successful Execution', () => {
|
|
57
|
+
it('should execute a command and capture stdout and stderr', async () => {
|
|
58
|
+
const { result, handle } = await simulateExecution('ls -l', (cp) => {
|
|
59
|
+
cp.stdout?.emit('data', Buffer.from('file1.txt\n'));
|
|
60
|
+
cp.stderr?.emit('data', Buffer.from('a warning'));
|
|
61
|
+
cp.emit('exit', 0, null);
|
|
62
|
+
});
|
|
63
|
+
expect(mockSpawn).toHaveBeenCalledWith('bash', ['-c', 'ls -l'], expect.any(Object));
|
|
64
|
+
expect(result.exitCode).toBe(0);
|
|
65
|
+
expect(result.signal).toBeNull();
|
|
66
|
+
expect(result.error).toBeNull();
|
|
67
|
+
expect(result.aborted).toBe(false);
|
|
68
|
+
expect(result.stdout).toBe('file1.txt\n');
|
|
69
|
+
expect(result.stderr).toBe('a warning');
|
|
70
|
+
expect(result.output).toBe('file1.txt\n\na warning');
|
|
71
|
+
expect(handle.pid).toBe(12345);
|
|
72
|
+
expect(onOutputEventMock).toHaveBeenCalledWith({
|
|
73
|
+
type: 'data',
|
|
74
|
+
stream: 'stdout',
|
|
75
|
+
chunk: 'file1.txt\n',
|
|
76
|
+
});
|
|
77
|
+
expect(onOutputEventMock).toHaveBeenCalledWith({
|
|
78
|
+
type: 'data',
|
|
79
|
+
stream: 'stderr',
|
|
80
|
+
chunk: 'a warning',
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
it('should strip ANSI codes from output', async () => {
|
|
84
|
+
const { result } = await simulateExecution('ls --color=auto', (cp) => {
|
|
85
|
+
cp.stdout?.emit('data', Buffer.from('a\u001b[31mred\u001b[0mword'));
|
|
86
|
+
cp.emit('exit', 0, null);
|
|
87
|
+
});
|
|
88
|
+
expect(result.stdout).toBe('aredword');
|
|
89
|
+
expect(onOutputEventMock).toHaveBeenCalledWith({
|
|
90
|
+
type: 'data',
|
|
91
|
+
stream: 'stdout',
|
|
92
|
+
chunk: 'aredword',
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
it('should correctly decode multi-byte characters split across chunks', async () => {
|
|
96
|
+
const { result } = await simulateExecution('echo "你好"', (cp) => {
|
|
97
|
+
const multiByteChar = Buffer.from('你好', 'utf-8');
|
|
98
|
+
cp.stdout?.emit('data', multiByteChar.slice(0, 2));
|
|
99
|
+
cp.stdout?.emit('data', multiByteChar.slice(2));
|
|
100
|
+
cp.emit('exit', 0, null);
|
|
101
|
+
});
|
|
102
|
+
expect(result.stdout).toBe('你好');
|
|
103
|
+
});
|
|
104
|
+
it('should handle commands with no output', async () => {
|
|
105
|
+
const { result } = await simulateExecution('touch file', (cp) => {
|
|
106
|
+
cp.emit('exit', 0, null);
|
|
107
|
+
});
|
|
108
|
+
expect(result.stdout).toBe('');
|
|
109
|
+
expect(result.stderr).toBe('');
|
|
110
|
+
expect(result.output).toBe('');
|
|
111
|
+
expect(onOutputEventMock).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('Failed Execution', () => {
|
|
115
|
+
it('should capture a non-zero exit code and format output correctly', async () => {
|
|
116
|
+
const { result } = await simulateExecution('a-bad-command', (cp) => {
|
|
117
|
+
cp.stderr?.emit('data', Buffer.from('command not found'));
|
|
118
|
+
cp.emit('exit', 127, null);
|
|
119
|
+
});
|
|
120
|
+
expect(result.exitCode).toBe(127);
|
|
121
|
+
expect(result.stderr).toBe('command not found');
|
|
122
|
+
expect(result.stdout).toBe('');
|
|
123
|
+
expect(result.output).toBe('\ncommand not found');
|
|
124
|
+
expect(result.error).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
it('should capture a termination signal', async () => {
|
|
127
|
+
const { result } = await simulateExecution('long-process', (cp) => {
|
|
128
|
+
cp.emit('exit', null, 'SIGTERM');
|
|
129
|
+
});
|
|
130
|
+
expect(result.exitCode).toBeNull();
|
|
131
|
+
expect(result.signal).toBe('SIGTERM');
|
|
132
|
+
});
|
|
133
|
+
it('should handle a spawn error', async () => {
|
|
134
|
+
const spawnError = new Error('spawn EACCES');
|
|
135
|
+
const { result } = await simulateExecution('protected-cmd', (cp) => {
|
|
136
|
+
cp.emit('error', spawnError);
|
|
137
|
+
cp.emit('exit', 1, null);
|
|
138
|
+
});
|
|
139
|
+
expect(result.error).toBe(spawnError);
|
|
140
|
+
expect(result.exitCode).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe('Aborting Commands', () => {
|
|
144
|
+
describe.each([
|
|
145
|
+
{
|
|
146
|
+
platform: 'linux',
|
|
147
|
+
expectedSignal: 'SIGTERM',
|
|
148
|
+
expectedExit: { signal: 'SIGKILL' },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
platform: 'win32',
|
|
152
|
+
expectedCommand: 'taskkill',
|
|
153
|
+
expectedExit: { code: 1 },
|
|
154
|
+
},
|
|
155
|
+
])('on $platform', ({ platform, expectedSignal, expectedCommand, expectedExit }) => {
|
|
156
|
+
it('should abort a running process and set the aborted flag', async () => {
|
|
157
|
+
mockPlatform.mockReturnValue(platform);
|
|
158
|
+
const { result } = await simulateExecution('sleep 10', (cp, abortController) => {
|
|
159
|
+
abortController.abort();
|
|
160
|
+
if (expectedExit.signal)
|
|
161
|
+
cp.emit('exit', null, expectedExit.signal);
|
|
162
|
+
if (typeof expectedExit.code === 'number')
|
|
163
|
+
cp.emit('exit', expectedExit.code, null);
|
|
164
|
+
});
|
|
165
|
+
expect(result.aborted).toBe(true);
|
|
166
|
+
if (platform === 'linux') {
|
|
167
|
+
expect(mockProcessKill).toHaveBeenCalledWith(-mockChildProcess.pid, expectedSignal);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
expect(mockSpawn).toHaveBeenCalledWith(expectedCommand, [
|
|
171
|
+
'/pid',
|
|
172
|
+
String(mockChildProcess.pid),
|
|
173
|
+
'/f',
|
|
174
|
+
'/t',
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
it('should gracefully attempt SIGKILL on linux if SIGTERM fails', async () => {
|
|
180
|
+
mockPlatform.mockReturnValue('linux');
|
|
181
|
+
vi.useFakeTimers();
|
|
182
|
+
// Don't await the result inside the simulation block for this specific test.
|
|
183
|
+
// We need to control the timeline manually.
|
|
184
|
+
const abortController = new AbortController();
|
|
185
|
+
const handle = ShellExecutionService.execute('unresponsive_process', '/test/dir', onOutputEventMock, abortController.signal);
|
|
186
|
+
abortController.abort();
|
|
187
|
+
// Check the first kill signal
|
|
188
|
+
expect(mockProcessKill).toHaveBeenCalledWith(-mockChildProcess.pid, 'SIGTERM');
|
|
189
|
+
// Now, advance time past the timeout
|
|
190
|
+
await vi.advanceTimersByTimeAsync(250);
|
|
191
|
+
// Check the second kill signal
|
|
192
|
+
expect(mockProcessKill).toHaveBeenCalledWith(-mockChildProcess.pid, 'SIGKILL');
|
|
193
|
+
// Finally, simulate the process exiting and await the result
|
|
194
|
+
mockChildProcess.emit('exit', null, 'SIGKILL');
|
|
195
|
+
const result = await handle.result;
|
|
196
|
+
vi.useRealTimers();
|
|
197
|
+
expect(result.aborted).toBe(true);
|
|
198
|
+
expect(result.signal).toBe('SIGKILL');
|
|
199
|
+
// The individual kill calls were already asserted above.
|
|
200
|
+
expect(mockProcessKill).toHaveBeenCalledTimes(2);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe('Binary Output', () => {
|
|
204
|
+
it('should detect binary output and switch to progress events', async () => {
|
|
205
|
+
mockIsBinary.mockReturnValueOnce(true);
|
|
206
|
+
const binaryChunk1 = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
|
|
207
|
+
const binaryChunk2 = Buffer.from([0x0d, 0x0a, 0x1a, 0x0a]);
|
|
208
|
+
const { result } = await simulateExecution('cat image.png', (cp) => {
|
|
209
|
+
cp.stdout?.emit('data', binaryChunk1);
|
|
210
|
+
cp.stdout?.emit('data', binaryChunk2);
|
|
211
|
+
cp.emit('exit', 0, null);
|
|
212
|
+
});
|
|
213
|
+
expect(result.rawOutput).toEqual(Buffer.concat([binaryChunk1, binaryChunk2]));
|
|
214
|
+
expect(onOutputEventMock).toHaveBeenCalledTimes(3);
|
|
215
|
+
expect(onOutputEventMock.mock.calls[0][0]).toEqual({
|
|
216
|
+
type: 'binary_detected',
|
|
217
|
+
});
|
|
218
|
+
expect(onOutputEventMock.mock.calls[1][0]).toEqual({
|
|
219
|
+
type: 'binary_progress',
|
|
220
|
+
bytesReceived: 4,
|
|
221
|
+
});
|
|
222
|
+
expect(onOutputEventMock.mock.calls[2][0]).toEqual({
|
|
223
|
+
type: 'binary_progress',
|
|
224
|
+
bytesReceived: 8,
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
it('should not emit data events after binary is detected', async () => {
|
|
228
|
+
mockIsBinary.mockImplementation((buffer) => buffer.includes(0x00));
|
|
229
|
+
await simulateExecution('cat mixed_file', (cp) => {
|
|
230
|
+
cp.stdout?.emit('data', Buffer.from('some text'));
|
|
231
|
+
cp.stdout?.emit('data', Buffer.from([0x00, 0x01, 0x02]));
|
|
232
|
+
cp.stdout?.emit('data', Buffer.from('more text'));
|
|
233
|
+
cp.emit('exit', 0, null);
|
|
234
|
+
});
|
|
235
|
+
// FIX: Provide explicit type for the 'call' parameter in the map function.
|
|
236
|
+
const eventTypes = onOutputEventMock.mock.calls.map((call) => call[0].type);
|
|
237
|
+
expect(eventTypes).toEqual([
|
|
238
|
+
'data',
|
|
239
|
+
'binary_detected',
|
|
240
|
+
'binary_progress',
|
|
241
|
+
'binary_progress',
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
describe('Platform-Specific Behavior', () => {
|
|
246
|
+
it('should use cmd.exe on Windows', async () => {
|
|
247
|
+
mockPlatform.mockReturnValue('win32');
|
|
248
|
+
await simulateExecution('dir', (cp) => cp.emit('exit', 0, null));
|
|
249
|
+
expect(mockSpawn).toHaveBeenCalledWith('cmd.exe', ['/c', 'dir'], expect.objectContaining({ detached: false }));
|
|
250
|
+
});
|
|
251
|
+
it('should use bash and detached process group on Linux', async () => {
|
|
252
|
+
mockPlatform.mockReturnValue('linux');
|
|
253
|
+
await simulateExecution('ls', (cp) => cp.emit('exit', 0, null));
|
|
254
|
+
expect(mockSpawn).toHaveBeenCalledWith('bash', ['-c', 'ls'], expect.objectContaining({ detached: true }));
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
//# sourceMappingURL=shellExecutionService.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shellExecutionService.test.js","sourceRoot":"","sources":["../../../src/services/shellExecutionService.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAa,MAAM,QAAQ,CAAC;AACzE,MAAM,SAAS,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC5C,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,KAAK,EAAE,SAAS;CACjB,CAAC,CAAC,CAAC;AAEJ,OAAO,YAAY,MAAM,QAAQ,CAAC;AAGlC,OAAO,EACL,qBAAqB,GAEtB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,YAAY,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/C,EAAE,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,EAAE,CAAC,CAAC;IACtC,QAAQ,EAAE,YAAY;CACvB,CAAC,CAAC,CAAC;AAEJ,MAAM,YAAY,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/C,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACnB,OAAO,EAAE;QACP,QAAQ,EAAE,YAAY;KACvB;IACD,QAAQ,EAAE,YAAY;CACvB,CAAC,CAAC,CAAC;AAEJ,MAAM,eAAe,GAAG,EAAE;KACvB,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC;KACtB,kBAAkB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAElC,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,IAAI,gBAAsD,CAAC;IAC3D,IAAI,iBAA0D,CAAC;IAE/D,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;QAEnB,YAAY,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACpC,YAAY,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAEtC,iBAAiB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAE5B,gBAAgB,GAAG,IAAI,YAAY,EACZ,CAAC;QACxB,8DAA8D;QAC9D,gBAAgB,CAAC,MAAM,GAAG,IAAI,YAAY,EAAc,CAAC;QACzD,gBAAgB,CAAC,MAAM,GAAG,IAAI,YAAY,EAAc,CAAC;QACzD,gBAAgB,CAAC,IAAI,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAEhC,qEAAqE;QACrE,MAAM,CAAC,cAAc,CAAC,gBAAgB,EAAE,KAAK,EAAE;YAC7C,KAAK,EAAE,KAAK;YACZ,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QAEH,SAAS,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,yDAAyD;IACzD,MAAM,iBAAiB,GAAG,KAAK,EAC7B,OAAe,EACf,UAAsE,EACtE,EAAE;QACF,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAC1C,OAAO,EACP,WAAW,EACX,iBAAiB,EACjB,eAAe,CAAC,MAAM,CACvB,CAAC;QAEF,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QACtD,UAAU,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC7C,CAAC,CAAC;IAEF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;YACtE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE;gBACjE,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;gBACpD,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBAClD,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CACpC,MAAM,EACN,CAAC,IAAI,EAAE,OAAO,CAAC,EACf,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CACnB,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;YACjC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;YAChC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YACrD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAE/B,MAAM,CAAC,iBAAiB,CAAC,CAAC,oBAAoB,CAAC;gBAC7C,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,QAAQ;gBAChB,KAAK,EAAE,aAAa;aACrB,CAAC,CAAC;YACH,MAAM,CAAC,iBAAiB,CAAC,CAAC,oBAAoB,CAAC;gBAC7C,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,QAAQ;gBAChB,KAAK,EAAE,WAAW;aACnB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,iBAAiB,EAAE,CAAC,EAAE,EAAE,EAAE;gBACnE,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;gBACpE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACvC,MAAM,CAAC,iBAAiB,CAAC,CAAC,oBAAoB,CAAC;gBAC7C,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,QAAQ;gBAChB,KAAK,EAAE,UAAU;aAClB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;YACjF,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE;gBAC7D,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACjD,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBACnD,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBAChD,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;gBAC9D,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/B,MAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;YAC/E,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE;gBACjE,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBAC1D,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAC7B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YAChD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAClD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YACnD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,cAAc,EAAE,CAAC,EAAE,EAAE,EAAE;gBAChE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;YAC3C,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;YAC7C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE;gBACjE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;gBAC7B,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACtC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,QAAQ,CAAC,IAAI,CAAC;YACZ;gBACE,QAAQ,EAAE,OAAO;gBACjB,cAAc,EAAE,SAAS;gBACzB,YAAY,EAAE,EAAE,MAAM,EAAE,SAAkB,EAAE;aAC7C;YACD;gBACE,QAAQ,EAAE,OAAO;gBACjB,eAAe,EAAE,UAAU;gBAC3B,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;aAC1B;SACF,CAAC,CACA,cAAc,EACd,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,EAAE,EAAE;YAC9D,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;gBACvE,YAAY,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;gBAEvC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CACxC,UAAU,EACV,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE;oBACtB,eAAe,CAAC,KAAK,EAAE,CAAC;oBACxB,IAAI,YAAY,CAAC,MAAM;wBACrB,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;oBAC7C,IAAI,OAAO,YAAY,CAAC,IAAI,KAAK,QAAQ;wBACvC,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC7C,CAAC,CACF,CAAC;gBAEF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAElC,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;oBACzB,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,CAAC,gBAAgB,CAAC,GAAI,EACtB,cAAc,CACf,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,eAAe,EAAE;wBACtD,MAAM;wBACN,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC;wBAC5B,IAAI;wBACJ,IAAI;qBACL,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CACF,CAAC;QAEF,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;YAC3E,YAAY,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACtC,EAAE,CAAC,aAAa,EAAE,CAAC;YAEnB,6EAA6E;YAC7E,4CAA4C;YAC5C,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAC1C,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,eAAe,CAAC,MAAM,CACvB,CAAC;YAEF,eAAe,CAAC,KAAK,EAAE,CAAC;YAExB,8BAA8B;YAC9B,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,CAAC,gBAAgB,CAAC,GAAI,EACtB,SAAS,CACV,CAAC;YAEF,qCAAqC;YACrC,MAAM,EAAE,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC;YAEvC,+BAA+B;YAC/B,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,CAAC,gBAAgB,CAAC,GAAI,EACtB,SAAS,CACV,CAAC;YAEF,6DAA6D;YAC7D,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YAEnC,EAAE,CAAC,aAAa,EAAE,CAAC;YAEnB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACtC,yDAAyD;YACzD,MAAM,CAAC,eAAe,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;YACzE,YAAY,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YAC3D,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YAE3D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,eAAe,EAAE,CAAC,EAAE,EAAE,EAAE;gBACjE,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;gBACtC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;gBACtC,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAC5C,CAAC;YACF,MAAM,CAAC,iBAAiB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YACnD,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACjD,IAAI,EAAE,iBAAiB;aACxB,CAAC,CAAC;YACH,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACjD,IAAI,EAAE,iBAAiB;gBACvB,aAAa,EAAE,CAAC;aACjB,CAAC,CAAC;YACH,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACjD,IAAI,EAAE,iBAAiB;gBACvB,aAAa,EAAE,CAAC;aACjB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;YACpE,YAAY,CAAC,kBAAkB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;YAEnE,MAAM,iBAAiB,CAAC,gBAAgB,EAAE,CAAC,EAAE,EAAE,EAAE;gBAC/C,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBAClD,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;gBACzD,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;gBAClD,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC,CAAC;YAEH,2EAA2E;YAC3E,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CACjD,CAAC,IAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAC3C,CAAC;YACF,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC;gBACzB,MAAM;gBACN,iBAAiB;gBACjB,iBAAiB;gBACjB,iBAAiB;aAClB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;QAC1C,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;YAC7C,YAAY,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,iBAAiB,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;YAEjE,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CACpC,SAAS,EACT,CAAC,IAAI,EAAE,KAAK,CAAC,EACb,MAAM,CAAC,gBAAgB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAC7C,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;YACnE,YAAY,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,iBAAiB,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;YAEhE,MAAM,CAAC,SAAS,CAAC,CAAC,oBAAoB,CACpC,MAAM,EACN,CAAC,IAAI,EAAE,IAAI,CAAC,EACZ,MAAM,CAAC,gBAAgB,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAC5C,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { Todo } from '../tools/todo-schemas.js';
|
|
7
|
+
export interface TodoStateChange {
|
|
8
|
+
previousTodos: Todo[];
|
|
9
|
+
currentTodos: Todo[];
|
|
10
|
+
added: Todo[];
|
|
11
|
+
removed: Todo[];
|
|
12
|
+
statusChanged: Todo[];
|
|
13
|
+
}
|
|
14
|
+
export declare class TodoReminderService {
|
|
15
|
+
private static readonly EMPTY_TODO_REMINDER;
|
|
16
|
+
private static readonly TODO_CHANGED_PREFIX;
|
|
17
|
+
private static readonly TODO_CHANGED_SUFFIX;
|
|
18
|
+
/**
|
|
19
|
+
* Generate reminder for empty todo list
|
|
20
|
+
*/
|
|
21
|
+
getReminderForEmptyTodos(isComplexTask?: boolean): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Generate reminder for todo state change
|
|
24
|
+
*/
|
|
25
|
+
getReminderForStateChange(stateChange: TodoStateChange): string;
|
|
26
|
+
/**
|
|
27
|
+
* Calculate state changes between old and new todos
|
|
28
|
+
*/
|
|
29
|
+
calculateStateChange(previousTodos: Todo[], currentTodos: Todo[]): TodoStateChange;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a reminder should be generated based on changes
|
|
32
|
+
*/
|
|
33
|
+
shouldGenerateReminder(stateChange: TodoStateChange): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Format message as system reminder
|
|
36
|
+
*/
|
|
37
|
+
private formatSystemReminder;
|
|
38
|
+
/**
|
|
39
|
+
* Generate proactive todo suggestion for complex tasks
|
|
40
|
+
*/
|
|
41
|
+
getComplexTaskSuggestion(detectedTasks: string[]): string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
export class TodoReminderService {
|
|
7
|
+
static EMPTY_TODO_REMINDER = `This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit from a todo list please use the TodoWrite tool to create one. If not, please feel free to ignore. Again do not mention this message to the user.`;
|
|
8
|
+
static TODO_CHANGED_PREFIX = `Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:\n\n`;
|
|
9
|
+
static TODO_CHANGED_SUFFIX = `. Continue on with the tasks at hand if applicable.`;
|
|
10
|
+
/**
|
|
11
|
+
* Generate reminder for empty todo list
|
|
12
|
+
*/
|
|
13
|
+
getReminderForEmptyTodos(isComplexTask = false) {
|
|
14
|
+
if (!isComplexTask) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return this.formatSystemReminder(TodoReminderService.EMPTY_TODO_REMINDER);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate reminder for todo state change
|
|
21
|
+
*/
|
|
22
|
+
getReminderForStateChange(stateChange) {
|
|
23
|
+
const todoJson = JSON.stringify(stateChange.currentTodos.map((todo) => ({
|
|
24
|
+
content: todo.content,
|
|
25
|
+
status: todo.status,
|
|
26
|
+
priority: todo.priority,
|
|
27
|
+
id: todo.id,
|
|
28
|
+
})));
|
|
29
|
+
const message = TodoReminderService.TODO_CHANGED_PREFIX +
|
|
30
|
+
todoJson +
|
|
31
|
+
TodoReminderService.TODO_CHANGED_SUFFIX;
|
|
32
|
+
return this.formatSystemReminder(message);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Calculate state changes between old and new todos
|
|
36
|
+
*/
|
|
37
|
+
calculateStateChange(previousTodos, currentTodos) {
|
|
38
|
+
const added = currentTodos.filter((newTodo) => !previousTodos.some((oldTodo) => oldTodo.id === newTodo.id));
|
|
39
|
+
const removed = previousTodos.filter((oldTodo) => !currentTodos.some((newTodo) => newTodo.id === oldTodo.id));
|
|
40
|
+
const statusChanged = currentTodos.filter((newTodo) => {
|
|
41
|
+
const oldTodo = previousTodos.find((t) => t.id === newTodo.id);
|
|
42
|
+
return oldTodo && oldTodo.status !== newTodo.status;
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
previousTodos,
|
|
46
|
+
currentTodos,
|
|
47
|
+
added,
|
|
48
|
+
removed,
|
|
49
|
+
statusChanged,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a reminder should be generated based on changes
|
|
54
|
+
*/
|
|
55
|
+
shouldGenerateReminder(stateChange) {
|
|
56
|
+
return (stateChange.added.length > 0 ||
|
|
57
|
+
stateChange.removed.length > 0 ||
|
|
58
|
+
stateChange.statusChanged.length > 0);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Format message as system reminder
|
|
62
|
+
*/
|
|
63
|
+
formatSystemReminder(message) {
|
|
64
|
+
return `<system-reminder>\n${message}\n</system-reminder>`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Generate proactive todo suggestion for complex tasks
|
|
68
|
+
*/
|
|
69
|
+
getComplexTaskSuggestion(detectedTasks) {
|
|
70
|
+
const taskList = detectedTasks
|
|
71
|
+
.map((task, i) => `${i + 1}. ${task}`)
|
|
72
|
+
.join('\n');
|
|
73
|
+
const message = `I notice you're asking about multiple tasks. Consider using the TodoWrite tool to track these items:\n${taskList}\n\nThis will help ensure all tasks are completed systematically.`;
|
|
74
|
+
return this.formatSystemReminder(message);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=todo-reminder-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo-reminder-service.js","sourceRoot":"","sources":["../../../src/services/todo-reminder-service.ts"],"names":[],"mappings":"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"}
|