@netnodeag/kraftwerk 0.15.0 → 0.16.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/inspector/chat/sessions.d.ts +2 -0
- package/dist/inspector/chat/sessions.js +33 -3
- package/dist/inspector/server.js +10 -1
- package/inspector/dist/assets/index-Bo7R5E73.js +80 -0
- package/inspector/dist/assets/index-CSUBZW5F.css +1 -0
- package/inspector/dist/index.html +2 -2
- package/package.json +1 -1
- package/inspector/dist/assets/index-DZMZvmSf.js +0 -80
- package/inspector/dist/assets/index-lrjqm5-N.css +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ChatAgentId, ChatMeta, ChatScope, StoredChatEvent } from "./types.js";
|
|
2
|
+
/** Server shutdown: no agent subprocess may outlive the inspector. */
|
|
3
|
+
export declare function disposeAllBackends(): void;
|
|
2
4
|
export declare function createChat(opts: {
|
|
3
5
|
agent: ChatAgentId;
|
|
4
6
|
scope: ChatScope;
|
|
@@ -10,6 +10,32 @@ import { startAcpBackend } from "./acp.js";
|
|
|
10
10
|
import { startPiBackend } from "./pi.js";
|
|
11
11
|
import { appendEvent, listChatMetas, newChatId, readEvents, readMeta, safeChatDir, writeMeta, } from "./store.js";
|
|
12
12
|
const states = new Map();
|
|
13
|
+
/** Kill a chat's agent subprocess; the next message spawns a fresh one. */
|
|
14
|
+
function dropBackend(state) {
|
|
15
|
+
state.backend?.dispose();
|
|
16
|
+
state.backend = null;
|
|
17
|
+
}
|
|
18
|
+
// Idle reaper: a finished chat must not keep its agent subprocess alive
|
|
19
|
+
// forever (one ACP adapter + one harness binary per chat adds up fast —
|
|
20
|
+
// hourly routines used to leak a process pair per tick). History is on
|
|
21
|
+
// disk and ensureBackend re-sends scope context, so reaping just costs
|
|
22
|
+
// the next message one respawn. busy=true covers the whole turn including
|
|
23
|
+
// pending permission prompts, so nothing is killed mid-work.
|
|
24
|
+
const IDLE_BACKEND_MS = 15 * 60_000;
|
|
25
|
+
const reaper = setInterval(() => {
|
|
26
|
+
const cutoff = Date.now() - IDLE_BACKEND_MS;
|
|
27
|
+
for (const state of states.values()) {
|
|
28
|
+
if (state.backend && !state.busy && Date.parse(state.meta.updatedAt) < cutoff) {
|
|
29
|
+
dropBackend(state);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}, 60_000);
|
|
33
|
+
reaper.unref?.();
|
|
34
|
+
/** Server shutdown: no agent subprocess may outlive the inspector. */
|
|
35
|
+
export function disposeAllBackends() {
|
|
36
|
+
for (const state of states.values())
|
|
37
|
+
dropBackend(state);
|
|
38
|
+
}
|
|
13
39
|
async function loadState(id) {
|
|
14
40
|
const existing = states.get(id);
|
|
15
41
|
if (existing)
|
|
@@ -387,13 +413,17 @@ export async function postMessage(id, text) {
|
|
|
387
413
|
const promptText = context ? `<context>\n${context}\n</context>\n\n${body}` : body;
|
|
388
414
|
const stopReason = await backend.prompt(promptText);
|
|
389
415
|
emit(state, { type: "turn_end", stopReason });
|
|
416
|
+
// Routine sessions are one-shot and unattended: release the agent
|
|
417
|
+
// process as soon as the turn ends instead of waiting for the reaper.
|
|
418
|
+
const scope = state.meta.scope;
|
|
419
|
+
if (scope.kind === "team" && scope.routine)
|
|
420
|
+
dropBackend(state);
|
|
390
421
|
}
|
|
391
422
|
catch (err) {
|
|
392
423
|
emit(state, { type: "error", message: err.message });
|
|
393
424
|
// A failed turn may mean a dead subprocess; drop it so the next
|
|
394
425
|
// message spawns a fresh agent (thread history stays on disk).
|
|
395
|
-
state
|
|
396
|
-
state.backend = null;
|
|
426
|
+
dropBackend(state);
|
|
397
427
|
}
|
|
398
428
|
finally {
|
|
399
429
|
state.busy = false;
|
|
@@ -419,7 +449,7 @@ export async function deleteChat(id) {
|
|
|
419
449
|
return { error: "not found" };
|
|
420
450
|
for (const resolve of state.pendingPermissions.values())
|
|
421
451
|
resolve(null);
|
|
422
|
-
state
|
|
452
|
+
dropBackend(state);
|
|
423
453
|
states.delete(id);
|
|
424
454
|
await fs.rm(safeChatDir(id), { recursive: true, force: true });
|
|
425
455
|
return {};
|
package/dist/inspector/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { resolveProject } from "../config.js";
|
|
|
6
6
|
import { listRuns, getRun, readRunFile } from "./runs.js";
|
|
7
7
|
import { listWorkflows, getWorkflow } from "./workflows.js";
|
|
8
8
|
import { dockerStatus, triggerRun, stopRun } from "./runner.js";
|
|
9
|
-
import { cancelChat, createChat, deleteChat, getChat, listChats, postMessage, resolvePermission, subscribeChat, } from "./chat/sessions.js";
|
|
9
|
+
import { cancelChat, createChat, deleteChat, disposeAllBackends, getChat, listChats, postMessage, resolvePermission, subscribeChat, } from "./chat/sessions.js";
|
|
10
10
|
import { bundleDetail, conceptDetail, createBundle, knowledgeIndex, putConcept, verifyFromUi, } from "./knowledge.js";
|
|
11
11
|
import { deleteMember, getMember, listMembers, saveMember, teamRoot, } from "./team.js";
|
|
12
12
|
import { getSkill, listSkills, skillsRoot } from "./skills.js";
|
|
@@ -488,6 +488,15 @@ export function startInspector(opts) {
|
|
|
488
488
|
if (opts.projectRoot)
|
|
489
489
|
setProjectRoot(opts.projectRoot);
|
|
490
490
|
startRoutineScheduler();
|
|
491
|
+
// Chat agent subprocesses must die with the server — signals bypass
|
|
492
|
+
// "exit" handlers, so hook the signals themselves.
|
|
493
|
+
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
494
|
+
process.once(sig, () => {
|
|
495
|
+
disposeAllBackends();
|
|
496
|
+
process.exit(sig === "SIGINT" ? 130 : 143);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
process.once("exit", disposeAllBackends);
|
|
491
500
|
const server = http.createServer(async (req, res) => {
|
|
492
501
|
try {
|
|
493
502
|
const url = new URL(req.url ?? "/", "http://localhost");
|