@librechat/agents 3.1.77 → 3.1.78
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/dist/cjs/common/enum.cjs +54 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +155 -4
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +291 -0
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -0
- package/dist/cjs/main.cjs +90 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/anthropicToolCache.cjs +102 -0
- package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +27 -0
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +99 -0
- package/dist/cjs/messages/recency.cjs.map +1 -0
- package/dist/cjs/run.cjs +30 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +100 -6
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +635 -23
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/local/CompileCheckTool.cjs +227 -0
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -0
- package/dist/cjs/tools/local/FileCheckpointer.cjs +90 -0
- package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalCodingTools.cjs +1098 -0
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +1042 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalExecutionTools.cjs +122 -0
- package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -0
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +453 -0
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -0
- package/dist/cjs/tools/local/attachments.cjs +183 -0
- package/dist/cjs/tools/local/attachments.cjs.map +1 -0
- package/dist/cjs/tools/local/bashAst.cjs +129 -0
- package/dist/cjs/tools/local/bashAst.cjs.map +1 -0
- package/dist/cjs/tools/local/editStrategies.cjs +188 -0
- package/dist/cjs/tools/local/editStrategies.cjs.map +1 -0
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +141 -0
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -0
- package/dist/cjs/tools/local/syntaxCheck.cjs +182 -0
- package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -0
- package/dist/cjs/tools/local/textEncoding.cjs +30 -0
- package/dist/cjs/tools/local/textEncoding.cjs.map +1 -0
- package/dist/cjs/tools/local/workspaceFS.cjs +51 -0
- package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +31 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +53 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +156 -5
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +289 -0
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -0
- package/dist/esm/main.mjs +17 -2
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/anthropicToolCache.mjs +99 -0
- package/dist/esm/messages/anthropicToolCache.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +26 -1
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +97 -0
- package/dist/esm/messages/recency.mjs.map +1 -0
- package/dist/esm/run.mjs +30 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +100 -6
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +635 -23
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/local/CompileCheckTool.mjs +223 -0
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -0
- package/dist/esm/tools/local/FileCheckpointer.mjs +87 -0
- package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -0
- package/dist/esm/tools/local/LocalCodingTools.mjs +1075 -0
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -0
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +1022 -0
- package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -0
- package/dist/esm/tools/local/LocalExecutionTools.mjs +117 -0
- package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -0
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +448 -0
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -0
- package/dist/esm/tools/local/attachments.mjs +180 -0
- package/dist/esm/tools/local/attachments.mjs.map +1 -0
- package/dist/esm/tools/local/bashAst.mjs +126 -0
- package/dist/esm/tools/local/bashAst.mjs.map +1 -0
- package/dist/esm/tools/local/editStrategies.mjs +185 -0
- package/dist/esm/tools/local/editStrategies.mjs.map +1 -0
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +137 -0
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -0
- package/dist/esm/tools/local/syntaxCheck.mjs +179 -0
- package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -0
- package/dist/esm/tools/local/textEncoding.mjs +27 -0
- package/dist/esm/tools/local/textEncoding.mjs.map +1 -0
- package/dist/esm/tools/local/workspaceFS.mjs +49 -0
- package/dist/esm/tools/local/workspaceFS.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +31 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +39 -1
- package/dist/types/graphs/Graph.d.ts +34 -0
- package/dist/types/hooks/createWorkspacePolicyHook.d.ts +95 -0
- package/dist/types/hooks/index.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/messages/anthropicToolCache.d.ts +51 -0
- package/dist/types/messages/index.d.ts +2 -0
- package/dist/types/messages/prune.d.ts +11 -0
- package/dist/types/messages/recency.d.ts +64 -0
- package/dist/types/run.d.ts +21 -0
- package/dist/types/tools/ToolNode.d.ts +145 -2
- package/dist/types/tools/local/CompileCheckTool.d.ts +31 -0
- package/dist/types/tools/local/FileCheckpointer.d.ts +39 -0
- package/dist/types/tools/local/LocalCodingTools.d.ts +57 -0
- package/dist/types/tools/local/LocalExecutionEngine.d.ts +149 -0
- package/dist/types/tools/local/LocalExecutionTools.d.ts +9 -0
- package/dist/types/tools/local/LocalProgrammaticToolCalling.d.ts +21 -0
- package/dist/types/tools/local/attachments.d.ts +84 -0
- package/dist/types/tools/local/bashAst.d.ts +11 -0
- package/dist/types/tools/local/editStrategies.d.ts +28 -0
- package/dist/types/tools/local/index.d.ts +12 -0
- package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +38 -0
- package/dist/types/tools/local/syntaxCheck.d.ts +42 -0
- package/dist/types/tools/local/textEncoding.d.ts +21 -0
- package/dist/types/tools/local/workspaceFS.d.ts +49 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +29 -0
- package/dist/types/types/hitl.d.ts +56 -27
- package/dist/types/types/run.d.ts +8 -1
- package/dist/types/types/summarize.d.ts +30 -0
- package/dist/types/types/tools.d.ts +341 -6
- package/package.json +21 -2
- package/src/common/enum.ts +54 -0
- package/src/graphs/Graph.ts +173 -6
- package/src/hooks/__tests__/compactHooks.test.ts +38 -2
- package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +393 -0
- package/src/hooks/createWorkspacePolicyHook.ts +355 -0
- package/src/hooks/index.ts +6 -0
- package/src/index.ts +1 -0
- package/src/messages/__tests__/anthropicToolCache.test.ts +125 -0
- package/src/messages/__tests__/recency.test.ts +267 -0
- package/src/messages/anthropicToolCache.ts +116 -0
- package/src/messages/index.ts +2 -0
- package/src/messages/prune.ts +27 -1
- package/src/messages/recency.ts +155 -0
- package/src/run.ts +31 -0
- package/src/scripts/compare_pi_vs_ours.ts +840 -0
- package/src/scripts/local_engine.ts +166 -0
- package/src/scripts/local_engine_checkpointer.ts +205 -0
- package/src/scripts/local_engine_compile.ts +263 -0
- package/src/scripts/local_engine_hooks.ts +226 -0
- package/src/scripts/local_engine_image.ts +201 -0
- package/src/scripts/local_engine_ptc.ts +151 -0
- package/src/scripts/local_engine_workspace.ts +258 -0
- package/src/scripts/subagent-configurable-inheritance.ts +252 -0
- package/src/scripts/summarization-recency.ts +462 -0
- package/src/specs/prune.test.ts +39 -0
- package/src/summarization/__tests__/node.test.ts +499 -3
- package/src/summarization/node.ts +124 -7
- package/src/tools/ToolNode.ts +769 -20
- package/src/tools/__tests__/LocalExecutionTools.test.ts +2647 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +175 -0
- package/src/tools/__tests__/SubagentExecutor.test.ts +148 -0
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +114 -0
- package/src/tools/__tests__/ToolNode.session.test.ts +84 -0
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +467 -0
- package/src/tools/__tests__/directToolHooks.test.ts +411 -0
- package/src/tools/__tests__/localToolNames.test.ts +73 -0
- package/src/tools/__tests__/workspaceSeam.test.ts +134 -0
- package/src/tools/local/CompileCheckTool.ts +278 -0
- package/src/tools/local/FileCheckpointer.ts +93 -0
- package/src/tools/local/LocalCodingTools.ts +1342 -0
- package/src/tools/local/LocalExecutionEngine.ts +1329 -0
- package/src/tools/local/LocalExecutionTools.ts +167 -0
- package/src/tools/local/LocalProgrammaticToolCalling.ts +594 -0
- package/src/tools/local/__tests__/FileCheckpointer.test.ts +120 -0
- package/src/tools/local/__tests__/editStrategies.test.ts +134 -0
- package/src/tools/local/attachments.ts +251 -0
- package/src/tools/local/bashAst.ts +151 -0
- package/src/tools/local/editStrategies.ts +188 -0
- package/src/tools/local/index.ts +12 -0
- package/src/tools/local/resolveLocalExecutionTools.ts +208 -0
- package/src/tools/local/syntaxCheck.ts +243 -0
- package/src/tools/local/textEncoding.ts +37 -0
- package/src/tools/local/workspaceFS.ts +89 -0
- package/src/tools/subagent/SubagentExecutor.ts +60 -0
- package/src/types/hitl.ts +56 -27
- package/src/types/run.ts +12 -1
- package/src/types/summarize.ts +31 -0
- package/src/types/tools.ts +359 -7
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/scripts/local_engine.ts
|
|
3
|
+
*
|
|
4
|
+
* Live end-to-end exercise of the local execution engine. Spins up a
|
|
5
|
+
* temporary workspace, points an agent at it via
|
|
6
|
+
* `toolExecution: { engine: 'local' }`, and asks the model to do a
|
|
7
|
+
* small coding task that exercises `write_file`, `read_file`,
|
|
8
|
+
* `edit_file`, `bash`, and `list_directory`. After the run, dumps the
|
|
9
|
+
* workspace contents so you can verify the tools actually mutated
|
|
10
|
+
* disk.
|
|
11
|
+
*
|
|
12
|
+
* Run with: `npm run local`
|
|
13
|
+
*/
|
|
14
|
+
import { config } from 'dotenv';
|
|
15
|
+
config();
|
|
16
|
+
import { tmpdir } from 'os';
|
|
17
|
+
import { join } from 'path';
|
|
18
|
+
import { mkdtemp, readdir, readFile, rm } from 'fs/promises';
|
|
19
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
20
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
21
|
+
import type * as t from '@/types';
|
|
22
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
23
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
24
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
25
|
+
import { getArgs } from '@/scripts/args';
|
|
26
|
+
import { GraphEvents } from '@/common';
|
|
27
|
+
import { Run } from '@/run';
|
|
28
|
+
|
|
29
|
+
const conversationHistory: BaseMessage[] = [];
|
|
30
|
+
|
|
31
|
+
async function main(): Promise<void> {
|
|
32
|
+
const { userName, provider } = await getArgs();
|
|
33
|
+
const cwd = await mkdtemp(join(tmpdir(), 'lc-local-engine-'));
|
|
34
|
+
console.log(`[local_engine] workspace: ${cwd}`);
|
|
35
|
+
|
|
36
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
37
|
+
const customHandlers = {
|
|
38
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
39
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
40
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
41
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
42
|
+
handle: (
|
|
43
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
44
|
+
data: t.StreamEventData
|
|
45
|
+
): void => {
|
|
46
|
+
console.log('====== ON_RUN_STEP_COMPLETED ======');
|
|
47
|
+
const cast = data as unknown as { result: t.ToolEndEvent };
|
|
48
|
+
const tc = (cast.result as { tool_call?: { name?: string } } | undefined)
|
|
49
|
+
?.tool_call;
|
|
50
|
+
console.log(`tool=${tc?.name ?? '<unknown>'}`);
|
|
51
|
+
aggregateContent({ event, data: cast });
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
55
|
+
handle: (
|
|
56
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
57
|
+
data: t.StreamEventData
|
|
58
|
+
): void => {
|
|
59
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
63
|
+
handle: (
|
|
64
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
65
|
+
data: t.StreamEventData
|
|
66
|
+
): void => {
|
|
67
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
71
|
+
handle: (
|
|
72
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
73
|
+
data: t.StreamEventData
|
|
74
|
+
): void => {
|
|
75
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
[GraphEvents.TOOL_START]: {
|
|
79
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
80
|
+
const obj = data as unknown as { name?: string; input?: unknown };
|
|
81
|
+
console.log(
|
|
82
|
+
`====== TOOL_START tool=${obj.name ?? '?'} input=${JSON.stringify(
|
|
83
|
+
obj.input
|
|
84
|
+
)} ======`
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const llmConfig = getLLMConfig(provider);
|
|
91
|
+
|
|
92
|
+
const runConfig: t.RunConfig = {
|
|
93
|
+
runId: 'local-engine-1',
|
|
94
|
+
graphConfig: {
|
|
95
|
+
type: 'standard',
|
|
96
|
+
llmConfig,
|
|
97
|
+
instructions:
|
|
98
|
+
'You are a friendly AI coding assistant with access to local file and shell tools. ' +
|
|
99
|
+
'Always operate inside the configured working directory and prefer the smallest tool ' +
|
|
100
|
+
'that gets the job done (read_file before grep, write_file before bash heredocs, etc.). ' +
|
|
101
|
+
`Address the user by their name (${userName}).`,
|
|
102
|
+
},
|
|
103
|
+
toolExecution: {
|
|
104
|
+
engine: 'local',
|
|
105
|
+
local: {
|
|
106
|
+
cwd,
|
|
107
|
+
// Sandbox is opt-in; leave off for this baseline script. The engine
|
|
108
|
+
// logs a one-time warning when it runs without sandboxing.
|
|
109
|
+
bashAst: 'auto',
|
|
110
|
+
timeoutMs: 30_000,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
returnContent: true,
|
|
114
|
+
skipCleanup: true,
|
|
115
|
+
customHandlers,
|
|
116
|
+
};
|
|
117
|
+
const run = await Run.create<t.IState>(runConfig);
|
|
118
|
+
|
|
119
|
+
const streamConfig = {
|
|
120
|
+
configurable: { provider, thread_id: 'local-engine-thread-1' },
|
|
121
|
+
streamMode: 'values',
|
|
122
|
+
version: 'v2' as const,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const userMessage = new HumanMessage(
|
|
126
|
+
`Hi, I'm ${userName}. In the current working directory, please do the following step by step:\n\n` +
|
|
127
|
+
'1. Create a file called `greet.py` with a tiny Python function `greet(name)` that returns "Hello, <name>!".\n' +
|
|
128
|
+
'2. Use `bash` to run `python3 -c "from greet import greet; print(greet(\\"world\\"))"` and confirm it prints "Hello, world!".\n' +
|
|
129
|
+
'3. Use `edit_file` to change the greeting to "Hi, <name>!" (only that one literal change).\n' +
|
|
130
|
+
'4. Re-run the same bash command and confirm the new output.\n' +
|
|
131
|
+
'5. Use `list_directory` to show what files exist now.\n\n' +
|
|
132
|
+
'Before exiting, briefly summarise the steps you took.'
|
|
133
|
+
);
|
|
134
|
+
conversationHistory.push(userMessage);
|
|
135
|
+
|
|
136
|
+
console.log('====== USER ======\n' + userMessage.content + '\n');
|
|
137
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
138
|
+
const finalMessages = run.getRunMessages();
|
|
139
|
+
if (finalMessages) {
|
|
140
|
+
conversationHistory.push(...finalMessages);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log('\n====== FINAL CONTENT PARTS ======');
|
|
144
|
+
console.dir(contentParts, { depth: null });
|
|
145
|
+
|
|
146
|
+
console.log('\n====== WORKSPACE ON DISK ======');
|
|
147
|
+
const entries = await readdir(cwd, { withFileTypes: true });
|
|
148
|
+
for (const e of entries) {
|
|
149
|
+
if (!e.isFile()) continue;
|
|
150
|
+
const path = join(cwd, e.name);
|
|
151
|
+
const body = await readFile(path, 'utf8').catch(() => '<binary>');
|
|
152
|
+
console.log(`--- ${e.name} ---\n${body}\n`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await rm(cwd, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
process.on('unhandledRejection', (reason) => {
|
|
159
|
+
console.error('Unhandled Rejection:', reason);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
main().catch((err) => {
|
|
164
|
+
console.error(err);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/scripts/local_engine_checkpointer.ts
|
|
3
|
+
*
|
|
4
|
+
* Live demonstration of the file checkpointer. We:
|
|
5
|
+
*
|
|
6
|
+
* 1. Build the local-coding-tool bundle ourselves with
|
|
7
|
+
* `createLocalCodingToolBundle({ fileCheckpointing: true })` so we
|
|
8
|
+
* can keep a handle on the checkpointer.
|
|
9
|
+
* 2. Pass those tools to the run as `graphConfig.tools`. We do NOT
|
|
10
|
+
* use `toolExecution.engine: 'local'` here — that path auto-binds
|
|
11
|
+
* its own internal tools without exposing a checkpointer back to
|
|
12
|
+
* us. The point is to show how a host wires checkpointing on top
|
|
13
|
+
* of the local tools when it cares about rewind.
|
|
14
|
+
* 3. Seed two files with known contents.
|
|
15
|
+
* 4. Ask the model to MUTATE both via `edit_file` / `write_file`.
|
|
16
|
+
* 5. Verify the contents changed on disk.
|
|
17
|
+
* 6. Call `checkpointer.rewind()` and verify the original contents
|
|
18
|
+
* came back.
|
|
19
|
+
*
|
|
20
|
+
* Run with: `npm run local:checkpointer`
|
|
21
|
+
*/
|
|
22
|
+
import { config } from 'dotenv';
|
|
23
|
+
config();
|
|
24
|
+
import { tmpdir } from 'os';
|
|
25
|
+
import { join } from 'path';
|
|
26
|
+
import { mkdtemp, readFile, rm, writeFile } from 'fs/promises';
|
|
27
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
28
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
29
|
+
import type * as t from '@/types';
|
|
30
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
31
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
32
|
+
import { createLocalCodingToolBundle } from '@/tools/local';
|
|
33
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
34
|
+
import { getArgs } from '@/scripts/args';
|
|
35
|
+
import { GraphEvents } from '@/common';
|
|
36
|
+
import { Run } from '@/run';
|
|
37
|
+
|
|
38
|
+
const conversationHistory: BaseMessage[] = [];
|
|
39
|
+
|
|
40
|
+
const FILE_A = 'config.json';
|
|
41
|
+
const FILE_B = 'README.md';
|
|
42
|
+
const ORIGINAL_A = '{\n "version": "1.0.0",\n "name": "demo"\n}\n';
|
|
43
|
+
const ORIGINAL_B = '# Demo\n\nThis README is intentionally tiny.\n';
|
|
44
|
+
|
|
45
|
+
async function main(): Promise<void> {
|
|
46
|
+
const { userName, provider } = await getArgs();
|
|
47
|
+
const cwd = await mkdtemp(join(tmpdir(), 'lc-local-cp-'));
|
|
48
|
+
console.log(`[local_engine_checkpointer] workspace: ${cwd}`);
|
|
49
|
+
|
|
50
|
+
await writeFile(join(cwd, FILE_A), ORIGINAL_A, 'utf8');
|
|
51
|
+
await writeFile(join(cwd, FILE_B), ORIGINAL_B, 'utf8');
|
|
52
|
+
|
|
53
|
+
// Build the local-engine tool bundle with file checkpointing on so
|
|
54
|
+
// we can call `bundle.checkpointer.rewind()` after the run.
|
|
55
|
+
const bundle = createLocalCodingToolBundle({
|
|
56
|
+
cwd,
|
|
57
|
+
fileCheckpointing: true,
|
|
58
|
+
timeoutMs: 30_000,
|
|
59
|
+
});
|
|
60
|
+
if (bundle.checkpointer == null) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
'expected bundle.checkpointer to be defined when fileCheckpointing is true'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
67
|
+
const customHandlers = {
|
|
68
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
69
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
70
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
71
|
+
// Forward ON_RUN_STEP so the aggregator's stepMap is seeded
|
|
72
|
+
// before ON_RUN_STEP_COMPLETED arrives — without this the
|
|
73
|
+
// aggregator logs "No run step or runId found for completed step
|
|
74
|
+
// event" once per tool call (issue #142).
|
|
75
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
76
|
+
handle: (
|
|
77
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
78
|
+
data: t.StreamEventData
|
|
79
|
+
): void => {
|
|
80
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
84
|
+
handle: (
|
|
85
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
86
|
+
data: t.StreamEventData
|
|
87
|
+
): void => {
|
|
88
|
+
const cast = data as unknown as { result: t.ToolEndEvent };
|
|
89
|
+
const tc = (cast.result as { tool_call?: { name?: string } } | undefined)
|
|
90
|
+
?.tool_call;
|
|
91
|
+
console.log(`====== ON_RUN_STEP_COMPLETED tool=${tc?.name ?? '?'} ======`);
|
|
92
|
+
aggregateContent({ event, data: cast });
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
96
|
+
handle: (
|
|
97
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
98
|
+
data: t.StreamEventData
|
|
99
|
+
): void => {
|
|
100
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
[GraphEvents.TOOL_START]: {
|
|
104
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
105
|
+
const obj = data as unknown as { name?: string; input?: unknown };
|
|
106
|
+
console.log(
|
|
107
|
+
`====== TOOL_START tool=${obj.name ?? '?'} input=${JSON.stringify(
|
|
108
|
+
obj.input
|
|
109
|
+
)} ======`
|
|
110
|
+
);
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const llmConfig = getLLMConfig(provider);
|
|
116
|
+
|
|
117
|
+
const runConfig: t.RunConfig = {
|
|
118
|
+
runId: 'local-engine-cp-1',
|
|
119
|
+
graphConfig: {
|
|
120
|
+
type: 'standard',
|
|
121
|
+
llmConfig,
|
|
122
|
+
tools: bundle.tools,
|
|
123
|
+
instructions:
|
|
124
|
+
'You are a coding assistant with local file tools. Make exactly the changes ' +
|
|
125
|
+
'the user asks for, no more. Use `edit_file` for single-line literal ' +
|
|
126
|
+
'replacements and `write_file` for full overwrites.',
|
|
127
|
+
},
|
|
128
|
+
returnContent: true,
|
|
129
|
+
skipCleanup: true,
|
|
130
|
+
customHandlers,
|
|
131
|
+
};
|
|
132
|
+
const run = await Run.create<t.IState>(runConfig);
|
|
133
|
+
|
|
134
|
+
const streamConfig = {
|
|
135
|
+
configurable: { provider, thread_id: 'local-engine-cp-thread-1' },
|
|
136
|
+
streamMode: 'values',
|
|
137
|
+
version: 'v2' as const,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const userMessage = new HumanMessage(
|
|
141
|
+
`Hi ${userName}. Please do exactly the following:\n\n` +
|
|
142
|
+
`1. Use \`edit_file\` on \`${FILE_A}\` to replace \`"version": "1.0.0"\` with \`"version": "2.0.0"\`.\n` +
|
|
143
|
+
`2. Use \`write_file\` on \`${FILE_B}\` to overwrite it with the contents:\n` +
|
|
144
|
+
' "# Demo\\n\\nThis README has been overwritten by the agent.\\n"\n\n' +
|
|
145
|
+
'Confirm both changes succeeded. Do not touch any other files.'
|
|
146
|
+
);
|
|
147
|
+
conversationHistory.push(userMessage);
|
|
148
|
+
console.log('====== USER ======\n' + userMessage.content + '\n');
|
|
149
|
+
|
|
150
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
151
|
+
const finalMessages = run.getRunMessages();
|
|
152
|
+
if (finalMessages) {
|
|
153
|
+
conversationHistory.push(...finalMessages);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log('\n====== FINAL CONTENT PARTS ======');
|
|
157
|
+
console.dir(contentParts, { depth: null });
|
|
158
|
+
|
|
159
|
+
// After the run: verify the agent actually changed the files.
|
|
160
|
+
const afterA = await readFile(join(cwd, FILE_A), 'utf8');
|
|
161
|
+
const afterB = await readFile(join(cwd, FILE_B), 'utf8');
|
|
162
|
+
console.log('\n====== AFTER AGENT EDITS ======');
|
|
163
|
+
console.log(`--- ${FILE_A} ---\n${afterA}`);
|
|
164
|
+
console.log(`--- ${FILE_B} ---\n${afterB}`);
|
|
165
|
+
|
|
166
|
+
const aChanged = afterA !== ORIGINAL_A;
|
|
167
|
+
const bChanged = afterB !== ORIGINAL_B;
|
|
168
|
+
console.log(`changed ${FILE_A}: ${aChanged}`);
|
|
169
|
+
console.log(`changed ${FILE_B}: ${bChanged}`);
|
|
170
|
+
|
|
171
|
+
console.log('\n====== CAPTURED CHECKPOINTS ======');
|
|
172
|
+
console.log(bundle.checkpointer.capturedPaths());
|
|
173
|
+
|
|
174
|
+
// Now rewind and verify originals come back.
|
|
175
|
+
const restoredCount = await bundle.checkpointer.rewind();
|
|
176
|
+
console.log(`\n====== AFTER REWIND (restored ${restoredCount} files) ======`);
|
|
177
|
+
const rewoundA = await readFile(join(cwd, FILE_A), 'utf8');
|
|
178
|
+
const rewoundB = await readFile(join(cwd, FILE_B), 'utf8');
|
|
179
|
+
console.log(`--- ${FILE_A} ---\n${rewoundA}`);
|
|
180
|
+
console.log(`--- ${FILE_B} ---\n${rewoundB}`);
|
|
181
|
+
|
|
182
|
+
const aBack = rewoundA === ORIGINAL_A;
|
|
183
|
+
const bBack = rewoundB === ORIGINAL_B;
|
|
184
|
+
console.log(`${FILE_A} restored to original: ${aBack}`);
|
|
185
|
+
console.log(`${FILE_B} restored to original: ${bBack}`);
|
|
186
|
+
|
|
187
|
+
if (!(aBack && bBack)) {
|
|
188
|
+
console.error('\n[checkpointer] rewind did not restore both files (BUG)');
|
|
189
|
+
process.exitCode = 1;
|
|
190
|
+
} else {
|
|
191
|
+
console.log('\n[checkpointer] all files restored to pre-run state ✔');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
await rm(cwd, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
process.on('unhandledRejection', (reason) => {
|
|
198
|
+
console.error('Unhandled Rejection:', reason);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
main().catch((err) => {
|
|
203
|
+
console.error(err);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
});
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/scripts/local_engine_compile.ts
|
|
3
|
+
*
|
|
4
|
+
* Live demonstration of the cheap typecheck integration shipped as
|
|
5
|
+
* an alternative to full LSP:
|
|
6
|
+
*
|
|
7
|
+
* 1. Post-edit syntax check (`local.postEditSyntaxCheck: 'auto'`)
|
|
8
|
+
* runs `node --check` after every edit_file / write_file on
|
|
9
|
+
* `.js`/`.mjs` files — the model sees the parse error in the
|
|
10
|
+
* tool result and self-corrects on the next turn.
|
|
11
|
+
*
|
|
12
|
+
* 2. `compile_check` tool runs the project's standard typecheck
|
|
13
|
+
* command (auto-detected from project markers, or supplied via
|
|
14
|
+
* the `command` arg) and surfaces the result.
|
|
15
|
+
*
|
|
16
|
+
* The script seeds a tiny package.json + tsconfig + index.ts in a
|
|
17
|
+
* temp dir, then asks the model to:
|
|
18
|
+
* (a) `write_file` an intentionally broken `index.js`,
|
|
19
|
+
* (b) call `compile_check` to see the error,
|
|
20
|
+
* (c) fix the file via `edit_file`,
|
|
21
|
+
* (d) call `compile_check` again to confirm green.
|
|
22
|
+
*
|
|
23
|
+
* Run with: `npm run local:compile`
|
|
24
|
+
*/
|
|
25
|
+
import { config } from 'dotenv';
|
|
26
|
+
config();
|
|
27
|
+
import { tmpdir } from 'os';
|
|
28
|
+
import { join } from 'path';
|
|
29
|
+
import { mkdtemp, rm, symlink, writeFile } from 'fs/promises';
|
|
30
|
+
import { resolve } from 'path';
|
|
31
|
+
import { HumanMessage, ToolMessage } from '@langchain/core/messages';
|
|
32
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
33
|
+
import type * as t from '@/types';
|
|
34
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
35
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
36
|
+
import { getLLMConfig } from '@/utils/llmConfig';
|
|
37
|
+
import { getArgs } from '@/scripts/args';
|
|
38
|
+
import { GraphEvents } from '@/common';
|
|
39
|
+
import { Run } from '@/run';
|
|
40
|
+
|
|
41
|
+
const conversationHistory: BaseMessage[] = [];
|
|
42
|
+
|
|
43
|
+
async function main(): Promise<void> {
|
|
44
|
+
const { userName, provider } = await getArgs();
|
|
45
|
+
const cwd = await mkdtemp(join(tmpdir(), 'lc-local-compile-'));
|
|
46
|
+
console.log(`[local_engine_compile] workspace: ${cwd}`);
|
|
47
|
+
|
|
48
|
+
// Seed a tiny TypeScript project so `compile_check` can actually
|
|
49
|
+
// resolve a real toolchain (tsc). We include a healthy index.ts
|
|
50
|
+
// baseline; the LLM will be asked to add a broken file alongside.
|
|
51
|
+
await writeFile(
|
|
52
|
+
join(cwd, 'tsconfig.json'),
|
|
53
|
+
JSON.stringify(
|
|
54
|
+
{
|
|
55
|
+
compilerOptions: {
|
|
56
|
+
target: 'ES2020',
|
|
57
|
+
module: 'commonjs',
|
|
58
|
+
strict: true,
|
|
59
|
+
noEmit: true,
|
|
60
|
+
skipLibCheck: true,
|
|
61
|
+
},
|
|
62
|
+
include: ['*.ts'],
|
|
63
|
+
},
|
|
64
|
+
null,
|
|
65
|
+
2
|
|
66
|
+
),
|
|
67
|
+
'utf8'
|
|
68
|
+
);
|
|
69
|
+
await writeFile(
|
|
70
|
+
join(cwd, 'index.ts'),
|
|
71
|
+
'export function add(a: number, b: number): number {\n return a + b;\n}\n',
|
|
72
|
+
'utf8'
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Make tsc available in the temp workspace by symlinking the
|
|
76
|
+
// surrounding repo's node_modules. This avoids a slow first-run
|
|
77
|
+
// npm install while the model waits, and lets `npx --no-install
|
|
78
|
+
// tsc` resolve typescript instantly. `process.cwd()` is the
|
|
79
|
+
// package root when running via `npm run`. We fall back
|
|
80
|
+
// gracefully on platforms where the symlink isn't possible.
|
|
81
|
+
const repoNodeModules = resolve(process.cwd(), 'node_modules');
|
|
82
|
+
try {
|
|
83
|
+
await symlink(repoNodeModules, join(cwd, 'node_modules'), 'dir');
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.warn(
|
|
86
|
+
'[local_engine_compile] could not symlink node_modules:',
|
|
87
|
+
(err as Error).message
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
await writeFile(
|
|
91
|
+
join(cwd, 'package.json'),
|
|
92
|
+
JSON.stringify({ name: 'lc-local-compile-demo', private: true }, null, 2),
|
|
93
|
+
'utf8'
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const { aggregateContent } = createContentAggregator();
|
|
97
|
+
const customHandlers = {
|
|
98
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
99
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
100
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
101
|
+
// Forward ON_RUN_STEP so the aggregator's stepMap is seeded
|
|
102
|
+
// before ON_RUN_STEP_COMPLETED arrives — without this the
|
|
103
|
+
// aggregator logs "No run step or runId found for completed step
|
|
104
|
+
// event" once per tool call (issue #142).
|
|
105
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
106
|
+
handle: (event: GraphEvents.ON_RUN_STEP, data: t.StreamEventData): void => {
|
|
107
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
111
|
+
handle: (
|
|
112
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
113
|
+
data: t.StreamEventData
|
|
114
|
+
): void => {
|
|
115
|
+
aggregateContent({
|
|
116
|
+
event,
|
|
117
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
[GraphEvents.TOOL_START]: {
|
|
122
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
123
|
+
const obj = data as unknown as { name?: string; input?: unknown };
|
|
124
|
+
const inputStr = JSON.stringify(obj.input);
|
|
125
|
+
console.log(
|
|
126
|
+
`====== TOOL_START tool=${obj.name ?? '?'} input=${
|
|
127
|
+
inputStr.length > 200 ? inputStr.slice(0, 200) + '…' : inputStr
|
|
128
|
+
} ======`
|
|
129
|
+
);
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const llmConfig = getLLMConfig(provider);
|
|
135
|
+
|
|
136
|
+
const runConfig: t.RunConfig = {
|
|
137
|
+
runId: 'local-engine-compile-1',
|
|
138
|
+
graphConfig: {
|
|
139
|
+
type: 'standard',
|
|
140
|
+
llmConfig,
|
|
141
|
+
instructions:
|
|
142
|
+
`You are ${userName}'s coding assistant. The host has wired a ` +
|
|
143
|
+
'post-edit syntax check (you will see `[syntax-check warning ...]` blocks ' +
|
|
144
|
+
'in tool results) and the `compile_check` tool. Use them to verify your ' +
|
|
145
|
+
'edits. When a syntax check warns you of an error, FIX IT before doing ' +
|
|
146
|
+
'anything else. Keep responses tight.',
|
|
147
|
+
},
|
|
148
|
+
toolExecution: {
|
|
149
|
+
engine: 'local',
|
|
150
|
+
local: {
|
|
151
|
+
cwd,
|
|
152
|
+
postEditSyntaxCheck: 'auto',
|
|
153
|
+
timeoutMs: 30_000,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
returnContent: true,
|
|
157
|
+
skipCleanup: true,
|
|
158
|
+
customHandlers,
|
|
159
|
+
};
|
|
160
|
+
const run = await Run.create<t.IState>(runConfig);
|
|
161
|
+
|
|
162
|
+
const streamConfig = {
|
|
163
|
+
configurable: { provider, thread_id: 'local-engine-compile-thread-1' },
|
|
164
|
+
streamMode: 'values',
|
|
165
|
+
version: 'v2' as const,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const userMessage = new HumanMessage(
|
|
169
|
+
`Hi ${userName}. Here's the workflow. The workspace is a tiny TypeScript project (tsconfig.json + index.ts).\n\n` +
|
|
170
|
+
'1. First, exercise the post-edit syntax check. Use `write_file` to create `broken.js` with EXACTLY this content (it is intentionally broken):\n' +
|
|
171
|
+
'```\n' +
|
|
172
|
+
'function broken(a,) {\n' +
|
|
173
|
+
' return a +;\n' +
|
|
174
|
+
'}\n' +
|
|
175
|
+
'```\n' +
|
|
176
|
+
'2. After the write, the post-edit syntax check should warn you with `[syntax-check warning ...]`. Read the warning carefully and fix the file via `edit_file` or `write_file`. The smallest valid fix is fine.\n\n' +
|
|
177
|
+
'3. Now exercise `compile_check`. Use `write_file` to create `broken.ts` with EXACTLY this content (it has a TYPE error, not a syntax error — `tsc` will catch it):\n' +
|
|
178
|
+
'```\n' +
|
|
179
|
+
'export const x: number = "not a number";\n' +
|
|
180
|
+
'```\n' +
|
|
181
|
+
'4. Call `compile_check` (no args). Anthropic\'s tsc should fail with a type error.\n' +
|
|
182
|
+
'5. Fix the type error in `broken.ts` (e.g. change the literal to a number) via `edit_file`.\n' +
|
|
183
|
+
'6. Call `compile_check` again to confirm green.\n\n' +
|
|
184
|
+
'Tell me what each `compile_check` call reported. Be concise.'
|
|
185
|
+
);
|
|
186
|
+
conversationHistory.push(userMessage);
|
|
187
|
+
console.log('====== USER ======\n' + userMessage.content + '\n');
|
|
188
|
+
|
|
189
|
+
await run.processStream({ messages: conversationHistory }, streamConfig);
|
|
190
|
+
const finalMessages = run.getRunMessages();
|
|
191
|
+
if (finalMessages) {
|
|
192
|
+
conversationHistory.push(...finalMessages);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let sawSyntaxWarning = false;
|
|
196
|
+
let compileCheckCalls = 0;
|
|
197
|
+
let compileCheckFailedAtLeastOnce = false;
|
|
198
|
+
let compileCheckPassedAtLeastOnce = false;
|
|
199
|
+
console.log('\n====== TOOL MESSAGES IN HISTORY ======');
|
|
200
|
+
for (const msg of conversationHistory) {
|
|
201
|
+
if (msg instanceof ToolMessage) {
|
|
202
|
+
const content =
|
|
203
|
+
typeof msg.content === 'string'
|
|
204
|
+
? msg.content
|
|
205
|
+
: JSON.stringify(msg.content);
|
|
206
|
+
const head = content.slice(0, 240);
|
|
207
|
+
console.log(`- ${msg.name}: ${head.replace(/\n/g, ' ⏎ ')}`);
|
|
208
|
+
if (content.includes('[syntax-check')) sawSyntaxWarning = true;
|
|
209
|
+
if (msg.name === 'compile_check') {
|
|
210
|
+
compileCheckCalls += 1;
|
|
211
|
+
if (content.includes('PASSED')) compileCheckPassedAtLeastOnce = true;
|
|
212
|
+
if (content.includes('FAILED')) compileCheckFailedAtLeastOnce = true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log('\n====== ASSISTANT FINAL TEXT ======');
|
|
218
|
+
const lastAssistant = [...conversationHistory]
|
|
219
|
+
.reverse()
|
|
220
|
+
.find((m) => m._getType() === 'ai');
|
|
221
|
+
if (lastAssistant) {
|
|
222
|
+
const c = lastAssistant.content;
|
|
223
|
+
console.log(
|
|
224
|
+
typeof c === 'string'
|
|
225
|
+
? c
|
|
226
|
+
: Array.isArray(c)
|
|
227
|
+
? c
|
|
228
|
+
.map((b) => ('text' in b ? b.text : `<${b.type}>`))
|
|
229
|
+
.join(' ')
|
|
230
|
+
: JSON.stringify(c)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log('\n====== ASSERTIONS ======');
|
|
235
|
+
console.log(
|
|
236
|
+
`post-edit syntax-check warning observed: ${sawSyntaxWarning} ${
|
|
237
|
+
sawSyntaxWarning ? '✔' : '✖'
|
|
238
|
+
}`
|
|
239
|
+
);
|
|
240
|
+
console.log(
|
|
241
|
+
`compile_check called ${compileCheckCalls} time(s); FAILED-then-PASSED cycle observed: ${
|
|
242
|
+
compileCheckFailedAtLeastOnce && compileCheckPassedAtLeastOnce
|
|
243
|
+
} ${compileCheckFailedAtLeastOnce && compileCheckPassedAtLeastOnce ? '✔' : '✖'}`
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (
|
|
247
|
+
!sawSyntaxWarning ||
|
|
248
|
+
!(compileCheckFailedAtLeastOnce && compileCheckPassedAtLeastOnce)
|
|
249
|
+
) {
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
}
|
|
252
|
+
await rm(cwd, { recursive: true, force: true });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
process.on('unhandledRejection', (reason) => {
|
|
256
|
+
console.error('Unhandled Rejection:', reason);
|
|
257
|
+
process.exit(1);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
main().catch((err) => {
|
|
261
|
+
console.error(err);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
});
|