@lasso-ai/cli 1.4.5 → 2.0.0
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/cli/agent.d.ts +10 -89
- package/dist/cli/agent.js +66 -1412
- package/dist/cli/backends.d.ts +53 -0
- package/dist/cli/backends.js +267 -0
- package/dist/cli/bridge-old.d.ts +259 -0
- package/dist/cli/bridge-old.js +1350 -0
- package/dist/cli/bridge.d.ts +22 -12
- package/dist/cli/bridge.js +824 -1174
- package/dist/cli/host/install.js +12 -1
- package/dist/cli/host/next-host-entry.js +0 -2
- package/dist/cli/host/vite-host-entry.js +1 -3
- package/dist/cli/local-runtime.d.ts +28 -0
- package/dist/cli/local-runtime.js +294 -0
- package/dist/cli/oneshot.js +177 -961
- package/dist/cli/runtime-client.d.ts +310 -0
- package/dist/cli/runtime-client.js +178 -0
- package/dist/cli/snapshot.d.ts +3 -1
- package/dist/cli/snapshot.js +8 -1
- package/dist/overlay.js +44 -28
- package/dist/runtime/__tests__/adapter-normalization.test.d.ts +1 -0
- package/dist/runtime/__tests__/adapter-normalization.test.js +392 -0
- package/dist/runtime/__tests__/state-machine.test.d.ts +1 -0
- package/dist/runtime/__tests__/state-machine.test.js +107 -0
- package/dist/runtime/__tests__/verification-engine.test.d.ts +1 -0
- package/dist/runtime/__tests__/verification-engine.test.js +91 -0
- package/dist/runtime/__tests__/workspace-guard.test.d.ts +1 -0
- package/dist/runtime/__tests__/workspace-guard.test.js +104 -0
- package/dist/runtime/adapters/claude-code-adapter.d.ts +46 -0
- package/dist/runtime/adapters/claude-code-adapter.js +230 -0
- package/dist/runtime/adapters/codex-cli-adapter.d.ts +44 -0
- package/dist/runtime/adapters/codex-cli-adapter.js +202 -0
- package/dist/runtime/adapters/cursor-adapter.d.ts +47 -0
- package/dist/runtime/adapters/cursor-adapter.js +214 -0
- package/dist/runtime/adapters/factory.d.ts +19 -0
- package/dist/runtime/adapters/factory.js +48 -0
- package/dist/runtime/adapters/local-agent-adapter.d.ts +143 -0
- package/dist/runtime/adapters/local-agent-adapter.js +408 -0
- package/dist/runtime/adapters/opencode-adapter.d.ts +43 -0
- package/dist/runtime/adapters/opencode-adapter.js +210 -0
- package/dist/runtime/agent-runtime.d.ts +286 -0
- package/dist/runtime/agent-runtime.js +8 -0
- package/dist/runtime/agent-state-machine.d.ts +67 -0
- package/dist/runtime/agent-state-machine.js +234 -0
- package/dist/runtime/backend-interface.d.ts +272 -0
- package/dist/runtime/backend-interface.js +38 -0
- package/dist/runtime/event-mapper.d.ts +11 -0
- package/dist/runtime/event-mapper.js +40 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +43 -0
- package/dist/runtime/local-session-runner.d.ts +60 -0
- package/dist/runtime/local-session-runner.js +185 -0
- package/dist/runtime/review.d.ts +24 -0
- package/dist/runtime/review.js +30 -0
- package/dist/runtime/verification-engine.d.ts +71 -0
- package/dist/runtime/verification-engine.js +374 -0
- package/dist/runtime/workspace-guard.d.ts +70 -0
- package/dist/runtime/workspace-guard.js +321 -0
- package/docs/local-coding-agents.md +183 -0
- package/package.json +22 -3
package/dist/cli/host/install.js
CHANGED
|
@@ -27,7 +27,18 @@ const AGENT_LABEL = "com.lasso.host";
|
|
|
27
27
|
const LAUNCH_AGENT_FILE = node_path_1.default.join(node_os_1.default.homedir(), "Library", "LaunchAgents", `${AGENT_LABEL}.plist`);
|
|
28
28
|
function cliEntry() {
|
|
29
29
|
const entry = process.argv[1];
|
|
30
|
-
|
|
30
|
+
if (!entry)
|
|
31
|
+
return null;
|
|
32
|
+
// Resolve symlinks (e.g. pnpm's global bin is extensionless) so the
|
|
33
|
+
// spawned child gets a real .js path.
|
|
34
|
+
let resolved = entry;
|
|
35
|
+
try {
|
|
36
|
+
resolved = node_fs_1.default.realpathSync(entry);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// keep the original if realpath fails
|
|
40
|
+
}
|
|
41
|
+
return resolved.endsWith(".js") ? resolved : null;
|
|
31
42
|
}
|
|
32
43
|
function spawnDaemon(env) {
|
|
33
44
|
const port = (0, paths_1.hostProxyPort)(env);
|
|
@@ -130,13 +130,11 @@ void (async () => {
|
|
|
130
130
|
await waitForNext(nextPort, nextProcess);
|
|
131
131
|
}
|
|
132
132
|
catch (error) {
|
|
133
|
-
bridge.close();
|
|
134
133
|
nextProcess.kill("SIGTERM");
|
|
135
134
|
throw error;
|
|
136
135
|
}
|
|
137
136
|
server.listen(publicPort, "127.0.0.1");
|
|
138
137
|
const shutdown = () => {
|
|
139
|
-
bridge.close();
|
|
140
138
|
nextProcess.kill("SIGTERM");
|
|
141
139
|
proxy.close();
|
|
142
140
|
server.close(() => process.exit(0));
|
|
@@ -27,7 +27,7 @@ function lassoOverlayPlugin() {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
void (async () => {
|
|
30
|
-
|
|
30
|
+
await (0, bridge_1.startBridge)(cwd, null, bridgePort);
|
|
31
31
|
const require = (0, node_module_1.createRequire)(node_path_1.default.join(cwd, "package.json"));
|
|
32
32
|
const viteEntry = require.resolve("vite");
|
|
33
33
|
const { createServer } = await import((0, node_url_1.pathToFileURL)(viteEntry).href);
|
|
@@ -49,11 +49,9 @@ void (async () => {
|
|
|
49
49
|
});
|
|
50
50
|
await server.listen();
|
|
51
51
|
process.on("SIGTERM", () => {
|
|
52
|
-
bridge.close();
|
|
53
52
|
void server.close().then(() => process.exit(0));
|
|
54
53
|
});
|
|
55
54
|
process.on("SIGINT", () => {
|
|
56
|
-
bridge.close();
|
|
57
55
|
void server.close().then(() => process.exit(0));
|
|
58
56
|
});
|
|
59
57
|
})();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalRuntime — local coding agent execution on the user's machine.
|
|
3
|
+
*
|
|
4
|
+
* Implements the same session surface the CLI bridge uses for hosted
|
|
5
|
+
* sessions (RuntimeClient), but runs Claude Code / OpenCode / Codex CLI /
|
|
6
|
+
* Cursor Agent in THIS process: prompts, project context, and source code
|
|
7
|
+
* never travel through Lasso's server. Session state is in-memory (per CLI
|
|
8
|
+
* process) and the review-first workflow comes from the shared runtime core
|
|
9
|
+
* (LocalSessionRunner + WorkspaceGuard + VerificationEngine).
|
|
10
|
+
*/
|
|
11
|
+
import type { RuntimeSession, RuntimeSessionInput, RuntimeEventHandler, SessionVerificationResult, RuntimeFacade } from "./runtime-client.js";
|
|
12
|
+
export declare class LocalRuntime implements RuntimeFacade {
|
|
13
|
+
private sessions;
|
|
14
|
+
private abortControllers;
|
|
15
|
+
createSession(input: RuntimeSessionInput): Promise<RuntimeSession>;
|
|
16
|
+
getSession(sessionId: string): Promise<RuntimeSession>;
|
|
17
|
+
runSession(sessionId: string, projectPath: string, onEvent: RuntimeEventHandler, signal?: AbortSignal): Promise<void>;
|
|
18
|
+
verifySession(sessionId: string, projectPath: string): Promise<SessionVerificationResult>;
|
|
19
|
+
switchModel(sessionId: string, model: {
|
|
20
|
+
provider: string;
|
|
21
|
+
id: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
}, _apiKey?: string, backendConfig?: RuntimeSessionInput["backendConfig"]): Promise<void>;
|
|
24
|
+
cancelSession(sessionId: string): Promise<void>;
|
|
25
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
26
|
+
private toSession;
|
|
27
|
+
}
|
|
28
|
+
export declare function getLocalRuntime(): LocalRuntime;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LocalRuntime — local coding agent execution on the user's machine.
|
|
4
|
+
*
|
|
5
|
+
* Implements the same session surface the CLI bridge uses for hosted
|
|
6
|
+
* sessions (RuntimeClient), but runs Claude Code / OpenCode / Codex CLI /
|
|
7
|
+
* Cursor Agent in THIS process: prompts, project context, and source code
|
|
8
|
+
* never travel through Lasso's server. Session state is in-memory (per CLI
|
|
9
|
+
* process) and the review-first workflow comes from the shared runtime core
|
|
10
|
+
* (LocalSessionRunner + WorkspaceGuard + VerificationEngine).
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.LocalRuntime = void 0;
|
|
17
|
+
exports.getLocalRuntime = getLocalRuntime;
|
|
18
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
19
|
+
const index_js_1 = require("../runtime/index.js");
|
|
20
|
+
class LocalRuntime {
|
|
21
|
+
sessions = new Map();
|
|
22
|
+
abortControllers = new Map();
|
|
23
|
+
async createSession(input) {
|
|
24
|
+
const backendConfig = input.backendConfig;
|
|
25
|
+
if (!(0, index_js_1.isLocalAgentConfig)(backendConfig)) {
|
|
26
|
+
throw new Error("LocalRuntime only accepts local_agent backend configs");
|
|
27
|
+
}
|
|
28
|
+
if (!backendConfig.projectPath) {
|
|
29
|
+
throw new Error("LocalRuntime requires backendConfig.projectPath");
|
|
30
|
+
}
|
|
31
|
+
const id = node_crypto_1.default.randomUUID();
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const record = {
|
|
34
|
+
id,
|
|
35
|
+
phase: "execute",
|
|
36
|
+
status: "running",
|
|
37
|
+
iteration: 0,
|
|
38
|
+
userPrompt: input.userPrompt,
|
|
39
|
+
conversation: [
|
|
40
|
+
{ id: node_crypto_1.default.randomUUID(), role: "user", content: input.userPrompt, timestamp: now.getTime() },
|
|
41
|
+
],
|
|
42
|
+
observations: [],
|
|
43
|
+
actions: [],
|
|
44
|
+
completedActions: [],
|
|
45
|
+
errors: [],
|
|
46
|
+
selection: input.selection,
|
|
47
|
+
project: input.project,
|
|
48
|
+
model: input.model,
|
|
49
|
+
createdAt: now,
|
|
50
|
+
updatedAt: now,
|
|
51
|
+
metadata: { backendConfig },
|
|
52
|
+
backendConfig: { ...backendConfig, type: "local_agent" },
|
|
53
|
+
projectPath: backendConfig.projectPath,
|
|
54
|
+
input,
|
|
55
|
+
running: false,
|
|
56
|
+
};
|
|
57
|
+
this.sessions.set(id, record);
|
|
58
|
+
return this.toSession(record);
|
|
59
|
+
}
|
|
60
|
+
async getSession(sessionId) {
|
|
61
|
+
const record = this.sessions.get(sessionId);
|
|
62
|
+
if (!record)
|
|
63
|
+
throw new Error("Session not found");
|
|
64
|
+
return this.toSession(record);
|
|
65
|
+
}
|
|
66
|
+
async runSession(sessionId, projectPath, onEvent, signal) {
|
|
67
|
+
const record = this.sessions.get(sessionId);
|
|
68
|
+
if (!record)
|
|
69
|
+
throw new Error("Session not found");
|
|
70
|
+
if (record.running)
|
|
71
|
+
throw new Error("Session is already running");
|
|
72
|
+
record.running = true;
|
|
73
|
+
record.status = "running";
|
|
74
|
+
record.phase = "execute";
|
|
75
|
+
record.updatedAt = new Date();
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
this.abortControllers.set(sessionId, controller);
|
|
78
|
+
const onOuterAbort = () => controller.abort();
|
|
79
|
+
signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
80
|
+
const emit = (event) => {
|
|
81
|
+
void onEvent(event);
|
|
82
|
+
};
|
|
83
|
+
try {
|
|
84
|
+
// Fresh capture per run — corrective re-runs capture the post-apply state.
|
|
85
|
+
const guard = await index_js_1.WorkspaceGuard.capture(projectPath);
|
|
86
|
+
record.backend =
|
|
87
|
+
record.backend ||
|
|
88
|
+
(await (0, index_js_1.createLocalAgentBackend)({ ...record.backendConfig, type: "local_agent", projectPath }));
|
|
89
|
+
const previousErrors = (record.errors || []);
|
|
90
|
+
const externalSessionId = typeof record.metadata.backendSessionId === "string" ? record.metadata.backendSessionId : undefined;
|
|
91
|
+
const runner = new index_js_1.LocalSessionRunner({
|
|
92
|
+
sessionId: record.id,
|
|
93
|
+
backend: record.backend,
|
|
94
|
+
guard,
|
|
95
|
+
emit,
|
|
96
|
+
phase: "execute",
|
|
97
|
+
});
|
|
98
|
+
const outcome = await runner.run({
|
|
99
|
+
taskId: record.id,
|
|
100
|
+
userPrompt: record.userPrompt,
|
|
101
|
+
project: {
|
|
102
|
+
projectId: record.project.projectId || "local",
|
|
103
|
+
framework: record.project.framework,
|
|
104
|
+
hasTypeScript: Boolean(record.project.hasTypeScript),
|
|
105
|
+
hasTailwind: false,
|
|
106
|
+
hasReact: Boolean(record.project.hasReact),
|
|
107
|
+
hasNext: Boolean(record.project.hasNext),
|
|
108
|
+
hasVite: Boolean(record.project.hasVite),
|
|
109
|
+
files: record.project.files || [],
|
|
110
|
+
},
|
|
111
|
+
selection: record.selection,
|
|
112
|
+
conversation: record.conversation.map((msg) => ({
|
|
113
|
+
role: msg.role,
|
|
114
|
+
content: msg.content,
|
|
115
|
+
timestamp: msg.timestamp,
|
|
116
|
+
})),
|
|
117
|
+
previousObservations: [],
|
|
118
|
+
previousErrors,
|
|
119
|
+
currentPhase: "execute",
|
|
120
|
+
constraints: { safetyLevel: "standard" },
|
|
121
|
+
sessionRef: externalSessionId ? { id: externalSessionId } : undefined,
|
|
122
|
+
}, controller.signal);
|
|
123
|
+
// Persist review payload + normalized actions for the review flow.
|
|
124
|
+
record.actions = outcome.actions;
|
|
125
|
+
record.observations.push(...outcome.observations.map((obs) => ({
|
|
126
|
+
id: obs.id,
|
|
127
|
+
type: String(obs.type),
|
|
128
|
+
summary: obs.summary,
|
|
129
|
+
data: obs.data,
|
|
130
|
+
timestamp: obs.timestamp,
|
|
131
|
+
phase: String(obs.phase),
|
|
132
|
+
})));
|
|
133
|
+
record.errors = outcome.errors.map((err) => ({
|
|
134
|
+
id: err.id,
|
|
135
|
+
type: String(err.type),
|
|
136
|
+
message: err.message,
|
|
137
|
+
phase: String(err.phase),
|
|
138
|
+
timestamp: err.timestamp,
|
|
139
|
+
recoverable: err.recoverable,
|
|
140
|
+
}));
|
|
141
|
+
if (outcome.conversation.length > 0) {
|
|
142
|
+
record.conversation.push(...outcome.conversation.map((msg) => ({
|
|
143
|
+
id: node_crypto_1.default.randomUUID(),
|
|
144
|
+
role: msg.role,
|
|
145
|
+
content: msg.content,
|
|
146
|
+
timestamp: msg.timestamp,
|
|
147
|
+
})));
|
|
148
|
+
}
|
|
149
|
+
record.metadata = {
|
|
150
|
+
...record.metadata,
|
|
151
|
+
...outcome.metadata,
|
|
152
|
+
pendingChanges: outcome.changes,
|
|
153
|
+
binaryChanges: outcome.binaryPaths,
|
|
154
|
+
};
|
|
155
|
+
record.status =
|
|
156
|
+
outcome.status === "waiting" ? "waiting" : outcome.status === "completed" ? "completed" : "failed";
|
|
157
|
+
record.phase = "complete";
|
|
158
|
+
record.updatedAt = new Date();
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
record.status = "failed";
|
|
162
|
+
record.errors = [
|
|
163
|
+
...record.errors,
|
|
164
|
+
{
|
|
165
|
+
id: node_crypto_1.default.randomUUID(),
|
|
166
|
+
type: "execution",
|
|
167
|
+
message: error instanceof Error ? error.message : "Local agent run failed",
|
|
168
|
+
phase: "execute",
|
|
169
|
+
timestamp: Date.now(),
|
|
170
|
+
recoverable: false,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
record.updatedAt = new Date();
|
|
174
|
+
emit({
|
|
175
|
+
type: "error",
|
|
176
|
+
sessionId: record.id,
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
data: { error: error instanceof Error ? error.message : "Local agent run failed" },
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
signal?.removeEventListener("abort", onOuterAbort);
|
|
183
|
+
this.abortControllers.delete(sessionId);
|
|
184
|
+
record.running = false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async verifySession(sessionId, projectPath) {
|
|
188
|
+
const record = this.sessions.get(sessionId);
|
|
189
|
+
if (!record)
|
|
190
|
+
throw new Error("Session not found");
|
|
191
|
+
const engine = new index_js_1.VerificationEngine(projectPath);
|
|
192
|
+
const results = await engine.verifyAll("verify");
|
|
193
|
+
const observations = engine.getObservations(results);
|
|
194
|
+
const errors = engine.getErrors(results);
|
|
195
|
+
const passed = engine.allPassed(results);
|
|
196
|
+
// Keep verification errors so a corrective re-run receives them as context.
|
|
197
|
+
if (!passed) {
|
|
198
|
+
record.errors = [
|
|
199
|
+
...record.errors,
|
|
200
|
+
...errors.map((err) => ({
|
|
201
|
+
id: err.id,
|
|
202
|
+
type: String(err.type),
|
|
203
|
+
message: err.message,
|
|
204
|
+
phase: String(err.phase),
|
|
205
|
+
timestamp: err.timestamp,
|
|
206
|
+
recoverable: err.recoverable,
|
|
207
|
+
})),
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
const command = results.map((r) => r.command).find((c) => typeof c === "string");
|
|
211
|
+
return { passed, errors, observations, command, requiresFix: !passed };
|
|
212
|
+
}
|
|
213
|
+
async switchModel(sessionId, model, _apiKey, backendConfig) {
|
|
214
|
+
const record = this.sessions.get(sessionId);
|
|
215
|
+
if (!record)
|
|
216
|
+
throw new Error("Session not found");
|
|
217
|
+
if (!(0, index_js_1.isLocalAgentConfig)(backendConfig)) {
|
|
218
|
+
throw new Error("LocalRuntime.switchModel requires a local_agent backendConfig");
|
|
219
|
+
}
|
|
220
|
+
const nextBackend = await (0, index_js_1.createLocalAgentBackend)({ ...backendConfig, type: "local_agent" });
|
|
221
|
+
if (record.backend) {
|
|
222
|
+
await record.backend.switchBackend(nextBackend, {
|
|
223
|
+
taskId: record.id,
|
|
224
|
+
userPrompt: record.userPrompt,
|
|
225
|
+
project: {
|
|
226
|
+
projectId: record.project.projectId || "local",
|
|
227
|
+
hasTypeScript: Boolean(record.project.hasTypeScript),
|
|
228
|
+
hasTailwind: false,
|
|
229
|
+
hasReact: Boolean(record.project.hasReact),
|
|
230
|
+
hasNext: Boolean(record.project.hasNext),
|
|
231
|
+
hasVite: Boolean(record.project.hasVite),
|
|
232
|
+
files: record.project.files || [],
|
|
233
|
+
},
|
|
234
|
+
conversation: [],
|
|
235
|
+
previousObservations: [],
|
|
236
|
+
previousErrors: [],
|
|
237
|
+
currentPhase: "execute",
|
|
238
|
+
constraints: {},
|
|
239
|
+
sessionRef: typeof record.metadata.backendSessionId === "string"
|
|
240
|
+
? { id: record.metadata.backendSessionId }
|
|
241
|
+
: undefined,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
record.backend = nextBackend;
|
|
245
|
+
record.backendConfig = { ...backendConfig, type: "local_agent" };
|
|
246
|
+
record.model = { provider: model.provider, id: model.id, name: model.name };
|
|
247
|
+
record.metadata = {
|
|
248
|
+
...record.metadata,
|
|
249
|
+
backendConfig: record.backendConfig,
|
|
250
|
+
backendModel: model.id,
|
|
251
|
+
};
|
|
252
|
+
// Switching agents drops the previous agent's external session: context
|
|
253
|
+
// cannot be transferred across CLI agents.
|
|
254
|
+
delete record.metadata.backendSessionId;
|
|
255
|
+
record.updatedAt = new Date();
|
|
256
|
+
}
|
|
257
|
+
async cancelSession(sessionId) {
|
|
258
|
+
const controller = this.abortControllers.get(sessionId);
|
|
259
|
+
if (controller)
|
|
260
|
+
controller.abort();
|
|
261
|
+
}
|
|
262
|
+
async deleteSession(sessionId) {
|
|
263
|
+
this.sessions.delete(sessionId);
|
|
264
|
+
this.abortControllers.delete(sessionId);
|
|
265
|
+
}
|
|
266
|
+
toSession(record) {
|
|
267
|
+
return {
|
|
268
|
+
id: record.id,
|
|
269
|
+
phase: record.phase,
|
|
270
|
+
status: record.status,
|
|
271
|
+
iteration: record.iteration,
|
|
272
|
+
userPrompt: record.userPrompt,
|
|
273
|
+
conversation: record.conversation,
|
|
274
|
+
observations: record.observations,
|
|
275
|
+
actions: record.actions,
|
|
276
|
+
completedActions: record.completedActions,
|
|
277
|
+
errors: record.errors,
|
|
278
|
+
selection: record.selection,
|
|
279
|
+
project: record.project,
|
|
280
|
+
model: record.model,
|
|
281
|
+
createdAt: record.createdAt,
|
|
282
|
+
updatedAt: record.updatedAt,
|
|
283
|
+
metadata: record.metadata,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.LocalRuntime = LocalRuntime;
|
|
288
|
+
/** Process-wide local runtime (sessions live for the CLI bridge lifetime). */
|
|
289
|
+
let sharedLocalRuntime;
|
|
290
|
+
function getLocalRuntime() {
|
|
291
|
+
if (!sharedLocalRuntime)
|
|
292
|
+
sharedLocalRuntime = new LocalRuntime();
|
|
293
|
+
return sharedLocalRuntime;
|
|
294
|
+
}
|