@suwujs/king-ai 0.2.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/README.md +96 -0
- package/dist/src/agent-config-validation.d.ts +9 -0
- package/dist/src/agent-config-validation.js +30 -0
- package/dist/src/api.d.ts +4 -0
- package/dist/src/api.js +48 -0
- package/dist/src/attachments.d.ts +45 -0
- package/dist/src/attachments.js +322 -0
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +1697 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +20 -0
- package/dist/src/cron.d.ts +11 -0
- package/dist/src/cron.js +65 -0
- package/dist/src/daemon.d.ts +36 -0
- package/dist/src/daemon.js +373 -0
- package/dist/src/engine.d.ts +32 -0
- package/dist/src/engine.js +1014 -0
- package/dist/src/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat.js +28 -0
- package/dist/src/host-api.d.ts +40 -0
- package/dist/src/host-api.js +59 -0
- package/dist/src/host-control.d.ts +48 -0
- package/dist/src/host-control.js +1279 -0
- package/dist/src/host-export.d.ts +50 -0
- package/dist/src/host-export.js +187 -0
- package/dist/src/host-feedback.d.ts +78 -0
- package/dist/src/host-feedback.js +178 -0
- package/dist/src/host-home.d.ts +13 -0
- package/dist/src/host-home.js +54 -0
- package/dist/src/host-ledger.d.ts +261 -0
- package/dist/src/host-ledger.js +554 -0
- package/dist/src/host-loop-events.d.ts +69 -0
- package/dist/src/host-loop-events.js +288 -0
- package/dist/src/host-permission.d.ts +36 -0
- package/dist/src/host-permission.js +180 -0
- package/dist/src/host-policy.d.ts +15 -0
- package/dist/src/host-policy.js +36 -0
- package/dist/src/host-run-executor.d.ts +13 -0
- package/dist/src/host-run-executor.js +221 -0
- package/dist/src/host-run-heartbeat.d.ts +40 -0
- package/dist/src/host-run-heartbeat.js +103 -0
- package/dist/src/host-run-layout.d.ts +17 -0
- package/dist/src/host-run-layout.js +387 -0
- package/dist/src/host-run-meta.d.ts +41 -0
- package/dist/src/host-run-meta.js +115 -0
- package/dist/src/host-run-spec.d.ts +149 -0
- package/dist/src/host-run-spec.js +465 -0
- package/dist/src/host-runs.d.ts +77 -0
- package/dist/src/host-runs.js +195 -0
- package/dist/src/host-sdk.d.ts +412 -0
- package/dist/src/host-sdk.js +628 -0
- package/dist/src/host-server.d.ts +26 -0
- package/dist/src/host-server.js +921 -0
- package/dist/src/host-timeline.d.ts +24 -0
- package/dist/src/host-timeline.js +161 -0
- package/dist/src/jsonl.d.ts +13 -0
- package/dist/src/jsonl.js +47 -0
- package/dist/src/lifecycle.d.ts +5 -0
- package/dist/src/lifecycle.js +18 -0
- package/dist/src/message-routing.d.ts +32 -0
- package/dist/src/message-routing.js +119 -0
- package/dist/src/paths.d.ts +19 -0
- package/dist/src/paths.js +26 -0
- package/dist/src/project-profile.d.ts +49 -0
- package/dist/src/project-profile.js +356 -0
- package/dist/src/remediation.d.ts +14 -0
- package/dist/src/remediation.js +114 -0
- package/dist/src/remote-devices.d.ts +41 -0
- package/dist/src/remote-devices.js +156 -0
- package/dist/src/remote-diagnostics.d.ts +39 -0
- package/dist/src/remote-diagnostics.js +199 -0
- package/dist/src/remote-ssh.d.ts +39 -0
- package/dist/src/remote-ssh.js +129 -0
- package/dist/src/run-stream.d.ts +57 -0
- package/dist/src/run-stream.js +119 -0
- package/dist/src/runner.d.ts +131 -0
- package/dist/src/runner.js +1161 -0
- package/dist/src/runtime-data.d.ts +68 -0
- package/dist/src/runtime-data.js +172 -0
- package/dist/src/service.d.ts +114 -0
- package/dist/src/service.js +631 -0
- package/dist/src/shared-skills.d.ts +26 -0
- package/dist/src/shared-skills.js +85 -0
- package/dist/src/shim.d.ts +1 -0
- package/dist/src/shim.js +64 -0
- package/dist/src/skill-check.d.ts +17 -0
- package/dist/src/skill-check.js +158 -0
- package/dist/src/sse.d.ts +9 -0
- package/dist/src/sse.js +36 -0
- package/dist/src/team-routing.d.ts +55 -0
- package/dist/src/team-routing.js +131 -0
- package/dist/src/team-workflow.d.ts +78 -0
- package/dist/src/team-workflow.js +253 -0
- package/dist/src/text.d.ts +7 -0
- package/dist/src/text.js +27 -0
- package/dist/src/types.d.ts +98 -0
- package/dist/src/types.js +1 -0
- package/dist/src/usage.d.ts +116 -0
- package/dist/src/usage.js +350 -0
- package/dist/src/workspace.d.ts +9 -0
- package/dist/src/workspace.js +56 -0
- package/dist/src/worktree.d.ts +47 -0
- package/dist/src/worktree.js +201 -0
- package/package.json +63 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { CONFIG_DIR, CURRENT_VERSION, RUNNING_STATE_PATH } from "./paths.js";
|
|
9
|
+
import { loadConfig } from "./config.js";
|
|
10
|
+
import { cleanupWorktreePlans, formatWorktreeCleanupResults, formatWorktreePreparationResults, prepareWorktreePlans } from "./worktree.js";
|
|
11
|
+
import { formatAgentRunStats, formatTokenBudgetCheck } from "./usage.js";
|
|
12
|
+
const execFileP = promisify(execFile);
|
|
13
|
+
const LOG_MAX_BYTES = 20 * 1024 * 1024;
|
|
14
|
+
export function daemonLogPath() {
|
|
15
|
+
return join(CONFIG_DIR, "daemon.log");
|
|
16
|
+
}
|
|
17
|
+
export function serviceNames(commandName = "king-ai") {
|
|
18
|
+
return {
|
|
19
|
+
packageName: "@suwujs/king-ai",
|
|
20
|
+
serviceUnit: "king-ai",
|
|
21
|
+
displayName: "King AI",
|
|
22
|
+
serviceLabel: "io.king-ai.daemon"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function updateRegistryUrl(commandName = "king-ai") {
|
|
26
|
+
return `https://registry.npmjs.org/${encodeURIComponent(serviceNames(commandName).packageName)}/latest`;
|
|
27
|
+
}
|
|
28
|
+
export function versionGt(a, b) {
|
|
29
|
+
const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
30
|
+
const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
31
|
+
for (let i = 0; i < 3; i += 1) {
|
|
32
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
33
|
+
return true;
|
|
34
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function resolveNpx() {
|
|
40
|
+
const sibling = join(dirname(process.execPath), "npx");
|
|
41
|
+
return existsSync(sibling) ? sibling : "npx";
|
|
42
|
+
}
|
|
43
|
+
function darwinPlistPath(commandName = "king-ai") {
|
|
44
|
+
return join(homedir(), "Library", "LaunchAgents", `${serviceNames(commandName).serviceLabel}.plist`);
|
|
45
|
+
}
|
|
46
|
+
function linuxUnitPath(commandName = "king-ai") {
|
|
47
|
+
return join(homedir(), ".config", "systemd", "user", `${serviceNames(commandName).serviceUnit}.service`);
|
|
48
|
+
}
|
|
49
|
+
function windowsServiceDir(commandName = "king-ai") {
|
|
50
|
+
return join(CONFIG_DIR, "service", commandName);
|
|
51
|
+
}
|
|
52
|
+
export function windowsTaskName(commandName = "king-ai") {
|
|
53
|
+
return `KingAI.BYOA.${serviceNames(commandName).serviceUnit}`;
|
|
54
|
+
}
|
|
55
|
+
export function windowsWrapperPath(commandName = "king-ai") {
|
|
56
|
+
return join(windowsServiceDir(commandName), "king-ai-agent-computer.cmd");
|
|
57
|
+
}
|
|
58
|
+
export function buildWindowsServiceWrapper(args, logPath = daemonLogPath()) {
|
|
59
|
+
const quotedArgs = args.map(windowsCmdQuote).join(" ");
|
|
60
|
+
return [
|
|
61
|
+
"@echo off",
|
|
62
|
+
"setlocal",
|
|
63
|
+
"set KING_AI_SUPERVISED=1",
|
|
64
|
+
`echo [%date% %time%] starting King AI daemon>>${windowsCmdQuote(logPath)}`,
|
|
65
|
+
`${quotedArgs} >>${windowsCmdQuote(logPath)} 2>&1`
|
|
66
|
+
].join("\r\n") + "\r\n";
|
|
67
|
+
}
|
|
68
|
+
export function parseWindowsTaskStatus(stdout) {
|
|
69
|
+
return {
|
|
70
|
+
taskName: stdout.match(/^TaskName:\s*(.+)$/im)?.[1]?.trim(),
|
|
71
|
+
status: stdout.match(/^Status:\s*(.+)$/im)?.[1]?.trim(),
|
|
72
|
+
lastResult: stdout.match(/^Last Result:\s*(.+)$/im)?.[1]?.trim()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function shouldKillDaemonCommand(command) {
|
|
76
|
+
return (/agent computer/.test(command) &&
|
|
77
|
+
!/--(stop|status|restart|logs|version|install-service|uninstall-service|pair)\b|\bnpx\b/.test(command));
|
|
78
|
+
}
|
|
79
|
+
export function parseDarwinLaunchctlStatus(stdout) {
|
|
80
|
+
const pidText = stdout.match(/"PID"\s*=\s*(\d+)/)?.[1];
|
|
81
|
+
const lastExitText = stdout.match(/"LastExitStatus"\s*=\s*(-?\d+)/)?.[1];
|
|
82
|
+
return {
|
|
83
|
+
pid: pidText ? Number(pidText) : null,
|
|
84
|
+
lastExitStatus: lastExitText ? Number(lastExitText) : null
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function parseLinuxMainPid(stdout) {
|
|
88
|
+
const pid = Number(stdout.trim());
|
|
89
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
90
|
+
}
|
|
91
|
+
export async function reloadService(commandName = "king-ai") {
|
|
92
|
+
const names = serviceNames(commandName);
|
|
93
|
+
if (process.platform === "darwin") {
|
|
94
|
+
const plistPath = darwinPlistPath(commandName);
|
|
95
|
+
await execFileP("launchctl", ["unload", plistPath]).catch(() => undefined);
|
|
96
|
+
await execFileP("launchctl", ["load", plistPath]);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (process.platform === "linux") {
|
|
100
|
+
await execFileP("systemctl", ["--user", "restart", names.serviceUnit]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export async function installService(serverUrl, commandName = "king-ai") {
|
|
104
|
+
const names = serviceNames(commandName);
|
|
105
|
+
const cfg = await loadConfig();
|
|
106
|
+
if (!cfg)
|
|
107
|
+
throw new Error(`pair this computer first: ${names.displayName} agent computer --pair <code>`);
|
|
108
|
+
const resolvedServerUrl = serverUrl ?? cfg.serverUrl;
|
|
109
|
+
const tenantArgs = cfg.tenantId ? ["--tenant", cfg.tenantId] : [];
|
|
110
|
+
const npx = resolveNpx();
|
|
111
|
+
const logPath = daemonLogPath();
|
|
112
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
113
|
+
if (process.platform === "darwin") {
|
|
114
|
+
await mkdir(dirname(darwinPlistPath(commandName)), { recursive: true });
|
|
115
|
+
const plistPath = darwinPlistPath(commandName);
|
|
116
|
+
const args = [npx, "-y", `${names.packageName}@latest`, "agent", "computer", "--server", resolvedServerUrl, ...tenantArgs];
|
|
117
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
118
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
119
|
+
<plist version="1.0"><dict>
|
|
120
|
+
<key>Label</key><string>${names.serviceLabel}</string>
|
|
121
|
+
<key>ProgramArguments</key><array>${args.map((a) => `<string>${a}</string>`).join("")}</array>
|
|
122
|
+
<key>RunAtLoad</key><true/>
|
|
123
|
+
<key>KeepAlive</key><true/>
|
|
124
|
+
<key>StandardOutPath</key><string>${logPath}</string>
|
|
125
|
+
<key>StandardErrorPath</key><string>${logPath}</string>
|
|
126
|
+
<key>EnvironmentVariables</key><dict>
|
|
127
|
+
<key>PATH</key><string>${process.env.PATH ?? ""}</string>
|
|
128
|
+
<key>KING_AI_SUPERVISED</key><string>1</string>
|
|
129
|
+
</dict>
|
|
130
|
+
</dict></plist>`;
|
|
131
|
+
await writeFile(plistPath, plist, "utf8");
|
|
132
|
+
await execFileP("launchctl", ["unload", plistPath]).catch(() => undefined);
|
|
133
|
+
await execFileP("launchctl", ["load", plistPath]);
|
|
134
|
+
console.log(`installed LaunchAgent ${names.serviceLabel}. Logs: ${logPath}`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (process.platform === "linux") {
|
|
138
|
+
await mkdir(dirname(linuxUnitPath(commandName)), { recursive: true });
|
|
139
|
+
const unitPath = linuxUnitPath(commandName);
|
|
140
|
+
const unit = `[Unit]
|
|
141
|
+
Description=King AI BYOA daemon
|
|
142
|
+
After=network-online.target
|
|
143
|
+
|
|
144
|
+
[Service]
|
|
145
|
+
ExecStart=${npx} -y ${names.packageName}@latest agent computer --server ${resolvedServerUrl}${tenantArgs.length ? ` --tenant ${tenantArgs[1]}` : ""}
|
|
146
|
+
Restart=always
|
|
147
|
+
RestartSec=5
|
|
148
|
+
Environment=PATH=${process.env.PATH ?? ""}
|
|
149
|
+
Environment=KING_AI_SUPERVISED=1
|
|
150
|
+
|
|
151
|
+
[Install]
|
|
152
|
+
WantedBy=default.target
|
|
153
|
+
`;
|
|
154
|
+
await writeFile(unitPath, unit, "utf8");
|
|
155
|
+
await execFileP("systemctl", ["--user", "daemon-reload"]);
|
|
156
|
+
await execFileP("systemctl", ["--user", "enable", "--now", names.serviceUnit]);
|
|
157
|
+
console.log(`installed systemd --user service ${names.serviceUnit}`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (process.platform === "win32") {
|
|
161
|
+
await mkdir(windowsServiceDir(commandName), { recursive: true });
|
|
162
|
+
const resolvedArgs = [resolveNpx(), "-y", `${names.packageName}@latest`, "agent", "computer", "--server", resolvedServerUrl, ...tenantArgs];
|
|
163
|
+
const wrapperPath = windowsWrapperPath(commandName);
|
|
164
|
+
await writeFile(wrapperPath, buildWindowsServiceWrapper(resolvedArgs), "utf8");
|
|
165
|
+
await execFileP("schtasks", ["/Delete", "/TN", windowsTaskName(commandName), "/F"]).catch(() => undefined);
|
|
166
|
+
await execFileP("schtasks", [
|
|
167
|
+
"/Create",
|
|
168
|
+
"/TN", windowsTaskName(commandName),
|
|
169
|
+
"/SC", "ONLOGON",
|
|
170
|
+
"/TR", wrapperPath,
|
|
171
|
+
"/RL", "LIMITED",
|
|
172
|
+
"/F"
|
|
173
|
+
]);
|
|
174
|
+
await execFileP("schtasks", ["/Run", "/TN", windowsTaskName(commandName)]).catch(() => undefined);
|
|
175
|
+
console.log(`installed Windows scheduled task ${windowsTaskName(commandName)}. Logs: ${daemonLogPath()}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
throw new Error(`service installation is not supported on ${process.platform}`);
|
|
179
|
+
}
|
|
180
|
+
export async function uninstallService(commandName = "king-ai") {
|
|
181
|
+
const names = serviceNames(commandName);
|
|
182
|
+
if (process.platform === "darwin") {
|
|
183
|
+
const plistPath = darwinPlistPath(commandName);
|
|
184
|
+
await execFileP("launchctl", ["unload", plistPath]).catch(() => undefined);
|
|
185
|
+
await rm(plistPath, { force: true });
|
|
186
|
+
console.log(`removed LaunchAgent ${names.serviceLabel}`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (process.platform === "linux") {
|
|
190
|
+
await execFileP("systemctl", ["--user", "disable", "--now", names.serviceUnit]).catch(() => undefined);
|
|
191
|
+
await rm(linuxUnitPath(commandName), { force: true });
|
|
192
|
+
await execFileP("systemctl", ["--user", "daemon-reload"]).catch(() => undefined);
|
|
193
|
+
console.log(`removed systemd --user service ${names.serviceUnit}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (process.platform === "win32") {
|
|
197
|
+
await execFileP("schtasks", ["/End", "/TN", windowsTaskName(commandName)]).catch(() => undefined);
|
|
198
|
+
await execFileP("schtasks", ["/Delete", "/TN", windowsTaskName(commandName), "/F"]).catch(() => undefined);
|
|
199
|
+
await rm(windowsServiceDir(commandName), { recursive: true, force: true });
|
|
200
|
+
console.log(`removed Windows scheduled task ${windowsTaskName(commandName)}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`service removal is not supported on ${process.platform}`);
|
|
204
|
+
}
|
|
205
|
+
export function isServiceInstalled(commandName = "king-ai") {
|
|
206
|
+
if (process.platform === "darwin")
|
|
207
|
+
return existsSync(darwinPlistPath(commandName));
|
|
208
|
+
if (process.platform === "linux")
|
|
209
|
+
return existsSync(linuxUnitPath(commandName));
|
|
210
|
+
if (process.platform === "win32") {
|
|
211
|
+
try {
|
|
212
|
+
execFileSync("schtasks", ["/Query", "/TN", windowsTaskName(commandName)], { stdio: "ignore" });
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
export async function restartService(commandName = "king-ai") {
|
|
222
|
+
const names = serviceNames(commandName);
|
|
223
|
+
if (!isServiceInstalled(commandName)) {
|
|
224
|
+
console.log(`service not installed; run: ${names.displayName} agent computer --install-service`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (process.platform === "darwin") {
|
|
228
|
+
const uid = process.getuid?.() ?? 0;
|
|
229
|
+
await execFileP("launchctl", ["kickstart", "-k", `gui/${uid}/${names.serviceLabel}`]).catch(() => reloadService(commandName));
|
|
230
|
+
}
|
|
231
|
+
else if (process.platform === "linux") {
|
|
232
|
+
await execFileP("systemctl", ["--user", "restart", names.serviceUnit]);
|
|
233
|
+
}
|
|
234
|
+
else if (process.platform === "win32") {
|
|
235
|
+
await execFileP("schtasks", ["/End", "/TN", windowsTaskName(commandName)]).catch(() => undefined);
|
|
236
|
+
await execFileP("schtasks", ["/Run", "/TN", windowsTaskName(commandName)]);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
throw new Error(`restart is not supported on ${process.platform}`);
|
|
240
|
+
}
|
|
241
|
+
console.log(`service restarted; it will relaunch using ${names.packageName}@latest`);
|
|
242
|
+
}
|
|
243
|
+
export async function killRunningDaemons() {
|
|
244
|
+
const candidates = new Set();
|
|
245
|
+
try {
|
|
246
|
+
const state = JSON.parse(await readFile(RUNNING_STATE_PATH, "utf8"));
|
|
247
|
+
if (typeof state.pid === "number" && state.pid > 0)
|
|
248
|
+
candidates.add(state.pid);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// No tracked foreground daemon.
|
|
252
|
+
}
|
|
253
|
+
if (process.platform !== "win32") {
|
|
254
|
+
try {
|
|
255
|
+
const { stdout } = await execFileP("pgrep", ["-f", "agent computer"]);
|
|
256
|
+
for (const line of stdout.split("\n")) {
|
|
257
|
+
const pid = Number.parseInt(line.trim(), 10);
|
|
258
|
+
if (pid > 0)
|
|
259
|
+
candidates.add(pid);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// pgrep exits non-zero when no process matches.
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
candidates.delete(process.pid);
|
|
267
|
+
if (typeof process.ppid === "number")
|
|
268
|
+
candidates.delete(process.ppid);
|
|
269
|
+
const victims = [];
|
|
270
|
+
for (const pid of candidates) {
|
|
271
|
+
try {
|
|
272
|
+
const { stdout } = await execFileP("ps", ["-p", String(pid), "-o", "command="]);
|
|
273
|
+
if (shouldKillDaemonCommand(stdout.trim()))
|
|
274
|
+
victims.push(pid);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Process already exited or cannot be inspected.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
for (const pid of victims) {
|
|
281
|
+
try {
|
|
282
|
+
process.kill(pid, "SIGTERM");
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// Process already exited.
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (victims.length > 0) {
|
|
289
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
290
|
+
for (const pid of victims) {
|
|
291
|
+
try {
|
|
292
|
+
process.kill(pid, 0);
|
|
293
|
+
process.kill(pid, "SIGKILL");
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
// SIGTERM already worked.
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
console.log(`killed ${victims.length} running daemon process(es)`);
|
|
300
|
+
}
|
|
301
|
+
await rm(RUNNING_STATE_PATH, { force: true });
|
|
302
|
+
return victims.length;
|
|
303
|
+
}
|
|
304
|
+
export async function stopService(commandName = "king-ai") {
|
|
305
|
+
if (isServiceInstalled(commandName)) {
|
|
306
|
+
await uninstallService(commandName);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
console.log("no background service installed; killing any running daemon process directly");
|
|
310
|
+
}
|
|
311
|
+
const killed = await killRunningDaemons();
|
|
312
|
+
console.log(`stopped; killed ${killed} foreground daemon process(es)`);
|
|
313
|
+
}
|
|
314
|
+
export async function printStatus(commandName = "king-ai") {
|
|
315
|
+
const names = serviceNames(commandName);
|
|
316
|
+
const cfg = await loadConfig();
|
|
317
|
+
console.log(`cli: ${names.displayName} ${CURRENT_VERSION} (this command)`);
|
|
318
|
+
console.log(cfg ? `paired: computer ${cfg.computerId} @ ${cfg.serverUrl}` : `paired: NO; run: ${names.displayName} agent computer --pair <code>`);
|
|
319
|
+
let livePid = null;
|
|
320
|
+
if (!isServiceInstalled(commandName)) {
|
|
321
|
+
console.log(`service: not installed; run: ${names.displayName} agent computer --install-service`);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
if (process.platform === "darwin") {
|
|
325
|
+
try {
|
|
326
|
+
const { stdout } = await execFileP("launchctl", ["list", names.serviceLabel]);
|
|
327
|
+
const status = parseDarwinLaunchctlStatus(stdout);
|
|
328
|
+
livePid = status.pid;
|
|
329
|
+
console.log(livePid
|
|
330
|
+
? `service: installed; running (pid ${livePid})`
|
|
331
|
+
: `service: installed; NOT running${status.lastExitStatus != null ? ` (last exit ${status.lastExitStatus})` : ""}`);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
console.log(`service: installed; not loaded (try: launchctl load ${darwinPlistPath(commandName)})`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
else if (process.platform === "linux") {
|
|
338
|
+
const active = await execFileP("systemctl", ["--user", "is-active", names.serviceUnit])
|
|
339
|
+
.then((result) => result.stdout.trim())
|
|
340
|
+
.catch(() => "inactive");
|
|
341
|
+
const pidText = await execFileP("systemctl", ["--user", "show", names.serviceUnit, "-p", "MainPID", "--value"])
|
|
342
|
+
.then((result) => result.stdout)
|
|
343
|
+
.catch(() => "");
|
|
344
|
+
livePid = parseLinuxMainPid(pidText);
|
|
345
|
+
console.log(`service: installed; ${active}${livePid ? ` (pid ${livePid})` : ""}`);
|
|
346
|
+
}
|
|
347
|
+
else if (process.platform === "win32") {
|
|
348
|
+
const stdout = await execFileP("schtasks", ["/Query", "/TN", windowsTaskName(commandName), "/V", "/FO", "LIST"])
|
|
349
|
+
.then((result) => result.stdout)
|
|
350
|
+
.catch(() => "");
|
|
351
|
+
const status = parseWindowsTaskStatus(stdout);
|
|
352
|
+
console.log(`service: installed; ${status.status ?? "unknown"}${status.lastResult ? ` (last result ${status.lastResult})` : ""}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const running = await resolveRunningVersion(livePid, commandName);
|
|
356
|
+
const state = await readRunningState();
|
|
357
|
+
if (running) {
|
|
358
|
+
console.log(`running: ${names.displayName} ${running}${running === CURRENT_VERSION ? " (same as this cli)" : " (differs from this cli; run --restart to pick up latest)"}`);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
console.log("running: unknown; run --restart to start it and record the version");
|
|
362
|
+
}
|
|
363
|
+
const snapshot = formatRunningStateSnapshot(state);
|
|
364
|
+
if (snapshot)
|
|
365
|
+
console.log(snapshot);
|
|
366
|
+
console.log(`logs: ${process.platform === "linux" ? `journalctl --user -u ${names.serviceUnit} -f` : daemonLogPath()}`);
|
|
367
|
+
}
|
|
368
|
+
export async function resolveRunningVersion(livePid, commandName = "king-ai") {
|
|
369
|
+
const names = serviceNames(commandName);
|
|
370
|
+
try {
|
|
371
|
+
const state = JSON.parse(await readFile(RUNNING_STATE_PATH, "utf8"));
|
|
372
|
+
if (typeof state.version === "string" && state.version && (livePid == null || state.pid === livePid))
|
|
373
|
+
return state.version;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Fall through to log parsing.
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const text = process.platform === "linux"
|
|
380
|
+
? (await execFileP("journalctl", ["--user", "-u", names.serviceUnit, "-n", "400", "--no-pager"])).stdout
|
|
381
|
+
: await readFile(daemonLogPath(), "utf8");
|
|
382
|
+
const matches = [...text.matchAll(new RegExp(`${names.displayName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} (\\d+\\.\\d+\\.\\d+) starting`, "g"))];
|
|
383
|
+
return matches.at(-1)?.[1] ?? "";
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return "";
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
export async function readRunningState(path = RUNNING_STATE_PATH) {
|
|
390
|
+
try {
|
|
391
|
+
const state = JSON.parse(await readFile(path, "utf8"));
|
|
392
|
+
return typeof state.version === "string" && typeof state.pid === "number" ? state : null;
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
export function updateRunningStateData(previous, patch) {
|
|
399
|
+
const base = previous ?? { version: CURRENT_VERSION, pid: process.pid, startedAt: new Date().toISOString() };
|
|
400
|
+
const events = [...(base.events ?? [])];
|
|
401
|
+
if (patch.event)
|
|
402
|
+
events.push(patch.event);
|
|
403
|
+
return {
|
|
404
|
+
...base,
|
|
405
|
+
...patch,
|
|
406
|
+
events: events.slice(-50)
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
export async function writeRunningState(patch = {}) {
|
|
410
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
411
|
+
const previous = await readRunningState();
|
|
412
|
+
await writeFile(RUNNING_STATE_PATH, JSON.stringify(updateRunningStateData(previous, {
|
|
413
|
+
version: CURRENT_VERSION,
|
|
414
|
+
pid: process.pid,
|
|
415
|
+
startedAt: previous?.startedAt ?? new Date().toISOString(),
|
|
416
|
+
...patch
|
|
417
|
+
}), null, 2), "utf8");
|
|
418
|
+
}
|
|
419
|
+
export function recordRunningState(patch) {
|
|
420
|
+
void writeRunningState(patch).catch(() => undefined);
|
|
421
|
+
}
|
|
422
|
+
export function formatRecentRunningEvents(state, max = 10) {
|
|
423
|
+
const events = state?.events?.slice(-max) ?? [];
|
|
424
|
+
if (events.length === 0)
|
|
425
|
+
return "";
|
|
426
|
+
return [
|
|
427
|
+
"recent daemon events:",
|
|
428
|
+
...events.map((event) => ` ${event.at} ${event.kind}${event.detail ? `: ${event.detail}` : ""}`)
|
|
429
|
+
].join("\n");
|
|
430
|
+
}
|
|
431
|
+
export function runningEventCategory(kind) {
|
|
432
|
+
if (/^agent\./.test(kind))
|
|
433
|
+
return kind.includes("budget") ? "budget" : "agent";
|
|
434
|
+
if (/^(turn|agenda|wake)\./.test(kind))
|
|
435
|
+
return "turn";
|
|
436
|
+
if (/budget/.test(kind))
|
|
437
|
+
return "budget";
|
|
438
|
+
if (/^(daemon|heartbeat|service)\./.test(kind))
|
|
439
|
+
return "daemon";
|
|
440
|
+
return "other";
|
|
441
|
+
}
|
|
442
|
+
export function groupRunningEvents(events = [], maxPerCategory = 4) {
|
|
443
|
+
const grouped = {
|
|
444
|
+
agent: [],
|
|
445
|
+
turn: [],
|
|
446
|
+
budget: [],
|
|
447
|
+
daemon: [],
|
|
448
|
+
other: []
|
|
449
|
+
};
|
|
450
|
+
for (const event of events) {
|
|
451
|
+
grouped[runningEventCategory(event.kind)].push(event);
|
|
452
|
+
}
|
|
453
|
+
for (const key of Object.keys(grouped)) {
|
|
454
|
+
grouped[key] = grouped[key].slice(-maxPerCategory);
|
|
455
|
+
}
|
|
456
|
+
return grouped;
|
|
457
|
+
}
|
|
458
|
+
export function formatRunningEventSummary(state, maxPerCategory = 4) {
|
|
459
|
+
if (!state?.events?.length)
|
|
460
|
+
return "";
|
|
461
|
+
const grouped = groupRunningEvents(state.events, maxPerCategory);
|
|
462
|
+
const lines = ["events by category:"];
|
|
463
|
+
for (const category of ["agent", "turn", "budget", "daemon", "other"]) {
|
|
464
|
+
const events = grouped[category];
|
|
465
|
+
if (events.length === 0)
|
|
466
|
+
continue;
|
|
467
|
+
lines.push(` ${category}:`);
|
|
468
|
+
for (const event of events) {
|
|
469
|
+
lines.push(` - ${event.at} ${event.kind}${event.detail ? `: ${event.detail}` : ""}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return lines.length === 1 ? "" : lines.join("\n");
|
|
473
|
+
}
|
|
474
|
+
export function formatRunningStateSnapshot(state, eventMax = 5) {
|
|
475
|
+
if (!state)
|
|
476
|
+
return "";
|
|
477
|
+
const lines = [];
|
|
478
|
+
if (state.startedAt)
|
|
479
|
+
lines.push(`started: ${state.startedAt}${state.pid ? ` (pid ${state.pid})` : ""}`);
|
|
480
|
+
if (state.lastHeartbeatAt)
|
|
481
|
+
lines.push(`heartbeat: ${state.lastHeartbeatAt}`);
|
|
482
|
+
if (state.lastSyncAt)
|
|
483
|
+
lines.push(`agent sync: ${state.lastSyncAt}`);
|
|
484
|
+
if (state.capabilities?.workspaces) {
|
|
485
|
+
lines.push(`workspaces: ${state.capabilities.workspaces.length ? state.capabilities.workspaces.join(", ") : "(none)"}`);
|
|
486
|
+
}
|
|
487
|
+
if (state.agents?.length) {
|
|
488
|
+
lines.push("agents:");
|
|
489
|
+
for (const agent of state.agents) {
|
|
490
|
+
lines.push(` - ${agent.id} ${agent.name} on ${agent.engine}${agent.lifecycle ? ` lifecycle=${agent.lifecycle}` : ""}${agent.status ? ` status=${agent.status}` : ""}${agent.model ? ` model=${agent.model}` : ""}${agent.workspaceRoot ? ` workspace=${agent.workspaceRoot}` : ""}`);
|
|
491
|
+
const usage = formatAgentRunStats(agent.runStats);
|
|
492
|
+
if (usage)
|
|
493
|
+
lines.push(` usage: ${usage}`);
|
|
494
|
+
const budget = formatTokenBudgetCheck(agent.tokenBudget);
|
|
495
|
+
if (budget)
|
|
496
|
+
lines.push(` token budget: ${budget}`);
|
|
497
|
+
if (agent.remediation) {
|
|
498
|
+
lines.push(` remediation: ${agent.remediation.summary}`);
|
|
499
|
+
for (const action of agent.remediation.actions)
|
|
500
|
+
lines.push(` - ${action}`);
|
|
501
|
+
}
|
|
502
|
+
for (const warning of agent.configWarnings ?? []) {
|
|
503
|
+
lines.push(` config warning: ${warning.code} - ${warning.summary}`);
|
|
504
|
+
}
|
|
505
|
+
if (agent.sharedSkillSnapshot) {
|
|
506
|
+
lines.push(` skill snapshot: ${agent.sharedSkillSnapshot.id} (${agent.sharedSkillSnapshot.skills.join(", ") || "no skills"}) ${agent.sharedSkillSnapshot.manifestPath}`);
|
|
507
|
+
}
|
|
508
|
+
for (const entry of agent.hostHomeEntries ?? []) {
|
|
509
|
+
lines.push(` host home entry: ${entry.name} -> ${entry.target || "(not linked)"}${entry.linked ? "" : ` (${entry.reason ?? "skipped"})`}`);
|
|
510
|
+
}
|
|
511
|
+
for (const plan of agent.worktreePlans ?? []) {
|
|
512
|
+
lines.push(` worktree plan: ${plan.repoName} -> ${plan.worktreePath} (${plan.branch})${plan.repoUrl ? ` from ${plan.repoUrl}` : ""}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
const events = formatRunningEventSummary(state, eventMax);
|
|
517
|
+
if (events)
|
|
518
|
+
lines.push(events);
|
|
519
|
+
return lines.join("\n");
|
|
520
|
+
}
|
|
521
|
+
export function formatWatchSnapshot(state, now = new Date()) {
|
|
522
|
+
const lines = [`king-ai watch ${now.toISOString()}`];
|
|
523
|
+
if (!state) {
|
|
524
|
+
lines.push("running: no running.json found; start the daemon with `king-ai agent computer`");
|
|
525
|
+
return lines.join("\n");
|
|
526
|
+
}
|
|
527
|
+
lines.push(`running: ${state.version} pid=${state.pid}`);
|
|
528
|
+
if (state.computerId || state.serverUrl)
|
|
529
|
+
lines.push(`paired: ${state.computerId ?? "unknown"} @ ${state.serverUrl ?? "unknown"}`);
|
|
530
|
+
const snapshot = formatRunningStateSnapshot(state, 8);
|
|
531
|
+
if (snapshot)
|
|
532
|
+
lines.push(snapshot);
|
|
533
|
+
return lines.join("\n");
|
|
534
|
+
}
|
|
535
|
+
export async function watchStatus(intervalMs = Number(process.env.KING_AI_WATCH_INTERVAL_MS) || 2000) {
|
|
536
|
+
const render = async () => {
|
|
537
|
+
process.stdout.write("\x1Bc");
|
|
538
|
+
console.log(formatWatchSnapshot(await readRunningState()));
|
|
539
|
+
console.log("\nPress Ctrl+C to stop.");
|
|
540
|
+
};
|
|
541
|
+
await render();
|
|
542
|
+
await new Promise((resolve) => {
|
|
543
|
+
const timer = setInterval(() => void render(), Math.max(250, intervalMs));
|
|
544
|
+
const stop = () => {
|
|
545
|
+
clearInterval(timer);
|
|
546
|
+
process.off("SIGINT", stop);
|
|
547
|
+
process.off("SIGTERM", stop);
|
|
548
|
+
resolve();
|
|
549
|
+
};
|
|
550
|
+
process.once("SIGINT", stop);
|
|
551
|
+
process.once("SIGTERM", stop);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
export function worktreePlansFromRunningState(state) {
|
|
555
|
+
const seen = new Set();
|
|
556
|
+
const plans = [];
|
|
557
|
+
for (const agent of state?.agents ?? []) {
|
|
558
|
+
for (const plan of agent.worktreePlans ?? []) {
|
|
559
|
+
const key = `${plan.repoRoot}\0${plan.worktreePath}\0${plan.branch}`;
|
|
560
|
+
if (seen.has(key))
|
|
561
|
+
continue;
|
|
562
|
+
seen.add(key);
|
|
563
|
+
plans.push(plan);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return plans;
|
|
567
|
+
}
|
|
568
|
+
export async function prepareWorktrees(options = {}) {
|
|
569
|
+
const plans = worktreePlansFromRunningState(await readRunningState());
|
|
570
|
+
console.log(formatWorktreePreparationResults(await prepareWorktreePlans(plans, { execute: options.execute }), Boolean(options.execute)));
|
|
571
|
+
}
|
|
572
|
+
export async function cleanupWorktrees(options = {}) {
|
|
573
|
+
const plans = worktreePlansFromRunningState(await readRunningState());
|
|
574
|
+
console.log(formatWorktreeCleanupResults(await cleanupWorktreePlans(plans, { execute: options.execute }), Boolean(options.execute)));
|
|
575
|
+
}
|
|
576
|
+
export async function rotateLogsIfNeeded(logPath = join(CONFIG_DIR, "daemon.log"), maxBytes = LOG_MAX_BYTES) {
|
|
577
|
+
try {
|
|
578
|
+
const st = await stat(logPath);
|
|
579
|
+
if (st.size <= maxBytes)
|
|
580
|
+
return;
|
|
581
|
+
await rm(`${logPath}.1`, { force: true });
|
|
582
|
+
await rename(logPath, `${logPath}.1`);
|
|
583
|
+
await writeFile(logPath, "", "utf8");
|
|
584
|
+
console.log(`daemon.log exceeded ${Math.round(maxBytes / 1048576)}MB; rotated to daemon.log.1`);
|
|
585
|
+
}
|
|
586
|
+
catch {
|
|
587
|
+
// No log yet.
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
export async function checkForUpdate(fetchImpl = fetch, commandName = "king-ai") {
|
|
591
|
+
try {
|
|
592
|
+
const res = await fetchImpl(updateRegistryUrl(commandName), {
|
|
593
|
+
headers: { Accept: "application/json" }
|
|
594
|
+
});
|
|
595
|
+
if (!res.ok)
|
|
596
|
+
return null;
|
|
597
|
+
const body = (await res.json());
|
|
598
|
+
return typeof body.version === "string" && versionGt(body.version, CURRENT_VERSION) ? body.version : null;
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
export async function tailLogs(commandName = "king-ai") {
|
|
605
|
+
const names = serviceNames(commandName);
|
|
606
|
+
const state = await readRunningState();
|
|
607
|
+
const recent = formatRunningEventSummary(state, 3) || formatRecentRunningEvents(state);
|
|
608
|
+
if (recent)
|
|
609
|
+
console.log(`${recent}\n`);
|
|
610
|
+
if (process.platform === "linux") {
|
|
611
|
+
await new Promise((resolve) => {
|
|
612
|
+
const child = spawn("journalctl", ["--user", "-u", names.serviceUnit, "-n", "100", "-f"], { stdio: "inherit" });
|
|
613
|
+
child.on("close", () => resolve());
|
|
614
|
+
child.on("error", () => resolve());
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const logPath = daemonLogPath();
|
|
619
|
+
if (!existsSync(logPath)) {
|
|
620
|
+
console.log(`no log at ${logPath} yet; is the service installed and running?`);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
await new Promise((resolve) => {
|
|
624
|
+
const child = spawn("tail", ["-n", "100", "-f", logPath], { stdio: "inherit" });
|
|
625
|
+
child.on("close", () => resolve());
|
|
626
|
+
child.on("error", () => resolve());
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function windowsCmdQuote(value) {
|
|
630
|
+
return `"${value.replace(/"/g, "\"\"")}"`;
|
|
631
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface SharedSkill {
|
|
2
|
+
name: string;
|
|
3
|
+
sourceDir: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SharedSkillInstallResult {
|
|
6
|
+
sourceRoots: string[];
|
|
7
|
+
installed: SharedSkill[];
|
|
8
|
+
targets: string[];
|
|
9
|
+
snapshot?: SharedSkillSnapshot;
|
|
10
|
+
}
|
|
11
|
+
export interface SharedSkillSnapshot {
|
|
12
|
+
id: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
root: string;
|
|
15
|
+
manifestPath: string;
|
|
16
|
+
skills: SharedSkillSnapshotSkill[];
|
|
17
|
+
}
|
|
18
|
+
export interface SharedSkillSnapshotSkill {
|
|
19
|
+
name: string;
|
|
20
|
+
sourceDir: string;
|
|
21
|
+
snapshotDir: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function sharedSkillRoots(env?: NodeJS.ProcessEnv): string[];
|
|
24
|
+
export declare function sharedSkillSnapshotsRoot(env?: NodeJS.ProcessEnv): string | undefined;
|
|
25
|
+
export declare function listSharedSkills(sourceRoots: string[]): Promise<SharedSkill[]>;
|
|
26
|
+
export declare function installSharedSkills(agentHome: string, sourceRoots?: string[], env?: NodeJS.ProcessEnv): Promise<SharedSkillInstallResult>;
|