@aexol/spectral 0.9.234 → 0.9.240
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 +47 -0
- package/dist/agent/index.d.ts +32 -5
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +120 -22
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -4
- package/dist/commands/desktop.d.ts +167 -0
- package/dist/commands/desktop.d.ts.map +1 -0
- package/dist/commands/desktop.js +540 -0
- package/dist/commands/serve.d.ts +9 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +78 -1
- package/dist/commands/update.d.ts +85 -5
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +288 -58
- package/dist/extensions/desktop-control/macos.d.ts.map +1 -1
- package/dist/extensions/desktop-control/macos.js +2 -2
- package/dist/mcp/server-manager.d.ts.map +1 -1
- package/dist/mcp/server-manager.js +6 -0
- package/dist/memory/tools/receive-agent-observations.d.ts.map +1 -1
- package/dist/memory/tools/receive-agent-observations.js +12 -2
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +30 -2
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +8 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/system-prompt.js +5 -0
- package/dist/server/agent-bridge/tps-metrics.d.ts +34 -0
- package/dist/server/agent-bridge/tps-metrics.d.ts.map +1 -1
- package/dist/server/agent-bridge/tps-metrics.js +22 -0
- package/dist/server/agent-bridge.d.ts +9 -0
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +83 -3
- package/dist/server/handlers/paths-list.d.ts +1 -1
- package/dist/server/handlers/paths-list.js +1 -1
- package/dist/server/inter-agent-broker.d.ts +12 -0
- package/dist/server/inter-agent-broker.d.ts.map +1 -1
- package/dist/server/inter-agent-broker.js +1 -0
- package/dist/server/preset-chain.d.ts +151 -0
- package/dist/server/preset-chain.d.ts.map +1 -0
- package/dist/server/preset-chain.js +250 -0
- package/dist/server/session-stream.d.ts +55 -1
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +203 -24
- package/dist/server/storage/rows.d.ts +2 -0
- package/dist/server/storage/rows.d.ts.map +1 -1
- package/dist/server/storage/schema.d.ts +2 -2
- package/dist/server/storage/schema.d.ts.map +1 -1
- package/dist/server/storage/schema.js +2 -1
- package/dist/server/storage.d.ts +7 -0
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +142 -9
- package/dist/server/wire.d.ts +71 -0
- package/dist/server/wire.d.ts.map +1 -1
- package/dist/utils/augment-path.d.ts +51 -0
- package/dist/utils/augment-path.d.ts.map +1 -0
- package/dist/utils/augment-path.js +234 -0
- package/dist/utils/open-browser.d.ts +55 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +163 -0
- package/dist/utils/serve-state.d.ts +264 -0
- package/dist/utils/serve-state.d.ts.map +1 -0
- package/dist/utils/serve-state.js +563 -0
- package/dist/utils/standalone.d.ts +14 -0
- package/dist/utils/standalone.d.ts.map +1 -0
- package/dist/utils/standalone.js +23 -0
- package/package.json +1 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desktop.d.ts","sourceRoot":"","sources":["../../src/commands/desktop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `spectral desktop` — the thin, headless replacement for the Electron shell.
|
|
3
|
+
*
|
|
4
|
+
* What the Electron app actually contributed was four things, all reproduced
|
|
5
|
+
* here without a bundled Chromium:
|
|
6
|
+
*
|
|
7
|
+
* 1. single-instance guarantee → `~/.spectral/serve.pid` + liveness check
|
|
8
|
+
* 1. single-instance guarantee → `~/.spectral/serve.pid`, claimed
|
|
9
|
+
* atomically (`O_EXCL`) before the spawn
|
|
10
|
+
* 2. start the local relay daemon → detached `spectral serve`, unref'd,
|
|
11
|
+
* stdout+stderr appended to serve.log
|
|
12
|
+
* 3. "wait until it is really up" → the `SPECTRAL_SERVE_READY` stdout marker
|
|
13
|
+
* and `serve-ready.json` (see
|
|
14
|
+
* src/utils/serve-state.ts), replacing the
|
|
15
|
+
* old "assume ready after 3 s" heuristic
|
|
16
|
+
* 4. show the UI → open the system browser at
|
|
17
|
+
* https://aexol.ai/agent
|
|
18
|
+
* (override: SPECTRAL_LANDING_URL)
|
|
19
|
+
*
|
|
20
|
+
* The PATH augmentation the app performed for its child process now happens in
|
|
21
|
+
* `augmentPath()` (src/utils/augment-path.ts) and is applied to the spawned
|
|
22
|
+
* daemon, so MCP servers launched via `npx` keep working.
|
|
23
|
+
*
|
|
24
|
+
* What is deliberately NOT reproduced (see the removal notes): the splash
|
|
25
|
+
* window, in-app auto-update (use `spectral update`), the native app menu, and
|
|
26
|
+
* the JWT-via-IPC login bridge — authentication goes through the existing
|
|
27
|
+
* `spectral login` browser flow, which already writes `userJwt` to
|
|
28
|
+
* `~/.spectral/config.json`.
|
|
29
|
+
*
|
|
30
|
+
* Exit codes: 0 success / already running, 1 failure, 3 stopped-state for
|
|
31
|
+
* `status` when the daemon is not running.
|
|
32
|
+
*/
|
|
33
|
+
import { closeSync, fchmodSync, existsSync, mkdirSync, openSync } from "node:fs";
|
|
34
|
+
import { spawn } from "node:child_process";
|
|
35
|
+
import { homedir } from "node:os";
|
|
36
|
+
import { basename, dirname, join } from "node:path";
|
|
37
|
+
import { fileURLToPath } from "node:url";
|
|
38
|
+
import pc from "picocolors";
|
|
39
|
+
import { readConfig } from "../config.js";
|
|
40
|
+
import { getVersion } from "../version.js";
|
|
41
|
+
import { augmentPath, getPathEnvKey } from "../utils/augment-path.js";
|
|
42
|
+
import { openBrowser } from "../utils/open-browser.js";
|
|
43
|
+
import { isStandaloneBinary } from "../utils/standalone.js";
|
|
44
|
+
import { claimServePidFile, clearServeState, confirmServePidChild, fileSizeOf, getLiveServePid, getServeLogFile, getServePidFile, isProcessAlive, readServePidFile, readServeReadyFile, releaseServePidClaim, rotateServeLogFile, DEFAULT_SERVE_LOG_MAX_BYTES, waitForServeReady, writeServePid, } from "../utils/serve-state.js";
|
|
45
|
+
/** Where the Studio agent UI lives. */
|
|
46
|
+
export const DEFAULT_LANDING_URL = "https://aexol.ai/agent";
|
|
47
|
+
/** How long to wait for the daemon's readiness signal. */
|
|
48
|
+
export const DEFAULT_READY_TIMEOUT_MS = 15_000;
|
|
49
|
+
/** Grace period between SIGTERM and SIGKILL in `desktop stop`. */
|
|
50
|
+
export const DEFAULT_STOP_TIMEOUT_MS = 10_000;
|
|
51
|
+
/**
|
|
52
|
+
* Set in the daemon's env to the wrapper's own pid. The daemon sees the
|
|
53
|
+
* placeholder we left in `serve.pid` and must not read it as "another serve is
|
|
54
|
+
* running" — it is the claim of the process that just launched it.
|
|
55
|
+
*/
|
|
56
|
+
export const SERVE_PID_CLAIM_ENV = "SPECTRAL_SERVE_PID_CLAIM";
|
|
57
|
+
export function getLandingUrl(env = process.env) {
|
|
58
|
+
const override = env.SPECTRAL_LANDING_URL?.trim();
|
|
59
|
+
return override && override.length > 0 ? override : DEFAULT_LANDING_URL;
|
|
60
|
+
}
|
|
61
|
+
function readyTimeoutMs(env = process.env) {
|
|
62
|
+
const raw = env.SPECTRAL_DESKTOP_READY_TIMEOUT_MS;
|
|
63
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
64
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_READY_TIMEOUT_MS;
|
|
65
|
+
}
|
|
66
|
+
/** Grace period the daemon gets to drain in-flight turns before SIGKILL. */
|
|
67
|
+
function stopGraceMs(env = process.env) {
|
|
68
|
+
const raw = env.SPECTRAL_DESKTOP_STOP_TIMEOUT_MS;
|
|
69
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
70
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_STOP_TIMEOUT_MS;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Serve-log size that triggers a rotation at start. Overridable so the rotation
|
|
74
|
+
* path is testable without writing 10 MB.
|
|
75
|
+
*/
|
|
76
|
+
function logMaxBytes(env = process.env) {
|
|
77
|
+
const raw = env.SPECTRAL_DESKTOP_LOG_MAX_BYTES;
|
|
78
|
+
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
79
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_SERVE_LOG_MAX_BYTES;
|
|
80
|
+
}
|
|
81
|
+
/** Render a grace window the way it is actually configured (10s, 250ms). */
|
|
82
|
+
export function formatGraceMs(ms) {
|
|
83
|
+
return ms >= 1_000 && ms % 1_000 === 0 ? `${ms / 1_000}s` : `${ms}ms`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Decide how to launch a detached `spectral serve`.
|
|
87
|
+
*
|
|
88
|
+
* Three launch shapes must all work:
|
|
89
|
+
* - standalone Bun binary → exec the binary itself with `serve`
|
|
90
|
+
* - npm install (`spectral` bin → dist/cli.js) → `node <cli.js> serve`
|
|
91
|
+
* - `npm run dev` (tsx src/cli.ts) → re-run the very entry the user invoked,
|
|
92
|
+
* with `process.execArgv` so the tsx loader is carried over
|
|
93
|
+
*
|
|
94
|
+
* @returns the spawn plan's command/args portion. Falls back to whatever
|
|
95
|
+
* `spectral` is on PATH when no entry point can be identified.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveServeCommand(input = {}) {
|
|
98
|
+
const execPath = input.execPath ?? process.execPath;
|
|
99
|
+
const execArgv = input.execArgv ?? process.execArgv;
|
|
100
|
+
const argv1 = input.argv1 ?? process.argv[1];
|
|
101
|
+
const standalone = input.standalone ?? isStandaloneBinary();
|
|
102
|
+
const exists = input.exists ?? ((p) => existsSync(p));
|
|
103
|
+
if (standalone)
|
|
104
|
+
return { command: execPath, args: ["serve"] };
|
|
105
|
+
const candidates = (input.candidates ?? [argv1, ...defaultEntryCandidates()]).filter((candidate) => typeof candidate === "string" && candidate.length > 0);
|
|
106
|
+
const entry = candidates.find((candidate) => exists(candidate));
|
|
107
|
+
if (entry)
|
|
108
|
+
return { command: execPath, args: [...execArgv, entry, "serve"] };
|
|
109
|
+
return { command: "spectral", args: ["serve"] };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Entry points relative to this module, covering both the compiled (`dist/`)
|
|
113
|
+
* and the source (`src/`, via tsx) layouts.
|
|
114
|
+
*/
|
|
115
|
+
function defaultEntryCandidates() {
|
|
116
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
117
|
+
return [
|
|
118
|
+
join(here, "..", "cli.js"), // dist/commands/desktop.js -> dist/cli.js
|
|
119
|
+
join(here, "..", "..", "src", "cli.ts"), // src/commands -> src/cli.ts (dev)
|
|
120
|
+
join(here, "..", "..", "dist", "cli.js"), // src/commands -> dist/cli.js
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
/** Compose the full plan for spawning the daemon. */
|
|
124
|
+
export function planServeSpawn(env = process.env, input = {}) {
|
|
125
|
+
const { command, args } = resolveServeCommand(input);
|
|
126
|
+
const pathKey = getPathEnvKey(env);
|
|
127
|
+
return {
|
|
128
|
+
command,
|
|
129
|
+
args,
|
|
130
|
+
cwd: homedir(),
|
|
131
|
+
// The env we were given is the source of truth — `augmentPath(undefined)`
|
|
132
|
+
// would silently fall back to *this* process's PATH and leak it into a
|
|
133
|
+
// caller-supplied env.
|
|
134
|
+
env: {
|
|
135
|
+
...env,
|
|
136
|
+
[pathKey]: augmentPath(env[pathKey] ?? ""),
|
|
137
|
+
[SERVE_PID_CLAIM_ENV]: String(process.pid),
|
|
138
|
+
},
|
|
139
|
+
logFile: getServeLogFile(env),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Will this spawn re-exec into the daemon instead of becoming it directly?
|
|
144
|
+
*
|
|
145
|
+
* Matters for readiness: a standalone binary and `node dist/cli.js serve` are
|
|
146
|
+
* the daemon, so their exit is a hard failure we can report instantly. Under
|
|
147
|
+
* `npm run dev` (`tsx src/cli.ts`) the child is a loader wrapper that execs
|
|
148
|
+
* something else — its death tells us nothing, and only the ready signal or the
|
|
149
|
+
* timeout may decide.
|
|
150
|
+
*/
|
|
151
|
+
export function spawnExpectsReExec(plan) {
|
|
152
|
+
if (isStandaloneBinary())
|
|
153
|
+
return false;
|
|
154
|
+
const command = basename(plan.command).replace(/\.exe$/i, "");
|
|
155
|
+
if (command === "tsx" || command === "ts-node" || command === "oxnode")
|
|
156
|
+
return true;
|
|
157
|
+
return plan.args.some((arg) => /(?:^|[/\\.-])(?:tsx|ts-node)(?:$|[/\\._])/.test(arg) || arg.startsWith("--loader"));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Launch the daemon detached from this terminal so it survives the wrapper
|
|
161
|
+
* exiting (that is the whole point: close the shell, the agent stays up).
|
|
162
|
+
* Returns the child pid, or null when the spawn itself failed.
|
|
163
|
+
*/
|
|
164
|
+
export function spawnDetachedServe(plan) {
|
|
165
|
+
try {
|
|
166
|
+
mkdirSync(dirname(plan.logFile), { recursive: true, mode: 0o700 });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// The log dir may already exist or be read-only; spawning with an
|
|
170
|
+
// unwritable log is still worth trying.
|
|
171
|
+
}
|
|
172
|
+
let fd;
|
|
173
|
+
try {
|
|
174
|
+
// 0600: the daemon log carries machine ids and relay diagnostics, and this
|
|
175
|
+
// file is created by whoever happened to start the agent first. The mode
|
|
176
|
+
// argument is umask-masked, so chmod the fresh descriptor as well.
|
|
177
|
+
fd = openSync(plan.logFile, "a", 0o600);
|
|
178
|
+
try {
|
|
179
|
+
fchmodSync(fd, 0o600);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// filesystem without POSIX modes
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
fd = undefined;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const child = spawn(plan.command, plan.args, {
|
|
190
|
+
cwd: plan.cwd,
|
|
191
|
+
// detached: new process group, so `desktop stop` can signal the daemon
|
|
192
|
+
// without touching this CLI's own group, and Ctrl-C in the wrapper's
|
|
193
|
+
// terminal does not take the agent down.
|
|
194
|
+
detached: true,
|
|
195
|
+
stdio: [
|
|
196
|
+
"ignore",
|
|
197
|
+
fd ?? "ignore",
|
|
198
|
+
// Same fd for stdout and stderr, as the daemon interleaves them. When
|
|
199
|
+
// the log could not be opened, drop both rather than leaking the
|
|
200
|
+
// daemon's chatter into the wrapper's terminal (which we are about to
|
|
201
|
+
// exit) — the readiness fallback file still works.
|
|
202
|
+
fd ?? "ignore",
|
|
203
|
+
],
|
|
204
|
+
env: plan.env,
|
|
205
|
+
});
|
|
206
|
+
child.unref();
|
|
207
|
+
return child.pid ?? null;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
// The child dup'd the descriptor at spawn time; our copy is dead weight.
|
|
214
|
+
if (fd !== undefined) {
|
|
215
|
+
try {
|
|
216
|
+
closeSync(fd);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
225
|
+
function tailLines(tail, max = 12) {
|
|
226
|
+
if (!tail)
|
|
227
|
+
return "";
|
|
228
|
+
return tail
|
|
229
|
+
.split(/\r?\n/)
|
|
230
|
+
.map((line) => line.trimEnd())
|
|
231
|
+
.filter((line) => line.length > 0)
|
|
232
|
+
.slice(-max)
|
|
233
|
+
.join("\n");
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* SIGTERM → wait out the drain → SIGKILL. Shared by `desktop stop` and by the
|
|
237
|
+
* start path's "it never came up, do not leave it half-started" branch — the
|
|
238
|
+
* old start path fired a bare SIGTERM and walked away, which left a detached
|
|
239
|
+
* daemon that ignored TERM (or won the race) running behind a failed command.
|
|
240
|
+
*
|
|
241
|
+
* The wait is bounded by a poll COUNT rather than a wall-clock deadline, so a
|
|
242
|
+
* caller with a synchronous `sleep` (tests) is both fast and deterministic.
|
|
243
|
+
*/
|
|
244
|
+
export async function terminateAgentPid(opts) {
|
|
245
|
+
const out = opts.out ?? (() => { });
|
|
246
|
+
const pollMs = opts.pollMs ?? 100;
|
|
247
|
+
const killWaitMs = opts.killWaitMs ?? 2_000;
|
|
248
|
+
try {
|
|
249
|
+
opts.signal(opts.pid, "SIGTERM");
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
// ESRCH: it exited between the liveness check and the signal.
|
|
253
|
+
const code = err.code;
|
|
254
|
+
if (code !== "ESRCH")
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
257
|
+
// `serve` drains in-flight turns on SIGTERM, so wait the grace window out
|
|
258
|
+
// before escalating. Then SIGKILL — bounded, never a hang.
|
|
259
|
+
for (let i = 0; i < Math.max(1, Math.ceil(opts.graceMs / pollMs)) && opts.alive(opts.pid); i++) {
|
|
260
|
+
await opts.sleep(pollMs);
|
|
261
|
+
}
|
|
262
|
+
if (!opts.alive(opts.pid))
|
|
263
|
+
return "stopped";
|
|
264
|
+
out(pc.yellow(` Agent did not exit within ${formatGraceMs(opts.graceMs)}; forcing SIGKILL.\n`));
|
|
265
|
+
try {
|
|
266
|
+
opts.signal(opts.pid, "SIGKILL");
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// racing exit — fine
|
|
270
|
+
}
|
|
271
|
+
for (let i = 0; i < Math.max(1, Math.ceil(killWaitMs / pollMs)) && opts.alive(opts.pid); i++) {
|
|
272
|
+
await opts.sleep(pollMs);
|
|
273
|
+
}
|
|
274
|
+
return opts.alive(opts.pid) ? "failed" : "killed";
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Which pid may this wrapper signal? Only the one recorded in the pid file:
|
|
278
|
+
* the spawn handle names the loader wrapper under `tsx`, and once the daemon
|
|
279
|
+
* has claimed the file the handle may refer to a process that is already gone.
|
|
280
|
+
*
|
|
281
|
+
* `placeholderPid` is what we wrote ourselves before spawning, so it counts as
|
|
282
|
+
* ours and falls back to the child handle. A *missing* entry means somebody
|
|
283
|
+
* else cleared the state — then nothing is signalled, rather than a guessed pid.
|
|
284
|
+
*/
|
|
285
|
+
function stopTargetPid(childPid, placeholderPid) {
|
|
286
|
+
const recorded = readServePidFile();
|
|
287
|
+
if (recorded === null)
|
|
288
|
+
return null;
|
|
289
|
+
return recorded === placeholderPid ? childPid : recorded;
|
|
290
|
+
}
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
// Subcommands
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
async function startDesktop(deps) {
|
|
295
|
+
const out = deps.out ?? ((t) => process.stdout.write(t));
|
|
296
|
+
const err = deps.err ?? ((t) => process.stderr.write(t));
|
|
297
|
+
const env = deps.env ?? process.env;
|
|
298
|
+
const landingUrl = getLandingUrl(env);
|
|
299
|
+
const logFile = getServeLogFile(env);
|
|
300
|
+
const alive = deps.alive ?? isProcessAlive;
|
|
301
|
+
const readCfg = deps.readCfg ?? readConfig;
|
|
302
|
+
/** What both single-instance paths print: the agent is up, open the UI. */
|
|
303
|
+
const reportAlreadyRunning = async (pid) => {
|
|
304
|
+
out(pc.yellow(`Spectral agent is already running (pid ${pid}).\n`));
|
|
305
|
+
const opened = await (deps.openUrl ?? openBrowser)(landingUrl);
|
|
306
|
+
out(opened.opened ? `Opened ${landingUrl}\n` : `Agent: ${landingUrl}\n`);
|
|
307
|
+
out(`Stop it with ${pc.bold("spectral desktop stop")}.\n`);
|
|
308
|
+
return 0;
|
|
309
|
+
};
|
|
310
|
+
// Single instance. The pid file is only trusted after a liveness check, so a
|
|
311
|
+
// crashed daemon does not block a restart.
|
|
312
|
+
const running = getLiveServePid();
|
|
313
|
+
if (running !== null) {
|
|
314
|
+
return reportAlreadyRunning(running);
|
|
315
|
+
}
|
|
316
|
+
// Auth gate: the web UI needs a user JWT, and `serve` needs credentials too.
|
|
317
|
+
const cfg = await readCfg();
|
|
318
|
+
if (!cfg?.userJwt) {
|
|
319
|
+
const interactive = deps.isInteractive ?? (() => Boolean(process.stdin.isTTY));
|
|
320
|
+
if (!interactive()) {
|
|
321
|
+
err(pc.red("✗ Not signed in for Aexol Studio.\n"));
|
|
322
|
+
err(` Run ${pc.bold("spectral login")} in a terminal, then retry.\n`);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
out(pc.dim("Not signed in for Aexol Studio — opening the sign-in flow…\n"));
|
|
326
|
+
const runLogin = deps.runLogin ??
|
|
327
|
+
(async () => {
|
|
328
|
+
const { runLogin: login } = await import("./login.js");
|
|
329
|
+
await login();
|
|
330
|
+
});
|
|
331
|
+
await runLogin();
|
|
332
|
+
const afterLogin = await readCfg();
|
|
333
|
+
if (!afterLogin?.userJwt) {
|
|
334
|
+
err(pc.red("✗ Sign-in finished without a Studio token, so the desktop UI cannot start.\n"));
|
|
335
|
+
err(` Run ${pc.bold("spectral login")} and choose the OAuth option.\n`);
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Claim the pid file NOW, before spawning. The liveness check above is only
|
|
340
|
+
// an optimisation: two invocations can both see "not running" and then both
|
|
341
|
+
// spawn, which is the race `open(O_EXCL)` closes. The claim records this
|
|
342
|
+
// wrapper's pid as a placeholder, so a late racer sees a live holder.
|
|
343
|
+
const claim = claimServePidFile();
|
|
344
|
+
if (claim.status === "failed") {
|
|
345
|
+
err(pc.red(`✗ Could not claim ${getServePidFile()}: ${claim.error}\n`));
|
|
346
|
+
return 1;
|
|
347
|
+
}
|
|
348
|
+
if (claim.status === "busy") {
|
|
349
|
+
return reportAlreadyRunning(claim.pid);
|
|
350
|
+
}
|
|
351
|
+
// Keep the daemon log bounded before anything is appended to it, and only
|
|
352
|
+
// THEN snapshot the offset — after a rotation the fresh log starts at byte 0,
|
|
353
|
+
// and the readiness marker of a previous run must never satisfy this one.
|
|
354
|
+
const maxLogBytes = logMaxBytes(env);
|
|
355
|
+
if (rotateServeLogFile(logFile, maxLogBytes)) {
|
|
356
|
+
out(pc.dim(`Rotated ${logFile} to ${logFile}.old (larger than ${maxLogBytes} bytes)\n`));
|
|
357
|
+
}
|
|
358
|
+
const logOffset = fileSizeOf(logFile);
|
|
359
|
+
const plan = planServeSpawn(env, deps.planInput);
|
|
360
|
+
const spawnServe = deps.spawnServe ?? spawnDetachedServe;
|
|
361
|
+
// Captured before the spawn: a ready file older than this is from an earlier
|
|
362
|
+
// run and must not be accepted as this one's signal.
|
|
363
|
+
const spawnStartedAt = Date.now();
|
|
364
|
+
const pid = spawnServe(plan);
|
|
365
|
+
if (pid === null || pid === undefined) {
|
|
366
|
+
err(pc.red(`✗ Could not start the Spectral agent (${plan.command}).\n`));
|
|
367
|
+
err(` Log: ${logFile}\n`);
|
|
368
|
+
releaseServePidClaim(claim.placeholderPid);
|
|
369
|
+
return 1;
|
|
370
|
+
}
|
|
371
|
+
// Hand the claim over: the pid file must end up naming the daemon, not us.
|
|
372
|
+
const ownership = confirmServePidChild(pid, claim.placeholderPid, {
|
|
373
|
+
genealogy: deps.genealogy,
|
|
374
|
+
});
|
|
375
|
+
// A foreign or unverifiable holder both mean "not provably ours": warn the
|
|
376
|
+
// user and leave the recorded state untouched instead of hijacking it.
|
|
377
|
+
if (ownership.status === "foreign" || ownership.status === "unknown") {
|
|
378
|
+
const held = ownership.pid === null
|
|
379
|
+
? `${getServePidFile()} is held by a process this command could not verify`
|
|
380
|
+
: ownership.status === "foreign"
|
|
381
|
+
? `${getServePidFile()} is held by pid ${ownership.pid}, which this command did not start`
|
|
382
|
+
: `${getServePidFile()} is held by pid ${ownership.pid}, which could not be verified as ours`;
|
|
383
|
+
err(pc.yellow(`⚠ ${held}; leaving its state alone.\n`));
|
|
384
|
+
}
|
|
385
|
+
const waitForReady = deps.waitForReady ?? ((o) => waitForServeReady(o));
|
|
386
|
+
const result = await waitForReady({
|
|
387
|
+
pid,
|
|
388
|
+
logFile,
|
|
389
|
+
logOffset,
|
|
390
|
+
sinceMs: spawnStartedAt,
|
|
391
|
+
expectReExec: spawnExpectsReExec(plan),
|
|
392
|
+
timeoutMs: readyTimeoutMs(env),
|
|
393
|
+
});
|
|
394
|
+
if (!result.ok) {
|
|
395
|
+
const why = result.reason === "process-exited"
|
|
396
|
+
? "the agent process exited during startup"
|
|
397
|
+
: `no readiness signal within ${Math.round((result.waitedMs ?? 0) / 1000)}s`;
|
|
398
|
+
err(pc.red(`✗ Spectral agent did not become ready: ${why}.\n`));
|
|
399
|
+
err(` Log: ${logFile}\n`);
|
|
400
|
+
const tail = tailLines(result.logTail);
|
|
401
|
+
if (tail)
|
|
402
|
+
err(pc.dim(`${tail}\n`));
|
|
403
|
+
// A half-started detached daemon that may still connect later is worse than
|
|
404
|
+
// a restart, so stop it the same way `desktop stop` would — SIGTERM, grace,
|
|
405
|
+
// then SIGKILL — but only when the pid file provably names a process WE
|
|
406
|
+
// launched (`child` or `daemon`). A foreign or unverifiable holder must
|
|
407
|
+
// never be signalled, so its state is released untouched instead.
|
|
408
|
+
const target = ownership.status === "child" || ownership.status === "daemon"
|
|
409
|
+
? stopTargetPid(pid, claim.placeholderPid)
|
|
410
|
+
: null;
|
|
411
|
+
if (target !== null && alive(target)) {
|
|
412
|
+
await terminateAgentPid({
|
|
413
|
+
pid: target,
|
|
414
|
+
graceMs: deps.stopGraceMs ?? stopGraceMs(env),
|
|
415
|
+
alive,
|
|
416
|
+
signal: deps.signal ?? ((p, s) => process.kill(p, s)),
|
|
417
|
+
sleep: deps.sleep ?? defaultSleep,
|
|
418
|
+
out,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
releaseServePidClaim(claim.placeholderPid, pid);
|
|
422
|
+
return 1;
|
|
423
|
+
}
|
|
424
|
+
// The daemon writes its own pid file; backfill if it could not (read-only
|
|
425
|
+
// config dir races) so `status`/`stop` always have something to work with.
|
|
426
|
+
if (getLiveServePid() === null)
|
|
427
|
+
writeServePid(result.info?.pid ?? pid);
|
|
428
|
+
const opened = await (deps.openUrl ?? openBrowser)(landingUrl);
|
|
429
|
+
out(pc.green(`✓ Spectral agent running (pid ${result.info?.pid ?? pid}).\n`));
|
|
430
|
+
out(opened.opened
|
|
431
|
+
? ` Opened ${pc.cyan(landingUrl)} in your browser.\n`
|
|
432
|
+
: ` Open ${pc.cyan(landingUrl)} in your browser (auto-open failed: ${opened.error ?? "no browser"}).\n`);
|
|
433
|
+
out(` Logs: ${logFile}\n`);
|
|
434
|
+
out(` Stop with ${pc.bold("spectral desktop stop")}, check with ${pc.bold("spectral desktop status")}.\n`);
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
async function statusDesktop(deps) {
|
|
438
|
+
const out = deps.out ?? ((t) => process.stdout.write(t));
|
|
439
|
+
const env = deps.env ?? process.env;
|
|
440
|
+
const landingUrl = getLandingUrl(env);
|
|
441
|
+
const pid = getLiveServePid();
|
|
442
|
+
if (pid === null) {
|
|
443
|
+
out("Spectral agent: not running\n");
|
|
444
|
+
out(` Start with ${pc.bold("spectral desktop")}.\n`);
|
|
445
|
+
return 3;
|
|
446
|
+
}
|
|
447
|
+
const ready = readServeReadyFile();
|
|
448
|
+
out(pc.green(`Spectral agent: running (pid ${pid})`) + "\n");
|
|
449
|
+
if (ready) {
|
|
450
|
+
out(` Version: ${ready.version}\n`);
|
|
451
|
+
out(` Ready since: ${new Date(ready.ts).toISOString()}\n`);
|
|
452
|
+
out(` CLI version now: ${getVersion()}\n`);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
out(` Process is alive, but no readiness signal was recorded (relay may not be connected).\n`);
|
|
456
|
+
}
|
|
457
|
+
out(` Agent UI: ${landingUrl}\n`);
|
|
458
|
+
out(` Logs: ${getServeLogFile(env)}\n`);
|
|
459
|
+
return 0;
|
|
460
|
+
}
|
|
461
|
+
async function stopDesktop(deps) {
|
|
462
|
+
const out = deps.out ?? ((t) => process.stdout.write(t));
|
|
463
|
+
const err = deps.err ?? ((t) => process.stderr.write(t));
|
|
464
|
+
const alive = deps.alive ?? isProcessAlive;
|
|
465
|
+
const signal = deps.signal ?? ((p, s) => process.kill(p, s));
|
|
466
|
+
const sleep = deps.sleep ?? defaultSleep;
|
|
467
|
+
const graceMs = deps.stopGraceMs ?? stopGraceMs(deps.env ?? process.env);
|
|
468
|
+
const pid = readServePidFile();
|
|
469
|
+
if (pid === null) {
|
|
470
|
+
clearServeState();
|
|
471
|
+
out("Spectral agent: not running\n");
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
474
|
+
if (!alive(pid)) {
|
|
475
|
+
clearServeState();
|
|
476
|
+
out(`Spectral agent was not running (stale pid ${pid}); cleaned up.\n`);
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
479
|
+
out(`Stopping Spectral agent (pid ${pid})…\n`);
|
|
480
|
+
// Same escalation the start path uses when the daemon never came up. The
|
|
481
|
+
// target is always the pid read back from the pid file — never a pid cached
|
|
482
|
+
// from an earlier lookup, which could already have been recycled.
|
|
483
|
+
const outcome = await terminateAgentPid({
|
|
484
|
+
pid: readServePidFile() ?? pid,
|
|
485
|
+
graceMs,
|
|
486
|
+
alive,
|
|
487
|
+
signal,
|
|
488
|
+
sleep,
|
|
489
|
+
out,
|
|
490
|
+
});
|
|
491
|
+
if (outcome === "failed") {
|
|
492
|
+
err(pc.red(`✗ Could not stop pid ${pid}; it is still alive. Kill it manually.\n`));
|
|
493
|
+
return 1;
|
|
494
|
+
}
|
|
495
|
+
clearServeState();
|
|
496
|
+
out(pc.green("✓ Spectral agent stopped.\n"));
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
function printUsage(out) {
|
|
500
|
+
out([
|
|
501
|
+
"spectral desktop — run the Spectral agent and open the Studio UI",
|
|
502
|
+
"",
|
|
503
|
+
" spectral desktop Start the agent (or reuse a running one) and open the UI",
|
|
504
|
+
" spectral desktop status Report whether the agent is running",
|
|
505
|
+
` spectral desktop stop Stop the agent (SIGTERM, then SIGKILL after ${formatGraceMs(DEFAULT_STOP_TIMEOUT_MS)})`,
|
|
506
|
+
"",
|
|
507
|
+
"Environment:",
|
|
508
|
+
" SPECTRAL_LANDING_URL UI to open (default https://aexol.ai/agent)",
|
|
509
|
+
" SPECTRAL_SERVE_LOG_PATH Daemon log (default ~/.spectral/serve.log)",
|
|
510
|
+
" SPECTRAL_DESKTOP_READY_TIMEOUT_MS Readiness timeout (default 15000)",
|
|
511
|
+
` SPECTRAL_DESKTOP_STOP_TIMEOUT_MS SIGTERM grace before SIGKILL (default ${DEFAULT_STOP_TIMEOUT_MS})`,
|
|
512
|
+
"",
|
|
513
|
+
].join("\n"));
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* `spectral desktop [start|status|stop]`
|
|
517
|
+
*
|
|
518
|
+
* @returns process exit code; `cli.ts` assigns it to `process.exitCode`.
|
|
519
|
+
*/
|
|
520
|
+
export async function runDesktop(args = [], deps = {}) {
|
|
521
|
+
const out = deps.out ?? ((t) => process.stdout.write(t));
|
|
522
|
+
const sub = args[0];
|
|
523
|
+
switch (sub) {
|
|
524
|
+
case undefined:
|
|
525
|
+
case "start":
|
|
526
|
+
return startDesktop(deps);
|
|
527
|
+
case "status":
|
|
528
|
+
return statusDesktop(deps);
|
|
529
|
+
case "stop":
|
|
530
|
+
return stopDesktop(deps);
|
|
531
|
+
case "--help":
|
|
532
|
+
case "-h":
|
|
533
|
+
printUsage(out);
|
|
534
|
+
return 0;
|
|
535
|
+
default:
|
|
536
|
+
out(`spectral desktop: unknown subcommand "${sub}".\n`);
|
|
537
|
+
printUsage(out);
|
|
538
|
+
return 1;
|
|
539
|
+
}
|
|
540
|
+
}
|
package/dist/commands/serve.d.ts
CHANGED
|
@@ -88,6 +88,13 @@ export interface RunServeOptions {
|
|
|
88
88
|
fetchImpl?: typeof fetch;
|
|
89
89
|
/** Override the WebSocket constructor (tests, RelayClient). */
|
|
90
90
|
webSocketImpl?: ConstructorParameters<typeof RelayClient>[0]["webSocketImpl"];
|
|
91
|
+
/**
|
|
92
|
+
* Write the `serve.pid` / `serve-ready.json` bookkeeping files and print the
|
|
93
|
+
* `SPECTRAL_SERVE_READY` stdout marker once the relay socket opens. Defaults
|
|
94
|
+
* to true — it is the readiness contract `spectral desktop` waits on.
|
|
95
|
+
* Tests pass false to keep a temp config dir clean.
|
|
96
|
+
*/
|
|
97
|
+
serveState?: boolean;
|
|
91
98
|
}
|
|
92
99
|
export interface RunServeHandle {
|
|
93
100
|
/** Open store. Exposed for tests. */
|
|
@@ -104,7 +111,8 @@ export interface RunServeHandle {
|
|
|
104
111
|
export declare function runServe(opts?: RunServeOptions): Promise<RunServeHandle>;
|
|
105
112
|
/**
|
|
106
113
|
* CLI entry point used by `cli.ts`. Surfaces the deprecation warning for
|
|
107
|
-
* `--port`/`SPECTRAL_PORT` once, then defers
|
|
114
|
+
* `--port`/`SPECTRAL_PORT` once, augments PATH for child processes, then defers
|
|
115
|
+
* to `runServe`.
|
|
108
116
|
*/
|
|
109
117
|
export declare function runServeCli(rawArgs: string[]): Promise<RunServeHandle>;
|
|
110
118
|
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAclE,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAclE,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAsBrE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAazD;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA6B9D;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;IAC9E;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,WAAW,CAAC;IACnB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAoqBlF;AAmFD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAsB5E"}
|